Web接口加密逆向实战:Shopee x-sap-sec参数分析与复现 1. 项目概述一次典型的Web接口加密逆向实战最近在分析一些电商平台的数据交互时Shopee虾皮的接口引起了我的注意。它的请求头里带着几个看起来就不好惹的字段x-sap-sec、x-sap-ri还有那个af-ac-enc-dat。做过逆向的朋友都知道这种以x-开头的自定义头部往往就是平台反爬策略的核心所在里面通常封装了时间戳、签名、设备指纹等关键信息用于验证请求的合法性。尤其是x-sap-sec从名字就能猜到它和安全Security强相关是本次逆向分析的主要目标。这不仅仅是为了“爬数据”更是理解现代Web应用如何构建前端安全防线的一个绝佳案例。对于前端开发、安全研究甚至是测试同学来说掌握这套分析思路在遇到类似加密参数、签名校验问题时都能有一套清晰的排查路径。整个逆向过程就像是在玩一个没有攻略的解谜游戏。你面对的是经过混淆、压缩甚至虚拟化保护的JavaScript代码目标是从一堆看似杂乱无章的字符中找到生成那个特定加密字符串的几行关键逻辑。这需要耐心、对JavaScript运行机制的深刻理解以及一套行之有效的工具链。本次分析将完全聚焦于技术层面记录从定位加密入口、追踪参数生成、到最终还原x-sap-sec加密逻辑的完整步骤。我会尽量还原思考过程并分享其中踩过的坑和总结的技巧希望能给正在或即将进行类似逆向分析的朋友一些参考。2. 逆向环境准备与核心思路拆解在开始“硬刚”代码之前搭建一个高效的逆向分析环境至关重要。这能让你在后续的动态调试中事半功倍。我的核心思路是“动静结合”静态分析寻找线索动态调试验证猜想。2.1 工具链选型与配置工欲善其事必先利其器。以下是我本次分析使用的主要工具它们各自扮演着不同的角色浏览器开发者工具Chrome DevTools这是主战场。重点是Sources面板和Network面板。在Sources面板中我通常会启用Pretty Print美化代码功能让压缩后的代码变得可读。同时熟练使用断点Breakpoint、监视器Watch和调用堆栈Call Stack是动态调试的基础。抓包工具Charles/Fiddler/浏览器Network面板用于捕获和观察所有网络请求。关键是要能清晰地看到请求头Request Headers和请求体RequestBody特别是那些自定义的x-头。我会过滤出目标域名如shopee.co.id的请求方便观察。Node.js 环境这是最终复现加密算法的环境。但在此之前它还有一个重要作用本地执行可疑的JavaScript代码片段。当你在浏览器中定位到一段可能是加密函数的代码时可以尝试将其提取出来在Node.js环境中运行和调试这比在浏览器中反复刷新页面调试要高效得多。代码编辑与搜索工具VS Code用于管理和搜索从网站上下载下来的大型JavaScript文件。当面对一个数兆字节的main.js或vendor.js时全局搜索关键词如x-sap-sec、encrypt、sign是快速定位的必备技能。注意有些网站会检测开发者工具是否打开或者检测常见的调试行为如debugger语句。如果遇到页面无限debugger或代码无法正常执行的情况可能需要使用一些反反调试技巧例如在开发者工具设置中禁用“停用断点Deactivate breakpoints”或者使用override功能替换掉包含debugger的脚本文件。2.2 逆向分析的核心方法论面对一个黑盒般的加密参数我的分析通常遵循以下步骤形成一个闭环观察与假设首先通过抓包工具观察目标参数x-sap-sec出现在哪些请求中它的值有什么特征长度、字符集、是否随时间变化。同时对比多次请求观察哪些请求参数或头部是固定的哪些是变化的初步假设x-sap-sec可能与哪些变量有关如URL、请求体、时间戳、某个固定Token。定位入口点这是最关键也是最难的一步。目标是找到在浏览器中生成和设置x-sap-sec头部的那段JavaScript代码。有两种主要策略搜索法在开发者工具的Sources面板中对所有加载的JS文件进行全局搜索关键词可以是x-sap-sec、sap-sec甚至是setRequestHeader。如果代码混淆程度不高可能会直接找到设置该头部的代码行。堆栈追踪法如果搜索无果很可能因为字符串被编码或拆分就需要更高级的方法。在Network面板中找到携带x-sap-sec的请求右键选择Copy - Copy as cURL或Copy - Copy as fetch然后将其粘贴到可以发起请求的地方如浏览器控制台但在此之前在Sources面板中为XMLHttpRequest.prototype.send或fetch函数设置一个断点。当请求发起时代码会在这个断点处暂停。此时查看调用堆栈Call Stack一步步向上回溯就能找到最终设置请求头的代码位置。逻辑分析与还原找到入口函数后开始分析其内部的逻辑。这里可能会涉及复杂的混淆比如变量名被替换成a、b、c逻辑被分割成多个小函数然后互相调用。需要耐心地梳理执行流程关注核心的加密或哈希函数如CryptoJS、Web API的SubtleCrypto或自定义的算法。使用控制台的console.log或debugger语句输出中间变量的值验证你的理解是否正确。本地复现与验证将分析清楚的加密逻辑用Node.js或Python重写。用相同的输入如URL、请求体、时间戳运行你的代码将输出的x-sap-sec值与浏览器捕获的真实值进行比对。如果一致恭喜你逆向成功。如果不一致则需要返回第三步检查是否有遗漏的细节比如某个全局变量、环境变量如浏览器指纹也被作为加密因子参与了计算。3. 针对Shopee的x-sap-sec参数逆向实战下面我就以Shopee的x-sap-sec为例详细走一遍这个流程。请注意不同平台、不同时间的反爬策略可能升级这里的步骤更多是提供一种方法论具体代码位置和函数名可能会变化。3.1 初步观察与特征分析首先我打开Shopee网站以印尼站为例打开开发者工具的Network面板并勾选Preserve log。随意浏览几个商品页面或搜索观察发出的XHR或Fetch请求。很快就能发现大部分向shopee.co.id后端API发起的请求其请求头中都包含以下几个字段x-sap-sec: 一串很长的、看似随机的Base64字符串或类似编码 x-sap-ri: 通常是一个较短的数字或字符串 af-ac-enc-dat: 另一串较长的加密字符串 User-Agent: ...x-sap-sec的值每次请求都不同即使两次完全相同的请求如刷新页面其值也会变化。这说明它很可能包含了时间戳timestamp或随机数nonce来保证唯一性。x-sap-ri的值在一段时间内如一个会话中可能保持不变可能是一个会话ID或请求标识。af-ac-enc-dat则可能与具体的请求体加密有关。我们的首要目标是x-sap-sec。初步假设它可能是一个对“特定字符串”进行某种加密如HMAC-SHA256后再进行Base64编码的结果。这个“特定字符串”很可能由“固定部分”“变动部分”组成。3.2 定位加密代码入口点直接搜索x-sap-sec在庞大的JS文件中可能如大海捞针。我采用更有效的堆栈追踪法。设置全局请求断点在Sources面板找到Event Listener Breakpoints展开XHR勾选send。这样任何XMLHttpRequest.send()调用都会触发断点。对于使用fetch的请求可以尝试在控制台执行debugger; fetch new Proxy(fetch, {apply: function(target, thisArg, argumentsList) { debugger; return target.apply(thisArg, argumentsList); }});来拦截注意这可能会影响页面正常功能需谨慎。触发请求并暂停回到网页进行一个会触发API请求的操作如点击搜索。此时代码会在send方法处暂停。分析调用堆栈查看Call Stack面板。这里会显示从send开始一层层向上是哪个函数调用了send。我们需要忽略浏览器内置和库文件如jquery.min.js寻找属于Shopee业务逻辑的、文件名可读的栈帧。回溯查找设置头部的代码在Call Stack中点击上层的栈帧查看其对应的源代码。在源代码区域查找setRequestHeader方法的调用。你可能会看到类似这样的代码t.setRequestHeader(x-sap-sec, o) t.setRequestHeader(x-sap-ri, i)这里的t通常是XMLHttpRequest对象o和i就是对应的值。找到这行代码就找到了入口实操心得在实际操作中代码通常是混淆过的。t、o、i可能是经过多次赋值和计算得到的。关键是要找到生成o即x-sap-sec值的那行代码或函数调用。通常它就在setRequestHeader附近。你可以将鼠标悬停在变量o上或者在其上一行打上断点重新触发请求查看o的值是如何计算出来的。3.3 深入分析加密逻辑假设我们找到了类似var o c(e, r, n)的代码其中c就是生成x-sap-sec的函数e、r、n是传入的参数。接下来就需要深入分析函数c。跟进函数定义点击函数名c或者在其定义处打上断点跳转到该函数的实现。你可能会进入一个被混淆得面目全非的函数。函数体里可能充满了a b ^ c、d e 3这样的位操作以及大量无意义的变量名。简化与重命名这是最耗费心力的部分。你需要结合上下文猜测每个变量的作用。例如如果看到一个变量被初始化为Date.now()那它很可能就是时间戳。如果看到一个变量是window.location.pathname那它就是URL路径。在开发者工具中你可以右键点击变量选择Evaluate in console来实时查看它的值帮助理解。识别加密原语在混淆的代码中寻找熟悉的模式。常见的加密或哈希函数调用即使被混淆其结构也有迹可循。例如CryptoJS.HmacSHA256(message, key).toString()是常见的HMAC-SHA256。new Uint8Array、window.crypto.subtle.digest(SHA-256, ...)是使用Web Crypto API。看到0xdeadbeef、0x9e3779b9这类魔数可能是在进行TEA、XXTEA等分组加密。大量的查表操作S-Box和循环移位可能是AES或DES。动态调试记录输入输出在疑似核心加密函数的前后设置断点并利用console.log记录所有输入参数和最终返回值。对比多次请求观察输入的变化如何影响输出。例如// 在函数c内部开始处添加 console.log(【x-sap-sec输入】, 参数1:, e, 参数2:, r, 参数3:, n); // 在函数c返回前添加 console.log(【x-sap-sec输出】, o);通过多次请求的日志你可以归纳出规律x-sap-secEncrypt_Function( 时间戳 某种ID 请求路径 ... )。在我的分析中我发现Shopee的x-sap-sec生成逻辑大致如下具体细节已做模糊化处理但流程真实获取当前时间戳毫秒级和一个固定的salt可能来自页面初次加载时服务器下发的某个变量。将时间戳、salt、以及x-sap-ri的值可能还有用户ID的哈希值按特定顺序拼接成一个字符串。对这个拼接后的字符串使用一个自定义的编码/哈希算法进行计算。这个算法不是标准的SHA或MD5而是一系列位运算和算术运算的组合是平台自定义的混淆算法。将上一步的结果再进行一次Base64编码或类似编码最终得到x-sap-sec的值。关键点很多现代平台的签名算法并非直接使用标准加密库而是自己实现一套轻量级的、但足以对抗通用爬虫的混淆算法。逆向的重点就在于还原这套自定义算法。3.4 算法还原与Node.js复现分析清楚逻辑后就需要用代码复现。由于算法是自定义的我们需要将混淆的JS代码“翻译”成清晰可读的代码。提取核心函数将浏览器中分析得到的关键函数c及其所有依赖函数即c内部调用的其他函数的代码完整地复制出来。注意要复制函数定义而不是压缩后的单行代码。创建本地测试环境在Node.js项目中创建一个新的JS文件将复制出来的函数粘贴进去。由于这些函数可能依赖浏览器环境下的某些全局变量如window、document你需要模拟这些环境或者找到这些变量的实际值并用常量替换。例如如果代码中有window.performance.now()在Node.js中可以用Date.now()或process.hrtime()模拟。如果代码使用了一个全局的saltKey你需要从网页的源代码或网络请求中找到这个saltKey是如何被初始化的并将其硬编码到你的Node.js代码中。补全缺失逻辑混淆代码可能会将一些常量字符串拆分成数组再拼接或者使用String.fromCharCode来构造字符串。你需要耐心地执行这些逻辑得到最终的常量值。验证与调试编写测试用例。从浏览器中捕获2-3组真实的请求数据包括时间戳可近似用请求发起时间、x-sap-ri、请求URL等。将这些作为输入运行你的Node.js复现函数将输出结果与真实的x-sap-sec进行比对。如果一致恭喜逆向成功。你可以尝试用不同的输入进行更多测试确保算法在各种边界情况下都正确。如果不一致这是常态。需要回头检查是否遗漏了某个加密因子如浏览器指纹navigator.userAgent的某个哈希值时间戳的精度是否正确是秒还是毫秒是否经过了取整字符串拼接的顺序或分隔符是否正确自定义算法中的某些常数是否看错了例如0x5A827999错看成0x5A82799以下是一个极度简化的伪代码示例展示了复现代码可能的结构// 从网页源码或初始化请求中提取的固定值 const STATIC_SALT 提取出的固定盐值; const APP_VERSION_HASH 某个应用版本哈希值; // 还原出的自定义哈希函数 function customHash(inputStr) { let hash 0x12345678; // 初始魔数 for (let i 0; i inputStr.length; i) { hash ((hash 5) - hash) inputStr.charCodeAt(i); hash hash hash; // 模拟32位整数溢出 } // ... 可能还有其他复杂的位运算轮次 return hash; } // 生成 x-sap-sec 的主函数 function generateXSapSec(timestamp, sapRi, requestPath) { // 1. 拼接关键信息 const rawString ${timestamp}:${STATIC_SALT}:${sapRi}:${requestPath}:${APP_VERSION_HASH}; // 2. 使用自定义算法计算哈希 const hashResult customHash(rawString); // 3. 将整数哈希值转换为十六进制字符串并可能进行填充 let hexHash (hashResult 0).toString(16).padStart(8, 0); // 确保8位十六进制 // 4. 进行Base64编码注意可能是URL安全的Base64 const base64Encoded Buffer.from(hexHash, hex).toString(base64); // 可能需要替换掉 和 /并去掉末尾的 const finalSec base64Encoded.replace(/\/g, -).replace(/\//g, _).replace(/$/, ); return finalSec; } // 测试 const testTimestamp 1712345678901; const testSapRi 123456; const testPath /api/v2/item/get; const mySec generateXSapSec(testTimestamp, testSapRi, testPath); console.log(生成的 x-sap-sec:, mySec); // 与抓包得到的真实值进行比对4. 逆向过程中的常见问题与排查技巧即使思路清晰逆向过程中也总会遇到各种意想不到的问题。下面记录一些典型场景和我的应对策略。4.1 代码混淆严重无法阅读这是最常遇到的情况。混淆工具会将变量名替换为短字符拆分字符串插入无用代码甚至使用JavaScript Obfuscator等工具进行控制流扁平化。策略使用反混淆工具可以尝试一些在线的或本地的JS反混淆工具如de4js、jsnice。它们有时能还原出部分可读的变量名和结构但对付高级混淆效果有限。动态调试关注数据流不要试图理解每一行代码。在关键函数入口设断点然后重点关注数据的流向。在Watch面板添加你需要监视的变量如最终生成签名的那个变量然后一步步执行F10看这个变量的值是如何一步步被计算出来的。你的目标是找到原始输入和最终输出之间的转换路径而不是理解路径上每一块石头的形状。Hook关键函数如果怀疑使用了CryptoJS或Web Crypto API可以在控制台提前注入代码钩住Hook这些标准函数。例如var _originalDigest window.crypto.subtle.digest; window.crypto.subtle.digest function(algorithm, data) { console.log(【Crypto Digest调用】, algorithm, data); return _originalDigest.apply(this, arguments).then(result { console.log(【Crypto Digest结果】, new Uint8Array(result)); return result; }); };这样当页面代码调用加密函数时输入和输出就会被打印出来。4.2 加密逻辑依赖浏览器环境或全局变量算法中可能使用了window.navigator.userAgent、window.screen.width、document.cookie中的某个值或者是一个在页面加载时由服务器注入的全局变量如window.__DATA__或window.__NUXT__。策略在断点处查看上下文当代码在断点处暂停时在Console中直接输入window.然后按Tab键补全查看有哪些全局对象和属性。或者输入你怀疑的变量名查看其当前值。搜索变量定义在Sources面板全局搜索这个变量名如__DATA__找到它被赋值的地方通常是在一个script标签内由服务器渲染时直接写入。将这个值记录下来用于本地复现。完整模拟环境在Node.js复现时可能需要创建一个模拟的window或navigator对象并设置相应的属性值。确保这些值与你在真实浏览器环境中捕获到的完全一致。4.3 算法中有随机数或动态因子导致每次结果不同如果发现即使所有输入都一样两次生成的签名也不同那很可能算法中引入了随机数。策略查找随机数生成器在加密函数附近搜索Math.random()、Date.now()、performance.now()、crypto.getRandomValues()的调用。分析这个随机值是否被用于计算以及它是否会被发送到服务端可能在另一个字段里。有时随机数只是盐salt的一部分而盐值可能会通过x-sap-ri或其他头部传回用于服务端验证。对比多次请求捕获连续多次请求的所有相关头部和参数。仔细对比看除了时间戳和疑似签名的字段外是否还有一个字段每次都在变且看起来像随机数。这个字段很可能就是动态因子。4.4 本地复现结果与浏览器不一致这是调试的常态。需要系统性地进行差分排查。排查清单输入一致性检查确保你本地代码的输入时间戳、URL、请求体字符串、各种ID与浏览器中断点捕获到的完全一致。特别注意字符串的编码UTF-8、空格、大小写。时间戳精度和格式浏览器用的是毫秒时间戳还是秒时间戳是否经过了取整如Math.floor(Date.now() / 1000)在断点处打印出时间戳的值与你本地生成的值进行比对。算法步骤还原一步步对比。在浏览器中在算法关键步骤后打印中间变量console.log。在你的本地代码中在相同步骤后也打印中间变量。从第一步开始比对找到第一个出现差异的地方那里就是问题所在。依赖函数行为确保你从混淆代码中提取并“翻译”的每一个辅助函数其行为都与原函数完全一致。特别是涉及位运算,,,|,^和整数溢出处理的地方JavaScript的位数处理需要格外小心。编码与解码最终输出前是否进行了Base64、Hex或其他编码编码方式是否正确标准Base64 vs URL安全的Base64。在浏览器中在编码前打印出待编码的原始字节或字符串与你本地编码前的数据进行比对。逆向分析x-sap-sec这类参数是一个需要耐心、细心和逻辑推理能力的过程。它没有一成不变的公式每一个网站都可能是一道新的谜题。但万变不离其宗掌握“观察-定位-分析-复现-验证”这套基本方法论并熟练运用开发者工具进行动态调试就能应对大多数挑战。最重要的是在这个过程中培养出的对前端安全机制和JavaScript运行时的深刻理解其价值远超过破解某一个具体的参数。