<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>安全 on pbuff07</title><link>https://pbuff.cc/categories/%E5%AE%89%E5%85%A8/</link><description>Recent content in 安全 on pbuff07</description><generator>Hugo -- gohugo.io</generator><language>zh-CN</language><managingEditor>pbuff07</managingEditor><webMaster>pbuff07</webMaster><lastBuildDate>Thu, 09 Apr 2026 10:30:00 +0800</lastBuildDate><atom:link href="https://pbuff.cc/categories/%E5%AE%89%E5%85%A8/index.xml" rel="self" type="application/rss+xml"/><item><title>AChat 代码审计By Claude Code</title><link>https://pbuff.cc/posts/achat-%E4%BB%A3%E7%A0%81%E5%AE%A1%E8%AE%A1by-claude-code/</link><pubDate>Thu, 09 Apr 2026 10:30:00 +0800</pubDate><author>pbuff07</author><guid>https://pbuff.cc/posts/achat-%E4%BB%A3%E7%A0%81%E5%AE%A1%E8%AE%A1by-claude-code/</guid><description>&lt;p>对 AChat（一个 ChatGPT 管理后台）进行了一次完整的安全审计，记录已确认的漏洞。&lt;/p>
&lt;p>项目技术栈是 NestJS + Fastify + Prisma，用的是 JWT 认证，结构比较清晰。&lt;/p>
&lt;p>项目地址：&lt;a href="https://github.com/AprilNEA/AChat">AprilNEA/AChat&lt;/a>&lt;/p>
&lt;h2 id="公告接口未授权访问">公告接口未授权访问&lt;/h2>
&lt;p>审计过程中，首先梳理了全局的路由配置和认证标记。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-typescript" data-lang="typescript">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">// auth.guard.ts - 全局认证 Guard
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">@Public&lt;/span>()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">@Controller&lt;/span>()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">export&lt;/span> &lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">AppController&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">@Get&lt;/span>(&lt;span style="color:#e6db74">&amp;#39;/health&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">health() {&lt;/span> ... }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">@Post&lt;/span>(&lt;span style="color:#e6db74">&amp;#39;/announcement&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">async&lt;/span> &lt;span style="color:#a6e22e">newAnnouncement&lt;/span>(&lt;span style="color:#66d9ef">@Body&lt;/span>() &lt;span style="color:#a6e22e">data&lt;/span>: &lt;span style="color:#66d9ef">any&lt;/span>) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> { &lt;span style="color:#a6e22e">success&lt;/span>: &lt;span style="color:#66d9ef">true&lt;/span>, &lt;span style="color:#a6e22e">data&lt;/span>: &lt;span style="color:#66d9ef">await&lt;/span> &lt;span style="color:#66d9ef">this&lt;/span>.&lt;span style="color:#a6e22e">appService&lt;/span>.&lt;span style="color:#a6e22e">upsertAnnouncement&lt;/span>({ ...&lt;span style="color:#a6e22e">data&lt;/span> }) };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">@Delete&lt;/span>(&lt;span style="color:#e6db74">&amp;#39;/announcement/:id&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">async&lt;/span> &lt;span style="color:#a6e22e">deleteAnnouncement&lt;/span>(&lt;span style="color:#66d9ef">@Param&lt;/span>(&lt;span style="color:#e6db74">&amp;#39;id&amp;#39;&lt;/span>) &lt;span style="color:#a6e22e">id&lt;/span>: &lt;span style="color:#66d9ef">number&lt;/span>) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">await&lt;/span> &lt;span style="color:#66d9ef">this&lt;/span>.&lt;span style="color:#a6e22e">appService&lt;/span>.&lt;span style="color:#a6e22e">deleteAnnouncement&lt;/span>(&lt;span style="color:#a6e22e">id&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> { &lt;span style="color:#a6e22e">success&lt;/span>: &lt;span style="color:#66d9ef">true&lt;/span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>/announcement&lt;/code> 的 POST 和 DELETE 方法均未添加 &lt;code>@Public()&lt;/code> 或 &lt;code>@Roles(Role.Admin)&lt;/code> 装饰器，理论上需要 JWT 认证且要求管理员权限。&lt;/p>
&lt;p>实测验证：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>curl -X POST &lt;span style="color:#e6db74">&amp;#39;http://localhost:3001/api/announcement&amp;#39;&lt;/span> &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> -H &lt;span style="color:#e6db74">&amp;#39;Content-Type: application/json&amp;#39;&lt;/span> &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> -d &lt;span style="color:#e6db74">&amp;#39;{&amp;#34;title&amp;#34;: &amp;#34;test&amp;#34;, &amp;#34;content&amp;#34;: &amp;#34;hacked&amp;#34;}&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># HTTP 200&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># { &amp;#34;success&amp;#34;: true, &amp;#34;data&amp;#34;: { &amp;#34;id&amp;#34;: 1, ... } }&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>无需任何认证即可创建公告。删除接口同样可被未授权访问。&lt;/p>
&lt;p>问题根因在于：AppController 本身没有任何认证相关装饰器，而 NestJS 的全局 AuthGuard 对该 Controller 的部分路由保护失效，导致认证检查被绕过。&lt;/p>
&lt;p>攻击者可利用此漏洞发布钓鱼公告诱骗用户，造成实际财产损失。&lt;/p>
&lt;p>&lt;img src="https://pbuff-blogs-1257793641.cos.ap-chengdu.myqcloud.com//blogsimage-20260409184055353.png" alt="image-20260409184055353">&lt;/p>
&lt;p>修复方案：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-typescript" data-lang="typescript">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">import&lt;/span> { &lt;span style="color:#a6e22e">Role&lt;/span>, &lt;span style="color:#a6e22e">Roles&lt;/span> } &lt;span style="color:#66d9ef">from&lt;/span> &lt;span style="color:#e6db74">&amp;#39;@/common/guards/auth.guard&amp;#39;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">@Roles&lt;/span>(&lt;span style="color:#a6e22e">Role&lt;/span>.&lt;span style="color:#a6e22e">Admin&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">@Post&lt;/span>(&lt;span style="color:#e6db74">&amp;#39;/announcement&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">async&lt;/span> &lt;span style="color:#a6e22e">newAnnouncement&lt;/span>(&lt;span style="color:#66d9ef">@Body&lt;/span>() &lt;span style="color:#a6e22e">data&lt;/span>: &lt;span style="color:#66d9ef">any&lt;/span>) { ... }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">@Roles&lt;/span>(&lt;span style="color:#a6e22e">Role&lt;/span>.&lt;span style="color:#a6e22e">Admin&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">@Delete&lt;/span>(&lt;span style="color:#e6db74">&amp;#39;/announcement/:id&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">async&lt;/span> &lt;span style="color:#a6e22e">deleteAnnouncement&lt;/span>(&lt;span style="color:#66d9ef">@Param&lt;/span>(&lt;span style="color:#e6db74">&amp;#39;id&amp;#39;&lt;/span>) &lt;span style="color:#a6e22e">id&lt;/span>: &lt;span style="color:#66d9ef">number&lt;/span>) { ... }
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="其他发现">其他发现&lt;/h2>
&lt;p>审计过程中还发现以下问题:&lt;/p>
&lt;ol>
&lt;li>&lt;code>/api/auth/admin/setup&lt;/code> 标记了 &lt;code>@Public()&lt;/code>，全新部署时可被用于创建首个管理员账户&lt;/li>
&lt;li>&lt;code>/api/order/callback/xunhu&lt;/code> 支付回调的签名验证被注释，仅验证 appid，攻击者可伪造支付成功&lt;/li>
&lt;li>JWT 默认密钥为 &amp;ldquo;secret&amp;rdquo;，若未修改，攻击者可伪造任意用户 Token&lt;/li>
&lt;/ol></description></item></channel></rss>