iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Blind SQLi

Blind SQL injection extracts data when the app shows no SQL errors and no obvious data. Attackers infer values one bit at a time via boolean or time delays. The same fixes as classic SQLi: parameterise, allowlist, least privilege.

Boolean-based, time-based, defences

EXAMPLE
# Boolean-based blind SQLi — attacker observes app behaviour for true/false

# Vulnerable code (Node, pg) — never write this
const sql = `SELECT id FROM users WHERE username = '${req.query.user}'`;
const { rows } = await db.query(sql);
if (rows.length) return res.send('user exists');
else return res.status(404).end();

# Attacker probes:
#   ?user=ada' AND SUBSTRING(password, 1, 1) = 'a' --      → 200 if first char is 'a'
#   ?user=ada' AND SUBSTRING(password, 1, 1) = 'b' --      → 404
# Binary-search through every character. Hundreds of requests; full hash extracted.

# Defence — parameterised + scoped query (impossible to inject)
const { rows } = await db.query(
    'SELECT id FROM users WHERE username = $1',
    [req.query.user],
);

# === Time-based blind SQLi ===
# When the app doesn't even leak success/failure in HTTP status — attacker uses
# database SLEEP to infer truth via response latency.

# Vulnerable:
const sql = `SELECT count(*) FROM products WHERE category = '${req.query.cat}'`;

# Attacker probes:
#   ?cat=x' OR (SELECT pg_sleep(5) FROM users WHERE id=1 AND substr(password,1,1)='a') IS NULL --
# If the page takes 5s, the condition was true.

# Server-side detections you should have:
#   - WAF rule: queries with sleep/pg_sleep/benchmark in URLs
#   - DB statement timeout (Postgres: SET statement_timeout = '5s')
#   - Per-IP rate limit on endpoints that hit the DB
#   - Anomaly monitor on query latency

# === Out-of-band exfiltration ===
# When neither boolean nor time channel is available, attackers may use DNS / HTTP from inside the DB:
#   xp_dirtree (MSSQL), UTL_HTTP (Oracle), copy ... program (PG with superuser)
# Defences:
#   - DB user has ZERO network egress permissions (no superuser, no copy program)
#   - Egress firewall — DB host can't reach the internet

# === Real defence stack ===

# 1) Parameterise — every query, every driver
await db.query('SELECT * FROM users WHERE username = $1 AND active = true', [user]);

# 2) Allowlist identifiers (table / column / sort)
const SORT = { id: 'id', date: 'created_at', total: 'total' };
const sort = SORT[req.query.sort] ?? 'created_at';
await db.query(`SELECT * FROM orders ORDER BY ${sort} DESC LIMIT 50`);

# 3) Least privilege
# - App user can SELECT/INSERT/UPDATE/DELETE only the schemas it needs
# - No CREATE TABLE, DROP, GRANT, COPY FROM PROGRAM
# - Use connection pooling (PgBouncer) — limits blast radius if creds leak

# 4) Statement timeout
# Postgres: SET statement_timeout = '5s';
# MySQL:    SET SESSION MAX_EXECUTION_TIME=5000;  (ms)

# 5) Generic 404s — don't leak existence
# Consistent 404 whether the row exists or not (after auth fails) makes blind boolean SQLi require more probes.

# 6) Logging + alerting
# - Log every query with parameters; alert on outliers (unusual table access, abnormal duration)
# - SIEM rule: many 4xx from same IP, or many requests with sleep/benchmark patterns
# - DB audit: pg_audit / Datadog DB monitoring

# 7) Web Application Firewall (WAF)
# - Cloudflare / AWS WAF / Imperva — generic SQLi patterns blocked at the edge
# - NOT a substitute for parameterisation; defence-in-depth
# - Pair with bot detection (Cloudflare Turnstile, hCaptcha) for high-value endpoints

# 8) ORMs reduce surface area
# Type-safe query builders make string-built SQL the rare exception.
# Lint rules ban raw query methods ($queryRawUnsafe, FromSqlRaw, find_by_sql).

# 9) Pre-prod testing
# - sqlmap, dotdotpwn — automated SQLi scanners (use ONLY against authorised targets)
# - Burp Suite extensions: Backslash Powered Scanner, SQLiPy
# - DAST in CI: OWASP ZAP, Nuclei templates for SQLi

# 10) Code review checklist
# Look for: string concatenation in queries, template literals with user data, raw query methods
# In review: 'Is every user-controlled value a placeholder?' If no → reject.

# 11) Defence-in-depth wins
# Even with parameterised queries everywhere:
# - WAF stops dumb scanners (saves logs + CPU)
# - Least-privilege limits damage if a NEW bug ships
# - Statement timeout limits time-based exfiltration window
# - Egress firewall stops out-of-band exfiltration
# - Audit logs let you reconstruct an incident

# 12) Audit your existing code
rg -t js -e 'query\(\s*`' -e 'query\(\s*"' src/
rg -t py -e 'execute\([^,)]+%' -e 'f"SELECT' app/
# Find candidates; verify each is either safe (parameterised) or fix it.

Why it matters

Blind SQLi is invisible without good logging. Layer parameterisation (kills the bug) + statement timeouts (limits time-based) + rate limits + WAF (frustrates scanners) + audit logs (post-incident forensics).

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
// No data in the response — attacker infers by status / content / timing.
// Same defence: parameterise. Hide raw DB errors from users.
Try it Yourself »

Discussion

Loading…