声明:文中所涉及的技术、思路和工具仅供以安全为目的的学习交流使用,任何人不得将其用于非法用途给予盈利等目的,否则后果自行承担!如有侵权烦请告知,我会立即删除并致歉。谢谢!
文章有疑问的,可以公众号发消息问我,或者留言。我每天都会看的。
字数 1021,阅读大约需 6 分钟
前言
在最近的渗透测试项目,经手了某机构的管理系统,其中,它对请求包进行了sign签名校验。
签名位置:请求头中,单独列出一项 X-Request-Sign
签名的效果:
-
• 请求包仅能发送一次,重放提示签名已校验 -
• 签名存在时间校验,超出一定时间,签名过期 -
• 签名会校验请求体中的内容是否被修改,被修改后签名无效
初步分析:
第一条,一般是后端X-Request-Sign的值进行了记录,判断签名是否已经存在,比如保存在Redis中。因为存在签名过期时间,所以只要设置X-Request-Sign值的过期时间,就能避免存储数据过大的问题。
第二条,在X-Request-Sign中分为两个部分,一部分是经过算法得到的hash值,还有一部分拼接在字符串后面,是一串时间,精确到毫秒。
第三条,签名中进行hash算法的一部分参数为请求体中的内容。
X-Request-Sign分析
要分析具体的签名算法,就要先在前端定位代码。
因为X-Request-Sign不常见,基本可以当做是自定义的。
我们在JS代码中全局搜索X-Request-Sign
,即可定位到位置在哪。
在给X-Request-Sign
请求头赋值处,打上断点,向上追栈。
最后定位到的代码格式为
对请求体中参数分割为字符串数组,比如
["username=admin","password=admin123","timexx=202505241145141"]
然后对数组进行排序,采用的是array.sort。
排序完成后,通过&
将字符串数组拼接为一个字符串。
python代码演示:
lis1 = ["username=admin", "password=admin123", "timexx=202505241145141"]
lis1.sort()
print("&".join(lis1))
接着进行国密SM3运算,SM3是国密中用来取hash值的算法,类似与MD5。
最后的结果为
803f6958f180714b77c940e306f7c395bd4b799b3d6652ba797809f2c515abf3202505241145141
Yakit + flask 模拟签名过程
因为涉及到数据排序的情况,Yak在这一块是继承的golang,也就是要自己实现。对此,我表示,能写,但没必要。有这个时间,用python调用库不香吗?
Python的国密库gmssl
pip install gmssl
流程如下:
Yakit webfuzzer ——》热加载 beforeRequest ——》Python flask ——》 热加载 beforeRequest ——》后端
在beforeRequest中,我们要先从req中获取请求体,然后构造http请求,发送给flask。
flask接收到请求体,对其进行分割、排序、拼接,然后进行SM3算法,将结果返回给beforeRequest。
Yakit beforeRequest
beforeRequest = func(https, originReq, req) {
// 获取请求体
body_post = poc.GetHTTPPacketBody(req)
// 发送到flask的数据包
flask_sign = `POST /sign HTTP/1.1
Host: 127.0.0.1:5000
Accept-Encoding: gzip, deflate, br
Accept: */*
Accept-Language: en-US;q=0.9,en;q=0.8
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36
Cache-Control: max-age=0
Content-Type: application/x-www-form-urlencoded
a=1`
// 发送http请求到flask,将请求中的请求体替换为req中的请求体
rsq_body, _, _ = poc.HTTP(
flask_sign,
poc.https(false),
poc.replaceBody(body_post, false),
)
// 得到签名
sign = poc.GetHTTPPacketBody(rsq_body)
// 替换到请求头中
req = poc.ReplaceHTTPPacketHeader(req, "X-Request-SIGN", sign)
/*
一个替换请求参数a的例子
poc.ReplaceHTTPPacketQueryParam(req, "a", "bbb")
*/
// 将修改后的请求返回
return []byte(req)
}
flask
@app.route('/sign', methods=['POST'])
defsign():
body = request.get_data().decode("utf-8")
body_list = body.lower().split("&")
body_list.sort()
body_sort = "&".join(body_list)
return sm3_hash(body_sort) + datetime.now().strftime("%Y%m%d%H%M%S%f")[:15]
SM3算法
from gmssl import sm3
import binascii
def sm3_hash(message: str) -> str:
"""
使用 SM3 算法计算输入消息的哈希值
参数:
message: 待计算哈希的字符串
返回:
计算结果的十六进制字符串
"""
# 将输入字符串转换为字节
message_bytes = message.encode('utf-8')
# 计算 SM3 哈希
hash_bytes = sm3.sm3_hash(list(message_bytes))
# 将字节结果转换为十六进制字符串
return binascii.hexlify(bytes.fromhex(hash_bytes)).decode('ascii')
结果
开启共用热加载代码
后续修改请求包发送到后端,Yakit就会自动替换sign,使修改对我们透明。正常进行渗透测试即可。
攻破了这一块,后续系统也像平时遇到的系统一样,漏洞一堆。
收工,写报告。
总结
梳理了前端sign签名的分析过程,积累了相关的脚本。
sign校验增加了渗透测试的难度,但通过热加载构造sign,能够让其过程对测试人员透明,降低测试难度。
原文始发于微信公众号(进击的HACK):记一次前端一次性sign签名的分析
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论