Stack Attack
Acme Corp's product search page runs a SELECT query. But what if the database supports stacked queries—multiple statements separated by semicolons? You could piggyback an entirely different statement onto the search.
Use a stacked query (semicolon) to create a new admin user in the users table while searching for products.
| Name | Price |
|---|
| ID | Username | Password | Role |
|---|
Close the LIKE string, end the first statement with a semicolon, then write an INSERT:
'; INSERT INTO users (username, password, role) VALUES ('hacker', 'pwned', 'admin') --
The semicolon separates the SELECT from your malicious INSERT. The -- comments out the trailing %'.
Stacked queries are devastating because they allow any SQL operation, not just data extraction. The attacker is no longer limited to the original statement type. MySQL with PHP (mysql_query) historically blocked stacked queries, but PostgreSQL, SQL Server, and modern MySQL drivers often allow them. Always use parameterized queries regardless.
The Sleeper Agent
This is a two-step attack. Step 1: Register with a carefully crafted username. The registration form properly escapes input (parameterized query). Step 2: View "My Profile," which uses the stored username directly in a new query without sanitization. The payload sleeps in the database until triggered.
Register (Safe Insert)
The username is stored safely via parameterized query. No injection occurs here.
View Profile (Vulnerable)
The stored username is concatenated directly into a new query. The injection triggers here.
Register with a username that will cause injection when your profile is viewed. The goal: make the profile query return the admin's data instead of yours.
| ID | Username | Role |
|---|
Register with username: admin' --
Step 1 safely stores it as the literal string admin' --.
Step 2 builds: SELECT * FROM users WHERE username='admin' --'
The stored quote closes the string, -- comments out the rest, and the query returns the admin's row.
Second-order injection is particularly hard to detect because the input point and the trigger point are in different parts of the application. The registration form did everything right—it used parameterized queries. But the profile page trusted stored data as if it were safe. Every query must use parameters, even when the data source is your own database.
Sort Order Secrets
The employee directory lets users sort results by column. The sort parameter goes directly into an ORDER BY clause. You cannot use UNION here (ORDER BY comes after SELECT), but you can use a CASE expression to change the sort order based on a boolean condition—effectively asking the database yes/no questions.
Use an ORDER BY CASE injection to determine: does the admin (employee ID 1) earn more than $90,000? The sort order will change based on the answer.
| Name | Department | Salary |
|---|
Inject a CASE expression that sorts by different columns depending on a condition:
(CASE WHEN (SELECT salary FROM employees WHERE id=1) > 90000 THEN name ELSE department END)
If the condition is TRUE, results sort by name. If FALSE, they sort by department. Compare the two orderings to infer the answer.
ORDER BY injection is a form of inferential (boolean-based) extraction. You cannot see data directly, but you can ask true/false questions and observe which sort order appears. By refining the condition (> 90000, > 95000, > 92500...), an attacker can binary-search for exact values. This is slow but unstoppable if the ORDER BY parameter is not parameterized.
Filter Evasion
The developers got smart. They added a keyword filter that blocks common SQL injection terms. Your input is checked against a blocklist before it reaches the query. But blocklists are notoriously easy to bypass...
Bypass the keyword filter and extract usernames from the users table. The filter is case-sensitive and checks for exact keyword matches.
| Results |
|---|
The filter is case-sensitive. It blocks UNION and SELECT, but what about mixed case?
Try: 0) UniOn SeLeCt username FROM users--
Or use inline comments to break up keywords: 0) UN/**/ION SE/**/LECT username FROM users--
The SQL engine ignores case and strips comments, but the naive filter does not.
Blocklist-based filtering (also called "blacklisting") is fundamentally flawed for SQL injection prevention. Attackers can use mixed case, inline comments (/**/), URL encoding, Unicode normalization, and countless other tricks. The only reliable defense is parameterized queries (prepared statements), which separate code from data at the protocol level. No amount of filtering can match the security of proper parameterization.