-
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Port MASTG-TEST-0025: Testing for Injection Flaws (android) (by @appknox) #3424
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
ScreaMy7
wants to merge
8
commits into
OWASP:master
Choose a base branch
from
ScreaMy7:injection-flaw-draft
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 7 commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
0661ed6
draft
ScreaMy7 200cd94
md fix
ScreaMy7 ef93867
small fix
ScreaMy7 ff67773
fix test-beta
ScreaMy7 28a04d4
added weakness
ScreaMy7 903768b
added fix and best practise
ScreaMy7 52a416d
another fix
ScreaMy7 40896f1
added new tech and outputs.
ScreaMy7 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| --- | ||
| title: Prevent SQL Injection in ContentProviders | ||
| alias: prevent-sqli-contentprovider | ||
| id: MASTG-BEST-0015 | ||
| 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) | ||
|
|
||
| ``` | ||
|
|
||
| Refer to ["Protect against malicious input"](https://developer.android.com/guide/topics/providers/content-provider-basics#Injection) for more information. |
78 changes: 78 additions & 0 deletions
78
demos/android/MASVS-CODE/MASTG-DEMO-0062/AndroidManifest_reversed.xml
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| <?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"/> | ||
| <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="MASTG SQLi Test 2" | ||
| android:debuggable="true" | ||
| android:testOnly="true" | ||
| android:allowBackup="true" | ||
| android:supportsRtl="true" | ||
| android:extractNativeLibs="false" | ||
| android:appComponentFactory="androidx.core.app.CoreComponentFactory"> | ||
| <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> | ||
| <provider | ||
| android:name="org.owasp.mastestapp.MastgTest.StudentProvider" | ||
| android:exported="true" | ||
| android:authorities="org.owasp.mastestapp.provider"/> | ||
| <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> |
35 changes: 35 additions & 0 deletions
35
demos/android/MASVS-CODE/MASTG-DEMO-0062/MASTG-DEMO-0062.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| --- | ||
| platform: android | ||
| title: Injection flaws in android Content providers | ||
| id: MASTG-DEMO-0061 | ||
| code: [kotlin] | ||
| test: MASTG-TEST-0288 | ||
| status: new | ||
| profiles: [L1, L2] | ||
ScreaMy7 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| --- | ||
|
|
||
| ### Sample | ||
|
|
||
| The following code implements a vulnerable `ContentProvider` that appends user-controlled input from the URI path directly into a SQL. | ||
|
|
||
| {{ MastgTest.kt # MastgTest_reversed.kt }} | ||
|
|
||
| ### 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: | ||
|
|
||
| `content query --uri content://org.owasp.mastestapp.provider/students --where "name='Bob' OR '1'='1'"` | ||
ScreaMy7 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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<String>?, | ||
| selection: String?, | ||
| selectionArgs: Array<String>?, | ||
| 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<out String>?): Int = 0 | ||
| override fun update(uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<out String>?): 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) | ||
| } | ||
| } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.