Skip to content

起因

像往常一样没啥事查看青龙日志的时候发现脚本报错了。

一开始还以为是网络问题没管他,等练车回来的时候看了一眼发现还是报错,仔细看了看发现是加了验证的参数

经过

打开Fiddler,开始抓包。

随便点点,看看请求参数有没有什么变化。

这里可以明显看出比之前多了3个参数 timestamp nonce signature

而且在每次的请求时都会通过/public/api/getServerTimestamp获取一下服务器的时间戳 。

先重放一次试试....

居然获取成功,那这不简单了,把原来脚本的请求再加上这三个参数不就行了。

不能这么简单吧,再重放一次试试...

果然...是我想的太简单了,看样子经过一定的时间那几个参数会过期,貌似是20秒左右。

那我手快点,生成一个现在的时间戳然后Get一下看看会怎么样。

不出所料依旧是 {"code":802,"message":"sign error3"}

看来不得不琢磨琢磨另外两个参数了。

开始分析参数的生成

因为服务器就传过来一个时间戳,那两个参数应该就是本地的JS生成的,不出意外只要知道生成的规则,应该就能解决了。

把这个网页全部的 JS 文件下载下来,然后搜索 timestamp nonce signature 这几个参数,发现在 index.d774f.js 这个文件的 549-590 行出现了很多次。那应该就是这里了。

分析

可以看出这里有对变量赋值的操作。

先来看看 signature 它通过 xysz.encrypt.Md5.encode(o).toUpperCase() 赋值。

.toUpperCase() 是转换成大写,xysz.encrypt.Md5.encode(o) 看样子是他自己写的函数应该和 Md5 有关,传递的参数是 o 。

那再往上看看 o 是怎么来的

o 的值又是通过 xysz.tool.getSortedQuery(a) 赋值 ,参数是 a ,那再往上看 a 是怎么来的。

js
a = {
	clientKey: e[t].clientKey,
	clientSecret: e[t].clientSecret,
	timestamp: i.default.ServerTimestamp,
    nonce: n
},

clientKey clientSecret 和 e 有关,timestamp 应该就是前面服务器传的时间戳,nonce 等于 n 的值。

还要再往上看......

js
n = this.randomString()

n 的值和randomString函数有关,通过搜索在当前的文件找到了这个函数。

js
e.prototype.randomString = function (e) {
 	void 0 === e && (e = 16);
 	for (var t = "ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678", n = t.length,a = "", r = 0; r < e; r++) a += t.charAt(Math.floor(Math.random() *n));
 	return a
	},

可以看出是随机生成一个长度为16的字符串,那看来nonce的值就是一个随机字符串了。

还剩下一个 signature ,前面说到 clientKey clientSecret和 e 的值有关,而关于e 的值是个对象又和 t 有关,没办法再去找 t 的值。

js
t = xysz.saas.getEnvByHostname()

t 又是一个函数赋的值。

xysz.min.8ac30.js 文件发现了这个函数

js
t.prototype.getEnvByHostname = function() {
    return ["devapi.xiaoyisz.com", "qiehuangdev.ioutu.cn", "localhost"].includes(location.hostname) ?
        "dev" : "prod"
}

发现是一个三元运算符,拿到正确的值后回到对象 a,e[t].clientKey e[t].clientSecret 的值就显而易见了。

吐槽一句...这不管三元运算符的结果是啥 e[t].clientKey e[t].clientSecret 这两个值的结果一样啊。

a的值有了,扔到 xysz.tool.getSortedQuery(a) 函数里取到值。

如法炮制得到 getSortedQuery

js
t.getSortedQuery = function(t) {
        var e = Object.keys(t).sort(),
            n = "";
        return e.forEach(function(e) {
            if (t[e] || 0 === t[e]) {
                var o;
                o = t[e] instanceof Object ? e + "=" + JSON.stringify(t[e]) : e + "=" + t[e], n &&
                    o && (n += "&"), n += o
            }
        }), n
    },

作用就是把 a 格式化输出,赋值给 o

js
'clientKey=xxx&clientSecret=xxx&nonce=xxx&timestamp=xxx'

有了 o 后,回到最初的起点

js
signature: xysz.encrypt.Md5.encode(o).toUpperCase(),

经过测试,是把o的值转换成32位 Md5 后再变成大写。

那这样,signature 的值就很明确了。

到此,对JS加密的分析结束了。改改之前的脚本,把那几个参数加上去,脚本又能正常运行啦!

Released under the MIT License.