Efficient Data Extraction in Blind SQLi using Bitwise Logic
Table of Contents
What if you could extract a character from a database in 8 requests instead of 95?
Once you see it, the naive approach never quite looks the same again.
Background: What Is Blind SQL Injection? #
Most SQL injection tutorials focus on attacks that dump data directly. But real world apps often don’t display query results. They just say “found” or “not found.”
This is called blind injection. You can’t see the data, but you can ask yes/no questions:
“Does the first character of this token equal
a?”
If the app responds differently based on the answer, you can recover the entire value, one character at a time, like a patient detective who is only allowed to ask questions with boolean answers.
The Naive Way: Sequential Guessing #
Imagine you’re targeting this query:
SELECT * FROM `Users` WHERE username LIKE '[INPUT]%';You inject a UNION payload that checks one character of a secret token:
'' UNION SELECT id, token, null, null, null, null, null, null
FROM `AuthTokens`
WHERE id = 1
AND SUBSTR(token, [POSITION], 1) = '[CHAR]'; -- You loop through printable ASCII characters (! to ~, roughly 95 values) for each position until the app says “found.”
The worst case for a 32-character token:
$$32 \times 95 = 3{,}040 \text{ requests}$$That’s slow and noisy. Every failed guess before the right character is a wasted round trip. You could parallelize across positions (run 32 threads, one per character slot), but each thread still grinds through up to 95 sequential guesses. Parallelism cuts wall clock time; it does nothing about the total request count.
A Smarter Approach: Binary Search #
Instead of stepping through candidates one by one, binary search narrows the ASCII value by comparing ranges:
“Is the ASCII value greater than 64? Greater than 96? Greater than 112?”
This collapses the attempts per character from up to 95 down to exactly \(\lceil \log_2 256 \rceil = 8\) when searching the full byte range \([0,\, 255]\).
But here’s the catch: binary search is inherently sequential. Each question depends on the previous answer. You cannot ask “is the value greater than 112?” until you know the answer to “is it greater than 96?” No amount of concurrency untangles that chain of dependencies.
The Real Insight: Go Bitwise #
Here is where things get genuinely exciting.
Every ASCII character is a number from 0 to 255, which fits in exactly 8 bits. And here’s what that means: instead of asking about the character as a whole, you can ask about each bit independently.
“Is bit 0 of this character a 1?” “Is bit 1 of this character a 1?” …and so on.
The SQL payload looks like this:
'' UNION SELECT id, token, null, null, null, null, null, null
FROM `AuthTokens`
WHERE id = 1
AND ASCII(SUBSTR(token, [POSITION], 1)) & [BIT_VALUE] = [BIT_VALUE]; -- Here, [BIT_VALUE] cycles through \(2^0,\, 2^1,\, \ldots,\, 2^7\), that is \(1, 2, 4, 8, 16, 32, 64, 128\), one power of two for each bit position.
The key operation is bitwise AND: for a character with value \(c\) and a mask \(m = 2^k\),
$$c \mathbin{\&} m \neq 0 \iff \text{bit } k \text{ of } c \text{ is set to 1}$$Each request tells you the state of exactly one bit, completely independent of all the others.
Example: Recovering the Character b
#
b has ASCII value 98, which in binary is \(98_{10} = \texttt{01100010}_{2}\).
| Bit \(k\) | Mask \(2^k\) | \(98 \mathbin{\&} 2^k\) | Bit set? |
|---|---|---|---|
| 0 | 1 | 0 | 0 |
| 1 | 2 | 2 | 1 |
| 2 | 4 | 0 | 0 |
| 3 | 8 | 0 | 0 |
| 4 | 16 | 0 | 0 |
| 5 | 32 | 32 | 1 |
| 6 | 64 | 64 | 1 |
| 7 | 128 | 0 | 0 |
Read the “Bit set?” column from row 7 down to row 0: \(\texttt{01100010}_{2} = 98_{10} =\) b.
Exactly 8 requests per character. Every single time.
Why 8 Is the Minimum (And Why That’s Beautiful) #
Let me take a small detour into information theory, because this is where the real magic lives.
A byte holds one of 256 equally possible values. In Shannon’s framework, the information content of a uniformly random byte is:
$$H = \log_2(256) = 8 \text{ bits}$$Each yes/no question can reveal at most 1 bit of information (assuming you ask the perfect question that splits remaining possibilities evenly). So you need at least 8 questions to uniquely identify any byte. This is not a quirk of SQL or any particular algorithm. It is a hard lower bound from information theory. No technique, however clever its questions, can do it in fewer.
Both binary search and the bitwise approach reach this floor of 8. What separates them is not the count but the structure of the questions.
Binary search forms a chain of dependencies. “Is the value greater than 128?” tells you which half to search next, but only after the answer arrives. Each question gates the next. You are stuck waiting.
Bitwise shatters that chain. Whether bit 3 is set has absolutely no bearing on bit 5. The questions are fully independent. There is no waiting, no branching, no sequencing required. You fire all 8 at the exact same moment.
That’s the real revelation: bitwise doesn’t just reach the information theoretic minimum. It reaches it in the only way that allows true parallelism.
Why This Beats Binary Search #
Both approaches need exactly 8 requests per character. The difference is whether those 8 requests can run at the same time.
Binary search is synchronous. Step \(N\) depends on the result of step \(N - 1\). You wait, check, decide, then advance. Eight questions become eight sequential round trips with no way out.
Bitwise is embarrassingly parallel. All 8 bit checks are independent. Fire them all at once, and collect the results when they come back.
Binary search: →→→→→→→→ (8 sequential steps, blocked at each)
Bitwise: ↓↓↓↓↓↓↓↓ (8 parallel requests, no blocking)
←←←←←←←← (reconstruct character from results)For a 32-character token, you can dispatch all
$$8 \text{ bits} \times 32 \text{ positions} = 256 \text{ requests}$$at the same moment and reconstruct every character from a single parallel batch. Wall clock time collapses to roughly one round trip latency instead of hundreds of sequential ones. It feels like a different class of attack entirely, because it genuinely is.
Takeaway #
| Method | Requests per character | Parallelizable |
|---|---|---|
| Sequential guess | up to 95 | Per position only |
| Binary search | 8 | No |
| Bitwise | 8 | Yes, fully |
Blind injection doesn’t have to be slow. When the only feedback is true or false, the fastest path is to stop asking about characters and start asking about bits.
The Bigger Picture #
SQL injection is just one instance of a broader class of problems.
Any system where you can ask yes/no questions about a secret value is called a boolean oracle. The bitwise technique is the optimal answer to the boolean oracle problem, not just in request count but in structure. It simultaneously hits the information theoretic minimum of \(\lceil \log_2 n \rceil\) questions (where \(n\) is the number of possible values) and makes every question fully independent, which is the only combination that unlocks parallelism.
SQL is the example here, not the boundary. Any channel that leaks a single bit per query, whether through timing differences, distinct error codes, or observable cache behavior, admits the same treatment.
Stop guessing at the whole value. Ask about its bits.
Happy discovering!
There are no articles to list here yet.