Intent Forwarding

ID

kotlin.android_intent_forwarding

Severity

high

Resource

Access Control

Language

Kotlin

Tags

CWE:926, CWE:940, MASVS:MSTG-PLATFORM-4, NIST.SP.800-53, OWASP:2021:A1, OWASP:2021:A4, PCI-DSS:6.5.10, android

Description

Intent forwarding (also called intent redirection) occurs when an application extracts an Intent from user-controlled sources and passes it to component launch methods without proper validation. This vulnerability allows attackers to access private application components, bypass security controls, and manipulate permissions.

This detector identifies dangerous patterns where:

  • Intent extracted via getParcelableExtra(), parseUri(), or similar methods

  • Intent passed directly to startActivity(), startService(), or sendBroadcast()

  • No validation between extraction and forwarding

Research shows over 80% of Android applications contain this vulnerability pattern.

The terms "Intent Forwarding," "Intent Redirection," and "Intent Manipulation" are often used interchangeably in security literature. Please note that Intent Manipulation is an umbrella term used in OWASP Mobile Top 10 (M1: Improper Platform Usage) that encompasses intent-based vulnerabilities, such as Intent Forwarding/Redirection (CWE-940, CWE-926) covered by this detector, and Intent URI Permission Manipulation (CWE-266) covered by kotlin.android_uri_permission_manipulation.

Rationale

Android’s security model relies on component export controls to protect internal functionality. Intent forwarding bypasses these controls by allowing external applications to leverage an exported "proxy" component to reach unexported components.

Attack Vectors:

  • Component Access: Force the app to launch private activities or services not meant to be publicly accessible

  • URI Permission Manipulation: Inject flags like FLAG_GRANT_PERSISTABLE_URI_PERMISSION to gain persistent access to content providers

  • Data Exfiltration: Access protected files, databases, or credentials through FileProvider exploitation

  • Privilege Escalation: Execute functionality requiring permissions the attacker’s app doesn’t have

Remediation

Validate the destination component before launching:

// SECURE: Validate component destination
val forwardIntent = intent.getParcelableExtra<Intent>("key")
val component = forwardIntent?.resolveActivity(packageManager)

if (component != null &&
    component.packageName == packageName &&
    component.className == "com.example.SafeActivity") {
    startActivity(forwardIntent)
} else {
    Log.w(TAG, "Rejected unsafe intent destination")
}

IntentSanitizer (Android 12+)

Use IntentSanitizer to allowlist safe components:

import androidx.core.content.IntentSanitizer

val unsafeIntent = intent.getParcelableExtra<Intent>("forward")

val sanitized = IntentSanitizer.Builder()
    .allowComponent("com.example.SafeActivity")
    .allowPackage("com.example")
    .allowData("com.example")
    .allowType("text/plain")
    .allowFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
    .build()
    .sanitizeByThrowing(unsafeIntent)

startActivity(sanitized)

Explicit Intents Only

Replace dynamic forwarding with explicit intents:

// SECURE: Create new explicit intent
val safeIntent = Intent(this, TargetActivity::class.java)
safeIntent.putExtra("data", intent.getStringExtra("data"))
startActivity(safeIntent)

Flag Validation

Check for dangerous permission flags:

val forwardIntent = intent.getParcelableExtra<Intent>("intent")
val dangerousFlags = Intent.FLAG_GRANT_READ_URI_PERMISSION or
                     Intent.FLAG_GRANT_WRITE_URI_PERMISSION or
                     Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION

if (forwardIntent != null && (forwardIntent.flags and dangerousFlags) != 0) {
    Log.w(TAG, "Intent contains dangerous URI permission flags")
    return
}

// Additional validation before launching
validateAndStartActivity(forwardIntent)

Avoid Exposing Intent Forwarding

The best solution is to avoid creating proxy components that forward intents:

// VULNERABLE: Proxy pattern
class ProxyActivity : Activity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        val forward = intent.getParcelableExtra<Intent>("forward")
        startActivity(forward)  // Dangerous!
    }
}

// SECURE: Direct implementation
class SafeActivity : Activity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        // Handle the request directly without forwarding
        processRequest(intent.getStringExtra("data"))
    }
}

Vulnerable Patterns

Direct Forwarding

// VULNERABLE: No validation
val forwardIntent = intent.getParcelableExtra<Intent>("key")
startActivity(forwardIntent)

Service Redirection

// VULNERABLE: Service forwarding
val extraIntent = intent.getParcelableExtra<Intent>("service")
startService(extraIntent)

Broadcast Redirection

// VULNERABLE: Broadcast forwarding
val broadcastIntent = intent.getParcelableExtra<Intent>("broadcast")
sendBroadcast(broadcastIntent)

Intent URI Parsing

// VULNERABLE: WebView intent:// handling
webView.shouldOverrideUrlLoading { view, url ->
    if (url.startsWith("intent://")) {
        val intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME)
        startActivity(intent)  // No validation!
    }
}

Weak Caller Verification

// VULNERABLE: getCallingActivity() can be null
val caller = callingActivity
if (caller?.packageName?.startsWith("com.trusted") == true) {
    val forward = intent.getParcelableExtra<Intent>("intent")
    startActivity(forward)  // Attacker provides null caller
}

References