<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>MySQL on SYM01</title>
    <link>https://sym01.com/tags/mysql/</link>
    <description>Recent content in MySQL on SYM01</description>
    <generator>Hugo</generator>
    <language>zh-CN</language>
    <lastBuildDate>Sun, 17 May 2026 02:50:19 +0800</lastBuildDate>
    <atom:link href="https://sym01.com/tags/mysql/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>MySQL Order By 注入：行为背后的原理分析</title>
      <link>https://sym01.com/posts/2026/mysql-order-by-behavior-analysis/</link>
      <pubDate>Sun, 17 May 2026 02:50:19 +0800</pubDate>
      <guid>https://sym01.com/posts/2026/mysql-order-by-behavior-analysis/</guid>
      <description>&lt;h1 id=&#34;0x00-前言&#34;&gt;0x00 前言&lt;/h1&gt;
&lt;p&gt;时隔近两年，回头看了一下之前写的&lt;a href=&#34;https://sym01.com/posts/2024/mysql-tricks/&#34;&gt;《MySQL Order By 注入的小技巧》&lt;/a&gt;，结尾留了一句：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;至于 MySQL 的行为为何如此奇怪，本人并没有了解清楚，有待研究。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;最近在 Claude Code 的帮助下终于把这个坑填上了。本文基于 MySQL 8.4 源码（tag &lt;code&gt;mysql-8.4.0&lt;/code&gt;）、官方 Bug Tracker，以及 Docker 里跑 &lt;code&gt;mysql:8.4&lt;/code&gt; 抓到的 &lt;code&gt;optimizer_trace&lt;/code&gt;，把这套行为彻底捋清楚。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;结论先抛在前&lt;/strong&gt;：当年那个「直接写 sleep / GTID_SUBSET 没效果、包成子查询就能跑」并不是优化器的&lt;strong&gt;三条独立优化路径叠加&lt;/strong&gt;的结果：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;JOIN::remove_const&lt;/code&gt; 在 &lt;code&gt;plan_is_const()&lt;/code&gt; 为真时，把整段 &lt;code&gt;ORDER BY&lt;/code&gt; 直接砍掉。&lt;/li&gt;
&lt;li&gt;同一函数在逐项扫描时，会把 &lt;code&gt;used_tables() == 0&lt;/code&gt; 的常量项静默丢弃，&lt;strong&gt;完全不调用 &lt;code&gt;val_int&lt;/code&gt;&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;子查询里的 derived table（派生表）会在优化阶段被独立物化，与外层 &lt;code&gt;ORDER BY&lt;/code&gt; 的命运无关 —— 这才是 POC 写法稳定可用的根本原因。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Oracle 工程师 Roy Lyseng 在 &lt;a href=&#34;https://bugs.mysql.com/bug.php?id=67632&#34;&gt;Bug #67632&lt;/a&gt; 已经明确表态，这种「能算就提前算」的激进求值是** by design**。&lt;/p&gt;
&lt;h1 id=&#34;0x01-重新审视现象&#34;&gt;0x01 重新审视现象&lt;/h1&gt;
&lt;p&gt;用 10 行整数表 &lt;code&gt;rows10&lt;/code&gt; 和 MySQL 内置的 &lt;code&gt;dual&lt;/code&gt;（行为上等价于一行的常量表）作为两种外层 FROM，跑下面 5 类 &lt;code&gt;ORDER BY&lt;/code&gt; 表达式：&lt;/p&gt;</description>
    </item>
    <item>
      <title>MySQL Order By 注入的小技巧</title>
      <link>https://sym01.com/posts/2024/mysql-tricks/</link>
      <pubDate>Mon, 17 Jun 2024 23:10:27 +0800</pubDate>
      <guid>https://sym01.com/posts/2024/mysql-tricks/</guid>
      <description>&lt;h1 id=&#34;0x00-前言&#34;&gt;0x00 前言&lt;/h1&gt;
&lt;p&gt;前几年在工作上遇到了一个 MySQL 的注入问题，注入点在 &lt;code&gt;order by&lt;/code&gt; 后面，类似于&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;select&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;from&lt;/span&gt; dual &lt;span style=&#34;color:#66d9ef&#34;&gt;order&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;by&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;;&lt;span style=&#34;color:#f92672&#34;&gt;`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;其中， &lt;code&gt;*&lt;/code&gt; 所在的地方即为注入点。&lt;/p&gt;
&lt;p&gt;随即本人尝试对这个点进行注入，经过若干次尝试后均以失败告终。 具体尝试结果如下：&lt;/p&gt;
&lt;h4 id=&#34;1-报错注入&#34;&gt;1. 报错注入&lt;/h4&gt;
&lt;p&gt;尝试使用常见的 &lt;code&gt;GTID_SUBSET()&lt;/code&gt; 函数进行报错注入，发现 &lt;code&gt;order by&lt;/code&gt; 后没有发挥应用的效果。&lt;/p&gt;
&lt;picture&gt;
  &lt;source srcset=&#34;https://sym01.com/posts/2024/mysql-tricks/1_hu_b35c696181d845d7.webp&#34; type=&#34;image/webp&#34; /&gt;
  &lt;img
    src=&#34;./1.png&#34; 
    loading=&#34;lazy&#34;
    decoding=&#34;async&#34;
  /&gt;
&lt;/picture&gt;

&lt;h4 id=&#34;2-延时注入&#34;&gt;2. 延时注入&lt;/h4&gt;
&lt;p&gt;尝试使用的 &lt;code&gt;sleep()&lt;/code&gt; 函数进行延时注入，发现 &lt;code&gt;order by&lt;/code&gt; 后没有发挥应用的效果。&lt;/p&gt;
&lt;picture&gt;
  &lt;source srcset=&#34;https://sym01.com/posts/2024/mysql-tricks/2_hu_94988ac369ae70a3.webp&#34; type=&#34;image/webp&#34; /&gt;
  &lt;img
    src=&#34;./2.png&#34; 
    loading=&#34;lazy&#34;
    decoding=&#34;async&#34;
  /&gt;
&lt;/picture&gt;

&lt;p&gt;此时你可能会发现， &lt;code&gt;order by&lt;/code&gt; 后面的行为稍显怪异，难以琢磨。
难道就没有办法利用这个注入了吗？肯定得有，不然这篇文章就写不下去了。&lt;/p&gt;
&lt;h1 id=&#34;0x01-order-by-后的注入-poc&#34;&gt;0x01 &lt;code&gt;order by&lt;/code&gt; 后的注入 POC&lt;/h1&gt;
&lt;p&gt;在经过若干次尝试后，本人发现下面的查询语句能够触发报错。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;select&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;from&lt;/span&gt; dual &lt;span style=&#34;color:#66d9ef&#34;&gt;order&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;by&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;,(&lt;span style=&#34;color:#66d9ef&#34;&gt;SELECT&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;)&lt;span style=&#34;color:#66d9ef&#34;&gt;FROM&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;SELECT&lt;/span&gt;(GTID_SUBSET(&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;,&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;))&lt;span style=&#34;color:#66d9ef&#34;&gt;where&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;))test); &lt;span style=&#34;color:#f92672&#34;&gt;#&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;报错&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;select&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;from&lt;/span&gt; dual &lt;span style=&#34;color:#66d9ef&#34;&gt;order&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;by&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;,(&lt;span style=&#34;color:#66d9ef&#34;&gt;SELECT&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;)&lt;span style=&#34;color:#66d9ef&#34;&gt;FROM&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;SELECT&lt;/span&gt;(GTID_SUBSET(&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;,&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;))&lt;span style=&#34;color:#66d9ef&#34;&gt;where&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;))test); &lt;span style=&#34;color:#f92672&#34;&gt;#&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;不报错&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;picture&gt;
  &lt;source srcset=&#34;https://sym01.com/posts/2024/mysql-tricks/3_hu_7ebc6beb66ea9cc.webp&#34; type=&#34;image/webp&#34; /&gt;
  &lt;img
    src=&#34;./3.png&#34; 
    loading=&#34;lazy&#34;
    decoding=&#34;async&#34;
  /&gt;
&lt;/picture&gt;

&lt;p&gt;因此，完全可以利用上述语句进行 bool 类型的注入， 甚至直接一点，利用该特性进行报错注入：&lt;/p&gt;
&lt;picture&gt;
  &lt;source srcset=&#34;https://sym01.com/posts/2024/mysql-tricks/4_hu_8ec20920074bce86.webp&#34; type=&#34;image/webp&#34; /&gt;
  &lt;img
    src=&#34;./4.png&#34; 
    loading=&#34;lazy&#34;
    decoding=&#34;async&#34;
  /&gt;
&lt;/picture&gt;

&lt;p&gt;或者将 &lt;code&gt;GTID_SUBSET&lt;/code&gt; 换成 &lt;code&gt;sleep&lt;/code&gt; 进行延时注入。&lt;/p&gt;
&lt;h1 id=&#34;0x02-未完待续&#34;&gt;0x02 未完待续&lt;/h1&gt;
&lt;p&gt;本文能够利用一些特殊的 SQL，对 &lt;code&gt;order by&lt;/code&gt; 后的注入点进行注入。然而，这个只是表现，并非原理。至于 MySQL 的行为为何如此奇怪，本人并没有了解清楚，有待研究。&lt;/p&gt;</description>
    </item>
  </channel>
</rss>
