news 2026/7/3 8:17:52

H5业务逻辑漏洞实战:从负数金额到签名算法绕过

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
H5业务逻辑漏洞实战:从负数金额到签名算法绕过

1. 项目概述:一次典型的H5业务逻辑漏洞挖掘之旅

最近在复盘一个很有意思的实战案例,目标是一个典型的混合开发APP,其核心业务逻辑都封装在H5页面里。整个过程从发现一个看似简单的“负数金额”漏洞开始,逐步深入,最终绕过了其核心的签名校验机制,揭示了这类应用在安全设计上常见的薄弱环节。这不仅仅是两个独立漏洞的发现,更是一次完整的攻击路径演示,非常适合刚接触APP或Web渗透测试的朋友理解业务逻辑漏洞的挖掘思路。如果你对抓包、前端代码审计、签名算法逆向感兴趣,那么这个案例能给你提供一个非常清晰的实操范本。

简单来说,这次实战的目标是一个水电卡缴费系统。用户可以通过APP进行水卡充值、转账以及缴纳电费。测试环境是我自己搭建的模拟靶场,完全复现了真实场景中的逻辑。整个渗透过程的核心思路是:先通过常规的参数篡改测试发现低垂的果实(负数金额漏洞),再利用混合APP的特性(前端代码暴露)深入分析其安全防护机制(签名算法),最终实现完全绕过。这种由浅入深、环环相扣的测试方法,在实际的授权渗透测试中非常有效。

2. 环境准备与目标分析

2.1 为什么选择混合开发APP作为切入点

在移动应用渗透测试中,目标类型的选择往往决定了测试的难易度和入门门槛。原生APP(Native App)通常需要逆向工程、动态调试(如Frida Hook)等相对高阶的技术,对新手不太友好。而混合开发APP(Hybrid App)则是一个绝佳的起点。

混合APP的本质是在一个原生容器(通常是WebView)中运行网页(H5)。这意味着:

  1. 业务逻辑在前端:大量的核心业务逻辑,包括参数校验、数据组装、甚至部分加密签名算法,都是用JavaScript编写的,并随着H5资源文件下发到客户端。
  2. 通信透明:H5页面与后端服务器的通信,依然是标准的HTTP/HTTPS协议,我们可以用常规的抓包工具(如Burp Suite、Charles、Reqable)轻松拦截和修改。
  3. 代码可读:前端JS代码虽然可能被压缩或混淆,但相较于原生二进制代码,其可读性和可分析性要高得多。

本次实战的目标应用,就是一个使用“原生壳+H5页面”架构的典型物业缴费APP。判断方法很简单:抓包时观察请求URL或静态资源路径,如果出现*h5**web*等字眼,或者直接能抓到.html文件的请求,基本就可以确定。更彻底一点,可以解压APK文件,查看assetsres目录下是否存在大量的htmljscss文件。

2.2 工具链准备

工欲善其事,必先利其器。针对这类H5渗透测试,我习惯使用以下工具组合,它们覆盖了从流量捕获到代码分析的完整链条:

  1. 抓包工具

    • Burp Suite / Charles:老牌且功能全面的代理工具,适合深度测试和漏洞利用。但对于某些快速重定向或WebSocket请求,可能需要精细配置。
    • Reqable:这是我近年来非常喜欢的一款国产抓包工具,界面现代化,对HTTP/HTTPS、WebSocket的支持很好,特别是在处理APP请求时非常稳定。本次实战中,因为目标应用在操作成功后页面会立即跳转,浏览器开发者工具(F12)的Network面板经常来不及捕获完整的请求/响应,Reqable的全局抓包能力就派上了大用场。
  2. 浏览器开发者工具:Chrome或Edge的DevTools是分析前端代码的利器。主要用到以下几个面板:

    • Network:查看所有网络请求,复制为cURL命令,重放请求。
    • Sources:查看、搜索和调试前端JavaScript代码。可以设置断点,单步跟踪变量和函数执行。
    • Console:执行JS代码,测试函数,查看日志输出。
  3. 代码编辑与脚本工具

    • VS Code / Sublime Text:用于查看和格式化JS代码。
    • Python3 + requests库:用于编写POC脚本,模拟签名算法,自动化发送测试请求。这是绕过签名校验的关键一步。
  4. 模拟靶场环境:为了合法、安全地学习和研究,我基于真实漏洞逻辑在本地搭建了模拟靶场。强烈建议所有安全研究人员都在授权环境或自建靶场中进行测试,严格遵守法律法规。

3. 第一阶段漏洞挖掘:负数金额的逻辑缺陷

3.1 充值功能的初步测试

拿到目标APP后,我首先从最核心的“充值”功能开始测试。流程很简单:输入充值金额(例如50),点击提交。同时,打开抓包工具(这里我用Reqable)监控所有流量。

一个正常的充值请求包可能如下所示:

POST /api/v1/recharge HTTP/1.1 Host: target.com Content-Type: application/x-www-form-urlencoded user_id=12345&amount=50.00&order_id=20240320001

我的第一个测试点就是修改amount参数。将正数50改为0、改为一个极大的数(如999999),或者改为一个负数-50,然后重放(Replay)这个请求,观察后端响应和账户余额的实际变化。

注意:在测试金融类操作时,务必使用测试账户,并确认环境是隔离的。直接在生产环境测试是极其危险且违法的行为。

3.2 漏洞原理与利用

当我将amount参数修改为-50并重放请求后,服务器返回了“充值成功”。查询我的账户余额,发现不是减少了50,而是增加了50!

这就是典型的“负数金额”逻辑漏洞。其根本原因在于后端服务器在处理业务逻辑时,缺乏对关键参数(这里是金额)的有效性校验。我们来看一下问题代码可能的样子:

# 有漏洞的后端逻辑(伪代码) def recharge(user_id, amount): user = get_user(user_id) # 错误:没有校验 amount > 0 user.balance += amount # 如果 amount = -50, 则 balance = balance + (-50) user.save() return “充值成功”

正确的逻辑应该在进行余额操作前,增加一道校验:

# 修复后的后端逻辑(伪代码) def recharge(user_id, amount): if amount <= 0: return “充值金额必须大于零” user = get_user(user_id) user.balance += amount user.save() return “充值成功”

这个漏洞的危害非常直观:攻击者可以通过“充值”负数,无限增加自己的账户余额。

3.3 漏洞的横向扩展:转账功能

发现充值漏洞后,我立刻进行了横向思维:同一个系统,同一个开发团队,充值功能有漏洞,那么类似的“转账”功能呢?

果然,在测试转账功能时,遇到了和充值一样的情况:前端页面虽然对输入框做了限制(不允许输入负数),但通过抓包修改amount为负数,请求同样成功。

转账漏洞的后果有时比充值更严重。假设A向B转账-100元:

  • A的余额变化:balance_A = balance_A - (-100) = balance_A + 100(A的余额增加了)
  • B的余额变化:balance_B = balance_B + (-100) = balance_B - 100(B的余额被扣除了)

这就造成了“我转给你负钱,我反而赚钱,你却被扣钱”的荒谬局面,业务逻辑完全失控。

实操心得:在发现某一类漏洞(如参数未校验)后,一定要立即对系统内所有功能相似、接口相似的模块进行“地毯式”测试。开发人员往往会复制粘贴代码,导致同一个漏洞模式在多个地方出现。

4. 深入核心业务:电费缴纳与签名机制的发现

4.1 业务深入与新的挑战

充值和水卡转账属于“资金入口”的漏洞,那么资金消耗的“业务出口”呢?我顺着业务流程,测试了“电费缴纳”功能。

流程是:输入电表号,查询欠费金额,然后进行支付。抓取支付请求包后,我发现了一个关键变化:

POST /api/v1/pay_electric HTTP/1.1 Host: target.com Content-Type: application/x-www-form-urlencoded meter_no=EB123456&amount=100.00×tamp=1647841234&sign=a1b2c3d4e5f67890...

相比之前的接口,这里多了一个sign参数。这是一个非常明显的信号:后端可能对请求参数进行了签名校验,以防止参数在传输过程中被篡改。

我尝试像之前一样,直接将amount100.00改为-100.00然后重放请求。服务器返回了明确的错误:“签名验证失败”。这说明签名机制生效了,它检测到了我对amount参数的篡改。

4.2 签名机制的原理分析

签名是一种常见的数据完整性校验和身份验证机制。其基本原理是:

  1. 客户端:将需要发送的参数(不包括sign本身)按照一定规则(如按字母排序)拼接成一个字符串,然后与一个只有客户端和服务器知道的“密钥”(Secret Key)组合,最后对这个组合字符串进行哈希运算(如MD5、SHA256),得到的结果就是sign值。
  2. 客户端:将参数和计算出的sign一起发送给服务器。
  3. 服务器:收到请求后,用同样的规则和密钥,对收到的参数重新计算一次签名。
  4. 服务器:比较自己计算出的签名和请求中传来的sign值。如果一致,说明参数未被篡改;如果不一致,则拒绝请求。

这个机制的关键在于“密钥”的保密性。如果密钥泄露,或者签名算法被攻击者掌握,那么签名就形同虚设。

5. 第二阶段漏洞挖掘:逆向前端签名算法

5.1 定位签名生成代码

既然签名校验阻止了我们直接修改参数,那么下一步就是分析这个sign是如何生成的。在混合APP中,签名算法有很大概率就写在前端的JavaScript代码里。

我打开浏览器开发者工具(Sources面板),在支付页面触发一次缴费操作,同时在Network面板找到那个带sign的请求。然后,我在Sources面板中全局搜索(Ctrl+Shift+F)关键词,如signgenerateSignmd5加密等。

很快,我找到了关键代码。通常,开发者会有一个名为utils.jscommon.js或包含sign字样的文件。通过搜索,我定位到了一个名为generateSign的函数,并在支付按钮的点击事件处理函数中找到了对它的调用。我在generateSign函数入口处打了一个断点,再次触发支付。

当断点命中时,所有局部变量、传入参数都清晰可见。我看到了传入的params对象包含了meter_noamounttimestamp,唯独没有sign。这印证了签名计算不包含sign自身的常识。

5.2 算法逆向与分析

一步步单步执行(F10),我清晰地看到了签名算法的每一步:

function generateSign(params) { // 1. 获取所有参数名,过滤掉sign本身,然后按字母顺序排序 // 假设 params = {meter_no: “EB123456”, amount: “100.00”, timestamp: “1647841234”, sign: “...”} // 则 sortedKeys = [“amount”, “meter_no”, “timestamp”] const sortedKeys = Object.keys(params).filter(k => k !== ‘sign’).sort(); // 2. 将参数按 key=value 格式拼接,用 & 连接 // signStr = “amount=100.00&meter_no=EB123456×tamp=1647841234” let signStr = sortedKeys.map(k => `${k}=${params[k]}`).join(‘&’); // 3. 在末尾追加密钥(关键!密钥硬编码在前端) signStr += ‘&key=WaterCard@2024#SecretKey’; // 密钥暴露了! // 4. 对拼接后的字符串进行MD5哈希,得到签名 return md5(signStr); // 返回类似 ‘a1b2c3d4e5f67890...’ 的字符串 }

至此,签名算法完全清晰:

  1. 排序:将所有非sign参数按键名升序排列。
  2. 拼接:将排序后的参数转换为key=value形式,用&连接。
  3. 加盐:在拼接好的字符串末尾,加上固定的连接符和密钥字符串&key=WaterCard@2024#SecretKey
  4. 哈希:对最终字符串计算MD5值,作为签名。

最大的安全问题暴露了:密钥WaterCard@2024#SecretKey被硬编码(Hard-Coded)在前端JS代码中!对于任何可以访问前端代码的用户(而所有用户都可以),这个密钥就不再是秘密。这是混合APP乃至Web应用非常常见的安全反模式。

5.3 编写POC脚本绕过签名

掌握了算法和密钥,我就可以在服务器外,自主计算任何参数组合对应的合法签名了。我立刻写了一个Python脚本来实现这个算法:

import hashlib import urllib.parse import time def generate_sign(params, secret_key=‘WaterCard@2024#SecretKey’): “”” 根据逆向的算法生成签名 params: 参数字典,不应包含 ‘sign’ 键 secret_key: 从前端代码中提取的密钥 “”” # 1. 过滤并排序键 sorted_keys = sorted([k for k in params.keys() if k != ‘sign’]) # 2. 拼接 key=value 字符串 sign_str = ‘&’.join([f‘{k}={params[k]}’ for k in sorted_keys]) # 3. 追加密钥 sign_str += f‘&key={secret_key}’ # 4. 计算MD5 md5_hash = hashlib.md5() md5_hash.update(sign_str.encode(‘utf-8’)) return md5_hash.hexdigest() # 测试用例:正常缴费100元 params = { ‘meter_no’: ‘EB123456’, ‘amount’: ‘100.00’, ‘timestamp’: str(int(time.time())) # 通常使用当前时间戳 } sign = generate_sign(params) print(f“正常签名: {sign}”) # 可以构造请求:params[‘sign’] = sign # 攻击用例:缴纳-100元“电费” attack_params = { ‘meter_no’: ‘EB123456’, ‘amount’: ‘-100.00’, # 篡改金额为负数 ‘timestamp’: params[‘timestamp’] # 使用相同的时间戳 } attack_sign = generate_sign(attack_params) print(f“攻击签名: {attack_sign}”)

运行脚本,我成功得到了篡改后参数(amount=-100.00)对应的“合法”签名。用这个签名替换原请求中的sign值,再次发送请求,服务器返回了“缴费成功”!

5.4 漏洞的复合影响

这个绕过签名后的负数金额漏洞,危害性比之前的充值和转账更大,因为它直接影响了物理世界的计量系统。在这个水电卡系统中,缴纳电费可能伴随着电表读数的更新。如果缴纳-100度电的费用:

  1. 资金层面:用户账户会增加100元的余额(因为扣除了负数的费用)。
  2. 计量层面:用户的电表读数可能被减少100度。这意味着不仅“赚钱”,还可能“偷电”。

这体现了业务逻辑漏洞的一个特点:漏洞的危害性不仅取决于漏洞本身,更取决于它所处的业务上下文。同一个“负数金额”漏洞,在充值模块是金融损失,在电费缴纳模块就可能演变为涉及计量欺诈的严重安全问题。

6. 漏洞挖掘的通用思路与防御建议

6.1 针对H5/混合APP的渗透测试方法论

通过这个案例,我们可以总结出一套针对混合APP或Web前端业务逻辑测试的通用流程:

  1. 信息收集与目标分析:确认目标为混合APP,抓包识别主要功能接口和参数。
  2. 基础参数篡改测试:对每个接口的关键参数(如ID、金额、数量、状态)进行边界值测试(负数、0、极大值、特殊字符)。
  3. 发现初级漏洞:如本例的未校验负数金额。这类漏洞往往因为后端过度信任前端校验或简单遗漏导致。
  4. 识别防护机制:测试中遇到如签名校验、Token验证等阻拦时,不要轻易放弃。这标志着进入了更深层的测试。
  5. 逆向前端逻辑:利用开发者工具分析JS代码,寻找校验算法的实现。重点搜索:encryptsignmd5shacheckvalidate等关键词,并跟踪核心函数的调用栈。
  6. 算法还原与密钥查找:分析出算法步骤,并特别注意查找硬编码的密钥、盐值或IV。这是突破防线的关键。
  7. 编写POC实现绕过:使用Python等脚本语言,复现签名算法,构造合法的恶意请求。
  8. 漏洞串联与扩大影响:思考如何将绕过后的漏洞与之前发现的漏洞结合,或者应用到其他模块,评估最大危害。

6.2 开发者的安全防御方案

对于开发者和企业来说,如何避免这类漏洞?

  1. 后端进行完整的业务校验:这是铁律!永远不要信任前端传来的数据。所有业务规则校验(金额>0、库存充足、状态合法)必须在后端严格进行。前端校验仅用于提升用户体验。
  2. 签名密钥必须保密:签名或加密用的密钥、盐值等敏感信息,绝对不可以硬编码在客户端代码(如JS、APP)中。它们应该存储在服务器端的安全配置中心或环境变量中。客户端不参与核心签名的生成,或者使用一次性的临时Token。
  3. 使用更安全的签名方案
    • 非对称加密:考虑使用RSA等非对称加密。服务器持有私钥用于生成签名,客户端或前端使用公钥进行验证。即使公钥暴露,攻击者也无法伪造签名。
    • 动态密钥:可以为每个会话或每次请求生成动态的签名因子,增加预测难度。
    • 加入随机数(Nonce):在签名参数中加入服务器下发的随机数,并确保一次性使用,防止重放攻击。
  4. 对关键业务操作进行多重校验:对于支付、转账、重要状态变更等操作,除了签名,还可以加入短信验证码、二次密码、人工审核等机制。
  5. 定期安全审计与代码审查:建立代码审查制度,重点关注业务逻辑校验点和敏感信息处理。定期进行渗透测试和安全扫描,主动发现潜在问题。

7. 常见问题与排查技巧实录

在实际的测试和后续的复盘中,我遇到并总结了一些典型问题和技巧,这里分享给大家:

  1. 问题:抓包工具抓不到APP的HTTPS请求?

    • 排查:这是因为没有安装抓包工具的CA证书到手机系统信任区。仅安装在用户证书区,APP默认是不信任的。
    • 解决:对于Android 7.0以上,需要将Burp/Charles的CA证书导出,并借助Magisk等工具进行系统级安装,或者将APP的网络安全配置(android:networkSecurityConfig)设置为允许用户证书。对于测试,更方便的方法是使用已Root的模拟器或测试机。
  2. 问题:前端JS代码被压缩混淆,完全看不懂怎么办?

    • 技巧:首先尝试使用浏览器的“Pretty Print”功能(Sources面板中{}图标)格式化代码。对于常见混淆,可以搜索关键字符串(如API端点/api/v1/、参数名amount),这些字符串通常不会被混淆。找到疑似函数后,通过断点调试观察其输入输出,反向推导功能,而不必完全理解每一行代码。
  3. 问题:签名算法看起来复杂,逆向困难?

    • 技巧:善用“Hook”思想。即使不逆向算法,也可以在前端代码中“劫持”签名函数。例如,在控制台重新定义generateSign函数,让它先打印出传入的参数和计算前的原始字符串,再调用原函数。这样就能直接窃取到正确的签名逻辑和密钥。这是一种“黑盒”式的动态分析技巧。
  4. 问题:时间戳(timestamp)参数是签名的一部分,服务器会校验吗?

    • 分析:是的,通常服务器会校验时间戳以防止重放攻击(Replay Attack)。常见策略是检查客户端时间戳与服务器时间的差值,比如超过5分钟就拒绝请求。
    • 绕过:在构造攻击请求时,需要生成一个当前的有效时间戳。如果服务器校验非常严格(如要求时间戳单调递增),可能需要更精细的同步。但在很多实现不严的场景,直接使用当前时间戳即可。
  5. 问题:除了MD5,还遇到其他哈希或加密算法怎么办?

    • 思路:方法不变。核心是找到算法和密钥。常见的算法如SHA1、SHA256、HMAC、AES等,在JS中都有标准的库(如CryptoJS)。在代码中搜索这些算法库的初始化代码或密钥字符串。对于自定义的加密函数,则需要耐心跟踪其每一步操作。

这次从简单的负数金额到签名绕过的H5渗透实战,清晰地展示了一条由浅入深的测试路径。它告诉我们,很多看似坚固的防护(如签名),其弱点可能就暴露在最容易访问的地方。对于防御方而言,牢记“不信任客户端”和“保护密钥”的原则;对于学习渗透测试的朋友,这个案例则提供了一个完美的模板:保持好奇心,遇到阻碍时深入分析,往往就能发现通往核心的钥匙。在安全的世界里,逻辑的严谨性永远是第一道,也是最后一道防线。

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

Biotinyl-Pancreatic Polypeptide (human)

一、基础信息中文名称&#xff1a;N 端生物素修饰人源胰腺多肽英文全称&#xff1a;Biotinyl-Pancreatic Polypeptide (human)三字母序列&#xff1a;Biotin-Ala-Pro-Leu-Glu-Pro-Val-Tyr-Pro-Gly-Asp-Asn-Ala-Thr-Pro-Glu-Gln-Met-Ala-Gln-Tyr-Ala-Ala-Asp-Leu-Arg-Arg-Tyr-Il…

作者头像 李华
网站建设 2026/7/3 8:14:28

Web安全注入漏洞实战:从Webgoat靶场到SQL与命令注入攻防

1. 项目概述&#xff1a;为什么从Webgoat的Injection开始&#xff1f;如果你刚接触Web安全&#xff0c;或者想系统性地检验一下自己的SQL注入、命令注入等漏洞的挖掘与利用能力&#xff0c;那么Webgoat这个“故意不安全的”Web应用靶场&#xff0c;绝对是你的不二之选。而“Inj…

作者头像 李华
网站建设 2026/7/3 8:14:07

AI生成企业官网靠谱吗?从页面框架、内容创作和上线维护看选择

AI生成企业官网靠谱吗&#xff1f;从页面框架、内容创作和上线维护看选择AI生成企业官网靠不靠谱&#xff0c;不能用一句“靠谱”或“不靠谱”回答。它适合解决从0到1的效率问题&#xff0c;比如生成页面框架、栏目标题、基础文案、FAQ和初版布局&#xff1b;但企业官网最终能不…

作者头像 李华
网站建设 2026/7/3 8:12:36

2026年6月南大通用GBase 8c数据库认证培训圆满结束

南大通用培训中心举办的“GBase 8c数据库认证培训”于2026年6月底圆满结束。本次培训共382人报名&#xff0c;经过两周的学习和认证考试&#xff0c;通过考试265人&#xff0c;获得GBase 8c数据库&#xff08;gbase database&#xff09;管理工程师证书。本次培训共有13人成绩优…

作者头像 李华