Web应用加密参数逆向分析:从JS签名绕过到漏洞挖掘实战
2026/6/23 9:30:49 网站建设 项目流程

1. 项目概述:一次典型的Web应用加密参数逆向之旅

最近在复盘一个SRC(安全应急响应中心)的实战案例,感触颇深。这个案例的入口点非常经典:一个看似坚不可摧的付费资源下载接口,其核心防护机制就是一个名为sign的加密参数。相信很多刚入门漏洞挖掘的朋友,看到这种带签名校验的接口,第一反应可能就是“这玩意儿有加密,应该没戏”,然后就直接跳过了。但恰恰是这种“看起来安全”的地方,往往藏着逻辑漏洞的富矿。这次实战,我们就从一个付费视频资源的sign参数绕过入手,完整地走一遍前端JavaScript加密参数的逆向分析流程。整个过程不涉及任何高深的二进制逆向,纯粹是前端代码的逻辑梳理与调试,非常适合想从Web基础转向JS逆向和漏洞挖掘的朋友参考。你会发现,很多看似复杂的加密,其核心逻辑就明明白白地躺在浏览器的开发者工具里,等着你去发现。

2. 核心思路与逆向目标拆解

2.1 目标接口与漏洞场景还原

我们遇到的目标是一个在线教育平台。用户购买课程后,可以获得一系列视频资源的播放/下载权限。平台为了防止资源被未授权爬取或盗链,对视频流媒体的请求接口做了加固。具体来说,当你点击播放一个已购课程的视频时,前端会向一个形如/api/v1/video/stream?video_id=12345&t=1678886400&sign=abcdefg123456789的接口发起请求。其中:

  • video_id: 视频的唯一标识,明文传输。
  • t: 当前时间戳,用于防止重放攻击。
  • sign: 签名参数,由video_idt以及一个未知的密钥(secret)通过某种算法生成。

服务器的逻辑是:收到请求后,用同样的密钥和算法,根据收到的video_idt重新计算一个签名,然后与请求中的sign值进行比对。如果一致,则认为请求合法,返回视频流;否则返回403错误。

我们的漏洞假设:这个签名验证逻辑可能存在缺陷。比如,服务器在验证时,是否严格校验了t参数的有效期(时间戳是否过期)?签名算法本身是否有逻辑漏洞,导致我们可以构造出合法的sign而无需知道密钥?或者,前端生成sign的JavaScript代码是否存在泄露密钥或算法可预测的风险?这就是我们本次逆向分析要回答的核心问题。

2.2 逆向分析的核心路径规划

面对一个带sign的接口,标准的逆向分析路径通常如下:

  1. 定位加密函数: 找到负责生成sign参数的那段JavaScript代码。
  2. 理解算法逻辑: 静态分析结合动态调试,弄清楚sign是如何由video_idt等输入计算出来的。算法是简单的MD5?还是HMAC-SHA256?或者是自定义的拼接+哈希?
  3. 提取关键要素: 算法中是否使用了硬编码的密钥(Secret Key)?是否有其他可变或可预测的因子(如固定的盐值、从其他接口获取的临时Token等)?
  4. 验证与复现: 在本地或控制台,尝试用分析出的算法和要素,复现出与前端一致的sign值。
  5. 寻找逻辑漏洞: 在完全理解算法后,审视整个校验流程,寻找可能的绕过点。例如:时间戳t校验不严、签名算法存在长度扩展攻击风险、密钥因前端混淆不彻底而泄露等。

本次实战将严格遵循这个路径。我们将使用最常用的浏览器开发者工具(Chrome DevTools)作为主要武器。

3. 前端加密代码定位与静态分析

3.1 网络请求追踪与入口点查找

首先,在目标网站登录并进入已购课程的视频播放页面。打开Chrome开发者工具(F12),切换到Network(网络)面板。在开始播放视频前,先清空已有的请求记录,然后点击播放按钮。

很快,我们在网络请求列表中找到了目标请求:GET /api/v1/video/stream?...。点击这个请求,查看其Headers(标头)。在Query String Parameters(查询字符串参数)或直接看URL里,确认了video_idtsign三个参数的存在。

关键技巧: 在sign参数上右键,选择“Copy value”,保存下来以备后续验证。同时记录下当前的video_idt的值。

接下来是最关键的一步:找到生成这个sign的JavaScript代码。有几种常见方法:

  • 搜索关键词: 在开发者工具的Sources(源代码)面板或Search(搜索)标签页中,全局搜索signencrypthmacMD5SHA等关键词。但现代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 = 'x@m9!pL2qS'; // 注意:这里疑似硬编码密钥! const dataString = `video=${videoId}&t=${timestamp}&key=${secretKey}`; return md5(dataString).toLowerCase(); }

初步分析结果令人惊讶又兴奋

  1. 算法非常简单: 仅仅是video_idt和一个硬编码的secretKey字符串,按固定格式拼接后,进行MD5哈希,再转为小写。
  2. 密钥疑似硬编码secretKey的值直接以明文形式出现在前端JS代码中 ('x@m9!pL2qS')。这是一个非常严重的风险点,但我们需要验证它是否就是服务器端使用的真实密钥。

静态分析注意事项

  • 不要轻信一眼看到的逻辑: 现在的代码是经过我们初步解读的。原始代码很可能被混淆,变量名可能是abc,函数名可能是_0x12ab。我们需要耐心地跟踪变量传递,使用开发者工具的“Pretty-print”(美化代码)功能({}按钮)来让代码更可读。
  • 关注核心加密函数: 在这个案例中,md5()函数可能是引用的某个库(如CryptoJS)或自己实现的。我们需要确认这个md5函数是标准的、未被魔改的。可以搜索md5函数的定义,或者直接在控制台测试md5('test')的输出是否与标准MD5(098f6bcd4621d373cade4e832627b4f6)一致。
  • 留意环境变量: 有些应用会将密钥藏在更深处,比如从另一个接口动态获取,或者由服务器渲染时注入到全局变量window.__CONFIG__中。我们需要检查generateVideoSign函数外部,secretKey是否有被赋值或修改的可能。

4. 动态调试验证与算法复现

静态分析给了我们一个清晰的假设。现在需要通过动态调试来验证这个假设,并确保我们完全理解了这个过程。

4.1 控制台验证与密钥确认

我们在开发者工具的Console(控制台)标签页中进行验证:

  1. 验证MD5函数

    // 测试内置或全局的md5函数是否标准 console.log(md5('hello')); // 应该输出 '5d41402abc4b2a76b9719d911017c592'

    如果输出符合标准MD5,说明算法基础是可靠的。

  2. 复现签名: 将之前记录的实际请求参数代入我们分析出的算法。

    const videoId = '12345'; // 替换为实际值 const timestamp = '1678886400'; // 替换为实际值 const secretKey = 'x@m9!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面板搜索secretkey,或在Network面板查看页面加载初期是否有返回配置的请求。
  • 存在额外的签名因子: 除了我们看到的参数,算法可能还拼接了用户ID、会话Cookie中的某个字段,或者一个固定的“盐值”(salt)。这就需要我们对比多个不同上下文(不同用户、不同视频、不同时间)的请求,分析sign的变化规律,并通过调试找出所有参与计算的变量。

实操心得: 动态调试时,善用Watch(监视)面板。将你怀疑的变量(如videoId,timestamp,secretKey, 以及拼接过程中的中间字符串)添加到监视列表,可以非常直观地看到它们在代码执行过程中的值变化,极大提升分析效率。

5. 漏洞挖掘:从算法还原到实际绕过

成功复现签名算法,只是第一步。真正的漏洞挖掘,是分析这套机制哪里可能出问题。针对这个案例,我们至少可以发现三个层面的安全问题:

5.1 漏洞点一:密钥硬编码于前端

这是最直接、最严重的漏洞。一旦攻击者通过我们上面的逆向分析手段获取了secretKey = 'x@m9!pL2qS',他就拥有了为任意video_idt生成合法sign的能力。

攻击利用: 攻击者可以编写一个简单的Python脚本,伪造任意付费视频的请求:

import hashlib import time import requests SECRET_KEY = 'x@m9!pL2qS' TARGET_VIDEO_ID = '67890' # 一个未购买的付费视频ID def generate_sign(vid, t): data = f'video={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 = f'https://target.com/api/v1/video/stream?video_id={TARGET_VIDEO_ID}&t={current_timestamp}&sign={signature}' response = requests.get(url) # 如果成功,即可下载或播放未授权的视频资源

这直接导致了付费资源的未授权访问,属于高危漏洞。

5.2 漏洞点二:时间戳校验缺陷

即使密钥没有硬编码,时间戳t的校验也可能存在问题。我们分析服务器端可能的校验逻辑:

  1. 接收请求,提取t
  2. 计算当前服务器时间server_time
  3. 检查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. 漏洞报告与修复建议

基于以上分析,我们可以撰写一份高质量的安全漏洞报告。

报告核心要素

  1. 漏洞标题: [目标系统] 付费视频流接口签名校验绕过导致未授权访问
  2. 风险等级: 高危
  3. 漏洞详情
    • 受影响端点GET /api/v1/video/stream
    • 漏洞描述: 该接口用于校验用户权限并返回视频流,其签名(sign)参数由前端JavaScript生成。经逆向分析发现,签名密钥(secretKey)以明文形式硬编码在前端代码中。攻击者可通过浏览器开发者工具轻易提取该密钥,并据此为任意video_id伪造合法签名,从而无需购买即可访问所有付费视频资源。
    • 技术分析: 详细描述逆向过程,包括定位到的关键函数generateVideoSign,展示其硬编码的密钥'x@m9!pL2qS'和简单的MD5拼接算法。提供复现步骤和PoC(概念验证)代码。
    • 潜在影响: 所有付费课程内容可被未授权下载/播放,造成直接经济损失和版权侵害。
  4. 修复建议
    • 立即措施: 后端临时增加更严格的校验,如绑定用户会话ID与请求,或启用短期有效的访问Token。
    • 根本解决方案
      • 密钥前端硬编码: 绝对禁止将签名密钥放在前端。所有涉及权限校验的签名应在后端生成。前端需要签名时,应向后端发起一个安全(如携带用户登录态)的请求,由后端计算后返回。
      • 升级签名算法: 改用更安全的HMAC-SHA256等算法。
      • 增加随机因子: 在签名中引入服务器下发的随机数(nonce),确保每次请求的签名唯一,防止重放。
      • 强化参数校验: 严格校验时间戳t的有效期(建议在±30秒内),并确保校验逻辑在所有服务器节点一致。
      • 进行代码混淆与加固: 虽然不能从根本上解决密钥泄露问题,但可以增加逆向分析的难度,作为纵深防御的一环。

7. 拓展思考与防御方视角

通过这个案例,我们不仅作为攻击方学会了如何挖掘漏洞,更应该从防御方角度思考如何构建更安全的系统。

对于前端加密/签名的正确认识: 前端加密(包括JS加密参数)的主要目的,通常不是为了防止恶意用户,而是:

  1. 增加自动化攻击的成本: 防止简单的爬虫和脚本直接调用接口。
  2. 保护数据传输过程中的一些敏感参数(但密钥在前端时无效)。
  3. 满足合规性或业务逻辑需求,比如对数据进行格式化处理。

真正的安全防线应该在后端

  • 永不信任前端: 所有来自前端的参数,包括sign,都必须经过后端严格的、无状态的验证。
  • 密钥安全: 签名密钥必须存储在安全的服务器环境(如环境变量、密钥管理服务)中,绝对不出现在客户端代码、配置文件和版本控制历史里。
  • 完善的校验逻辑: 签名验证、权限判断、业务逻辑校验必须形成一个完整的链条,任何一环缺失都可能导致漏洞。

作为漏洞挖掘者,我们的价值在于帮助厂商发现这些“前端看似安全,后端实则脆弱”的认知盲区。这种从加密参数逆向入手,直指核心业务逻辑漏洞的挖掘方式,在SRC实战中非常高效,尤其适合那些前端交互复杂、对安全性过度自信的现代Web应用。每一次成功的逆向,都是对系统安全假设的一次有力挑战。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询