news 2026/7/4 14:43:21

JS逆向实战:破解企业查询网站动态请求头x-apiKey生成逻辑

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
JS逆向实战:破解企业查询网站动态请求头x-apiKey生成逻辑

1. 项目概述与核心价值

最近在带几个刚入行的朋友做JS逆向的实战练习,发现一个很有意思的现象:很多新手一上来就想搞复杂的参数加密,比如signtoken,结果卡在第一步就进行不下去了。其实,逆向的入门,往往是从最不起眼的地方开始的,比如请求头。今天这个案例,我们就拿一个大家可能都接触过的“某查查”类网站(泛指企业信息查询平台)来开刀,目标不是破解复杂的登录或数据加密,而是搞定它请求头里一个看似简单、实则关键的参数。

这个参数通常叫x-apiKeyAuthorization或者一个自定义的token,它静静地躺在请求头里,却是服务器验证请求合法性的第一道门。很多新手会直接复制浏览器里的值去用,结果发现这个值一会儿就失效了,爬虫跑几分钟就挂了。这就是典型的“动态请求头”问题,也是JS逆向最经典的入门场景。通过这个案例,你能清晰地看到前端JavaScript是如何生成这个关键参数的,理解基本的浏览器环境检测逻辑,并掌握一套从抓包、定位到扣代码、模拟的完整逆向流程。无论你是想学习爬虫应对反爬,还是前端开发想了解安全机制,这个案例都再合适不过了。

2. 逆向目标分析与抓包定位

2.1 目标网站与参数初探

我们以某个典型的企业信息查询网站为例(以下简称“目标站”)。当你打开其搜索页面,输入公司名并点击查询时,浏览器会向服务器发送一个POSTGET请求来获取数据。打开浏览器的开发者工具(F12),切换到Network(网络)选项卡,勾选Preserve log(保留日志),然后进行一次搜索操作。

很快,你会找到一个包含查询结果的请求,比如叫api/search/v1。点击这个请求,查看它的Headers(请求头)。在一堆诸如User-AgentContent-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(源代码)面板,按Ctrl+Shift+F(Windows/Linux)或Cmd+Opt+F(Mac)打开全局搜索框。直接输入你发现的参数名,比如x-apiKey。如果网站没有对代码进行严重的混淆,你很可能会直接找到设置这个请求头的地方。这通常是一个XMLHttpRequest或现代更常用的fetch请求,在发送前被拦截并添加了自定义头部。

第二步:调用栈回溯如果全局搜索没有结果,或者结果太多难以定位,那么“调用栈回溯”就是你的利器。回到Network面板,找到那个携带目标参数的请求,右键点击它,选择Copy->Copy as cURL(或类似选项)。然后,在开发者工具中打开Sources面板,找到并点击Event Listener Breakpoints(事件监听器断点)。展开XHR/Fetch分类,勾选onreadystatechangeonload等事件断点。重新触发请求(比如再次点击搜索),代码执行会在这个请求发出前或收到响应后暂停。此时,查看Call Stack(调用栈)面板,你能看到一长串函数调用关系。从栈顶(最上面、最近被调用的函数)往下逐一查看,寻找那些包含设置请求头逻辑(如setRequestHeader方法)的代码。

第三步:逻辑分析与扣取找到关键代码后,不要急着全部复制。你需要分析这段代码的依赖关系。这个x-apiKey的值是从哪里来的?可能是:

  1. 从某个全局变量或对象属性中读取:比如window._globalToken
  2. 调用某个函数生成:比如generateApiKey()encrypt(timestamp)
  3. 从页面隐藏元素或Cookie中获取。 你需要顺着代码的引用关系,像剥洋葱一样,一层层找到最根源的生成逻辑。这个过程就是“扣代码”,即把生成这个参数所必需的所有JavaScript函数和变量,从庞大的网站源码中提取出来。

第四步:环境补全与模拟执行扣出来的代码往往不能直接在你的Node.js或Python环境中运行,因为它依赖浏览器环境。常见的依赖包括:

  • windowdocumentnavigator等BOM对象。
  • CryptoJSjsencrypt等前端加密库。
  • 网站自定义的一些全局函数或对象。 你需要使用像jsdomPyExecJSNode.jsvm模块,或者更专业的SeleniumPuppeteer(无头浏览器)来补全这些环境,让扣出来的代码能够正确执行。

3.2 本案例的针对性策略

针对“某查查”这类网站,其x-apiKey的生成逻辑通常不会特别复杂(相较于核心业务数据的加密),但往往会包含一些反爬策略:

  1. 时间戳参与:密钥很可能与当前时间戳(Date.now())有关,可能是直接拼接,也可能是经过某种编码(Base64)或哈希(MD5, SHA256)。
  2. 固定盐值或密钥:在代码中可能会硬编码一个字符串(盐值salt)或密钥(secret),用于和时间戳等变量进行组合运算。
  3. 轻度混淆:变量名可能被压缩成单字母(如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 代码逻辑解析

  1. 函数s(t):这很可能就是设置请求头的函数。它首先获取当前时间戳e,然后调用函数o,传入时间戳e和一个固定的字符串"your_secret_key_here123"。最后,使用setRequestHeader方法将o函数的返回值设置为x-apiKey的值。
  2. 函数o(t, e):这是核心的加密函数。它接收两个参数t(时间戳)和e(密钥)。代码中使用了i这个对象进行加密操作。从i.AES.encrypti.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需要:

  1. CryptoJS库。
  2. 当前时间戳。
  3. 固定的密钥"your_secret_key_here123"
  4. 加密函数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-js

5.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: U2FsdGVkX19qBzH8Q9Q7w1+3R4a7LmZ... 完整的请求头示例: { 'User-Agent': 'Mozilla/5.0...', 'Content-Type': 'application/json', 'x-apiKey': 'U2FsdGVkX19qBzH8Q9Q7w1+3R4a7LmZ...' }

关键验证步骤

  1. 在同一时刻(几秒内),用浏览器访问目标网站,抓取真实的x-apiKey值。
  2. 同时运行你的Node.js脚本,生成一个x-apiKey
  3. 对比两个值。如果它们完全一致,恭喜你,逆向成功!如果不一致,请检查:
    • 时间戳精度:网站用的是秒级时间戳(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 请求头管理的进阶技巧

在实际项目中,管理请求头会更复杂一些,这里分享几个技巧:

  1. 使用请求拦截器:如果你使用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; });
  2. 密钥管理:不要把密钥硬编码在代码里,尤其是上传到公共仓库时。应该使用环境变量或配置文件来管理。
    // 使用 dotenv 从 .env 文件读取 require('dotenv').config(); const SECRET_KEY = process.env.API_SECRET_KEY;
  3. 处理时效性:由于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. 检查是否漏掉了某个关键的初始化函数或全局变量赋值。
请求返回401403错误。1.x-apiKey格式或值错误
2.缺少其他必要请求头
3.请求频率过高触发风控
4.IP地址被限制
1. 确保x-apiKey被正确放置在headers对象中,且键名完全正确(注意大小写和连字符)。用脚本生成的Key去替换浏览器请求中的Key,看浏览器请求是否还能成功,进行交叉验证。
2. 检查浏览器成功请求的Headers,看是否还有CookieRefererOriginX-Requested-With等其他重要头信息,一并模拟带上。
3. 在请求间增加随机延迟(如sleep(1-3秒)),模拟人类操作。
4. 考虑使用代理IP池。
扣出来的代码在Node.js中执行报错,提示window/ document is not defined代码依赖浏览器环境(BOM/DOM)。1.补环境:使用jsdom库来模拟windowdocument等对象。
2.代码改写:分析代码,如果它只是用window来存储一个全局变量,你可以直接在Node.js中定义一个同名全局变量。
3.使用无头浏览器:对于环境依赖极其复杂的,直接用Puppeteer控制浏览器执行,虽然重但最稳妥。
加密库(如CryptoJS)版本不兼容,导致加密结果不同。网站使用的库版本与你本地安装的版本内部实现有差异。1. 查看网站加载的crypto-js.js文件的URL,里面通常包含版本号(如crypto-js-4.1.1.min.js)。
2. 在你的项目中安装指定版本:npm install crypto-js@4.1.1
3. 如果网站使用的是自定义打包或修改过的版本,最彻底的方法是直接将其源码扣取下来,保存为本地文件,然后在Node.js中用require(‘./path/to/crypto-js.js’)引入。
算法看起来是标准的,但结果就是不对。可能存在“盐值混淆”或“多步编码”。1.仔细审计代码:在加密函数o之前,可能对时间戳或密钥进行了预处理,比如反转字符串、与一个固定数组进行异或运算、先进行了一次MD5哈希等。
2.使用调试器:在浏览器Sources面板给加密函数打上断点,一步步跟踪每个变量的值,与你本地脚本的中间结果进行比对,找到第一个出现差异的步骤。

独家避坑技巧:在扣取代码时,我习惯使用一个“最小可执行单元测试”的方法。不要一次性扣取一大段代码。而是先只扣取最核心的加密函数(比如上面的o函数),以及它直接依赖的一两个工具函数。然后写一个极简的Node.js脚本,用硬编码的输入去运行这个函数,将输出与浏览器在相同输入下的输出进行对比。如果一致,再逐步扩大扣取范围,添加更多的依赖。这种方法能帮你快速定位问题到底出在哪一层依赖上。

逆向工程就像解谜,耐心和细致是关键。这个“某查查请求头”的案例,完美涵盖了从抓包定位、逻辑分析、环境模拟到集成应用的完整链条。掌握了这套方法,你就拿到了打开JS逆向大门的钥匙,后续面对更复杂的参数加密、WebAssembly(Wasm)或OLLVM混淆时,就有了扎实的基础和清晰的排查思路。记住,多动手、多调试、多思考,每一个报错信息都是通往正确答案的线索。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/4 14:41:39

终极Mac电源管理指南:如何彻底掌控Hackintosh睡眠策略

终极Mac电源管理指南:如何彻底掌控Hackintosh睡眠策略 【免费下载链接】SleeperX MacBook prevent idle/lid sleep! Hackintosh sleep on low battery capacity. 项目地址: https://gitcode.com/gh_mirrors/sl/SleeperX SleeperX是一款专为macOS系统设计的智…

作者头像 李华
网站建设 2026/7/4 14:40:33

安卓平板微信双开实战:WeChatPad原理与太极框架部署指南

1. 项目概述与核心价值如果你和我一样,同时拥有手机和一台平板电脑,并且希望它们都能独立登录微信、同时接收消息,而不是在平板上扫码登录后手机端就被迫下线,那么你肯定对微信官方“不支持平板与手机同时在线”的限制感到无比头疼…

作者头像 李华
网站建设 2026/7/4 14:40:20

5分钟快速修复Umi-OCR启动失败的终极指南:OCR引擎插件缺失解决方案

5分钟快速修复Umi-OCR启动失败的终极指南:OCR引擎插件缺失解决方案 【免费下载链接】Umi-OCR OCR software, free and offline. 开源、免费的离线OCR软件。支持截屏/批量导入图片,PDF文档识别,排除水印/页眉页脚,扫描/生成二维码。…

作者头像 李华
网站建设 2026/7/4 14:39:16

零代码AI智能体创建工具实战指南

1. 项目概述:零门槛AI智能体创建工具 去年我在帮朋友公司搭建客服系统时,发现市面上突然涌现出一批号称"零代码"的AI工具。抱着试试看的心态,我体验了其中三款主流平台,结果意外发现有个工具确实能在2分钟内完成基础AI助…

作者头像 李华
网站建设 2026/7/4 14:38:15

PSO优化BP神经网络:全局寻优与参数反演实战

1. 项目概述:当PSO遇上BP神经网络在机器学习领域,BP神经网络因其强大的非线性拟合能力被广泛应用,但传统的梯度下降训练方式常常陷入局部最优。而粒子群优化算法(PSO)作为一种群体智能优化方法,在参数搜索空…

作者头像 李华
网站建设 2026/7/4 14:36:24

GitLab CI 制品(Artifacts)完全指南:从作用到配置实践

GitLab CI 制品(Artifacts)完全指南:从作用到配置实践一、Artifacts 的核心作用1.1 🟢 什么是 Artifacts?1.2 🔵 Artifacts 与 Cache 的本质区别1.3 🟡 Artifacts 的三大应用场景二、Artifacts …

作者头像 李华