某站点Sign值逆向
随着网络安全的发展,如何保护API接口的数据安全性成了一个很重要的问题:
- 请求身份来源是否合法?
- 请求参数是否被篡改?
- 请求的唯一性
面临以上一些问题,Sign(签名)也被设计出来保护API接口数据的安全。更多应用厂商也开始意识到,很多应用系统已经开始采用验证签名的方法来保护,防止一些攻击,如 Replay attack (重放攻击),Data tampering(数据篡改)。
但是加上Sign并不是说其它漏洞或者安全问题就不存在,还是需要我们进行渗透测试,验证系统是否安全,有防自然有攻,还是那句话,没有绝对安全的系统。
在应用系统加上签名后,进行Web端渗透测试时就面临了一些麻烦,不能重放,修改请求数据,使用 Sign 值的应用系统确实提高了一定的安全性,但是还是存在很大的安全隐患。 Sign 值在生成的过程中是在Web前端完成的,Web前端代码对我们可见,虽说大多数进行过混淆,但是只要去找,一定可以将应用系统 Sign 值生成方式逆向出来。
抓包分析
以下是对某个站点Sign值的逆向过程,如有错误请各位指正。
打开应用系统尝试抓包测试,发现应用系统使用Sign来验证接口安全,修改参数则提示签名校验失败。
简单观察一下Header头,Customized-Field 和 Sign 是重点
Customized-Field 和 Sign 分析
Customized-Field 头由三个值组成:
- api 请求接口URL
- client-type 字面意思为连接的类型
- timestamp 时间戳(timestamp)是指格林威治时间1970年01月01日00时00分00秒起至现在的总秒数。
Sign 头:
- 长度为64位数字加大写字母组合
根据了解的一些加密算法特征,猜测为SHA256加密算法。
参考常见的一些加密编码类型的密文特征:
https://cloud.tencent.com/developer/article/1748394
前端代码分析
通过简单的对请求进行分析,找到了一些关键点,现在我们在控制台中去查找一下这些关键字:
Sign值
F12 打开控制台,找到调试器,CTRL + F 查找Sign值(PS-1):
PS-1:JS逆向定位技巧有很多,这里采用全局搜索关键字来进行定位,介绍几个常用的
- 全局搜索关键字,如 sign , password=, .post , .get
- Dom元素事件监听
- xhr断点
- Initator栈追踪
经过全局查找(PS-2),可以看到四个比较可能是Sign值生成的位置。
PS-2:目标系统使用VUE前端框架,在生成JS文件时一般如**_app.js**形式的文件是主要入口文件,所以在逆向查找时从该文件进入
先看 this.headers.sign, 进入该行代码查看:
该位置为某些接口的验证
进入 sign: Object(c.b) (l, n, a)
简单查看可以确认是生成Sign值和Customized-Field的位置,在这行打断点开始调试:
打上断点后刷新网站,运行到断点处停止,在控制台打印 sign: Object(c.b) (l, n, a) 各个参数看看:
1 | l |
l 为Customized-Field值
c.b 为 function p
a 为空
继续跟进 function p(e, t, n)
运气很好,跟进进来直接看到我们需要的东西
1 | p = function (e, t, n) { |
现在可以确认加密编码方式为 Hmac-SHA256
有部分看不懂部的代码,打印一下输出。
i.a.enc
现在知道是什么意思了,代表调用的加密方法,其它情况也可以这样去判断,对不了解的函数直接谷歌查找一下就OK。
继续跟进 funtion p
跟进函数 d()
1 | function d(e) { |
Object.keys()
方法会返回一个由一个给定对象的自身可枚举属性组成的数组,数组中属性名的排列顺序和正常循环遍历该对象时返回的顺序一致。
默认情况下,
sort()
方法将按字母和升序将值作为字符串进行排序。
对 e 进行了排序处理,按照数组首字母进行升序排序。
继续跟进 s
1 | for (var t = 1; t < arguments.length; t++) { |
**
forEach()
**方法用于调用数组的每个元素,并将元素传递给回调函数。注意:forEach() 对于空数组是不会执行回调函数的。
Object.getOwnPropertyDescriptors()
方法用来获取一个对象的所有自身属性的描述符。
还是不太能看懂要干什么,继续跟进看输出
虽然具体不理解 s() 要干什么,但是知道 **s()**是什么也行。大致对 e 进行处理,可以看到 e 是 参数 Customized-Field 。
那么 o = d(s({}, n, {‘Customized-Field’: e})) ,就是进行了数组排序处理。
继续更进:
1 | for (var c in o) { |
PS-3: 以上调试时未在请求中post数据
再请求中带上POST数据后
对排序过后的 o 进行重组,重组格式大致如下:
1 | "Customized-Field={\"api\":\"/user/institutionListByAccount\",\"client-type\":\"web\",\"timestamp\":\"1662367339537\"}&account=admin&key=JQGyyyXXXXXCA9WPEx72jXXXXXXUNpEK1FL6B" |
Customized-Field + = + {排序后api, client-type, timestamp} + & 参数(字母顺序升序排序)+ & + 参数值 + & + key值(不参与排序)
继续跟进,Sign值生成
到这不用关注加密是怎么执行,步出得到结果就行,控制台执行 i.a.enc.Hex.stringify(f).toUpperCase()
好了,现在已经得到Sign值的加密以及生成方法了。整个流程大致如下:
整个分析过程也比较简单,第一步便找到入口点,那后面的只需要时间就能解决了。下面使用python来实现一下加密代码:
Python代码实现:
1 | import hmac |
PS-4: 代码写的跟屎一样,各位师傅勿视。
目前第一版代码如上,后续会配合@f0ng师傅写的 项目autoDecoder 结合在BurpSuite中实现自动更新Sign值和Customized-Field。
项目地址:https://github.com/f0ng/autoDecoder
参考: