免责声明: 本文仅供安全学习研究,所有测试均在授权环境或自建靶场中进行。严禁用于非法用途,否则后果自负,与作者及云栈社区无关!
某水卡系统漏洞实战
此次实战是针对混合开发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之所以相对容易测试,原因有三:
- 业务逻辑在H5中,可直接抓包
- 前端JS代码可查看,签名算法可逆向
- 提取H5链接后可在浏览器中测试
如何判断APP类型
最简单的方法就是看请求包。举个例子,如果接口里面带有 h5 关键词: GET https://h5.***/app/index.html ,这基本就“实锤”了。或者你也可以解包 APK,去搜索 WebView 相关代码,亦或查看 assets 目录下是否有 H5 文件。
话不多说,直接开始实战!本次实战基于真实场景搭建的模拟靶场进行演示。
正文
由于充值后页面会立即重定向,浏览器 F12 根本抓不到完整请求。这难不倒我们,这里换用 Reqable 抓包。

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

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

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

虽然页面展示“我”转账至 A-102 是 -50,但我的余额没扣反加,从 349.48 变成了 399.48。原理其实很简单:
- 我的余额 = 100 - (-50) = 150 ← 不减反加
- 对方余额 = 200 + (-50) = 150 ← 被扣钱了

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

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

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

这是一种常见的防篡改机制,后端会根据参数重新计算签名并与请求中的 sign 比对。要如何绕过签名校验?这需要我们逆向分析签名算法。因为是混合APP,签名逻辑就赤裸裸地写在前端JS代码里,我们可以直接分析。
直接全局搜索 sign 参数,触发充值事件,下个断点。看,明文就是 amount 加上 meter_no 和时间戳,一块儿被送到 generateSign 函数里进行了加密。

点进 generateSign 函数一探究竟。

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 模拟发包。

后端逻辑跟之前的充值、转账如出一辙,但这次的危害可大多了。因为它不仅“吸”了钱,还真正加上了电费!
- 负数度数 → 负数费用 → 扣负数 = 加钱
- 电表读数直接加负数 → 读数倒退

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