公司在 22 周年司庆活动期间推出了一款 H5 小游戏。
在游戏中,玩家通过控制小人躲避障碍来不断提高成绩(跑步米数),在游戏中还会随机掉落各种道具,率先收集齐全套道具的玩家则有机会获得奖品。
本着学(na)习(jiang)的精神对小游戏进行来一番研究,发现游戏存在漏洞,能通过伪造请求的方式直接刷满道具。
游戏介绍
游戏内容前面已经做过简单介绍,补充一些游戏截图 ⬇️
游戏分析
通过抓包,找到几个关键请求如下:
在游戏中会有四种随机的道具掉落,4 种道具凑满 22 个时即可有机会获得奖品。
分析上面的请求很明显能够猜到 item_id
应该代表了四种不同的道具类型。
token
可能是某个密钥或者身份标识,通过对比好几个其他请求后发现 token 总是不变的,这里暂且可以认为是代表了当前用户的身份标识。
t
明显是一个时间戳,这些都可以很轻易的进行伪造。
剩下最后一个 sign
则是对当前请求内容的一个签名,表示游戏还是有进行简单的安全验证的。
与上面 drop
请求类似,在碰到障碍后结束游戏并发起 end
请求上报成绩。除了 sign
字段,我们基本上已经可以完全伪造上面的请求。
前后端交互部分我们基本已经摸清,剩下的就是搞清楚 sign
是如何而来的。
Javascript 部分
带着问题出发,我们没有必要完完全全分析整个游戏的 JavaScript,只需要针对性查看 sign
部分即可。
看起来 sign
就是这个 getMd5
方法通过计算参数 e
得到的签名。继续顺藤摸瓜找到 getMd5
方法:
到这里基本可以确定了,sign
是通过将请求数据的 key
按照字典顺序排序后,再将 value
拼接起来,最后进行 sha256
计算得的到 16 进制签名。
伪造请求
将相关代码扒出来,加上伪造的相关逻辑:
const token = ''
const cookie = ''
const dropURL = "xxxxx/api/api/collect/drop";
const endURL = 'xxxxx/api/api/game/end'
const headers = {
"Content-Type": "application/x-www-form-urlencoded",
"x-requested-with": "xxx",
cookie,
};
async function getItems () {
for (let type = 1; type < 5; type++) {
for (let i = 0; i < 22; i++) {
const requestData = {item_id: type, token, t: Date.now()}
requestData.sign = getMd5(requestData);
await axios.post(dropURL, requestData, { headers })
.then((res) => {
res.data.code === 200 && console.log(`成功获取第 ${i + 1} 个道具 ${type}`)
})
.catch(e => {
console.log(`获取道具 ${type} 失败`)
});
}
}
}
function run (meter) {
const requestData = {meter, token, t: Date.now()}
requestData.sign = getMd5(requestData);
axios.post(endURL, requestData, { headers }).then(res => {
res.data.code === 200 && console.log(`成功跑了: ${meter} 米`)
})
}
console.log('开始游戏 ...')
getItems().then(() => {
run(6666)).then(() => {
console.log('游戏结束')
})
})
效果 ⬇️