丁香公开课请求 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&timestamp="+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&timestamp="+res.timestamp+"&noncestr="+res.noncestr+"&sign="+res.sign;
console.log("请求连接",url);

通过在线JS运行工具:https://www.sojson.com/runjs.html

 得到链接,直接浏览器打开(因为这个是个get请求),然后就得到如下内容

结果拿到了。好了,分析到此结束,其实 JS 算法也好,只要是重要的部分,最好还是加密一下,使用本站的JS最牛加密,或者先用JS方法加密,加密后在用JS最牛加密加密JS,这样整个逻辑就打乱了,可以使分析者第一步就很难。

申明:当前内容只能用于学习,不能用于其他。

 

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


学习编程是顺着互联网的发展潮流,是一件好事。新手如何学习编程?其实不难,不过在学习编程之前你得先了解你的目的是什么?这个很重要,因为目的决定你的发展方向、决定你的发展速度。
IT行业是什么工作做什么?IT行业的工作有:产品策划类、页面设计类、前端与移动、开发与测试、营销推广类、数据运营类、运营维护类、游戏相关类等,根据不同的分类下面有细分了不同的岗位。
女生学Java好就业吗?女生适合学Java编程吗?目前有不少女生学习Java开发,但要结合自身的情况,先了解自己适不适合去学习Java,不要盲目的选择不适合自己的Java培训班进行学习。只要肯下功夫钻研,多看、多想、多练
Can’t connect to local MySQL server through socket \'/var/lib/mysql/mysql.sock问题 1.进入mysql路径
oracle基本命令 一、登录操作 1.管理员登录 # 管理员登录 sqlplus / as sysdba 2.普通用户登录
一、背景 因为项目中需要通北京网络,所以需要连vpn,但是服务器有时候会断掉,所以写个shell脚本每五分钟去判断是否连接,于是就有下面的shell脚本。
BETWEEN 操作符选取介于两个值之间的数据范围内的值。这些值可以是数值、文本或者日期。
假如你已经使用过苹果开发者中心上架app,你肯定知道在苹果开发者中心的web界面,无法直接提交ipa文件,而是需要使用第三方工具,将ipa文件上传到构建版本,开...
下面的 SQL 语句指定了两个别名,一个是 name 列的别名,一个是 country 列的别名。**提示:**如果列名称包含空格,要求使用双引号或方括号:
在使用H5混合开发的app打包后,需要将ipa文件上传到appstore进行发布,就需要去苹果开发者中心进行发布。​
+----+--------------+---------------------------+-------+---------+
数组的声明并不是声明一个个单独的变量,比如 number0、number1、...、number99,而是声明一个数组变量,比如 numbers,然后使用 nu...
第一步:到appuploader官网下载辅助工具和iCloud驱动,使用前面创建的AppID登录。
如需删除表中的列,请使用下面的语法(请注意,某些数据库系统不允许这种在数据库表中删除列的方式):
前不久在制作win11pe,制作了一版,1.26GB,太大了,不满意,想再裁剪下,发现这次dism mount正常,commit或discard巨慢,以前都很快...
赛门铁克各个版本概览:https://knowledge.broadcom.com/external/article?legacyId=tech163829
实测Python 3.6.6用pip 21.3.1,再高就报错了,Python 3.10.7用pip 22.3.1是可以的
Broadcom Corporation (博通公司,股票代号AVGO)是全球领先的有线和无线通信半导体公司。其产品实现向家庭、 办公室和移动环境以及在这些环境...
发现个问题,server2016上安装了c4d这些版本,低版本的正常显示窗格,但红色圈出的高版本c4d打开后不显示窗格,
TAT:https://cloud.tencent.com/document/product/1340