Deep Link与URL Scheme安全:自定义Scheme的劫持风险、验证Deep Link的Host与Path、防止恶意应用注册相同Scheme

大家好,我是你们的老朋友。今天我们来聊聊Android组件安全里一个特别容易踩坑的话题——Deep Link和URL Scheme的安全问题。说实话,我在做安全审计的时候,十有八九都能在App里找到Scheme相关的漏洞。这玩意儿看着简单,但坑是真的多。

一、自定义Scheme的劫持风险

先说说什么是URL Scheme。说白了,它就是App的“身份证号”。比如你打开淘宝,用的就是taobao://这个Scheme。但问题来了——这个“身份证号”是随便写的,谁都可以注册。

⚠️ 核心风险:任何应用都可以声明相同的Scheme。如果恶意应用注册了和你一样的Scheme,用户点击链接时,系统会弹出选择框。用户一旦选错,数据就泄露了。

我在项目中遇到过这样一个案例:某支付App使用了mypay://作为Scheme。结果有个恶意应用也注册了mypay://,而且它的Activity优先级更高。用户点击支付链接时,直接跳到了钓鱼页面。嗯,这就是典型的Scheme劫持。

为什么会这样?因为Android系统在处理Scheme时,遵循的是“先到先得”原则。如果多个App声明了同一个Scheme,系统会弹窗让用户选择。但用户哪分得清哪个是真的?

二、验证Deep Link的Host与Path

那怎么防?我的建议是:永远不要只依赖Scheme做路由。一定要加上Host和Path的验证。

你想想看,如果只是myapp://,那谁都能劫持。但如果是myapp://secure/login,你就能在代码里校验Host是不是你自己的域名,Path是不是合法的路径。

✅ 最佳实践:在AndroidManifest.xml中声明Deep Link时,务必指定android:host和android:pathPattern。

代码示例:

<activity android:name=".DeepLinkActivity">
    <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="www.myapp.com"
            android:pathPrefix="/secure" />
    </intent-filter>
</activity>

注意这里的android:hostandroid:pathPrefix。我习惯把Host设成自己的官网域名,这样恶意应用就算注册了同样的Scheme,也没法伪造你的域名。

但光在Manifest里声明还不够。你还要在代码里做二次校验:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    
    Intent intent = getIntent();
    Uri data = intent.getData();
    
    if (data != null) {
        String host = data.getHost();
        String path = data.getPath();
        
        // 二次校验Host和Path
        if (!"www.myapp.com".equals(host)) {
            // 非法来源,直接关闭
            finish();
            return;
        }
        
        if (!path.startsWith("/secure")) {
            // 路径不合法,拒绝处理
            finish();
            return;
        }
        
        // 安全通过,处理业务逻辑
        handleDeepLink(data);
    }
}
💡 个人经验:我曾经见过一个App只在Manifest里配了Scheme,代码里完全没校验。结果被恶意应用用myapp://evil直接调起,传了个恶意参数进去。从那以后,我所有Deep Link的入口都加了双重校验。

三、防止恶意应用注册相同Scheme

这个问题其实挺棘手的。因为Android系统层面并没有提供“独占Scheme”的机制。你没法阻止别人注册跟你一样的Scheme。

那怎么办?我的思路是:让恶意应用注册了也没用

具体做法有几种:

  • 使用App Links(Android 6.0+):通过Digital Asset Links验证,只有你的App能处理特定域名下的链接。系统不会弹窗让用户选择。
  • 增加签名校验:在Deep Link处理逻辑中,检查调用方的包名和签名。如果不是你自己的App,直接拒绝。
  • 使用加密参数:在Deep Link的URL里带上加密的token,服务端验证token合法性。恶意应用伪造的链接没有合法token。

我个人最推荐的是App Links。它通过HTTPS验证你的域名所有权,系统会信任你的App。恶意应用就算注册了同样的Scheme,系统也不会把它列为候选。

配置App Links需要两步:

  1. 在服务器上放置.well-known/assetlinks.json文件
  2. 在Manifest中声明android:autoVerify="true"
<activity android:name=".DeepLinkActivity">
    <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="www.myapp.com"
            android:pathPrefix="/deep" />
    </intent-filter>
</activity>
⚠️ 注意:App Links只支持HTTPS Scheme。如果你必须使用自定义Scheme,那就只能靠代码层面的校验了。

四、知识体系结构图

下面这张图总结了Deep Link安全的核心逻辑,我画了很久才理清楚这些关系:

Deep Link安全防护体系 Deep Link安全 Scheme劫持风险 Host/Path验证 防止恶意注册 系统弹窗用户误选 钓鱼页面窃取数据 Manifest声明host/path 代码二次校验 App Links域名验证 签名校验+加密token 核心原则:永远不要信任外部输入

五、避坑指南

最后,分享几个我踩过的坑:

  • 不要用纯数字Scheme:比如123://,有些系统版本会把数字Scheme当成电话号码处理,导致链接无法正常跳转。
  • 注意大小写:Scheme是大小写敏感的。MyApp://myapp://是两个不同的Scheme。我建议统一用小写。
  • 别把敏感数据放URL里:Deep Link的URL可能会被系统日志记录,也可能被其他App截获。token、密码这类东西千万别放进去。
💡 我曾经...犯过一个低级错误:在Deep Link的URL里直接传了用户ID。结果测试的时候发现,换个ID就能看到别人的数据。从那以后,我所有Deep Link的参数都做了签名校验。

好了,关于Deep Link和URL Scheme的安全,今天就聊这么多。记住一句话:Scheme是门,Host是锁,Path是钥匙。门谁都能装,但锁和钥匙得你自己配好。

公众号:蓝海资料掘金营,微信deep3321