深入 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

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

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

假设在扫描前 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>'
相关推荐
花生了什么事o18 小时前
DDD:领域驱动设计的初步认识
java·数据库
BGK11235818 小时前
基于qemu_v8+optee 4.00 平台构建 ca/ta
java·大数据·数据库
Ethan010719 小时前
Mybatis数据源切换
mysql
数据知道19 小时前
网络地址转换(NAT)与安全:映射原理、打洞与内网暴露风险
网络·安全·智能路由器·内网安全
何中应19 小时前
Spring Boot整合Doris
java·数据库·spring boot
J-Tony1119 小时前
【Redis】数据结构&&持久化
数据结构·数据库·redis
KaMeidebaby20 小时前
卡梅德生物技术快报|原核膜蛋白表达优化实操手册,膜蛋白的纯化梯度洗脱完整流程
前端·网络·数据库·人工智能·算法
Summer-Bright20 小时前
深度 | Agent 协议标准化:一场决定了 AI 经济底层规则的基础设施战争
java·数据库·人工智能·ai
我叫张小白。20 小时前
LangChain 结构化输出(Structured Output)技术文档
java·数据库·langchain
吴声子夜歌20 小时前
MongoDB 4.x——数据模型
数据库·mongodb