OWASP category: MASVS-PLATFORM: Platform Interaction
Overview
An intent redirection occurs when an attacker can partly or fully control the contents of an intent used to launch a new component in the context of a vulnerable app.
The intent used to launch the new component can be supplied in several ways,
most commonly either as a serialized intent in an extras
field, or marshaled
to a string and parsed. Partial control of parameters can also lead to the same
result.
Impact
The impact can vary. An attacker might execute internal features in the vulnerable app, or it might access private components like unexported ContentProvider objects.
Mitigations
In general, don't expose features related to redirecting nested intents. In cases where it's unavoidable, apply the following mitigation methods:
- Properly sanitize the bundled information. It's important to remember to check
or clear flags (
FLAG_GRANT_READ_URI_PERMISSION, FLAG_GRANT_WRITE_URI_PERMISSION, FLAG_GRANT_PERSISTABLE_URI_PERMISSION, and FLAG_GRANT_PREFIX_URI_PERMISSION
), and to check where the intent is being redirected.IntentSanitizer
can help with this process. - Use
PendingIntent
objects. This prevents your component from being exported and makes the target action intent immutable.
Apps can check where an intent is being redirected using methods such as
ResolveActivity
:
Kotlin
val intent = getIntent()
// Get the component name of the nested intent.
val forward = intent.getParcelableExtra<Parcelable>("key") as Intent
val name: ComponentName = forward.resolveActivity(packageManager)
// Check that the package name and class name contain the expected values.
if (name.packagename == "safe_package" && name.className == "safe_class") {
// Redirect the nested intent.
startActivity(forward)
}
Java
Intent intent = getIntent()
// Get the component name of the nested intent.
Intent forward = (Intent) intent.getParcelableExtra("key");
ComponentName name = forward.resolveActivity(getPackageManager());
// Check that the package name and class name contain the expected values.
if (name.getPackageName().equals("safe_package") &&
name.getClassName().equals("safe_class")) {
// Redirect the nested intent.
startActivity(forward);
}
Apps can use IntentSanitizer
using logic similar to the
following:
Kotlin
val intent = IntentSanitizer.Builder()
.allowComponent("com.example.ActivityA")
.allowData("com.example")
.allowType("text/plain")
.build()
.sanitizeByThrowing(intent)
Java
Intent intent = new IntentSanitizer.Builder()
.allowComponent("com.example.ActivityA")
.allowData("com.example")
.allowType("text/plain")
.build()
.sanitizeByThrowing(intent);
Common mistakes
- Checking if
getCallingActivity()
returns a non-null value. Malicious apps can supply a null value for this function. - Assuming that
checkCallingPermission()
works in all contexts, or that the method throws an exception when it is actually returning an integer.
Debugging features
For apps that target Android 12 (API level 31) or higher, you can enable a debugging feature that, in some cases, helps you detect whether your app is performing an unsafe launch of an intent.
If your app performs both of the following actions, the system detects an
unsafe intent launch, and a StrictMode
violation occurs:
- Your app unparcels a nested intent from the extras of a delivered intent.
- Your app immediately starts an app component using that nested intent, such as
passing the intent into
startActivity()
,startService()
, orbindService()
.