news 2026/6/21 6:07:52

H5前端安全攻防实战:从逻辑漏洞到签名绕过

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
H5前端安全攻防实战:从逻辑漏洞到签名绕过

1. 项目概述:一次完整的H5前端安全攻防演练

最近在复盘一个内部安全众测项目时,遇到一个非常典型的H5支付场景渗透案例。这个案例几乎涵盖了从最基础的逻辑漏洞到相对复杂的签名机制绕过的完整链条,非常适合用来剖析当前H5应用,尤其是涉及金融交易的前端,在安全设计上可能存在的系统性风险。整个流程并非高深莫测的0day利用,而是由一系列看似独立、实则环环相扣的“疏忽”构成。对于开发者和安全工程师来说,理解这个链条,比单纯复现一个CVE更有价值。它能让你站在攻击者的视角,审视自己代码中那些“应该没问题吧”的假设。

简单来说,这次实战的目标是一个提供在线课程购买的H5页面。用户选择课程、填写信息、调起支付(对接了常见的第三方支付渠道),最后完成订单。表面看流程标准,但深入测试后,我们发现了从“支付金额可以为负数”这一离谱的漏洞开始,最终竟然能绕过整个支付签名验证,实现“零元购”甚至“平台倒贴钱”的完整路径。下文,我将以这个案例为蓝本,拆解每一步的攻击思路、技术细节、背后的原理,以及作为防御方该如何系统性加固。

2. 核心攻击链拆解:从逻辑漏洞到机制绕过

一次成功的渗透很少依赖于单一漏洞。更多时候,攻击者像侦探一样,将多个看似微小的线索(漏洞)串联起来,形成一条通往核心资产的路径。本次案例的攻击链可以清晰地分为四个阶段,每个阶段都为下一阶段提供了必要的信息或权限。

2.1 第一阶段:负数金额漏洞的发现与利用

这是整个攻击的起点,也是一个非常低级的业务逻辑错误。在课程购买页面,前端会展示课程价格,比如999元。点击购买后,浏览器会发起一个请求到后端创建订单。

漏洞点分析:正常的请求载荷(Request Payload)可能如下:

{ "course_id": "101", "price": 99900, // 单位是分 "user_id": "12345" }

问题出在,这个price参数竟然完全由前端控制,并且后端没有进行任何有效性校验。这意味着,我们可以通过浏览器开发者工具(F12),在发起请求前拦截并修改这个请求。

攻击实操:

  1. 打开目标H5页面,选择课程,点击“立即购买”。
  2. 按下 F12 打开开发者工具,切换到Network(网络)标签页,并确保Preserve log(保留日志)被勾选。
  3. 再次点击购买,在Network中找到创建订单的请求(通常是POST /api/order/create之类的)。
  4. 右键该请求,选择Edit and Resend(编辑并重发)。在请求体(Request Body)中,将"price": 99900修改为"price": -99900
  5. 发送请求。

后端错误逻辑:如果后端逻辑是用户账户余额 -= 订单金额来计算支付,那么当订单金额为-99900(即 -999元)时,计算就变成了余额 -= (-99900),等价于余额 += 99900。这意味着,用户不仅不用付钱,系统反而会“奖励”用户999元到账户余额。更常见的情况是,后端直接使用这个price去调用支付渠道,如果支付渠道也未做负数校验,就可能产生一笔负向支付,资金流向用户。

注意:这种漏洞在成熟的系统中已较少见,但在一些快速迭代、前后端信任边界模糊的H5应用(尤其是创业公司早期产品)中仍有发现。其根源在于对客户端传入数据的绝对信任

2.2 第二阶段:信息搜集与接口探测

在成功利用负数金额漏洞(可能触发了风控或仅限测试环境)后,攻击进入信息搜集阶段。目标是理解应用的整体结构、API接口和参数规律。

关键操作:

  1. 浏览所有前端JS文件:Sources(源代码)标签页中,查看加载的所有.js文件。搜索关键词如api,url,/pay,/order,sign,key,secret。前端可能硬编码了一些接口路径甚至配置信息。
  2. 分析网络请求规律:Network中观察不同功能点的请求。重点关注:
    • 接口URL模式:/api/v1/action还是/module/controller/action
    • 参数命名风格:使用userId还是user_id?时间戳参数叫什么?timestamp还是_t
    • 认证方式:是使用CookieAuthorization: Bearer token还是将token放在请求参数里?
    • 签名特征:是否存在名为sign,signature,sig的参数?其他参数看起来是否有序排列?这可能是签名算法的线索。
  3. 尝试未授权访问:将登录后才能访问的接口URL,直接在无登录状态的浏览器标签页中访问,或使用工具重放请求,观察是否返回数据。这可以暴露出直接对象引用(IDOR)或权限校验缺失问题。

在这一阶段,我们可能发现订单创建接口、支付发起接口、订单查询接口等。更重要的是,发现了所有关键接口都携带一个sign参数,这表明后端存在签名验证机制。攻击重点随之转移。

2.3 第三阶段:签名机制的原理分析与逆向

发现签名参数后,正面强攻无效。我们需要理解其签名算法。签名通常用于防止参数被篡改,确保请求来自合法的客户端。

常见的H5签名算法逻辑:

  1. 前端将所有待发送的业务参数(不包括sign本身)按照特定规则(如字母序)排序。
  2. 将排序后的参数拼接成key1=value1&key2=value2...格式的字符串。
  3. 在字符串末尾拼接一个只有前后端知道的secret_key(密钥)。
  4. 对这个拼接后的字符串进行哈希运算(通常是MD5或SHA256),得到的结果就是sign值。
  5. 前端将sign连同其他业务参数一起发送给后端。
  6. 后端用同样的算法和密钥重新计算一遍sign,并与前端传来的sign对比,一致则通过校验。

逆向工程方法:由于密钥secret_key存放在后端,前端不可能直接获得。攻击者的突破口在于:密钥是否可能泄露?或者,签名算法是否存在逻辑缺陷?

  • 源码泄露:继续深挖前端JS。有时开发者会将密钥硬编码在注释里、配置文件中,甚至因为打包失误将测试环境的配置发布到生产环境。全局搜索secret,key,salt等关键词。
  • 算法还原:即使没有密钥,也可以通过观察多组请求来还原算法。收集5-10个不同功能、不同参数的请求,记录它们的参数和sign值。
    • 对比参数,看是否每次都有timestamp(时间戳)和nonce(随机数),这常用于防止重放攻击。
    • 尝试猜测排序规则。按参数名字母序排序是最常见的。
    • 使用工具(如Burp Suite的Comparer)对比不同请求的原始参数字符串,寻找规律。
  • 寻找“本地计算”漏洞:最致命的情况是,签名算法完全在前端JavaScript中实现。这意味着,只要你能运行JS,就能知道密钥和算法。检查是否有名为sign.js,crypto.js的文件,或者主业务JS文件中存在function generateSign(params)这样的函数。如果签名在前端计算,那么整个签名机制形同虚设,因为攻击者可以完全模拟这个计算过程。

在本案例中,我们幸运地(对防御方是不幸地)发现,签名生成函数genSign()直接暴露在一个未压缩混淆的公共JS文件里,密钥appSecret以字符串形式硬编码其中。

2.4 第四阶段:构造请求与签名绕过实战

拿到密钥和算法后,绕过签名验证就变成了一个“合法”的请求构造过程。

攻击步骤:

  1. 提取算法与密钥:从JS文件中复制出genSign()函数代码和appSecret的值。
  2. 编写攻击脚本:使用 Python 或 Node.js 编写一个小脚本,模拟前端的签名过程。
    import hashlib import urllib.parse import time app_secret = "这里是硬编码的密钥" def generate_sign(params): # 1. 参数排序 sorted_params = sorted(params.items(), key=lambda x: x[0]) # 2. 拼接键值对 str_to_sign = '&'.join([f"{k}={v}" for k, v in sorted_params]) # 3. 拼接密钥 str_to_sign += app_secret # 4. MD5哈希 sign = hashlib.md5(str_to_sign.encode('utf-8')).hexdigest() return sign # 构造一个恶意请求参数:购买ID为101的课程,价格改为1分钱 malicious_params = { "course_id": "101", "price": 1, # 不再是负数,避免触发简单风控,改为极小正数 "user_id": "攻击者的用户ID", "timestamp": int(time.time()), # 当前时间戳 "nonce": "随机字符串" # 随机数,可从一次正常请求中复制 } malicious_params["sign"] = generate_sign(malicious_params) print("最终请求参数:", malicious_params)
  3. 发起恶意请求:使用脚本生成的、带有合法sign的参数,通过curl命令或 Burp Suite 的Repeater模块,直接向订单创建接口发起请求。
  4. 结果:后端验证签名通过,因为算法和密钥都正确。于是成功创建了一个价格为1分钱的订单。后续支付流程中,如果支付渠道允许自定义金额(有些渠道的“企业付款”或特定接口存在此问题),或者平台存在“余额支付”且用户余额足够抵扣这1分钱,那么攻击者就能以近乎零成本完成购买。

至此,一条从前端参数篡改开始,到完全绕过核心安全机制(签名验证)的攻击链就完成了。攻击者获得了以任意价格(包括非法价格)购买商品的能力。

3. 漏洞深度解析:为什么这些漏洞会发生?

理解攻击手法后,我们必须深挖其根源。这些漏洞不是偶然,而是特定开发模式和安全意识缺失下的必然产物。

3.1 负数金额漏洞:信任边界的彻底失效

这个漏洞的本质是“服务端对客户端数据缺乏校验”。在Web安全中,有一条铁律:永远不要信任客户端传来的任何数据。客户端(浏览器、App)是完全在用户控制下的,任何参数都可以被篡改。

  • 开发者的典型错误思维:“价格是从我们数据库里读出来,前端只是展示一下,提交时传回去,后端直接用就行了。” 他们忽略了中间环节——数据在用户浏览器内存中可以被任意修改。
  • 正确的做法:
    1. 关键业务参数不从客户端获取:订单金额、商品单价等核心数据,应该在后端根据course_id从数据库中实时查询得出,而不是依赖前端传递。
    2. 如果必须传递,则需强校验:即使传递,后端也必须进行有效性校验。例如,金额必须为正数,且与服务器查询到的商品价格一致(允许微小差异用于优惠券抵扣,但需有明确规则)。
    3. 使用不可篡改的令牌:对于复杂流程,可以在用户选择商品后,后端生成一个包含商品ID和价格的加密令牌(Token)给前端。前端提交订单时传回令牌,后端解密后使用其中的价格,而不是前端传来的明文价格。

3.2 签名机制被绕过:安全机制的错误实现

签名机制本身是好的,但错误的实现使其沦为摆设。

  • 密钥硬编码在前端:这是最致命的错误。密钥(secret_key)是签名安全性的基石,必须保存在服务器端,永远不能下发到客户端。一旦前端JS中包含密钥,任何访问页面的用户都可以轻松获取。
  • 签名算法过于简单或可预测:即使密钥未泄露,如果算法存在缺陷,也可能被破解。例如,不使用随机数(nonce)和时间戳(timestamp),会导致签名无法防止重放攻击(攻击者截获一个合法请求,可以无限次重放)。又或者,参数排序规则固定且简单,容易被推测。
  • 缺乏签名绑定:签名没有与当前用户会话或特定请求绑定。攻击者可能用自己的密钥为一组参数生成签名,但这组参数却是其他用户的数据,如果后端没有检查参数中的用户ID与当前登录用户是否一致,就会导致越权。

一个相对安全的签名实现应包含:

  • 密钥隔离:secret_key仅存于后端,且分环境配置(开发、测试、生产不同)。
  • 动态盐值:引入nonce(一次性随机字符串)和timestamp,并将它们参与签名计算。后端需校验nonce是否使用过(防重放)以及timestamp是否在合理时间窗口内(如±5分钟)。
  • 签名绑定:将用户身份标识(如user_id)或会话ID作为签名参数的一部分,确保签名无法跨用户使用。
  • 算法强度:使用HMAC-SHA256等强哈希算法,而非已被证明存在碰撞风险的MD5。

3.3 信息泄露与过度暴露:为攻击者铺路

前端JS未压缩混淆、接口文档信息过于详细、错误信息回显敏感内容等,都属于信息泄露,它们为攻击者提供了宝贵的“地图”。

  • 前端代码混淆:对发布到生产环境的JS文件进行压缩和混淆,可以大幅增加逆向工程的难度。虽然不能绝对防止,但能有效提高攻击门槛。
  • 最小化接口暴露:前端只加载执行当前页面所必需的JS代码。避免将整个项目的、包含所有接口和逻辑的源代码打包到一个文件中供浏览器下载。
  • 统一的错误处理:后端接口应返回统一的、信息模糊的错误信息。例如,无论是密码错误、用户不存在还是签名错误,都返回“请求失败,请检查输入”,而不是“签名校验未通过”这样明确指向安全机制的错误提示。

4. 防御加固方案:构建纵深防御体系

针对上述攻击链,防御不能只堵一个点,而需要构建从客户端到服务端的纵深防御体系。

4.1 后端校验:铸就最后也是最坚固的防线

后端是安全的最终裁决者,必须实施多层校验。

  1. 业务逻辑校验层:

    • 价格校验:所有涉及金额的字段,必须在后端进行非负、数值范围、与数据库记录一致性等校验。
    • 库存校验:创建订单前,校验商品库存。
    • 状态机校验:订单状态流转必须符合预设流程(如“待支付”->“已支付”->“已完成”),防止通过接口直接修改状态。
  2. 数据合法性校验层:

    • 输入验证:对所有输入参数进行严格的类型、长度、格式验证(如手机号、邮箱正则匹配)。
    • 输出编码:防止XSS,对所有输出到前端的数据进行HTML编码。
  3. 身份与权限校验层:

    • 会话验证:每个敏感接口都必须验证用户会话(Token/Cookie)的有效性。
    • 权限验证:校验当前登录用户是否有权操作其请求的资源(如修改的订单是否属于自己)。这通常需要在数据库查询时加入user_id条件。
  4. 签名验证层(如果使用):

    • 密钥安全存储:使用配置中心或环境变量管理密钥,严禁硬编码。
    • 实现防重放:服务端缓存已使用的nonce(可设置较短过期时间),拒绝重复请求。校验timestamp与服务器时间差。
    • 绑定用户:user_id或会话标识作为签名参数。

4.2 前端防护:增加攻击难度

前端安全是“防君子不防小人”,但能有效阻挡自动化脚本和低水平攻击。

  1. 代码混淆与压缩:使用 Webpack、UglifyJS 等工具对生产环境代码进行混淆,重命名变量和函数,增加阅读难度。
  2. 敏感信息隔离:绝对不要在前端代码中硬编码API密钥、数据库密码、加密盐值等任何秘密。
  3. 输入格式限制:在表单提交前进行基本的格式校验(如金额输入框限制为正数),虽然可以被绕过,但能拦截大部分普通用户的误操作。
  4. 启用CSP:内容安全策略(Content Security Policy)可以有效地减少XSS攻击的影响范围,阻止恶意脚本的执行。

4.3 监控与响应:及早发现异常

再完善的防御也可能有疏忽,因此监控至关重要。

  1. 业务风险监控:

    • 金额异常告警:监控订单金额为负数、0、或远低于正常值的订单。
    • 频率异常告警:监控同一用户、IP在短时间内发起大量相同请求(如频繁创建订单)。
    • 库存异常告警:监控商品库存被异常清空。
  2. 安全日志审计:

    • 详细记录所有敏感操作(登录、支付、订单修改)的日志,包含用户ID、IP、时间、操作内容和结果。
    • 记录所有签名验证失败的请求,分析其模式和来源,这可能是攻击探测的信号。
  3. WAF(Web应用防火墙):

    • 部署WAF,配置规则拦截常见的攻击模式,如SQL注入、XSS、路径遍历等。
    • 可以自定义规则,拦截包含price=-等明显恶意参数的请求。

5. 渗透测试思维与技巧延伸

这个案例不仅展示了漏洞,更展示了一种系统性的测试思维。在实际的渗透测试或安全评估中,可以沿着以下思路深化:

1. 参数污染与边界测试:不仅仅是price字段,对所有接收参数的字段进行测试:

  • 数据类型:数字型参数传字符串、数组、对象会怎样?
  • 边界值:传入极大值(如9999999999)、极小值(0.01)、负数。
  • 特殊字符:传入单引号、分号;、注释符--测试SQL注入;传入<script>测试XSS。
  • 参数缺失:不传某个必填参数,或者多传一些后端未定义的参数。

2. 接口顺序与状态绕过:尝试不按正常业务流程调用接口。例如:

  • 跳过“创建订单”接口,直接调用“支付成功回调”接口,并传入一个伪造的订单号。
  • 在订单“待支付”状态时,直接调用“确认收货”接口。
  • 使用低权限用户(如普通用户)的Token,去访问高权限(如管理员)的接口。

3. 工具链的熟练使用:

  • Burp Suite:拦截、重放、修改请求是基础。其Intruder模块可用于参数爆破,Scanner模块可进行自动化漏洞扫描,Repeater用于手动测试接口响应。
  • 浏览器开发者工具:除了NetworkSources用于调试JS,Application用于查看和修改Cookie、LocalStorage,Console可以执行JavaScript来动态修改页面逻辑。
  • 自定义脚本:对于需要复杂逻辑或批量测试的场景,用Python编写脚本是最高效的方式。requests库用于发包,re库用于正则匹配响应内容。

4. 关注新型漏洞与供应链风险:

  • 第三方依赖漏洞:H5项目大量使用第三方JS库(如 jQuery, Vue, React)和组件。需要关注这些依赖的公开漏洞(CVE),并及时升级。可以使用npm audit或 Snyk 等工具进行检查。
  • 配置错误:如前面提到的,生产环境使用了测试环境的配置(如开关、密钥),或者错误的CORS配置导致接口被任意网站调用。
  • 业务逻辑耦合漏洞:两个单独看都安全的业务,组合起来可能产生漏洞。例如,“优惠券”系统和“退款”系统如果设计不当,可能组合出“使用优惠券购买后全额退款”的套利漏洞。

安全是一个持续的过程,而非一劳永逸的状态。这个H5渗透案例告诉我们,最大的风险往往来自于对“常识”的忽视和对客户端环境的过度信任。作为开发者,应当时刻保持“零信任”心态,对每一行来自外部的数据都抱有怀疑;作为安全人员,则需要像攻击者一样思考,不放过任何细微的异常,才能构建起真正有效的防御。

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

基于ARM Cortex-M的PMSM无传感器FOC控制实战指南

1. 项目概述&#xff1a;从零开始构建PMSM无传感器控制系统如果你正在开发一个需要精确控制、高效率的电机驱动项目&#xff0c;比如无人机电调、工业伺服驱动器或者家用电器里的变频风机&#xff0c;那么永磁同步电机&#xff08;PMSM&#xff09;很可能是你的首选。这种电机以…

作者头像 李华
网站建设 2026/6/21 5:56:55

快乐是最好的运气密码

人很容易陷入烦恼的循环。 一件小事就能搅乱心绪&#xff0c;一个念头就能困住自己。你总在想那些不顺心的事&#xff0c;总在担心还没发生的事&#xff0c;快乐就这样被一点点挤走。日子过得越来越压抑&#xff0c;运气似乎也越来越差。可你有没有发现&#xff0c;当你心情好的…

作者头像 李华
网站建设 2026/6/21 5:53:27

Claude Code与DeepSeek V4 Pro协议对齐实战指南

1. 这不是“换模型”而是重构编程工作流&#xff1a;Claude Code 与 DeepSeek V4 Pro 的真实协同逻辑你点开这个标题&#xff0c;大概率正被两件事困扰&#xff1a;一是 Claude Code 自带的 Anthropic 模型在写复杂后端逻辑、长链路调试或跨服务集成时频繁卡在“32000 token 输…

作者头像 李华
网站建设 2026/6/21 5:53:07

Python项目安全审计实战:四大开源工具构建自动化防护体系

1. 项目概述&#xff1a;一个被忽视的“隐形守护者”干了这么多年开发&#xff0c;从写第一个“Hello World”到现在带团队做项目&#xff0c;我见过太多因为赶进度、图省事&#xff0c;最后在安全上栽跟头的案例。尤其是Python项目&#xff0c;大家总觉得它语法优雅、生态丰富…

作者头像 李华
网站建设 2026/6/21 5:50:10

Qwen3.5-27B在T4显存上的实战部署与显存碎片治理

1. 这不是“又一个大模型评测”&#xff0c;而是27B级推理引擎的实战压力测试现场最近在阿里云百炼平台看到 Qwen3.5-27B 的正式灰度入口&#xff0c;没点开控制台&#xff0c;先翻了三遍 release note——不是因为兴奋&#xff0c;是心里发毛。270亿参数的模型&#xff0c;标称…

作者头像 李华
网站建设 2026/6/21 5:48:17

终极指南:如何让2008-2017款旧Mac免费升级到最新macOS系统

终极指南&#xff1a;如何让2008-2017款旧Mac免费升级到最新macOS系统 【免费下载链接】OpenCore-Legacy-Patcher Experience macOS just like before 项目地址: https://gitcode.com/GitHub_Trending/op/OpenCore-Legacy-Patcher 还在为你的旧Mac无法升级到最新macOS系…

作者头像 李华