1. 项目概述一次典型的Web应用加密参数逆向之旅最近在复盘一个SRC安全应急响应中心的实战案例感触颇深。这个案例的入口点非常经典一个看似坚不可摧的付费资源下载接口其核心防护机制就是一个名为sign的加密参数。相信很多刚入门漏洞挖掘的朋友看到这种带签名校验的接口第一反应可能就是“这玩意儿有加密应该没戏”然后就直接跳过了。但恰恰是这种“看起来安全”的地方往往藏着逻辑漏洞的富矿。这次实战我们就从一个付费视频资源的sign参数绕过入手完整地走一遍前端JavaScript加密参数的逆向分析流程。整个过程不涉及任何高深的二进制逆向纯粹是前端代码的逻辑梳理与调试非常适合想从Web基础转向JS逆向和漏洞挖掘的朋友参考。你会发现很多看似复杂的加密其核心逻辑就明明白白地躺在浏览器的开发者工具里等着你去发现。2. 核心思路与逆向目标拆解2.1 目标接口与漏洞场景还原我们遇到的目标是一个在线教育平台。用户购买课程后可以获得一系列视频资源的播放/下载权限。平台为了防止资源被未授权爬取或盗链对视频流媒体的请求接口做了加固。具体来说当你点击播放一个已购课程的视频时前端会向一个形如/api/v1/video/stream?video_id12345t1678886400signabcdefg123456789的接口发起请求。其中video_id 视频的唯一标识明文传输。t 当前时间戳用于防止重放攻击。sign 签名参数由video_id、t以及一个未知的密钥secret通过某种算法生成。服务器的逻辑是收到请求后用同样的密钥和算法根据收到的video_id和t重新计算一个签名然后与请求中的sign值进行比对。如果一致则认为请求合法返回视频流否则返回403错误。我们的漏洞假设这个签名验证逻辑可能存在缺陷。比如服务器在验证时是否严格校验了t参数的有效期时间戳是否过期签名算法本身是否有逻辑漏洞导致我们可以构造出合法的sign而无需知道密钥或者前端生成sign的JavaScript代码是否存在泄露密钥或算法可预测的风险这就是我们本次逆向分析要回答的核心问题。2.2 逆向分析的核心路径规划面对一个带sign的接口标准的逆向分析路径通常如下定位加密函数 找到负责生成sign参数的那段JavaScript代码。理解算法逻辑 静态分析结合动态调试弄清楚sign是如何由video_id、t等输入计算出来的。算法是简单的MD5还是HMAC-SHA256或者是自定义的拼接哈希提取关键要素 算法中是否使用了硬编码的密钥Secret Key是否有其他可变或可预测的因子如固定的盐值、从其他接口获取的临时Token等验证与复现 在本地或控制台尝试用分析出的算法和要素复现出与前端一致的sign值。寻找逻辑漏洞 在完全理解算法后审视整个校验流程寻找可能的绕过点。例如时间戳t校验不严、签名算法存在长度扩展攻击风险、密钥因前端混淆不彻底而泄露等。本次实战将严格遵循这个路径。我们将使用最常用的浏览器开发者工具Chrome DevTools作为主要武器。3. 前端加密代码定位与静态分析3.1 网络请求追踪与入口点查找首先在目标网站登录并进入已购课程的视频播放页面。打开Chrome开发者工具F12切换到Network网络面板。在开始播放视频前先清空已有的请求记录然后点击播放按钮。很快我们在网络请求列表中找到了目标请求GET /api/v1/video/stream?...。点击这个请求查看其Headers标头。在Query String Parameters查询字符串参数或直接看URL里确认了video_id、t、sign三个参数的存在。关键技巧 在sign参数上右键选择“Copy value”保存下来以备后续验证。同时记录下当前的video_id和t的值。接下来是最关键的一步找到生成这个sign的JavaScript代码。有几种常见方法搜索关键词 在开发者工具的Sources源代码面板或Search搜索标签页中全局搜索sign、encrypt、hmac、MD5、SHA等关键词。但现代Web应用通常代码被压缩和混淆直接搜索字符串可能无效。XHR/ Fetch断点 在Sources面板找到右侧的XHR/ Fetch Breakpoints点击“”号添加一个包含部分URL的断点例如*stream*。然后重新触发请求如刷新页面或再次点击播放。当请求发起时代码执行会暂停此时调用栈Call Stack会显示出发起这个网络请求前执行的所有函数我们可以一步步回溯找到生成参数的代码。事件监听器断点 如果请求是由按钮点击触发的可以在Elements元素面板找到那个按钮右键选择“Break on” - “subtree modifications” 或 “attribute modifications”但这种方法不够精准。在本案例中我们使用XHR断点成功捕获了请求。代码暂停后调用栈显示了一个名为requestVideoStream的函数。点击进入这个函数我们看到了类似下面的代码已做反混淆和简化function requestVideoStream(videoId) { const timestamp Math.floor(Date.now() / 1000); const sign generateVideoSign(videoId, timestamp); const url /api/v1/video/stream?video_id${videoId}t${timestamp}sign${sign}; return fetch(url).then(response response.json()); }太好了我们找到了入口generateVideoSign函数。这就是我们要重点分析的目标。3.2 加密函数分析与算法还原点击进入generateVideoSign函数我们看到了实际的签名生成逻辑function generateVideoSign(videoId, timestamp) { const secretKey xm9!pL2qS; // 注意这里疑似硬编码密钥 const dataString video${videoId}t${timestamp}key${secretKey}; return md5(dataString).toLowerCase(); }初步分析结果令人惊讶又兴奋算法非常简单 仅仅是video_id、t和一个硬编码的secretKey字符串按固定格式拼接后进行MD5哈希再转为小写。密钥疑似硬编码secretKey的值直接以明文形式出现在前端JS代码中 (xm9!pL2qS)。这是一个非常严重的风险点但我们需要验证它是否就是服务器端使用的真实密钥。静态分析注意事项不要轻信一眼看到的逻辑 现在的代码是经过我们初步解读的。原始代码很可能被混淆变量名可能是a、b、c函数名可能是_0x12ab。我们需要耐心地跟踪变量传递使用开发者工具的“Pretty-print”美化代码功能{}按钮来让代码更可读。关注核心加密函数 在这个案例中md5()函数可能是引用的某个库如CryptoJS或自己实现的。我们需要确认这个md5函数是标准的、未被魔改的。可以搜索md5函数的定义或者直接在控制台测试md5(test)的输出是否与标准MD5(098f6bcd4621d373cade4e832627b4f6)一致。留意环境变量 有些应用会将密钥藏在更深处比如从另一个接口动态获取或者由服务器渲染时注入到全局变量window.__CONFIG__中。我们需要检查generateVideoSign函数外部secretKey是否有被赋值或修改的可能。4. 动态调试验证与算法复现静态分析给了我们一个清晰的假设。现在需要通过动态调试来验证这个假设并确保我们完全理解了这个过程。4.1 控制台验证与密钥确认我们在开发者工具的Console控制台标签页中进行验证验证MD5函数// 测试内置或全局的md5函数是否标准 console.log(md5(hello)); // 应该输出 5d41402abc4b2a76b9719d911017c592如果输出符合标准MD5说明算法基础是可靠的。复现签名 将之前记录的实际请求参数代入我们分析出的算法。const videoId 12345; // 替换为实际值 const timestamp 1678886400; // 替换为实际值 const secretKey xm9!pL2qS; // 从代码中提取的 const dataString video${videoId}t${timestamp}key${secretKey}; const calculatedSign md5(dataString).toLowerCase(); console.log(计算出的sign:, calculatedSign); console.log(请求中的sign:, abcdefg123456789); // 替换为实际复制的值如果calculatedSign与从网络请求中复制的sign完全一致那么恭喜你已经完全掌握了该接口的签名算法并且确认了前端硬编码的密钥就是真实密钥。4.2 调试技巧与可能遇到的障碍在实际操作中不会总是一帆风顺。以下是几个常见障碍及应对策略代码被严重混淆 变量和函数名毫无意义。此时不要试图去理解每一行。我们的目标是找到输入videoId,timestamp如何变成输出sign的路径。可以在这两个变量被使用或传递的地方打上日志点Logpoint或条件断点观察其值的变化。重点关注字符串拼接、调用类似CryptoJS.MD5(...).toString()或btoa/atob的地方。签名函数被多次封装generateVideoSign内部可能又调用了utils.encrypt再调用lib.hash。耐心地跟随调用栈使用Step into (F11)逐层深入直到看到最核心的加密操作如MD5、SHA256的调用。密钥是动态获取的 如果secretKey不是硬编码而是通过window._config.secret或一个异步请求获取的。我们需要找到这个配置被初始化的地方或者拦截那个异步请求。可以在Sources面板搜索secret、key或在Network面板查看页面加载初期是否有返回配置的请求。存在额外的签名因子 除了我们看到的参数算法可能还拼接了用户ID、会话Cookie中的某个字段或者一个固定的“盐值”salt。这就需要我们对比多个不同上下文不同用户、不同视频、不同时间的请求分析sign的变化规律并通过调试找出所有参与计算的变量。实操心得 动态调试时善用Watch监视面板。将你怀疑的变量如videoId,timestamp,secretKey, 以及拼接过程中的中间字符串添加到监视列表可以非常直观地看到它们在代码执行过程中的值变化极大提升分析效率。5. 漏洞挖掘从算法还原到实际绕过成功复现签名算法只是第一步。真正的漏洞挖掘是分析这套机制哪里可能出问题。针对这个案例我们至少可以发现三个层面的安全问题5.1 漏洞点一密钥硬编码于前端这是最直接、最严重的漏洞。一旦攻击者通过我们上面的逆向分析手段获取了secretKey xm9!pL2qS他就拥有了为任意video_id和t生成合法sign的能力。攻击利用 攻击者可以编写一个简单的Python脚本伪造任意付费视频的请求import hashlib import time import requests SECRET_KEY xm9!pL2qS TARGET_VIDEO_ID 67890 # 一个未购买的付费视频ID def generate_sign(vid, t): data fvideo{vid}t{t}key{SECRET_KEY}.encode(utf-8) return hashlib.md5(data).hexdigest() current_timestamp int(time.time()) signature generate_sign(TARGET_VIDEO_ID, current_timestamp) url fhttps://target.com/api/v1/video/stream?video_id{TARGET_VIDEO_ID}t{current_timestamp}sign{signature} response requests.get(url) # 如果成功即可下载或播放未授权的视频资源这直接导致了付费资源的未授权访问属于高危漏洞。5.2 漏洞点二时间戳校验缺陷即使密钥没有硬编码时间戳t的校验也可能存在问题。我们分析服务器端可能的校验逻辑接收请求提取t。计算当前服务器时间server_time。检查abs(server_time - t) expiry_window例如时间差是否在300秒内。潜在的绕过方式服务器时间未同步 如果服务器集群间时间不同步攻击者从一个时间偏差较大的节点获取的t可能在另一个校验节点上通过。** expiry_window 设置过大** 如果窗口期设置成1小时甚至更长攻击者截获一个合法请求后在窗口期内可以无限次重放实现盗链。服务器未校验t 这是最坏的情况。服务器只验证sign是否正确而忽略t值。那么攻击者只要获取一个合法的sign例如通过购买一次课程就可以永久使用这个sign和对应的video_idt可以随意填写或沿用旧值。我们可以通过修改请求中的t值为一个很旧或未来的时间戳但保持sign不变因为sign是根据原始t计算的观察服务器是否拒绝来验证这一点。5.3 漏洞点三签名算法本身的问题MD5算法本身已经不再安全但这里的问题更多在于使用模式。拼接方式可预测video${videoId}t${timestamp}key${secretKey}这种固定格式的拼接如果密钥长度已知在某些极端情况下可能面临长度扩展攻击虽然对MD5这种简单拼接的威胁模型需要具体分析。缺乏随机数 签名算法中没有任何随机因子nonce导致同一对(video_id, t)永远生成相同的sign。这使得请求可以被完美重放。6. 漏洞报告与修复建议基于以上分析我们可以撰写一份高质量的安全漏洞报告。报告核心要素漏洞标题 [目标系统] 付费视频流接口签名校验绕过导致未授权访问风险等级 高危漏洞详情受影响端点GET /api/v1/video/stream漏洞描述 该接口用于校验用户权限并返回视频流其签名(sign)参数由前端JavaScript生成。经逆向分析发现签名密钥(secretKey)以明文形式硬编码在前端代码中。攻击者可通过浏览器开发者工具轻易提取该密钥并据此为任意video_id伪造合法签名从而无需购买即可访问所有付费视频资源。技术分析 详细描述逆向过程包括定位到的关键函数generateVideoSign展示其硬编码的密钥xm9!pL2qS和简单的MD5拼接算法。提供复现步骤和PoC概念验证代码。潜在影响 所有付费课程内容可被未授权下载/播放造成直接经济损失和版权侵害。修复建议立即措施 后端临时增加更严格的校验如绑定用户会话ID与请求或启用短期有效的访问Token。根本解决方案密钥前端硬编码 绝对禁止将签名密钥放在前端。所有涉及权限校验的签名应在后端生成。前端需要签名时应向后端发起一个安全如携带用户登录态的请求由后端计算后返回。升级签名算法 改用更安全的HMAC-SHA256等算法。增加随机因子 在签名中引入服务器下发的随机数nonce确保每次请求的签名唯一防止重放。强化参数校验 严格校验时间戳t的有效期建议在±30秒内并确保校验逻辑在所有服务器节点一致。进行代码混淆与加固 虽然不能从根本上解决密钥泄露问题但可以增加逆向分析的难度作为纵深防御的一环。7. 拓展思考与防御方视角通过这个案例我们不仅作为攻击方学会了如何挖掘漏洞更应该从防御方角度思考如何构建更安全的系统。对于前端加密/签名的正确认识 前端加密包括JS加密参数的主要目的通常不是为了防止恶意用户而是增加自动化攻击的成本 防止简单的爬虫和脚本直接调用接口。保护数据传输过程中的一些敏感参数但密钥在前端时无效。满足合规性或业务逻辑需求比如对数据进行格式化处理。真正的安全防线应该在后端永不信任前端 所有来自前端的参数包括sign都必须经过后端严格的、无状态的验证。密钥安全 签名密钥必须存储在安全的服务器环境如环境变量、密钥管理服务中绝对不出现在客户端代码、配置文件和版本控制历史里。完善的校验逻辑 签名验证、权限判断、业务逻辑校验必须形成一个完整的链条任何一环缺失都可能导致漏洞。作为漏洞挖掘者我们的价值在于帮助厂商发现这些“前端看似安全后端实则脆弱”的认知盲区。这种从加密参数逆向入手直指核心业务逻辑漏洞的挖掘方式在SRC实战中非常高效尤其适合那些前端交互复杂、对安全性过度自信的现代Web应用。每一次成功的逆向都是对系统安全假设的一次有力挑战。