本文编译自 Oversecured 博客《Android deep link vulnerabilities: how intent filters lead to account takeover》,结合 SRC 挖洞实战重新梳理,并补充了自查清单与实战视角。原文链接见文末。
如果你在做企业 SRC 或者移动端众测,一定对 深链(Deep Link) 不陌生。几乎每一款主流 App 都会用深链做跳转、分享、拉活、OAuth 回调,但绝大多数开发团队根本不会去想它背后的安全隐患。
结果就是:一条链接,就能带来账户接管、Token 窃取、数据外传。更关键的是,这类漏洞往往「看着代码是对的」,静态扫描和人工一眼扫过去很容易漏掉,属于典型的高危却易被忽视的攻击面。
这篇文章把生产环境里最常见的 5 种深链漏洞模式、一条完整的攻击链,以及静态分析(SAST)和动态测试(DAST)分别能抓到什么,一次性讲清楚。
一、intent-filter 为什么不是安全边界
在 Android 上,深链通过 Manifest 里的 <intent-filter> 声明,它告诉系统「哪个组件应该处理哪个 URI scheme / host / path」。PackageManager 会把进来的 Intent 拿去和所有已安装 App 注册的 filter 逐一匹配。
这里有一个很多人忽略的事实:intent-filter 本质上只做「路由匹配」,从来不是「访问控制」。即使你的 filter 配置得完全正确,攻击者也能通过 intent selector 直接绕过 filter 匹配,强行指定目标组件。
例如下面这条 intent:// URL,就通过 selector 直接点名 InternalActivity:
intent://open#Intent;scheme=myapp;component=com.victim/.InternalActivity;action=android.intent.action.SEND;SEL;action=android.intent.action.VIEW;end
当这个 URI 解析出的 Intent 被传给 startActivity() 时,InternalActivity 会被以一个「意外的 SEND action」拉起——尽管 filter 匹配用的是 VIEW。注意:Chrome 会拦截 intent selector,但大多数 WebView 实现和 Firefox 并不会。
真正把「普通 filter」变成「安全问题」的,是下面这些常见配置:
android:exported="true"却没有权限校验:任意第三方 App 都能向该组件发 Intent。- 自定义 URI scheme 没有验证:
myapp://这种 scheme,谁先装谁就能抢到。
exported=true + BROWSABLE 分类 + 无参数校验,这三者的组合,是我们在生产 App 里见过最多的一种配置。它是「让深链能用起来」的默认姿势,也是「让 App 被打穿」的默认姿势。

二、5 种真正能打出危害的漏洞模式

1. 自定义 scheme 劫持(Custom scheme hijacking)
当一个 App 注册了自定义 URI scheme(比如 myapp://),系统层面并不会强制「只有你的 App 能处理它」,任何已安装 App 都能声明同一个 scheme。
恶意 App 可以用更高的 priority(最高 999)抢注册,或者系统弹出「选择打开方式」让用户误点。到了 Android 12+,未验证的链接默认走浏览器,风险反而转移到了 OAuth 流程:用户授权后,授权码通过 myapp://oauth?code=xyz 回传,恶意 App 一旦截获这个回调,就可能拿 code 去换 access token。
注意:实现了 PKCE 的 OAuth 能显著降低这个风险(恶意 App 拿不到 code verifier)。但别忘了——只要是走 URI 回调的敏感 token,都可能被同样方式截获:密码重置 token、魔法登录链接、任何作为深链参数传进来的值,统统适用。
2. 深链开放重定向(Open redirect via deep links)
深链接收一个 URL 参数再交给 WebView,开发团队通常会加一层 host 校验,比如 startsWith("https://trusted.com") 或 contains("trusted.com"),然后就觉得「没事了」。
两种校验都不安全,用下面这类 payload 就能轻松绕过:
https://trusted.com@attacker.com/ https://trusted.com.attacker.com/steal
我们在一次对某大型亚洲超级 App 的现场扫描里,就演示过这个模式:它的路由库用了 startsWith 校验,被三种不同 payload 变体直接绕过。
3. WebView 意图重定向(Intent redirection via WebView)
Android 支持在 WebView 里加载 intent:// URL。如果 App 用 Intent.parseUri() 解析深链 URL,再把结果传给 startActivity(),攻击者就能触达内部的、非导出的组件——哪怕这些组件被声明为 private。
它甚至不需要深链作为入口:只要用户在 WebView 加载的页面上点了一个 intent:// 链接,就会触发,不管用户是经深链、恶意跳转还是正常站内浏览来到这一页。我们把它叫做「受限意图重定向」,因为某些 flag 和数据类型会被拦截,但它仍然能拉起任意的内部 Activity,从而打通认证绕过和访问受保护页面的路径。
在某个大型亚洲超级 App 的案例里,攻击链是这样的:用户点击 WebView 里的 intent:// 链接 → 调用 Intent.parseUri() → 拉起内部非导出 Activity → 以用户身份执行敏感操作(比如外传数据)。

4. 完整意图重定向(Full intent redirection)
更危险的变体:一个导出的 Activity 从 extras 里取出一个嵌套的 Intent 对象,然后直接透传给 startActivity()。这是完整的「透传」。
攻击者构造一个嵌套 Intent,用任意 flag 和 data 指向任意内部组件,甚至带上 FLAG_GRANT_READ_URI_PERMISSION 去访问非导出的 Content Provider。文件外传链基本都是这么搭出来的。
5. 过期白名单域名接管(Domain takeover via expired whitelist)
这是最隐蔽、也最难被任何自动化工具抓到的一种。
App 在 Android 侧有一个 URL 白名单,这个白名单本身没有被绕过——问题出在白名单里躺着一个已经过期的域名,攻击者把它注册并接管了。因为域名在白名单里,Android 侧的校验「合法通过」,App 会去加载攻击者控制的 URL,并带上 Authorization 头,攻击者直接拿到一个有效 auth token,完整账户接管。
难点在于:这段 Android 代码看起来完全正确,白名单校验也正常工作,漏洞点是「某个受信任域名在现实中已经不再受信任了」。静态分析不可能知道一个域名已经过期;动态测试能在运行时标记异常行为,但要识别过期域名,还得把域名注册状态监控和安全扫描结合起来。
三、自定义 scheme vs App Links:安全差异
| 维度 | 自定义 URI scheme | HTTPS App Links |
|---|---|---|
| 劫持风险 | 高,任意 App 都能声明同一 scheme | 低,绑定域名所有权 |
| 系统验证 | 无 | 通过域名上的 assetlinks.json |
| OAuth 安全 | 不安全,code 可被截获 | 配置正确时安全 |
深链的配置错误,直接对应 OWASP Mobile Top 10 的 M3(不安全认证) 和 M8(基于不可信输入的安全决策)。
App Links 能消除 scheme 劫持,但自己也有绕过的坑:assetlinks.json 配错、SHA-256 指纹写错、或者验证静默失败后回落到浏览器处理。OAuth 流程里,永远用 App Links,不要用自定义 scheme。
四、SAST 能抓到什么,抓不到什么
SAST 能抓的:从深链源(URI 参数、Intent extras)到危险 sink(WebView 加载 URL、startActivity()、文件访问、SQL 查询)的数据流。它会专门标记那些可绕过的校验——比如 contains 和 startsWith 这种对 host 校验不充分的做法。当路由逻辑在静态代码路径里可见时,SAST 也能追内部深链导航链,跨非导出 Activity 追踪数据流。
SAST 抓不到的:
- 过期或被接管的域名:代码里域名看着有效,漏洞在它现实的注册状态里。
- 后端问题:比如需要服务端跳转的 token 泄漏(两跳 token 泄漏要靠追服务端重定向才能还原)。
- 运行时消歧:到底哪个 App 处理某个 scheme,取决于「此刻装了哪些 App」。
五、DAST 补上了什么
DAST 在运行时工作。对深链来说,它会从 Manifest 声明重建攻击面,用定向 payload 去 fuzz 参数,并确认绕过是否真的成立——不是「这里可能有洞」,而是「这条漏洞真的打出了危害」,并配上录屏和完整堆栈。
这个差别很关键:代码里看着安全的白名单校验,遇到特定 payload 模式常常会失效,DAST 就是靠「跑一遍」把这种模式找出来。当前局限是:后端问题(需要特定后端条件 + App 问题组合的情况)仍难以自动检测。
六、附赠:HierarchicalUri 绕过
这个攻击针对 WebView 里的 host 校验,适用于「接收攻击者可控 Uri 对象」的 App。
android.net.Uri 是抽象类,它的一个子类 android.net.Uri$HierarchicalUri 可以通过 Java 反射构造出一个「getHost() 返回 legitimate.com,但传给 webView.loadUrl() 时却指向 attacker.com」的 URI。这个攻击需要同一设备上的第三方 App 能把构造好的 Uri 对象传给受害 App。
从 API 28(Android 9)起,内部接口的使用受限(不过 RestrictionBypass 这类工具能绕过)。这类漏洞主要影响 Android 12 之前的版本,对现代设备影响小,但如果你要挖还支持 Android 11 及以下的 App,仍然值得了解。
七、给你的一份「6 项快速自查清单」
在 Android 渗透测试 / SRC 挖洞中,最快暴露深链问题的六个检查点:

- 所有带
BROWSABLE分类的导出 Activity——浏览器和任意 App 都能触达。 - URL 参数流入 WebView——找白名单绕过,而不是只看「有没有校验」。
- 导出组件上的
getParcelableExtra()——尤其是收到的 Parcelable 是 Intent 对象、又被直接透传给startActivity(),这是意图重定向风险的具体条件。 - WebView 加载 URL 后链路上的
Intent.parseUri()调用。 - OAuth 回调 scheme——自定义 scheme 还是已验证的 App Link?OAuth 是否用了 PKCE?
- 白名单域名是否仍在有效期内——这是静态分析永远看不到的「域名已过期」。
写在最后
深链漏洞几乎从不孤立存在,能带来账户接管的往往是链式攻击。如果测试只检查单个模式,就会漏掉链。真正危险的发现,是那些要追三到四步才能落地危害的——这正是「污点分析 + 运行时确认」组合拳被设计出来的意义。
对做 SRC 的人来说,深链 + WebView + OAuth 回调,是一个投入产出比极高的攻击面:代码逻辑往往「看起来正确」,厂商验收时容易被降级或忽略,但只要链打通,就是账户接管级别的严重漏洞。
参考来源:Oversecured Blog — Android deep link vulnerabilities: how intent filters lead to account takeover
https://oversecured.com/blog/android-deep-link-vulnerabilities
关于作者 · 白帽攻防录
我是 akihi,一名甲方网络安全工程师,也是 白帽攻防录 SRC 培训的主讲讲师。专注 Web / App / PC 客户端漏洞挖掘,有多年实战经验,腾讯 SRC 连续三年 TOP10、合合 SRC 2025 年度第一,单个漏洞最高 4w+。
如果你也想系统学习 SRC 漏洞挖掘、掌握「一条链打通账户接管」的实战思路,欢迎来白帽攻防录一起交流:

404

被折叠的 条评论
为什么被折叠?



