0x01 故事背景
那是 HW 开展的第三天,我正盯着某市级智慧教育平台的页面出神。
作为一名红队攻击队员,面对这种大型公共服务系统,常规的 SQL 注入或文件上传往往被防守方的 WAF 挡得死死的。我深吸一口气,决定把目光从“正面强攻”转向“逻辑挖掘”。
系统的首页看起来四平八稳,但在我利用个人账号通过“随申办”单点登录进入后台后,右上角一个“开发者中心“的入口引起了我的注意。经验告诉我,这种面向开发者的功能模块,往往是权限校验的“重灾区”。
0x02 信息收集
进入开发者中心后,我发现了一个“实名认证”的功能。

习惯性地打开 Burp Suite,点击“查看认证信息”,捕捉到了这样一个 GET 请求:
GET /tenant/tenantAuth/personal/authentication/getPersonalAuthentication/65 HTTP/1.1
Host: api.target-edu.com
Authorization: Bearer [REDACTED_JWT_TOKEN]

注意到 URL 末尾的那个 65 了吗?这简直是渗透师眼中的“暗号”。我的直觉瞬间紧绷:这个 ID 是不是代表当前用户?如果我把它改成 66, 67 甚至更小的值呢?
我将请求发送到 Repeater,将 id 修改为 64。点击发送的那一秒,心跳不由自主地加快了。
0x03 漏洞利用
响应包弹出来了。
Bingo! 服务器不仅没有返回“无权访问”,反而吐出了一大串 JSON 数据。然而,还没等我欢呼,一盆冷水泼了下来:
{
"code": "200",
"data": {
"account": "mfGjYQDqDwqdOl/1ohujiQ==",
"password": "m/3vm12CBmKHfS7jPNG+Vtuh8X2BGbUrZh8rE+RGaXPcJ53jteNEm7U1/qqYrPYY",
"userName": "/LGR3uXXsrzBzHwzKg8SUQ==",
"documents": "BLEMkdDN2NMbGWhNztQp03GKd9QIdsBhMJBT68IsqXI="
}
}
所有的关键字段(账号、密码、真实姓名、证件号)全是密文。开发者显然意识到了数据敏感性,在传输层做了 AES 加密。
但我并没打算放弃。既然浏览器能正常显示我的个人信息,说明解密逻辑一定在前端 JS 里!
我开启了 Chrome 的开发者工具,开始了一场硬核的“寻宝游戏”。
- 定位全局搜索:搜索 decrypt、AES、setContext 等关键字。
- 断点调试:我在一个名为 useEncryptKey-8L73w.js 的文件里发现了一个可疑的解密函数 Kr。
- 真相浮出水面:我给 Kr 函数打上断点,刷新页面。随着循环的跳动,在第五轮迭代时,一个 32 位的字符串在局部变量窗口中一闪而过!

发现硬编码密钥: 3j4N5k7P9qBvDx[REDACTED]
开发者为了方便,直接将 AES 密钥硬编码在了 JS 逻辑中,并采用了 ECB 模式进行加密
以document字段为例:
“documents”:”BLEMkdDN2NMbGWhNztQp03GKd9QIdsBhMJBT68IsqXI=”,发现利用获取到的密钥可以成功解密出身份证号:

一个清晰的身份证号跃然纸上。通过遍历 ID 范围,我成功获取了该系统内数千名用户的详细敏感数据
0x05 防御与反思
这次漏洞的产生,归根结底是“过分信任客户端”造成的。
- 越权修复(Root Cause):
- 水平越权检查:后端接口在返回数据前,必须验证当前 Session 中的 UserID 是否与请求的 ID 一致。绝对不能仅仅依赖前端传参。
- 加密安全:
- 严禁硬编码:AES 密钥不应存在于静态资源(JS 文件)中。
- 动态协商:建议采用非对称加密(RSA)动态交换对称密钥,或在服务端完成敏感数据的脱敏处理后再下发。
- 最小化暴露面:
- 对于“开发者中心”这种高敏感模块,应增加二次身份核验。