SQL injection has been on the OWASP Top 10 list since the list was first published in 2003. It has caused some of the most damaging breaches in history — the 2009 Heartland Payment Systems breach (130 million card numbers), the 2011 Sony Pictures breach (77 million accounts), and hundreds of smaller incidents every year.
Despite being fully understood and entirely preventable, SQLi vulnerabilities continue to appear in new codebases, inherited applications, and third-party components in 2026.
This tutorial covers SQL injection from first principles through to advanced blind exploitation. You will learn how each injection type works at the SQL level, how to find injection points systematically with Burp Suite, how to extract data manually using UNION and error-based techniques, how to exploit blind SQLi when there is no visible output, how to automate with sqlmap, and how to bypass common filters. Every payload in this article is plain text — safe to read without executing.
- What is SQL injection and how does it work?
- The four types of SQL injection
- How to find SQL injection vulnerabilities
- Error-based SQL injection
- UNION-based SQL injection — dumping data
- Blind SQL injection — boolean-based
- Blind SQL injection — time-based
- Automating with sqlmap
- Second-order SQL injection
- Filter and WAF bypass techniques
- Complete SQL injection cheatsheet
- Defences — how SQLi is properly prevented
- Practice on DVWA — step by step
- Frequently asked questions
SQL injection occurs when user-supplied data is incorporated into a database query without proper separation of code and data, allowing an attacker to alter the query's logic. The database cannot distinguish between the developer's intended SQL and the attacker's injected SQL — it executes both as trusted instructions.
The root cause is always the same: the application builds SQL queries by concatenating strings that include user input, rather than using parameterised queries that keep data and code structurally separate.
The attacker injects 1 OR 1=1-- - as the id parameter. The developer's query template is:
After substituting the attacker's input, the database receives:
The WHERE clause now has two conditions joined by OR. Because 1=1 is always true, every row in the table satisfies the condition. The -- - is a SQL comment that discards the rest of the query (any closing quote, bracket, or additional condition). The database returns every row — all users instead of one.
| Type | How you see the result | Difficulty | Speed | When to use |
|---|---|---|---|---|
| Error-based | Database error messages appear in the response | Easy | Fast | When verbose errors are enabled — extracts data via error messages |
| UNION-based | Injected query results appear in the normal response | Medium | Fast | When results are displayed on page — most efficient for data dumping |
| Boolean blind | True/false change in response (different content, status code, or length) | Medium | Slow | When no data shown but behaviour changes — extract data bit by bit |
| Time-based blind | No visible difference — only response time changes | Hard | Very slow | When absolutely no output difference — last resort, uses delays |
Not all inputs are equal. Focus on inputs most likely to be used in SQL queries:
- URL parameters — ?id=1, ?category=shoes, ?user=alice
- Form fields — search boxes, login forms, registration forms, filter dropdowns
- HTTP headers — User-Agent, Referer, X-Forwarded-For (some apps log these to a database)
- JSON/API request bodies — especially {"id": 1} or {"filter": "price"}
- Cookies — session values, user preferences, remember-me tokens
- Path parameters — /users/alice/profile (the username may be used in a query)
These are the minimal detection strings — they cause SQL syntax errors or behavioural changes without requiring a specific database type:
- SQL error message → confirmed SQLi. Error reveals database type and query structure.
- Application error (500) → possible SQLi — something changed in the query.
- Different content / fewer results → Boolean blind SQLi likely.
- Delayed response → Time-based blind SQLi (add sleep payloads to confirm).
- No change at all → Either no SQLi, or input is sanitised, or not used in a query.
Error-based SQLi exploits verbose database error messages that reveal information about the query structure and database contents. When a malformed query causes a database error, the error message itself becomes a data exfiltration channel.
UNION-based injection appends a second SELECT query to the original, merging its results with the legitimate results and displaying them on the page. This is the most efficient method for data extraction when query results are rendered in the response.
The requirement: your injected SELECT must have the same number of columns as the original query, and the data types must be compatible. Finding the column count is always the first step.
Boolean blind SQLi is used when the application is vulnerable but shows no database output or errors in the response. Instead, the response changes based on whether the injected condition is TRUE or FALSE — different content, a different status code, or even just a different response length. You extract data one bit at a time by asking the database yes/no questions.
Time-based blind SQLi is the last resort — used when there is absolutely no visible difference in responses between true and false conditions. The only channel is response time: injecting a conditional sleep causes the server to delay its response by a known amount if the condition is true, and respond immediately if false.
sqlmap is the industry-standard open-source tool for automated SQL injection detection and exploitation. It automatically identifies injection points, determines the database type, selects the most efficient injection technique, and extracts data — compressing hours of manual work into minutes.
Second-order (or stored) SQLi occurs when user input is safely stored in the database at the point of insertion, but is later retrieved and used in a SQL query without re-sanitisation. The injection payload "sleeps" in the database and triggers when it is used in a subsequent operation.
Classic example: A user registers with username admin'-- -. The registration page safely parameterises the INSERT. But the password-change page does:
- Register a username containing SQL meta-characters: admin'-- -, test" OR "1"="1
- Log in with the registered account
- Trigger all actions that use your username — password change, profile update, account deletion, email notification, order placement
- Look for unexpected errors, unexpected behaviour, or evidence that another user's data was affected
- Second-order SQLi requires understanding the application's full data flow, not just individual parameters
Parameterised queries separate SQL code from data structurally at the driver level. No matter what the user inputs, it is treated as data — it can never alter the query logic.
Stored procedures can be safe if they use parameterised inputs internally, but they do not automatically prevent SQLi if they concatenate strings inside the procedure body. Verify the stored procedure implementation.
For parameters that accept a limited set of values (sort direction, column names, category IDs), use allowlisting — only permit known-safe values. This is secondary to parameterised queries, not a replacement.
The database account used by the web application should have only the permissions it needs — typically SELECT, INSERT, UPDATE, and DELETE on specific tables. It should not have FILE privilege, DROP privilege, or access to other databases. This limits what an attacker can do after finding SQLi.
Access DVWA at http://192.168.56.102/dvwa. Set up your home lab first using our home hacking lab guide. Log in (admin / password) and navigate to SQL Injection.
⚡ Continue building your SQLi skills
- Practice on DVWA now — complete the SQL Injection and SQL Injection (Blind) modules at Low and Medium difficulty. Your home lab has DVWA ready. Home lab setup guide →
- PortSwigger Web Security Academy SQL injection labs — 20+ free browser-based labs taking you from basic UNION injection to complex blind exploitation. The gold standard for building this skill properly. portswigger.net/web-security/sql-injection
- Master Burp Suite for SQLi — Repeater for manual payload testing, Intruder for automated parameter fuzzing, and the built-in scanner for detection. Burp Suite guide →
- Hunt SQLi on bug bounty programmes — SQLi typically pays $500–$10,000+ depending on data exposure. Search for programmes with large legacy codebases. Bug bounty guide →
- Learn the complementary web vulnerabilities — SQLi and XSS together cover a large portion of web security.
- Understand real-world SQLi CVEs — several high-profile CVEs are SQL injection vulnerabilities. 10 real-world CVEs explained →
SQL injection occurs when user input is incorporated into a database query without proper separation, allowing the attacker to alter the query's logic. It remains common in 2026 because: legacy codebases written before modern ORM frameworks are still in production, third-party components frequently contain SQLi vulnerabilities, developers using raw SQL queries sometimes forget parameterisation, and automated scanners often miss complex or non-standard injection points. It continues to appear in the OWASP Top 10 (under Injection) because it is genuinely still found in production systems worldwide.
Error-based SQLi extracts data through database error messages — fast but requires verbose error display. UNION-based SQLi appends a second SELECT to merge attacker data with legitimate results displayed on page — the most efficient method when output is shown. Boolean blind SQLi has no visible output but the response changes based on true/false conditions — extract data by asking yes/no questions. Time-based blind SQLi uses response delays (SLEEP) as the only channel — last resort when nothing else works.
Parameterised queries (prepared statements) — they separate SQL code from data at the database driver level, making it structurally impossible for user input to alter query logic regardless of what characters it contains. Input filtering, WAF rules, and escaping functions are secondary mitigations that reduce risk but are not complete fixes — they have bypass techniques and edge cases. Parameterised queries have no known bypass when implemented correctly.
Check the programme's rules first — some programmes explicitly prohibit automated scanning tools including sqlmap. When permitted, use sqlmap conservatively: use --level=1 --risk=1 (the defaults) rather than aggressive settings, avoid --os-shell and file read/write operations unless you have explicit permission, and test one parameter at a time. The --technique=B flag (boolean blind only) is the safest mode as it makes no data-modifying requests. Always use --batch to avoid interactive prompts that might take unexpected actions.
In an authorised penetration test, document the hashes as evidence of the SQLi vulnerability and optionally attempt to crack them to demonstrate password weakness — use crackstation.net (online lookup), hashcat (offline GPU cracking), or john the ripper. In a bug bounty report, you do not need to crack the hashes — showing you can extract them is sufficient to demonstrate the vulnerability's severity. Never crack, share, or use credentials extracted from systems without the owner's explicit written permission.
Modern web apps often use JSON APIs rather than traditional URL parameters. Proxy the API traffic through Burp Suite and look for numeric or string values in JSON bodies that might be used in database queries. Test by modifying these values with SQLi payloads — adding a single quote to a string value or appending AND 1=1 to a numeric value. Use Burp's Content-Type: application/json header and --data flag in sqlmap. ORM frameworks (SQLAlchemy, Hibernate, ActiveRecord) usually prevent SQLi by default, but raw() or execute() methods bypass ORM protections — look for these in code reviews.
Second-order (stored) SQLi occurs when malicious input is safely inserted into the database but later retrieved and used in another query without re-sanitisation. The injection payload "sleeps" in the database and triggers during a subsequent operation. It differs from first-order SQLi in that the injection and the effect happen at different points in the application flow — often discovered by registering an account with a SQLi payload in the username, then triggering password change or profile update functionality. Basic automated scanners miss it because they test each request in isolation.