找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

4019

积分

0

好友

527

主题
发表于 2 小时前 | 查看: 3| 回复: 0

免责声明: 本文仅供安全学习研究,所有测试均在授权环境或自建靶场中进行。严禁用于非法用途,否则后果自负,与作者及云栈社区无关!


某水卡系统漏洞实战

此次实战是针对混合开发APP的渗透测试,通过抓包提取APP内嵌的H5页面,对其API接口进行安全测试。这类目标到底有什么魔力,能成为APP渗透的入门首选?

常见混合开发框架:

Cordova

  • 技术栈:WebView + H5
  • 代表应用:早期银行APP、政务APP
  • 特点:最早的混合开发方案,插件生态丰富

Ionic

  • 技术栈:Angular/React/Vue + WebView
  • 代表应用:企业OA、CRM系统
  • 特点:UI组件丰富,适合企业应用

原生WebView

  • 技术栈:原生壳 + H5页面
  • 代表应用:各种物业/水电缴费APP
  • 特点:开发成本低,更新灵活,最常见

为什么选择混合开发APP作为切入点?因为原生APP需要逆向、Hook等技术,门槛相对较高。而混合APP内嵌H5页面,业务逻辑和界面都跑在WebView里,我们只需抓包分析即可发现漏洞,是APP渗透的最佳入门目标。这次实战目标就是从APP提取的H5页面进行挖掘——本质上更偏向Web渗透

混合APP之所以相对容易测试,原因有三:

  1. 业务逻辑在H5中,可直接抓包
  2. 前端JS代码可查看,签名算法可逆向
  3. 提取H5链接后可在浏览器中测试

如何判断APP类型

最简单的方法就是看请求包。举个例子,如果接口里面带有 h5 关键词: GET https://h5.***/app/index.html ,这基本就“实锤”了。或者你也可以解包 APK,去搜索 WebView 相关代码,亦或查看 assets 目录下是否有 H5 文件。

话不多说,直接开始实战!本次实战基于真实场景搭建的模拟靶场进行演示。

正文

由于充值后页面会立即重定向,浏览器 F12 根本抓不到完整请求。这难不倒我们,这里换用 Reqable 抓包。

智慧水电卡应用首页界面,显示账户余额及常用服务入口

抓包后,直接将 amount 改为 -50,竟然充值成功!这是典型的负数金额漏洞——后端未校验金额正负,导致 balance + (-50) 使余额反减。既然充值接口没有校验金额正负,那么整个系统的资金逻辑可能都存在缺陷,转账接口会不会也“中招”呢?

API调试界面,展示了通过修改请求体中的金额为负数,后端返回充值成功的响应数据

转账功能在前端倒是做了校验,提示“请输入正确的金额”。可这又有什么用呢?跟充值一样,转账后会立即重定向,浏览器 F12 依旧抓不到完整请求。我们继续使用 Reqable 抓包。

转账界面,显示可用余额和转账金额输入框,其中金额被修改为负数

转账接口同样存在相同的逻辑漏洞。我们来瞧瞧这笔糊涂账是怎么个算法。

API调试界面,展示了发送负数额的转账请求,后端依然返回了转账成功的响应

虽然页面展示“我”转账至 A-102 是 -50,但我的余额没扣反加,从 349.48 变成了 399.48。原理其实很简单:

  • 我的余额 = 100 - (-50) = 150 ← 不减反加
  • 对方余额 = 200 + (-50) = 150 ← 被扣钱了

应用账户界面,展示了余额因负数额转账而异常增加

嗯……这是个水电卡系统,转账和充值只是资金入口,真正的业务核心在缴费环节。我们继续沿着业务流程,深挖电费缴纳模块,看看里面的水有多深。

进入缴费页面,只有两个接口。

电力费用查询与缴费界面,包含电表读数信息及输入充值度数的区域

还是熟悉的配方,还是熟悉的味道?试一试修改成负值进行“充电”。

缴费界面,充值度数输入框被修改为-1度,试图触发漏洞

然而,这次接口没那么简单了。电费缴纳接口增加了安全防护——请求中包含 sign 签名参数。直接修改 amount 为 -100 发送,返回"密钥验证失败"。

API调试界面,因修改请求参数后签名未更新,返回了“密钥验证失败”的错误

这是一种常见的防篡改机制,后端会根据参数重新计算签名并与请求中的 sign 比对。要如何绕过签名校验?这需要我们逆向分析签名算法。因为是混合APP,签名逻辑就赤裸裸地写在前端JS代码里,我们可以直接分析。

直接全局搜索 sign 参数,触发充值事件,下个断点。看,明文就是 amount 加上 meter_no 和时间戳,一块儿被送到 generateSign 函数里进行了加密。

前端代码调试界面,展示了生成签名前,构造的请求参数对象,包含电表号、金额和时间戳

点进 generateSign 函数一探究竟。

generateSign函数代码,显示了通过密钥对排序后的参数字符串进行MD5加密的具体实现

function generateSign(params) {
    // 1. 获取所有参数名,过滤掉sign本身,然后按字母顺序排序
    // → ["amount", "meter_no", "timestamp"]
    const sortedKeys = Object.keys(params).filter(k => k !== 'sign').sort();
    // 2. 将参数按 key=value 格式拼接,用 & 连接
    let signStr = sortedKeys.map(k => `${k}=${params[k]}`).join('&');
    // 3. 在末尾追加密钥(这就是签名的关键!密钥硬编码在前端)
    signStr += '&key=WaterCard@2024#SecretKey';
    // 4. 对拼接后的字符串进行MD5哈希,得到签名
    return md5(signStr);
}

签名算法总结下来就是: sign = MD5(参数按字母排序拼接 + &key=密钥)

加密算法分析完之后,我们用 Python 模拟发包。

Python代码编辑器与终端,展示了签名算法的重构、请求的发送,以及成功返回“缴费成功”的响应

后端逻辑跟之前的充值、转账如出一辙,但这次的危害可大多了。因为它不仅“吸”了钱,还真正加上了电费!

  • 负数度数 → 负数费用 → 扣负数 = 加钱
  • 电表读数直接加负数 → 读数倒退

电表充值记录,成功充值了-100.0度电,金额为-52.00元

结尾

本文仅供安全学习研究使用,请勿用于非法用途。




上一篇:一次 malloc 背后:glibc 内存分配器 30 年演进史(从 dlmalloc 到 TCache)
下一篇:2026江苏招生计划PDF发布: AI驱动分数线查询工具开发实录
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-6-25 03:52 , Processed in 0.959109 second(s), 42 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表