MySQL Order By 注入:行为背后的原理分析

0x00 前言 时隔近两年,回头看了一下之前写的《MySQL Order By 注入的小技巧》,结尾留了一句: 至于 MySQL 的行为为何如此奇怪,本人并没有了解清楚,有待研究。 最近在 Claude Code 的帮助下终于把这个坑填上了。本文基于 MySQL 8.4 源码(tag mysql-8.4.0)、官方 Bug Tracker,以及 Docker 里跑 mysql:8.4 抓到的 optimizer_trace,把这套行为彻底捋清楚。 结论先抛在前:当年那个「直接写 sleep / GTID_SUBSET 没效果、包成子查询就能跑」并不是优化器的三条独立优化路径叠加的结果: JOIN::remove_const 在 plan_is_const() 为真时,把整段 ORDER BY 直接砍掉。 同一函数在逐项扫描时,会把 used_tables() == 0 的常量项静默丢弃,完全不调用 val_int。 子查询里的 derived table(派生表)会在优化阶段被独立物化,与外层 ORDER BY 的命运无关 —— 这才是 POC 写法稳定可用的根本原因。 Oracle 工程师 Roy Lyseng 在 Bug #67632 已经明确表态,这种「能算就提前算」的激进求值是** by design**。 0x01 重新审视现象 用 10 行整数表 rows10 和 MySQL 内置的 dual(行为上等价于一行的常量表)作为两种外层 FROM,跑下面 5 类 ORDER BY 表达式: ...

May 17, 2026 · 4 min · 793 words · SYM01

MySQL Order By 注入的小技巧

0x00 前言 前几年在工作上遇到了一个 MySQL 的注入问题,注入点在 order by 后面,类似于 select 1 from dual order by "", *;` 其中, * 所在的地方即为注入点。 随即本人尝试对这个点进行注入,经过若干次尝试后均以失败告终。 具体尝试结果如下: 1. 报错注入 尝试使用常见的 GTID_SUBSET() 函数进行报错注入,发现 order by 后没有发挥应用的效果。 2. 延时注入 尝试使用的 sleep() 函数进行延时注入,发现 order by 后没有发挥应用的效果。 此时你可能会发现, order by 后面的行为稍显怪异,难以琢磨。 难道就没有办法利用这个注入了吗?肯定得有,不然这篇文章就写不下去了。 0x01 order by 后的注入 POC 在经过若干次尝试后,本人发现下面的查询语句能够触发报错。 select 1 from dual order by "",(SELECT(1)FROM(SELECT(GTID_SUBSET(2,2))where(1=1))test); # 报错 select 1 from dual order by "",(SELECT(1)FROM(SELECT(GTID_SUBSET(2,2))where(1=2))test); # 不报错 因此,完全可以利用上述语句进行 bool 类型的注入, 甚至直接一点,利用该特性进行报错注入: 或者将 GTID_SUBSET 换成 sleep 进行延时注入。 0x02 未完待续 本文能够利用一些特殊的 SQL,对 order by 后的注入点进行注入。然而,这个只是表现,并非原理。至于 MySQL 的行为为何如此奇怪,本人并没有了解清楚,有待研究。

June 17, 2024 · 1 min · 84 words · SYM01