diff --git a/best-practices/MASTG-BEST-XXXX.md b/best-practices/MASTG-BEST-XXXX.md
new file mode 100644
index 00000000000..3e81885d8dc
--- /dev/null
+++ b/best-practices/MASTG-BEST-XXXX.md
@@ -0,0 +1,23 @@
+---
+title: Prevent SQL Injection in ContentProviders
+alias: prevent-sqli-contentprovider
+id: MASTG-BEST-XXXX
+platform: android
+---
+
+The `ContentProvider` enables Android applications to share data with other applications and system components. If a `ContentProvider` constructs SQL queries using untrusted input from URIs, IPC calls, or Intents without validation or parameterization, it becomes vulnerable to SQL injection. Attackers can take advantage of this vulnerability to bypass access controls and extract sensitive data. Improper handling of URI path segments, query parameters, or `selection` arguments in `ContentProvider` queries can lead to arbitrary SQL execution.
+
+- **Use Parameterized Queries** : Instead of building SQL using string concatenation, use `selection` and `selectionArgs` parameters.
+
+For example:
+
+```kotlin
+ val idSegment = uri.getPathSegments()[1]
+ val selection = "id = ?"
+ val selectionArgs = arrayOf(idSegment)
+ val cursor = qb.query(db, projection, selection, selectionArgs, null, null, sortOrder)
+```
+
+- **Use Prepared Statements**: When performing insert, update, or delete operations, use SQLite prepared statements (for example, `SQLiteStatement` or `SQLiteDatabase` methods that support argument binding) instead of dynamically constructed SQL. Prepared statements ensure that untrusted input is bound as parameters and cannot alter the structure of the SQL query, effectively preventing SQL injection even when input originates from URIs or IPC calls.
+
+Refer to ["Protect against malicious input"](https://developer.android.com/guide/topics/providers/content-provider-basics#Injection) for more information.
diff --git a/demos/android/MASVS-CODE/MASTG-DEMO-00XX/AndroidManifest_reversed.xml b/demos/android/MASVS-CODE/MASTG-DEMO-00XX/AndroidManifest_reversed.xml
new file mode 100644
index 00000000000..f1f5809cf7a
--- /dev/null
+++ b/demos/android/MASVS-CODE/MASTG-DEMO-00XX/AndroidManifest_reversed.xml
@@ -0,0 +1,78 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/demos/android/MASVS-CODE/MASTG-DEMO-00XX/MASTG-DEMO-00XX.md b/demos/android/MASVS-CODE/MASTG-DEMO-00XX/MASTG-DEMO-00XX.md
new file mode 100644
index 00000000000..bdf4b9ffc21
--- /dev/null
+++ b/demos/android/MASVS-CODE/MASTG-DEMO-00XX/MASTG-DEMO-00XX.md
@@ -0,0 +1,41 @@
+---
+platform: android
+title: Injection flaws in Android Content providers
+id: MASTG-DEMO-00XX
+code: [kotlin]
+test: MASTG-TEST-02XX
+status: new
+---
+
+### Sample
+
+The following code implements a vulnerable `ContentProvider` that appends user-controlled input from the URI path directly into a SQL query.
+
+{{ MastgTest.kt # MastgTest_reversed.java }}
+
+### Steps
+
+Let's run our @MASTG-TOOL-0110 rule against the sample code.
+
+{{ ../../../../rules/mastg-android-sql-injection-contentprovider.yml }}
+
+{{ run.sh }}
+
+### Observation
+
+The rule has identified the use of untrusted input from `Uri.getPathSegments().get(...)` being concatenated and passed into `SQLiteQueryBuilder.appendWhere(...)`, which is a known vector for SQL injection in exported `ContentProviders`.
+
+{{ output.txt }}
+
+### Evaluation
+
+This test case fails because the application constructs a SQL `WHERE` clause by directly appending untrusted user input from the URI without any validation or sanitization. This approach allows attackers to perform SQL injection by crafting a malicious `content://` URI to manipulate the query logic. For example, the following content query command can be used to list all names:
+
+```bash
+$ content query --uri content://org.owasp.mastestapp.provider/students --where "name='Bob' OR '1'='1'"
+Row: 0 id=1, name=Alice
+Row: 1 id=2, name=Bob
+Row: 2 id=3, name=Charlie
+```
+
+Refer to @MASTG-TECH-XXXX, to know more on using content query.
diff --git a/demos/android/MASVS-CODE/MASTG-DEMO-00XX/MastgTest.kt b/demos/android/MASVS-CODE/MASTG-DEMO-00XX/MastgTest.kt
new file mode 100644
index 00000000000..6160088cb32
--- /dev/null
+++ b/demos/android/MASVS-CODE/MASTG-DEMO-00XX/MastgTest.kt
@@ -0,0 +1,96 @@
+package org.owasp.mastestapp
+
+import android.content.ContentProvider
+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.database.sqlite.SQLiteQueryBuilder
+import android.net.Uri
+import android.util.Log
+
+class MastgTest(private val context: Context) {
+
+ fun mastgTest(): String {
+ return """
+ This app's content provider is vulnerable to SQLI.
+
+ Test on adb shell with:
+ # content query --uri content://org.owasp.mastestapp.provider/students --where "name='Bob' OR '1'='1'"
+ """.trimIndent()
+ }
+
+ // Vulnerable ContentProvider with path-based SQL injection
+ class StudentProvider : ContentProvider() {
+
+ companion object {
+ const val AUTHORITY = "org.owasp.mastestapp.provider"
+ const val STUDENTS = 1
+ const val STUDENT_ID = 2
+ val uriMatcher = UriMatcher(UriMatcher.NO_MATCH).apply {
+ addURI(AUTHORITY, "students", STUDENTS)
+ addURI(AUTHORITY, "students/#", STUDENT_ID)
+ }
+ }
+
+ private lateinit var dbHelper: DatabaseHelper
+
+ override fun onCreate(): Boolean {
+ dbHelper = DatabaseHelper(context!!)
+ return true
+ }
+
+ override fun query(
+ uri: Uri,
+ projection: Array?,
+ selection: String?,
+ selectionArgs: Array?,
+ sortOrder: String?
+ ): Cursor? {
+ val db = dbHelper.readableDatabase
+ val qb = SQLiteQueryBuilder()
+ qb.tables = "students"
+
+ when (uriMatcher.match(uri)) {
+ STUDENTS -> {
+ // No filtering — all rows
+ }
+ STUDENT_ID -> {
+ // Vulnerable: unvalidated input from path used in query
+ val id = uri.getPathSegments().get(1)
+ qb.appendWhere("id=" + id)
+ Log.e("SQLI", "Injected ID segment: $id")
+ }
+ else -> throw IllegalArgumentException("Unknown URI: $uri")
+ }
+
+ val cursor = qb.query(db, projection, selection, selectionArgs, null, null, sortOrder)
+ cursor.setNotificationUri(context!!.contentResolver, uri)
+ return cursor
+ }
+
+ override fun getType(uri: Uri): String? = null
+ override fun insert(uri: Uri, values: ContentValues?): Uri? = null
+ override fun delete(uri: Uri, selection: String?, selectionArgs: Array?): Int = 0
+ override fun update(uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array?): Int = 0
+ }
+
+ // DB helper for student data
+ class DatabaseHelper(context: Context) :
+ SQLiteOpenHelper(context, "students.db", null, 1) {
+
+ override fun onCreate(db: SQLiteDatabase) {
+ db.execSQL("CREATE TABLE students (id INTEGER PRIMARY KEY, name TEXT)")
+ db.execSQL("INSERT INTO students (name) VALUES ('Alice')")
+ db.execSQL("INSERT INTO students (name) VALUES ('Bob')")
+ db.execSQL("INSERT INTO students (name) VALUES ('Charlie')")
+ }
+
+ override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
+ db.execSQL("DROP TABLE IF EXISTS students")
+ onCreate(db)
+ }
+ }
+}
diff --git a/demos/android/MASVS-CODE/MASTG-DEMO-00XX/MastgTest_reversed.java b/demos/android/MASVS-CODE/MASTG-DEMO-00XX/MastgTest_reversed.java
new file mode 100644
index 00000000000..b805af2c7a8
--- /dev/null
+++ b/demos/android/MASVS-CODE/MASTG-DEMO-00XX/MastgTest_reversed.java
@@ -0,0 +1,159 @@
+package org.owasp.mastestapp;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.UriMatcher;
+import android.database.Cursor;
+import android.database.SQLException;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.database.sqlite.SQLiteQueryBuilder;
+import android.net.Uri;
+import android.util.Log;
+import kotlin.Metadata;
+import kotlin.jvm.internal.DefaultConstructorMarker;
+import kotlin.jvm.internal.Intrinsics;
+
+/* compiled from: MastgTest.kt */
+@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;", "", "(Landroid/content/Context;)V", "mastgTest", "", "StudentProvider", "DatabaseHelper", "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() {
+ Log.d("MASTG-TEST", "Hello from the OWASP MASTG Test app.");
+ return "Hello from the OWASP MASTG Test app.";
+ }
+
+ /* 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\u0016JK\u0010\b\u001a\u0004\u0018\u00010\t2\u0006\u0010\n\u001a\u00020\u000b2\u000e\u0010\f\u001a\n\u0012\u0004\u0012\u00020\u000e\u0018\u00010\r2\b\u0010\u000f\u001a\u0004\u0018\u00010\u000e2\u000e\u0010\u0010\u001a\n\u0012\u0004\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$StudentProvider;", "Landroid/content/ContentProvider;", "", "()V", "dbHelper", "Lorg/owasp/mastestapp/MastgTest$DatabaseHelper;", "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 StudentProvider extends ContentProvider {
+ public static final String AUTHORITY = "org.owasp.mastestapp.provider";
+ public static final int STUDENTS = 1;
+ public static final int STUDENT_ID = 2;
+ private static final UriMatcher uriMatcher;
+ private DatabaseHelper dbHelper;
+
+ /* renamed from: Companion, reason: from kotlin metadata */
+ public static final Companion INSTANCE = new Companion(null);
+ public static final int $stable = 8;
+
+ /* compiled from: MastgTest.kt */
+ @Metadata(d1 = {"\u0000\"\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\b\u0003\n\u0002\u0010\u000e\n\u0000\n\u0002\u0010\b\n\u0002\b\u0002\n\u0002\u0018\u0002\n\u0002\b\u0003\b\u0086\u0003\u0018\u00002\u00020\u0001B\t\b\u0002¢\u0006\u0004\b\u0002\u0010\u0003R\u000e\u0010\u0004\u001a\u00020\u0005X\u0086T¢\u0006\u0002\n\u0000R\u000e\u0010\u0006\u001a\u00020\u0007X\u0086T¢\u0006\u0002\n\u0000R\u000e\u0010\b\u001a\u00020\u0007X\u0086T¢\u0006\u0002\n\u0000R\u0011\u0010\t\u001a\u00020\n¢\u0006\b\n\u0000\u001a\u0004\b\u000b\u0010\f¨\u0006\r"}, d2 = {"Lorg/owasp/mastestapp/MastgTest$StudentProvider$Companion;", "", "", "()V", "AUTHORITY", "", "STUDENTS", "", "STUDENT_ID", "uriMatcher", "Landroid/content/UriMatcher;", "getUriMatcher", "()Landroid/content/UriMatcher;", "app_debug"}, k = 1, mv = {2, 0, 0}, xi = 48)
+ public static final class Companion {
+ public /* synthetic */ Companion(DefaultConstructorMarker defaultConstructorMarker) {
+ this();
+ }
+
+ private Companion() {
+ }
+
+ public final UriMatcher getUriMatcher() {
+ return StudentProvider.uriMatcher;
+ }
+ }
+
+ static {
+ UriMatcher $this$uriMatcher_u24lambda_u240 = new UriMatcher(-1);
+ $this$uriMatcher_u24lambda_u240.addURI(AUTHORITY, "students", 1);
+ $this$uriMatcher_u24lambda_u240.addURI(AUTHORITY, "students/#", 2);
+ uriMatcher = $this$uriMatcher_u24lambda_u240;
+ }
+
+ @Override // android.content.ContentProvider
+ public boolean onCreate() {
+ Context context = getContext();
+ Intrinsics.checkNotNull(context);
+ this.dbHelper = new DatabaseHelper(context);
+ return true;
+ }
+
+ @Override // android.content.ContentProvider
+ public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
+ Intrinsics.checkNotNullParameter(uri, "uri");
+ DatabaseHelper databaseHelper = this.dbHelper;
+ if (databaseHelper == null) {
+ Intrinsics.throwUninitializedPropertyAccessException("dbHelper");
+ databaseHelper = null;
+ }
+ SQLiteDatabase db = databaseHelper.getReadableDatabase();
+ SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
+ qb.setTables("students");
+ switch (uriMatcher.match(uri)) {
+ case 1:
+ break;
+ case 2:
+ String id = uri.getPathSegments().get(1);
+ qb.appendWhere("id=" + id);
+ Log.e("SQLI", "Injected ID segment: " + id);
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown URI: " + uri);
+ }
+ Cursor cursor = qb.query(db, projection, selection, selectionArgs, null, null, sortOrder);
+ Context context = getContext();
+ Intrinsics.checkNotNull(context);
+ cursor.setNotificationUri(context.getContentResolver(), uri);
+ return cursor;
+ }
+
+ @Override // android.content.ContentProvider
+ public String getType(Uri uri) {
+ Intrinsics.checkNotNullParameter(uri, "uri");
+ 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;
+ }
+ }
+
+ /* compiled from: MastgTest.kt */
+ @Metadata(d1 = {"\u0000(\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\u0002\b\u0007\u0018\u00002\u00020\u0001B\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\u0016¨\u0006\u000e"}, d2 = {"Lorg/owasp/mastestapp/MastgTest$DatabaseHelper;", "Landroid/database/sqlite/SQLiteOpenHelper;", "context", "Landroid/content/Context;", "", "(Landroid/content/Context;)V", "onCreate", "", "db", "Landroid/database/sqlite/SQLiteDatabase;", "onUpgrade", "oldVersion", "", "newVersion", "app_debug"}, k = 1, mv = {2, 0, 0}, xi = 48)
+ public static final class DatabaseHelper extends SQLiteOpenHelper {
+ public static final int $stable = 0;
+
+ /* JADX WARN: 'super' call moved to the top of the method (can break code semantics) */
+ public DatabaseHelper(Context context) {
+ super(context, "students.db", (SQLiteDatabase.CursorFactory) null, 1);
+ Intrinsics.checkNotNullParameter(context, "context");
+ }
+
+ @Override // android.database.sqlite.SQLiteOpenHelper
+ public void onCreate(SQLiteDatabase db) throws SQLException {
+ Intrinsics.checkNotNullParameter(db, "db");
+ db.execSQL("CREATE TABLE students (id INTEGER PRIMARY KEY, name TEXT)");
+ db.execSQL("INSERT INTO students (name) VALUES ('Alice')");
+ db.execSQL("INSERT INTO students (name) VALUES ('Bob')");
+ db.execSQL("INSERT INTO students (name) VALUES ('Charlie')");
+ }
+
+ @Override // android.database.sqlite.SQLiteOpenHelper
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) throws SQLException {
+ Intrinsics.checkNotNullParameter(db, "db");
+ db.execSQL("DROP TABLE IF EXISTS students");
+ onCreate(db);
+ }
+ }
+}
diff --git a/demos/android/MASVS-CODE/MASTG-DEMO-00XX/output.txt b/demos/android/MASVS-CODE/MASTG-DEMO-00XX/output.txt
new file mode 100644
index 00000000000..b4bf6b625dc
--- /dev/null
+++ b/demos/android/MASVS-CODE/MASTG-DEMO-00XX/output.txt
@@ -0,0 +1,13 @@
+
+
+┌────────────────┐
+│ 1 Code Finding │
+└────────────────┘
+
+ MastgTest_reversed.java
+ ❯❱ mastg-android-sqli-contentprovider
+ Possible SQL Injection: Unvalidated user input from `Uri.getPathSegments()` used in
+ `SQLiteQueryBuilder.appendWhere()
+
+ 94┆ qb.appendWhere("id=" + id);
+
diff --git a/demos/android/MASVS-CODE/MASTG-DEMO-00XX/run.sh b/demos/android/MASVS-CODE/MASTG-DEMO-00XX/run.sh
new file mode 100644
index 00000000000..e1f9f69eed3
--- /dev/null
+++ b/demos/android/MASVS-CODE/MASTG-DEMO-00XX/run.sh
@@ -0,0 +1,2 @@
+#!/bin/bash
+NO_COLOR=true semgrep -c ../../../../rules/mastg-android-sql-injection-contentprovider.yml ./MastgTest_reversed.java > output.txt
\ No newline at end of file
diff --git a/rules/mastg-android-sql-injection-contentprovider.yml b/rules/mastg-android-sql-injection-contentprovider.yml
new file mode 100644
index 00000000000..9d4ce949fd3
--- /dev/null
+++ b/rules/mastg-android-sql-injection-contentprovider.yml
@@ -0,0 +1,14 @@
+rules:
+ - id: mastg-android-sqli-contentprovider
+ severity: WARNING
+ languages:
+ - java
+ metadata:
+ summary: This rule inspects the possible SQL injection in android content providers.
+ message: "Possible SQL Injection: Unvalidated user input from `Uri.getPathSegments()` used in `SQLiteQueryBuilder.appendWhere()`"
+ patterns:
+ - pattern: |
+ $QB.appendWhere("..." + $VAR);
+ - pattern-inside: |
+ $VAR = $URI.getPathSegments().get(...);
+ ...
\ No newline at end of file
diff --git a/techniques/android/MASTG-TECH-XXXX.md b/techniques/android/MASTG-TECH-XXXX.md
new file mode 100644
index 00000000000..98c1bfd1dfb
--- /dev/null
+++ b/techniques/android/MASTG-TECH-XXXX.md
@@ -0,0 +1,83 @@
+---
+title: Testing Content URI Injection and Path-Segment Abuse
+platform: android
+---
+
+Android `ContentProvider`s make structured data available to other applications through `content://` URIs. They specify an authority (a unique identifier), one or more paths (tables/resources), and carry out CRUD operations (`query`, `insert`, `update`, `delete`). Clients interact with them via `ContentResolver` or directly from the device shell. The accessibility of a provider is contingent upon its `exported` setting and any permissions declared in the application's manifest (refer to @MASTG-TECH-0117).
+
+## What They Are
+
+- Interface for cross-app data access and IPC on Android.
+- Identified by a URI: `content:///` or `content:////`.
+- Backed by storage such as SQLite; many apps use `SQLiteQueryBuilder` in `query`.
+- Access control via `android:exported` and read/write permissions; signature-level permissions can restrict access to trusted apps only.
+
+## Using Content query
+
+Use @MASTG-TOOL-0004 to interact with providers on a device or emulator via the `content` command:
+
+- Query rows
+
+```bash
+$ adb shell content query --uri content://org.owasp.mastestapp.provider/students
+$ adb shell content query --uri content://org.owasp.mastestapp.provider/students --where "name='Bob'"
+```
+
+- Insert a row
+
+```bash
+$ adb shell content insert \
+ --uri content://org.owasp.mastestapp.provider/students \
+ --bind name:s:"Eve"
+```
+
+- Update rows
+
+```bash
+$ adb shell content update \
+ --uri content://org.owasp.mastestapp.provider/students \
+ --where "id=1" \
+ --bind name:s:"Alice Jr"
+```
+
+- Delete rows
+
+```bash
+$ adb shell content delete --uri content://org.owasp.mastestapp.provider/students --where "id=3"
+```
+
+## Inputs To Validate
+
+- URI path segments
+ - Risk: values from `Uri.getPathSegments()` / `lastPathSegment` concatenated into SQL (for example, `appendWhere("id=" + id)`).
+ - Safer: parse numeric IDs with `ContentUris.parseId(uri)`; strictly validate/whitelist path segments; never concatenate untrusted data into SQL.
+
+## Injection Flaw Testing
+
+Injection vulnerabilities in `ContentProvider`s usually arise when untrusted input (such as a path segment from `Uri.getPathSegments()` or a selection string provided by the caller) is directly concatenated into SQL queries rather than being parameterized. A frequent point of concern is `SQLiteQueryBuilder.appendWhere(...)`. The potential risk is particularly significant for exported providers or those that offer extensive read permissions. As a tester, you can investigate the behavior using the Android shell; a positive indication of an issue is when a query retrieves more rows than expected or circumvents filtering.
+
+```bash
+$ adb shell content query --uri content://org.owasp.mastestapp.provider/students
+Row: 0 id=1, name=Alice
+Row: 1 id=2, name=Bob
+Row: 2 id=3, name=Charlie
+```
+
+**Injection probe**(only if applicable, to detect unsafe string concatenation in selection logic)
+
+```bash
+$ content query --uri content://org.owasp.mastestapp.provider/students --where "name='Bob' OR '1'='1'"
+Row: 0 id=1, name=Alice
+Row: 1 id=2, name=Bob
+Row: 2 id=3, name=Charlie
+```
+
+If results exceed the intended filter, the content provider may be concatenating untrusted input instead of using parameterized selections.
+
+## Observe logs
+
+Use @MASTG-TOOL-0004 to look for `SQLiteException`, syntax errors, or provider log statements that indicate raw string concatenation or leaking SQL statements.
+
+```bash
+$ adb logcat | grep -i -E "sqlite|contentresolver|provider"
+```
diff --git a/tests-beta/android/MASVS-CODE/MASTG-TEST-02XX.md b/tests-beta/android/MASVS-CODE/MASTG-TEST-02XX.md
new file mode 100644
index 00000000000..38f1c1010d6
--- /dev/null
+++ b/tests-beta/android/MASVS-CODE/MASTG-TEST-02XX.md
@@ -0,0 +1,33 @@
+---
+title: SQL Injection in ContentProvider
+platform: android
+id: MASTG-TEST-02XX
+type: [static]
+weakness: MASWE-0086
+best-practices: [MASTG-BEST-XXXX]
+profiles: [L1, L2]
+---
+
+## Overview
+
+Android applications can share structured data via `ContentProvider` components. However, if these providers create SQL queries using untrusted input from URIs without adequate validation or parameterization, they risk becoming susceptible to SQL injection attacks.
+
+## Steps
+
+1. Run @MASTG-TOOL-0110 rule against the code file to detect SQL injection caused by unvalidated use of `Uri.getPathSegments()` inside `appendWhere()`.
+
+## Observation
+
+This code uses untrusted input from the URI path directly in a SQL query, enabling potential SQL injection.
+
+```java
+String id = uri.getPathSegments().get(1);
+qb.appendWhere("id=" + id);
+```
+
+## Evaluation
+
+The test fails if:
+
+- Untrusted user input (e.g., from `getPathSegments()`) is directly concatenated into SQL statements.
+- The app uses `appendWhere()` or builds queries unsafely without sanitization or parameterization.
diff --git a/tests/android/MASVS-CODE/MASTG-TEST-0025.md b/tests/android/MASVS-CODE/MASTG-TEST-0025.md
index 09d2b06c98f..16a13ed9ffe 100644
--- a/tests/android/MASVS-CODE/MASTG-TEST-0025.md
+++ b/tests/android/MASVS-CODE/MASTG-TEST-0025.md
@@ -9,6 +9,9 @@ masvs_v1_levels:
- L1
- L2
profiles: [L1, L2]
+status: deprecated
+covered_by: [MASTG-TEST-0288]
+deprecation_note: New version available in MASTG V2
---
## Overview