ISCC 擂台赛八卦星图馆:NoSQL 注入、越权、竞争与随机数预测
一、题目背景
这道题按页面提示依次通过四关:
1 | 乾 -> 兑 -> 离 -> 震 |
每一关对应一个不同类型的 Web 安全问题。完整利用链如下:
1 | MongoDB 注入登录 |
这是一道很典型的综合 Web 题,覆盖了认证绕过、权限控制、并发安全和伪随机数预测。
二、乾卦:NoSQL 注入登录
访问 /qian 后可以知道入口用户为:
1 | qingyun |
后端使用 MongoDB,登录接口为:
1 | POST /qian/login |
如果后端直接将 JSON 请求体传给 MongoDB 查询,例如:
1 | db.users.findOne({ |
并且没有限制 password 必须是字符串,那么就可以传入 MongoDB 查询操作符。
Payload:
1 | { |
含义是密码匹配任意内容。登录成功后拿到当前 session,身份为:
1 | apprentice |
三、兑卦:Mass Assignment 越权
进入下一关后发现存在用户信息更新接口:
1 | POST /dui/update |
如果后端直接把用户提交的 JSON 合并到用户对象中,没有做字段白名单,就会出现 Mass Assignment 漏洞。
提交:
1 | { |
如果服务端没有限制普通用户修改 role 字段,就可以把自己的角色提升为 elder,从而进入后续令牌领取流程。
这个漏洞本质是权限字段被客户端控制。
四、离卦:并发竞争获取令牌
下一关需要获取 3 个司命令牌。领取接口:
1 | POST /li/grab |
正常情况下单线程只能领取 1 个 token,但后端领取逻辑不是原子操作,可能类似:
1 | 读取当前用户领取数量 |
当多个请求并发到达时,它们可能同时通过“领取数量检查”,从而多次写入 token。
利用方式:
1 | 1. 使用 NoSQL 注入登录 |
示例代码结构:
1 | from concurrent.futures import ThreadPoolExecutor |
凑够 3 个 token 后进入最后一关。
五、震卦:Math.random 状态预测
/zhen/history 返回最近开奖历史:
1 | { |
页面提示签号生成方式为:
1 | Math.floor(Math.random() * 1000000) |
V8 的 Math.random() 使用 xorshift128+ 生成随机数。每期虽然只泄露 0 ~ 999999 的整数结果,但连续历史数量足够时,可以用 Z3 对内部状态建立约束。
关键注意点:
/zhen/history返回顺序是新到旧,求解时要反转为旧到新。- 每个历史
number对应一个随机浮点数区间,而不是完整随机数。 - 恢复初始状态后,需要按历史数量推进到当前轮。
- 最后用当前状态计算本轮预测值。
六、提交预测
预测接口:
1 | POST /zhen/predict |
请求体:
1 | { |
如果预测正确但提示权限不足,可以继续利用 /dui/update 把角色改成:
1 | { |
然后重新提交预测,得到最终结果。
七、自动化脚本思路
完整自动化脚本可以按以下步骤写:
1 | 1. NoSQL 注入登录 qingyun |
依赖:
1 | pip install requests z3-solver |
八、漏洞总结
这道题不是单点漏洞,而是多漏洞链式利用:
| 阶段 | 漏洞类型 | 影响 |
|---|---|---|
| 乾卦 | NoSQL 注入 | 绕过登录 |
| 兑卦 | Mass Assignment | 修改角色 |
| 离卦 | 条件竞争 | 超额领取 token |
| 震卦 | 伪随机数预测 | 预测签号 |
每个漏洞单独看都不一定直接拿到结果,但组合起来就形成了完整攻击路径。
九、防护建议
1. NoSQL 注入防护
- 限制字段类型,密码必须是字符串。
- 禁止用户输入对象操作符。
- 使用固定查询结构和参数校验。
2. Mass Assignment 防护
- 更新用户信息时使用字段白名单。
role、isAdmin、uid等敏感字段不允许客户端修改。- 权限变更必须走单独审核流程。
3. 条件竞争防护
- 令牌领取逻辑使用数据库原子操作。
- 对关键资源加锁。
- 领取次数检查和写入必须在同一事务中完成。
4. 随机数安全
- 不使用
Math.random()生成安全相关结果。 - 使用密码学安全随机数。
- 不向客户端暴露过多历史随机输出。
十、复盘
这道题很适合训练综合 Web 思维。它提醒我:真实攻击链里经常不是一个漏洞直接打穿,而是认证、权限、并发和业务逻辑连续出现小问题,最终被串成完整路径。
做这类题时,建议按业务流程走,不要只盯单个接口。每进入一个新状态,都重新观察页面、接口、角色和返回内容,往往能发现下一段链路。