webpack js 前端签名(sign)算法实战分析

简介

目前许多网站前端源码都趋于模块化,参数值通过webpack的模块进行加解密,这对于爬虫和渗透都是一个拦路虎,因此写下该博客,记录自己的学习思路。

目标站点

这里分析的是某动某站前端的js代码

1
aHR0cDovLzVnbWF0Y2guZ3guY2hpbmFtb2JpbGUuY29tL2hvbWVwYWdlLyMvbG9naW4=

算法分析

正常发送请求包

打开开发者工具,点击network,输入账号密码,点击登录。

效果

查看响应请求的内容,其中password的参数值被加密了,这里我输入的是123456,密文看着很像md5,但是通过解密发现,密文又似乎不是md5

效果

发送第二个登录包。

password的值没变,但是sign的值发生了变化,较大几率sign值在生成的时候,使用了时间戳

效果

此时寻找该参数加密点。

寻找password、sign加密相关的模块与函数

全局搜索该登录请求的接口

效果

并成功找到相关的参数。

效果

分析password

通过分析发现,password的值是由n决定的。

1
2
3
4
5
6
7
s = {
phone: e.phone,
password: n,
type: t,
sign: "",
timestamp: d
}

并在第1886行,找到了有关n的声明

效果

此时发现password被md5加密后,与pwdkey的值,进行字符串拼接后,在进行一次md5加密。

直接搜索pwdkey,并在第27589行,找到了该值,发现该值是不变的。

效果

这里使用python3,写一个简单的password的加密demo

1
2
3
4
5
6
7
8
9
10
11
import hashlib

def md5Encode(str):
m = hashlib.md5(str.encode(encoding='utf-8'))
return m.hexdigest()

password = "123456"
pwdkey = "kla5ra8h9s"

encrypt_password = md5Encode(md5Encode(password)+pwdkey)
print(encrypt_password)

效果

发现该值,与我在前端登录框处输入密码123456后,经前端webpack模块加密后的值是一样的,说明前面的分析思路是正确的。

效果

分析sign

sign的生成方式在第1866行进行的。

1
d.sign = this.$md5Sign(n)

效果

sign的值是通过md5Sign进行加密后得到的,因此这里需要分析两个点。分别是md5Sign的算法流程,以及l的值。

分析l

在第1858行发现

1
l = "" + e.phone + n + t + d

这里我下个断点,传值进来方便查看。

效果

l的值是由手机号+密码密文+1+时间戳生成的。

l=phone+md5(md5(password)+pwdkey)+1+时间戳

分析md5Sign

此时分析md5Sign的算法。

通过断点追踪发现,md5Sign和函数mn()有关。

效果

有点奇怪,为什么不应该是直接追到md5Sign的函数声明去吗?怎么会是mn()的声明。

此时我直接搜索了mn()

发送mn的值又赋值给了md5Sign

因此此处mnmd5Sign可以理解是等价的关系。

效果

此时来分析mn的函数流程。

1
2
3
4
5
6
7
8
9
sn = a("8237"),
cn = a.n(sn);
function mn(e) {
var i = "",
a = "6d9fkhj33rk8sa5fc";
return i = e + a,
i = cn()(i).toLowerCase(),
i
}

效果

此处并设置断点传值。

效果

并连续调试到下一步

效果

此时分析可知

e = phone +password的密文+1+时间戳

e = 151123412349ac13f8bad58389388ecc1604eaed32011635925676

a = 6d9fkhj33rk8sa5fc

i = e + a

i = phone +password的密文 +1 + 时间戳 + 6d9fkhj33rk8sa5fc

i = 151123412349ac13f8bad58389388ecc1604eaed320116359256766d9fkhj33rk8sa5fc

这里为了方便查看看,就将i最后的值拆分一下

phone md5(md5(password)+pwdkey) 1 时间戳 a
15112341234 9ac13f8bad58389388ecc1604eaed320 1 1635925676 6d9fkhj33rk8sa5fc

i的值再被cn函数加密,加密后的值,全部通过toLowerCase()转换成小写字母。

1
2
sn = a("8237");
cn = a.n(sn);

为了方便调试,这里我打开了编辑器,准备扣前端js代码下来,本地执行调试了。

先把前面的流程进行整理,这里我把所有时间戳写死了,方便等下观察值是否一样。

当时间戳为1635925676时,sign的值为32138108f605a5122cc6f6c1bc54c7b3。(之前的截图)

1
2
3
4
5
6
7
8
9
10
11
12
var password = "9ac13f8bad58389388ecc1604eaed320"
var phone = "15112341234"
var timestamp = "1635925676"
var a = "6d9fkhj33rk8sa5fc"


var sn = a("8237");
var cn = a.n(sn);
var i = phone + password + '1' + timestamp + a;
var sign = cn()(i).toLowerCase();

console.log(sign)

效果

此时只需要分析a

效果

断点跟进

在这里找到了a

效果

此时分析确定了分发器

效果

我们将这段代码全部扣下来。

声明一个flag,用于接收分发器模块的值。

效果

l赋值给flag,并要把原先的a("8237")a.n(sn)修改成flag("8237")flag.n(sn)

效果

运行之后,报错。

提示window没有被定义,这里就把window声明一下。

效果

1
var window = global;

将这个声明放在最上面,和flag的声明放在一起。

此时再次运行。报错,提示call没有被定义。原因是因此flag中的8237模块没有被调用。

效果

这里全局搜索8237

在此js文件中找到了有关8237的定义

效果

将这个js代码全部复制至本地的2.js中,并在1.js中进行调用。

并声明一下window

效果

并再次运行

效果

和预期值不一样?猜测时间戳填错了。

因此此处重新抓一个时间戳的数据包。

并修改timestamp的值

效果

此时本地调试的结果,与前端的结果一致,因此该sign的签名算法分析完成。

使用python3完成此次签名算法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import hashlib
import time

def md5Encode(str):
m = hashlib.md5(str.encode(encoding='utf-8'))
return m.hexdigest()

if __name__ == "__main__":
phone = "15112341234"
password = "123456"
pwdkey = "kla5ra8h9s"
#timestamp = "1635927403"
timestamp = int(time.time())
a = "6d9fkhj33rk8sa5fc"
n = md5Encode(md5Encode(password)+pwdkey)
i = phone + n + "1" + str(timestamp) + a

print("时间戳是:"+str(timestamp))
print(md5Encode(i))

效果

使用系统时间戳

效果

总结

遇到前端加密不要慌,作为一个安服仔,应该直接干就完了,反正js代码已经默认开源给你了,慢慢调试分析算法逻辑,并调用相应的模块即可。

自主学习时,解决问题应有多样性,尽量还是多增加自己的知识层面。

实际解决问题时,应考虑效率。

所以此处我提供了javascript和python3的两种解决问题的方式方法。

Author: jdr
Link: https://jdr2021.github.io/2021/11/03/webpack-js-前端签名-sign-算法实战分析/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.