丁香公开课请求 sign(签名) 分析过程讲解
本教程仅供学习。如有侵权请联系本站删除。
背景分析
1.网站链接:https://class.dxy.cn/
2.所有异步请求都会带上 sign=xxxx
,并且每次sign
只能用一次。
3.目的:解决sign
的算法,得到正确的值。
4.初步定位算法js为:https://assets.dxycdn.com/gitrepo/dxycourse-pc/dist/index.e8a8a63d2fc74a69.js
格式化一看发现有6万多行的JS。有点头大,但是JS没有加密,只是打包了。
0x0、定位 sign 具体位置
发现只有8处位置,通过经验+第六感得出,大概在43895这行这个sign
可能是最终算法。估计你们看到说凭借经验+第六感,就慌了。接下来我们来验证吧。
使用 charles
工具,Mapping Local
到本地格式化的 js 中,添加一处debugger
。
return {
sign: function () {
debugger;//增加debug
console.log('arguments',arguments);
var e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {},
t = arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : "省略长串",
r = n({}, e), i = {appSignKey: t};
r.timestamp = Date.now(), r.noncestr = a(8, "number");
var o = Object.keys(r).filter(function (e) {
return void 0 != r[e] && "" !== r[e] || (delete r[e], !1)
}).concat("appSignKey").sort().map(function (e) {
var t = i[e] || (void 0 == r[e] ? "" : r[e]);
return "".concat(e, "=").concat(t)
}).join("&");
return r.sign = u(o), r
}
}
然后刷新页面,果然是此处。
0x1、提取JS代码
既然是这里,那么就提取此处代码用于单独调用,毕竟6万多行的js,并且是打包的,外部是无法调用的。
//提取算法代码
var CORE = (function () {
function e(e, t, n) {
return t in e ? Object.defineProperty(e, t, {
value: n,
enumerable: !0,
configurable: !0,
writable: !0
}) : e[t] = n, e
}
function n(t) {
for (var n = 1; n < arguments.length; n++) {
var r = null != arguments[n] ? arguments[n] : {}, a = Object.keys(r);
"function" === typeof Object.getOwnPropertySymbols && (a = a.concat(Object.getOwnPropertySymbols(r).filter(function (e) {
return Object.getOwnPropertyDescriptor(r, e).enumerable
}))), a.forEach(function (n) {
e(t, n, r[n])
})
}
return t
}
function r(e, t) {
return t = {exports: {}}, e(t, t.exports), t.exports
}
function a() {
for (var e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : 8, t = arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : "alphabet", n = "", r = {
alphabet: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
number: "0123456789"
}[t], a = 0; a < e; a++) n += r.charAt(Math.floor(Math.random() * r.length));
return n
}
var i = r(function (e) {
!function () {
var t = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", n = {
rotl: function (e, t) {
return e << t | e >>> 32 - t
}, rotr: function (e, t) {
return e << 32 - t | e >>> t
}, endian: function (e) {
if (e.constructor == Number) return 16711935 & n.rotl(e, 8) | 4278255360 & n.rotl(e, 24);
for (var t = 0; t < e.length; t++) e[t] = n.endian(e[t]);
return e
}, randomBytes: function (e) {
for (var t = []; e > 0; e--) t.push(Math.floor(256 * Math.random()));
return t
}, bytesToWords: function (e) {
for (var t = [], n = 0, r = 0; n < e.length; n++, r += 8) t[r >>> 5] |= e[n] << 24 - r % 32;
return t
}, 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("")
}, hexToBytes: function (e) {
for (var t = [], n = 0; n < e.length; n += 2) t.push(parseInt(e.substr(n, 2), 16));
return t
}, bytesToBase64: function (e) {
for (var n = [], r = 0; r < e.length; r += 3) for (var a = e[r] << 16 | e[r + 1] << 8 | e[r + 2], i = 0; i < 4; i++) 8 * r + 6 * i <= 8 * e.length ? n.push(t.charAt(a >>> 6 * (3 - i) & 63)) : n.push("=");
return n.join("")
}, base64ToBytes: function (e) {
e = e.replace(/[^A-Z0-9+\/]/gi, "");
for (var n = [], r = 0, a = 0; r < e.length; a = ++r % 4) 0 != a && n.push((t.indexOf(e.charAt(r - 1)) & Math.pow(2, -2 * a + 8) - 1) << 2 * a | t.indexOf(e.charAt(r)) >>> 6 - 2 * a);
return n
}
};
e.exports = n
}()
}), o = {
utf8: {
stringToBytes: function (e) {
return o.bin.stringToBytes(unescape(encodeURIComponent(e)))
}, bytesToString: function (e) {
return decodeURIComponent(escape(o.bin.bytesToString(e)))
}
}, bin: {
stringToBytes: function (e) {
for (var t = [], n = 0; n < e.length; n++) t.push(255 & e.charCodeAt(n));
return t
}, bytesToString: function (e) {
for (var t = [], n = 0; n < e.length; n++) t.push(String.fromCharCode(e[n]));
return t.join("")
}
}
}, s = o, u = r(function (e) {
!function () {
var n = i, r = s.utf8, a = s.bin, o = function (e) {
e.constructor == String ? e = r.stringToBytes(e) : "undefined" !== typeof t && "function" == typeof t.isBuffer && t.isBuffer(e) ? e = Array.prototype.slice.call(e, 0) : Array.isArray(e) || (e = e.toString());
var a = n.bytesToWords(e), i = 8 * e.length, o = [], s = 1732584193, u = -271733879,
l = -1732584194, c = 271733878, d = -1009589776;
a[i >> 5] |= 128 << 24 - i % 32, a[15 + (i + 64 >>> 9 << 4)] = i;
for (var f = 0; f < a.length; f += 16) {
for (var p = s, h = u, m = l, v = c, y = d, g = 0; g < 80; g++) {
if (g < 16) o[g] = a[f + g]; else {
var _ = o[g - 3] ^ o[g - 8] ^ o[g - 14] ^ o[g - 16];
o[g] = _ << 1 | _ >>> 31
}
var b = (s << 5 | s >>> 27) + d + (o[g] >>> 0) + (g < 20 ? 1518500249 + (u & l | ~u & c) : g < 40 ? 1859775393 + (u ^ l ^ c) : g < 60 ? (u & l | u & c | l & c) - 1894007588 : (u ^ l ^ c) - 899497514);
d = c, c = l, l = u << 30 | u >>> 2, u = s, s = b
}
s += p, u += h, l += m, c += v, d += y
}
return [s, u, l, c, d]
}, u = function (e, t) {
var r = n.wordsToBytes(o(e));
return t && t.asBytes ? r : t && t.asString ? a.bytesToString(r) : n.bytesToHex(r)
};
u._blocksize = 16, u._digestsize = 20, e.exports = u
}()
});
return {
sign: function () {
var e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {},
t = arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : "...",
r = n({}, e), i = {appSignKey: t};
var mt = Date.now();
r.timestamp =mt, r.noncestr = a(8, "number");
var o = Object.keys(r).filter(function (e) {
return void 0 != r[e] && "" !== r[e] || (delete r[e], !1)
}).concat("appSignKey").sort().map(function (e) {
var t = i[e] || (void 0 == r[e] ? "" : r[e]);
return "".concat(e, "=").concat(t)
}).join("&");
return r.sign = u(o), r
}
}
})(n("EuP9").Buffer);
在提取代码的过程中,发现依赖了一个外部调用 n("EuP9").Buffer
然后跟踪下,发现里面还有调用,一坨很大,然后我们分析下当前 sign
算法是否使用了,发现没有明确的地方使用,直接去掉先。
接着我们开始来测试。先找了一个无参数的 Get 请求试一下。
var res = CORE.sign();
//得到 {timestamp: 1600356565232, noncestr: "51070119", sign: "c2d89f55ca8c4e1da93002e274739e70e43fdf89"}
好像成功了,然后尝试一下。
拼接无参数链接请求:
https://class.dxy.cn/pcweb/user/info?timestamp=1600356565232&noncestr=51070119&sign=c2d89f55ca8c4e1da93002e274739e70e43fdf89
返回签名错误。
这就有点蛋疼了,刚刚的喜悦被当头一棒。冷静冷静。仔细分析下。
调用sign获取签名是不是有参数?看下面部分代码。
var e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {},
t = arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : "太长省略",
r = n({}, e), i = {appSignKey: t};
这里sign
直接读取了参数判断,然后去取值,看语义分析下,第一个应该是一个 json 对象格式的参数,第二个是一个字符串,并且是个key
,如果得不到就给一个默认的值。咱们直接在js里输出 “arguments
”看看。
return {
sign: function () {
debugger;//增加debug
//输出参数
console.log('arguments',arguments);
var e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {},
t = arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : "...",
r = n({}, e), i = {appSignKey: t};
r.timestamp = Date.now(), r.noncestr = a(8, "number");
var o = Object.keys(r).filter(function (e) {
return void 0 != r[e] && "" !== r[e] || (delete r[e], !1)
}).concat("appSignKey").sort().map(function (e) {
var t = i[e] || (void 0 == r[e] ? "" : r[e]);
return "".concat(e, "=").concat(t)
}).join("&");
return r.sign = u(o), r
}
}
看到了吧,第一个是 {}
,第二个参数是一个key
,而且这个key
和代码里默认的key
不一样。
那我们就也带上这个key
吧。或者把这个key
写死在代码里,然后不传第二个参数也可以。
这里观察了多个请求并 console.log('arguments',arguments);
后发现第一个参数是提交到后台的参数值。
好,然后优化下代码结果是:
//有参数,data就给参数。
var data = {courseId: 402,courseType: 2};
var res = s.sign(data,"695e7084696bf4a6b4c73dc67da4f5df41405763ef4a2a854e3af96b141254b0050986ccf2b8cd9a6ab5cfabc6e4bb30");
var url = "https://class.dxy.cn/pcweb/user/course/like/status?courseId=402&courseType=2×tamp="+res.timestamp+"&noncestr="+res.noncestr+"&sign="+res.sign;
获取一个分页信息:
var data = {pageNum:1,pageSize:4,courseId:402,courseType:2};
var res = CORE.sign(data,"695e7084696bf4a6b4c73dc67da4f5df41405763ef4a2a854e3af96b141254b0050986ccf2b8cd9a6ab5cfabc6e4bb30");
console.log("签名",res.sign);
console.log("timestamp",res.timestamp);
console.log("noncestr",res.noncestr);
var url = "https://class.dxy.cn/pcweb/user/pack/comment/list?pageNum=1&pageSize=4&courseId=402&courseType=2×tamp="+res.timestamp+"&noncestr="+res.noncestr+"&sign="+res.sign;
console.log("请求连接",url);
通过在线JS运行工具:https://www.sojson.com/runjs.html
得到链接,直接浏览器打开(因为这个是个get请求),然后就得到如下内容
结果拿到了。好了,分析到此结束,其实 JS 算法也好,只要是重要的部分,最好还是加密一下,使用本站的JS最牛加密,或者先用JS方法加密,加密后在用JS最牛加密去加密JS,这样整个逻辑就打乱了,可以使分析者第一步就很难。
申明:当前内容只能用于学习,不能用于其他。
版权所属:JavaScript加密
原文地址:https://www.jsjiami.com/article/dxy-sign.html
转载时必须以链接形式注明原始出处及本声明。