Efficient Data Extraction in Blind SQLi using Bitwise Logic
Table of Contents
What if you could extract a character from a database in exactly 8 requests instead of up to 95?
Once you look at blind SQLi this way, the naive approach just feels broken.
The Usual Suspect: Sequential Guessing #
If you’ve played with blind SQLi, you know the drill. The app doesn’t dump the table to the screen, it just gives you a boolean signal—like a page returning a 200 vs 404, or taking 5 seconds to load instead of 0.5.
You inject a payload checking 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]'; -- Then you write a script to loop through printable ASCII characters (! to ~, about 95 values) until you get a hit.
For a 32-character token, the worst-case scenario is gross:
$$32 \times 95 = 3{,}040 \text{ requests}$$That’s noisy and painfully slow. You can run 32 threads (one for each character position), but every single thread is still grinding through up to 95 sequential guesses.
Stepping Up: Binary Search #
Instead of dumb-guessing one by one, you can binary search the ASCII value:
“Is the ASCII value > 64? > 96? > 112?”
This crushes the attempts per character from up to 95 down to exactly \(\lceil \log_2 256 \rceil = 8\) for a full byte.
But here’s the annoying part: binary search is strictly sequential. You can’t ask if it’s > 112 until you know if it’s > 96. You’re blocked on every single request.
The Real Trick: Going Bitwise #
Here’s the fun part. Every ASCII character is just an 8-bit number. Instead of asking about the character’s overall value, just ask about its individual bits.
“Is bit 0 a 1?” “Is bit 1 a 1?”
The 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]; -- [BIT_VALUE] cycles through \(2^0,\, 2^1,\, \ldots,\, 2^7\) (1, 2, 4, 8, 16, 32, 64, 128).
Using bitwise AND (\(\mathbin{\&}\)):
$$c \mathbin{\&} m \neq 0 \iff \text{bit } k \text{ of } c \text{ is 1}$$Each request checks one bit, entirely independent of the others.
Quick Example: Finding b
#
b is ASCII 98 (\(\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 from row 7 to 0: \(\texttt{01100010}_{2} = 98_{10} =\) b.
Exactly 8 requests per character. Every time.
Why Bitwise Beats Binary Search #
A byte has 256 possible values. Information theory tells us the information content of a random byte is exactly 8 bits. A true/false question gives you 1 bit of information. So, you mathematically must ask at least 8 questions to identify a byte.
Both binary search and bitwise hit this 8-request minimum.
But binary search is a dependency chain. You wait for the answer to know what to ask next.
Bitwise shatters that chain. Checking bit 3 has absolutely nothing to do with bit 5.
Binary search is synchronous. Bitwise is embarrassingly parallel.
Binary search: →→→→→→→→ (blocked at each step)
Bitwise: ↓↓↓↓↓↓↓↓ (fire all at once)
←←←←←←←← (reconstruct character)For a 32-character token, you fire off:
$$8 \text{ bits} \times 32 \text{ positions} = 256 \text{ requests}$$All at the same time. You collect the responses and reconstruct the entire token from one massive parallel batch. It reduces the wall clock time to practically a single round trip.
Wrapping Up #
If all you get is true or false, stop guessing characters and start checking bits.
This isn’t just a neat SQLi trick, either. Anytime you’re dealing with a boolean oracle—like exploiting a timing leak or cache behavior—you can drop this same logic in. Hit the theoretical minimum request count, parallelize everything, and go grab a coffee.
Happy hacking!
There are no articles to list here yet.