Skip to content

MASTG-DEMO-0120: Uses of Unauthorized Access to Exported Content Providers

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

Sample

This demo uses the same sample as Unauthorized Access to Database Records through Exported Content Provider.

The code below implements an AppointmentProvider backed by a SQLite database that stores sensitive patient data. The provider is registered in the AndroidManifest.xml with android:exported="true" and no android:readPermission or android:writePermission and protectionLevel, allowing any app on the device to query sensitive patient data.

  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
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
package org.owasp.mastestapp

import android.content.ContentProvider
import android.content.ContentUris
import android.content.ContentValues
import android.content.Context
import android.content.UriMatcher
import android.database.Cursor
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
import android.net.Uri

// SUMMARY: Demonstrates unauthorized access to sensitive medical records through an exported ContentProvider without permission enforcement.
// FAIL: [MASTG-TEST-0356] AppointmentProvider is exported without android:readPermission or android:writePermission, allowing any app on the device to query patient appointment records.

class MastgTest(private val context: Context) {

    fun mastgTest(): String {
        val r = DemoResults("0121")
        return try {
            context.openOrCreateDatabase(AppointmentDbHelper.DB_NAME, 0, null).close()
            r.add(
                Status.FAIL,
                "Appointment database initialized. Any app can read patient records via: content://org.owasp.mastestapp.appointments/appointments"
            )
            r.toJson()
        } catch (e: Exception) {
            r.add(Status.ERROR, "Initialization error: ${e.javaClass.simpleName}: ${e.message}")
            r.toJson()
        }
    }

    class AppointmentDbHelper(context: Context) :
        SQLiteOpenHelper(context, DB_NAME, null, DB_VERSION) {

        override fun onCreate(db: SQLiteDatabase) {
            db.execSQL(
                """
                CREATE TABLE $TABLE_APPOINTMENTS (
                  _id INTEGER PRIMARY KEY AUTOINCREMENT,
                  patient_name TEXT NOT NULL,
                  dob TEXT NOT NULL,
                  appointment_date TEXT NOT NULL,
                  doctor TEXT NOT NULL,
                  diagnosis TEXT,
                  notes TEXT
                )
                """.trimIndent()
            )
            insert(db, "Maria Garcia", "1988-03-14", "2026-04-10", "Dr. Chen",  "Type 2 Diabetes",        "Adjust metformin dosage to 1000mg twice daily")
            insert(db, "James Wilson", "1975-09-27", "2026-04-11", "Dr. Patel", "Essential Hypertension", "Refer to cardiology; increase lisinopril to 20mg")
        }

        override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
            db.execSQL("DROP TABLE IF EXISTS $TABLE_APPOINTMENTS")
            onCreate(db)
        }

        private fun insert(
            db: SQLiteDatabase,
            name: String, dob: String, date: String,
            doctor: String, diagnosis: String, notes: String
        ) {
            val cv = ContentValues().apply {
                put("patient_name", name)
                put("dob", dob)
                put("appointment_date", date)
                put("doctor", doctor)
                put("diagnosis", diagnosis)
                put("notes", notes)
            }
            db.insert(TABLE_APPOINTMENTS, null, cv)
        }

        companion object {
            const val DB_NAME = "appointments.db"
            const val DB_VERSION = 1
            const val TABLE_APPOINTMENTS = "appointments"
        }
    }

    class AppointmentProvider : ContentProvider() {

        private lateinit var db: AppointmentDbHelper

        override fun onCreate(): Boolean {
            db = AppointmentDbHelper(requireNotNull(context))
            return true
        }

        override fun query(
            uri: Uri,
            projection: Array<out String>?,
            selection: String?,
            selectionArgs: Array<out String>?,
            sortOrder: String?
        ): Cursor? {
            val readableDb = db.readableDatabase
            return when (MATCHER.match(uri)) {
                MATCH_APPOINTMENTS -> readableDb.query(
                    AppointmentDbHelper.TABLE_APPOINTMENTS,
                    null, null, null, null, null, "_id ASC"
                )
                MATCH_APPOINTMENT_BY_ID -> {
                    val id = ContentUris.parseId(uri)
                    readableDb.query(
                        AppointmentDbHelper.TABLE_APPOINTMENTS,
                        null, "_id=?", arrayOf(id.toString()), null, null, "_id ASC"
                    )
                }
                else -> null
            }
        }

        override fun getType(uri: Uri): String? = when (MATCHER.match(uri)) {
            MATCH_APPOINTMENTS -> "vnd.android.cursor.dir/vnd.mastestapp.appointment"
            MATCH_APPOINTMENT_BY_ID -> "vnd.android.cursor.item/vnd.mastestapp.appointment"
            else -> null
        }

        override fun insert(uri: Uri, values: ContentValues?): Uri? = null

        override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int = 0

        override fun update(
            uri: Uri, values: ContentValues?,
            selection: String?, selectionArgs: Array<out String>?
        ): Int = 0

        companion object {
            private const val AUTH = "org.owasp.mastestapp.appointments"
            private const val MATCH_APPOINTMENTS = 1
            private const val MATCH_APPOINTMENT_BY_ID = 2

            private val MATCHER = UriMatcher(UriMatcher.NO_MATCH).apply {
                addURI(AUTH, "appointments", MATCH_APPOINTMENTS)
                addURI(AUTH, "appointments/#", MATCH_APPOINTMENT_BY_ID)
            }
        }
    }
}
  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
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
package org.owasp.mastestapp;

import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.net.Uri;
import androidx.autofill.HintConstants;
import kotlin.Metadata;
import kotlin.jvm.internal.Intrinsics;

/* JADX INFO: compiled from: MastgTest.kt */
/* JADX INFO: loaded from: classes3.dex */
@Metadata(d1 = {"\u0000\u001a\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0000\n\u0002\u0018\u0002\n\u0002\b\u0003\n\u0002\u0010\u000e\n\u0002\b\u0003\b\u0007\u0018\u00002\u00020\u0001:\u0002\b\tB\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\n"}, d2 = {"Lorg/owasp/mastestapp/MastgTest;", "", "context", "Landroid/content/Context;", "<init>", "(Landroid/content/Context;)V", "mastgTest", "", "AppointmentDbHelper", "AppointmentProvider", "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() {
        DemoResults r = new DemoResults("0121");
        try {
            this.context.openOrCreateDatabase(AppointmentDbHelper.DB_NAME, 0, null).close();
            r.add(Status.FAIL, "Appointment database initialized. Any app can read patient records via: content://org.owasp.mastestapp.appointments/appointments");
            return r.toJson();
        } catch (Exception e) {
            r.add(Status.ERROR, "Initialization error: " + e.getClass().getSimpleName() + ": " + e.getMessage());
            return r.toJson();
        }
    }

    /* JADX INFO: compiled from: MastgTest.kt */
    @Metadata(d1 = {"\u00000\n\u0002\u0018\u0002\n\u0002\u0018\u0002\n\u0000\n\u0002\u0018\u0002\n\u0002\b\u0003\n\u0002\u0010\u0002\n\u0000\n\u0002\u0018\u0002\n\u0002\b\u0002\n\u0002\u0010\b\n\u0002\b\u0003\n\u0002\u0010\u000e\n\u0002\b\u0007\b\u0007\u0018\u0000 \u00162\u00020\u0001:\u0001\u0016B\u000f\u0012\u0006\u0010\u0002\u001a\u00020\u0003¢\u0006\u0004\b\u0004\u0010\u0005J\u0010\u0010\u0006\u001a\u00020\u00072\u0006\u0010\b\u001a\u00020\tH\u0016J \u0010\n\u001a\u00020\u00072\u0006\u0010\b\u001a\u00020\t2\u0006\u0010\u000b\u001a\u00020\f2\u0006\u0010\r\u001a\u00020\fH\u0016J@\u0010\u000e\u001a\u00020\u00072\u0006\u0010\b\u001a\u00020\t2\u0006\u0010\u000f\u001a\u00020\u00102\u0006\u0010\u0011\u001a\u00020\u00102\u0006\u0010\u0012\u001a\u00020\u00102\u0006\u0010\u0013\u001a\u00020\u00102\u0006\u0010\u0014\u001a\u00020\u00102\u0006\u0010\u0015\u001a\u00020\u0010H\u0002¨\u0006\u0017"}, d2 = {"Lorg/owasp/mastestapp/MastgTest$AppointmentDbHelper;", "Landroid/database/sqlite/SQLiteOpenHelper;", "context", "Landroid/content/Context;", "<init>", "(Landroid/content/Context;)V", "onCreate", "", "db", "Landroid/database/sqlite/SQLiteDatabase;", "onUpgrade", "oldVersion", "", "newVersion", "insert", HintConstants.AUTOFILL_HINT_NAME, "", "dob", "date", "doctor", "diagnosis", "notes", "Companion", "app_debug"}, k = 1, mv = {2, 0, 0}, xi = 48)
    public static final class AppointmentDbHelper extends SQLiteOpenHelper {
        public static final int $stable = 0;
        public static final String DB_NAME = "appointments.db";
        public static final int DB_VERSION = 1;
        public static final String TABLE_APPOINTMENTS = "appointments";

        /* JADX WARN: 'super' call moved to the top of the method (can break code semantics) */
        public AppointmentDbHelper(Context context) {
            super(context, DB_NAME, (SQLiteDatabase.CursorFactory) null, 1);
            Intrinsics.checkNotNullParameter(context, "context");
        }

        @Override // android.database.sqlite.SQLiteOpenHelper
        public void onCreate(SQLiteDatabase db) {
            Intrinsics.checkNotNullParameter(db, "db");
            db.execSQL("CREATE TABLE appointments (\n  _id INTEGER PRIMARY KEY AUTOINCREMENT,\n  patient_name TEXT NOT NULL,\n  dob TEXT NOT NULL,\n  appointment_date TEXT NOT NULL,\n  doctor TEXT NOT NULL,\n  diagnosis TEXT,\n  notes TEXT\n)");
            insert(db, "Maria Garcia", "1988-03-14", "2026-04-10", "Dr. Chen", "Type 2 Diabetes", "Adjust metformin dosage to 1000mg twice daily");
            insert(db, "James Wilson", "1975-09-27", "2026-04-11", "Dr. Patel", "Essential Hypertension", "Refer to cardiology; increase lisinopril to 20mg");
        }

        @Override // android.database.sqlite.SQLiteOpenHelper
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
            Intrinsics.checkNotNullParameter(db, "db");
            db.execSQL("DROP TABLE IF EXISTS appointments");
            onCreate(db);
        }

        private final void insert(SQLiteDatabase db, String name, String dob, String date, String doctor, String diagnosis, String notes) {
            ContentValues cv = new ContentValues();
            cv.put("patient_name", name);
            cv.put("dob", dob);
            cv.put("appointment_date", date);
            cv.put("doctor", doctor);
            cv.put("diagnosis", diagnosis);
            cv.put("notes", notes);
            db.insert(TABLE_APPOINTMENTS, null, cv);
        }
    }

    /* JADX INFO: compiled from: MastgTest.kt */
    @Metadata(d1 = {"\u0000>\n\u0002\u0018\u0002\n\u0002\u0018\u0002\n\u0002\b\u0003\n\u0002\u0018\u0002\n\u0000\n\u0002\u0010\u000b\n\u0000\n\u0002\u0018\u0002\n\u0000\n\u0002\u0018\u0002\n\u0000\n\u0002\u0010\u0011\n\u0002\u0010\u000e\n\u0002\b\u0007\n\u0002\u0018\u0002\n\u0000\n\u0002\u0010\b\n\u0002\b\u0005\b\u0007\u0018\u0000 \u001c2\u00020\u0001:\u0001\u001cB\t\b\u0007¢\u0006\u0004\b\u0002\u0010\u0003J\b\u0010\u0006\u001a\u00020\u0007H\u0016JO\u0010\b\u001a\u0004\u0018\u00010\t2\u0006\u0010\n\u001a\u00020\u000b2\u0010\u0010\f\u001a\f\u0012\u0006\b\u0001\u0012\u00020\u000e\u0018\u00010\r2\b\u0010\u000f\u001a\u0004\u0018\u00010\u000e2\u0010\u0010\u0010\u001a\f\u0012\u0006\b\u0001\u0012\u00020\u000e\u0018\u00010\r2\b\u0010\u0011\u001a\u0004\u0018\u00010\u000eH\u0016¢\u0006\u0002\u0010\u0012J\u0012\u0010\u0013\u001a\u0004\u0018\u00010\u000e2\u0006\u0010\n\u001a\u00020\u000bH\u0016J\u001c\u0010\u0014\u001a\u0004\u0018\u00010\u000b2\u0006\u0010\n\u001a\u00020\u000b2\b\u0010\u0015\u001a\u0004\u0018\u00010\u0016H\u0016J1\u0010\u0017\u001a\u00020\u00182\u0006\u0010\n\u001a\u00020\u000b2\b\u0010\u000f\u001a\u0004\u0018\u00010\u000e2\u0010\u0010\u0010\u001a\f\u0012\u0006\b\u0001\u0012\u00020\u000e\u0018\u00010\rH\u0016¢\u0006\u0002\u0010\u0019J;\u0010\u001a\u001a\u00020\u00182\u0006\u0010\n\u001a\u00020\u000b2\b\u0010\u0015\u001a\u0004\u0018\u00010\u00162\b\u0010\u000f\u001a\u0004\u0018\u00010\u000e2\u0010\u0010\u0010\u001a\f\u0012\u0006\b\u0001\u0012\u00020\u000e\u0018\u00010\rH\u0016¢\u0006\u0002\u0010\u001bR\u000e\u0010\u0004\u001a\u00020\u0005X\u0082.¢\u0006\u0002\n\u0000¨\u0006\u001d"}, d2 = {"Lorg/owasp/mastestapp/MastgTest$AppointmentProvider;", "Landroid/content/ContentProvider;", "<init>", "()V", "db", "Lorg/owasp/mastestapp/MastgTest$AppointmentDbHelper;", "onCreate", "", "query", "Landroid/database/Cursor;", "uri", "Landroid/net/Uri;", "projection", "", "", "selection", "selectionArgs", "sortOrder", "(Landroid/net/Uri;[Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;)Landroid/database/Cursor;", "getType", "insert", "values", "Landroid/content/ContentValues;", "delete", "", "(Landroid/net/Uri;Ljava/lang/String;[Ljava/lang/String;)I", "update", "(Landroid/net/Uri;Landroid/content/ContentValues;Ljava/lang/String;[Ljava/lang/String;)I", "Companion", "app_debug"}, k = 1, mv = {2, 0, 0}, xi = 48)
    public static final class AppointmentProvider extends ContentProvider {
        private static final String AUTH = "org.owasp.mastestapp.appointments";
        private static final UriMatcher MATCHER;
        private static final int MATCH_APPOINTMENTS = 1;
        private static final int MATCH_APPOINTMENT_BY_ID = 2;
        private AppointmentDbHelper db;
        public static final int $stable = 8;

        @Override // android.content.ContentProvider
        public boolean onCreate() {
            Context context = getContext();
            if (context != null) {
                this.db = new AppointmentDbHelper(context);
                return true;
            }
            throw new IllegalArgumentException("Required value was null.".toString());
        }

        @Override // android.content.ContentProvider
        public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
            Intrinsics.checkNotNullParameter(uri, "uri");
            AppointmentDbHelper appointmentDbHelper = this.db;
            if (appointmentDbHelper == null) {
                Intrinsics.throwUninitializedPropertyAccessException("db");
                appointmentDbHelper = null;
            }
            SQLiteDatabase readableDb = appointmentDbHelper.getReadableDatabase();
            switch (MATCHER.match(uri)) {
                case 1:
                    return readableDb.query(AppointmentDbHelper.TABLE_APPOINTMENTS, null, null, null, null, null, "_id ASC");
                case 2:
                    long id = ContentUris.parseId(uri);
                    return readableDb.query(AppointmentDbHelper.TABLE_APPOINTMENTS, null, "_id=?", new String[]{String.valueOf(id)}, null, null, "_id ASC");
                default:
                    return null;
            }
        }

        @Override // android.content.ContentProvider
        public String getType(Uri uri) {
            Intrinsics.checkNotNullParameter(uri, "uri");
            switch (MATCHER.match(uri)) {
                case 1:
                    return "vnd.android.cursor.dir/vnd.mastestapp.appointment";
                case 2:
                    return "vnd.android.cursor.item/vnd.mastestapp.appointment";
                default:
                    return null;
            }
        }

        @Override // android.content.ContentProvider
        public Uri insert(Uri uri, ContentValues values) {
            Intrinsics.checkNotNullParameter(uri, "uri");
            return null;
        }

        @Override // android.content.ContentProvider
        public int delete(Uri uri, String selection, String[] selectionArgs) {
            Intrinsics.checkNotNullParameter(uri, "uri");
            return 0;
        }

        @Override // android.content.ContentProvider
        public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
            Intrinsics.checkNotNullParameter(uri, "uri");
            return 0;
        }

        static {
            UriMatcher $this$MATCHER_u24lambda_u240 = new UriMatcher(-1);
            $this$MATCHER_u24lambda_u240.addURI(AUTH, AppointmentDbHelper.TABLE_APPOINTMENTS, 1);
            $this$MATCHER_u24lambda_u240.addURI(AUTH, "appointments/#", 2);
            MATCHER = $this$MATCHER_u24lambda_u240;
        }
    }
}
 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
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

    <uses-permission android:name="android.permission.INTERNET" />

    <application
    android:allowBackup="true"
    android:dataExtractionRules="@xml/data_extraction_rules"
    android:fullBackupContent="@xml/backup_rules"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:roundIcon="@mipmap/ic_launcher_round"
    android:supportsRtl="true"
    android:theme="@style/Theme.MASTestApp"
    tools:targetApi="31">
    <activity
        android:name=".MainActivity"
        android:exported="true"
        android:windowSoftInputMode="adjustResize"
        android:theme="@style/Theme.MASTestApp">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>

    <provider
        android:name="org.owasp.mastestapp.MastgTest$AppointmentProvider"
        android:authorities="org.owasp.mastestapp.appointments"
        android:exported="true" />

</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
<?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: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>
        <provider
            android:name="org.owasp.mastestapp.MastgTest.AppointmentProvider"
            android:exported="true"
            android:authorities="org.owasp.mastestapp.appointments"/>
        <activity
            android:name="androidx.compose.ui.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.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>

Steps

Let's run our semgrep rule against the sample code.

../../../../rules/mastg-android-content-provider-exported.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
rules:
  - id: mastg-android-provider-permission-protected
    languages: [xml]
    severity: WARNING
    metadata:
      summary: Detects provider-level permission protection on a ContentProvider.
      masvs:
        - MASVS-PLATFORM
    message: >
      [MASVS-PLATFORM] The ContentProvider declares permission protection.
      Verify that the permission strength matches the intended trust boundary.
    pattern-either:
      - pattern: |
          <provider ... android:permission="$PERM" ... />
      - pattern: |
          <provider ... android:readPermission="$PERM" ... />
      - pattern: |
          <provider ... android:writePermission="$PERM" ... />

  - id: mastg-android-provider-exported-without-permissions
    languages: [xml]
    severity: WARNING
    metadata:
      summary: Detects an exported ContentProvider without any read or write permission enforcement.
      masvs:
        - MASVS-PLATFORM
    message: >
      [MASVS-PLATFORM] The ContentProvider is exported without
      android:readPermission, android:writePermission, or android:permission,
      allowing any app on the device to access it without restriction.
    patterns:
      - pattern: <provider ... android:exported="true" ... />
      - pattern-not: <provider ... android:permission="$PERM" ... />
      - pattern-not: <provider ... android:readPermission="$PERM" ... />
      - pattern-not: <provider ... android:writePermission="$PERM" ... />
run.sh
1
NO_COLOR=true semgrep -c ../../../../rules/mastg-android-content-provider-exported.yml ../MASTG-DEMO-0121/AndroidManifest_reversed.xml > output.txt

Observation

The output should contain a finding that a <provider> is exported (android:exported="true") without android:readPermission, android:writePermission, or android:permission.

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

    ../MASTG-DEMO-0121/AndroidManifest_reversed.xml
    ❯❱ rules.mastg-android-provider-exported-without-permissions
          [MASVS-PLATFORM] The ContentProvider is exported without android:readPermission,                   
          android:writePermission, or android:permission, allowing any app on the device to access it without
          restriction.                                                                                       

           40 <provider
           41     android:name="org.owasp.mastestapp.MastgTest.AppointmentProvider"
           42     android:exported="true"
           43     android:authorities="org.owasp.mastestapp.appointments"/>

Evaluation

The test case fails because the exported AppointmentProvider exposes sensitive patient medical records through an exported database-backed content provider without enforcing appropriate access restrictions.

This means any app on the device is allowed to query the content provider to retrieve the data that is offered by the database.