Privilege Escalation Vulnerability

ID

java.android_privilege_escalation_attack

Severity

critical

Resource

Access Control

Language

Java

Tags

CWE:250, CWE:269, MASVS:MSTG-PLATFORM-4, OWASP:2021:A1, android, privilege-escalation

Description

Android privilege escalation occurs when applications gain unauthorized access to resources, permissions, or capabilities beyond what was explicitly granted. This bypasses Android’s security sandbox model, which assigns different user IDs (UIDs) to each app for privilege separation.

Rationale

Privilege escalation vulnerability enables attackers to:

  • Bypass runtime permission checks

  • Execute operations requiring dangerous permissions without user consent

  • Gain access to protected data (contacts, SMS, photos, files)

  • Hijack application components through PendingIntent manipulation

Applications may unintentionally:

  • Grant URI permissions to untrusted apps

  • Allow PendingIntent hijacking for privilege gain

  • Break app sandboxing through shared user IDs

This vulnerability is related to Improper Privilege Management (CWE-269) and Execution with Unnecessary Privileges (CWE-250).

This detector identifies privilege escalation across multiple attack vectors:

  • PendingIntent with FLAG_MUTABLE:

Implicit Intent + FLAG_MUTABLE allows PendingIntent hijacking:

// VULNERABLE: Implicit Intent with FLAG_MUTABLE
val intent = Intent("com.example.ACTION")  // No component set
val pendingIntent = PendingIntent.getActivity(// FLAW
    context, 0, intent,
    PendingIntent.FLAG_MUTABLE
)

// Used in notification
notificationManager.notify(1, notification)

A malicious app can then intercept and modify the PendingIntent:

// Attacker fills in missing component to access private data
notification.contentIntent.send(
    context, 0,
    Intent().apply {
        setClassName("com.victim.app", "com.victim.PrivateActivity")
    }
)
  • setResult with getIntent():

Returning incoming Intent via setResult() grants URI permissions:

// VULNERABLE: Reuses incoming Intent
class ResultActivity : AppCompatActivity() {
    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        setResult(RESULT_OK, intent)  // getIntent() reused// FLAW
        finish()
    }
}

Combined with ContentProvider:

<provider
    android:name=".ContactsProvider"
    android:grantUriPermissions="true" /><!-- FLAW -->

An attacker then can gain access to ContentProvider data:

val attack = Intent().apply {
    addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
startActivityForResult(attack, REQUEST_CODE)
// Result contains URI permission to contacts
  • Shared User ID

Breaking app sandboxing through sharedUserId is a recipe to disaster:

<!-- VULNERABLE: Shares UID with other apps -->
<manifest
    package="com.example.app"
    android:sharedUserId="com.example.shared"><!-- FLAW -->
</manifest>

Apps with same sharedUserId can access each other’s data and run in same process.

Remediation

  1. Use FLAG_IMMUTABLE for PendingIntent

    Always use explicit Intents with FLAG_IMMUTABLE:

    // SECURE: Explicit Intent + FLAG_IMMUTABLE
    val intent = Intent(context, TargetActivity::class.java)
    intent.setPackage(packageName)
    
    val pendingIntent = PendingIntent.getActivity(
        context, 0, intent,
        PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_ONE_SHOT
    )
  2. Create New Intent for setResult

    Never reuse incoming Intent:

    // SECURE: Create new Intent with only safe data
    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        val result = Intent()
        result.putExtra("safe_data", sanitizedValue)
        setResult(RESULT_OK, result)
        finish()
    }
  3. Limit grantUriPermissions

    Use path-specific grants instead of blanket permission:

    <!-- SECURE: Limit to specific paths -->
    <provider
        android:name=".ContentProvider"
        android:grantUriPermissions="false">
        <grant-uri-permission android:pathPrefix="/public/" />
    </provider>
  4. Remove sharedUserId

    Migrate to alternative approaches:

    <!-- DON'T: Shared UID breaks sandboxing -->
    <manifest android:sharedUserId="com.example.shared" />
    
    <!-- DO: Use ContentProvider with signature permission -->
    <provider
        android:name=".SharedDataProvider"
        android:permission="com.example.SIGNATURE_PERM" />
    
    <permission
        android:name="com.example.SIGNATURE_PERM"
        android:protectionLevel="signature" />

References