得物H5接口sign签名逆向分析:从抓包到Python复现全流程

发布时间:2026/7/22 1:24:02
得物H5接口sign签名逆向分析:从抓包到Python复现全流程 1. 项目概述与核心价值最近在分析一些主流电商平台的H5端数据交互逻辑得物Dewu的接口签名机制引起了我的兴趣。对于从事数据采集、风控策略研究或者单纯想了解现代Web应用安全机制的朋友来说逆向分析一个健壮的sign签名参数生成流程是一次绝佳的实战演练。这不仅仅是“破解”一个参数那么简单它涉及到对前端JavaScript混淆、加密算法识别、关键逻辑定位以及完整调用链还原的综合性考验。通过这个过程我们能深刻理解客户端如何与服务器进行“对暗号”以及服务端如何验证请求的合法性与时效性这对于构建或加固我们自己的API安全体系有着直接的参考价值。简单来说这个项目就是搞清楚当你在得物H5页面比如浏览商品、查看订单进行操作时浏览器发出的每一个网络请求中那个看似随机的sign参数到底是怎么算出来的。我们将从最基础的抓包开始一步步拆解JavaScript代码定位核心加密函数最终用Python等语言复现整个签名算法。无论你是前端开发者想深入理解安全实践还是对爬虫逆向感兴趣这篇文章都将提供一份详尽的“地图”。2. 前期环境准备与抓包分析工欲善其事必先利其器。逆向分析的第一步永远是观察。我们需要一套能清晰捕获和调试H5网络请求的环境。2.1 工具链搭建对于H5页面的分析桌面浏览器自带的开发者工具是最直接高效的。我首选Chrome或Edge基于Chromium内核因为它们对JavaScript的调试支持非常完善。浏览器确保使用最新版本的Chrome/Edge。开发者工具快捷键F12或CtrlShiftI打开。重点关注两个面板Network网络用于捕获所有HTTP/HTTPS请求。Sources源代码或Debugger调试器用于查看、搜索和调试JavaScript文件。抓包辅助工具虽然浏览器自带的工具足够强大但像Charles或Fiddler这类代理工具在需要观察手机端H5流量、或者进行请求重发测试时非常有用。你可以将手机代理到电脑捕获其访问得物H5时的请求。不过对于初期的静态分析纯浏览器环境即可。2.2 关键请求捕获与特征观察打开得物H5页面例如其官网的移动端进行任意能触发API请求的操作比如搜索商品、查看商品详情、刷新个人主页等。打开Network面板确保勾选了“Preserve log”保留日志并清空现有记录。触发请求在页面进行交互。很快你会看到一系列请求出现。我们需要从中筛选出携带sign参数的接口。通常这类接口的路径Path会包含api关键字且请求方法为POST或GET。定位目标请求在请求列表中仔细查看每个请求的Headers请求头和Payload请求负载对于POST请求。我们的目标是找到请求URL或请求体body中包含一个名为sign的参数的请求。这个sign值通常是一长串看似随机的字母数字组合可能是Hex字符串或Base64编码。记录关键信息找到目标请求后需要完整记录以下信息这些是后续逆向的“输入”请求URL完整的接口地址。请求方法GET或POST。请求参数包括URL查询字符串Query String和请求体Form Data或JSON。特别注意除了sign之外的其他参数如timestamp时间戳、nonce随机数、token用户令牌等。sign的生成极大概率与这些参数有关。请求头有些签名算法也会将特定的Header如User-Agent、自定义的App-Version等纳入计算。注意首次分析时建议对一个简单的、不依赖复杂登录态的接口如商品列表查询进行抓包。过于复杂的接口可能涉及多层令牌会增加初始逆向的难度。3. 核心逆向流程定位签名函数捕获到包含sign的请求后真正的挑战开始——在前端混淆压缩后的代码海洋里找到生成这个sign的那几行关键逻辑。3.1 搜索与断点设置策略现代Web应用通常会将JavaScript代码打包、压缩、混淆变量名可能变成a、b、c函数名也难以辨认。但我们有办法。全局搜索在开发者工具的Sources面板按CtrlShiftF进行全局文件搜索。尝试搜索以下关键词sign直接搜索参数名。encrypt、encode、signature搜索可能的功能函数名。MD5、SHA、HmacSHA、AES搜索常见的加密算法关键词。即使代码被混淆这些算法库的调用特征或常量字符串可能依然存在。你抓包到的sign值的一部分有时可以直接搜索签名结果本身如果它是硬编码在某个配置里虽然可能性小或者用于对比的代码中。XHR/Fetch断点这是一个非常高效的方法。在Sources面板找到“XHR/Fetch Breakpoints”区域点击“”号。由于我们不知道具体的请求URL全路径可以尝试添加包含部分接口路径关键字的断点例如*api*或*/v1/*。当浏览器发起匹配的请求时代码执行会自动暂停此时调用栈Call Stack会显示出发起这个网络请求的JavaScript代码位置。通过调用栈一步步向上回溯就很有可能找到组装参数和计算sign的函数。Hook关键函数在Console面板中可以重写Hook一些原生方法来拦截参数。例如在页面加载前注入以下代码(function() { var originalSend XMLHttpRequest.prototype.send; XMLHttpRequest.prototype.send function(body) { console.trace(XHR send called, body:, body); // 可以在这里检查body是否包含目标URL或sign return originalSend.apply(this, arguments); }; var originalFetch window.fetch; window.fetch function() { console.trace(Fetch called, args:, arguments); return originalFetch.apply(this, arguments); }; })();这能帮我们定位到发起请求的精确时刻和上下文。3.2 调用栈分析与逻辑追踪通过上述任何一种方法我们最终目的是让代码在计算sign附近暂停下来。理解调用栈当断点触发后右侧的Call Stack面板显示了从当前断点位置一路回溯到最初入口的函数调用链。通常最顶上是浏览器内置函数越往下越接近业务代码。逐层回溯从调用栈的底部通常是你的断点处开始逐个点击上层的函数。每点击一个代码面板就会跳转到对应的位置。你需要仔细阅读每一层函数的代码寻找参数被处理、尤其是被传递给某个加密函数的痕迹。寻找加密函数在回溯过程中关注以下几种模式的代码crypto.subtle.digest或CryptoJS.MD5(...)等明显的加密库调用。将多个参数拼接成一个字符串的操作例如var s a b c;。调用一个名字可疑的函数如function e(t) { ... return n; }而其返回值被赋值给了sign字段。一旦你疑似找到了计算sign的函数就在该函数入口处打上断点然后重新触发请求。观察函数的输入参数入参是什么它的输出结果是否就是网络请求中的sign值。通过多次触发不同参数的请求验证这个函数的输入输出关系是否稳定。4. 算法解析与参数构造逻辑假设我们已经成功定位到了核心的签名函数命名为generateSign(params)。接下来要像解谜一样弄清楚它内部的运作机制。4.1 参数排序与拼接规则绝大多数API签名都是为了验证请求的完整性和唯一性。一个常见的模式是筛选有效参数并非所有发送的参数都参与签名。通常排除sign本身有时也排除file等二进制字段。函数内部会有一个参数白名单或黑名单逻辑。参数排序为了确保服务端和客户端以同样的顺序计算需要对参与签名的参数名按**字典序ASCII码顺序**进行排序。这是非常关键且常见的一步。在JavaScript中通常使用Object.keys(params).sort()来实现。键值对拼接将排序后的参数按keyvalue的格式用特定的连接符如或|拼接成一个长字符串。例如a1b2c3。添加盐值Salt或密钥为了增加安全性拼接后的字符串前后可能会附加上一个只有客户端和服务端知道的固定字符串称为“盐值”或“密钥”。这可能是硬编码在JS里的也可能是从服务器动态获取的。拼接后可能形成secretKey queryString secretKey的形式。4.2 加密算法识别与还原拼接好的字符串需要经过加密或哈希运算才能生成最终的sign。识别算法MD5输出32位十六进制字符串。在代码中可能搜索到MD5、md5或0x67452301等魔数。SHA-1输出40位十六进制字符串。SHA-256输出64位十六进制字符串。目前更常见。HMAC基于密钥的哈希运算如HMAC-SHA256。代码中可能出现HmacSHA256的调用。AES对称加密输出通常是Base64编码的字符串。相对少见用于简单的参数签名更多用于加密请求体。在调试器中你可以观察核心函数调用的返回值类型和长度或者搜索相关算法库如CryptoJS的典型初始化代码来辅助判断。还原计算过程在调试模式下你可以把参与拼接的最终字符串复制出来然后在你自己的环境中比如Node.js或Python用猜测的算法和盐值尝试计算看结果是否与请求中的sign匹配。这是一个试错的过程但一旦匹配成功算法就确定了。4.3 时间戳与随机数的处理timestamp时间戳和nonce随机数是防重放攻击的常用手段它们几乎一定会参与签名计算。timestamp确保请求在有效时间窗口内如服务器时间前后5分钟。客户端通常使用Date.now()或Math.floor(new Date().getTime() / 1000)生成。nonce一个一次性随机字符串确保同一签名短时间内不能重复使用。可能由Math.random().toString(36).substr(2)或UUID生成。在逆向时要确认这两个参数是如何生成的以及它们是否被包含在待签名字符串中。有时服务端会拒绝timestamp偏差过大或nonce已使用过的请求。5. 使用Python复现签名算法当我们完全弄清楚了签名算法的每一步后就可以用Python或其他语言来复现它实现脱离浏览器环境的自动化请求。5.1 环境依赖安装首先确保你的Python环境安装了必要的加密库。最常用的是hashlib内置和hmac内置对于更复杂的可能需要pycryptodome。pip install pycryptodome # 如果需要AES等算法5.2 算法复现代码示例假设我们分析得出的得物H5某接口签名算法如下获取所有请求参数排除sign和file包括timestamp和nonce。将参数按键名ASCII码升序排序。用连接键值对形如key1value1key2value2。在字符串末尾拼接一个固定密钥secret_key dewusecret2023。对拼接后的字符串计算MD5哈希值并转为小写十六进制字符串。那么Python复现代码可能如下import hashlib import time import uuid def generate_dewu_sign(params, secret_keydewusecret2023): 生成得物H5接口签名 :param params: dict, 请求参数字典 :param secret_key: str, 密钥 :return: str, 签名sign # 1. 移除sign参数本身如果存在并过滤掉值为None或空字符串的参数根据实际情况调整 filtered_params {k: v for k, v in params.items() if v is not None and k ! sign} # 2. 按键名ASCII码升序排序 sorted_keys sorted(filtered_params.keys()) # 3. 拼接键值对 query_string_parts [] for key in sorted_keys: # 确保所有值为字符串列表或字典可能需要特殊处理如JSON序列化 value str(filtered_params[key]) query_string_parts.append(f{key}{value}) query_string .join(query_string_parts) # 4. 拼接密钥 string_to_sign query_string secret_key # 5. 计算MD5并返回小写hex m hashlib.md5() m.update(string_to_sign.encode(utf-8)) sign m.hexdigest().lower() return sign # 模拟请求参数 params { page: 1, size: 20, timestamp: str(int(time.time() * 1000)), # 毫秒时间戳 nonce: str(uuid.uuid4()).replace(-, ), keyword: 球鞋 } # 计算签名 signature generate_dewu_sign(params) params[sign] signature # 将签名加入请求参数 print(生成的签名:, signature) print(完整请求参数:, params)这段代码定义了一个通用的签名函数。你需要根据实际逆向结果调整其中的过滤规则、排序方式、连接符、密钥拼接位置和加密算法。5.3 验证与调试将你的Python代码计算出的sign与通过浏览器抓包获取的真实请求中的sign进行对比。确保参数完全一致包括所有键值对甚至空格和编码URL编码与否。有时前端会对值进行encodeURIComponent处理Python中需要使用urllib.parse.quote。注意数据类型数字1和字符串1经过str()后可能没区别但布尔值true在前端可能是字符串true而在Python中是True需要统一。时间戳精度确认前端用的是秒级还是毫秒级时间戳。密钥是否正确密钥可能是硬编码也可能是从其他接口动态获取。如果静态密钥不对需要检查是否有获取密钥的先行接口。实操心得在复现过程中最棘手的往往是参数的预处理。有些参数值可能是嵌套的JSON对象前端会将其JSON.stringify后再进行签名。这时你的Python代码也需要用json.dumps生成完全相同的字符串包括空格和键序。建议在JavaScript调试器中在签名函数入口处打印出即将被加密的原始字符串然后在你Python代码的对应步骤也打印出来进行逐字符比对这是最有效的调试方法。6. 动态密钥与代码混淆的应对策略简单的静态签名算法容易被逆向和固定因此稍复杂的应用会引入动态元素。6.1 动态密钥的获取与更新你可能发现硬编码的secret_key并不起作用。签名算法可能使用了动态密钥登录后下发用户登录成功后服务器可能在返回的数据中包含一个sessionKey或token此后的签名计算需要使用这个动态密钥而非固定值。定期刷新通过一个特定的“获取密钥”接口使用之前的密钥或独立令牌来获取一个新的临时密钥用于后续一段时间内的请求签名。应对方法你需要完整地走一遍应用的主要流程如登录并监控所有接口的响应寻找可能包含密钥的字段。然后修改你的签名函数使其能够接收并应用这个动态密钥。6.2 应对高级JavaScript混淆如果核心代码被Webpack等工具打包并经过obfuscator等工具深度混淆函数名和变量名会变得毫无意义逻辑也可能被控制流扁平化等技巧打乱。关注常量与字符串加密算法常量、API路径、固定的密钥字符串可能未被完全混淆。搜索这些字符串依然是突破口。调试执行流即使代码难读在调试器中一步步执行F10单步跳过F11单步进入仍然是可行的。观察变量的变化理解数据流向。Hook通用函数如果混淆严重可以尝试Hook更底层的函数如String.prototype.concat、Array.prototype.join或Object.keys因为签名拼接过程必然会调用它们。使用AST还原工具对于有经验的开发者可以尝试使用Babel等工具对混淆代码进行解析和简化但这需要较高的JavaScript和编译原理知识。注意事项逆向工程应仅用于学习、安全研究和授权测试。未经授权对他人服务进行大规模、恶意的数据抓取可能违反网站的服务条款甚至相关法律法规。务必遵守robots.txt协议控制请求频率避免对目标服务器造成负担。7. 完整请求构建与测试算法复现成功后最后一步是构建一个完整的、可成功获得数据的HTTP请求。7.1 组装请求参数除了业务参数和计算出的sign通常还需要添加一些必要的请求头以模拟真实的浏览器环境User-Agent: 模拟移动端浏览器例如Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Mobile/15E148 Safari/604.1Content-Type: 根据接口要求通常是application/x-www-form-urlencoded或application/json。Referer: 请求来源页有时会进行校验。Cookie: 如果需要维持登录态则需要携带有效的Cookie。这通常需要通过模拟登录流程先获取。7.2 使用Requests库发送请求以下是一个使用Pythonrequests库发送带签名请求的完整示例import requests import json import time import uuid from generate_dewu_sign import generate_dewu_sign # 导入我们之前写的签名函数 def make_dewu_api_request(api_url, business_params, secret_key, extra_headersNone): 构造并发送得物API请求 # 1. 准备基础参数 base_params { timestamp: str(int(time.time() * 1000)), nonce: str(uuid.uuid4()).replace(-, ), # 可能还有其他固定参数如appVersion, platform等需根据实际情况添加 appVersion: 6.0.0, platform: h5 } # 2. 合并业务参数 all_params {**base_params, **business_params} # 3. 生成签名 sign generate_dewu_sign(all_params, secret_key) all_params[sign] sign # 4. 准备请求头 headers { User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Mobile/15E148 Safari/604.1, Content-Type: application/x-www-form-urlencoded; charsetUTF-8, Referer: https://m.dewu.com/, # 如果有Cookie在此添加 # Cookie: your_cookie_here } if extra_headers: headers.update(extra_headers) # 5. 发送请求 (假设是POST表单请求) # 注意根据接口实际要求可能是GET参数在URL或POST JSON print(f请求URL: {api_url}) print(f请求参数: {all_params}) response requests.post(api_url, dataall_params, headersheaders, timeout10) # 6. 处理响应 print(f状态码: {response.status_code}) if response.status_code 200: try: result response.json() print(响应JSON:, json.dumps(result, indent2, ensure_asciiFalse)) return result except json.JSONDecodeError: print(响应文本:, response.text) return response.text else: print(f请求失败: {response.status_code}) print(response.text) return None # 使用示例 if __name__ __main__: # 替换为实际值 target_url https://app.dewu.com/api/v1/goods/list # 示例接口 my_secret_key your_actual_secret_key # 逆向得到的密钥 my_business_params { page: 1, size: 20, keyword: AJ1 } result make_dewu_api_request(target_url, my_business_params, my_secret_key)运行这个脚本如果一切正确你应该能收到和浏览器访问时间相同的JSON格式数据。如果失败请仔细检查控制台输出的请求参数和响应信息与浏览器抓包的数据进行一一比对。8. 常见问题排查与解决思路在实际操作中你几乎一定会遇到各种问题。这里记录一些典型的坑和解决方向。8.1 签名验证失败这是最普遍的问题表现为服务器返回“签名错误”、“非法请求”等。问题Python计算的sign与浏览器发出的sign不一致。排查字符串比对在JavaScript签名函数入口处用console.log打印出待加密的原始字符串。在Python的generate_dewu_sign函数中在计算MD5前也打印出string_to_sign。将两者进行逐字符比对包括不可见字符、空格、引号。这是最有效的方法。参数遗漏检查是否所有参与签名的参数都已包含timestamp、nonce的格式和值是否完全一致编码问题前端是否对参数值进行了URL编码Python中是否需要使用urllib.parse.quote注意有时键名也需要编码。排序规则确认排序规则是ASCII升序。对于包含数字、字母、下划线的键名Python的sorted默认排序与JavaScript的Array.sort()默认排序将元素转换为字符串比较Unicode码点在绝大多数情况下一致但最好验证。密钥错误密钥是否正确是静态的还是动态的是否需要先调用其他接口获取算法错误确认加密算法是MD5、SHA256还是HMAC输出是hex还是base64是否需要转换为大写或小写8.2 请求返回403/404或其他非业务错误问题请求根本不到达业务逻辑层。排查请求头缺失检查User-Agent、Referer、Origin、Cookie等头部是否被服务器校验。特别是User-Agent有些服务器会拦截非浏览器或非指定客户端的请求。IP限制或风控频繁请求或来自数据中心IP的请求可能被风控系统拦截。尝试降低请求频率或使用更接近真实用户环境的IP。接口路径或方法错误确认URL完全正确并且使用了正确的HTTP方法GET/POST/PUT等。HTTPS证书问题在极少数情况下自签证书或代理可能导致问题。requests库默认验证证书可通过verifyFalse参数关闭仅用于测试生产环境不安全。8.3 登录态维持问题问题需要登录才能访问的接口使用代码请求时返回“未登录”。排查Cookie失效通过模拟登录获取的Cookie可能有过期时间。需要实现Cookie的持久化存储和更新逻辑。Token机制现代应用更多使用Token如JWT。登录接口返回一个access_token需要在后续请求的Authorization头部或特定参数中携带。你需要解析登录响应提取并正确使用这个token。签名依赖登录信息签名算法本身可能依赖用户ID或token这意味着不同用户的签名密钥或参数可能不同。8.4 代码混淆导致逻辑难以追踪问题核心代码被严重混淆无法直接阅读。策略动态调试不要试图静态阅读所有代码。在疑似入口打上断点通过“单步执行”F11和“监视变量”Watch功能跟踪数据的流动。混淆不会改变程序的执行逻辑。Hook关键点如之前所述HookJSON.stringify、Object.keys、Array.prototype.join等原生函数可以快速定位参数被组装的位置。搜索特征值即使变量名混淆加密算法产生的常量如MD5的初始化向量或固定的盐值字符串可能依然以明文或简单变换的形式存在。搜索这些特征值。逆向分析是一个需要耐心和细致观察的过程。每一个成功的签名验证都是对前端安全机制和自身技术能力的一次深刻理解。保持好奇心多动手测试善用调试工具你就能解开大多数签名算法的奥秘。记住我们的目的是学习和研究其设计思路从而更好地设计或评估自身系统的安全性。