CTF 总结:JWT 登录逻辑审计
记录 JWT 登录认证题中的源码线索、字段验证和 JavaScript 隐式类型转换问题。
一、考察知识点
- JWT 组成:Header、Payload、Signature
- HS256 对称签名验证
- Node.js(Koa)接口流程
- JavaScript 隐式类型转换
- 身份认证逻辑审计
- 从源码寻找漏洞点
二、背景
这题由登录、注册和后台组成。可以正常注册账号并登录后台,但是在后台输入内容时会弹窗:
permission denied
三、分析过程
1. 检查是否可以触发 XSS
创建账号时,先检查输入能不能触发 XSS。
结果是内容在输出端全部被转换为大写并进行了转义。
2. 创建多个账号检查越权
创建多个账号,观察是否存在越权。
发现:
username不能是admin- 只有最新创建的用户可以登录
因此,username 是重点关注对象。
3. 从开发者工具寻找遗留信息
查看浏览器开发者工具(F12),检查是否存在遗留信息。
发现作者提示使用了 koa-static 架构,并且主动说明网站根目录的配置问题,最终获得 app.js 源码。

Koa 项目的目录架构如下,其中包含控制器、数据库操作封装、数据模型、路由和项目入口等部分:

根据架构图中 controllers 目录用于存放接口控制器这一线索,尝试直接访问题目环境中的控制器文件:
https://6a88a12301b5b94ce38d876c.http-ctf2.dasctf.com/controllers/api.js
该地址成功返回了 api.js 源码,从而进一步确认了静态文件目录配置不当导致的源码泄露问题。这个线索很关键,因为后续 JWT 注册和登录逻辑的分析都建立在获得源码的基础上。

4. 分析 JWT 登录验证
使用 Burp Suite 抓包后发现,登录数据不是根据 Session 验证,而是验证 JWT。
接着寻找 JWT 中哪些字段参与最终认证。单独修改 HS256 相关内容或者 secretid 都会返回错误,而且这些报错疑似是作者给出的侧面提示,不像正常业务中的报错。
只处理 HS256 相关内容时的响应:

只处理 secretid 时的响应:

由此得出:两个参数都需要修改。
根据源码,可以将 secretid 设置为空字符串。空字符串既不等于 undefined 或 null,又会在 <、>= 数值比较中被临时转换为 0,从而通过 secretid 的范围检查;变量本身仍然是字符串。
5. 修改 JWT 参数
使用在线 JWT 工具修改参数。源码中的签名逻辑为:
jwt.sign(
{ secretid, username, password },
secret,
{ algorithm: "HS256" }
)
修改内容:
- 将 JWT Header 中的
alg从HS256改为none - 将
secretid设置为空字符串 - 将
username设置为admin
通过以上修改,可以绕过注册限制,直接登录并获得 admin 权限。
使用工具
浏览器开发者工具(F12)
用于查看前端请求、响应和遗留信息,并寻找题目给出的架构提示。
Burp Suite
用于抓取登录请求,观察 JWT 数据,并修改请求参数进行验证。
在线 JWT 工具
用于解析 JWT 的 Header 和 Payload,并修改题目中的算法及认证字段。