深入 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>'
相关推荐
IvorySQL3 小时前
PostgreSQL 日报 | JOIN 与外键优化(8 月 9 日)
数据库·postgresql
ltl3 小时前
自治数据库十年回顾:Peloton、NoisePage、OtterTune 到云原生 auto-tuning
数据库
灯澜忆梦4 小时前
【MySQL10】进阶篇 | 索引_#2性能优化
数据库·sql·mysql·性能优化
猎嘤一号5 小时前
博弈论(Game Theory)的理论、算法与工程
人工智能·算法·安全·博弈论
C++ 老炮儿的技术栈6 小时前
从 Qt Designer 属性编辑器的层级可以看到继承链
c语言·数据库·c++·qt·sqlite·visual studio
MC皮蛋侠客6 小时前
Redis 系列(一):全景与最小闭环——从 `SET` 命令到内存数据结构
数据结构·数据库·redis
观远数据7 小时前
当ChatBI遇上数据合规:AI+BI规模化落地的安全边界如何划定
大数据·人工智能·安全
zt1985q8 小时前
本地部署开源网络书签与内容管理工具 Karakeep 并实现外部访问
运维·服务器·网络·数据库·网络协议·开源
YYJ-F8 小时前
Anthropic——AI安全人工智能研究公司,核心产品Claude Code辅助编程
人工智能·安全
山东科恩光电8 小时前
安全触边系统在工业设备运行中的安全保障与应用分析
安全