SRC 挖洞:5000 万安装量的推送 SDK 翻车,移动端供应链漏洞怎么挖

我是 akihi,白帽攻防录讲师,专注 Web / App / PC 客户端漏洞挖掘。腾讯 SRC 连续三年 TOP10、合合 SRC 2026 年度第二。

先说结论:你 App 里最大的攻击面,不是你写的代码。

2026 年 4 月 9 日,微软披露了推送 SDK EngageSDK 里的一个 Intent 重定向漏洞。这个 SDK 被集成进一批合计安装量超过 5000 万的 Android App,其中 3000 万以上是第三方加密货币钱包。

同设备上一个恶意 App,绕过 Android 沙箱,构造出这个 SDK 会当成内部来源的 Intent,就能把宿主 App 的私有数据、认证 token、钱包凭证捞走。微软没报告野外利用,但整条攻击路径是被完整演示出来的,涉及的 App 被从 Google Play 下架。

补丁其实早在 2025 年 11 月 3 日就发了,EngageSDK 5.2.1。但所有基于更老版本构建的 App——数量很多——已经带着这个漏洞给用户发了好几个月的版本。

最难受的地方在这里:那 5000 万安装量背后的开发者,没写过出问题的那行代码。

他们只是集成一个依赖,省下几周工程量。漏洞披露的时候,他们看不见它,现有的工具链检测不到它,除了等 SDK 厂商打补丁、自己再发一版 App,什么也做不了。

现代 App 的代码构成

先看下这张图,它把一个典型移动 App 的内部构成拆开了。

一个典型移动 App 里到底装了什么

最上面那条蓝色的是「你团队写的代码」——业务逻辑、UI、App 特有功能,占比很小。下面五条全白的是第三方 SDK:分析崩溃上报、广告归因、认证推送深链、支付风控、网络存储基础库。

图底那句总结说得很直白:大部分攻击面,存在于你团队没写过、也看不到源码的代码里。

先搞清楚:你的 App 里到底有多少不是你的代码

大多数移动安全方案有个隐含假设——把 App 当成「团队自己写的那份代码」。

这个假设是错的。

按体积算,一个现代移动 App 大部分是第三方代码;按攻击面算,大部分是第三方行为。

过去几年的行业研究反复给出同一个结论:现代应用的绝大多数安全债来自第三方组件和软件供应链,公开估计值远高于一半。移动端更极端——一个典型 App 会集成大量 SDK,第三方代码的占比通常压倒团队自己写的那部分。

光一个 Firebase(Google 的分析、消息推送、认证平台)就出现在绝大多数 Android App 和相当比例的 iOS App 里,而它自己还会拉进来好几个子组件。

再加一个支付 SDK、一个广告网络、一个崩溃上报、一个风控库、一个深链框架、两三个归因厂商——团队自己写的那部分,就变成了堆在一整套第三方机器上的一层薄胶水。

与此同时,供应链攻击在猛涨。过去两年多份行业分析都记录了供应链相关安全事件的同比大幅上升。移动端在这张图里的位置越来越靠前,不是附属品而是主目标——因为一个被攻陷的 SDK,会通过一次更新周期传播到下游每一个 App

这里有个对挖洞的人很实用的推论:如果你在一个 App 里挖到一个 SDK 引入的漏洞,同一个漏洞很可能在成百上千个集成了同版本 SDK 的 App 里同样成立。

一条发现,能验证出几十个厂商的问题。这买卖划算。

SDK 是怎么把攻击面撑大的

起点是一个很简单的事实:你集成的每一个 SDK,都继承了宿主 App 的信任边界。

它在同一个进程里跑,同一个用户 ID,同样的文件系统访问权限,同样的网络权限,同样的密码学材料。SDK 行为不端,App 就不端;SDK 有漏洞,App 就有漏洞。

SDK 继承宿主的信任边界

具体来说,SDK 引入的攻击面反复出现在这么几类模式里。下面每一类我都在银行、金融、电商、消费类 App 的生产版本里见过变体。

1. 导出组件与 Intent 重定向

Android 的进程间通信建立在 Intent 上。一个需要处理推送、深链或回调的 SDK,通常会把 Activity、BroadcastReceiver 或 Service 声明为 exported——也就是设备上别的 App 可以直接给它发 Intent。

有些 SDK 本意是内部使用,但 bug 或配置失误照样把组件导出出去,等于把内部功能敞开给设备上每一个 App。

危险会叠加:当这个「本不该被外部够到」的组件,同时又对输入不做校验时,一个误导出就升级成严重漏洞。

2024 年开源图片裁剪库 canhub 的披露是个教科书案例。这个库的 CropImageActivity 处理外部 Intent 时不校验 customOutputUri 参数,而 BitmapUtils.writeBitmapToUri 也不检查文件扩展名和 compressFormat 是否匹配。

同设备上的恶意 App 用构造好的 URI 启动这个 Activity,指向宿主 App 的私有存储,就能覆盖任意文件——包括 SecureStore.xml 这类凭证文件:

Bundle extras = new Bundle();
Uri.Builder builder = new Uri.Builder();
builder.scheme("file");
builder.authority("");
builder.path("/data/user/0/com.example/cache/DocumentPicker/646fe846-....jpeg");
Uri uri = builder.build();

extras.putParcelable("CROP_IMAGE_EXTRA_SOURCE", uri);

builder.path("/data/user/0/com.example/shared_prefs/SecureStore.xml");
Uri uri2 = builder.build();

CropImageOptions opt = new CropImageOptions();
if (uri2 != null) {
    opt.customOutputUri = uri2;
}
extras.putParcelable("CROP_IMAGE_EXTRA_OPTIONS", opt);

Intent intent = new Intent();
intent.putExtra("CROP_IMAGE_EXTRA_BUNDLE", extras);
intent.setClassName("com.example", "com.canhub.cropper.CropImageActivity");
mStartForResult.launch(intent);

根因还要再深一层,落在 BitmapUtils.writeBitmapToUri 里。它直接接受攻击者控制的 Uri 并往里写,既不校验目标路径,也不校验扩展名,更不管会不会覆盖已有文件:

fun writeBitmapToUri(
    context: Context,
    bitmap: Bitmap,
    compressFormat: CompressFormat,
    compressQuality: Int,
    customOutputUri: Uri?,
): Uri {
    val newUri = customOutputUri ?: buildUri(context, compressFormat)
    return context.contentResolver.openOutputStream(newUri, WRITE_AND_TRUNCATE).use {
        bitmap.compress(compressFormat, compressQuality, it)
        newUri
    }
}

加一个「扩展名必须匹配 compressFormat」的检查,再加一个「不许覆盖工作目录之外已有文件」的保护,整类攻击在库这一层就堵死了。

这个漏洞是 Oversecured 团队一位研究员在一个主流加密货币 App 里发现并上报上游的。任何导出了这个 Activity 的 App——不管是有意还是手滑——都继承了这个问题。

EngageSDK 那个案例是同一个模式的另一个版本:SDK 的 MTCommonActivity 处理外部 Intent 时不验证来源,恶意 App 借此拿到 Content Provider 权限,进而暴露宿主 App 的私有存储。

这类 bug 一点都不少见。

2. 广告和分析 SDK 里配置错误的 WebView

广告 SDK、站内浏览器 SDK、富媒体分析 SDK 几乎都依赖内置 WebView。这个 WebView 会变成宿主 App 进程里的一个特权执行环境。

如果 SDK 开了 JavaScript、通过 JS 桥暴露了原生方法、允许从 file URL 访问文件、或者放开了对地理位置的任意 origin 访问——那么 SDK 加载的任何远程内容都能提权到原生 App。

后果从会话 token 外泄,一直到在 App 沙箱内执行任意代码。有记录的 Android WebView 配置错误攻击表明:一个从 SDK 生成的深链直接传进 WebView 的 URL 参数,就足够泄漏认证 token 或执行桥方法,除了点一下链接,不需要用户做任何事。

3. 不安全的深链处理器

SDK 经常自带 URI scheme 和 intent filter,用来处理营销链接、密码重置、OAuth 回调。

每加一个 scheme,就是往 App 里开一个新的不可信入口。如果处理器不校验来源、不做重放保护、不清洗参数,那么任何能投递链接的人(钓鱼、恶意 App、被黑的网站)都能在 App 里以用户身份触发敏感操作。

4. SDK 代码里的硬编码密钥

云厂商密钥、分析 token、内置 LLM API 的凭证,非常稳定地出现在 SDK 的封装类里。

反编译 APK 的人都能看见,于是那些「只给这个 SDK 用」的后端资源,实际上对整个互联网开放。

5. 不安全的本地存储

SDK 习惯性地把用户标识、设备指纹、会话 token、埋点数据丢进 SharedPreferences 或没加密的 SQLite。

在已 root 设备上做取证恢复,这些东西反复被翻出来——哪怕宿主 App 自己的存储是老老实实加密过的。

6. 权限过度申请与 PII 过度采集

SDK 声明的权限经常比它文档里的功能需要的更宽:定位、相机、麦克风、外部存储、读取已安装应用列表。这些权限会合并进宿主 App 的清单文件,把 App 整体的权限面撑大。

分析和归因 SDK 还反复被抓到采集超出宿主 App 隐私政策披露范围的设备标识、精确位置和行为数据——X-Mode 这类位置数据厂商被美国 FTC 执法,就是这个原因。

7. 支付 SDK 的密码学缺陷

支付和钱包 SDK 自成一类,因为任何一个缺陷——弱 TLS 配置、不安全的密钥存储、写错的证书固定、能被重放的 nonce——都可能暴露持卡人数据或支付 token。

按 PCI DSS 4.0.1 的移动端指引,第三方支付 SDK 必须默认当作不可信处理,它们中介的整条支付流程都落在 App 的合规范围内。

四起标志性事件,看下这个坑有多深

把话说具体一点。下面这些事件定义了现在的 SDK 威胁图景——没有一起是团队自己写错代码造成的,全都影响了几千到几百万个「只是集成了一个依赖」的 App。

2024-2026 标志性 SDK 安全事件

  • MavenGate(2024.01)——劫持被废弃的 Maven / Gradle 构件,Oversecured 向 200+ 组织披露,其中包含 Google、Meta、Signal、Amazon。
  • CocoaPods 供应链缺陷(2024.10)——CVE-2024-38366,iOS 依赖管理器的邮箱验证流程可被操纵,进而篡改包。
  • SparkCat(2025.02)——恶意 SDK 窃取钱包助记词,Play Store 上下载量 24 万+,App Store 里也有。
  • EngageSDK(2026.04)——导出组件的 Intent 重定向,5000 万+ 安装量受影响。

这不是几个孤立事件,这是规律本身。

为什么传统 SAST 看不见 SDK 漏洞

应用安全测试方案通常就败在这里:它们围绕的工具,分析不了 App 里最要紧的那部分。

两种 SAST 路径实际能看到的东西

上图左边是只看源码的 SAST:它读你的仓库,能看见「你的代码」,但下面三层 SDK 全是灰的——not visible,一个字都看不到。

右边是分析编译产物的路子:反编译 APK / AAB 之后,分析、广告、认证、支付、推送、框架库全都能进去。图底那句就是结论:只看源码的 SAST 检查不了第三方 SDK,因为你没有它的源码;反编译编译产物是看清完整攻击面的唯一办法。

SAST(静态应用安全测试)是为源码设计的。你把工具指向代码仓库,它解析你的代码,标出问题。

这个模型对「团队自己写的那一小块」有效。但对 SDK 那一大块直接失效——因为你没有第三方 SDK 的源码。你手上是一个编译好的 .aar.jar.framework.xcframework。只看源码的 SAST 根本进不去。

开源工具比如 MobSF 确实能扫编译后的 APK,比纯源码 SAST 进了一大步,但它们靠模式匹配而不是数据流分析。

它能告诉你某个可疑的类存在,却没法追踪攻击者控制的数据是不是真的穿过 SDK 内部逻辑到达了可利用的 sink。

实际用起来就是两种失败模式:要么漏报(追不动数据流,真漏洞没报出来),要么噪声高到输出没法用。

说到底,正确的问法不是「这个 App 的源码有没有安全问题」,而是「用户实际装的那个东西,是不是真的能被利用」。

要回答这个问题,只能去分析用户实际安装的那个产物——APK、AAB 或 IPA,把它反编译回可读形式,然后在整个反编译代码库上跑完整的数据流和污点分析,把你自己写的和每一个 SDK 写的代码一起算进去。

有一点值得单独说:混淆不构成障碍。用 ProGuard 和 R8 构建的 App 照样能扫。非混淆版本在「区分自有代码和第三方代码」上会清晰一点,但混淆本身不会让分析失明。

怎么把 SDK 安全管起来

补上这个缺口不需要把整个应用安全方案推倒重来,需要的是四个动作的转变。

SDK 安全治理四件事

1. 维护移动端 SBOM。每个 App 的每个发布版本,都产出一份软件物料清单,列出每一个 SDK、每一个版本、每一个传递依赖。

没有它,你就没法响应 SDK 披露事件。下一个 EngageSDK 出现时,你需要在几分钟内知道「我是否受影响」,而不是几天。

2. 扫编译产物,不是只扫源码。SAST 必须能分析真正发给用户的 APK、AAB 或 IPA。只看源码的分析在原理上就看不见 SDK 代码。如果你的工具不接受编译产物作为输入,这是第一个要补的缺口。

3. 盯住 SDK 的 CVE 和更新节奏。给你集成的每一个 SDK 订阅安全公告。把安装量前十的 SDK 排进明确的更新节奏:至少按季度,支付和认证类按月。

对商业 SDK,厂商补丁响应慢本身就是采购信号。很多广泛使用的 SDK 是开源的,没有厂商关系可靠——这时信号变成了「项目是否还在积极维护」,一个停滞的仓库应该直接驱动「fork、替换,或者自己提修复」的决定。

4. 审权限,也审运行时行为。集成任何新 SDK 之前,把它声明的每一个权限、连接的每一个网络端点、采集的每一项数据、导出的每一个组件都列出来。如果 SDK 厂商拿不出这份文档,这本身就是一条发现。

集成之后,用 DAST 验证它的运行时行为——它声明了什么和它实际做了什么,往往不是一回事

总结就是

做移动端挖洞,第三方 SDK 值得单独排一块时间。理由很实际:

它在上游。你打穿一个 SDK,等于同时验证了下游一批 App;反过来,一个 App 打不通的入口,换个集成了同版本 SDK 的目标可能就是通的。

它容易被忽略。厂商自己的安全团队盯的是自研代码,SDK 那部分既不在他们的代码评审范围里,也不在他们工具链的可见范围内。

而它又确实有货。前面那些模式——导出的组件不校验来源、WebView 桥暴露凭证、深链参数不清洗、硬编码密钥——都不是什么新颖手法,但它们在 SDK 层的出现频率,比在自研代码层高得多。

原因也简单:SDK 的开发者要的是「接进来就能用」,而安全校验恰恰是那个会挡住「接进来就能用」的东西。

所以下次开一个 App,别急着翻它的业务代码。先看看它集成了什么。加油。

参考来源:Oversecured Blog — SDK security in mobile apps: the attack surface your team didn't write
https://oversecured.com/blog/blog-sdk-security-mobile-apps

白帽攻防录

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值