翻译自 Nicole Borrelli 在 Medium 上的 post 《Android Nesting Intents》。
你的 App 是否在某些情况下对外提供了一个 Service 来执行启动其他 App 的 Activity 组件的回调。比如说,接收的 Intent 请求会以 extra 参数的形式内嵌着的其他 Intent ,而这个 Intent 参数会被用作 startActivity() 调用。
你有没有意识到这种做法会让你的 App 变得脆弱、易攻击?
如下的内容将解释采用这种做法会带来的问题,并提供一个解决方案,来确保你的 App 能以更加安全的方式来实现相同的功能。
带来的问题
我们期望这种类型的交互,会按照示意图的设计来进行:
上述流程图展示了如何将一个用来启动回调 Activity 的 Intent 添加到启动 Service 的 Intent 中,以及该 Service 被用作启动参数提供的 Activity。
Client App 为 ClientCallbackActivity 创建了一个 Intent 实例并将它以 extra 形式添加到了用于其他 Provider App 的 ApiService 的 Intent 属性中。Provider App 处理完该请求后将使用 Client App 提供的 Intent 来启动目标 Activity。
❗️这里需要注意的是 Provider App 调用的 startActivity() 采用的是它自己的 Context,这将带来两个欠佳的后果。
因为 ClientCallbackActivity 将被外部的 Provider App 启动,所以它需要标记为对外可见即 exported,而这将允许 Provider App 以外的任何其他 App 都可以启动它
传递给 ApiService 的嵌套 Intent 可被用来启动 Provider App 的任何 Activity,包括私有的、有潜在敏感信息的、对外不可见的所有 Activity!
为了进一步说明,请思考一下如果调用方 App 提供的内嵌 Intent 并非指向自己的 Activity,相反其指向了 Provider App 内部的私有 Activity,会发生什么?
上述流程图展示了一个精心构造的 Intent 如何被用来启动 Provider App 的 ApiSensitiveActivity,尽管它对外不可见也不应该被其他 App 启动。
因为采用了嵌套 Intent,对于 Provider App 来说很难去防止其他 App 去访问它的私有的、有潜在敏感信息的 Activity 们。而且 Provider App 直接使用了 startActivity() 去处理 Intent,即便ApiSensitiveActivity 未被声明为对外可见仍然可以被启动。
解决方案:PendingIntent
解决方案很简单:Provider App 不要接收 Intent
,而是接收 PendingIntent
。原因在于 Intent 和 PendingIntent 的区别: PendingIntent 总是使用创建它的 Context 进行 Intent 的处理。
上述流程图展示接收 PendingIntent 的话如何使用 App 创建它的 Context 进行处理,这可以阻止访问 Provider App 中非对外可见的 Activity 们。
因为回调提供的是 PendingIntent 对象,当 Provider App 调用它的 send() 时,startActivity() 请求会被当做 Attacker App 这一方进行处理。而 Attacker App 并不具备调用 Provider App 中 ApiSensitiveActivity 的特权,所以系统将会阻止这个启动请求。
对于 Provider App 来说这必然很有好处,但是我们自己的 Client App 呢?那么,如之前所说我们提供的是 PendingIntent 类型,ClientCallbackActivity 改为私有、非公开的话一样可以启动。也就是说,这种做法增强了双方 App 的安全。
如果你熟悉 Notification、Alarm Manager 等相关的 API,你应该知道它们采用 PendingIntent 来激活某些操作以及向 App 发出 alarm 通知。它始终以创建它的 App 身份进行处理,这也是系统选择 PendingIntent 而非一般 Intent 的原因。
结语
无论是对于 Client App 还是对于 Provider App,采用 Intent 这种机制来实现 Activity 启动回调的做法会导致双方 App 的安全隐患。这源自于 Intent 会在调用它的 App 上下文进行处理。而这个上下文造成了 Provider App 中非公开 Activity 被启动的可能性,同时也迫使 Client App 必须对外公开处理回调的 Activity。
相较之下,PendingIntent 会在创建它的上下文进行处理。这将允许 Provider App 可以自由使用、不会对外暴露 Activity,同时可以使得 Client App 指定任意 Activity 来处理回调,包括非公开 Activity。
可以查看 《不安全的 intent 启动》 这篇文章了解 Android 系统如何帮助 App 减少嵌套 Intent 的影响。