深入 MySQL 内核:从临时哈希表分配机制详解 Floor 报错注入核心原理

目录

1、基础函数铺垫

2、导致报错核心


之前一直没怎么理解 floor 报错注入的原理,只是用固定语句打,这次详细理解了下原理,整理了笔记,希望对大家理解 Floor 报错注入有帮助。

1、基础函数铺垫

生成随机数:

sql 复制代码
select rand();
//生成0-1之间的小数

向下取整:

floor 函数并不是四舍五入,而是直接向下取整

sql 复制代码
select floor(1.78);
select floor(2.78);
select floor(1.28);

随机取 0 或者 1

sql 复制代码
select floor(rand()*2);

给语句取别名:

sql 复制代码
select floor(rand()*2) a;

拼接:

sql 复制代码
select concat(1,2,3);

2、导致报错核心

以 id 进行排序

sql 复制代码
select * from users group by id;

当 MySQL 看到 GROUP BY id 时,它的核心任务是:去重、分类、合并。

为了完成这个任务,MySQL 需要用到一个数据结构------临时哈希表

MySQL 会在内存中开辟一块空间,建立一张临时表。这张临时表的信息非常特殊:

它的主键(Unique Key) ,就是 GROUP BY 后面的那个字段(在这里是 id)。因为是主键,所以里面的值绝对不能重复。

MySQL 开始一行行扫描 users 表:

  1. 读取第 1 行,发现 id = 1。

  2. 去临时表里查找:"主键里有没有 1?" ➔ 没有。

  3. 在临时表中新建一行 ,把 id = 1 作为主键写入。

  4. 读取第 2 行,发现 id = 1。

  5. 去临时表里查找:"主键里有没有 1?" ➔ 有。

  6. 直接跳过或做覆盖处理(因为已经有这个组了,不能重复插入)。

接着读取第 3 行 :发现 id = 2。

去临时表里查找 :"主键里有没有 2?" ➔ 没有 (因为现在里面只有 id=1 )。

在临时表中新建一行 :把 id = 2 作为新主键写入。

测试语句:

sql 复制代码
select concat(0x3a,(database()),0x3a,floor(rand()*2)) x;
//0x3a 是十六进制,表示的是英文冒号:(方便我们定位报错位置)

这一句只是单纯的计算,触发了一次 rand() 计算,结果是 :security:1,就结束了。这里没有对比,没有表格,没有冲突,所以它永远都是正常的。

sql 复制代码
select concat(0x3a,(database()),0x3a,floor(rand()*2)) x from information_schema.tables group by x;

MySQL 的优化器在解析这条语句时,它发现用户的意图非常简单:"用户只是想把所有的结果分个组,看看到底有哪些不重复的值。"

为了节省内存和提高速度,MySQL 在底层不需要记录数量 ,它的执行计划会变成 流式去重(Streaming / Filesort)。它的内部执行逻辑变成了这样:

  1. 扫描第 1 行 :调用一次表达式,RAND() 运行,算出来 :security:0。MySQL 直接把 :security:0 吐给屏幕。

  2. 扫描第 2 行 :调用一次表达式,RAND() 运行,算出来 :security:1。MySQL 直接把 :security:1 吐给屏幕。

  3. 扫描第 3 行 :调用一次表达式,RAND()* 运行,算出来 :security:0。MySQL 发现屏幕上刚才输出过 :security:0 了,于是就把这一行丢弃(去重)。

在这种流式处理下,每扫描一行数据,上面的 CONCAT(...) 表达式有且仅会被调用"一次"。算出来是什么就是什么,直接拿去去重或输出,绝对不可能报错。

下一条语句,统计某个表中有多少行:

sql 复制代码
select count(*) from users;

floor 报错注入语句通常长这样:

sql 复制代码
select count(*),concat(0x3a,(database()),0x3a,floor(rand()*2)) x from information_schema.tables group by x;

该语句可能报错,可能正常,为什么?

因为 COUNT(*) 的加入,导致 MySQL 必须在内存里建立一张带计数器的临时哈希表。

它的结构长这样:

  • Key(主键/分组键) :存放 x 的字符串。因为是主键,所以里面的值绝对不允许重复。

  • Value(计数器) :存放 COUNT(*) 的统计数字。

现在,MySQL 开始一行行扫描 information_schema.tables 表。

MySQL 拿着第 1 行数据,第一次调用我们的表达式。此时 rand() 生成了一个数,假设计算结果为 :security:0。

MySQL 去查临时表:"现在有 :security:0 吗?" ➔ 没有(此时临时表完全是空的)。

判定 :这是一个新分组,MySQL 执行插入新行的动作,在写入主键的瞬间,MySQL重新调用 了一次表达式,假设运气不错,这次算出来的结果还是 :security:0。

结果:成功开辟新行。

此时临时表的状态: [:security:0] ➔ 计数为 1

接着 MySQL 扫描第 2 行数据,调用表达式。这次 rand() 变了,计算结果为 :security:1。

MySQL 去查临时表:"有 :security:1 吗?" ➔ 没有(目前只有 :security:0 )。

判定:又是一个新分组,MySQL 再次决定执行 INSERT 动作。

写入主键的瞬间,MySQL 重新调用了一次表达式。运气依然不错,算出来还是 :security:1。

结果:成功开辟第二行。

此时临时表的状态:

  • [:security:0] ➔ 计数为 1

  • [:security:1] ➔ 计数为 1

注意:此时 0 和 1 两个坑位都已经坐稳了。如果这句语句能撑过这一步,后面不管扫描多少行,都只会有 0 或 1,都不会再产生报错。

接下来我们看运气不好的情况:

假设在扫描前 2 行时,因为随机数的组合,临时表里目前只成功建立了一个房间:[:security:0] (1 房间还没被建立出来)

此时,MySQL 拿着第 3 行数据计算,rand() 恰好生成了一个让结果为 :security:1 的数字。

MySQL 拿着 :security:1 去查临时表:"有 :security:1 的房间吗?"

临时表回答:"没有 ,我这里现在只有 :security:0。"

MySQL 决定:"那就在临时表里新建一行(INSERT),把新组塞进去!"

就在插入的一瞬间,由于重新调用了表达式,rand() 突然变脸,导致整个 x 的计算结果变成了 :security:0

MySQL 拿着算出来的 :security:0,往临时表的新一行主键区里硬塞

临时表的主键索引瞬间报警:"疯了吧你!我第一行早就躺着一个:security:0了!主键必须唯一,你不能在查重的时候说是 1,写入的时候写成 0 让我重复!"

于是,整个 SQL 语句执行立刻中断,MySQL 抛出报错:

sql 复制代码
ERROR 1062 (23000): Duplicate entry ':security:0' for key '<group_key>'
相关推荐
代码什么用6 分钟前
Spring对IoC的实现
数据库·spring
hz567892 小时前
涉密视频会议设备配置指南:终端、音视频采集与配套设施选型
服务器·网络·数据库·安全·实时音视频·信息与通信·智能硬件
广州浮点FLOATLIC2 小时前
许可证服务器迁移后软件打不开:研发 IT 怎样定位连接问题
linux·服务器·数据库
程序员Sunday2 小时前
MySQL 为什么使用 B+ 树索引?把范围查询、回表和覆盖索引连起来
数据库·mysql
半杯咖啡半行码3 小时前
Qt开发实战:数据库、MV 模式、QProcess与串口通信全攻略
数据库·qt
Yyyyyy~3 小时前
[ Mysql ] 库的操作
mysql
小米里的大麦3 小时前
16 MySQL 事务
数据库·mysql
西柚小萌新3 小时前
【LLM&&AI应用开发 八股文】--4.3.Agent智能体(下)
java·开发语言·数据库
Titan20243 小时前
MySQL访问个人学习笔记
笔记·学习·mysql
浮链序3 小时前
怎么证明"你这个模型是偷我的"——把蒸馏变成取证工具
算法·安全·llm