Skip to content

MASTG-DEMO-0147: Uses of Insecure PendingIntent

Download MASTG-DEMO-0147 APK Open MASTG-DEMO-0147 Folder Build MASTG-DEMO-0147 APK

Sample

This sample demonstrates insecure uses of PendingIntent in Android, including mutable PendingIntents and implicit base intents that could be vulnerable to hijacking by malicious applications.

The code shows four different scenarios:

  1. Mutable PendingIntent with implicit intent: Creates a PendingIntent using FLAG_UPDATE_CURRENT without FLAG_IMMUTABLE, combined with an implicit intent (only specifying ACTION_VIEW). This is the most dangerous combination as it allows a malicious app to intercept and modify the intent.

  2. Explicit FLAG_MUTABLE: Creates a PendingIntent with FLAG_MUTABLE explicitly set. While the base intent is explicit (targeting a specific class), the mutable flag allows modification of intent fields.

  3. Broadcast with implicit intent and no flags: Creates a broadcast PendingIntent with an implicit intent (custom action string) and no flags. On API levels below 31, this defaults to mutable.

  4. Secure PendingIntent (PASS): Creates a PendingIntent with FLAG_IMMUTABLE and an explicit intent that specifies both the target class and package. This is the recommended secure approach.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
package org.owasp.mastestapp

import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.os.Build

class MastgTest(private val context: Context) {

    // SUMMARY: This sample demonstrates insecure uses of PendingIntent in Android, including mutable PendingIntents and implicit base intents.

    fun mastgTest(): String {
        val results = StringBuilder()

        // FAIL: [MASTG-TEST-0313] Mutable PendingIntent with implicit intent - vulnerable to hijacking
        val implicitIntent = Intent(Intent.ACTION_VIEW)
        val mutablePendingIntent = PendingIntent.getActivity(
            context,
            0,
            implicitIntent,
            PendingIntent.FLAG_UPDATE_CURRENT  // Missing FLAG_IMMUTABLE
        )
        results.append("Created mutable PendingIntent with implicit intent\n")

        // FAIL: [MASTG-TEST-0313] Explicit FLAG_MUTABLE used without justification
        val explicitMutableIntent = Intent(context, MastgTest::class.java)
        val explicitMutablePendingIntent = PendingIntent.getService(
            context,
            1,
            explicitMutableIntent,
            PendingIntent.FLAG_MUTABLE
        )
        results.append("Created explicitly mutable PendingIntent\n")

        // FAIL: [MASTG-TEST-0313] Broadcast with implicit intent
        val broadcastIntent = Intent("com.example.CUSTOM_ACTION")
        val broadcastPendingIntent = PendingIntent.getBroadcast(
            context,
            2,
            broadcastIntent,
            0  // No flags - mutable by default on API < 31
        )
        results.append("Created broadcast PendingIntent with implicit intent\n")

        // PASS: [MASTG-TEST-0313] Secure PendingIntent with FLAG_IMMUTABLE and explicit intent
        val secureIntent = Intent(context, MastgTest::class.java).apply {
            setPackage(context.packageName)
        }
        val securePendingIntent = PendingIntent.getActivity(
            context,
            3,
            secureIntent,
            PendingIntent.FLAG_IMMUTABLE
        )
        results.append("Created secure PendingIntent with FLAG_IMMUTABLE\n")

        return results.toString()
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package org.owasp.mastestapp;

import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import androidx.core.view.accessibility.AccessibilityEventCompat;
import kotlin.Metadata;
import kotlin.jvm.internal.Intrinsics;

/* JADX INFO: compiled from: MastgTest.kt */
/* JADX INFO: loaded from: classes3.dex */
@Metadata(d1 = {"\u0000\u0018\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0000\n\u0002\u0018\u0002\n\u0002\b\u0003\n\u0002\u0010\u000e\n\u0000\b\u0007\u0018\u00002\u00020\u0001B\u000f\u0012\u0006\u0010\u0002\u001a\u00020\u0003¢\u0006\u0004\b\u0004\u0010\u0005J\u0006\u0010\u0006\u001a\u00020\u0007R\u000e\u0010\u0002\u001a\u00020\u0003X\u0082\u0004¢\u0006\u0002\n\u0000¨\u0006\b"}, d2 = {"Lorg/owasp/mastestapp/MastgTest;", "", "context", "Landroid/content/Context;", "<init>", "(Landroid/content/Context;)V", "mastgTest", "", "app_debug"}, k = 1, mv = {2, 0, 0}, xi = 48)
public final class MastgTest {
    public static final int $stable = 8;
    private final Context context;

    public MastgTest(Context context) {
        Intrinsics.checkNotNullParameter(context, "context");
        this.context = context;
    }

    public final String mastgTest() {
        StringBuilder results = new StringBuilder();
        Intent implicitIntent = new Intent("android.intent.action.VIEW");
        PendingIntent.getActivity(this.context, 0, implicitIntent, 134217728);
        results.append("Created mutable PendingIntent with implicit intent\n");
        Intent explicitMutableIntent = new Intent(this.context, (Class<?>) MastgTest.class);
        PendingIntent.getService(this.context, 1, explicitMutableIntent, 33554432);
        results.append("Created explicitly mutable PendingIntent\n");
        Intent broadcastIntent = new Intent("com.example.CUSTOM_ACTION");
        PendingIntent.getBroadcast(this.context, 2, broadcastIntent, 0);
        results.append("Created broadcast PendingIntent with implicit intent\n");
        Intent $this$mastgTest_u24lambda_u240 = new Intent(this.context, (Class<?>) MastgTest.class);
        $this$mastgTest_u24lambda_u240.setPackage(this.context.getPackageName());
        PendingIntent.getActivity(this.context, 3, $this$mastgTest_u24lambda_u240, AccessibilityEventCompat.TYPE_VIEW_TARGETED_BY_SCROLL);
        results.append("Created secure PendingIntent with FLAG_IMMUTABLE\n");
        String string = results.toString();
        Intrinsics.checkNotNullExpressionValue(string, "toString(...)");
        return string;
    }
}

Steps

Run the semgrep rule against the reversed Java code to identify all PendingIntent creation calls.

../../../../rules/mastg-android-pendingintent-mutable.yml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
rules:
  - id: mastg-android-pendingintent-mutable
    severity: WARNING
    languages:
      - java
    metadata:
      summary: Detects PendingIntent creation that may be vulnerable due to mutability.
    message: "[MASVS-PLATFORM-1] PendingIntent created without FLAG_IMMUTABLE or with FLAG_MUTABLE may be vulnerable to hijacking."
    pattern-either:
      - patterns:
          - pattern: PendingIntent.getActivity($CTX, $REQ, $INTENT, $FLAGS)
          - metavariable-regex:
              metavariable: $FLAGS
              regex: ^(0|134217728|33554432|0x08000000|0x02000000|PendingIntent\.FLAG_UPDATE_CURRENT|PendingIntent\.FLAG_MUTABLE)$
      - patterns:
          - pattern: PendingIntent.getActivities($CTX, $REQ, $INTENTS, $FLAGS)
          - metavariable-regex:
              metavariable: $FLAGS
              regex: ^(0|134217728|33554432|0x08000000|0x02000000|PendingIntent\.FLAG_UPDATE_CURRENT|PendingIntent\.FLAG_MUTABLE)$
      - patterns:
          - pattern: PendingIntent.getActivities($CTX, $REQ, $INTENTS, $FLAGS, $BUNDLE)
          - metavariable-regex:
              metavariable: $FLAGS
              regex: ^(0|134217728|33554432|0x08000000|0x02000000|PendingIntent\.FLAG_UPDATE_CURRENT|PendingIntent\.FLAG_MUTABLE)$
      - patterns:
          - pattern: PendingIntent.getService($CTX, $REQ, $INTENT, $FLAGS)
          - metavariable-regex:
              metavariable: $FLAGS
              regex: ^(0|134217728|33554432|0x08000000|0x02000000|PendingIntent\.FLAG_UPDATE_CURRENT|PendingIntent\.FLAG_MUTABLE)$
      - patterns:
          - pattern: PendingIntent.getForegroundService($CTX, $REQ, $INTENT, $FLAGS)
          - metavariable-regex:
              metavariable: $FLAGS
              regex: ^(0|134217728|33554432|0x08000000|0x02000000|PendingIntent\.FLAG_UPDATE_CURRENT|PendingIntent\.FLAG_MUTABLE)$
      - patterns:
          - pattern: PendingIntent.getBroadcast($CTX, $REQ, $INTENT, $FLAGS)
          - metavariable-regex:
              metavariable: $FLAGS
              regex: ^(0|134217728|33554432|0x08000000|0x02000000|PendingIntent\.FLAG_UPDATE_CURRENT|PendingIntent\.FLAG_MUTABLE)$
run.sh
1
NO_COLOR=true semgrep -c ../../../../rules/mastg-android-pendingintent-mutable.yml ./MastgTest_reversed.java > output.txt

Observation

The rule identifies 3 findings where PendingIntent creation APIs include insecure implementations.

output.txt
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
┌─────────────────┐
 3 Code Findings 
└─────────────────┘

    MastgTest_reversed.java
    ❯❱ rules.mastg-android-pendingintent-mutable
          ❰❰ Blocking ❱❱
          [MASVS-PLATFORM-1] PendingIntent created without FLAG_IMMUTABLE or with FLAG_MUTABLE may be
          vulnerable to hijacking.                                                                   

           25 PendingIntent.getActivity(this.context, 0, implicitIntent, 134217728);
            ⋮┆----------------------------------------
           28 PendingIntent.getService(this.context, 1, explicitMutableIntent, 33554432);
            ⋮┆----------------------------------------
           31 PendingIntent.getBroadcast(this.context, 2, broadcastIntent, 0);

Evaluation

Review each of the reported instances:

  • Line 25: FAIL - Uses PendingIntent.getActivity() with an implicit intent (ACTION_VIEW) and FLAG_UPDATE_CURRENT without FLAG_IMMUTABLE. A malicious app could intercept this PendingIntent and modify its target.

  • Line 28: FAIL - Uses PendingIntent.getService() with FLAG_MUTABLE explicitly set. Even though the intent is explicit, the mutable flag allows modification of unfilled fields.

  • Line 31: FAIL - Uses PendingIntent.getBroadcast() with an implicit intent (custom action) and no flags. On API < 31, the absence of flags results in implicit mutability.

The test fails for the three instances because they either lack FLAG_IMMUTABLE or use implicit intents that could be hijacked.

Confirm the Exposure

The sample creates PendingIntent instances using mutable or non-immutable flags. If exposed to external applications, they may become exploitable when shared through notifications, widgets, shortcuts, or IPC mechanisms.

For the getActivity() instance, the base intent uses Intent.ACTION_VIEW without specifying a target component. If the PendingIntent is exposed, an attacker-controlled application capable of handling the same action could be selected as the destination when the intent is resolved.

For the getService() instance, FLAG_MUTABLE is explicitly specified. If an attacker obtains a reference to the PendingIntent, mutable intent fields such as extras, actions, or data URIs may be modified before the intent is delivered to the target service.

For the getBroadcast() instance, the broadcast uses an implicit action and does not specify FLAG_IMMUTABLE. An attacker-controlled application could register a receiver for the same action and potentially receive or influence the broadcast when the PendingIntent is sent.