记一次前端一次性sign签名的分析

admin 2025年5月25日12:40:00评论6 views字数 2965阅读9分53秒阅读模式
记一次前端一次性sign签名的分析

声明:文中所涉及的技术、思路和工具仅供以安全为目的的学习交流使用,任何人不得将其用于非法用途给予盈利等目的,否则后果自行承担!如有侵权烦请告知,我会立即删除并致歉。谢谢

文章有疑问的,可以公众号发消息问我,或者留言。我每天都会看的。

记一次前端一次性sign签名的分析

字数 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。

记一次前端一次性sign签名的分析
ffcdfe7fb0ecd7ad8b93c60320d88d24.png

最后的结果为

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')

结果

开启共用热加载代码

记一次前端一次性sign签名的分析
6309201a57a7bc50ebf95ef68b7bf10a.png

后续修改请求包发送到后端,Yakit就会自动替换sign,使修改对我们透明。正常进行渗透测试即可。

攻破了这一块,后续系统也像平时遇到的系统一样,漏洞一堆。
收工,写报告。

总结

梳理了前端sign签名的分析过程,积累了相关的脚本。

sign校验增加了渗透测试的难度,但通过热加载构造sign,能够让其过程对测试人员透明,降低测试难度。

原文始发于微信公众号(进击的HACK):记一次前端一次性sign签名的分析

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年5月25日12:40:00
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   记一次前端一次性sign签名的分析http://cn-sec.com/archives/4095824.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息