如果你问我人生梦想是什么?
我可能会说:有什么天天嫖麦当劳优惠券狠狠吃更棒的事了呢😋
本文所谈及技术只为网络安全人员进行服务器检测或维护参考,仅供学习交流,请勿用于违法用途。
(碰壁了很多次,js代码经过混淆的可读性很差,代码量太大期间火狐崩溃了好几次😭)
页面: aHR0cHM6Ly9jZG4ubWNkLmNuL2Ntcy9wYWdlcy9RUXdhbGxldC5odG1s
首先抓取一键领取的数据包进行分析
- 尝试修改
receiveQuantity
为2,返回领取失败
对比两次请求可以发现,payload没有发生变化,改变的是请求头的Nonce
,St
,Sign
参数,而且容易看出St
参数是时间戳,Nonce
参数是时间戳拼接了一段字符串
而请求前并未有GetSign
相关请求,因此考虑是js本地进行Sign
的生成,定位Onclick
过程的js
- 尝试搜索请求的url:
/bff/member/coupon/bind
,缩短为/coupon/bind
搜索,发现couponsBind
函数(后续弯弯绕绕没有发现有用信息)
尝试搜索:sign
,结果太多;搜索.sign
,设置断点调试,得出Object(i.sign)(t,{})
,i.sign
是个函数对象,对 t 进行处理,返回了sign
值
发现c(e)
里调用了u(n,t)
进行处理,继续断点调试,我们深入到该函数,发现调用了e(b)
我们将所有相关的函数全部扒到本地,并把一些固定字符串列表的索引给直接取出字符串来赋值(x = test[1] -> x = ‘helloworld’)
// "e0a803e042c3f658cf31d9b09454f914"
var n =
{
wordsToBytes: function (e) {
for (var t = [
], n = 0; n < 32 * e.length; n += 8) t.push(e[n >>> 5] >>> 24 - n % 32 & 255);
return t
},
bytesToHex: function (e) {
for (var t = [
], n = 0; n < e.length; n++) t.push((e[n] >>> 4).toString(16)),
t.push((15 & e[n]).toString(16));
return t.join('')
},
}
function a(e) {
var t = [
];
for (var n in e) t.push(n);
return t
}
function r(e) {
var t = [
];
for (var n in e) t.push(e[n]);
return t
}
function e(e, t) {
if (null == e) throw new Error('Illegal argument ' + e);
var n = r.wordsToBytes(u(e, t));
return t && t.asBytes ? n : t && t.asString ? a.bytesToString(n) : r.bytesToHex(n)
}
var u = function (o) {
var i = arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : {
},
u = {
"data": {
"channelCode": "03",
"coupons": [
{
"couponId": "156BBFBC389F9EA666E2FACD40BC5605",
"receiveQuantity": 1,
"newUserFlag": 0
},
{
"couponId": "45F09AFEE6E455A7175B1085C5012321",
"receiveQuantity": 1,
"newUserFlag": 0
}
],
"pageId": "lRLlag9R"
},
"params": {},
"path": [],
"headers": {
"ct": "ct=10",
"nonce": "nonce=1676061464467544235",
"sid": "sid=c9643a4402552c67ca7a5a75cdee70b4_",
"st": "st=1676061464"
},
"url": "https://api.mcd.cn/bff/member/coupon/bind"
};
u.headers.ct = u.headers.ct || 10;
var c = u.headers.ct,
s = o.env,
l = '71d414c0-8fe1-495d-ac55-207414632479';
// s || (s = u.url.match(/-dev\./) ? 'dev' : u.url.match(/-sit\./) ? 'sit' : u.url.match(/-uat\./) ? 'uat' : u.url.match(/-pt\./) ? 'pt' : 'prd'),
n[s] && (l = n[s][c] || t[c]),
Array.isArray(u.path) || (u.path = [
]);
var f = {
};
a(u.params).sort().forEach((function (e) {
(0 === u.params[e] || !0 === u.params[e] || !1 === u.params[e] || u.params[e]) && (!0 === u.params[e] ? f[e] = e + '=true' : !1 === u.params[e] ? f[e] = e + '=false' : f[e] = e + '=' + u.params[e])
})),
u.params = f;
var d = {
},
p = [
'ct',
'language',
'ov',
'p',
'sid',
'token',
'v',
'st',
'nonce'
];
a(u.headers).sort().filter((function (e) {
(0 === u.headers[e] || u.headers[e]) && p.includes(e) && (d[e] = e + '=' + u.headers[e])
})),
u.headers = d;
var h = [
],
v = 101 === c || 102 === c ? '#' : '&';
if ('#' === v && h.push(r(u.headers).join(v)), u.data) {
var y = "{\"channelCode\":\"03\",\"coupons\":[{\"couponId\":\"156BBFBC389F9EA666E2FACD40BC5605\",\"receiveQuantity\":1,\"newUserFlag\":0},{\"couponId\":\"45F09AFEE6E455A7175B1085C5012321\",\"receiveQuantity\":1,\"newUserFlag\":0}],\"pageId\":\"lRLlag9R\"}";
'{}' !== y && h.push(y)
}
var m,
g = r(u.params).join(v);
if (g && h.push(g), h.push(u.path.join(',')), '&' === v && h.push(r(u.headers).join(v)), (h = h.filter((function (e) {
return e && '{}' !== e
}))).length > 0) {
l ? h.push('key=' + l) : console.warn('[WARN] ct='.concat(c, ' is not in pre-defined keys.'));
var b = h.join(v);
// i.debug && console.log('[DEBUG] @omc/api-signer - plainText: ', b),
m = e(b)
}
return m
};
n = {
"params": {
"putChannel": "03",
"pageId": "lRLlag9R",
"cityCode": "310100",
"sid": "c9643a4402552c67ca7a5a75cdee70b4_"
},
"headers": {
"common": {
"Accept": "application/json, text/plain, */*"
},
"delete": {},
"get": {},
"head": {},
"post": {
"Content-Type": "application/x-www-form-urlencoded"
},
"put": {
"Content-Type": "application/x-www-form-urlencoded"
},
"patch": {
"Content-Type": "application/x-www-form-urlencoded"
},
"tid": "00003TuM",
"sid": "c9643a4402552c67ca7a5a75cdee70b4_",
"sv": "v3",
"nonce": "1676058630105631320",
"st": 1676058630,
"ct": 10
},
"url": "https://api.mcd.cn/bff/member/page/coupon/customer/getable"
};
bgg = "{\"channelCode\":\"03\",\"coupons\":[{\"couponId\":\"156BBFBC389F9EA666E2FACD40BC5605\",\"receiveQuantity\":1,\"newUserFlag\":0},{\"couponId\":\"45F09AFEE6E455A7175B1085C5012321\",\"receiveQuantity\":1,\"newUserFlag\":0}],\"pageId\":\"lRLlag9R\"}&ct=10&nonce=1676060716058879900&sid=c9643a4402552c67ca7a5a75cdee70b4_&st=1676060716&key=71d414c0-8fe1-495d-ac55-207414632479"
// "cityCode=310100&pageId=lRLlag9R&putChannel=03&sid=c9643a4402552c67ca7a5a75cdee70b4_&ct=10&nonce=1676058630105631320&sid=c9643a4402552c67ca7a5a75cdee70b4_&st=1676058630&key=71d414c0-8fe1-495d-ac55-207414632479"
console.log(e(bgg));
尝试运行,结果栈溢出报错,因为发生了死循环
- 补充nonce和st的获取
经过分析,我们发现,u(e,t)
并不是前面所提到的u,而是一个新的函数。而且关键性的,我们发现了该函数下存在1732584193
等幻数,这些是MD5加密的特征
所以e(b)
本质就是将 b 进行了MD5加密,我们断点调试截获 b 来验证一下
成功破解sign
的加密逻辑🎉🎉
后续:尝试写个python脚本
import time import random import hashlib import requests st = int(time.time()) nonce = str(st) + str(round(999999 * random.random())) + '000' m = f"cityCode=310100&pageId=lRLlag9R&putChannel=03&sid=b4ccde8d6f2e6256ddfce3b3f4ef3dca_&ct=10&nonce={nonce}&sid=b4ccde8d6f2e6256ddfce3b3f4ef3dca_&st={st}&key=71d414c0-8fe1-495d-ac55-207414632479" sign = hashlib.md5(m.encode('UTF-8')).hexdigest() print(st, nonce, sign) data = {"channelCode": "03", "coupons": [{"couponId": "156BBFBC389F9EA666E2FACD40BC5605", "receiveQuantity": 1, "newUserFlag": 0}, { "couponId": "45F09AFEE6E455A7175B1085C5012321", "receiveQuantity": 1, "newUserFlag": 0}], "pageId": "lRLlag9R"} url = 'https://api.mcd.cn/bff/member/coupon/bind' headers = { 'Host': 'api.mcd.cn', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/109.0', 'Accept': 'application/json, text/plain, */*', 'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2', 'Accept-Encoding': 'gzip, deflate', 'Content-Type': 'application/json', 'tid': '00003TuM', 'sid': 'b4ccde8d6f2e6256ddfce3b3f4ef3dca_', 'sv': 'v3', 'nonce': f'{nonce}', 'st': f'{st}', 'ct': '10', 'sign': f'{sign}', 'Content-Length': '220', 'Origin': 'https://cdn.mcd.cn', 'Referer': 'https://cdn.mcd.cn/', 'Sec-Fetch-Dest': 'empty', 'Sec-Fetch-Mode': 'cors', 'Sec-Fetch-Site': 'same-site', 'Te': 'trailers', 'Connection': 'close' } # res = requests.get('https://api.mcd.cn/bff/member/page/coupon/customer/getable?putChannel=03&pageId=lRLlag9R&cityCode=310100&sid=b4ccde8d6f2e6256ddfce3b3f4ef3dca_',headers=headers) res = requests.post(url=url, headers=headers, data=data) print(res.text)
草,竟然报错显示缺少参数,怎么回事呢😢(在Burp-Proxy截获的数据包中success,Burp-Repeater则不行)
{"success":false,"code":400,"message":"缺少参数","datetime":"2023-02-11 16:04:17","traceId":"3563b509-808d-4582-907d-19fe5a2feb6e","data":null}
看来无法实现自动发包领取了,然后就狠狠的进行了脚本录制🥬给两个号都嫖了四百张券(
后续:第二天已修复(火速)🐶😡