做网页采集时,复制 Network 中的一条请求通常只能解释“这一次发出了什么”。要复现请求,还要知道参数来自输入框、页面状态、配置,还是运行时计算。JS 逆向分析可以先从这条数据链入手:原始输入 → 归一化 → 序列化 → 发起请求。
本文使用自建本地页面,不依赖真实站点。示例故意包含空格、加号和时间戳,用来观察字符串在哪一步变化。已通过本地浏览器验证正常请求与空输入分支;断点操作方法依据 Chrome 官方文档,未把它表述成第三方站点的测试结果。
一、准备两个文件
在同一个目录建立 index.html 和 result.json。index.html 内容如下:
<!doctype html>
<meta charset="utf-8">
<title>请求参数追踪实验</title>
<label>SKU <input id="sku" value=" A B+01 "></label>
<button id="query">查询商品</button>
<pre id="output">等待点击</pre>
<script>
const input = document.querySelector('#sku');
const output = document.querySelector('#output');
function normalize(raw) {
return raw.trim();
}
function buildQuery(sku, now) {
return new URLSearchParams({sku, region: 'CN', ts: String(now)}).toString();
}
async function requestProduct() {
const raw = input.value;
const sku = normalize(raw);
if (!sku) { output.textContent = 'SKU 不能为空'; return; }
const query = buildQuery(sku, Date.now());
const url = '/result.json?' + query;
try {
const response = await fetch(url, {cache: 'no-store'});
if (!response.ok) throw new Error('HTTP ' + response.status);
const data = await response.json();
output.textContent = JSON.stringify({raw, sku, url, data}, null, 2);
} catch (error) {
output.textContent = String(error);
}
}
document.querySelector('#query').addEventListener('click', requestProduct);
</script>
result.json 内容:
{"ok": true, "note": "静态演示响应,不是商品接口"}
在该目录运行:
python3 -m http.server 8768 --bind 127.0.0.1
浏览器打开 http://127.0.0.1:8768/ ,点击“查询商品”。请通过这个本地 HTTP 地址访问;直接双击文件会改变请求上下文。静态服务器忽略查询参数来读取 result.json,因此响应只用于验证链路,没有实现商品查询,也没有验证时间戳。
二、先记录一次正常样本
默认输入在两端带空格,中间包含一个空格与一个字面加号。页面输出的 raw 保留原输入,sku 为 A B+01,URL 中对应片段为:
sku=A+B%2B01®ion=CN&ts=运行时毫秒时间戳
这里发生了两件不同的事:trim 去除了首尾空白;URLSearchParams 把中间空格序列化为 +,把字面加号序列化为 %2B。它是 URL 查询参数编码,不是加密。MDN 的 URLSearchParams 文档说明了此类编码行为。
这也是为什么不应先把 SKU 手动编码,再交给 URLSearchParams:百分号可能被再次编码,最终发送值与业务值不同。先确定你比较的是原始字符串、解码后的参数,还是 URL 字节表示。
三、从请求出口向上追
打开 DevTools 的 Network,找到 result.json 请求,核对查询参数和响应。请求详情中的 Initiator 可以帮助定位发起代码。随后在 Sources 的 XHR/fetch Breakpoints 中添加 result.json,再次点击按钮。相关能力见 Chrome 的 断点文档。
暂停后先看当前请求相关的 url、query、sku;在调用栈中切换到业务函数,分清哪层负责准备参数,哪层仅仅包装 fetch。异步边界可能使调用栈不完整,此时结合 Initiator 和明确的行断点定位。
有个很容易踩的坑:停在 fetch 时,normalize 和 buildQuery 已经执行完了。调试器不会因为你继续单步就回到过去。要看参数生成过程,应在 requestProduct 中读取输入前或 buildQuery 调用前设置行断点,然后重新触发一次操作。
第二轮依次观察 raw、sku、query、url,每经过一个函数就记录输入与返回值。这样得到的是可验证的数据流,而不是根据压缩函数名猜用途。
四、用少量对照输入确认推断
本地正常样本已经观察到 A B+01 被发送为 A+B%2B01;仅输入空格时页面显示“SKU 不能为空”,不进入 fetch 分支。读者还可以继续做三组对照:
- 把字面加号换成空格,比较编码结果是否变化。
- 连续点击两次,检查只有时间戳变化还是其他字段也变了。
- 保持输入不变,修改本地 region 配置,确认字段究竟来自哪里。
这些对照建议不等于真实站点的业务规则。本例没有服务端签名校验、会话校验和缓存层,不能根据它推导生产请求只需要这三个参数。
如果值每次都变,也先不要断言“用了随机加密”。时间戳、计数器、随机数、会话状态和序列化顺序都可能导致变化。区分它们需要固定其他输入,并追到具体赋值点。
五、压缩代码与 source map 怎么处理
格式化可以改善缩进,但不会恢复原始变量名和业务含义。可用的 source map 能帮助把部署代码映射到源文件;是否存在、是否加载成功,要看实际页面。参考 Chrome 的 source map 文档。
没有 source map 时,优先用已观察到的 URL 片段、参数键和调用位置收窄范围,再从运行时输入输出判断函数作用。不要一开始就试图读懂整个 bundle。
AI 可以帮助解释已经定位的小段代码,或列出待验证的变量来源假设。给它的材料最好包括函数片段、一次输入、一次输出和你已经确认的调用关系。它给出的“像哈希”“可能是签名”只能作为线索,需要回到运行时验证。
六、最终应留下什么记录
一份有用的逆向笔记应写明触发动作、输入上下文、参数来源、转换函数、请求出口,以及尚未确认的依赖。比如本例可归纳为:按钮事件读取输入,trim 得到 sku,固定 CN 和 Date.now() 组成对象,URLSearchParams 序列化后交给 fetch。
采集实现是否与页面一致,要逐字段对比同一业务输入下的结果。先把“这个参数怎样生成”讲清楚,再讨论换客户端、移植逻辑或增加 AI 自动化,排错会更有依据。
本文由 AI 辅助编写;本地功能示例已运行验证,生产站点的行为需另行核实。

731

被折叠的 条评论
为什么被折叠?



