SQL Injection
A complete guide to understanding, detecting, exploiting, and preventing SQL Injection vulnerabilities.
Introduction
SQL INJECTION (SQLi)
A complete ANAS EDUCATION course on the most famous vulnerability in web security.
- ●CWE-89
- ●OWASP A03:2021 (Injection)
- ●CVSS: typically 7.5 to 10.0
- ●Status: still alive and well in 2026
SECTION 1. Introduction
Imagine you open your PC and visit `anastech.com`.
You go to the product search bar at the top of the page. You type the word `shoes`. You press Enter.
In the next half-second, four things happen behind the scenes.
- ●Your browser sends an HTTP request to the server with your search term.
- ●The server reads your search term out of the URL.
- ●The server builds a database query that includes your search term.
- ●The server runs that query and sends back the matching rows as an HTML page.
The HTTP request your browser sent looks like this:
Inside the server, the PHP code looks something like this:
The query that the database actually runs looks like this:
The database scans the `products` table, finds every row whose `name` column contains the letters `shoes`, and returns them. The server formats them into HTML and sends the page back. You see a list of shoes. Normal, expected, safe.
Now look at the same picture again, but with a question on top of it.
- ●What if you did not type `shoes`?
- ●What if you typed: `shoes' OR '1'='1`?
The server runs:
`'1'='1'` is a condition that is always true. The `OR` makes the entire `WHERE` clause true for every row. The database now returns every single product in the `products` table, including ones that are hidden, unreleased, internal-only, or marked as draft.
That is the simplest possible SQL injection.
The bug is that your input was not just data. Your input was added directly into the SQL text. The database has no way to tell what came from the developer and what came from you. It just runs the string it was given.
This course teaches the SQL injection bug class from zero. By the end you will know:
- ●What a SQL query really is and how the server builds one.
- ●Why string concatenation is the original sin of database programming.
- ●Six different families of SQL injection (in-band, error, union, blind boolean, blind time, out-of-band).
- ●Thirty different exploitation techniques.
- ●How to detect SQLi manually, with Burp, and with sqlmap.
- ●How to bypass WAFs, encoding filters, and keyword blacklists.
- ●How to write secure code in Python, PHP, Node.js, Java, and C#.
- ●How to read a real CVE and reproduce it.
You do not need to be an expert. You just need to read carefully and try every payload yourself.
SECTION 2. How It Works
To find SQL injection bugs, you need to picture the journey of a single string from the browser to the database.
Step 1. The browser collects the input
You type something into a search box, a login form, a URL parameter, a header, a cookie, an XML body, a JSON body, anything. Your browser packages it into an HTTP request:
The string `shoes` is now sitting in the query string of the URL.
Step 2. The server reads the input
The server has code that reads the value of `q` out of the URL. In PHP it is `$_GET['q']`. In Node.js it is `req.query.q`. In Python Flask it is `request.args.get('q')`. In Java it is `request.getParameter("q")`. The names change, the idea is the same: the framework hands the developer a string.
Step 3. The server builds the SQL query
This is where the bug lives.
The safe way:
The unsafe way:
The safe way passes the query and the parameter as two separate things to the database. The database treats `q` as a value, no matter what is inside it. Even if `q` contains a quote or a semicolon, it stays a value.
The unsafe way mashes the query and the parameter into one big string, and only then hands the result to the database. The database has no idea which part came from the developer and which part came from you. It just parses and runs whatever it got.
Step 4. The database parses and runs
The database engine takes the SQL string and parses it as code:
It sees a `SELECT`, a `FROM`, a `WHERE` and a `LIKE`. It builds an execution plan. It scans the rows. It returns the matches.
If your input contained extra SQL syntax, the parser cheerfully includes that syntax in the plan. A closing quote ends the string early. An `OR` adds another condition. A `UNION` glues a second query onto the first. A `;` ends the statement and starts a new one. The database does not know that part of the string came from a stranger on the internet.
Visual: the safe flow
Visual: the vulnerable flow
The difference between safe and unsafe is not a complicated algorithm. It is the difference between parameterized queries and string concatenation.
SECTION 3. Attack Flow
Here is the full life of an SQL injection attack, from the first probe to the final database dump.
Step 1. Map the input
You browse `anastech.com` and write down every place that accepts user input. Search boxes, login forms, filter dropdowns, URL parameters, hidden form fields, cookies, custom headers, JSON bodies, XML bodies, multipart forms, file names. Anything that goes into the server.
Step 2. Probe each input
For each input, you send a single quote.
You watch the response. Three things can happen.
- ●The page looks normal. The input was probably filtered or not used in SQL.
- ●The page looks broken. You see "Error in your SQL syntax near ...". You are in.
- ●The page looks normal but a little different. Stay alert. This could be blind SQLi.
Step 3. Confirm the bug
You send two payloads that should produce different results if there is a bug, and the same result if there is not.
The first one should return the original product. The second one should return nothing. If they differ, the input is going into a query.
Step 4. Identify the database
You guess the database based on errors, syntax differences, and version queries.
Step 5. Count the columns
If the injection point is a `SELECT` you can hijack with `UNION`, you need to know how many columns the original `SELECT` returns. You use `ORDER BY` until the database complains, or you use `UNION SELECT` with increasing numbers of `NULL`s.
That tells you the query returns 3 columns.
Step 6. Map the schema
You use `UNION` to read the database's own catalog tables.
You read every table name. You pick the juicy ones: `users`, `accounts`, `customers`, `payments`, `tokens`.
You read every column of the `users` table. You see `username`, `email`, `password_hash`, `role`, `is_admin`.
Step 7. Dump the data
You read every user's username, password hash, and role. You crack the hashes. You log in as admin.
Step 8. Escalate
You look for ways to go beyond reading the database.
- ●Read files: `LOAD_FILE('/etc/passwd')` on MySQL.
- ●Write files: `SELECT ... INTO OUTFILE '/var/www/html/shell.php'` on MySQL.
- ●Execute commands: `xp_cmdshell 'whoami'` on SQL Server.
- ●Pivot to internal services: `COPY (...) TO PROGRAM 'curl http://internal'` on PostgreSQL.
Timing diagram
Total time from first probe to full compromise on an unprotected site: under ten minutes by hand, under one minute with sqlmap.
SECTION 4. Why Developers Make This Mistake
SQL injection has been a known bug since 1998. It is still in the OWASP Top 10 in 2026. Developers still write it every day. Why?
Mistake 1. "Concatenation is just easier."
Writing this:
is faster than writing this:
The developer thinks they will fix it later. They never fix it later. The shortcut becomes the production code.
Mistake 2. "I escape the input, that is enough."
Developers learn about `mysql_real_escape_string()` or `addslashes()` and assume that running every input through it is enough. It is not. Escaping fails in many cases:
- ●Numeric inputs that are not quoted in the SQL: `WHERE id = $id`. The attacker injects `1 OR 1=1` and never needs a quote.
- ●Multibyte character bugs (`SET NAMES 'gbk'` plus `addslashes` is exploitable).
- ●Identifiers (table names, column names) which cannot be parameterized and which escaping does not protect.
- ●Double quoting that gets unwrapped later.
Escaping is not the right pattern. Parameterized queries are.
Mistake 3. "I validate the input on the client side."
JavaScript validation in the browser is a UX feature, not a security feature. The attacker turns off JavaScript or sends the request directly with curl. The server must validate. The server must use parameters.
Mistake 4. "I use an ORM, so I am safe."
Most of the time, yes. ORMs build parameterized queries by default. But every ORM has escape hatches:
- ●`raw()` methods in Django, SQLAlchemy, Sequelize.
- ●String concatenation inside a `where()` clause.
- ●Dynamic table and column names that the ORM cannot parameterize.
- ●Calling stored procedures with concatenated arguments.
Using an ORM is not magic. The developer still has to avoid the escape hatches.
Mistake 5. "Our database is internal, so SQLi is not exploitable."
This is the famous "we have a firewall" argument. It is wrong because:
- ●Any web user is a path to the database from the outside, through the app.
- ●SQLi gives data exfiltration even without direct network access.
- ●Out-of-band SQLi can reach the internet from the database itself (DNS, HTTP requests).
The database is never internal if a web form can talk to it.
SECTION 5. Beginner Summary
- ●SQL injection happens when user input is glued into a SQL query as text, instead of being passed as a parameter.
- ●The attacker types SQL syntax (like `' OR '1'='1`) into a normal input field, and that syntax becomes part of the query the server sends to the database.
- ●The fix is always the same: use parameterized queries (also called prepared statements) so the database treats your input as a value, never as code.
- ●SQL injection comes in six families: in-band (you see results directly), error-based (the error message leaks data), union-based (you append your own query), blind boolean (you ask yes/no questions), blind time (you make the database sleep), and out-of-band (you exfiltrate over DNS or HTTP).
- ●The cost of one SQL injection bug ranges from leaked search results to full database dump to remote code execution on the server. Test every input.
SECTION 6. Visual Explanation
The safe pattern (parameterized query)
The vulnerable pattern (string concatenation)
The injection ladder
The six families of SQLi
Query structure under attack
SECTION 7. Definition
Technical definition
SQL Injection (CWE-89) is a class of vulnerabilities in which untrusted input is incorporated into the text of a SQL statement before it is parsed by the database engine, allowing the attacker to alter the structure or semantics of the statement. It is the canonical instance of OWASP Top 10 A03:2021 (Injection). The vulnerability arises when a string-handling function in the application layer concatenates a user-controlled value into a query string, instead of passing it through the database driver's parameter binding interface. The resulting statement is parsed by the database with attacker-controlled tokens treated as language constructs (operators, identifiers, statement boundaries, sub-selects, function calls), enabling unauthorized read or write access to data, denial of service, code execution within the database process, and pivot to the file system or operating system.
Beginner-friendly definition
A SQL injection is what happens when you type computer code into a form, and the website mistakes your code for its own code.
Why it matters
SQL injection has been catastrophic for nearly thirty years and remains one of the highest-impact web bugs in 2026.
- ●CVE-2025-56316 (CVSS 9.8 critical) in MCMS allowed remote SQL injection via the content_title parameter in /cms/content/list, affecting versions 5.5.0 through 6.0.1.
- ●CVE-2025-29085 (CVSS 9.8) in vipshop Saturn 3.5.1 and earlier allowed remote SQL injection through the executorCount endpoint, with no patch available at disclosure.
- ●CVE-2026-34260 (CVSS 9.6, May 2026) in SAP S/4HANA's Enterprise Search for ABAP component allowed authenticated SQL injection via direct concatenation of user input into queries.
- ●CVE-2026-27681 (CVSS 9.9, April 2026) in SAP Business Planning and Consolidation allowed full database compromise through SQL injection.
- ●CVE-2026-9082 (May 2026) in Drupal core's PostgreSQL EntityQuery condition handler allowed unauthenticated remote SQL injection across all major Drupal branches, including end-of-life versions which received exceptional patches.
- ●CVE-2026-32306 in OneUptime allowed ClickHouse SQL injection through aggregate, sort, select, and groupBy parameters via unverified Identifier interpolation, patched only in version 10.0.34.
- ●In 2026 vulnerability research, blind SQL injection remains among the most common critical web vulnerabilities, with 32% of identified vulnerabilities remaining unpatched for over 180 days.
The Equifax breach (2017), the TalkTalk breach (2015, 150,000 customers), and many of the credit-card heists of the 2010s started with a single quote in a single input field.
Common affected systems
- ●Custom PHP applications that concatenate SQL.
- ●Legacy Java applications using `Statement` instead of `PreparedStatement`.
- ●Node.js applications using string templates with `mysql` or `mssql` drivers.
- ●Python applications using `%` formatting or `f-strings` to build queries.
- ●.NET applications using `SqlCommand` with `CommandText = "... " + input`.
- ●Enterprise products: SAP, Drupal, WordPress plugins, Joomla extensions, Magento extensions.
- ●Mobile back-ends that forward JSON straight into queries.
- ●GraphQL resolvers that build SQL on the server side.
- ●Reporting and analytics tools that accept "ad-hoc" filters.
- ●ORM applications that use `raw()` or string-built `where` clauses.
SECTION 8. Examples
Example 1. The product search on AnasMarket
The feature. AnasMarket has a search bar that lets you search products by name. The URL is `https://anasmarket.com/search?q=shoes`. The server takes `q` and runs `SELECT name, price, stock FROM products WHERE name LIKE '%[q]%' AND visible = 1`. The page shows matching products as a grid.
The bug. The value of `q` is concatenated into the SQL string. There is no parameterization, no escaping, no allowlist.
The attack step by step.
- ●Step 1. You probe with `q=shoes'`. The page returns a 500 error showing "Syntax error near '%shoes''".
- ●Step 2. You confirm with `q=shoes' AND '1'='1` and `q=shoes' AND '1'='2`. The first returns shoes. The second returns nothing.
- ●Step 3. You count columns with `q=shoes' UNION SELECT NULL,NULL,NULL--`. The page works. Three columns.
- ●Step 4. You read the version with `q=shoes' UNION SELECT @@version,NULL,NULL--`. The page shows "8.0.34-MySQL".
- ●Step 5. You list tables with `q=' UNION SELECT table_name,NULL,NULL FROM information_schema.tables--`. You see `users`, `orders`, `payments`, `coupons`.
- ●Step 6. You dump users with `q=' UNION SELECT CONCAT(email,':',password_hash),role,NULL FROM users--`. You have every user's credentials.
- ●Step 7. You crack the hashes offline with hashcat. You log in as the admin.
- ●Outcome: full account takeover of the entire marketplace.
Example 2. The login form on AnasBank
The feature. AnasBank's online banking login takes a username and password. The server runs `SELECT user_id, full_name FROM customers WHERE username = '[u]' AND password = MD5('[p]')`. If the query returns a row, the user is logged in.
The bug. Both `[u]` and `[p]` are concatenated directly. No prepared statement.
The attack step by step.
- ●Step 1. You enter username `admin' --` and any password.
- ●Step 2. The query becomes `SELECT user_id, full_name FROM customers WHERE username = 'admin' -- ' AND password = MD5('anything')`.
- ●Step 3. Everything after `--` is a comment. The query becomes `SELECT user_id, full_name FROM customers WHERE username = 'admin'`.
- ●Step 4. The query returns a row for admin. The server logs you in as admin.
- ●Outcome: full access to the bank's admin dashboard, which can transfer money, freeze accounts, and view every customer's balance.
Example 3. The product filter on AnasTravel
The feature. AnasTravel has a "sort by" dropdown on the flight search results: `?sort=price`, `?sort=duration`, `?sort=date`. The server runs `SELECT * FROM flights WHERE origin = 'CMN' ORDER BY [sort]`.
The bug. The `sort` parameter is concatenated into the `ORDER BY` clause. `ORDER BY` cannot be parameterized in standard SQL drivers, so the developer just concatenated it. There is no allowlist of valid column names.
The attack step by step.
- ●Step 1. You try `?sort=(CASE WHEN (1=1) THEN price ELSE date END)`. The list comes back sorted by price.
- ●Step 2. You try `?sort=(CASE WHEN ((SELECT SUBSTRING(password,1,1) FROM users WHERE username='admin')='a') THEN price ELSE date END)`. The list comes back sorted by date.
- ●Step 3. You write a script that loops through every character. For each character, you ask: "is the first letter of admin's password 'a'?" "Is it 'b'?" etc.
- ●Step 4. Within a few thousand requests you extract the entire admin password, character by character, just from observing the order of the results.
- ●Outcome: blind SQL injection through `ORDER BY` leaks the admin password without ever showing data on the page.
Example 4. The cookie-based session on AnasSocial
The feature. AnasSocial sets a cookie `tracking_id=abc123` to remember anonymous visitors. The server runs `SELECT preferences FROM tracking WHERE tracking_id = '[cookie]'` on every page load to load the user's preferences (dark mode, language).
The bug. The cookie value is concatenated into the query.
The attack step by step.
- ●Step 1. You open Burp Suite and intercept a request.
- ●Step 2. You change the `tracking_id` cookie to `abc123' AND SLEEP(5)-- `.
- ●Step 3. The page takes 5 seconds longer than usual to load.
- ●Step 4. You confirm with `tracking_id=abc123' AND SLEEP(0)-- ` which loads normally.
- ●Step 5. You build a time-based exfiltration: `tracking_id=abc123' AND IF((SELECT SUBSTRING(password,1,1) FROM users WHERE id=1)='a', SLEEP(5), 0)-- `.
- ●Step 6. You sweep through every character and extract the admin password without the page ever displaying it.
- ●Outcome: time-based blind SQLi through a cookie that the developer never thought to filter.
Example 5. The XML import on AnasDocs
The feature. AnasDocs lets administrators bulk-import documents by uploading an XML file. The server parses the XML and inserts each `<document>` into the database with `INSERT INTO documents (title, body, author) VALUES ('[title]', '[body]', '[author]')`.
The bug. The XML field values are concatenated into the INSERT statement.
The attack step by step.
- ●Step 1. You upload an XML file with a single document where the title is `Hello', (SELECT password FROM users WHERE id=1), '--`.
- ●Step 2. The INSERT runs as: `INSERT INTO documents (title, body, author) VALUES ('Hello', (SELECT password FROM users WHERE id=1), '--', '...', '...')`.
- ●Step 3. The body column of the new document now contains the admin's password.
- ●Step 4. You view the document in the normal UI. The admin's password is displayed as the body.
- ●Outcome: second-order SQL injection through an XML import bypasses every WAF that only inspects search and login.
SECTION 9. Vulnerable Code
These are the canonical wrong patterns in five common languages. Memorize them. You will see them in real codebases for the rest of your career.
Python (with sqlite3 / psycopg2 / pymysql)
PHP (with mysqli / PDO)
Node.js (with mysql / mysql2)
Java (with JDBC)
C# / .NET (with SqlCommand)
Go (with database/sql)
Ruby (with ActiveRecord)
The universal pattern across languages
Every example above contains the same four steps:
- ●Step 1. The application reads a value out of the request.
- ●Step 2. The application concatenates that value into a string of SQL.
- ●Step 3. The application calls the database driver's "run this string" function.
- ●Step 4. The database parses the whole string as SQL, including the attacker's syntax.
The variations are cosmetic. The same fix applies to all of them: split the query and the data into two arguments to the driver, and let the driver bind the data as a parameter.
SECTION 10. Detection
Manual detection
- ●Step 1. Identify the input. Search bars, login forms, URL parameters, hidden fields, headers, cookies, JSON bodies.
- ●Step 2. Send a single quote: `'`. Watch for a 500 error, a database error message, or a page that looks broken.
- ●Step 3. Send two quotes: `''`. The query is now balanced again. The page should look normal. If it does, the input is going into a string context.
- ●Step 4. Send a boolean test: `' AND '1'='1` versus `' AND '1'='2`. If responses differ, the input is in a query.
- ●Step 5. Send a numeric boolean test: ` AND 1=1` versus ` AND 1=2` for inputs that look numeric (no quotes).
- ●Step 6. Send a time test: `' AND SLEEP(5)-- ` and time the response. Compare with `' AND SLEEP(0)-- `.
- ●Step 7. Send a UNION test: `' UNION SELECT NULL-- ` with increasing NULLs until you find the column count.
Burp Suite step by step
- ●Step 1. Open Burp Suite and enable Intercept in the Proxy tab.
- ●Step 2. In Firefox or Chrome, configure the FoxyProxy extension to send traffic through `127.0.0.1:8080`.
- ●Step 3. Visit `https://anastech.com` and click around until you see the request you want to test in the Burp Proxy HTTP history.
- ●Step 4. Right-click the request and choose "Send to Repeater".
- ●Step 5. In Repeater, modify the parameter you suspect (URL, body, cookie, header). Add a single quote.
- ●Step 6. Click "Send". Look at the response. Search for SQL errors with Ctrl+F: "syntax", "MySQL", "PostgreSQL", "ORA-", "SQLite", "unclosed quotation mark", "Microsoft OLE DB".
- ●Step 7. If no error, send `' AND SLEEP(5)-- ` and watch the response time in the bottom right of the response panel.
- ●Step 8. For deep auditing, right-click and choose "Send to Intruder", set the payload position on the parameter, and load a wordlist from SecLists/Fuzzing/SQLi.
Automated tools
- ●sqlmap (https://github.com/sqlmapproject/sqlmap) -- The de-facto standard. Detects every family of SQLi automatically.
- ●Ghauri (https://github.com/r0oth3x49/ghauri) -- A newer Python rewrite of sqlmap, faster on some targets.
- ●NoSQLMap (https://github.com/codingo/NoSQLMap) -- sqlmap's NoSQL counterpart, useful when you don't yet know which DB family you face.
- ●OWASP ZAP Active Scan -- Built-in SQLi rules, including time-based blind.
- ●Burp Suite Pro Scanner -- Best-in-class commercial scanner with sophisticated blind SQLi detection.
- ●Nuclei (https://github.com/projectdiscovery/nuclei) -- Templates for SQLi probes, ideal for scanning many targets.
Quick command-line scripts
Indicators of vulnerability
- ●Database error message in HTTP response (any of: "Microsoft JET Database", "ORA-", "MySQL server version", "syntax error at or near", "SQLite3::SQLException", "Warning: mysql_").
- ●HTTP 500 response when a quote is appended.
- ●Response content changes when boolean payload is changed.
- ●Response time changes when SLEEP() payload is sent.
- ●Out-of-band DNS or HTTP callback after sending a payload that asks the DB to make a network request.
- ●The page displays user input verbatim near table-like output.
- ●The URL has a parameter that looks numeric (`?id=5`, `?page=2`) and the page changes when the value changes.
SECTION 11. Exploitation
Workflow
- ●Step 1. Confirm the bug (quote, boolean, time).
- ●Step 2. Determine database fingerprint (MySQL, PostgreSQL, Oracle, MSSQL, SQLite).
- ●Step 3. Determine injection context (numeric, string, ORDER BY, LIMIT, second-order, in JSON, in cookie).
- ●Step 4. Determine retrieval channel (visible response, error message, time delay, OOB).
- ●Step 5. Find the column count.
- ●Step 6. Find which columns are displayed.
- ●Step 7. Map the schema (information_schema / equivalent).
- ●Step 8. Dump the target tables.
- ●Step 9. Escalate (file read, file write, command execution, network pivot).
Technique 1. Single-quote probe
The starting payload. Append `'` to any input and watch for errors.
If you see "syntax error" or a 500, the input goes into a quoted string in SQL.
Technique 2. Numeric injection (no quote needed)
If the parameter looks like a number, the SQL is likely `WHERE id = 1` with no quotes. You can inject without ever needing a quote:
This bypasses any filter that only looks for single quotes.
Technique 3. Boolean-based blind (visible content)
You force the page to render two different results based on a boolean.
If the page returns the product, the first letter is 'a'. If not, try 'b'. Loop through every position and every character.
Technique 4. Time-based blind (delayed response)
You force the database to sleep based on a condition.
Technique 5. UNION-based extraction
The classic. After finding the column count, you append a `UNION SELECT` with your own query.
Technique 6. Column count via ORDER BY
The cleanest way to find the column count. Increment until you get an error.
The query has 3 columns.
Technique 7. Column count via UNION SELECT NULLs
The other way: increase NULLs until the UNION runs without a type error.
Technique 8. Identifying string columns
Once you know the column count, find which ones can display text. Replace NULL with a string one column at a time:
Whichever one returns a page (not a type error) is your output channel.
Technique 9. Information schema enumeration
For Oracle, use `all_tables`, `all_tab_columns`. For SQLite, use `sqlite_master`.
Technique 10. Concatenating multiple columns into one
When you only have one visible output column:
Technique 11. Error-based extraction (MySQL)
Force MySQL to throw an error that contains the data you want.
The error message will include the password.
Technique 12. Error-based extraction (PostgreSQL)
When the password is not an integer, PostgreSQL throws: "invalid input syntax for integer: SecretPass123".
Technique 13. Error-based extraction (SQL Server)
The convert fails and SQL Server reports: "Conversion failed when converting the varchar value 'SecretPass123' to data type int."
Technique 14. Stacked queries
Some drivers and DB combos let you send multiple statements separated by `;`.
Common where supported: SQL Server, PostgreSQL, MySQL with mysqli_multi_query, SQLite. Not supported in default PHP+MySQL.
Technique 15. Out-of-band exfiltration via DNS (MySQL)
The DB resolves the hostname, which sends the password as a subdomain to your DNS server.
Technique 16. Out-of-band exfiltration via DNS (Oracle)
Technique 17. Out-of-band exfiltration via HTTP (PostgreSQL)
Technique 18. Second-order SQL injection
You store a payload in a profile field or anywhere persistent. Later, an admin or a background job pulls that value and uses it in a different SQL query that was not parameterized. The payload fires then. The initial input might be sanitized, but the second use is not.
- ●Step 1. Sign up with username `admin'-- `.
- ●Step 2. Change your password through the "change password" flow. The application looks up "the user with username [admin'-- ]" using a vulnerable query.
- ●Step 3. The query becomes `UPDATE users SET password='new' WHERE username='admin'-- '`. The admin's password is overwritten.
Technique 19. WAF bypass with comments
The C-style `/**/` comments break up keywords but the SQL parser still sees them as one keyword.
Technique 20. WAF bypass with case mixing
Many WAFs use case-sensitive regex blacklists.
Technique 21. WAF bypass with URL encoding
Double URL encoding (`%2553` for `%53`) bypasses some WAFs that only decode once.
Technique 22. WAF bypass with alternate whitespace
Replace spaces with these:
Technique 23. WAF bypass with CHAR / CONCAT obfuscation
Most WAFs that block the string `admin` will miss this.
Technique 24. Bypassing AND/OR keyword blocks
Technique 25. File read on MySQL
Requires the FILE privilege and the `secure_file_priv` system variable to permit the path.
Technique 26. File write on MySQL
Requires FILE privilege. After writing, visit `https://anastech.com/shell.php?c=id` to run shell commands.
Technique 27. Command execution on SQL Server
Technique 28. Command execution on PostgreSQL
Technique 29. Reading from authentication context
Some queries do not return a row but set internal state. Boolean blind via login:
If the login succeeds the guess was right. If not, wrong.
Technique 30. Header-based injection
The `User-Agent`, `Referer`, `X-Forwarded-For`, and other headers are often logged to a database with a vulnerable INSERT.
The application logs your User-Agent and triggers a UNION via the logging query.
Technique 31. ORDER BY injection (no UNION)
When the injection is in an `ORDER BY` clause you cannot UNION. Use `CASE`:
The order of the visible results changes based on the password character.
Technique 32. LIMIT injection (MySQL)
When the injection is after `LIMIT`, you can use `PROCEDURE ANALYSE` (MySQL <8.0):
Technique 33. JSON injection (modern APIs)
Modern APIs accept JSON. The server JSON-decodes and concatenates fields:
Same payload, JSON wrapping.
Technique 34. NULL byte truncation in older MySQL
The null byte truncates the rest. Useful when the query has a known suffix.
Technique 35. Boolean blind with binary search
Instead of testing each character against each letter, do a binary search:
`m` is 109. If the response says "greater than", check 122. If "less than", check 96. Each character is found in 7 requests instead of 96.
SECTION 12. Proof of Concept
Burp Suite proof of concept
- ●Step 1. Open Burp. Browse to `https://anastech.com/search?q=shoes` through the Burp proxy.
- ●Step 2. Right-click the GET request in Proxy > HTTP history and pick "Send to Repeater".
- ●Step 3. Change the URL to `https://anastech.com/search?q=shoes'`.
- ●Step 4. Click Send. Look at the response. Notice the 500 error and the message "You have an error in your SQL syntax".
- ●Step 5. Change the URL to `https://anastech.com/search?q=shoes' AND 1=2--+`. The response now has no products but no error.
- ●Step 6. Change the URL to `https://anastech.com/search?q=shoes' UNION SELECT NULL,NULL,NULL--+`. The page renders.
- ●Step 7. Change to `https://anastech.com/search?q=shoes' UNION SELECT @@version,NULL,NULL--+`. The MySQL version is on the page.
- ●Step 8. Change to `https://anastech.com/search?q=shoes' UNION SELECT GROUP_CONCAT(username,0x3a,password),NULL,NULL FROM users--+`. The page now shows every user and password.
Python PoC for blind SQL injection
Bash PoC for time-based confirmation
PowerShell PoC for Windows pentesters
Node.js PoC
sqlmap PoC
Ghauri PoC (sqlmap alternative)
SECTION 13. Payloads
Tier 1: detection payloads
Tier 2: authentication bypass payloads
Tier 3: UNION-based extraction
Tier 4: error-based payloads
MySQL:
PostgreSQL:
SQL Server:
Oracle:
Tier 5: time-based blind payloads
MySQL:
PostgreSQL:
SQL Server:
Oracle:
SQLite:
Tier 6: out-of-band payloads (DNS exfiltration)
MySQL on Windows:
PostgreSQL:
Oracle:
SQL Server:
Tier 7: WAF bypass payloads
Tier 8: second-order payloads
These are designed to be stored harmlessly and fire on a later read:
Tier 9: JSON / NoSQL boundary payloads
SECTION 14. Wordlists and Payload Libraries
Pre-built payload collections for SQL injection. Use them with Burp Intruder, ffuf, sqlmap (custom payloads), or just for manual testing.
- ●PayloadsAllTheThings / SQL Injection ==> https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/SQL%20Injection
- ●HackTricks / SQL Injection ==> https://book.hacktricks.xyz/pentesting-web/sql-injection
- ●PortSwigger Web Security Academy / SQL Injection ==> https://portswigger.net/web-security/sql-injection
- ●PortSwigger SQL injection cheat sheet ==> https://portswigger.net/web-security/sql-injection/cheat-sheet
- ●OWASP SQL Injection page ==> https://owasp.org/www-community/attacks/SQL_Injection
- ●OWASP Testing Guide / Testing for SQL Injection ==> https://owasp.org/www-project-web-security-testing-guide/stable/4-Web_Application_Security_Testing/07-Input_Validation_Testing/05-Testing_for_SQL_Injection
- ●SecLists / Fuzzing / SQLi ==> https://github.com/danielmiessler/SecLists/tree/master/Fuzzing/SQLi
- ●SecLists / Fuzzing / SQLi / Generic-SQLi.txt ==> classic mixed list
- ●SecLists / Fuzzing / SQLi / quick-SQLi.txt ==> short list for fast probing
- ●sqlmap tamper scripts ==> https://github.com/sqlmapproject/sqlmap/tree/master/tamper
- ●NetSPI SQL Injection Wiki ==> https://sqlwiki.netspi.com/
- ●FuzzDB / attack-payloads / sql-injection ==> https://github.com/fuzzdb-project/fuzzdb/tree/master/attack/sql-injection
- ●Pentest Monkey SQL Injection Cheat Sheet ==> https://pentestmonkey.net/category/cheat-sheet/sql-injection
- ●Anas Magane Pentesting Notes ==> https://github.com/Anas-Magane/Pentesting/tree/main/TOP_10_OWASP/SQL_Injection
Tools you should install today:
- ●sqlmap ==> https://github.com/sqlmapproject/sqlmap
- ●Ghauri ==> https://github.com/r0oth3x49/ghauri
- ●NoSQLMap ==> https://github.com/codingo/NoSQLMap
- ●JSQL Injection ==> https://github.com/ron190/jsql-injection
- ●BBQSQL ==> https://github.com/CiscoCXSecurity/bbqsql
- ●SQLNinja ==> http://sqlninja.sourceforge.net/
- ●SQLi Hunter ==> Google dork helper for SQLi
- ●Burp Suite Pro (commercial) ==> https://portswigger.net/burp
- ●Burp Suite Community (free) ==> https://portswigger.net/burp/communitydownload
SECTION 15. Impact
SQL injection is not a single bug with a single consequence. It is a vulnerability whose severity depends entirely on what the database holds and how the database is connected to the rest of the system. The impact ladder goes from minor to catastrophic.
- ●1. Information disclosure of public data. Hidden rows, draft products, unreleased items become visible. Annoying but not critical.
- ●2. Information disclosure of private data. Personal information of users: emails, phone numbers, addresses, dates of birth. This triggers GDPR / CCPA notification requirements and lawsuits.
- ●3. Disclosure of credentials. Password hashes, password reset tokens, API keys, session tokens stored in the DB. Hashes are cracked offline. Tokens are reused immediately for account takeover.
- ●4. Disclosure of financial data. Credit-card numbers, IBANs, bank statements, transaction history. This is the highest-cost data class under PCI-DSS rules.
- ●5. Authentication bypass. The attacker logs in as any user, including the administrator, without ever cracking a hash.
- ●6. Authorization bypass. The attacker reads data they should not have access to even when their own account is valid (e.g., row-level access ignored, multi-tenant data crossed).
- ●7. Data modification. UPDATE and INSERT injection allow the attacker to change prices, balance amounts, role flags, ownership, audit logs.
- ●8. Data destruction. DELETE and DROP TABLE on stacked-query supporting databases. Backups become the only recovery path.
- ●9. Persistence. Insert an admin user, add a back-door token, modify a stored procedure to add a back door, write a malicious trigger.
- ●10. File read on the database host. `LOAD_FILE('/etc/passwd')` on MySQL. Configuration files leak more credentials. Other application source code leaks more bugs.
- ●11. File write on the database host. `SELECT INTO OUTFILE` on MySQL writes web shells to the web root. The attacker gets a shell in the application's own context.
- ●12. Command execution inside the database engine. `xp_cmdshell` on SQL Server. PostgreSQL `COPY ... TO PROGRAM`. PL/SQL package abuse on Oracle. Now the attacker has remote code execution on the database server.
- ●13. Network pivot. The DB server can reach internal subnets the attacker cannot. SQLi becomes a port scanner and a SSRF.
- ●14. DoS by exhaustion. `SELECT * FROM big_table CROSS JOIN big_table` exhausts CPU and disk. `BENCHMARK(10000000, MD5(1))` and `pg_sleep(99999)` lock connections.
- ●15. Regulatory and legal impact. SOX, HIPAA, PCI-DSS, GDPR violations. Fines, mandatory breach disclosures, lawsuits. The 2017 Equifax breach (147 million people) cost the company over $1.4 billion in penalties and settlements and started with an unpatched vulnerability that allowed SQL-like data extraction.
- ●16. Reputational impact. The breach is reported. Customers leave. Stock price drops. Executive resignations follow.
The most expensive thing about SQL injection is that the attacker often does not need to chain it with anything. A single injection in a single field can lead to a full data dump in under an hour.
SECTION 16. Prevention
Vulnerable vs secure: side by side
The fix explained
The secure pattern works because the database driver sends two messages to the database:
- ●Message 1: "Here is the SQL text. Parse it and prepare an execution plan. The `?` is a placeholder for a value."
- ●Message 2: "Here is the value to plug into the placeholder."
The database parses the SQL text *first*, before the value ever shows up. The value cannot change the parsed query because parsing is already done. Even if the value contains `' OR 1=1--`, the database treats it as a 14-character string.
The vulnerable pattern fails because the application sends one message: "Parse and run this string." Whatever is in the string gets parsed.
Eight rules to write SQLi-free code forever
- ●1. Always use parameterized queries. Every database driver supports them. Use `?` or `:name` placeholders. Never `+` and never `f-strings` to build SQL.
- ●2. Never put user input into identifiers (table names, column names) without an allowlist. If you must let users pick a sort column, validate against a fixed list: `if column not in ['price','date','name']: raise`.
- ●3. Use stored procedures correctly. Stored procedures are not magic. If the stored procedure itself concatenates input into dynamic SQL, the vulnerability moves into the database. Call procedures with parameters, and write procedures that use parameters internally.
- ●4. Use the least-privilege database account. The web application's DB user should have only the privileges it needs. No FILE. No EXECUTE on dangerous procedures. No DDL. No access to other databases.
- ●5. Validate input on the server side. This is not a substitute for parameters. It is a defense in depth. A numeric ID parameter should be cast to int before it ever reaches the query: `id = int(request.args['id'])`.
- ●6. Use an ORM and stay on the parameterized paths. Django ORM, SQLAlchemy, ActiveRecord, Sequelize, Entity Framework all generate parameterized SQL by default. Avoid `raw()`, `extra()`, `exec()`, string templates in `where()` clauses.
- ●7. Log database errors but never display them. A stack trace with `MySQL error: syntax near ...` is a free fingerprint for attackers. Log to disk, return a generic 500 page.
- ●8. Centralize query construction. Have a `db.query(sql, params)` wrapper. Never let raw `cursor.execute` calls spread across the code base.
Developer checklist
Framework-specific secure examples
Django ORM:
Spring JdbcTemplate:
SQLAlchemy:
Sequelize (Node.js):
Enterprise-level mitigations
- ●Deploy a WAF (ModSecurity with OWASP CRS, AWS WAF, Cloudflare, Akamai) and tune it.
- ●Database Activity Monitoring (DAM) with anomaly detection.
- ●Network segmentation: the database is on a private subnet, the web tier in a DMZ, with explicit ACLs.
- ●Code review process that fails any pull request with a string-concatenated SQL.
- ●Static analysis (Semgrep, CodeQL, Snyk, Checkmarx) in CI pipelines.
- ●Bug bounty program that incentivizes SQLi reports.
- ●Periodic penetration tests with explicit SQLi scope.
- ●Database firewall (Imperva, Datasunrise) that blocks unusual query patterns.
- ●Tokenization of sensitive fields (credit cards, SSNs) so that even a full dump exposes opaque tokens.
- ●Encrypted columns with key access via HSM so the application server can decrypt but a raw SQL dump cannot.
SECTION 17. Real-World Cases
CVE library (2022-2026)
- ●CVE-2026-9082 (Drupal core PostgreSQL, May 2026), Highly Critical, CVSSv3 6.5
NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-9082 Advisory: https://www.drupal.org/security Summary: Unauthenticated SQLi in the PostgreSQL EntityQuery condition handler. Detection PoC published the same day. Patch diff reverse-engineered within hours.
- ●CVE-2026-34260 (SAP S/4HANA Enterprise Search, May 2026), CVSS 9.6
NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-34260 Advisory: https://support.sap.com/en/my-support/knowledge-base/security-notes-news.html Summary: Authenticated SQLi in SAP Enterprise Search for ABAP via direct concatenation. Full DB read/write plus crashes.
- ●CVE-2026-27681 (SAP Business Planning and Consolidation, April 2026), CVSS 9.9
NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-27681 Summary: Critical SQLi allowing complete database compromise.
- ●CVE-2026-32306 (OneUptime ClickHouse, 2026)
NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-32306 GitHub Advisory: https://github.com/OneUptime/oneuptime/security/advisories Summary: SQLi via aggregate, sort, select, groupBy params. ClickHouse Identifier params substituted into queries without escaping. Patched in 10.0.34.
- ●CVE-2025-56316 (MCMS, October 2025), CVSS 9.8
NVD: https://nvd.nist.gov/vuln/detail/CVE-2025-56316 Summary: Remote SQLi via content_title param of /cms/content/list. Versions 5.5.0--6.0.1. Fixed in 6.0.2.
- ●CVE-2025-29085 (Vipshop Saturn Console, April 2025), CVSS 9.8
NVD: https://nvd.nist.gov/vuln/detail/CVE-2025-29085 Summary: SQLi via zkClusterKey param in /console/dashboard/executorCount. No patch at disclosure time.
- ●CVE-2024-42005 (Django QuerySet.values()/values_list() SQLi), 2024
NVD: https://nvd.nist.gov/vuln/detail/CVE-2024-42005 Django advisory: https://www.djangoproject.com/weblog/2024/aug/06/security-releases/ HackerOne disclosure: https://hackerone.com/reports/2646493 Summary: SQLi in column aliases via crafted JSON object key as *arg. High severity per Django policy.
- ●CVE-2024-53908 (Django HasKey(lhs, rhs) on Oracle SQLi), late 2024
NVD: https://nvd.nist.gov/vuln/detail/CVE-2024-53908 Django advisory: https://www.djangoproject.com/weblog/
- ●CVE-2024-0267 (Kashipara Hospital Management System, 2024), Critical
NVD: https://nvd.nist.gov/vuln/detail/CVE-2024-0267 Summary: SQLi via email and password params in login.php.
- ●CVE-2023-22515 (Atlassian Confluence, 2023)
NVD: https://nvd.nist.gov/vuln/detail/CVE-2023-22515 Atlassian: https://confluence.atlassian.com/security/cve-2023-22515-1300485778.html Summary: Privilege escalation chain involving SQL-like data manipulation via setup endpoint.
- ●CVE-2022-1388 (F5 BIG-IP, 2022)
NVD: https://nvd.nist.gov/vuln/detail/CVE-2022-1388 F5 advisory: https://my.f5.com/manage/s/article/K23605346 Summary: Auth bypass with SQL-like data access on management interface; triggered massive exploitation.
- ●CVE-2023-50164 (Apache Struts 2, 2023)
NVD: https://nvd.nist.gov/vuln/detail/CVE-2023-50164 Apache: https://cwiki.apache.org/confluence/display/WW/S2-066 Summary: File-upload headline issue; related research uncovered classic SQLi paths in surrounding apps.
- ●CVE-2022-22965 (Spring4Shell), 2022
NVD: https://nvd.nist.gov/vuln/detail/CVE-2022-22965 VMware: https://spring.io/security/cve-2022-22965 Summary: Deserialization headline; fix advisories surfaced multiple surrounding SQLi during audit.
HackerOne bug bounty case studies (REAL disclosed reports)
- ●Starbucks SQLi Extracts Enterprise Accounting & Payroll Database (790 upvotes, top SQLi report on HackerOne)
Report: https://hackerone.com/reports/531051 Lesson: a single quote on the right endpoint can unlock the entire enterprise database
- ●GSA Bounty -- SQLi at labs.data.gov via User-Agent header (694 upvotes)
Report: https://hackerone.com/reports/297478 Lesson: HTTP headers, not just query parameters, are common SQLi sinks
- ●Mail.ru / city-mobil.ru -- Time-based blind SQLi, bounty $15,000
Report: https://hackerone.com/reports/297478 (see linked Mail.ru disclosure) Lesson: timing-based blind extraction remains the most reliable technique
- ●Valve -- SQLi in report_xml.php via countryFilter[] parameter, bounty $25,000
Report: https://hackerone.com/reports (search Valve SQLi report_xml) Lesson: array-style parameter parsing breaks naive query construction
- ●Razer -- SQLi at sea-web.gold.razer.com via txid parameter, bounty $2,000
Report: https://hackerone.com/reports Lesson: payment-flow endpoints process transactional IDs and are high-value targets
- ●QIWI -- SQLi on contactws.contact-sys.com leading to RCE (475 upvotes)
Report: https://hackerone.com/reports/816254 Lesson: SQLi-to-RCE pivots through xp_cmdshell-style features remain alive in 2026
- ●U.S. Dept of Defense -- Blind SQLi in User-Agent parameter (disclosed 2024)
Report: https://hackerone.com/reports/2597543 Lesson: government VDP programs accept SQLi findings; impact must be demonstrated, not just scanned
- ●Automattic -- SQLi Union Based on intensedebate.com (disclosed 2021)
Report: https://hackerone.com/reports/1046084 Lesson: small-scope blog-comment products still ship classic UNION-based SQLi
- ●Grab -- SQLi on drivegrab.com via Formidable Pro plugin
Report: https://hackerone.com/reports/273946 Lesson: third-party WordPress plugins are the source of most large-org SQLi bounties
- ●GSA / U.S. Dept of Defense -- additional SQLi via in-DoD endpoints (multiple, 2023-2025)
Listing: https://github.com/reddelexc/hackerone-reports/blob/master/tops_by_bug_type/TOPSQLI.md Lesson: the curated H1 corpus is the best free training set for new hunters
- ●Internet Bug Bounty -- CVE-2024-42005 Django SQLi in QuerySet.values(), bounty $4,263
Report: https://hackerone.com/reports/2646493 Lesson: even mature ORMs ship CVE-worthy bugs in 2024+; never trust the framework alone
- ●ImpressCMS -- Unauth SQLi to RCE chain
Report: https://hackerone.com/reports/1081145 Full write-up: https://karmainsecurity.com/impresscms-from-unauthenticated-sqli-to-rce Lesson: SQLi-to-RCE write-ups are the highest-impact case studies for learning chaining
- ●inDrive -- Blind SQLi on id.indrive.com
Report: https://hackerone.com/reports/2051931 Lesson: ride-sharing identity endpoints with user-controlled query parameters remain reliable bounty targets
Curated corpora and dashboards
- ●reddelexc top SQLi reports: https://github.com/reddelexc/hackerone-reports/blob/master/tops_by_bug_type/TOPSQLI.md
- ●HackerOne Hacktivity (filter by SQLi): https://hackerone.com/hacktivity
- ●NIST NVD recent SQLi CVEs: https://nvd.nist.gov/vuln/search/results?form_type=Basic&results_type=overview&query=sql+injection
- ●Drupal security advisories (CVE-2026-9082): https://www.drupal.org/security
- ●SAP Security Notes (CVE-2026-34260, CVE-2026-27681): https://support.sap.com/en/my-support/knowledge-base/security-notes-news.html
Lessons learned
- ●The bug is almost always in a *non-obvious* place: a filter dropdown, a "sort by" parameter, a UUID-looking value, a header, a cookie, a JSON field. The login page is rarely vulnerable because it gets the most attention. Look at what nobody reviewed.
- ●"Defense in depth" without parameterized queries fails. Many of the CVEs above involved applications with WAFs, with input filtering, with admin authentication, and still fell. Only correct query construction prevents the root cause.
- ●Second-order SQLi is increasingly common as applications move to event-driven pipelines and background jobs. Untrusted data is stored cleanly and re-used later in unsafe ways.
- ●Modern apps with JSON APIs are not safer. They are often more vulnerable because schemas were considered "internal" and skipped in audits.
- ●ORM users have a false sense of security. Every reported SQLi against a Rails/Django/Laravel app has been in the part of the code where the developer dropped down to raw SQL.
- ●The blast radius keeps growing. Microservices share databases, single SQLi can leak data from multiple services because they share a schema.
SECTION 18. References
- ●OWASP SQL Injection ==> https://owasp.org/www-community/attacks/SQL_Injection
- ●OWASP Cheat Sheet: SQL Injection Prevention ==> https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html
- ●OWASP Cheat Sheet: Query Parameterization ==> https://cheatsheetseries.owasp.org/cheatsheets/Query_Parameterization_Cheat_Sheet.html
- ●OWASP Testing Guide ==> https://owasp.org/www-project-web-security-testing-guide/
- ●PortSwigger Web Security Academy / SQL Injection ==> https://portswigger.net/web-security/sql-injection
- ●PortSwigger SQL Injection Cheat Sheet ==> https://portswigger.net/web-security/sql-injection/cheat-sheet
- ●MITRE CWE-89 ==> https://cwe.mitre.org/data/definitions/89.html
- ●NIST NVD search "SQL Injection" ==> https://nvd.nist.gov/vuln/search
- ●HackerOne Hacktivity (SQLi filter) ==> https://hackerone.com/hacktivity?queryString=sql%20injection
- ●reddelexc Top SQLi Reports corpus ==> https://github.com/reddelexc/hackerone-reports/blob/master/tops_by_bug_type/TOPSQLI.md
- ●sqlmap (official) ==> https://github.com/sqlmapproject/sqlmap
- ●Ghauri ==> https://github.com/r0oth3x49/ghauri
- ●PayloadsAllTheThings -- SQL Injection ==> https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/SQL%20Injection
- ●HackTricks -- SQL Injection ==> https://book.hacktricks.xyz/pentesting-web/sql-injection
- ●Anas Magane Pentesting Notes ==> https://github.com/Anas-Magane/Pentesting/tree/main/TOP_10_OWASP
- ●HackTricks SQL Injection ==> https://book.hacktricks.xyz/pentesting-web/sql-injection
- ●PayloadsAllTheThings / SQL Injection ==> https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/SQL%20Injection
- ●sqlmap project ==> https://sqlmap.org and https://github.com/sqlmapproject/sqlmap
- ●Ghauri ==> https://github.com/r0oth3x49/ghauri
- ●Pentest Monkey MySQL SQLi Cheat Sheet ==> https://pentestmonkey.net/cheat-sheet/sql-injection/mysql-sql-injection-cheat-sheet
- ●Pentest Monkey MSSQL ==> https://pentestmonkey.net/cheat-sheet/sql-injection/mssql-sql-injection-cheat-sheet
- ●Pentest Monkey Oracle ==> https://pentestmonkey.net/cheat-sheet/sql-injection/oracle-sql-injection-cheat-sheet
- ●Pentest Monkey PostgreSQL ==> https://pentestmonkey.net/cheat-sheet/sql-injection/postgres-sql-injection-cheat-sheet
- ●NetSPI SQL Injection Wiki ==> https://sqlwiki.netspi.com/
- ●SecLists fuzzing wordlists ==> https://github.com/danielmiessler/SecLists/tree/master/Fuzzing/SQLi
- ●The Web Application Hacker's Handbook (book) ==> Dafydd Stuttard, Marcus Pinto
- ●SQL Injection Attacks and Defense (book) ==> Justin Clarke
- ●Drupal SA-CORE-2026-004 advisory ==> https://www.drupal.org/sa-core-2026-004
- ●SAP Security Patch Day ==> https://support.sap.com/securitynotes
- ●Anas Magane Pentesting Notes ==> https://github.com/Anas-Magane/Pentesting/tree/main/TOP_10_OWASP/SQL_Injection
SECTION 19. Practical Labs
SOON.
The ANAS EDUCATION platform will host hands-on labs covering every family of SQL injection. Planned labs:
- ●Lab 1. Basic in-band UNION on AnasMarket product search.
- ●Lab 2. Login bypass on AnasBank using `admin'-- `.
- ●Lab 3. Error-based extraction on AnasDocs using EXTRACTVALUE.
- ●Lab 4. Blind boolean extraction on AnasTravel with binary search.
- ●Lab 5. Time-based blind on AnasSocial through a cookie.
- ●Lab 6. Out-of-band exfiltration via DNS on AnasOne.
- ●Lab 7. Stacked queries on AnasTwo (SQL Server).
- ●Lab 8. Second-order SQLi on AnasCorp via profile update.
- ●Lab 9. UNION through a custom HTTP header on AnasTech.
- ●Lab 10. WAF bypass with comments and case mixing on AnasMarket.
- ●Lab 11. File read with LOAD_FILE on AnasDocs.
- ●Lab 12. xp_cmdshell escalation on a SQL Server lab.
- ●Lab 13. COPY TO PROGRAM RCE on a PostgreSQL lab.
- ●Lab 14. ORDER BY blind injection on AnasTravel.
- ●Lab 15. Building a Python time-based extractor from scratch.
PortSwigger Academy labs you can practice NOW
- ●SQL injection vulnerability in WHERE clause allowing retrieval of hidden data ==> APPRENTICE
- ●SQL injection vulnerability allowing login bypass ==> APPRENTICE
- ●SQL injection attack, querying the database type and version on Oracle ==> PRACTITIONER
- ●SQL injection attack, querying the database type and version on MySQL and Microsoft ==> PRACTITIONER
- ●SQL injection attack, listing the database contents on non-Oracle databases ==> PRACTITIONER
- ●SQL injection attack, listing the database contents on Oracle ==> PRACTITIONER
- ●SQL injection UNION attack, determining the number of columns returned by the query ==> PRACTITIONER
- ●SQL injection UNION attack, finding a column containing text ==> PRACTITIONER
- ●SQL injection UNION attack, retrieving data from other tables ==> PRACTITIONER
- ●SQL injection UNION attack, retrieving multiple values in a single column ==> PRACTITIONER
- ●Blind SQL injection with conditional responses ==> PRACTITIONER
- ●Blind SQL injection with conditional errors ==> PRACTITIONER
- ●Blind SQL injection with time delays ==> PRACTITIONER
- ●Blind SQL injection with time delays and information retrieval ==> PRACTITIONER
- ●Blind SQL injection with out-of-band interaction ==> PRACTITIONER
- ●Blind SQL injection with out-of-band data exfiltration ==> PRACTITIONER
- ●SQL injection with filter bypass via XML encoding ==> PRACTITIONER
- ●Visible error-based SQL injection ==> PRACTITIONER
- ●SQL injection attack, listing the database contents on Oracle ==> PRACTITIONER
- ●SQL injection in different contexts ==> EXPERT
- ●Lab: SQL injection with truncated output ==> EXPERT
Full list at https://portswigger.net/web-security/all-labs#sql-injection
SECTION 20. Cheat Sheet
SECTION 21. Exam
30 multiple-choice questions. The platform picks 20 at random. Scoring: 0-13 fail, 14-15 retry, 16-20 pass.
Q1. What is the root cause of SQL injection? A. Slow database performance. B. User input is concatenated into a SQL query string instead of being passed as a parameter. C. Missing TLS encryption on the database connection. D. Outdated database server version. Answer: B.
Q2. Which character is the classic probe for SQL injection? A. `&` B. `'` (single quote) C. `?` D. `#` Answer: B.
Q3. What does the payload `' OR '1'='1` do in a login query? A. It hashes the password. B. It makes the WHERE clause always true so the query returns a user row. C. It encrypts the SQL statement. D. It tells the database to ignore the query. Answer: B.
Q4. Which OWASP Top 10 category covers SQL injection? A. A01:2021 Broken Access Control. B. A03:2021 Injection. C. A07:2021 Identification and Authentication Failures. D. A09:2021 Security Logging and Monitoring Failures. Answer: B.
Q5. Which CWE number is most closely associated with SQL injection? A. CWE-79. B. CWE-22. C. CWE-89. D. CWE-352. Answer: C.
Q6. What does UNION SELECT do in a SQLi attack? A. Combines the original query result with a new attacker-chosen query result. B. Deletes the database. C. Encrypts the password column. D. Renames the database tables. Answer: A.
Q7. How do you find the number of columns in the original SELECT for a UNION attack? A. Try `SELECT count(*)`. B. Use `ORDER BY N` with increasing N until you get an error. C. Drop the table and recreate it. D. Read the database's source code. Answer: B.
Q8. Which payload would tell you if the database is MySQL? A. `SELECT version()` or `SELECT @@version`. B. `SELECT * FROM sys.indexes`. C. `SELECT name FROM v$tablespace`. D. `SELECT * FROM dba_users`. Answer: A.
Q9. What is blind SQL injection? A. SQL injection where the attacker cannot see the data directly and must infer it from response differences or timing. B. SQL injection that only works on databases without indexes. C. SQL injection performed from the command line. D. SQL injection in JavaScript code. Answer: A.
Q10. Which function would you use to delay a MySQL response by 5 seconds in a time-based blind attack? A. `WAIT(5)`. B. `DELAY(5)`. C. `SLEEP(5)`. D. `PAUSE(5)`. Answer: C.
Q11. The equivalent of `SLEEP(5)` for PostgreSQL is: A. `WAITFOR DELAY '0:0:5'`. B. `PG_SLEEP(5)`. C. `DBMS_LOCK.SLEEP(5)`. D. `SELECT * FROM pg_wait`. Answer: B.
Q12. The equivalent of `SLEEP(5)` for Microsoft SQL Server is: A. `WAITFOR DELAY '0:0:5'`. B. `PG_SLEEP(5)`. C. `BENCHMARK(5)`. D. `SLEEP(5)`. Answer: A.
Q13. Which of the following is a proper parameterized query in Python? A. `cur.execute("SELECT * FROM users WHERE id = " + str(uid))`. B. `cur.execute(f"SELECT * FROM users WHERE id = {uid}")`. C. `cur.execute("SELECT * FROM users WHERE id = %s", (uid,))`. D. `cur.execute("SELECT * FROM users WHERE id = " % uid)`. Answer: C.
Q14. Which of these table names contains the database catalog in MySQL and PostgreSQL? A. `system.tables`. B. `master.tables`. C. `information_schema.tables`. D. `sys.objects`. Answer: C.
Q15. What is the purpose of the `--` (double dash) in SQL injection payloads? A. To increment a column value. B. To start a SQL line comment, which makes the database ignore the rest of the query. C. To divide two numbers. D. To trigger a stored procedure. Answer: B.
Q16. What is "second-order" SQL injection? A. SQLi where the payload is stored at one input and triggered later when a different (vulnerable) query reads that stored value. B. SQLi that requires two attackers working together. C. SQLi that only works on databases with two columns. D. SQLi performed twice in a row. Answer: A.
Q17. Why is `mysql_real_escape_string()` not a complete protection against SQL injection? A. It only protects against XSS. B. It does not protect numeric (unquoted) injection points and has multibyte edge cases. C. It is a deprecated language feature. D. It costs money to use. Answer: B.
Q18. Which is a recommended way to handle dynamic table or column names that you cannot parameterize? A. Concatenate the user input directly. B. Hash the input with SHA-256. C. Compare against a server-side allowlist of valid identifiers. D. URL-encode the input. Answer: C.
Q19. Which of these is an out-of-band (OOB) SQLi technique? A. Use UNION to read into the HTML page. B. Force the database to make a DNS or HTTP request to an attacker-controlled host. C. Run the SQL twice. D. Encode the payload in base64. Answer: B.
Q20. Which MySQL function can be used for OOB exfiltration on Windows using SMB? A. `LOAD_FILE`. B. `SLEEP`. C. `BENCHMARK`. D. `EXPORT_SET`. Answer: A.
Q21. Which payload bypasses a WAF that blocks the keyword "UNION"? A. `UN//ION SE//LECT`. B. `SELECT DROP`. C. `SELECT *`. D. `;exit`. Answer: A.
Q22. Which sqlmap flag dumps the contents of a specific table? A. `--current-db`. B. `--dump`. C. `--tamper`. D. `--proxy`. Answer: B.
Q23. What does `--tamper=space2comment` do in sqlmap? A. Replaces every space in payloads with `/**/` to evade space-blocking filters. B. Converts SQL to NoSQL. C. Removes comments from the payload. D. Disables tampering. Answer: A.
Q24. Which SQL Server stored procedure allows arbitrary command execution? A. `sp_addrole`. B. `xp_cmdshell`. C. `sp_who2`. D. `sp_help`. Answer: B.
Q25. Which PostgreSQL feature allows arbitrary command execution if the DB user has permission? A. `COPY ... TO PROGRAM`. B. `EXPLAIN`. C. `LOCK TABLE`. D. `ANALYZE`. Answer: A.
Q26. What is the safer alternative to MySQL `mysqli_query` with string concatenation? A. Use `mysqli_prepare` with placeholders. B. Disable MySQL. C. Convert all queries to NoSQL. D. URL-encode the query. Answer: A.
Q27. In Java, which class produces a parameterized query? A. `Statement`. B. `PreparedStatement`. C. `ResultSet`. D. `Connection`. Answer: B.
Q28. Why does an ORM not automatically prevent all SQL injection? A. ORMs do not protect against XSS. B. ORMs have escape hatches such as `raw()`, `extra()`, and string-built `where` clauses that can be misused. C. ORMs are slower. D. ORMs only work in PHP. Answer: B.
Q29. Which of the following is a real critical SQL injection CVE from 2026? A. CVE-2026-9082 (Drupal core, PostgreSQL EntityQuery). B. CVE-2007-12345. C. CVE-1999-0517. D. CVE-2020-XSS. Answer: A.
Q30. Which is the best first detection payload for an unknown numeric URL parameter? A. `1' OR '1'='1`. B. ` AND 1=1` and ` AND 1=2` (compare the two responses). C. `<script>alert(1)</script>`. D. `../../../etc/passwd`. Answer: B.
SECTION 22. Certificate Requirements
- ●Complete every lesson in this course (Sections 1-24).
- ●Complete at least 5 practical labs from Section 19 (when available) or 5 PortSwigger SQL injection labs.
- ●Pass the final exam with a score of at least 16/20.
The ANAS EDUCATION certificate for "SQL Injection Specialist" is awarded once all three conditions are met.
SECTION 23. Important Notes
Common beginner mistakes
- ●Sending only the `'` payload and giving up when no error shows. Always follow up with the boolean pair (`AND 1=1` / `AND 1=2`) and a timing test.
- ●Forgetting URL encoding when sending payloads through the URL. A `#` in your payload becomes a fragment, not part of the query. Use `--+` or `--%20-`.
- ●Trying UNION with the wrong number of columns and assuming no SQLi exists. The page might just be hiding the error.
- ●Believing that "the input is a UUID, so SQLi is impossible". UUIDs are strings, and strings get concatenated.
- ●Confusing a database error in the response with a successful exploit. The error proves the input is in a query. You still have to extract data.
Pentester tips
- ●Always test every parameter, not just the obvious ones. Headers, cookies, multipart fields, JSON keys, XML attributes are common SQLi homes.
- ●Run sqlmap last, not first. Manual confirmation teaches you how the bug behaves and gives you the context sqlmap needs.
- ●Save your Burp request to a file and use `sqlmap -r request.txt`. It captures every header, cookie, and form field that the application expects.
- ●Use `--proxy=http://127.0.0.1:8080` to push sqlmap traffic through Burp for observation.
- ●Try `--level=5 --risk=3` on a target you control before assuming sqlmap "did not find anything" at default levels.
Bug bounty tips
- ●Read the program scope carefully. Many programs explicitly forbid sqlmap because it generates too much traffic. Use manual confirmation and switch to sqlmap only with permission.
- ●The first SQLi report for a target is often the largest payout. Hunt parameters that look "boring": admin-only filters, internal-looking endpoints, partner APIs.
- ●Reproduce your finding three times in three sessions before reporting. Time-based blind has false positives from network jitter.
- ●Document the proof of concept clearly with screenshots and step-by-step requests. A clean writeup gets paid faster.
- ●Never dump the production database in a bug bounty. Extract one row of the version banner, the current user, and stop.
Red team notes
- ●Internal SQLi is often easier than external. Internal apps have lower test coverage.
- ●Combine SQLi with credential reuse. Cracked password hashes often unlock VPNs, jump boxes, and admin consoles.
- ●Plant a back door in a stored procedure. It survives most patching cycles.
- ●Use the database's outbound network access for command-and-control if the web tier is heavily monitored but the DB tier is not.
Real-world advice
- ●SQLi has been "dead" for twenty years and is still alive. New frameworks introduce new escape hatches. New developers write the same old bugs.
- ●Code reviews catch more SQLi than scanners. Train your team to spot string concatenation in any language.
- ●Static analysis (Semgrep, CodeQL) covers about 80% of the obvious cases. The remaining 20% is where the bug bounties are.
- ●Database error messages in production are a vulnerability all by themselves. Strip them.
Things to remember during exams
- ●Read the question carefully. "Which is the safer pattern" and "Which is the vulnerable pattern" are easy to confuse under time pressure.
- ●When asked for the right defense, the answer is almost always "parameterized queries" or "prepared statements". Concatenation is never the right answer.
- ●When asked about a payload, picture the resulting SQL string in your head.
- ●CWE-89 is SQL injection. CWE-79 is XSS. CWE-22 is path traversal. Memorize these four-digit numbers.
Things to remember during real assessments
- ●Have authorization in writing before testing.
- ●Do not send destructive payloads (`DROP TABLE`, `DELETE`). Use `SELECT` and `SLEEP` only.
- ●Capture all your traffic for the customer's records.
- ●Notify the customer immediately if you find a critical SQLi in a production system.
Frequently confused concepts
- ●SQLi vs ORM Injection: SQLi targets raw SQL strings; ORM injection targets the way the ORM builds queries (e.g., dynamic `find` patterns). The fix differs.
- ●In-band vs out-of-band: In-band means the result of your injection is visible in the response. OOB means the result is delivered via a side channel like DNS.
- ●Error-based vs blind: Error-based makes the database speak through error messages. Blind means there is no error and no visible result, only timing or boolean differences.
- ●Stacked queries vs UNION: Stacked queries (`;` separates statements) require driver support. UNION fits a new SELECT inside the existing one and works in most drivers.
- ●Prepared statement vs stored procedure: A prepared statement separates query text from values. A stored procedure is a named, pre-defined database routine. A stored procedure that internally concatenates is still vulnerable.
Interview tips
- ●Be ready to explain why `mysql_real_escape_string` is not a complete solution.
- ●Be ready to write a vulnerable PHP, Python, Java, or Node snippet and then rewrite it safely.
- ●Know the OWASP Top 10 category and the CWE number by heart.
- ●Have a one-sentence answer to "what is SQL injection in plain language?": "It is when user input is glued into a SQL query as text, instead of being passed as a parameter."
Key takeaways
- ●The bug is in the *concatenation*, not in the input.
- ●Parameterized queries are the only complete fix.
- ●Every input is a SQLi candidate until proven safe.
- ●Test in this order: single quote, two quotes, boolean pair, time delay, UNION, blind exfil.
- ●sqlmap is your friend, but it does not replace understanding.
SECTION 24. Final Word from Your Instructor
SQL Injection is the vulnerability that turns a database into a confessional.
Every time a developer writes:
a new SQL injection is born somewhere in the world. The mistake is twenty-eight years old. The fix is also twenty-eight years old. The gap between them is what funds the bug bounty industry.
Your job from now on is to look at any input field and ask one question: "Does this value flow into a SQL query?"
When you see a search box, ask: "What does the server do with my search term?"
When you see a login form, ask: "Are the username and password compared against a concatenated string?"
When you see a filter or sort dropdown, ask: "Is the selected column name placed directly into the query?"
When you see a URL like `/product?id=5`, ask: "Is that `5` placed into a `WHERE id =` clause?"
If the answer is "yes, without parameterization", you have found a bug that is worth somewhere between four figures and six figures, depending on how big the database is and how badly the company wants it dumped.
The in-band UNION technique is the clearest demonstration. Memorize it.
The blind boolean technique is the most reliable when output is hidden. Memorize it.
The time-based technique is the last resort when even the boolean is hidden. Memorize it.
The sqlmap automation is your time-saver for complex schemas. Memorize the flags.
The error messages of the major databases (MySQL, PostgreSQL, SQL Server, Oracle, SQLite) are your fingerprint chart. Memorize the look of each.
The information_schema is your map. Memorize the table and column names.
The prepared statement is your fix. Memorize the syntax in your language of choice.
You are now equipped with the same knowledge that powers the top SQLi reports on HackerOne. The companies that pay $25,000 for a SQLi report are paying for somebody who can write a clean PoC, identify the database, count the columns, dump one row, and stop. That is a skill, not a tool. Tools execute what you already understand. They cannot understand for you.
Welcome to the world where a single quote changes everything.
- ●Go hunt.
