【SRC挖洞】Android 深链漏洞全解析:intent-filter 5 种利用模式,从 URI 劫持到账户接管

本文编译自 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 被打穿」的默认姿势。

intent-filter 为什么不是安全边界

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

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 schemeHTTPS 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 查询)的数据流。它会专门标记那些可绕过的校验——比如 containsstartsWith 这种对 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 挖洞中,最快暴露深链问题的六个检查点:

6 项自查清单

  1. 所有带 BROWSABLE 分类的导出 Activity——浏览器和任意 App 都能触达。
  2. URL 参数流入 WebView——找白名单绕过,而不是只看「有没有校验」。
  3. 导出组件上的 getParcelableExtra()——尤其是收到的 Parcelable 是 Intent 对象、又被直接透传给 startActivity(),这是意图重定向风险的具体条件。
  4. WebView 加载 URL 后链路上的 Intent.parseUri() 调用
  5. OAuth 回调 scheme——自定义 scheme 还是已验证的 App Link?OAuth 是否用了 PKCE?
  6. 白名单域名是否仍在有效期内——这是静态分析永远看不到的「域名已过期」。

写在最后

深链漏洞几乎从不孤立存在,能带来账户接管的往往是链式攻击。如果测试只检查单个模式,就会漏掉链。真正危险的发现,是那些要追三到四步才能落地危害的——这正是「污点分析 + 运行时确认」组合拳被设计出来的意义。

对做 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 漏洞挖掘、掌握「一条链打通账户接管」的实战思路,欢迎来白帽攻防录一起交流:白帽攻防录

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值