从rand()到主键冲突:深入剖析floor()报错注入的底层机制

张开发
2026/4/21 5:02:58 15 分钟阅读

分享文章

从rand()到主键冲突:深入剖析floor()报错注入的底层机制
1. 为什么floor()报错注入值得深入研究第一次接触floor()报错注入时很多人会觉得这不过是又一个SQL注入技巧罢了。但当我真正深入MySQL源码层面分析时才发现这个看似简单的报错背后隐藏着数据库引擎处理分组查询的精妙机制。这种注入方式之所以特殊是因为它不像常规注入那样依赖明显的逻辑漏洞而是巧妙地利用了数据库内部处理临时表时的边界条件。记得去年在给公司做安全审计时就遇到过一个真实案例。某电商平台的搜索接口存在SQL注入漏洞但常规的union select和布尔盲注都被WAF拦截得死死的。最后正是靠着floor()报错注入我们成功获取了数据库权限。当时团队里有个新人问我为什么这个注入方式需要至少三条数据才会报错这个问题直接戳中了floor()注入最核心的机制。2. 关键函数的工作原理剖析2.1 rand()函数的确定性之谜很多人以为rand()就是个简单的随机数生成器但加上种子参数后它的行为就变得非常有趣。在MySQL中rand(0)会产生一个固定的伪随机序列。我做过实测在5.7和8.0版本中执行SELECT rand(0) FROM (SELECT 1 UNION SELECT 2 UNION SELECT 3) as t;得到的序列永远是0.15522042769493574, 0.620881741513388, 0.6387634553767775。这个特性正是floor()注入能够稳定复现的关键。如果没有指定种子每次调用rand()都会产生真正的随机值注入就会变得不可控。2.2 floor()的数学魔法floor()函数的行为很简单——向下取整。但配合rand(0)*2使用时就产生了二进制开关的效果。来看个实际例子SELECT floor(0.15522042769493574 * 2); -- 返回0 SELECT floor(0.620881741513388 * 2); -- 返回1这个0和1的交替序列将成为后续主键冲突的导火索。我在本地测试时发现使用rand(0)产生的序列前五位是0,1,1,0,1对应floor计算后的结果。这个特定序列恰好满足触发条件。3. group by与临时表的幕后机制3.1 虚拟表的创建过程MySQL处理group by时会在内存中创建一张虚拟临时表。这个机制我通过explain验证过当执行包含group by的查询时Extra字段会显示Using temporary。临时表的结构大致是这样的group_keycount(动态生成)(累计值)关键点在于当遇到新的group_key时引擎会先检查临时表是否存在该键如果不存在就插入。这个检查-插入的过程正是漏洞触发的契机。3.2 主键冲突的产生时机让我们用实际数据推演整个过程。假设有张表test有三条记录执行SELECT count(*), floor(rand(0)*2) as x FROM test GROUP BY x;MySQL的处理流程是这样的读取第一条记录计算floor(rand(0)*2)0第一次计算检查临时表没有key0的记录准备插入插入前重新计算floor(rand(0)*2)1第二次计算插入key1count1读取第二条记录计算floor(rand(0)*2)1第三次计算发现key1存在直接count1读取第三条记录计算floor(rand(0)*2)0第四次计算检查发现key0不存在准备插入插入前计算floor(rand(0)*2)1第五次计算尝试插入key1但已存在触发主键冲突这个流程解释了为什么最少需要三条数据——只有到第三条时才会满足先查无key插入时key又已存在的条件。4. 实战中的注意事项与变体4.1 种子值的选择艺术rand(0)中的0不是随便选的。我测试过不同种子种子0稳定产生0,1,1,0,1序列种子1产生1,0,0,0,0序列无法触发冲突种子2产生0,1,1,1,1序列同样无效这说明种子选择直接影响注入成功率。在实际渗透测试中如果使用rand()不带种子成功率可能只有50%左右。4.2 表数据量的影响有个容易误解的点不是说表数据越多越好。关键是有足够记录让floor(rand(0)*2)计算达到冲突点。我做过实验3条记录稳定触发2条记录从不触发4条记录仍然在第3条处理时触发这是因为虚拟表是按行处理的与总记录数无关只与处理顺序有关。4.3 现代防御措施绕过现在很多WAF会检测floor(rand(0)*2)这种模式。我最近发现几个绕过技巧使用hex编码floor(rand(0)*0x2)变量替换set a2; floor(rand(0)*a)嵌套表达式floor(rand(0)*(11))但这些方法都需要根据具体环境调整。在最近一次红队演练中我们就用变量替换的方式成功绕过了某云WAF的检测。5. 从原理到防御的思考理解了注入原理后防御策略就清晰了。除了常规的参数化查询外我建议开发者在MySQL配置中设置sql_mode包含STRICT_ALL_TABLES限制应用程序账户的权限避免访问information_schema对数据库错误信息进行统一处理不直接返回给客户端在代码审查时要特别注意包含group by的动态SQL拼接。有次代码审计中我就发现某框架的统计模块直接拼接了用户输入的排序字段导致了潜在的注入风险。

更多文章