Skip to main content

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.