怎么逆向分析网站请求中的JS加密参数
详细介绍如何逆向分析网站请求中的JavaScript加密参数,涵盖网络抓包、断点定位、代码调试、逻辑还原及本地调用的完整工作流程与实用技巧。
在编写自动化请求脚本或进行数据接口集成时,很多开发者都会遇到这样的场景:在浏览器中点击某个查询按钮,接口正常返回了数据;但当复制该请求的 cURL 命令或在 Python 脚本中原样重放时,服务器却返回 403 拒绝或提示“签名验证失败”。检查请求载荷可以发现,请求体或 Headers 中往往多出了类似 sign: 8f9b2d...、_token: eyJhbG... 或 nonce: a742f1 的动态字符串。这些参数并不是用户手动输入的,而是前端网页在发起网络请求前,由 JavaScript 代码在后台结合请求数据、时间戳或设备状态实时计算生成的加密或签名摘要。逆向分析这些加密参数,核心就是顺着浏览器中的请求调用链,在海量的前端脚本中定位生成函数,理清输入明文与加密算法的对应逻辑,并最终在自己的程序中完成等效复现。
一、精准抓包与参数特征辨识
逆向分析的第一步是在浏览器开发者工具中准确辨识加密参数的特征。按下 F12 打开 DevTools,切换到 Network(网络) 面板,勾选 Fetch/XHR 过滤器,然后在网页上执行触发网络请求的操作(例如翻页或提交查询)。
在捕获到的请求中,需要细致观察以下三类关键信息:
- 参数出现的位置:加密信息可能位于 URL 查询字符串(Query String)、请求体(Request Payload / Form Data),或者位于自定义请求头(Request Headers,如
X-Signature、Authorization)。 - 动态关联字段:观察是否存在明显的时间戳(如 10 位或 13 位数字组成的
_t、timestamp)或随机序列(如 UUID 格式或随机十六进制串nonce)。这些通常是加密算法防重放攻击的“盐值”(Salt)。 - 密文编码特征:观察密文的字符构成与长度。如果是 32 位十六进制字符,很可能是 MD5 摘要;如果是 40 位或 64 位字符,通常对应 SHA-1 或 SHA-256;若字符串以
==结尾且包含大小写字母和斜杠,通常是经由 AES、DES 或 RSA 加密后输出的 Base64 字符串。若长短完全不固定且含有特殊分隔符,则更偏向于开发者自定义的序列化与混淆拼接。
二、定位加密代码生成位置的三大路径
定位加密函数是整个逆向分析中最关键的破局点。现代前端项目由于经过 Webpack、Vite 等构建工具打包,代码往往被高度压缩、混淆并合并在几个巨大的 .js 文件中。针对不同形态的网页,通常采用以下三种定位手段:
1. 全局关键词搜索
在 DevTools 中按下 Ctrl + Shift + F(macOS 为 Cmd + Option + F)打开全局搜索抽屉。如果加密参数具有较强的业务特征(如 _signature、secretKey 或 web_token),可以直接输入参数名进行检索。
为了过滤掉大量无关的无关文本,可以采用结构化检索技巧:
- 搜索
sign:或"sign"::直接定位键值对组装位置。 - 搜索
.sign =或['sign'] =:定位对象属性动态赋值位置。 - 搜索加密函数名:若在全局搜索中看到了诸如
CryptoJS.MD5、hex_md5、encrypt(等标识,通常说明该位置就是调用点。
2. XHR / Fetch 断点调试
当参数名称非常普遍(如 t、code、data),全局搜索会返回成千上万个结果,此时 XHR Breakpoints 是最稳妥的定位方法。
操作步骤如下:
- 在 DevTools 中切换到 Sources(源代码) 面板。
- 在右侧侧边栏展开 XHR/fetch Breakpoints 栏目,点击
+图号。 - 输入目标接口的唯一 URL 路径片段(例如
/api/v1/user/info),保存规则。 - 回到页面重新触发该接口请求。浏览器会在底层调用
xhr.send()或fetch()的那一瞬间暂停脚本执行。 - 此时查看右侧的 Call Stack(调用堆栈)。当前停留在底层网络请求库,顺着堆栈自下而上逐层点击回溯,即可离开通用网络库代码,回到进行业务数据打包、添加请求头并执行加密的函数内部。
3. 利用 Initiator 向上追溯
现代前端多使用 Axios 进行网络通信,常在 axios.interceptors.request.use 拦截器中统一注入签名。在 Network 面板中,找到目标接口,将鼠标移动到 Initiator(发起程序) 列上,会弹出一个请求调用栈预览。点击堆栈中靠近顶部但属于项目自身业务代码的文件位置,浏览器会自动打开该源码文件,在附近设置行断点后再次发起请求,就能停在拦截器处理参数的瞬间。
三、单步调试与代码混淆还原
成功下断后,页面会处于冻结状态,此时便进入了代码分析的核心阶段。
1. 格式化压缩代码(Pretty Print)
生产环境的前端代码通常经过了 UglifyJS 或 Terser 压缩,整段逻辑挤在单行内。点击 Sources 面板左下角的 {}(Pretty Print)按钮,浏览器会将混淆压缩的单行代码自动换行并重新缩进排版,极大提升可读性。
2. 断点单步跟踪(Step Over / Step Into)
断点暂停后,合理运用右侧调试按钮控制执行节奏:
- Step over (F10):执行下一行代码,遇到函数调用不进入函数体,适合跳过已确认无关的底层辅助方法。
- Step into (F11):进入当前行调用的函数内部,这是深入加密算法实现(如进入
md5()或自定义签名函数)的关键按键。 - Step out (Shift + F11):跳出当前函数,返回到上一层调用处。
在单步跟踪过程中,密切注视右侧的 Scope(作用域) 窗口,查看 Local 与 Closure 中的变量变化。通常可以看到参数从清晰的业务明文(如 { page: 1, keyword: "test" }),经过排序、拼接固定密钥(Salt),最终计算出散列值或密文的全过程。
3. 识别与应对 Webpack 打包结构
许多现代化站点使用 Webpack 模块化管理。在断点中可能会看到类似如下的代码片段:
// 典型的 Webpack 模块引入示例
var s = n("a7c4");
var signature = (0, s.default)(paramObj, "SALT_KEY_2026");
这里的 n 就是 Webpack 的模块加载函数(__webpack_require__),"a7c4" 是模块 ID。此时可以在 Console 中直接运行 n("a7c4") 查看导出的对象内容;若要了解 (0, s.default) 的具体算法,直接在 Console 中打印 s.default,或者在该行打断点并按下 F11 即可直接跳转到对应算法的定义模块。
四、算法还原与落地的三种途径
找到并理清加密逻辑后,需要将其转化为可在自身工程(如 Python 采集程序、监控脚本)中稳定调用的方案。根据加密逻辑的复杂度与对抗强度,主要有以下三种落地途径:
1. 方案 A:纯 Python / 语言原生算法重写
当经过调试发现前端使用的是业界通用的标准加密方法(例如带有特定盐值拼接的 MD5,或者特定密钥和偏移量的 AES-CBC),最佳做法是直接在自己的程序中用标准库重写。这种方案不需要依赖 JavaScript 运行环境,执行速度最快、消耗资源最少。
示例:若前端断点跟踪发现的拼接逻辑为:MD5(sort_keys(params) + "×tamp=" + t + "&key=ABC_SALT_2026"),对应的 Python 还原代码如下:
import hashlib
import time
def generate_signature(params: dict, salt: str) -> tuple[str, int]:
# 1. 获取当前时间戳(毫秒)
timestamp = int(time.time() * 1000)
# 2. 字典按键名 ASCII 升序排序并拼接
sorted_pairs = [f"{k}={params[k]}" for k in sorted(params.keys())]
query_string = "&".join(sorted_pairs)
# 3. 拼接时间戳与固定盐值
raw_text = f"{query_string}×tamp={timestamp}&key={salt}"
# 4. 计算标准 MD5 摘要
sign = hashlib.md5(raw_text.encode("utf-8")).hexdigest()
return sign, timestamp
# 示例调用
params_demo = {"page": 1, "pageSize": 20, "keyword": "tech"}
salt_demo = "ABC_SALT_2026"
sign, t = generate_signature(params_demo, salt_demo)
print(f"生成的参数: sign={sign}, t={t}")
2. 方案 B:代码抠取与 Node.js 补环境执行
如果前端代码经过深度混淆、含有多层平坦化控制流,或者是开发者手写的非标准魔改散列算法,重写成本过高,此时可以完整“抠出”相关 JS 代码并在 Node.js 中执行。
在 Node.js 中执行前端代码时,常遇到的报错是 ReferenceError: window is not defined 或 document is not defined。这是因为浏览器环境具有专属全局对象。可以使用如下基础补环境样板快速模拟运行环境:
// env_shim.js
// 针对常见 DOM/BOM 属性的基础全局模拟
global.window = global;
global.document = {
createElement: function(tag) { return {}; },
getElementById: function(id) { return null; }
};
global.navigator = {
userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
platform: "Win32"
};
global.location = {
href: "https://target-website.example.com/index",
protocol: "https:"
};
// 引入从网页抠出的加密模块
const coreEncrypt = require("./extracted_encrypt_bundle.js");
// 导出包装好的执行函数供 Python 或外层调用
function getEncryptedToken(payload) {
return coreEncrypt.sign(payload);
}
module.exports = { getEncryptedToken };
3. 方案 C:RPC(远程过程调用)技术
在遇到对抗强度极大、深度依赖浏览器内部硬件特性(例如检测 Canvas 指纹、Audio 指纹、WebGL 渲染上下文)的签名场景时,脱离真实浏览器环境极其繁琐。此时可以采用 RPC 注入调用(如开源工具 Sekiro 或基于原生 WebSocket 实现的简易代理)。
该方案在网页正常加载后,向浏览器控制台注入一段 WebSocket 客户端代码,将挂载在全局或闭包里的加密函数(如 window.getSecretKey)暴露给后端的 RPC 服务端。当外部 Python 脚本需要参数时,直接向 RPC 服务端发送请求,服务端通过 WebSocket 让正在运行的真实浏览器页面执行一次加密计算并将结果返回。这种方案免去了补齐复杂环境的烦恼,但依赖浏览器实例的长期存活。
五、常见踩坑点与验证注意事项
验证测试原则:在将逆向结果投入正式运行前,必须进行“固定输入对照测试”。即:在浏览器控制台给加密函数输入一组固定的明文和时间戳,记录生成的密文;然后在本地重写的代码中输入完全相同的明文和时间戳,确保两者输出的密文逐字完全一致。只有完全一致,才能排除大小写编码、编码字符集(UTF-8 与 GBK)或填充位(Padding)带来的隐形偏差。
在具体实操过程中,以下几个细节极易导致排查受阻,需要格外留意:
- 时间戳时钟偏差:部分网站对时间戳校验严格,如果本地系统的时钟与目标服务器时钟偏差超过 30 秒或数分钟,哪怕签名算法完全正确,接口依旧会返回超期失效错误。可从服务器响应头中的
Date字段提取并校准本地时间偏差。 - 动态盐值与 Cookie/Header 联动:部分签名不仅包含请求表单内容,还会悄悄提取当前 Cookie 中的
csrf_token,或页面初始化时由 HTML 隐藏域下发的特定seed。如果只逆向了算法却未同步该动态种子,重放请求依然会失败。 - 请求头其他特征校验:现代反爬策略常结合
User-Agent、Referer、Origin乃至 HTTP/2 伪头(Pseudo-Headers)顺序进行一致性验证。在发送请求时,必须保证 Headers 的完整性。
合规提示:逆向分析技术应严格用于安全防御测试、接口协议排查、教学研究及受授权的系统数据集成场景。严禁利用逆向技术破解他人未授权的商业系统、绕过正当付费权限或破坏计算机信息系统安全。
JS 单例模式会导致内存泄漏吗?闭包引用陷阱与排查方法
2026-11-05 17:29:14
JS reduce实战:数组去重、扁平化与分组统计
2026-11-05 13:05:36
JS代码怎么在线压缩?新手操作指南与工具推荐
2026-11-04 18:07:25
fflate 与 pako.js 性能对比:JS 解压库怎么选
2026-11-03 22:54:05
JS DOM查询性能优化:缓存引用与减少重排重绘
2026-11-03 22:48:27








