1. 项目概述与核心价值最近在带几个刚入行的朋友做JS逆向的实战练习发现一个很有意思的现象很多新手一上来就想搞复杂的参数加密比如sign、token结果卡在第一步就进行不下去了。其实逆向的入门往往是从最不起眼的地方开始的比如请求头。今天这个案例我们就拿一个大家可能都接触过的“某查查”类网站泛指企业信息查询平台来开刀目标不是破解复杂的登录或数据加密而是搞定它请求头里一个看似简单、实则关键的参数。这个参数通常叫x-apiKey、Authorization或者一个自定义的token它静静地躺在请求头里却是服务器验证请求合法性的第一道门。很多新手会直接复制浏览器里的值去用结果发现这个值一会儿就失效了爬虫跑几分钟就挂了。这就是典型的“动态请求头”问题也是JS逆向最经典的入门场景。通过这个案例你能清晰地看到前端JavaScript是如何生成这个关键参数的理解基本的浏览器环境检测逻辑并掌握一套从抓包、定位到扣代码、模拟的完整逆向流程。无论你是想学习爬虫应对反爬还是前端开发想了解安全机制这个案例都再合适不过了。2. 逆向目标分析与抓包定位2.1 目标网站与参数初探我们以某个典型的企业信息查询网站为例以下简称“目标站”。当你打开其搜索页面输入公司名并点击查询时浏览器会向服务器发送一个POST或GET请求来获取数据。打开浏览器的开发者工具F12切换到Network网络选项卡勾选Preserve log保留日志然后进行一次搜索操作。很快你会找到一个包含查询结果的请求比如叫api/search/v1。点击这个请求查看它的Headers请求头。在一堆诸如User-Agent、Content-Type的常见头信息中你很可能会发现一个“不速之客”例如x-apiKey: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c或者Authorization: Bearer xxxxx.yyyyy.zzzzz又或者是一个完全自定义的名字比如X-Client-Token。这个参数的值通常是一长串看起来像随机字符的字符串而且每次刷新页面或重新搜索这个值都会变化。这就是我们的目标——一个动态生成的请求头参数。2.2 为什么这个参数如此重要服务器端会校验这个参数。如果请求中没有这个参数或者参数值无效、过期服务器通常会返回401 Unauthorized未授权或403 Forbidden禁止访问的错误或者返回一个看似正常但数据为空的结果。直接使用你抓包时复制下来的那个值短时间内可能有效但很快可能是几分钟也可能是一次会话结束就会失效。因此我们必须找到这个参数在前端是如何被计算出来的。注意在开始任何逆向操作前请务必阅读目标网站的robots.txt文件和使用条款并确保你的行为符合法律法规以及网站的规定。本案例仅用于技术学习与交流请勿用于任何非法或侵扰性用途。3. 逆向思路与关键环节拆解面对一个动态的请求头参数标准的逆向思路可以概括为“搜索、定位、分析、扣取”四步。这个流程是通用的适用于大多数JS逆向场景。3.1 逆向核心思路四步法第一步全局搜索定位这是最直接的方法。在开发者工具的Sources源代码面板按CtrlShiftFWindows/Linux或CmdOptFMac打开全局搜索框。直接输入你发现的参数名比如x-apiKey。如果网站没有对代码进行严重的混淆你很可能会直接找到设置这个请求头的地方。这通常是一个XMLHttpRequest或现代更常用的fetch请求在发送前被拦截并添加了自定义头部。第二步调用栈回溯如果全局搜索没有结果或者结果太多难以定位那么“调用栈回溯”就是你的利器。回到Network面板找到那个携带目标参数的请求右键点击它选择Copy-Copy as cURL或类似选项。然后在开发者工具中打开Sources面板找到并点击Event Listener Breakpoints事件监听器断点。展开XHR/Fetch分类勾选onreadystatechange或onload等事件断点。重新触发请求比如再次点击搜索代码执行会在这个请求发出前或收到响应后暂停。此时查看Call Stack调用栈面板你能看到一长串函数调用关系。从栈顶最上面、最近被调用的函数往下逐一查看寻找那些包含设置请求头逻辑如setRequestHeader方法的代码。第三步逻辑分析与扣取找到关键代码后不要急着全部复制。你需要分析这段代码的依赖关系。这个x-apiKey的值是从哪里来的可能是从某个全局变量或对象属性中读取比如window._globalToken。调用某个函数生成比如generateApiKey()或encrypt(timestamp)。从页面隐藏元素或Cookie中获取。 你需要顺着代码的引用关系像剥洋葱一样一层层找到最根源的生成逻辑。这个过程就是“扣代码”即把生成这个参数所必需的所有JavaScript函数和变量从庞大的网站源码中提取出来。第四步环境补全与模拟执行扣出来的代码往往不能直接在你的Node.js或Python环境中运行因为它依赖浏览器环境。常见的依赖包括window、document、navigator等BOM对象。CryptoJS、jsencrypt等前端加密库。网站自定义的一些全局函数或对象。 你需要使用像jsdom、PyExecJS、Node.js的vm模块或者更专业的Selenium、Puppeteer无头浏览器来补全这些环境让扣出来的代码能够正确执行。3.2 本案例的针对性策略针对“某查查”这类网站其x-apiKey的生成逻辑通常不会特别复杂相较于核心业务数据的加密但往往会包含一些反爬策略时间戳参与密钥很可能与当前时间戳Date.now()有关可能是直接拼接也可能是经过某种编码Base64或哈希MD5, SHA256。固定盐值或密钥在代码中可能会硬编码一个字符串盐值salt或密钥secret用于和时间戳等变量进行组合运算。轻度混淆变量名可能被压缩成单字母如a, b, c但核心逻辑如CryptoJS.HmacSHA256(timestamp, secret).toString()仍然清晰可辨。 我们的策略就是通过上述四步法找到这个生成函数分析其输入时间戳、固定盐值和输出最终的x-apiKey然后模拟实现。4. 实操过程定位、分析与扣取关键代码假设我们通过全局搜索x-apiKey在某个名为app.8a9f1c.js的压缩文件中找到了如下代码片段代码已进行格式化以便阅读function s(t) { var e Date.now().toString(); var n o(e, your_secret_key_here123); return r.setRequestHeader(x-apiKey, n), t } function o(t, e) { var n i.enc.Utf8.parse(e); var r i.enc.Utf8.parse(t); var s i.AES.encrypt(r, n, { mode: i.mode.ECB, padding: i.pad.Pkcs7 }); return s.toString(); }4.1 代码逻辑解析函数s(t)这很可能就是设置请求头的函数。它首先获取当前时间戳e然后调用函数o传入时间戳e和一个固定的字符串your_secret_key_here123。最后使用setRequestHeader方法将o函数的返回值设置为x-apiKey的值。函数o(t, e)这是核心的加密函数。它接收两个参数t时间戳和e密钥。代码中使用了i这个对象进行加密操作。从i.AES.encrypt、i.mode.ECB等属性可以判断i就是前端常用的CryptoJS加密库。i.enc.Utf8.parse将字符串转换为CryptoJS内部使用的“WordArray”格式。i.AES.encrypt使用AES算法进行加密。参数分别是明文时间戳转换的WordArray、密钥固定密钥转换的WordArray、配置项模式为ECB填充为Pkcs7。s.toString()将加密后的密文对象转换为字符串这个字符串就是最终的x-apiKey值。4.2 依赖分析与扣取现在我们知道生成x-apiKey需要CryptoJS库。当前时间戳。固定的密钥your_secret_key_here123。加密函数o的逻辑。因此我们需要扣取的代码包括整个o函数。确保CryptoJS可用。我们可以直接扣取网站上加载的CryptoJS源码或者更简单的方法在Node.js环境中使用npm install crypto-js安装官方库。实操心得在扣取类似CryptoJS这种大型库时不建议手动复制压缩后的源码。最佳实践是识别出网站使用的库及其版本然后在你的本地环境中通过包管理器安装同名同版本的库这样能最大程度保证兼容性避免因版本差异导致的加密结果不一致。5. 环境模拟与本地复现我们选择在Node.js环境中复现这个逻辑因为Node.js的生态丰富执行JavaScript非常方便。5.1 项目初始化与依赖安装首先创建一个新的项目目录并初始化。mkdir js-reverse-demo cd js-reverse-demo npm init -y然后安装我们需要的crypto-js库。npm install crypto-js5.2 编写复现代码创建一个名为generate_key.js的文件将我们分析并扣取的逻辑写入。// 引入crypto-js库 const CryptoJS require(crypto-js); // 扣取的加密函数 o(t, e) function generateApiKey(timestamp, secret) { // 将密钥和明文转换为CryptoJS可处理的格式 const key CryptoJS.enc.Utf8.parse(secret); const message CryptoJS.enc.Utf8.parse(timestamp.toString()); // 确保是字符串 // AES-ECB-Pkcs7 加密 const encrypted CryptoJS.AES.encrypt(message, key, { mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7 }); // 返回密文字符串 return encrypted.toString(); } // 模拟请求头设置函数 s(t) function setRequestHeader() { const timestamp Date.now(); // 获取当前时间戳 const secret your_secret_key_here123; // 硬编码的密钥从源码中获取 const apiKey generateApiKey(timestamp, secret); console.log(Timestamp: ${timestamp}); console.log(Generated x-apiKey: ${apiKey}); // 这里模拟设置请求头实际使用时是放入headers对象 const headers { User-Agent: Mozilla/5.0..., Content-Type: application/json, x-apiKey: apiKey }; return headers; } // 执行并测试 const myHeaders setRequestHeader(); console.log(完整的请求头示例:, myHeaders);5.3 运行测试与验证在终端运行这个脚本node generate_key.js你会看到输出类似Timestamp: 1712345678901 Generated x-apiKey: U2FsdGVkX19qBzH8Q9Q7w13R4a7LmZ... 完整的请求头示例: { User-Agent: Mozilla/5.0..., Content-Type: application/json, x-apiKey: U2FsdGVkX19qBzH8Q9Q7w13R4a7LmZ... }关键验证步骤在同一时刻几秒内用浏览器访问目标网站抓取真实的x-apiKey值。同时运行你的Node.js脚本生成一个x-apiKey。对比两个值。如果它们完全一致恭喜你逆向成功如果不一致请检查时间戳精度网站用的是秒级时间戳Math.floor(Date.now()/1000)还是毫秒级Date.now()密钥是否正确确认你扣取的密钥字符串和源码中完全一致包括大小写和特殊字符。加密配置AES的模式ECB, CBC、填充Pkcs7, ZeroPadding是否完全匹配编码问题加密前的时间戳是否需要先进行Base64编码或其它处理6. 集成到爬虫与请求发送逆向的最终目的是为了应用。下面我们以Node.js的axios库为例展示如何将动态生成的x-apiKey集成到实际的网络请求中。6.1 安装axios并编写请求函数npm install axios创建一个request_demo.js文件const axios require(axios); const CryptoJS require(crypto-js); // 复用之前的生成函数 function generateApiKey(timestamp, secret) { const key CryptoJS.enc.Utf8.parse(secret); const message CryptoJS.enc.Utf8.parse(timestamp.toString()); const encrypted CryptoJS.AES.encrypt(message, key, { mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7 }); return encrypted.toString(); } async function searchCompany(companyName) { const timestamp Date.now(); const secret your_secret_key_here123; // 从源码扣取的真实密钥 const apiKey generateApiKey(timestamp, secret); const url https://目标网站域名/api/search/v1; // 替换为实际API地址 const payload { keyword: companyName, page: 1, size: 20 }; const headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36..., Content-Type: application/json, x-apiKey: apiKey, // 可能还需要其他固定或动态的头部根据抓包结果补充 // Referer: https://目标网站域名/, // Origin: https://目标网站域名/ }; try { const response await axios.post(url, payload, { headers }); console.log(请求成功); console.log(返回数据:, response.data); // 处理数据... return response.data; } catch (error) { console.error(请求失败); if (error.response) { // 服务器返回了错误状态码 console.error(状态码:, error.response.status); console.error(响应头:, error.response.headers); console.error(响应数据:, error.response.data); } else if (error.request) { // 请求发出了但没有收到响应 console.error(未收到响应:, error.request); } else { // 请求配置出错 console.error(请求配置错误:, error.message); } throw error; } } // 调用示例 (async () { try { await searchCompany(示例科技有限公司); } catch (e) { // 错误处理 } })();6.2 请求头管理的进阶技巧在实际项目中管理请求头会更复杂一些这里分享几个技巧使用请求拦截器如果你使用axios可以配置一个请求拦截器自动为每个请求计算并添加x-apiKey避免在每个请求函数里重复写生成逻辑。axios.interceptors.request.use(config { const timestamp Date.now(); const apiKey generateApiKey(timestamp, SECRET_KEY); config.headers[x-apiKey] apiKey; return config; });密钥管理不要把密钥硬编码在代码里尤其是上传到公共仓库时。应该使用环境变量或配置文件来管理。// 使用 dotenv 从 .env 文件读取 require(dotenv).config(); const SECRET_KEY process.env.API_SECRET_KEY;处理时效性由于x-apiKey基于时间戳生成要确保你的服务器时间与目标网站服务器时间大致同步。如果遇到“过期”错误可以尝试在生成时间戳时减去或加上一个小的偏移量如几秒钟进行校准。7. 常见问题排查与避坑指南在逆向和复现过程中你几乎一定会遇到各种问题。下面是一个常见问题速查表收录了我踩过的坑和解决方案。问题现象可能原因排查思路与解决方案生成的x-apiKey与浏览器抓包的值不一致。1.时间戳单位/格式错误。2.密钥错误大小写、多余空格。3.加密算法或模式不匹配。4.代码依赖环境缺失。1. 核对时间戳用console.log在浏览器端打印出参与加密的原始时间戳与你的脚本输出对比。确认是秒还是毫秒是否需要toString(16)十六进制。2. 仔细检查扣取的密钥字符串确保完全一致。可以在浏览器控制台打印出该变量进行核对。3. 仔细阅读加密部分的代码确认算法AES, DES, RSA、模式ECB, CBC、填充Pkcs7, ZeroPadding、输出格式Base64, Hex等所有参数。4. 检查是否漏掉了某个关键的初始化函数或全局变量赋值。请求返回401或403错误。1.x-apiKey格式或值错误。2.缺少其他必要请求头。3.请求频率过高触发风控。4.IP地址被限制。1. 确保x-apiKey被正确放置在headers对象中且键名完全正确注意大小写和连字符。用脚本生成的Key去替换浏览器请求中的Key看浏览器请求是否还能成功进行交叉验证。2. 检查浏览器成功请求的Headers看是否还有Cookie、Referer、Origin、X-Requested-With等其他重要头信息一并模拟带上。3. 在请求间增加随机延迟如sleep(1-3秒)模拟人类操作。4. 考虑使用代理IP池。扣出来的代码在Node.js中执行报错提示window/ document is not defined。代码依赖浏览器环境BOM/DOM。1.补环境使用jsdom库来模拟window、document等对象。2.代码改写分析代码如果它只是用window来存储一个全局变量你可以直接在Node.js中定义一个同名全局变量。3.使用无头浏览器对于环境依赖极其复杂的直接用Puppeteer控制浏览器执行虽然重但最稳妥。加密库如CryptoJS版本不兼容导致加密结果不同。网站使用的库版本与你本地安装的版本内部实现有差异。1. 查看网站加载的crypto-js.js文件的URL里面通常包含版本号如crypto-js-4.1.1.min.js。2. 在你的项目中安装指定版本npm install crypto-js4.1.1。3. 如果网站使用的是自定义打包或修改过的版本最彻底的方法是直接将其源码扣取下来保存为本地文件然后在Node.js中用require(‘./path/to/crypto-js.js’)引入。算法看起来是标准的但结果就是不对。可能存在“盐值混淆”或“多步编码”。1.仔细审计代码在加密函数o之前可能对时间戳或密钥进行了预处理比如反转字符串、与一个固定数组进行异或运算、先进行了一次MD5哈希等。2.使用调试器在浏览器Sources面板给加密函数打上断点一步步跟踪每个变量的值与你本地脚本的中间结果进行比对找到第一个出现差异的步骤。独家避坑技巧在扣取代码时我习惯使用一个“最小可执行单元测试”的方法。不要一次性扣取一大段代码。而是先只扣取最核心的加密函数比如上面的o函数以及它直接依赖的一两个工具函数。然后写一个极简的Node.js脚本用硬编码的输入去运行这个函数将输出与浏览器在相同输入下的输出进行对比。如果一致再逐步扩大扣取范围添加更多的依赖。这种方法能帮你快速定位问题到底出在哪一层依赖上。逆向工程就像解谜耐心和细致是关键。这个“某查查请求头”的案例完美涵盖了从抓包定位、逻辑分析、环境模拟到集成应用的完整链条。掌握了这套方法你就拿到了打开JS逆向大门的钥匙后续面对更复杂的参数加密、WebAssemblyWasm或OLLVM混淆时就有了扎实的基础和清晰的排查思路。记住多动手、多调试、多思考每一个报错信息都是通往正确答案的线索。
JS逆向实战:破解企业查询网站动态请求头x-apiKey生成逻辑
发布时间:2026/7/4 14:43:46
1. 项目概述与核心价值最近在带几个刚入行的朋友做JS逆向的实战练习发现一个很有意思的现象很多新手一上来就想搞复杂的参数加密比如sign、token结果卡在第一步就进行不下去了。其实逆向的入门往往是从最不起眼的地方开始的比如请求头。今天这个案例我们就拿一个大家可能都接触过的“某查查”类网站泛指企业信息查询平台来开刀目标不是破解复杂的登录或数据加密而是搞定它请求头里一个看似简单、实则关键的参数。这个参数通常叫x-apiKey、Authorization或者一个自定义的token它静静地躺在请求头里却是服务器验证请求合法性的第一道门。很多新手会直接复制浏览器里的值去用结果发现这个值一会儿就失效了爬虫跑几分钟就挂了。这就是典型的“动态请求头”问题也是JS逆向最经典的入门场景。通过这个案例你能清晰地看到前端JavaScript是如何生成这个关键参数的理解基本的浏览器环境检测逻辑并掌握一套从抓包、定位到扣代码、模拟的完整逆向流程。无论你是想学习爬虫应对反爬还是前端开发想了解安全机制这个案例都再合适不过了。2. 逆向目标分析与抓包定位2.1 目标网站与参数初探我们以某个典型的企业信息查询网站为例以下简称“目标站”。当你打开其搜索页面输入公司名并点击查询时浏览器会向服务器发送一个POST或GET请求来获取数据。打开浏览器的开发者工具F12切换到Network网络选项卡勾选Preserve log保留日志然后进行一次搜索操作。很快你会找到一个包含查询结果的请求比如叫api/search/v1。点击这个请求查看它的Headers请求头。在一堆诸如User-Agent、Content-Type的常见头信息中你很可能会发现一个“不速之客”例如x-apiKey: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c或者Authorization: Bearer xxxxx.yyyyy.zzzzz又或者是一个完全自定义的名字比如X-Client-Token。这个参数的值通常是一长串看起来像随机字符的字符串而且每次刷新页面或重新搜索这个值都会变化。这就是我们的目标——一个动态生成的请求头参数。2.2 为什么这个参数如此重要服务器端会校验这个参数。如果请求中没有这个参数或者参数值无效、过期服务器通常会返回401 Unauthorized未授权或403 Forbidden禁止访问的错误或者返回一个看似正常但数据为空的结果。直接使用你抓包时复制下来的那个值短时间内可能有效但很快可能是几分钟也可能是一次会话结束就会失效。因此我们必须找到这个参数在前端是如何被计算出来的。注意在开始任何逆向操作前请务必阅读目标网站的robots.txt文件和使用条款并确保你的行为符合法律法规以及网站的规定。本案例仅用于技术学习与交流请勿用于任何非法或侵扰性用途。3. 逆向思路与关键环节拆解面对一个动态的请求头参数标准的逆向思路可以概括为“搜索、定位、分析、扣取”四步。这个流程是通用的适用于大多数JS逆向场景。3.1 逆向核心思路四步法第一步全局搜索定位这是最直接的方法。在开发者工具的Sources源代码面板按CtrlShiftFWindows/Linux或CmdOptFMac打开全局搜索框。直接输入你发现的参数名比如x-apiKey。如果网站没有对代码进行严重的混淆你很可能会直接找到设置这个请求头的地方。这通常是一个XMLHttpRequest或现代更常用的fetch请求在发送前被拦截并添加了自定义头部。第二步调用栈回溯如果全局搜索没有结果或者结果太多难以定位那么“调用栈回溯”就是你的利器。回到Network面板找到那个携带目标参数的请求右键点击它选择Copy-Copy as cURL或类似选项。然后在开发者工具中打开Sources面板找到并点击Event Listener Breakpoints事件监听器断点。展开XHR/Fetch分类勾选onreadystatechange或onload等事件断点。重新触发请求比如再次点击搜索代码执行会在这个请求发出前或收到响应后暂停。此时查看Call Stack调用栈面板你能看到一长串函数调用关系。从栈顶最上面、最近被调用的函数往下逐一查看寻找那些包含设置请求头逻辑如setRequestHeader方法的代码。第三步逻辑分析与扣取找到关键代码后不要急着全部复制。你需要分析这段代码的依赖关系。这个x-apiKey的值是从哪里来的可能是从某个全局变量或对象属性中读取比如window._globalToken。调用某个函数生成比如generateApiKey()或encrypt(timestamp)。从页面隐藏元素或Cookie中获取。 你需要顺着代码的引用关系像剥洋葱一样一层层找到最根源的生成逻辑。这个过程就是“扣代码”即把生成这个参数所必需的所有JavaScript函数和变量从庞大的网站源码中提取出来。第四步环境补全与模拟执行扣出来的代码往往不能直接在你的Node.js或Python环境中运行因为它依赖浏览器环境。常见的依赖包括window、document、navigator等BOM对象。CryptoJS、jsencrypt等前端加密库。网站自定义的一些全局函数或对象。 你需要使用像jsdom、PyExecJS、Node.js的vm模块或者更专业的Selenium、Puppeteer无头浏览器来补全这些环境让扣出来的代码能够正确执行。3.2 本案例的针对性策略针对“某查查”这类网站其x-apiKey的生成逻辑通常不会特别复杂相较于核心业务数据的加密但往往会包含一些反爬策略时间戳参与密钥很可能与当前时间戳Date.now()有关可能是直接拼接也可能是经过某种编码Base64或哈希MD5, SHA256。固定盐值或密钥在代码中可能会硬编码一个字符串盐值salt或密钥secret用于和时间戳等变量进行组合运算。轻度混淆变量名可能被压缩成单字母如a, b, c但核心逻辑如CryptoJS.HmacSHA256(timestamp, secret).toString()仍然清晰可辨。 我们的策略就是通过上述四步法找到这个生成函数分析其输入时间戳、固定盐值和输出最终的x-apiKey然后模拟实现。4. 实操过程定位、分析与扣取关键代码假设我们通过全局搜索x-apiKey在某个名为app.8a9f1c.js的压缩文件中找到了如下代码片段代码已进行格式化以便阅读function s(t) { var e Date.now().toString(); var n o(e, your_secret_key_here123); return r.setRequestHeader(x-apiKey, n), t } function o(t, e) { var n i.enc.Utf8.parse(e); var r i.enc.Utf8.parse(t); var s i.AES.encrypt(r, n, { mode: i.mode.ECB, padding: i.pad.Pkcs7 }); return s.toString(); }4.1 代码逻辑解析函数s(t)这很可能就是设置请求头的函数。它首先获取当前时间戳e然后调用函数o传入时间戳e和一个固定的字符串your_secret_key_here123。最后使用setRequestHeader方法将o函数的返回值设置为x-apiKey的值。函数o(t, e)这是核心的加密函数。它接收两个参数t时间戳和e密钥。代码中使用了i这个对象进行加密操作。从i.AES.encrypt、i.mode.ECB等属性可以判断i就是前端常用的CryptoJS加密库。i.enc.Utf8.parse将字符串转换为CryptoJS内部使用的“WordArray”格式。i.AES.encrypt使用AES算法进行加密。参数分别是明文时间戳转换的WordArray、密钥固定密钥转换的WordArray、配置项模式为ECB填充为Pkcs7。s.toString()将加密后的密文对象转换为字符串这个字符串就是最终的x-apiKey值。4.2 依赖分析与扣取现在我们知道生成x-apiKey需要CryptoJS库。当前时间戳。固定的密钥your_secret_key_here123。加密函数o的逻辑。因此我们需要扣取的代码包括整个o函数。确保CryptoJS可用。我们可以直接扣取网站上加载的CryptoJS源码或者更简单的方法在Node.js环境中使用npm install crypto-js安装官方库。实操心得在扣取类似CryptoJS这种大型库时不建议手动复制压缩后的源码。最佳实践是识别出网站使用的库及其版本然后在你的本地环境中通过包管理器安装同名同版本的库这样能最大程度保证兼容性避免因版本差异导致的加密结果不一致。5. 环境模拟与本地复现我们选择在Node.js环境中复现这个逻辑因为Node.js的生态丰富执行JavaScript非常方便。5.1 项目初始化与依赖安装首先创建一个新的项目目录并初始化。mkdir js-reverse-demo cd js-reverse-demo npm init -y然后安装我们需要的crypto-js库。npm install crypto-js5.2 编写复现代码创建一个名为generate_key.js的文件将我们分析并扣取的逻辑写入。// 引入crypto-js库 const CryptoJS require(crypto-js); // 扣取的加密函数 o(t, e) function generateApiKey(timestamp, secret) { // 将密钥和明文转换为CryptoJS可处理的格式 const key CryptoJS.enc.Utf8.parse(secret); const message CryptoJS.enc.Utf8.parse(timestamp.toString()); // 确保是字符串 // AES-ECB-Pkcs7 加密 const encrypted CryptoJS.AES.encrypt(message, key, { mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7 }); // 返回密文字符串 return encrypted.toString(); } // 模拟请求头设置函数 s(t) function setRequestHeader() { const timestamp Date.now(); // 获取当前时间戳 const secret your_secret_key_here123; // 硬编码的密钥从源码中获取 const apiKey generateApiKey(timestamp, secret); console.log(Timestamp: ${timestamp}); console.log(Generated x-apiKey: ${apiKey}); // 这里模拟设置请求头实际使用时是放入headers对象 const headers { User-Agent: Mozilla/5.0..., Content-Type: application/json, x-apiKey: apiKey }; return headers; } // 执行并测试 const myHeaders setRequestHeader(); console.log(完整的请求头示例:, myHeaders);5.3 运行测试与验证在终端运行这个脚本node generate_key.js你会看到输出类似Timestamp: 1712345678901 Generated x-apiKey: U2FsdGVkX19qBzH8Q9Q7w13R4a7LmZ... 完整的请求头示例: { User-Agent: Mozilla/5.0..., Content-Type: application/json, x-apiKey: U2FsdGVkX19qBzH8Q9Q7w13R4a7LmZ... }关键验证步骤在同一时刻几秒内用浏览器访问目标网站抓取真实的x-apiKey值。同时运行你的Node.js脚本生成一个x-apiKey。对比两个值。如果它们完全一致恭喜你逆向成功如果不一致请检查时间戳精度网站用的是秒级时间戳Math.floor(Date.now()/1000)还是毫秒级Date.now()密钥是否正确确认你扣取的密钥字符串和源码中完全一致包括大小写和特殊字符。加密配置AES的模式ECB, CBC、填充Pkcs7, ZeroPadding是否完全匹配编码问题加密前的时间戳是否需要先进行Base64编码或其它处理6. 集成到爬虫与请求发送逆向的最终目的是为了应用。下面我们以Node.js的axios库为例展示如何将动态生成的x-apiKey集成到实际的网络请求中。6.1 安装axios并编写请求函数npm install axios创建一个request_demo.js文件const axios require(axios); const CryptoJS require(crypto-js); // 复用之前的生成函数 function generateApiKey(timestamp, secret) { const key CryptoJS.enc.Utf8.parse(secret); const message CryptoJS.enc.Utf8.parse(timestamp.toString()); const encrypted CryptoJS.AES.encrypt(message, key, { mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7 }); return encrypted.toString(); } async function searchCompany(companyName) { const timestamp Date.now(); const secret your_secret_key_here123; // 从源码扣取的真实密钥 const apiKey generateApiKey(timestamp, secret); const url https://目标网站域名/api/search/v1; // 替换为实际API地址 const payload { keyword: companyName, page: 1, size: 20 }; const headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36..., Content-Type: application/json, x-apiKey: apiKey, // 可能还需要其他固定或动态的头部根据抓包结果补充 // Referer: https://目标网站域名/, // Origin: https://目标网站域名/ }; try { const response await axios.post(url, payload, { headers }); console.log(请求成功); console.log(返回数据:, response.data); // 处理数据... return response.data; } catch (error) { console.error(请求失败); if (error.response) { // 服务器返回了错误状态码 console.error(状态码:, error.response.status); console.error(响应头:, error.response.headers); console.error(响应数据:, error.response.data); } else if (error.request) { // 请求发出了但没有收到响应 console.error(未收到响应:, error.request); } else { // 请求配置出错 console.error(请求配置错误:, error.message); } throw error; } } // 调用示例 (async () { try { await searchCompany(示例科技有限公司); } catch (e) { // 错误处理 } })();6.2 请求头管理的进阶技巧在实际项目中管理请求头会更复杂一些这里分享几个技巧使用请求拦截器如果你使用axios可以配置一个请求拦截器自动为每个请求计算并添加x-apiKey避免在每个请求函数里重复写生成逻辑。axios.interceptors.request.use(config { const timestamp Date.now(); const apiKey generateApiKey(timestamp, SECRET_KEY); config.headers[x-apiKey] apiKey; return config; });密钥管理不要把密钥硬编码在代码里尤其是上传到公共仓库时。应该使用环境变量或配置文件来管理。// 使用 dotenv 从 .env 文件读取 require(dotenv).config(); const SECRET_KEY process.env.API_SECRET_KEY;处理时效性由于x-apiKey基于时间戳生成要确保你的服务器时间与目标网站服务器时间大致同步。如果遇到“过期”错误可以尝试在生成时间戳时减去或加上一个小的偏移量如几秒钟进行校准。7. 常见问题排查与避坑指南在逆向和复现过程中你几乎一定会遇到各种问题。下面是一个常见问题速查表收录了我踩过的坑和解决方案。问题现象可能原因排查思路与解决方案生成的x-apiKey与浏览器抓包的值不一致。1.时间戳单位/格式错误。2.密钥错误大小写、多余空格。3.加密算法或模式不匹配。4.代码依赖环境缺失。1. 核对时间戳用console.log在浏览器端打印出参与加密的原始时间戳与你的脚本输出对比。确认是秒还是毫秒是否需要toString(16)十六进制。2. 仔细检查扣取的密钥字符串确保完全一致。可以在浏览器控制台打印出该变量进行核对。3. 仔细阅读加密部分的代码确认算法AES, DES, RSA、模式ECB, CBC、填充Pkcs7, ZeroPadding、输出格式Base64, Hex等所有参数。4. 检查是否漏掉了某个关键的初始化函数或全局变量赋值。请求返回401或403错误。1.x-apiKey格式或值错误。2.缺少其他必要请求头。3.请求频率过高触发风控。4.IP地址被限制。1. 确保x-apiKey被正确放置在headers对象中且键名完全正确注意大小写和连字符。用脚本生成的Key去替换浏览器请求中的Key看浏览器请求是否还能成功进行交叉验证。2. 检查浏览器成功请求的Headers看是否还有Cookie、Referer、Origin、X-Requested-With等其他重要头信息一并模拟带上。3. 在请求间增加随机延迟如sleep(1-3秒)模拟人类操作。4. 考虑使用代理IP池。扣出来的代码在Node.js中执行报错提示window/ document is not defined。代码依赖浏览器环境BOM/DOM。1.补环境使用jsdom库来模拟window、document等对象。2.代码改写分析代码如果它只是用window来存储一个全局变量你可以直接在Node.js中定义一个同名全局变量。3.使用无头浏览器对于环境依赖极其复杂的直接用Puppeteer控制浏览器执行虽然重但最稳妥。加密库如CryptoJS版本不兼容导致加密结果不同。网站使用的库版本与你本地安装的版本内部实现有差异。1. 查看网站加载的crypto-js.js文件的URL里面通常包含版本号如crypto-js-4.1.1.min.js。2. 在你的项目中安装指定版本npm install crypto-js4.1.1。3. 如果网站使用的是自定义打包或修改过的版本最彻底的方法是直接将其源码扣取下来保存为本地文件然后在Node.js中用require(‘./path/to/crypto-js.js’)引入。算法看起来是标准的但结果就是不对。可能存在“盐值混淆”或“多步编码”。1.仔细审计代码在加密函数o之前可能对时间戳或密钥进行了预处理比如反转字符串、与一个固定数组进行异或运算、先进行了一次MD5哈希等。2.使用调试器在浏览器Sources面板给加密函数打上断点一步步跟踪每个变量的值与你本地脚本的中间结果进行比对找到第一个出现差异的步骤。独家避坑技巧在扣取代码时我习惯使用一个“最小可执行单元测试”的方法。不要一次性扣取一大段代码。而是先只扣取最核心的加密函数比如上面的o函数以及它直接依赖的一两个工具函数。然后写一个极简的Node.js脚本用硬编码的输入去运行这个函数将输出与浏览器在相同输入下的输出进行对比。如果一致再逐步扩大扣取范围添加更多的依赖。这种方法能帮你快速定位问题到底出在哪一层依赖上。逆向工程就像解谜耐心和细致是关键。这个“某查查请求头”的案例完美涵盖了从抓包定位、逻辑分析、环境模拟到集成应用的完整链条。掌握了这套方法你就拿到了打开JS逆向大门的钥匙后续面对更复杂的参数加密、WebAssemblyWasm或OLLVM混淆时就有了扎实的基础和清晰的排查思路。记住多动手、多调试、多思考每一个报错信息都是通往正确答案的线索。