某站点Sign值逆向

某站点Sign值逆向

随着网络安全的发展,如何保护API接口的数据安全性成了一个很重要的问题:

  • 请求身份来源是否合法?
  • 请求参数是否被篡改?
  • 请求的唯一性

面临以上一些问题,Sign(签名)也被设计出来保护API接口数据的安全。更多应用厂商也开始意识到,很多应用系统已经开始采用验证签名的方法来保护,防止一些攻击,如 Replay attack (重放攻击),Data tampering(数据篡改)。

但是加上Sign并不是说其它漏洞或者安全问题就不存在,还是需要我们进行渗透测试,验证系统是否安全,有防自然有攻,还是那句话,没有绝对安全的系统。

在应用系统加上签名后,进行Web端渗透测试时就面临了一些麻烦,不能重放,修改请求数据,使用 Sign 值的应用系统确实提高了一定的安全性,但是还是存在很大的安全隐患。 Sign 值在生成的过程中是在Web前端完成的,Web前端代码对我们可见,虽说大多数进行过混淆,但是只要去找,一定可以将应用系统 Sign 值生成方式逆向出来。

抓包分析

以下是对某个站点Sign值的逆向过程,如有错误请各位指正。

打开应用系统尝试抓包测试,发现应用系统使用Sign来验证接口安全,修改参数则提示签名校验失败。

image-20220905125804706

简单观察一下Header头,Customized-Field 和 Sign 是重点

image-20220905125551126

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栈追踪

image-20220905134938501

经过全局查找(PS-2),可以看到四个比较可能是Sign值生成的位置。

PS-2:目标系统使用VUE前端框架,在生成JS文件时一般如**_app.js**形式的文件是主要入口文件,所以在逆向查找时从该文件进入

先看 this.headers.sign, 进入该行代码查看:

image-20220905141256503

该位置为某些接口的验证

进入 sign: Object(c.b) (l, n, a)

image-20220905142659988

image-20220905142821375

简单查看可以确认是生成Sign值和Customized-Field的位置,在这行打断点开始调试:

image-20220905144211939

打上断点后刷新网站,运行到断点处停止,在控制台打印 sign: Object(c.b) (l, n, a) 各个参数看看:

1
2
3
4
5
6
7
l
"{\"api\":\"/imessage/queryMessage\",\"client-type\":\"web\",\"timestamp\":\"1662359962053\"}"
c.b
function p(e, t, n)

a
Object { }
  • l 为Customized-Field值

  • c.b 为 function p

  • a 为空

继续跟进 function p(e, t, n)

image-20220905150025784

运气很好,跟进进来直接看到我们需要的东西

1
2
3
4
5
6
7
8
9
10
11
12
13
14
p = function (e, t, n) {
var r = '',
o = d(s({
}, n, {
'Customized-Field': e
}));
for (var c in o) {
var l = o[c] || 0 === o[c] ? 'object' === a(o[c]) ? JSON.stringify(o[c]) : o[c] : '';
0 === l || l ? r += ''.concat(c, '=').concat(l, '&') : delete n[c]
}
r += 'key='.concat(u);
var f = i.a.HmacSHA256(r, u);
return i.a.enc.Hex.stringify(f).toUpperCase()
}

现在可以确认加密编码方式为 Hmac-SHA256

有部分看不懂部的代码,打印一下输出。

i.a.enc

image-20220905152050050

现在知道是什么意思了,代表调用的加密方法,其它情况也可以这样去判断,对不了解的函数直接谷歌查找一下就OK。

继续跟进 funtion p

image-20220905154758475

跟进函数 d()

1
2
3
4
5
function d(e) {
for (var t = Object.keys(e).sort(), n = {
}, r = 0; r < t.length; r++) n[t[r]] = e[t[r]];
return n
}

Object.keys() 方法会返回一个由一个给定对象的自身可枚举属性组成的数组,数组中属性名的排列顺序和正常循环遍历该对象时返回的顺序一致。

默认情况下,sort() 方法将按字母和升序将值作为字符串进行排序。

对 e 进行了排序处理,按照数组首字母进行升序排序。

继续跟进 s

image-20220905160039509

1
2
3
4
5
6
7
8
9
10
11
  for (var t = 1; t < arguments.length; t++) {
var n = null != arguments[t] ? arguments[t] : {
};
t % 2 ? o(Object(n), !0).forEach((function (t) {
c(e, t, n[t])
})) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(n)) : o(Object(n)).forEach((function (t) {
Object.defineProperty(e, t, Object.getOwnPropertyDescriptor(n, t))
}))
}
return e
}

**forEach() **方法用于调用数组的每个元素,并将元素传递给回调函数。注意:forEach() 对于空数组是不会执行回调函数的。

Object.getOwnPropertyDescriptors() 方法用来获取一个对象的所有自身属性的描述符。

还是不太能看懂要干什么,继续跟进看输出

image-20220905161207683

虽然具体不理解 s() 要干什么,但是知道 **s()**是什么也行。大致对 e 进行处理,可以看到 e 是 参数 Customized-Field 。

image-20220905162148287

那么 o = d(s({}, n, {‘Customized-Field’: e})) ,就是进行了数组排序处理。

继续更进:

image-20220905163357004

1
2
3
4
5
for (var c in o) {
var l = o[c] || 0 === o[c] ? 'object' === a(o[c]) ? JSON.stringify(o[c]) : o[c] : '';
0 === l || l ? r += ''.concat(c, '=').concat(l, '&') : delete n[c]
}
r += 'key='.concat(u);

PS-3: 以上调试时未在请求中post数据

再请求中带上POST数据后

image-20220905165046078

对排序过后的 o 进行重组,重组格式大致如下:

1
"Customized-Field={\"api\":\"/user/institutionListByAccount\",\"client-type\":\"web\",\"timestamp\":\"1662367339537\"}&account=admin&key=JQGyyyXXXXXCA9WPEx72jXXXXXXUNpEK1FL6B" 

Customized-Field + = + {排序后api, client-type, timestamp} + & 参数(字母顺序升序排序)+ & + 参数值 + & + key值(不参与排序)

继续跟进,Sign值生成

image-20220905165601894

到这不用关注加密是怎么执行,步出得到结果就行,控制台执行 i.a.enc.Hex.stringify(f).toUpperCase()

image-20220905170329384

好了,现在已经得到Sign值的加密以及生成方法了。整个流程大致如下:

image-20220905172658183

整个分析过程也比较简单,第一步便找到入口点,那后面的只需要时间就能解决了。下面使用python来实现一下加密代码:

Python代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import hmac
from hashlib import sha256
import time
import json
import operator


timestamp = str(time.time()*1000)[0:-4][0:-1] # 返回当前时间的时间戳(1970纪元后经过的浮点秒数)。
key = "JQGyyy1dP2ku1XXXXXXXXX2S3UIf1n0uUNpEK1FL6B"


if __name__ == "__main__":
url = r'/file/download?fileName=&type=1' # GET 参数
data = """{"taskId":2,"pageNum":1,"pageSize":10}""" # POST参数
datan = dict(sorted(json.loads(data).items(), key=operator.itemgetter(0))) # POST参数str型使用json处理,按照首字母顺序进行重排列
post_datam = str(datan).replace('"',"").replace(':','=').replace(",","&").replace("{","&").replace("}","&").replace("'","").replace(" ","")
get_params = "&" + url.split("?")[1] + "&"
signdata = 'Customized-Field={' + '"api":"{}","client-type":"web"'.format(url) + "," + '"timestamp":"{}"'.format(timestamp) + "}" + '{}'.format(params) + "key={}".format(key)
sign = hmac.new(bytes(key, encoding='UTF-8'), bytes(signdata, encoding='UTF-8'), digestmod=sha256).hexdigest() # hmac-sha256加密
print(timestamp)
# print(signdata)
print(sign.upper())

image-20220905173351783

PS-4: 代码写的跟屎一样,各位师傅勿视。

目前第一版代码如上,后续会配合@f0ng师傅写的 项目autoDecoder 结合在BurpSuite中实现自动更新Sign值和Customized-Field。

项目地址:https://github.com/f0ng/autoDecoder

参考:

https://juejin.cn/post/7037704683468619807