Skip to content

MASTG-DEMO-0136: Internal Activity Communication via Implicit Intent

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

Sample

The following sample app attempts to start InternalActivity for app-internal communication by sending an implicit intent with the custom action org.owasp.mastestapp.INTERNAL_ACTION. This approach is insecure because another app can register a matching <intent-filter> and become a candidate during Android's intent resolution. For more details on the intent resolution mechanism, see Explicit vs Implicit Intents.

The intent in this sample is implicit because it relies on setAction without explicitly defining the target package or component. For the secure alternative, see Use Explicit Intents for Internal IPC.

 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
package org.owasp.mastestapp

import android.content.Context
import android.content.Intent

// SUMMARY: This sample demonstrates the insecure use of implicit intents for internal communication.
class MastgTest (private val context: Context){

    fun mastgTest(): String {
        val r = DemoResults("0x01")

        // FAIL: [MASTG-TEST-0372] The app uses an implicit intent to start an internal activity.
        val implicitIntent = Intent().apply {
            action = "org.owasp.mastestapp.INTERNAL_ACTION"
            putExtra("user_id", "12345")
            putExtra("session_token", "abcde-fghij-12345")
            addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
        }

        try {
            context.startActivity(implicitIntent)
            r.add(Status.FAIL, "Launched internal activity via implicit intent")
        } catch (e: Exception) {
            r.add(Status.ERROR, e.toString())
        }

        /*
        // PASS: [MASTG-TEST-0372] The app uses an explicit intent for internal communication.
        val explicitIntent = Intent(context, InternalActivity::class.java).apply {
            addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
        }

        try {
            context.startActivity(explicitIntent)
            r.add(Status.PASS, "Launched internal activity via explicit intent")
        } catch (e: Exception) {
            r.add(Status.ERROR, e.toString())
        }
        */

        return r.toJson()
    }
}

class InternalActivity : android.app.Activity() {
    override fun onCreate(savedInstanceState: android.os.Bundle?) {
        super.onCreate(savedInstanceState)
        val textView = android.widget.TextView(this)
        textView.text = "Internal Activity"
        textView.textSize = 24f
        textView.gravity = android.view.Gravity.CENTER
        setContentView(textView)
    }
}
 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
package org.owasp.mastestapp;

import android.content.Context;
import android.content.Intent;
import kotlin.Metadata;
import kotlin.jvm.internal.Intrinsics;

/* compiled from: MastgTest.kt */
@Metadata(m110d1 = {"\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"}, m111d2 = {"Lorg/owasp/mastestapp/MastgTest;", "", "context", "Landroid/content/Context;", "<init>", "(Landroid/content/Context;)V", "mastgTest", "", "app_debug"}, m112k = 1, m113mv = {2, 0, 0}, m115xi = 48)
/* loaded from: classes4.dex */
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() {
        DemoResults r = new DemoResults("0x01");
        Intent implicitIntent = new Intent();
        implicitIntent.setAction("org.owasp.mastestapp.INTERNAL_ACTION");
        implicitIntent.putExtra("user_id", "12345");
        implicitIntent.putExtra("session_token", "abcde-fghij-12345");
        implicitIntent.addFlags(268435456);
        try {
            this.context.startActivity(implicitIntent);
            r.add(Status.FAIL, "Launched internal activity via implicit intent");
        } catch (Exception e) {
            r.add(Status.ERROR, e.toString());
        }
        return r.toJson();
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

    <application>
        <activity
            android:name="org.owasp.mastestapp.MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <!-- Internal activity with an intent-filter, making it a resolver candidate -->
        <activity
            android:name="org.owasp.mastestapp.InternalActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="org.owasp.mastestapp.INTERNAL_ACTION" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>
    </application>
</manifest>
 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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    android:versionCode="1"
    android:versionName="1.0"
    android:compileSdkVersion="35"
    android:compileSdkVersionCodename="15"
    package="org.owasp.mastestapp.normal1"
    platformBuildVersionCode="35"
    platformBuildVersionName="15">
    <uses-sdk
        android:minSdkVersion="29"
        android:targetSdkVersion="35"/>
    <uses-permission android:name="android.permission.INTERNET"/>
    <permission
        android:name="org.owasp.mastestapp.normal1.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION"
        android:protectionLevel="signature"/>
    <uses-permission android:name="org.owasp.mastestapp.normal1.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION"/>
    <application
        android:theme="@style/Theme.MASTestApp"
        android:label="@string/app_name"
        android:icon="@mipmap/ic_launcher"
        android:debuggable="true"
        android:allowBackup="true"
        android:supportsRtl="true"
        android:extractNativeLibs="false"
        android:fullBackupContent="@xml/backup_rules"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:appComponentFactory="androidx.core.app.CoreComponentFactory"
        android:dataExtractionRules="@xml/data_extraction_rules">
        <activity
            android:name="org.owasp.mastestapp.MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
        <activity
            android:name="org.owasp.mastestapp.InternalActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="org.owasp.mastestapp.INTERNAL_ACTION"/>
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
        </activity>
        <activity
            android:name="androidx.compose.p000ui.tooling.PreviewActivity"
            android:exported="true"/>
        <activity
            android:theme="@android:style/Theme.Material.Light.NoActionBar"
            android:name="androidx.activity.ComponentActivity"
            android:exported="true"/>
        <provider
            android:name="androidx.startup.InitializationProvider"
            android:exported="false"
            android:authorities="org.owasp.mastestapp.normal1.androidx-startup">
            <meta-data
                android:name="androidx.emoji2.text.EmojiCompatInitializer"
                android:value="androidx.startup"/>
            <meta-data
                android:name="androidx.lifecycle.ProcessLifecycleInitializer"
                android:value="androidx.startup"/>
            <meta-data
                android:name="androidx.profileinstaller.ProfileInstallerInitializer"
                android:value="androidx.startup"/>
        </provider>
        <receiver
            android:name="androidx.profileinstaller.ProfileInstallReceiver"
            android:permission="android.permission.DUMP"
            android:enabled="true"
            android:exported="true"
            android:directBootAware="false">
            <intent-filter>
                <action android:name="androidx.profileinstaller.action.INSTALL_PROFILE"/>
            </intent-filter>
            <intent-filter>
                <action android:name="androidx.profileinstaller.action.SKIP_FILE"/>
            </intent-filter>
            <intent-filter>
                <action android:name="androidx.profileinstaller.action.SAVE_PROFILE"/>
            </intent-filter>
            <intent-filter>
                <action android:name="androidx.profileinstaller.action.BENCHMARK_OPERATION"/>
            </intent-filter>
        </receiver>
    </application>
</manifest>

Note

Attacker App Registering for Internal Implicit Intent provides an example attacker app that declares a matching <intent-filter> for org.owasp.mastestapp.INTERNAL_ACTION. It demonstrates how another app can become a candidate for handling this sample app's implicit intent.

Steps

Let's use Static Analysis on Android with an semgrep rule to scan the reverse-engineered code for implicit intents used for internal communication.

../../../../rules/mastg-android-implicit-intent-internal-communication.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
rules:
  - id: mastg-android-implicit-intent-internal-communication
    patterns:
      - pattern: |
          $INTENT = new Intent(...);
          ...
          $INTENT.setAction($ACTION);
          ...
          $CONTEXT.startActivity($INTENT);
      - pattern-not: |
          $INTENT = new Intent(...);
          ...
          $INTENT.setPackage(...);
          ...
          $CONTEXT.startActivity($INTENT);
      - pattern-not: |
          $INTENT = new Intent(...);
          ...
          $INTENT.setComponent(...);
          ...
          $CONTEXT.startActivity($INTENT);
      - pattern-not: |
          $INTENT = new Intent($CONTEXT, $CLASS);
          ...
          $CONTEXT.startActivity($INTENT);
    message: "[MASVS-CODE-4] The app uses an implicit intent for internal component communication. Use explicit intents by specifying the package or component."
    languages: [java]
    severity: WARNING
    metadata:
      summary: Detects implicit intents used for internal component communication without specifying a package or component.
run.sh
1
2
3
#!/bin/bash
# SUMMARY: This script uses semgrep to detect implicit intents in the source code.
NO_COLOR=true semgrep --config ../../../../rules/mastg-android-implicit-intent-internal-communication.yml MastgTest_reversed.java --text > output.txt

Observation

The output shows one Intent dispatch:

  • Intent creation: new Intent().
  • Action assignment: implicitIntent.setAction("org.owasp.mastestapp.INTERNAL_ACTION").
  • Extras added before dispatch: user_id and session_token.
  • Dispatch API: this.context.startActivity(implicitIntent).
output.txt
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
┌────────────────┐
 1 Code Finding 
└────────────────┘

    MastgTest_reversed.java
    ❯❱ rules.mastg-android-implicit-intent-internal-communication
          [MASVS-CODE-4] The app uses an implicit intent for internal component communication. Use explicit
          intents by specifying the package or component.                                                  

           22 Intent implicitIntent = new Intent();
           23 implicitIntent.setAction("org.owasp.mastestapp.INTERNAL_ACTION");
           24 implicitIntent.putExtra("user_id", "12345");
           25 implicitIntent.putExtra("session_token", "abcde-fghij-12345");
           26 implicitIntent.addFlags(268435456);
           27 try {
           28     this.context.startActivity(implicitIntent);
           29     r.add(Status.FAIL, "Launched internal activity via implicit intent");
           30 } catch (Exception e) {
           31     r.add(Status.ERROR, e.toString());
             [hid 1 additional lines, adjust with --max-lines-per-finding] 

Evaluation

The test case fails because the app uses an implicit intent (org.owasp.mastestapp.INTERNAL_ACTION) for app-internal communication with InternalActivity.

The action is app-specific and the manifest declares InternalActivity as the component intended to handle it, but the reported dispatch does not name that component or restrict the target package. The reported block does not show a target-defining call such as setPackage, setClass, setClassName, setComponent, or an explicit Intent(context, Class) constructor before dispatch. Another app can declare a matching <intent-filter> and become a candidate during Android intent resolution.