目录
前言
Signature(签名)是一种通过特定算法生成的数据校验值,通常叫签名,在Web安全中扮演着重要角色。其主要功能包括:
1. 数据完整性验证:确保请求数据在传输过程中未被篡改
2. 身份认证:验证请求来源的合法性
3. 接口保护:防止未授权访问
4. 防重放攻击:通过时间戳和随机字符串确保请求的唯一性
学习Signature的逆向分析有助于:
1. 安全技术研究:深入理解现代Web安全机制
2. 恶意行为分析:识别和防范潜在攻击
3. 技术创新:推动安全技术发展
4. 爬虫技术:理解反爬虫机制
一、初步抓包分析
使用谷歌浏览器打开一品威客官网,按F12打开开发者工具开始分析,调试,抓包。点击密码登录,随便输出手机号和密码,点击登录。
从开发者工具 的Network可以看到登录接口" https://www.epwk.com/api/epwk/v1/user/login "
从请求头就能看到 Signature
我们可以在Network中全局搜索Signature,看到第一个就是js源码中的计算实现
或者在Sources中右键搜索全部文件中的Signature
直接点击进去就能看到Signature的js源码实现
通过抓包分析,发现登录请求中的sign参数是经过加密的。通过搜索关键词定位到加密逻辑:
U.Signature = Object(f.a)(U, M, l.j ? l.g : l.c);
二、参数分析
2.1 U参数分析
往上溯源 U 的生成代码:
分析参数U,函数里面传入了U作为第一个参数,U的字段如上所示,这里面App-Id
和NonceStr
,Timestemp
都是需要分析的,其他都是固定的
Timestemp的值是A,A的计算可以往上搜到,
它作用应该是防止重放攻击
至于应用标识"App-Id":l.j ? l.f : l.b,我们可以在js中下断点,再次登录运行到这个地方
可以移动鼠标到变量上查看其值,也可以在开发者工具的Console项上,输入对应的变量查看其值
我们可以看到l.j的值为false,那么他会一直是false的值吗,我们可以继续如下追踪到j的实现
点击如上的get j 处的 js位置,就能跳到j的获取实现代码
代码往下溯源就能看到 c 就是 j 的值
看得出来,c的值是一个固定值false,所以我们接下来要看l.b
的值是怎么算的
往下溯源 h:
上面可以看出l.b也是一个固定值。App-Id的值由此容易得到,由于l.j为false,故App-Id的值为l.b的值。Timestemp的值就是个时间戳。接下来我们看到
NonceStr: "".concat(A).concat(Object(h.f)()),
为了增加请求唯一性,NonceStr
的变量计算稍微有点繁琐,由Timestemp
和Object(h.f)()
拼接而成。看Object(h.f)()
是什么。
同理点击直接跳到 h.f 实现之处,如下
看得出来e的默认值是5,该函数本质上就是生成一个随机字符串并截取字符串中的第3位到第,3+e位,既然是个随机函数,所以该值取什么值都无所谓。最后看M的实现,在Console的变量获取处输入M,就直接能获取其值
其中的username和password和code就是我们输入的账号密码和图片验证码。refer可以理解成一个固定值,不用变。
2.2 M参数分析
参数M就是我们填写的登陆信息,账号密码加上图形验证码。
2.3 加密函数分析
加密过程涉及两个主要函数:
1. f函数:用于参数排序和拼接
2. f.a函数:实现MD5和AES加密
首先来从 Object(f.a)
函数入手,看代码 U.Signature = Object(f.a)(U, M, l.j ? l.g : l.c);
我们可以直接断点此处代码,直接如上面截图地方输入 f.a 就能看到其实现之处,鼠标点击即可跳转至实现代码如下
看到函数中的变量都跟arguments
有关,先看arguments
是什么。
arguments 字典:它是一个字典类型的数据结构,里面有三个键值对。其中第一个键值对的值是变量 U,第二个是变量 M,第三个是一个固定值。不过目前不清楚 arguments 字典的具体键名。变量 data 和 e:变量 data 和 e 可以较容易获取到,且可以看出 e 的值为 "a75846eb4ac490420ac63db46d2a03bf"。公式 n = e + f(data) + f(t) + e:此公式里包含未知的函数 f 以及未知变量 t。若要计算出 n 的值,就需要明确 f 函数的具体功能和 t 的值。
变量 t
跟变量 U
是一致的。函数f
的实现如下同理找到。
如果我们看不懂具体代码是什么意思,我们可以断点到此,获取结果输出看看。
大概就是一个字符串的拼接。这些代码到时直接复制实现即可。最后看d
和v
函数。
三、加密实现
加密过程分为两步:
1. MD5加密:使用d函数
2. AES加密:使用v函数
如下同理追溯代码实现
d
函数是一个md5算法,v
算法是一个AES加密,只要知道了密钥、偏移量和加密模式即可。
上图中显示的key和iv都是字节,想要看到字符串类型的话可以采用toString函数。
四、Signature获取代码实现
运行环境:
- 浏览器环境
- 依赖库:CryptoJS
- 测试方法:Chrome控制台
把上面逆向实现的相关js代码合并如下:
js.js代码:
// 引入CryptoJS库
const Crypto = CryptoJS;
// 时间戳生成
A = parseInt((new Date).getTime() / 1e3);
// 请求头参数
var U = {
"App-Ver": "",
"Os-Ver": "",
"Device-Ver": "",
"Imei": "",
"Access-Token": "",
"Timestemp": A,
"NonceStr": "".concat(A).concat("syst41"),
"App-Id": "4ac490420ac63db4",
"Device-Os": "web"
};
// 请求体参数
var M = {
'code': 'as3e',
'hdn_refer': '',
'password': '123456',
'username': '15915863654'
};
// 签名盐值
const SIGNATURE_SALT = "a75846eb4ac490420ac63db46d2a03bf";
// 参数排序函数
f = function(t) {
var e = "";
return Object.keys(t).sort().forEach((function(n) {
e += n + ("object" === typeof t[n] ? JSON.stringify(t[n], (function(t, e) {
return "number" == typeof e && (e = String(e)),
e
})).replace(/\//g, "\\/") : t[n])
})), e
}
// MD5加密函数
d = function(data) {
return Crypto.MD5(data).toString();
}
// AES加密配置
l = {
key: Crypto.enc.Utf8.parse("fX@VyCQVvpdj8RCa"),
iv: Crypto.enc.Utf8.parse(function (t) {
for (var e = "", i = 0; i < t.length - 1; i += 2) {
var n = parseInt(t[i] + "" + t[i + 1], 16);
e += String.fromCharCode(n)
}
return e
}("00000000000000000000000000000000"))
}
// AES加密函数
v = function(data) {
return function(data) {
return Crypto.AES.encrypt(data, l.key, {
iv: l.iv,
mode: Crypto.mode.CBC,
padding: Crypto.pad.Pkcs7
}).toString()
}(data)
}
// 签名生成函数
h = function(t) {
var data = arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : {}
, e = arguments.length > 2 && void 0 !== arguments[2] ? arguments[2] : SIGNATURE_SALT
, n = e + f(data) + f(t) + e;
return n = d(n),
n = v(n)
}
// 生成签名
console.log(h(U, M, SIGNATURE_SALT));
这里没有另外安装js环境,只能用html在浏览器上打开运行获取了
<!DOCTYPE html>
<html>
<head>
<title>Crypto Test</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>
</head>
<body>
<script src="js.js"></script>
</body>
</html>
直接用谷歌浏览器打开,在开发者工具的Console看到输出值,就是Signature的值了
五、实现总结
整个签名生成过程:
1. 构造请求参数U和L
2. 对参数进行排序和拼接
3. 使用固定密钥进行MD5加密
4. 使用AES进行二次加密
5. 生成最终的signature参数
技术要点:
1. 参数排序确保一致性
2. 双重加密提高安全性
3. 时间戳防止重放攻击
4. 随机字符串增加复杂度
安全机制:
1. 验证码保护
2. 签名防篡改
3. 时间戳防重放
4. 双重加密防破解
单独逆向Signature确实作用有限,因为还有验证码的限制,无法绕过完整的验证流程,不能实现自动化操作。但是如果一些网站首次登录没有验证码,那就可以实现多个账号批量自动登录,然后可以被人实施自动爬取数据。
想要根据Signature实现自动化登录的,可以自行安装 Python 及相关库(execjs
和 requests
),安装 Node.js 作为 JavaScript 运行时环境,编写相关python代码,携带各种关键参数以及请求头,通过其登录接口进行请求,验证其完整流程的正确性。
这一节内容,我们也从中学到了服务器请求安全机制的实现,了解js部分逆向原理,熟悉了浏览器中js的调试操作。