一次移动应用越权漏洞的实战测试

7 阅读6分钟

近期,某社交应用(以下简称“目标应用”)遭遇了恶意攻击:攻击者利用脚本绕过业务限制,向全站用户批量发送了“情侣邀请”消息,导致大量用户受到骚扰。开发团队在紧急修复后,笔者对该应用进行了一次深入的安全测试,重点验证越权漏洞(IDOR)是否已被彻底修复,并尝试复现攻击者的可能路径。

首先,对目标应用的 Android 客户端(APK)进行了静态分析,使用 JADX 反编译后发现以下两个关键问题:

  1. 硬编码的第三方 SDK 密钥 代码中直接写入了百度定位 SDK 的 AccountID 和 Secret(例如 setAccountID("110001"))。若这些凭证属于正式账号,攻击者可提取后恶意消耗服务配额。
  2. WebView 暴露 JavaScript 接口 addJavascriptInterface 暴露了名为 BaiduLocAssistant 的 JS 接口,且 setJavaScriptEnabled(true)。恶意网页可借此获取用户的地理位置信息,构成隐私泄露风险。

上述问题已单独报告给开发团队,本文不再赘述。本次测试的重点是 业务接口的越权漏洞,即攻击者是否能够通过修改请求参数,访问或操作其他用户的数据。

动态测试:越权漏洞的验证

环境搭建:

使用 Burp Suite 作为中间人代理,拦截 HTTPS 流量。 在雷电模拟器(Android 9)中安装目标应用,并配置代理指向 Burp。 安装 Burp 的 CA 证书,以便解密 HTTPS 请求。

发现关键接口

登录应用后,在 Burp 的 HTTP History 中观察到大量 API 请求,例如:

GET /api/cards/check/10001 HTTP/2 Host: api.example.com X-Api-User-id: 10001 X-Api-Sign: 3ba5c0d6... X-Api-Ts: 1775279609 Authorization: Bearer eyJhbGciOiJIUzI1NiIs...


其中 X-Api-Sign 是一个长度为 64 的十六进制字符串,疑似 HMAC-SHA256 签名。X-Api-TsUnix 时间戳,X-Api-User-id 为当前用户的数字 ID。显然,服务端使用了动态签名来防止参数被篡改。

尝试越权:修改用户 ID

将请求发送到 Burp Repeater,将 X-Api-User-id 从 10001 改为 10002,保持其他头部不变,点击发送。服务端返回:

{"success":false,"data":null}
HTTP/2 403 Forbidden

说明签名校验失败(因为签名与参数绑定)。将 X-Api-User-id 改回原值后再次发送,仍然返回 403 —— 这是因为 X-Api-Ts 时间戳已经过期(通常只允许几分钟内的请求)。因此,简单的重放攻击无法绕过签名机制。

寻找无签名的接口

笔者猜测,攻击者批量发送情侣邀请可能利用了某个没有签名保护的接口。在 Burp 中使用过滤器筛选出不包含 X-Api-Sign 头的请求,结果发现所有业务 API(卡片、用户信息、学校、通知等)都强制签名,只有静态资源或第三方统计请求没有签名。似乎所有业务接口都被签名覆盖了。

2.5 逆向签名算法的尝试

为了彻底验证,笔者决定逆向客户端的签名生成逻辑。通过浏览器开发者工具注入拦截脚本:

javascript
(function() {
    const original = XMLHttpRequest.prototype.setRequestHeader;
    XMLHttpRequest.prototype.setRequestHeader = function(header, value) {
        if (header === 'X-Api-Sign') {
            console.trace('X-Api-Sign set to:', value);
            debugger;
        }
        return original.call(this, header, value);
    };
})();

触发 API 请求后,代码在 setRequestHeader 处断下。然而,调用栈中的函数名均为混淆后的单字母或随机字符串(如 ie、_0xad24a5),难以直接阅读。尝试使用 BurpSignSaboteur 扩展进行自动分析,但由于签名算法可能使用了非标准实现或强密钥,扩展未能破解。

考虑到时间成本,笔者决定放弃逆向签名,转而从服务端日志寻找线索。

服务端日志分析

开发团队提供了攻击发生时段的服务端访问日志。经过筛选,发现以下异常特征:

发起“情侣邀请”的 API 路径为 /api/relationship/invite,这是一个 POST 请求。
该请求的日志中 没有记录 X-Api-Sign 头,而是通过 Authorization: Bearer <token> 进行身份认证。
大量请求的 target_user_id 呈递增趋势(10001, 10002, 10003...),且 from_user_id 均来自同一个普通用户账号的 token。

所以,攻击者使用的邀请接口根本没有实施签名校验。服务端只验证了 token 的有效性,却未校验 token 中的用户 ID 与请求的 from_user_id 是否一致,也未要求签名。攻击者只需用一个有效 token,遍历 target_user_id,即可向全站用户发送邀请。

而笔者之前测试的卡片、学校等接口均属于另一套 API 版本(带签名),导致误以为全站都有强防护。

漏洞根因与修复

权限校验缺失:服务端在处理 /api/relationship/invite 时,没有从 token 中解析当前用户 ID,而是直接信任客户端传来的 from_user_id。
无签名保护:该接口未要求 X-Api-Sign,使得攻击者可以随意篡改请求参数。
ID 可遍历:用户 ID 为自增数字,攻击者可以轻易枚举所有用户。

修复方案

服务端强制获取操作者身份:从 token 中解析 user_id,忽略客户端传递的 from_user_id。
为敏感接口增加签名校验:使用 HMAC-SHA256 对请求参数+时间戳+用户标识进行签名,防止参数篡改。
使用 UUID 代替自增 ID:对外暴露的资源标识改用随机 UUID,防止遍历。
增加频率限制:对单个用户的邀请操作限制每分钟不超过 10 次。

本次测试揭示了一个常见但隐蔽的安全漏洞:业务接口的访问控制不全。即使客户端代码混淆得再强、签名算法再复杂,如果服务端某个接口“开了后门”,所有防护都形同虚设。

所以,永远不要信任客户端传来的用户 ID,所有操作者的身份必须从服务端会话或 token 中获取。
统一 API 安全策略:所有写操作(POST/PUT/DELETE)都应强制签名或至少做严格的权限校验,避免出现“有的接口有签名,有的没有”的情况。
日志监控:对异常高频访问、参数遍历等行为设置告警,及时发现攻击。
定期渗透测试:不仅要测试客户端,更要覆盖服务端业务逻辑漏洞。

最后,安全是一个整体,任何一个薄弱环节都可能成为攻击者的突破口。