From 86aa179258d9ac911565559363e43b254f1b982a Mon Sep 17 00:00:00 2001 From: Jeel Date: Tue, 3 Jun 2025 18:18:49 +0530 Subject: [PATCH 01/16] demo added draft --- .../MASTG-DEMO-0054/MASTG-DEMO-0054.md | 34 +++++++ .../MASVS-CODE/MASTG-DEMO-0054/MastgTest.kt | 49 ++++++++++ .../MASTG-DEMO-0054/MastgTest_reversed.java | 92 +++++++++++++++++++ .../MASVS-CODE/MASTG-DEMO-0054/output.txt | 24 +++++ .../android/MASVS-CODE/MASTG-DEMO-0054/run.sh | 1 + ...oid-Local-Storage-for-Input-Validation.yml | 12 +++ .../android/MASVS-CODE/MASTG-TEST-0281.md | 23 +++++ tests/android/MASVS-CODE/MASTG-TEST-0002.md | 2 + 8 files changed, 237 insertions(+) create mode 100644 demos/android/MASVS-CODE/MASTG-DEMO-0054/MASTG-DEMO-0054.md create mode 100644 demos/android/MASVS-CODE/MASTG-DEMO-0054/MastgTest.kt create mode 100644 demos/android/MASVS-CODE/MASTG-DEMO-0054/MastgTest_reversed.java create mode 100644 demos/android/MASVS-CODE/MASTG-DEMO-0054/output.txt create mode 100644 demos/android/MASVS-CODE/MASTG-DEMO-0054/run.sh create mode 100644 rules/mastg-android-Local-Storage-for-Input-Validation.yml create mode 100644 tests-beta/android/MASVS-CODE/MASTG-TEST-0281.md diff --git a/demos/android/MASVS-CODE/MASTG-DEMO-0054/MASTG-DEMO-0054.md b/demos/android/MASVS-CODE/MASTG-DEMO-0054/MASTG-DEMO-0054.md new file mode 100644 index 00000000000..478bcb3ab22 --- /dev/null +++ b/demos/android/MASVS-CODE/MASTG-DEMO-0054/MASTG-DEMO-0054.md @@ -0,0 +1,34 @@ +--- +platform: android +title: Testing Local Storage for Input Validation with Semgrep +id: MASTG-DEMO-0054 +code: [kotlin] +test: MASTG-TEST-0281 +--- + +### Sample + +This code demonstrates improper use of local storage via `SharedPreferences.putString()` and `getString()` for storing sensitive or user-influenced data, including potentially dangerous input like HTML and JSON. + +{{ MastgTest.kt # MastgTest_reversed.java }} + +### Steps + +Let's run @MASTG-TOOL-0110 rules against the sample code. + +{{ ../../../../rules/mastg-android-Local-Storage-for-Input-Validation.yml }} + +{{ run.sh }} + +### Observation + +The output file shows usages of string-based local storage in the code. + +{{ output.txt }} + +### Evaluation + +The test fails because `putString()` and `getString()` were used to store and retrieve structured or potentially user-controlled data, such as: + +- Line 48, 49, 67, 68 contains the `putString()` +- Line 81, 83 contains the `getString()` diff --git a/demos/android/MASVS-CODE/MASTG-DEMO-0054/MastgTest.kt b/demos/android/MASVS-CODE/MASTG-DEMO-0054/MastgTest.kt new file mode 100644 index 00000000000..2c60b7f2048 --- /dev/null +++ b/demos/android/MASVS-CODE/MASTG-DEMO-0054/MastgTest.kt @@ -0,0 +1,49 @@ +package org.owasp.mastestapp + +import android.content.Context +import android.content.SharedPreferences +import android.util.Log + +class MastgTest(private val context: Context) { + private val sharedPref: SharedPreferences = + context.getSharedPreferences("VulnerablePrefs", Context.MODE_PRIVATE) + + // Store initial data + fun storeInitialData(): String { + try { + sharedPref.edit().apply { + putInt("userLevel", 1) + putBoolean("isAdmin", false) + putString("userData", """{"name":"user","admin":false}""") + putString("htmlContent", "Welcome user") + putStringSet("permissions", setOf("read", "basic_write")) + apply() + } + return "Initial data stored successfully" + } catch (e: Exception) { + Log.e("MASTG-TEST", "Storage error: ${e.message}") + return "Error storing data: ${e.message}" + } + } + + // Read potentially tampered data + fun readTamperedData(): String { + return try { + """ + TAMPERED DATA: + -------------------------- + USER LEVEL: ${sharedPref.getInt("userLevel", 0)} + IS ADMIN: ${sharedPref.getBoolean("isAdmin", false)} + + USER JSON: ${sharedPref.getString("userData", "DEFAULT")} + HTML CONTENT: ${sharedPref.getString("htmlContent", "DEFAULT")} + + PERMISSIONS: ${sharedPref.getStringSet("permissions", setOf("DEFAULT"))} + -------------------------- + """.trimIndent() + } catch (e: Exception) { + Log.e("MASTG-TEST", "Read error: ${e.message}") + "Error reading tampered data: ${e.message}" + } + } +} \ No newline at end of file diff --git a/demos/android/MASVS-CODE/MASTG-DEMO-0054/MastgTest_reversed.java b/demos/android/MASVS-CODE/MASTG-DEMO-0054/MastgTest_reversed.java new file mode 100644 index 00000000000..1b195ffb715 --- /dev/null +++ b/demos/android/MASVS-CODE/MASTG-DEMO-0054/MastgTest_reversed.java @@ -0,0 +1,92 @@ +package org.owasp.mastestapp; + +import android.content.Context; +import android.content.SharedPreferences; +import android.util.Log; +import java.util.HashSet; +import java.util.Set; +import kotlin.Metadata; +import kotlin.collections.CollectionsKt; +import kotlin.collections.SetsKt; +import kotlin.jvm.internal.Intrinsics; + +/* compiled from: MastgTest.kt */ +@Metadata(d1 = {"\u0000&\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0000\n\u0002\u0018\u0002\n\u0002\b\u0003\n\u0002\u0018\u0002\n\u0000\n\u0002\u0010\u000e\n\u0000\n\u0002\u0010\u0002\n\u0002\b\u0005\b\u0007\u0018\u00002\u00020\u0001B\u000f\u0012\u0006\u0010\u0002\u001a\u00020\u0003¢\u0006\u0004\b\u0004\u0010\u0005J\u0006\u0010\b\u001a\u00020\tJ\b\u0010\n\u001a\u00020\u000bH\u0002J\b\u0010\f\u001a\u00020\u000bH\u0002J\b\u0010\r\u001a\u00020\u000bH\u0002J\b\u0010\u000e\u001a\u00020\u000bH\u0002J\b\u0010\u000f\u001a\u00020\tH\u0002R\u000e\u0010\u0002\u001a\u00020\u0003X\u0082\u0004¢\u0006\u0002\n\u0000R\u000e\u0010\u0006\u001a\u00020\u0007X\u0082\u0004¢\u0006\u0002\n\u0000¨\u0006\u0010"}, d2 = {"Lorg/owasp/mastestapp/MastgTest;", "", "context", "Landroid/content/Context;", "", "(Landroid/content/Context;)V", "sharedPref", "Landroid/content/SharedPreferences;", "mastgTest", "", "storePrimitiveTypes", "", "storeStrings", "storeStringSet", "simulateTampering", "retrieveAndVerifyData", "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; + private final SharedPreferences sharedPref; + + public MastgTest(Context context) { + Intrinsics.checkNotNullParameter(context, "context"); + this.context = context; + SharedPreferences sharedPreferences = this.context.getSharedPreferences("DemoPrefs", 0); + Intrinsics.checkNotNullExpressionValue(sharedPreferences, "getSharedPreferences(...)"); + this.sharedPref = sharedPreferences; + } + + public final String mastgTest() { + storePrimitiveTypes(); + storeStrings(); + storeStringSet(); + simulateTampering(); + return retrieveAndVerifyData(); + } + + private final void storePrimitiveTypes() { + SharedPreferences.Editor $this$storePrimitiveTypes_u24lambda_u240 = this.sharedPref.edit(); + $this$storePrimitiveTypes_u24lambda_u240.putInt("appLaunchCount", 5); + $this$storePrimitiveTypes_u24lambda_u240.putBoolean("isPremiumUser", false); + $this$storePrimitiveTypes_u24lambda_u240.putLong("lastLoginTime", System.currentTimeMillis()); + $this$storePrimitiveTypes_u24lambda_u240.apply(); + Log.d("MASTG-TEST", "Stored primitive types"); + } + + private final void storeStrings() { + SharedPreferences.Editor $this$storeStrings_u24lambda_u241 = this.sharedPref.edit(); + $this$storeStrings_u24lambda_u241.putString("userJson", "{\"name\":\"John\",\"admin\":false}"); + $this$storeStrings_u24lambda_u241.putString("htmlContent", "
Safe content
"); + $this$storeStrings_u24lambda_u241.apply(); + Log.d("MASTG-TEST", "Stored strings"); + } + + private final void storeStringSet() { + HashSet stringSet = new HashSet(); + stringSet.add("normal_item"); + stringSet.add("another_item"); + this.sharedPref.edit().putStringSet("itemSet", stringSet).apply(); + Log.d("MASTG-TEST", "Stored string set"); + } + + private final void simulateTampering() { + SharedPreferences prefsFile = this.context.getSharedPreferences("DemoPrefs", 0); + SharedPreferences.Editor $this$simulateTampering_u24lambda_u242 = prefsFile.edit(); + $this$simulateTampering_u24lambda_u242.putInt("appLaunchCount", 9999); + $this$simulateTampering_u24lambda_u242.putBoolean("isPremiumUser", true); + $this$simulateTampering_u24lambda_u242.putString("userJson", "{\"name\":\"John\",\"admin\":true}"); + $this$simulateTampering_u24lambda_u242.putString("htmlContent", ""); + HashSet maliciousSet = new HashSet(); + maliciousSet.add("normal_item"); + maliciousSet.add("malicious_payload"); + $this$simulateTampering_u24lambda_u242.putStringSet("itemSet", maliciousSet); + $this$simulateTampering_u24lambda_u242.apply(); + Log.d("MASTG-TEST", "Simulated tampering with all data types"); + } + + private final String retrieveAndVerifyData() { + StringBuilder result = new StringBuilder(); + result.append("Primitive Types:\n").append("Launch Count: " + this.sharedPref.getInt("appLaunchCount", 0) + "\n").append("Is Premium: " + this.sharedPref.getBoolean("isPremiumUser", false) + "\n\n"); + result.append("Strings:\n"); + String userJson = this.sharedPref.getString("userJson", ""); + result.append("User JSON: " + userJson + "\n"); + String htmlContent = this.sharedPref.getString("htmlContent", ""); + result.append("HTML Content: " + htmlContent + "\n\n"); + result.append("String Set:\n"); + Set itemSet = this.sharedPref.getStringSet("itemSet", SetsKt.emptySet()); + result.append("Items: " + (itemSet != null ? CollectionsKt.joinToString$default(itemSet, null, null, null, 0, null, null, 63, null) : null)); + String sb = result.toString(); + Intrinsics.checkNotNullExpressionValue(sb, "toString(...)"); + return sb; + } +} \ No newline at end of file diff --git a/demos/android/MASVS-CODE/MASTG-DEMO-0054/output.txt b/demos/android/MASVS-CODE/MASTG-DEMO-0054/output.txt new file mode 100644 index 00000000000..ea616585938 --- /dev/null +++ b/demos/android/MASVS-CODE/MASTG-DEMO-0054/output.txt @@ -0,0 +1,24 @@ + + +┌─────────────────┐ +│ 6 Code Findings │ +└─────────────────┘ + +  MASTestApp-Android/output/MastgTest_reversed.java + ❯❱ mastg-android-Local-Storage-for-Input-Validation + [MASVS-CODE-4] The application make use of Object Serialization in the code. + + 48┆ $this$storeStrings_u24lambda_u241.putString("userJson", + "{\"name\":\"John\",\"admin\":false}"); + ⋮┆---------------------------------------- + 49┆ $this$storeStrings_u24lambda_u241.putString("htmlContent", "
Safe content
"); + ⋮┆---------------------------------------- + 67┆ $this$simulateTampering_u24lambda_u242.putString("userJson", + "{\"name\":\"John\",\"admin\":true}"); + ⋮┆---------------------------------------- + 68┆ $this$simulateTampering_u24lambda_u242.putString("htmlContent", + ""); + ⋮┆---------------------------------------- + 81┆ String userJson = this.sharedPref.getString("userJson", ""); + ⋮┆---------------------------------------- + 83┆ String htmlContent = this.sharedPref.getString("htmlContent", ""); diff --git a/demos/android/MASVS-CODE/MASTG-DEMO-0054/run.sh b/demos/android/MASVS-CODE/MASTG-DEMO-0054/run.sh new file mode 100644 index 00000000000..4ae296ea933 --- /dev/null +++ b/demos/android/MASVS-CODE/MASTG-DEMO-0054/run.sh @@ -0,0 +1 @@ +NO_COLOR=true semgrep -c ../../../../rules/mastg-android-Local-Storage-for-Input-Validation.yml ./MastgTest_reversed.java --text -o output.txt \ No newline at end of file diff --git a/rules/mastg-android-Local-Storage-for-Input-Validation.yml b/rules/mastg-android-Local-Storage-for-Input-Validation.yml new file mode 100644 index 00000000000..e3c7c865d88 --- /dev/null +++ b/rules/mastg-android-Local-Storage-for-Input-Validation.yml @@ -0,0 +1,12 @@ +rules: + - id: mastg-android-Local-Storage-for-Input-Validation + severity: WARNING + languages: + - java + metadata: + summary: This rule looks for use of Local Storage for Input Validation. + message: "[MASVS-CODE-4] The application make use of Object Serialization in the code." + patterns: + - pattern-either: + - pattern: $EDITOR.putString($KEY, $VALUE) + - pattern: $PREF.getString($KEY, $DEFAULT) diff --git a/tests-beta/android/MASVS-CODE/MASTG-TEST-0281.md b/tests-beta/android/MASVS-CODE/MASTG-TEST-0281.md new file mode 100644 index 00000000000..d18d181117a --- /dev/null +++ b/tests-beta/android/MASVS-CODE/MASTG-TEST-0281.md @@ -0,0 +1,23 @@ +--- +title: Testing Local Storage for Input Validation with Semgrep +platform: android +id: MASTG-TEST-0281 +type: [static] +weakness: MASWE-0088 +--- + +## Overview + +Android provides `SharedPreferences` for storing key-value pairs of primitive data and strings. When structured data such as JSON or HTML is stored using `putString()` or `putStringSet()` without proper validation, it can lead to security issues like tampering or injection. This is particularly risky if the stored data is later trusted and used directly by the app. + +## Steps + +1. Run a static analysis tool such as @MASTG-TOOL-0110 on the code and look for uses of the `putString`, `getString`, `putStringSet`, and `getStringSet` methods from the `SharedPreferences` API. + +## Observation + +The output file shows usages of object persistence using string-based storage (`putString`, `getString`, etc.) in the code. + +## Evaluation + +The test fails if `putString()`, `putStringSet()`, `getString()` or `getStringSet()` are found in the code and used to store or retrieve JSON, HTML, or other potentially unsafe input. diff --git a/tests/android/MASVS-CODE/MASTG-TEST-0002.md b/tests/android/MASVS-CODE/MASTG-TEST-0002.md index 93f968ab710..4499c8e4ff1 100644 --- a/tests/android/MASVS-CODE/MASTG-TEST-0002.md +++ b/tests/android/MASVS-CODE/MASTG-TEST-0002.md @@ -8,6 +8,8 @@ title: Testing Local Storage for Input Validation masvs_v1_levels: - L1 - L2 +status: deprecated +deprecation_note: New version available in MASTG V2 --- ## Overview From 69ebf81063cfc0ca7b2ad53368aea7765a7e423e Mon Sep 17 00:00:00 2001 From: Jeel Date: Wed, 4 Jun 2025 13:41:49 +0530 Subject: [PATCH 02/16] fix grammar --- demos/android/MASVS-CODE/MASTG-DEMO-0054/MASTG-DEMO-0054.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/demos/android/MASVS-CODE/MASTG-DEMO-0054/MASTG-DEMO-0054.md b/demos/android/MASVS-CODE/MASTG-DEMO-0054/MASTG-DEMO-0054.md index 478bcb3ab22..c08e663c4b6 100644 --- a/demos/android/MASVS-CODE/MASTG-DEMO-0054/MASTG-DEMO-0054.md +++ b/demos/android/MASVS-CODE/MASTG-DEMO-0054/MASTG-DEMO-0054.md @@ -30,5 +30,5 @@ The output file shows usages of string-based local storage in the code. The test fails because `putString()` and `getString()` were used to store and retrieve structured or potentially user-controlled data, such as: -- Line 48, 49, 67, 68 contains the `putString()` -- Line 81, 83 contains the `getString()` +- Line 48, 49, 67, 68 contains the `putString()`. +- Line 81, 83 contains the `getString()`. From 8c4060bea62013721682d809688e67e52a3f6030 Mon Sep 17 00:00:00 2001 From: Jeel Date: Thu, 5 Jun 2025 16:42:32 +0530 Subject: [PATCH 03/16] mastgTest file update --- .../MASVS-CODE/MASTG-DEMO-0054/MastgTest.kt | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/demos/android/MASVS-CODE/MASTG-DEMO-0054/MastgTest.kt b/demos/android/MASVS-CODE/MASTG-DEMO-0054/MastgTest.kt index 2c60b7f2048..e841853c9ee 100644 --- a/demos/android/MASVS-CODE/MASTG-DEMO-0054/MastgTest.kt +++ b/demos/android/MASVS-CODE/MASTG-DEMO-0054/MastgTest.kt @@ -46,4 +46,43 @@ class MastgTest(private val context: Context) { "Error reading tampered data: ${e.message}" } } + + fun mastgTest(): String { + // Store initial data + val storeResult = try { + sharedPref.edit().apply { + putInt("userLevel", 1) + putBoolean("isAdmin", false) + putString("userData", """{"name":"user","admin":false}""") + putString("htmlContent", "Welcome user") + putStringSet("permissions", setOf("read", "basic_write")) + apply() + } + "Initial data stored successfully" + } catch (e: Exception) { + Log.e("MASTG-TEST", "Storage error: ${e.message}") + "Error storing data: ${e.message}" + } + + // Read potentially tampered data + val readResult = try { + """ + TAMPERED DATA: + -------------------------- + USER LEVEL: ${sharedPref.getInt("userLevel", 0)} + IS ADMIN: ${sharedPref.getBoolean("isAdmin", false)} + + USER JSON: ${sharedPref.getString("userData", "DEFAULT")} + HTML CONTENT: ${sharedPref.getString("htmlContent", "DEFAULT")} + + PERMISSIONS: ${sharedPref.getStringSet("permissions", setOf("DEFAULT"))} + -------------------------- + """.trimIndent() + } catch (e: Exception) { + Log.e("MASTG-TEST", "Read error: ${e.message}") + "Error reading tampered data: ${e.message}" + } + + return "$storeResult\n\n$readResult" + } } \ No newline at end of file From 96a766f145a320477797a3a94088d2255bb4ed14 Mon Sep 17 00:00:00 2001 From: Jeel Date: Thu, 5 Jun 2025 16:48:39 +0530 Subject: [PATCH 04/16] updated test file --- tests-beta/android/MASVS-CODE/MASTG-TEST-0281.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests-beta/android/MASVS-CODE/MASTG-TEST-0281.md b/tests-beta/android/MASVS-CODE/MASTG-TEST-0281.md index d18d181117a..dec3698026c 100644 --- a/tests-beta/android/MASVS-CODE/MASTG-TEST-0281.md +++ b/tests-beta/android/MASVS-CODE/MASTG-TEST-0281.md @@ -8,11 +8,11 @@ weakness: MASWE-0088 ## Overview -Android provides `SharedPreferences` for storing key-value pairs of primitive data and strings. When structured data such as JSON or HTML is stored using `putString()` or `putStringSet()` without proper validation, it can lead to security issues like tampering or injection. This is particularly risky if the stored data is later trusted and used directly by the app. +Android provides `SharedPreferences` for storing key-value pairs of primitive data and strings. When structured data such as JSON or HTML is stored using `putString()` & `getString()` without proper validation, it can lead to security issues like tampering or injection. This is particularly risky if the stored data is later trusted and used directly by the app. ## Steps -1. Run a static analysis tool such as @MASTG-TOOL-0110 on the code and look for uses of the `putString`, `getString`, `putStringSet`, and `getStringSet` methods from the `SharedPreferences` API. +1. Run a static analysis tool such as @MASTG-TOOL-0110 on the code and look for uses of the `putString` and `getString` methods from the `SharedPreferences` API. ## Observation @@ -20,4 +20,4 @@ The output file shows usages of object persistence using string-based storage (` ## Evaluation -The test fails if `putString()`, `putStringSet()`, `getString()` or `getStringSet()` are found in the code and used to store or retrieve JSON, HTML, or other potentially unsafe input. +The test fails if `putString()` and `getString()` are found in the code and used to store or retrieve JSON, HTML, or other potentially unsafe input. From 3e1c45a39023e89c14894ecef275bdec9e635d84 Mon Sep 17 00:00:00 2001 From: Jeel Patel <84757990+jeel38@users.noreply.github.com> Date: Mon, 9 Jun 2025 16:39:43 +0530 Subject: [PATCH 05/16] Update tests-beta/android/MASVS-CODE/MASTG-TEST-0281.md Co-authored-by: pruDhv! <58649792+sk3l10x1ng@users.noreply.github.com> --- tests-beta/android/MASVS-CODE/MASTG-TEST-0281.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests-beta/android/MASVS-CODE/MASTG-TEST-0281.md b/tests-beta/android/MASVS-CODE/MASTG-TEST-0281.md index dec3698026c..c2bef7aacdf 100644 --- a/tests-beta/android/MASVS-CODE/MASTG-TEST-0281.md +++ b/tests-beta/android/MASVS-CODE/MASTG-TEST-0281.md @@ -20,4 +20,4 @@ The output file shows usages of object persistence using string-based storage (` ## Evaluation -The test fails if `putString()` and `getString()` are found in the code and used to store or retrieve JSON, HTML, or other potentially unsafe input. +The test fails if the `putString()` and `getString()` was found in the code. From 2a90252fab8d7ab34d40917c030d7e1e4c25f9b6 Mon Sep 17 00:00:00 2001 From: Jeel Date: Mon, 9 Jun 2025 17:37:06 +0530 Subject: [PATCH 06/16] updated changes --- .../MASVS-CODE/MASTG-DEMO-0054/MASTG-DEMO-0054.md | 8 ++++---- demos/android/MASVS-CODE/MASTG-DEMO-0054/output.txt | 6 +++--- demos/android/MASVS-CODE/MASTG-DEMO-0054/run.sh | 2 +- ...astg-android-Local-Storage-for-Input-Validation.yml | 4 ++-- tests-beta/android/MASVS-CODE/MASTG-TEST-0281.md | 10 +++++----- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/demos/android/MASVS-CODE/MASTG-DEMO-0054/MASTG-DEMO-0054.md b/demos/android/MASVS-CODE/MASTG-DEMO-0054/MASTG-DEMO-0054.md index c08e663c4b6..e1197bbaaad 100644 --- a/demos/android/MASVS-CODE/MASTG-DEMO-0054/MASTG-DEMO-0054.md +++ b/demos/android/MASVS-CODE/MASTG-DEMO-0054/MASTG-DEMO-0054.md @@ -1,6 +1,6 @@ --- platform: android -title: Testing Local Storage for Input Validation with Semgrep +title: Local Storage for Input Validation with semgrep id: MASTG-DEMO-0054 code: [kotlin] test: MASTG-TEST-0281 @@ -8,7 +8,7 @@ test: MASTG-TEST-0281 ### Sample -This code demonstrates improper use of local storage via `SharedPreferences.putString()` and `getString()` for storing sensitive or user-influenced data, including potentially dangerous input like HTML and JSON. +The code snippet shows the improper use of local storage via `putString()` and `getString()` for storing sensitive data. {{ MastgTest.kt # MastgTest_reversed.java }} @@ -16,7 +16,7 @@ This code demonstrates improper use of local storage via `SharedPreferences.putS Let's run @MASTG-TOOL-0110 rules against the sample code. -{{ ../../../../rules/mastg-android-Local-Storage-for-Input-Validation.yml }} +{{ ../../../../rules/mastg-android-local-storage-for-input-validation.yml }} {{ run.sh }} @@ -28,7 +28,7 @@ The output file shows usages of string-based local storage in the code. ### Evaluation -The test fails because `putString()` and `getString()` were used to store and retrieve structured or potentially user-controlled data, such as: +The test fails because `putString()` and `getString()` were found in the code. - Line 48, 49, 67, 68 contains the `putString()`. - Line 81, 83 contains the `getString()`. diff --git a/demos/android/MASVS-CODE/MASTG-DEMO-0054/output.txt b/demos/android/MASVS-CODE/MASTG-DEMO-0054/output.txt index ea616585938..bdebd4ce11a 100644 --- a/demos/android/MASVS-CODE/MASTG-DEMO-0054/output.txt +++ b/demos/android/MASVS-CODE/MASTG-DEMO-0054/output.txt @@ -5,9 +5,9 @@ └─────────────────┘  MASTestApp-Android/output/MastgTest_reversed.java - ❯❱ mastg-android-Local-Storage-for-Input-Validation - [MASVS-CODE-4] The application make use of Object Serialization in the code. - + ❯❱ mastg-android-local-storage-for-input-validation + [MASVS-CODE-4] The application make use of input validation in the code. + 48┆ $this$storeStrings_u24lambda_u241.putString("userJson", "{\"name\":\"John\",\"admin\":false}"); ⋮┆---------------------------------------- diff --git a/demos/android/MASVS-CODE/MASTG-DEMO-0054/run.sh b/demos/android/MASVS-CODE/MASTG-DEMO-0054/run.sh index 4ae296ea933..4f90374e65e 100644 --- a/demos/android/MASVS-CODE/MASTG-DEMO-0054/run.sh +++ b/demos/android/MASVS-CODE/MASTG-DEMO-0054/run.sh @@ -1 +1 @@ -NO_COLOR=true semgrep -c ../../../../rules/mastg-android-Local-Storage-for-Input-Validation.yml ./MastgTest_reversed.java --text -o output.txt \ No newline at end of file +NO_COLOR=true semgrep -c ../../../../rules/mastg-android-local-storage-for-input-validation.yml ./MastgTest_reversed.java --text -o output.txt \ No newline at end of file diff --git a/rules/mastg-android-Local-Storage-for-Input-Validation.yml b/rules/mastg-android-Local-Storage-for-Input-Validation.yml index e3c7c865d88..4cf2ee995e6 100644 --- a/rules/mastg-android-Local-Storage-for-Input-Validation.yml +++ b/rules/mastg-android-Local-Storage-for-Input-Validation.yml @@ -1,11 +1,11 @@ rules: - - id: mastg-android-Local-Storage-for-Input-Validation + - id: mastg-android-local-storage-for-input-validation severity: WARNING languages: - java metadata: summary: This rule looks for use of Local Storage for Input Validation. - message: "[MASVS-CODE-4] The application make use of Object Serialization in the code." + message: "[MASVS-CODE-4] The application make use of input validation in the code." patterns: - pattern-either: - pattern: $EDITOR.putString($KEY, $VALUE) diff --git a/tests-beta/android/MASVS-CODE/MASTG-TEST-0281.md b/tests-beta/android/MASVS-CODE/MASTG-TEST-0281.md index c2bef7aacdf..20971e85fdb 100644 --- a/tests-beta/android/MASVS-CODE/MASTG-TEST-0281.md +++ b/tests-beta/android/MASVS-CODE/MASTG-TEST-0281.md @@ -1,22 +1,22 @@ --- -title: Testing Local Storage for Input Validation with Semgrep +title: Use of Local Storage for Input Validation platform: android id: MASTG-TEST-0281 type: [static] -weakness: MASWE-0088 +weakness: MASWE-0082 --- ## Overview -Android provides `SharedPreferences` for storing key-value pairs of primitive data and strings. When structured data such as JSON or HTML is stored using `putString()` & `getString()` without proper validation, it can lead to security issues like tampering or injection. This is particularly risky if the stored data is later trusted and used directly by the app. +Android offers `SharedPreferences` for saving key-value pairs of basic data types and strings. When you store structured data like JSON or HTML using `putString()` and `getString()` without adequate validation, it can result in security vulnerabilities such as tampering or injection. This becomes especially dangerous if the stored data is subsequently trusted and utilized directly by the application. ## Steps -1. Run a static analysis tool such as @MASTG-TOOL-0110 on the code and look for uses of the `putString` and `getString` methods from the `SharedPreferences` API. +1. Run a static analysis tool such as @MASTG-TOOL-0110 on the code and look for uses of the `putString` and `getString`. ## Observation -The output file shows usages of object persistence using string-based storage (`putString`, `getString`, etc.) in the code. +The output file shows usages of the input validation using `putString` and `getString` in the code. ## Evaluation From d31713cd171cedad961549363866b4c138828b9c Mon Sep 17 00:00:00 2001 From: Jeel Date: Mon, 9 Jun 2025 18:08:50 +0530 Subject: [PATCH 07/16] updated changes --- demos/android/MASVS-CODE/MASTG-DEMO-0054/output.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/demos/android/MASVS-CODE/MASTG-DEMO-0054/output.txt b/demos/android/MASVS-CODE/MASTG-DEMO-0054/output.txt index bdebd4ce11a..f014d5020b2 100644 --- a/demos/android/MASVS-CODE/MASTG-DEMO-0054/output.txt +++ b/demos/android/MASVS-CODE/MASTG-DEMO-0054/output.txt @@ -4,8 +4,8 @@ │ 6 Code Findings │ └─────────────────┘ -  MASTestApp-Android/output/MastgTest_reversed.java - ❯❱ mastg-android-local-storage-for-input-validation + MASTestApp-Android/output/MastgTest_reversed.java + ❯❱ mastg-android-local-storage-for-input-validation [MASVS-CODE-4] The application make use of input validation in the code. 48┆ $this$storeStrings_u24lambda_u241.putString("userJson", From 970f5586e8bcb36764b19f96779ed12e4ac18daa Mon Sep 17 00:00:00 2001 From: Jeel Date: Mon, 9 Jun 2025 18:11:17 +0530 Subject: [PATCH 08/16] updated changes done --- demos/android/MASVS-CODE/MASTG-DEMO-0054/MASTG-DEMO-0054.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demos/android/MASVS-CODE/MASTG-DEMO-0054/MASTG-DEMO-0054.md b/demos/android/MASVS-CODE/MASTG-DEMO-0054/MASTG-DEMO-0054.md index e1197bbaaad..ea3e9b77918 100644 --- a/demos/android/MASVS-CODE/MASTG-DEMO-0054/MASTG-DEMO-0054.md +++ b/demos/android/MASVS-CODE/MASTG-DEMO-0054/MASTG-DEMO-0054.md @@ -22,7 +22,7 @@ Let's run @MASTG-TOOL-0110 rules against the sample code. ### Observation -The output file shows usages of string-based local storage in the code. +The output file shows usages of string based local storage in the code. {{ output.txt }} From a323683d26da7a662602c4b20e73f789a83804b2 Mon Sep 17 00:00:00 2001 From: Jeel Date: Tue, 10 Jun 2025 13:44:13 +0530 Subject: [PATCH 09/16] updated changes test file --- tests/android/MASVS-CODE/MASTG-TEST-0002.md | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/android/MASVS-CODE/MASTG-TEST-0002.md b/tests/android/MASVS-CODE/MASTG-TEST-0002.md index 4499c8e4ff1..76f1669a7fe 100644 --- a/tests/android/MASVS-CODE/MASTG-TEST-0002.md +++ b/tests/android/MASVS-CODE/MASTG-TEST-0002.md @@ -9,6 +9,7 @@ masvs_v1_levels: - L1 - L2 status: deprecated +covered_by: [MASTG-TEST-0281] deprecation_note: New version available in MASTG V2 --- From e1a009979df062f66a098cfd4cd7d05c8c635792 Mon Sep 17 00:00:00 2001 From: Jeel Date: Tue, 10 Jun 2025 14:04:23 +0530 Subject: [PATCH 10/16] added profiles --- tests-beta/android/MASVS-CODE/MASTG-TEST-0281.md | 1 + 1 file changed, 1 insertion(+) diff --git a/tests-beta/android/MASVS-CODE/MASTG-TEST-0281.md b/tests-beta/android/MASVS-CODE/MASTG-TEST-0281.md index 20971e85fdb..c29f95d1cc9 100644 --- a/tests-beta/android/MASVS-CODE/MASTG-TEST-0281.md +++ b/tests-beta/android/MASVS-CODE/MASTG-TEST-0281.md @@ -4,6 +4,7 @@ platform: android id: MASTG-TEST-0281 type: [static] weakness: MASWE-0082 +profiles: [L1, L2] --- ## Overview From 3bb04a9a8754c370bef2161bb6015c6c3588c9d5 Mon Sep 17 00:00:00 2001 From: Jeel Date: Thu, 31 Jul 2025 17:45:44 +0530 Subject: [PATCH 11/16] Updated-Demo --- .../MASTG-DEMO-0054/MASTG-DEMO-0054.md | 10 +- .../MASVS-CODE/MASTG-DEMO-0054/MastgTest.kt | 223 ++++++++++++------ .../MASTG-DEMO-0054/MastgTest_reversed.java | 111 +++------ .../MASVS-CODE/MASTG-DEMO-0054/output.txt | 31 +-- .../android/MASVS-CODE/MASTG-DEMO-0054/run.sh | 0 ...oid-Local-Storage-for-Input-Validation.yml | 9 +- .../android/MASVS-CODE/MASTG-TEST-0281.md | 8 +- 7 files changed, 216 insertions(+), 176 deletions(-) mode change 100644 => 100755 demos/android/MASVS-CODE/MASTG-DEMO-0054/run.sh diff --git a/demos/android/MASVS-CODE/MASTG-DEMO-0054/MASTG-DEMO-0054.md b/demos/android/MASVS-CODE/MASTG-DEMO-0054/MASTG-DEMO-0054.md index ea3e9b77918..2a72610b17a 100644 --- a/demos/android/MASVS-CODE/MASTG-DEMO-0054/MASTG-DEMO-0054.md +++ b/demos/android/MASVS-CODE/MASTG-DEMO-0054/MASTG-DEMO-0054.md @@ -4,11 +4,12 @@ title: Local Storage for Input Validation with semgrep id: MASTG-DEMO-0054 code: [kotlin] test: MASTG-TEST-0281 +profiles: [L1, L2] --- ### Sample -The code snippet shows the improper use of local storage via `putString()` and `getString()` for storing sensitive data. +The code snippet demonstrates an insecure use of `SharedPreferences` where data is loaded without a proper integrity check, which is a form of input validation for stored data. {{ MastgTest.kt # MastgTest_reversed.java }} @@ -22,13 +23,10 @@ Let's run @MASTG-TOOL-0110 rules against the sample code. ### Observation -The output file shows usages of string based local storage in the code. +The output file correctly identifies the vulnerable pattern where data is loaded without being validated. {{ output.txt }} ### Evaluation -The test fails because `putString()` and `getString()` were found in the code. - -- Line 48, 49, 67, 68 contains the `putString()`. -- Line 81, 83 contains the `getString()`. +The test fails because the rule detected that the app does NOT use an `HMAC` integrity check together with `SharedPreferences`. diff --git a/demos/android/MASVS-CODE/MASTG-DEMO-0054/MastgTest.kt b/demos/android/MASVS-CODE/MASTG-DEMO-0054/MastgTest.kt index e841853c9ee..d48e0a358a4 100644 --- a/demos/android/MASVS-CODE/MASTG-DEMO-0054/MastgTest.kt +++ b/demos/android/MASVS-CODE/MASTG-DEMO-0054/MastgTest.kt @@ -3,86 +3,171 @@ package org.owasp.mastestapp import android.content.Context import android.content.SharedPreferences import android.util.Log +import androidx.core.content.edit +import java.security.InvalidKeyException +import java.security.NoSuchAlgorithmException +import javax.crypto.Mac +import javax.crypto.spec.SecretKeySpec +/** + * This is the main test class that orchestrates the demonstration. + * It now contains all logic in a single class to simplify decompilation. + * It uses a two-step process to allow for manual tampering. + */ class MastgTest(private val context: Context) { - private val sharedPref: SharedPreferences = - context.getSharedPreferences("VulnerablePrefs", Context.MODE_PRIVATE) - - // Store initial data - fun storeInitialData(): String { - try { - sharedPref.edit().apply { - putInt("userLevel", 1) - putBoolean("isAdmin", false) - putString("userData", """{"name":"user","admin":false}""") - putString("htmlContent", "Welcome user") - putStringSet("permissions", setOf("read", "basic_write")) - apply() + + companion object { + private const val PREFS_NAME = "app_settings" + private const val HMAC_ALGORITHM = "HmacSHA256" + // WARNING: In a real application, this key should NOT be hardcoded. + // It should be stored securely, for instance, in the Android Keystore. + // For this self-contained demo, we hardcode it to illustrate the HMAC mechanism. + private const val SECRET_KEY = "this-is-a-very-secret-key-for-the-demo" + } + + /** + * Main test function that runs the setup or verification phase. + */ + fun mastgTest(): String { + val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) + + // Check if the initial setup has been performed. + if (!prefs.contains("setup_complete")) { + // --- FIRST-TIME EXECUTION: SETUP PHASE --- + // This block runs only once. + + // 1. Set up the insecure preference (without HMAC). + saveData("user_role_insecure", "user", useHmac = false) + + // 2. Set up the secure preference (with HMAC). + saveData("user_role_secure", "user", useHmac = true) + + // 3. Mark setup as complete so this block doesn't run again. + prefs.edit(commit = true) { + putBoolean("setup_complete", true) + } + + // 4. Return instructions for the user. + return "INITIAL SETUP COMPLETE.\n\n" + + "The role for both secure and insecure tests has been set to 'user'.\n\n" + + "ACTION REQUIRED:\n" + + "1. Use a file explorer or ADB shell on a rooted device.\n" + + "2. Go to: /data/data/org.owasp.mastestapp/shared_prefs/\n" + + "3. Open the file: app_settings.xml\n" + + "4. Change BOTH user values to admin.\n" + + "5. Save the file and run this test again to see the results." + + } else { + // --- SUBSEQUENT EXECUTION: VERIFICATION PHASE --- + // This block runs after the user has tampered with the file. + + val results = StringBuilder() + + // 1. Verify the 'fail' case (insecure) + results.append("--- VERIFYING SCENARIO 1: 'kind: fail' (No HMAC Protection) ---\n") + val insecureRole = loadData("user_role_insecure", "error", useHmac = false) + results.append("Loaded role from 'user_role_insecure': '$insecureRole'\n") + if (insecureRole == "admin") { + results.append(">> OUTCOME: VULNERABLE. The application accepted the tampered 'admin' role because there was no integrity check.\n") + } else { + results.append(">> OUTCOME: NOT EXPLOITED. The role is still '$insecureRole'. Please ensure you changed it to 'admin' in the XML file.\n") + } + + // 2. Verify the 'pass' case (secure) + results.append("\n--- VERIFYING SCENARIO 2: 'kind: pass' (HMAC Protection Enabled) ---\n") + val secureRole = loadData("user_role_secure", "tampering_detected", useHmac = true) + results.append("Loaded role from 'user_role_secure': '$secureRole'\n") + if (secureRole == "tampering_detected") { + results.append(">> OUTCOME: SECURE. The application detected that the data was tampered with and correctly rejected the invalid 'admin' role.\n") + } else if (secureRole == "admin") { + results.append(">> OUTCOME: UNEXPECTED. The role is 'admin', which means the HMAC check failed. This should not happen.\n") + } else { // secureRole == "user" + results.append(">> OUTCOME: NOT TAMPERED. The role is still '$secureRole', and its HMAC signature is valid.\n") } - return "Initial data stored successfully" - } catch (e: Exception) { - Log.e("MASTG-TEST", "Storage error: ${e.message}") - return "Error storing data: ${e.message}" + + results.append("\n\nTest complete. To run the setup again, please clear the application's data in Android Settings and restart the test.") + return results.toString() } } - // Read potentially tampered data - fun readTamperedData(): String { - return try { - """ - TAMPERED DATA: - -------------------------- - USER LEVEL: ${sharedPref.getInt("userLevel", 0)} - IS ADMIN: ${sharedPref.getBoolean("isAdmin", false)} - - USER JSON: ${sharedPref.getString("userData", "DEFAULT")} - HTML CONTENT: ${sharedPref.getString("htmlContent", "DEFAULT")} - - PERMISSIONS: ${sharedPref.getStringSet("permissions", setOf("DEFAULT"))} - -------------------------- - """.trimIndent() - } catch (e: Exception) { - Log.e("MASTG-TEST", "Read error: ${e.message}") - "Error reading tampered data: ${e.message}" + /** + * Saves a key-value pair. If HMAC is enabled, it also saves an integrity check value. + */ + private fun saveData(key: String, value: String, useHmac: Boolean) { + val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) + prefs.edit(commit = true) { + putString(key, value) + if (useHmac) { + val hmac = calculateHmac(value) + if (hmac != null) { + putString("${key}_hmac", hmac) + Log.d("MASTG-TEST", "Saved data with HMAC.") + } + } else { + Log.d("MASTG-TEST", "Saved data WITHOUT HMAC.") + } } } - fun mastgTest(): String { - // Store initial data - val storeResult = try { - sharedPref.edit().apply { - putInt("userLevel", 1) - putBoolean("isAdmin", false) - putString("userData", """{"name":"user","admin":false}""") - putString("htmlContent", "Welcome user") - putStringSet("permissions", setOf("read", "basic_write")) - apply() - } - "Initial data stored successfully" - } catch (e: Exception) { - Log.e("MASTG-TEST", "Storage error: ${e.message}") - "Error storing data: ${e.message}" + /** + * Loads data for a given key. If HMAC is enabled, it first verifies the data's integrity. + */ + private fun loadData(key: String, defaultValue: String, useHmac: Boolean): String { + val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) + val value = prefs.getString(key, null) ?: return defaultValue + + if (!useHmac) { + Log.d("MASTG-TEST", "Loaded data without HMAC check. Value is: $value") + return value } - // Read potentially tampered data - val readResult = try { - """ - TAMPERED DATA: - -------------------------- - USER LEVEL: ${sharedPref.getInt("userLevel", 0)} - IS ADMIN: ${sharedPref.getBoolean("isAdmin", false)} - - USER JSON: ${sharedPref.getString("userData", "DEFAULT")} - HTML CONTENT: ${sharedPref.getString("htmlContent", "DEFAULT")} - - PERMISSIONS: ${sharedPref.getStringSet("permissions", setOf("DEFAULT"))} - -------------------------- - """.trimIndent() - } catch (e: Exception) { - Log.e("MASTG-TEST", "Read error: ${e.message}") - "Error reading tampered data: ${e.message}" + val storedHmac = prefs.getString("${key}_hmac", null) + if (storedHmac == null) { + Log.w("MASTG-TEST", "HMAC verification failed: No HMAC found for key '$key'.") + return defaultValue } - return "$storeResult\n\n$readResult" + val calculatedHmac = calculateHmac(value) + + return if (storedHmac == calculatedHmac) { + Log.d("MASTG-TEST", "HMAC verification SUCCESS. Value is: $value") + value + } else { + Log.e("MASTG-TEST", "HMAC verification FAILED! Data has been tampered with.") + defaultValue + } + } + + /** + * Calculates the HMAC for a given piece of data. + */ + private fun calculateHmac(data: String): String? { + return try { + val mac = Mac.getInstance(HMAC_ALGORITHM) + val secretKeySpec = SecretKeySpec(SECRET_KEY.toByteArray(), HMAC_ALGORITHM) + mac.init(secretKeySpec) + val hmacBytes = mac.doFinal(data.toByteArray()) + bytesToHex(hmacBytes) + } catch (e: NoSuchAlgorithmException) { + Log.e("MASTG-TEST", "HMAC algorithm not found", e) + null + } catch (e: InvalidKeyException) { + Log.e("MASTG-TEST", "Invalid HMAC key", e) + null + } + } + + /** + * Helper function to convert a byte array to a hexadecimal string. + */ + private fun bytesToHex(bytes: ByteArray): String { + val hexChars = "0123456789abcdef" + val result = StringBuilder(bytes.size * 2) + bytes.forEach { + val i = it.toInt() + result.append(hexChars[i shr 4 and 0x0f]) + result.append(hexChars[i and 0x0f]) + } + return result.toString() } -} \ No newline at end of file +} diff --git a/demos/android/MASVS-CODE/MASTG-DEMO-0054/MastgTest_reversed.java b/demos/android/MASVS-CODE/MASTG-DEMO-0054/MastgTest_reversed.java index 1b195ffb715..93fa2000f9a 100644 --- a/demos/android/MASVS-CODE/MASTG-DEMO-0054/MastgTest_reversed.java +++ b/demos/android/MASVS-CODE/MASTG-DEMO-0054/MastgTest_reversed.java @@ -2,91 +2,58 @@ import android.content.Context; import android.content.SharedPreferences; -import android.util.Log; -import java.util.HashSet; -import java.util.Set; import kotlin.Metadata; -import kotlin.collections.CollectionsKt; -import kotlin.collections.SetsKt; import kotlin.jvm.internal.Intrinsics; /* compiled from: MastgTest.kt */ -@Metadata(d1 = {"\u0000&\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0000\n\u0002\u0018\u0002\n\u0002\b\u0003\n\u0002\u0018\u0002\n\u0000\n\u0002\u0010\u000e\n\u0000\n\u0002\u0010\u0002\n\u0002\b\u0005\b\u0007\u0018\u00002\u00020\u0001B\u000f\u0012\u0006\u0010\u0002\u001a\u00020\u0003¢\u0006\u0004\b\u0004\u0010\u0005J\u0006\u0010\b\u001a\u00020\tJ\b\u0010\n\u001a\u00020\u000bH\u0002J\b\u0010\f\u001a\u00020\u000bH\u0002J\b\u0010\r\u001a\u00020\u000bH\u0002J\b\u0010\u000e\u001a\u00020\u000bH\u0002J\b\u0010\u000f\u001a\u00020\tH\u0002R\u000e\u0010\u0002\u001a\u00020\u0003X\u0082\u0004¢\u0006\u0002\n\u0000R\u000e\u0010\u0006\u001a\u00020\u0007X\u0082\u0004¢\u0006\u0002\n\u0000¨\u0006\u0010"}, d2 = {"Lorg/owasp/mastestapp/MastgTest;", "", "context", "Landroid/content/Context;", "", "(Landroid/content/Context;)V", "sharedPref", "Landroid/content/SharedPreferences;", "mastgTest", "", "storePrimitiveTypes", "", "storeStrings", "storeStringSet", "simulateTampering", "retrieveAndVerifyData", "app_debug"}, k = 1, mv = {2, 0, 0}, xi = 48) +@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;", "", "(Landroid/content/Context;)V", "mastgTest", "", "app_debug"}, k = 1, mv = {2, 0, 0}, xi = 48) /* loaded from: classes3.dex */ -public final class MastgTest { +public final class MastgTest_reversed { public static final int $stable = 8; private final Context context; - private final SharedPreferences sharedPref; - public MastgTest(Context context) { + public MastgTest_reversed(Context context) { Intrinsics.checkNotNullParameter(context, "context"); this.context = context; - SharedPreferences sharedPreferences = this.context.getSharedPreferences("DemoPrefs", 0); - Intrinsics.checkNotNullExpressionValue(sharedPreferences, "getSharedPreferences(...)"); - this.sharedPref = sharedPreferences; } public final String mastgTest() { - storePrimitiveTypes(); - storeStrings(); - storeStringSet(); - simulateTampering(); - return retrieveAndVerifyData(); - } - - private final void storePrimitiveTypes() { - SharedPreferences.Editor $this$storePrimitiveTypes_u24lambda_u240 = this.sharedPref.edit(); - $this$storePrimitiveTypes_u24lambda_u240.putInt("appLaunchCount", 5); - $this$storePrimitiveTypes_u24lambda_u240.putBoolean("isPremiumUser", false); - $this$storePrimitiveTypes_u24lambda_u240.putLong("lastLoginTime", System.currentTimeMillis()); - $this$storePrimitiveTypes_u24lambda_u240.apply(); - Log.d("MASTG-TEST", "Stored primitive types"); - } - - private final void storeStrings() { - SharedPreferences.Editor $this$storeStrings_u24lambda_u241 = this.sharedPref.edit(); - $this$storeStrings_u24lambda_u241.putString("userJson", "{\"name\":\"John\",\"admin\":false}"); - $this$storeStrings_u24lambda_u241.putString("htmlContent", "
Safe content
"); - $this$storeStrings_u24lambda_u241.apply(); - Log.d("MASTG-TEST", "Stored strings"); - } - - private final void storeStringSet() { - HashSet stringSet = new HashSet(); - stringSet.add("normal_item"); - stringSet.add("another_item"); - this.sharedPref.edit().putStringSet("itemSet", stringSet).apply(); - Log.d("MASTG-TEST", "Stored string set"); - } - - private final void simulateTampering() { - SharedPreferences prefsFile = this.context.getSharedPreferences("DemoPrefs", 0); - SharedPreferences.Editor $this$simulateTampering_u24lambda_u242 = prefsFile.edit(); - $this$simulateTampering_u24lambda_u242.putInt("appLaunchCount", 9999); - $this$simulateTampering_u24lambda_u242.putBoolean("isPremiumUser", true); - $this$simulateTampering_u24lambda_u242.putString("userJson", "{\"name\":\"John\",\"admin\":true}"); - $this$simulateTampering_u24lambda_u242.putString("htmlContent", ""); - HashSet maliciousSet = new HashSet(); - maliciousSet.add("normal_item"); - maliciousSet.add("malicious_payload"); - $this$simulateTampering_u24lambda_u242.putStringSet("itemSet", maliciousSet); - $this$simulateTampering_u24lambda_u242.apply(); - Log.d("MASTG-TEST", "Simulated tampering with all data types"); - } - - private final String retrieveAndVerifyData() { - StringBuilder result = new StringBuilder(); - result.append("Primitive Types:\n").append("Launch Count: " + this.sharedPref.getInt("appLaunchCount", 0) + "\n").append("Is Premium: " + this.sharedPref.getBoolean("isPremiumUser", false) + "\n\n"); - result.append("Strings:\n"); - String userJson = this.sharedPref.getString("userJson", ""); - result.append("User JSON: " + userJson + "\n"); - String htmlContent = this.sharedPref.getString("htmlContent", ""); - result.append("HTML Content: " + htmlContent + "\n\n"); - result.append("String Set:\n"); - Set itemSet = this.sharedPref.getStringSet("itemSet", SetsKt.emptySet()); - result.append("Items: " + (itemSet != null ? CollectionsKt.joinToString$default(itemSet, null, null, null, 0, null, null, 63, null) : null)); - String sb = result.toString(); + SharedPreferences prefs = this.context.getSharedPreferences("app_settings", 0); + if (!prefs.contains("setup_complete")) { + SecureSharedPreferences insecurePrefs = new SecureSharedPreferences(this.context, false); + insecurePrefs.saveData("user_role_insecure", "user"); + SecureSharedPreferences securePrefs = new SecureSharedPreferences(this.context, true); + securePrefs.saveData("user_role_secure", "user"); + Intrinsics.checkNotNull(prefs); + SharedPreferences.Editor editor$iv = prefs.edit(); + editor$iv.putBoolean("setup_complete", true); + editor$iv.commit(); + return "INITIAL SETUP COMPLETE.\n\nThe role for both secure and insecure tests has been set to 'user'.\n\nACTION REQUIRED:\n1. Use a file explorer or ADB shell on a rooted device.\n2. Go to: /data/data/org.owasp.mastestapp/shared_prefs/\n3. Open the file: app_settings.xml\n4. Change BOTH user values to admin.\n5. Save the file and run this test again to see the results."; + } + StringBuilder results = new StringBuilder(); + results.append("--- VERIFYING SCENARIO 1: 'kind: fail' (No HMAC Protection) ---\n"); + SecureSharedPreferences insecurePrefs2 = new SecureSharedPreferences(this.context, false); + String insecureRole = insecurePrefs2.loadData("user_role_insecure", "error"); + results.append("Loaded role from 'user_role_insecure': '" + insecureRole + "'\n"); + if (Intrinsics.areEqual(insecureRole, "admin")) { + results.append(">> OUTCOME: VULNERABLE. The application accepted the tampered 'admin' role because there was no integrity check.\n"); + } else { + results.append(">> OUTCOME: NOT EXPLOITED. The role is still '" + insecureRole + "'. Please ensure you changed it to 'admin' in the XML file.\n"); + } + results.append("\n--- VERIFYING SCENARIO 2: 'kind: pass' (HMAC Protection Enabled) ---\n"); + SecureSharedPreferences securePrefs2 = new SecureSharedPreferences(this.context, true); + String secureRole = securePrefs2.loadData("user_role_secure", "tampering_detected"); + results.append("Loaded role from 'user_role_secure': '" + secureRole + "'\n"); + if (Intrinsics.areEqual(secureRole, "tampering_detected")) { + results.append(">> OUTCOME: SECURE. The application detected that the data was tampered with and correctly rejected the invalid 'admin' role.\n"); + } else if (Intrinsics.areEqual(secureRole, "admin")) { + results.append(">> OUTCOME: UNEXPECTED. The role is 'admin', which means the HMAC check failed. This should not happen.\n"); + } else { + results.append(">> OUTCOME: NOT TAMPERED. The role is still '" + secureRole + "', and its HMAC signature is valid.\n"); + } + results.append("\n\nTest complete. To run the setup again, please clear the application's data in Android Settings and restart the test."); + String sb = results.toString(); Intrinsics.checkNotNullExpressionValue(sb, "toString(...)"); return sb; } -} \ No newline at end of file +} diff --git a/demos/android/MASVS-CODE/MASTG-DEMO-0054/output.txt b/demos/android/MASVS-CODE/MASTG-DEMO-0054/output.txt index f014d5020b2..60a36211a81 100644 --- a/demos/android/MASVS-CODE/MASTG-DEMO-0054/output.txt +++ b/demos/android/MASVS-CODE/MASTG-DEMO-0054/output.txt @@ -1,24 +1,15 @@ ┌─────────────────┐ -│ 6 Code Findings │ +│ 2 Code Findings │ └─────────────────┘ - - MASTestApp-Android/output/MastgTest_reversed.java - ❯❱ mastg-android-local-storage-for-input-validation - [MASVS-CODE-4] The application make use of input validation in the code. - - 48┆ $this$storeStrings_u24lambda_u241.putString("userJson", - "{\"name\":\"John\",\"admin\":false}"); - ⋮┆---------------------------------------- - 49┆ $this$storeStrings_u24lambda_u241.putString("htmlContent", "
Safe content
"); - ⋮┆---------------------------------------- - 67┆ $this$simulateTampering_u24lambda_u242.putString("userJson", - "{\"name\":\"John\",\"admin\":true}"); - ⋮┆---------------------------------------- - 68┆ $this$simulateTampering_u24lambda_u242.putString("htmlContent", - ""); - ⋮┆---------------------------------------- - 81┆ String userJson = this.sharedPref.getString("userJson", ""); - ⋮┆---------------------------------------- - 83┆ String htmlContent = this.sharedPref.getString("htmlContent", ""); + + MastgTest_reversed.java + ❯❱ rules.mastg-android-local-storage-for-input-validation + [MASVS-CODE-4] The application reads data from SharedPreferences without an integrity check (like + HMAC). This data could be tampered with by an attacker on a rooted device, leading to privilege + escalation or other vulnerabilities. + + 23┆ SecureSharedPreferences insecurePrefs = new SecureSharedPreferences(this.context, false); + ⋮┆---------------------------------------- + 35┆ SecureSharedPreferences insecurePrefs2 = new SecureSharedPreferences(this.context, false); diff --git a/demos/android/MASVS-CODE/MASTG-DEMO-0054/run.sh b/demos/android/MASVS-CODE/MASTG-DEMO-0054/run.sh old mode 100644 new mode 100755 diff --git a/rules/mastg-android-Local-Storage-for-Input-Validation.yml b/rules/mastg-android-Local-Storage-for-Input-Validation.yml index 4cf2ee995e6..10945fb705d 100644 --- a/rules/mastg-android-Local-Storage-for-Input-Validation.yml +++ b/rules/mastg-android-Local-Storage-for-Input-Validation.yml @@ -4,9 +4,8 @@ rules: languages: - java metadata: - summary: This rule looks for use of Local Storage for Input Validation. - message: "[MASVS-CODE-4] The application make use of input validation in the code." + summary: Detects SharedPreferences usage without an integrity check. + message: "[MASVS-CODE-4] The application reads data from SharedPreferences without an integrity check (like HMAC). This data could be tampered with by an attacker on a rooted device, leading to privilege escalation or other vulnerabilities." patterns: - - pattern-either: - - pattern: $EDITOR.putString($KEY, $VALUE) - - pattern: $PREF.getString($KEY, $DEFAULT) + - pattern: new SecureSharedPreferences($CONTEXT, false) + diff --git a/tests-beta/android/MASVS-CODE/MASTG-TEST-0281.md b/tests-beta/android/MASVS-CODE/MASTG-TEST-0281.md index c29f95d1cc9..cb191f1149f 100644 --- a/tests-beta/android/MASVS-CODE/MASTG-TEST-0281.md +++ b/tests-beta/android/MASVS-CODE/MASTG-TEST-0281.md @@ -9,16 +9,16 @@ profiles: [L1, L2] ## Overview -Android offers `SharedPreferences` for saving key-value pairs of basic data types and strings. When you store structured data like JSON or HTML using `putString()` and `getString()` without adequate validation, it can result in security vulnerabilities such as tampering or injection. This becomes especially dangerous if the stored data is subsequently trusted and utilized directly by the application. +Data stored in Android's `SharedPreference`s can be tampered with on a rooted device. If an application reads this data without verifying its integrity (e.g., with an HMAC signature), it can lead to security vulnerabilities. This test checks if the application properly validates data read from local storage. ## Steps -1. Run a static analysis tool such as @MASTG-TOOL-0110 on the code and look for uses of the `putString` and `getString`. +1. Run a static analysis tool such as @MASTG-TOOL-0110 on the code and look for patterns where data is read from `SharedPreferences` without a corresponding integrity check. ## Observation -The output file shows usages of the input validation using `putString` and `getString` in the code. +The static analysis tool identifies code where `SharedPreferences` data is loaded without an integrity check. ## Evaluation -The test fails if the `putString()` and `getString()` was found in the code. +The test fails if the application reads data from `SharedPreferences` without verifying its integrity using a mechanism like HMAC. From bcaec04295f1c6737c50ec559aab4dd871283825 Mon Sep 17 00:00:00 2001 From: Jeel Date: Fri, 1 Aug 2025 16:31:17 +0530 Subject: [PATCH 12/16] Updated-Demo-fix --- .../MASTG-DEMO-0054/MASTG-DEMO-0054.md | 4 ++-- .../MASVS-CODE/MASTG-DEMO-0054/output.txt | 17 ++++++++++------- demos/android/MASVS-CODE/MASTG-DEMO-0054/run.sh | 2 +- ...g-android-local-storage-input-validation.yml | 11 +++++++++++ 4 files changed, 24 insertions(+), 10 deletions(-) create mode 100644 rules/mastg-android-local-storage-input-validation.yml diff --git a/demos/android/MASVS-CODE/MASTG-DEMO-0054/MASTG-DEMO-0054.md b/demos/android/MASVS-CODE/MASTG-DEMO-0054/MASTG-DEMO-0054.md index 2a72610b17a..55647792d82 100644 --- a/demos/android/MASVS-CODE/MASTG-DEMO-0054/MASTG-DEMO-0054.md +++ b/demos/android/MASVS-CODE/MASTG-DEMO-0054/MASTG-DEMO-0054.md @@ -17,13 +17,13 @@ The code snippet demonstrates an insecure use of `SharedPreferences` where data Let's run @MASTG-TOOL-0110 rules against the sample code. -{{ ../../../../rules/mastg-android-local-storage-for-input-validation.yml }} +{{ ../../../../rules/mastg-android-local-storage-input-validation.yml }} {{ run.sh }} ### Observation -The output file correctly identifies the vulnerable pattern where data is loaded without being validated. +The output file correctly identifies the vulnerable pattern where data is loaded without being validated. {{ output.txt }} diff --git a/demos/android/MASVS-CODE/MASTG-DEMO-0054/output.txt b/demos/android/MASVS-CODE/MASTG-DEMO-0054/output.txt index 60a36211a81..13191368bde 100644 --- a/demos/android/MASVS-CODE/MASTG-DEMO-0054/output.txt +++ b/demos/android/MASVS-CODE/MASTG-DEMO-0054/output.txt @@ -5,11 +5,14 @@ └─────────────────┘ MastgTest_reversed.java - ❯❱ rules.mastg-android-local-storage-for-input-validation - [MASVS-CODE-4] The application reads data from SharedPreferences without an integrity check (like - HMAC). This data could be tampered with by an attacker on a rooted device, leading to privilege - escalation or other vulnerabilities. - - 23┆ SecureSharedPreferences insecurePrefs = new SecureSharedPreferences(this.context, false); + ❯❱rules.mastg-android-local-storage-input-validation + [MASVS-CODE-4] The application reads data from SharedPreferences without + an integrity check (like HMAC). This data could be tampered with by an + attacker on a rooted device, leading to privilege escalation or other + vulnerabilities. + + 23┆ SecureSharedPreferences insecurePrefs = new + SecureSharedPreferences(this.context, false); ⋮┆---------------------------------------- - 35┆ SecureSharedPreferences insecurePrefs2 = new SecureSharedPreferences(this.context, false); + 35┆ SecureSharedPreferences insecurePrefs2 = new + SecureSharedPreferences(this.context, false); diff --git a/demos/android/MASVS-CODE/MASTG-DEMO-0054/run.sh b/demos/android/MASVS-CODE/MASTG-DEMO-0054/run.sh index 4f90374e65e..f055560cbf1 100755 --- a/demos/android/MASVS-CODE/MASTG-DEMO-0054/run.sh +++ b/demos/android/MASVS-CODE/MASTG-DEMO-0054/run.sh @@ -1 +1 @@ -NO_COLOR=true semgrep -c ../../../../rules/mastg-android-local-storage-for-input-validation.yml ./MastgTest_reversed.java --text -o output.txt \ No newline at end of file +NO_COLOR=true semgrep -c ../../../../rules/mastg-android-local-storage-input-validation.yml ./MastgTest_reversed.java --text -o output.txt \ No newline at end of file diff --git a/rules/mastg-android-local-storage-input-validation.yml b/rules/mastg-android-local-storage-input-validation.yml new file mode 100644 index 00000000000..fba3a53a259 --- /dev/null +++ b/rules/mastg-android-local-storage-input-validation.yml @@ -0,0 +1,11 @@ +rules: + - id: mastg-android-local-storage-input-validation + severity: WARNING + languages: + - java + metadata: + summary: Detects SharedPreferences usage without an integrity check. + message: "[MASVS-CODE-4] The application reads data from SharedPreferences without an integrity check (like HMAC). This data could be tampered with by an attacker on a rooted device, leading to privilege escalation or other vulnerabilities." + patterns: + - pattern: new SecureSharedPreferences($CONTEXT, false) + From 2a4b4074a331064e84123c14bfc7f2e2a4cdf546 Mon Sep 17 00:00:00 2001 From: Jeel Date: Thu, 7 Aug 2025 17:07:14 +0530 Subject: [PATCH 13/16] internal-comment-completed --- .../MASTG-DEMO-0061/MASTG-DEMO-0061.md | 32 ++++ .../MASVS-CODE/MASTG-DEMO-0061/MastgTest.kt | 173 ++++++++++++++++++ .../MASTG-DEMO-0061/MastgTest_reversed.java | 59 ++++++ .../MASVS-CODE/MASTG-DEMO-0061/output.txt | 18 ++ .../android/MASVS-CODE/MASTG-DEMO-0061/run.sh | 1 + .../android/MASVS-CODE/MASTG-TEST-0288.md | 24 +++ 6 files changed, 307 insertions(+) create mode 100644 demos/android/MASVS-CODE/MASTG-DEMO-0061/MASTG-DEMO-0061.md create mode 100644 demos/android/MASVS-CODE/MASTG-DEMO-0061/MastgTest.kt create mode 100644 demos/android/MASVS-CODE/MASTG-DEMO-0061/MastgTest_reversed.java create mode 100644 demos/android/MASVS-CODE/MASTG-DEMO-0061/output.txt create mode 100755 demos/android/MASVS-CODE/MASTG-DEMO-0061/run.sh create mode 100644 tests-beta/android/MASVS-CODE/MASTG-TEST-0288.md diff --git a/demos/android/MASVS-CODE/MASTG-DEMO-0061/MASTG-DEMO-0061.md b/demos/android/MASVS-CODE/MASTG-DEMO-0061/MASTG-DEMO-0061.md new file mode 100644 index 00000000000..96d719c2ca5 --- /dev/null +++ b/demos/android/MASVS-CODE/MASTG-DEMO-0061/MASTG-DEMO-0061.md @@ -0,0 +1,32 @@ +--- +platform: android +title: Local Storage for Input Validation with semgrep +id: MASTG-DEMO-0061 +code: [kotlin] +test: MASTG-TEST-0288 +profiles: [L1, L2] +--- + +### Sample + +The code snippet demonstrates the insecure use of `SharedPreferences`, as data is loaded without an integrity check. + +{{ MastgTest.kt # MastgTest_reversed.java }} + +### Steps + +Let's run @MASTG-TOOL-0110 rules against the sample code. + +{{ ../../../../rules/mastg-android-local-storage-input-validation.yml }} + +{{ run.sh }} + +### Observation + +The rule identifies that data is being loaded without being validated. + +{{ output.txt }} + +### Evaluation + +The test fails as the code does not use an `HMAC` integrity check together with `SharedPreferences` data. diff --git a/demos/android/MASVS-CODE/MASTG-DEMO-0061/MastgTest.kt b/demos/android/MASVS-CODE/MASTG-DEMO-0061/MastgTest.kt new file mode 100644 index 00000000000..d48e0a358a4 --- /dev/null +++ b/demos/android/MASVS-CODE/MASTG-DEMO-0061/MastgTest.kt @@ -0,0 +1,173 @@ +package org.owasp.mastestapp + +import android.content.Context +import android.content.SharedPreferences +import android.util.Log +import androidx.core.content.edit +import java.security.InvalidKeyException +import java.security.NoSuchAlgorithmException +import javax.crypto.Mac +import javax.crypto.spec.SecretKeySpec + +/** + * This is the main test class that orchestrates the demonstration. + * It now contains all logic in a single class to simplify decompilation. + * It uses a two-step process to allow for manual tampering. + */ +class MastgTest(private val context: Context) { + + companion object { + private const val PREFS_NAME = "app_settings" + private const val HMAC_ALGORITHM = "HmacSHA256" + // WARNING: In a real application, this key should NOT be hardcoded. + // It should be stored securely, for instance, in the Android Keystore. + // For this self-contained demo, we hardcode it to illustrate the HMAC mechanism. + private const val SECRET_KEY = "this-is-a-very-secret-key-for-the-demo" + } + + /** + * Main test function that runs the setup or verification phase. + */ + fun mastgTest(): String { + val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) + + // Check if the initial setup has been performed. + if (!prefs.contains("setup_complete")) { + // --- FIRST-TIME EXECUTION: SETUP PHASE --- + // This block runs only once. + + // 1. Set up the insecure preference (without HMAC). + saveData("user_role_insecure", "user", useHmac = false) + + // 2. Set up the secure preference (with HMAC). + saveData("user_role_secure", "user", useHmac = true) + + // 3. Mark setup as complete so this block doesn't run again. + prefs.edit(commit = true) { + putBoolean("setup_complete", true) + } + + // 4. Return instructions for the user. + return "INITIAL SETUP COMPLETE.\n\n" + + "The role for both secure and insecure tests has been set to 'user'.\n\n" + + "ACTION REQUIRED:\n" + + "1. Use a file explorer or ADB shell on a rooted device.\n" + + "2. Go to: /data/data/org.owasp.mastestapp/shared_prefs/\n" + + "3. Open the file: app_settings.xml\n" + + "4. Change BOTH user values to admin.\n" + + "5. Save the file and run this test again to see the results." + + } else { + // --- SUBSEQUENT EXECUTION: VERIFICATION PHASE --- + // This block runs after the user has tampered with the file. + + val results = StringBuilder() + + // 1. Verify the 'fail' case (insecure) + results.append("--- VERIFYING SCENARIO 1: 'kind: fail' (No HMAC Protection) ---\n") + val insecureRole = loadData("user_role_insecure", "error", useHmac = false) + results.append("Loaded role from 'user_role_insecure': '$insecureRole'\n") + if (insecureRole == "admin") { + results.append(">> OUTCOME: VULNERABLE. The application accepted the tampered 'admin' role because there was no integrity check.\n") + } else { + results.append(">> OUTCOME: NOT EXPLOITED. The role is still '$insecureRole'. Please ensure you changed it to 'admin' in the XML file.\n") + } + + // 2. Verify the 'pass' case (secure) + results.append("\n--- VERIFYING SCENARIO 2: 'kind: pass' (HMAC Protection Enabled) ---\n") + val secureRole = loadData("user_role_secure", "tampering_detected", useHmac = true) + results.append("Loaded role from 'user_role_secure': '$secureRole'\n") + if (secureRole == "tampering_detected") { + results.append(">> OUTCOME: SECURE. The application detected that the data was tampered with and correctly rejected the invalid 'admin' role.\n") + } else if (secureRole == "admin") { + results.append(">> OUTCOME: UNEXPECTED. The role is 'admin', which means the HMAC check failed. This should not happen.\n") + } else { // secureRole == "user" + results.append(">> OUTCOME: NOT TAMPERED. The role is still '$secureRole', and its HMAC signature is valid.\n") + } + + results.append("\n\nTest complete. To run the setup again, please clear the application's data in Android Settings and restart the test.") + return results.toString() + } + } + + /** + * Saves a key-value pair. If HMAC is enabled, it also saves an integrity check value. + */ + private fun saveData(key: String, value: String, useHmac: Boolean) { + val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) + prefs.edit(commit = true) { + putString(key, value) + if (useHmac) { + val hmac = calculateHmac(value) + if (hmac != null) { + putString("${key}_hmac", hmac) + Log.d("MASTG-TEST", "Saved data with HMAC.") + } + } else { + Log.d("MASTG-TEST", "Saved data WITHOUT HMAC.") + } + } + } + + /** + * Loads data for a given key. If HMAC is enabled, it first verifies the data's integrity. + */ + private fun loadData(key: String, defaultValue: String, useHmac: Boolean): String { + val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) + val value = prefs.getString(key, null) ?: return defaultValue + + if (!useHmac) { + Log.d("MASTG-TEST", "Loaded data without HMAC check. Value is: $value") + return value + } + + val storedHmac = prefs.getString("${key}_hmac", null) + if (storedHmac == null) { + Log.w("MASTG-TEST", "HMAC verification failed: No HMAC found for key '$key'.") + return defaultValue + } + + val calculatedHmac = calculateHmac(value) + + return if (storedHmac == calculatedHmac) { + Log.d("MASTG-TEST", "HMAC verification SUCCESS. Value is: $value") + value + } else { + Log.e("MASTG-TEST", "HMAC verification FAILED! Data has been tampered with.") + defaultValue + } + } + + /** + * Calculates the HMAC for a given piece of data. + */ + private fun calculateHmac(data: String): String? { + return try { + val mac = Mac.getInstance(HMAC_ALGORITHM) + val secretKeySpec = SecretKeySpec(SECRET_KEY.toByteArray(), HMAC_ALGORITHM) + mac.init(secretKeySpec) + val hmacBytes = mac.doFinal(data.toByteArray()) + bytesToHex(hmacBytes) + } catch (e: NoSuchAlgorithmException) { + Log.e("MASTG-TEST", "HMAC algorithm not found", e) + null + } catch (e: InvalidKeyException) { + Log.e("MASTG-TEST", "Invalid HMAC key", e) + null + } + } + + /** + * Helper function to convert a byte array to a hexadecimal string. + */ + private fun bytesToHex(bytes: ByteArray): String { + val hexChars = "0123456789abcdef" + val result = StringBuilder(bytes.size * 2) + bytes.forEach { + val i = it.toInt() + result.append(hexChars[i shr 4 and 0x0f]) + result.append(hexChars[i and 0x0f]) + } + return result.toString() + } +} diff --git a/demos/android/MASVS-CODE/MASTG-DEMO-0061/MastgTest_reversed.java b/demos/android/MASVS-CODE/MASTG-DEMO-0061/MastgTest_reversed.java new file mode 100644 index 00000000000..93fa2000f9a --- /dev/null +++ b/demos/android/MASVS-CODE/MASTG-DEMO-0061/MastgTest_reversed.java @@ -0,0 +1,59 @@ +package org.owasp.mastestapp; + +import android.content.Context; +import android.content.SharedPreferences; +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;", "", "(Landroid/content/Context;)V", "mastgTest", "", "app_debug"}, k = 1, mv = {2, 0, 0}, xi = 48) +/* loaded from: classes3.dex */ +public final class MastgTest_reversed { + public static final int $stable = 8; + private final Context context; + + public MastgTest_reversed(Context context) { + Intrinsics.checkNotNullParameter(context, "context"); + this.context = context; + } + + public final String mastgTest() { + SharedPreferences prefs = this.context.getSharedPreferences("app_settings", 0); + if (!prefs.contains("setup_complete")) { + SecureSharedPreferences insecurePrefs = new SecureSharedPreferences(this.context, false); + insecurePrefs.saveData("user_role_insecure", "user"); + SecureSharedPreferences securePrefs = new SecureSharedPreferences(this.context, true); + securePrefs.saveData("user_role_secure", "user"); + Intrinsics.checkNotNull(prefs); + SharedPreferences.Editor editor$iv = prefs.edit(); + editor$iv.putBoolean("setup_complete", true); + editor$iv.commit(); + return "INITIAL SETUP COMPLETE.\n\nThe role for both secure and insecure tests has been set to 'user'.\n\nACTION REQUIRED:\n1. Use a file explorer or ADB shell on a rooted device.\n2. Go to: /data/data/org.owasp.mastestapp/shared_prefs/\n3. Open the file: app_settings.xml\n4. Change BOTH user values to admin.\n5. Save the file and run this test again to see the results."; + } + StringBuilder results = new StringBuilder(); + results.append("--- VERIFYING SCENARIO 1: 'kind: fail' (No HMAC Protection) ---\n"); + SecureSharedPreferences insecurePrefs2 = new SecureSharedPreferences(this.context, false); + String insecureRole = insecurePrefs2.loadData("user_role_insecure", "error"); + results.append("Loaded role from 'user_role_insecure': '" + insecureRole + "'\n"); + if (Intrinsics.areEqual(insecureRole, "admin")) { + results.append(">> OUTCOME: VULNERABLE. The application accepted the tampered 'admin' role because there was no integrity check.\n"); + } else { + results.append(">> OUTCOME: NOT EXPLOITED. The role is still '" + insecureRole + "'. Please ensure you changed it to 'admin' in the XML file.\n"); + } + results.append("\n--- VERIFYING SCENARIO 2: 'kind: pass' (HMAC Protection Enabled) ---\n"); + SecureSharedPreferences securePrefs2 = new SecureSharedPreferences(this.context, true); + String secureRole = securePrefs2.loadData("user_role_secure", "tampering_detected"); + results.append("Loaded role from 'user_role_secure': '" + secureRole + "'\n"); + if (Intrinsics.areEqual(secureRole, "tampering_detected")) { + results.append(">> OUTCOME: SECURE. The application detected that the data was tampered with and correctly rejected the invalid 'admin' role.\n"); + } else if (Intrinsics.areEqual(secureRole, "admin")) { + results.append(">> OUTCOME: UNEXPECTED. The role is 'admin', which means the HMAC check failed. This should not happen.\n"); + } else { + results.append(">> OUTCOME: NOT TAMPERED. The role is still '" + secureRole + "', and its HMAC signature is valid.\n"); + } + results.append("\n\nTest complete. To run the setup again, please clear the application's data in Android Settings and restart the test."); + String sb = results.toString(); + Intrinsics.checkNotNullExpressionValue(sb, "toString(...)"); + return sb; + } +} diff --git a/demos/android/MASVS-CODE/MASTG-DEMO-0061/output.txt b/demos/android/MASVS-CODE/MASTG-DEMO-0061/output.txt new file mode 100644 index 00000000000..13191368bde --- /dev/null +++ b/demos/android/MASVS-CODE/MASTG-DEMO-0061/output.txt @@ -0,0 +1,18 @@ + + +┌─────────────────┐ +│ 2 Code Findings │ +└─────────────────┘ + + MastgTest_reversed.java + ❯❱rules.mastg-android-local-storage-input-validation + [MASVS-CODE-4] The application reads data from SharedPreferences without + an integrity check (like HMAC). This data could be tampered with by an + attacker on a rooted device, leading to privilege escalation or other + vulnerabilities. + + 23┆ SecureSharedPreferences insecurePrefs = new + SecureSharedPreferences(this.context, false); + ⋮┆---------------------------------------- + 35┆ SecureSharedPreferences insecurePrefs2 = new + SecureSharedPreferences(this.context, false); diff --git a/demos/android/MASVS-CODE/MASTG-DEMO-0061/run.sh b/demos/android/MASVS-CODE/MASTG-DEMO-0061/run.sh new file mode 100755 index 00000000000..f055560cbf1 --- /dev/null +++ b/demos/android/MASVS-CODE/MASTG-DEMO-0061/run.sh @@ -0,0 +1 @@ +NO_COLOR=true semgrep -c ../../../../rules/mastg-android-local-storage-input-validation.yml ./MastgTest_reversed.java --text -o output.txt \ No newline at end of file diff --git a/tests-beta/android/MASVS-CODE/MASTG-TEST-0288.md b/tests-beta/android/MASVS-CODE/MASTG-TEST-0288.md new file mode 100644 index 00000000000..40a30480a60 --- /dev/null +++ b/tests-beta/android/MASVS-CODE/MASTG-TEST-0288.md @@ -0,0 +1,24 @@ +--- +title: Use of Local Storage for Input Validation +platform: android +id: MASTG-TEST-0288 +type: [static] +weakness: MASWE-0082 +profiles: [L1, L2] +--- + +## Overview + +Data stored in Android's `SharedPreference`s can be tampered with on a rooted device. If an application reads this data without verifying its integrity (e.g., with an HMAC signature), it can lead to security vulnerabilities. This test checks if the application properly validates data read from local storage. + +## Steps + +1. Run a static analysis tool such as @MASTG-TOOL-0110 on the code and look for patterns where data is read from `SharedPreferences` without a corresponding integrity check. + +## Observation + +The output identifies code where `SharedPreferences` data is loaded without an integrity check. + +## Evaluation + +The test fails if the application reads data from `SharedPreferences` without verifying its integrity using a mechanism like `HMAC`. From 4ba84d9dd6da724f576998b8cfcf40c84f962339 Mon Sep 17 00:00:00 2001 From: Jeel Date: Mon, 11 Aug 2025 14:56:33 +0530 Subject: [PATCH 14/16] deleted old demo --- .../MASTG-DEMO-0054/MASTG-DEMO-0054.md | 32 ---- .../MASVS-CODE/MASTG-DEMO-0054/MastgTest.kt | 173 ------------------ .../MASTG-DEMO-0054/MastgTest_reversed.java | 59 ------ .../MASVS-CODE/MASTG-DEMO-0054/output.txt | 18 -- .../android/MASVS-CODE/MASTG-DEMO-0054/run.sh | 1 - ...oid-Local-Storage-for-Input-Validation.yml | 11 -- .../android/MASVS-CODE/MASTG-TEST-0281.md | 24 --- 7 files changed, 318 deletions(-) delete mode 100644 demos/android/MASVS-CODE/MASTG-DEMO-0054/MASTG-DEMO-0054.md delete mode 100644 demos/android/MASVS-CODE/MASTG-DEMO-0054/MastgTest.kt delete mode 100644 demos/android/MASVS-CODE/MASTG-DEMO-0054/MastgTest_reversed.java delete mode 100644 demos/android/MASVS-CODE/MASTG-DEMO-0054/output.txt delete mode 100755 demos/android/MASVS-CODE/MASTG-DEMO-0054/run.sh delete mode 100644 rules/mastg-android-Local-Storage-for-Input-Validation.yml delete mode 100644 tests-beta/android/MASVS-CODE/MASTG-TEST-0281.md diff --git a/demos/android/MASVS-CODE/MASTG-DEMO-0054/MASTG-DEMO-0054.md b/demos/android/MASVS-CODE/MASTG-DEMO-0054/MASTG-DEMO-0054.md deleted file mode 100644 index 55647792d82..00000000000 --- a/demos/android/MASVS-CODE/MASTG-DEMO-0054/MASTG-DEMO-0054.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -platform: android -title: Local Storage for Input Validation with semgrep -id: MASTG-DEMO-0054 -code: [kotlin] -test: MASTG-TEST-0281 -profiles: [L1, L2] ---- - -### Sample - -The code snippet demonstrates an insecure use of `SharedPreferences` where data is loaded without a proper integrity check, which is a form of input validation for stored data. - -{{ MastgTest.kt # MastgTest_reversed.java }} - -### Steps - -Let's run @MASTG-TOOL-0110 rules against the sample code. - -{{ ../../../../rules/mastg-android-local-storage-input-validation.yml }} - -{{ run.sh }} - -### Observation - -The output file correctly identifies the vulnerable pattern where data is loaded without being validated. - -{{ output.txt }} - -### Evaluation - -The test fails because the rule detected that the app does NOT use an `HMAC` integrity check together with `SharedPreferences`. diff --git a/demos/android/MASVS-CODE/MASTG-DEMO-0054/MastgTest.kt b/demos/android/MASVS-CODE/MASTG-DEMO-0054/MastgTest.kt deleted file mode 100644 index d48e0a358a4..00000000000 --- a/demos/android/MASVS-CODE/MASTG-DEMO-0054/MastgTest.kt +++ /dev/null @@ -1,173 +0,0 @@ -package org.owasp.mastestapp - -import android.content.Context -import android.content.SharedPreferences -import android.util.Log -import androidx.core.content.edit -import java.security.InvalidKeyException -import java.security.NoSuchAlgorithmException -import javax.crypto.Mac -import javax.crypto.spec.SecretKeySpec - -/** - * This is the main test class that orchestrates the demonstration. - * It now contains all logic in a single class to simplify decompilation. - * It uses a two-step process to allow for manual tampering. - */ -class MastgTest(private val context: Context) { - - companion object { - private const val PREFS_NAME = "app_settings" - private const val HMAC_ALGORITHM = "HmacSHA256" - // WARNING: In a real application, this key should NOT be hardcoded. - // It should be stored securely, for instance, in the Android Keystore. - // For this self-contained demo, we hardcode it to illustrate the HMAC mechanism. - private const val SECRET_KEY = "this-is-a-very-secret-key-for-the-demo" - } - - /** - * Main test function that runs the setup or verification phase. - */ - fun mastgTest(): String { - val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) - - // Check if the initial setup has been performed. - if (!prefs.contains("setup_complete")) { - // --- FIRST-TIME EXECUTION: SETUP PHASE --- - // This block runs only once. - - // 1. Set up the insecure preference (without HMAC). - saveData("user_role_insecure", "user", useHmac = false) - - // 2. Set up the secure preference (with HMAC). - saveData("user_role_secure", "user", useHmac = true) - - // 3. Mark setup as complete so this block doesn't run again. - prefs.edit(commit = true) { - putBoolean("setup_complete", true) - } - - // 4. Return instructions for the user. - return "INITIAL SETUP COMPLETE.\n\n" + - "The role for both secure and insecure tests has been set to 'user'.\n\n" + - "ACTION REQUIRED:\n" + - "1. Use a file explorer or ADB shell on a rooted device.\n" + - "2. Go to: /data/data/org.owasp.mastestapp/shared_prefs/\n" + - "3. Open the file: app_settings.xml\n" + - "4. Change BOTH user values to admin.\n" + - "5. Save the file and run this test again to see the results." - - } else { - // --- SUBSEQUENT EXECUTION: VERIFICATION PHASE --- - // This block runs after the user has tampered with the file. - - val results = StringBuilder() - - // 1. Verify the 'fail' case (insecure) - results.append("--- VERIFYING SCENARIO 1: 'kind: fail' (No HMAC Protection) ---\n") - val insecureRole = loadData("user_role_insecure", "error", useHmac = false) - results.append("Loaded role from 'user_role_insecure': '$insecureRole'\n") - if (insecureRole == "admin") { - results.append(">> OUTCOME: VULNERABLE. The application accepted the tampered 'admin' role because there was no integrity check.\n") - } else { - results.append(">> OUTCOME: NOT EXPLOITED. The role is still '$insecureRole'. Please ensure you changed it to 'admin' in the XML file.\n") - } - - // 2. Verify the 'pass' case (secure) - results.append("\n--- VERIFYING SCENARIO 2: 'kind: pass' (HMAC Protection Enabled) ---\n") - val secureRole = loadData("user_role_secure", "tampering_detected", useHmac = true) - results.append("Loaded role from 'user_role_secure': '$secureRole'\n") - if (secureRole == "tampering_detected") { - results.append(">> OUTCOME: SECURE. The application detected that the data was tampered with and correctly rejected the invalid 'admin' role.\n") - } else if (secureRole == "admin") { - results.append(">> OUTCOME: UNEXPECTED. The role is 'admin', which means the HMAC check failed. This should not happen.\n") - } else { // secureRole == "user" - results.append(">> OUTCOME: NOT TAMPERED. The role is still '$secureRole', and its HMAC signature is valid.\n") - } - - results.append("\n\nTest complete. To run the setup again, please clear the application's data in Android Settings and restart the test.") - return results.toString() - } - } - - /** - * Saves a key-value pair. If HMAC is enabled, it also saves an integrity check value. - */ - private fun saveData(key: String, value: String, useHmac: Boolean) { - val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) - prefs.edit(commit = true) { - putString(key, value) - if (useHmac) { - val hmac = calculateHmac(value) - if (hmac != null) { - putString("${key}_hmac", hmac) - Log.d("MASTG-TEST", "Saved data with HMAC.") - } - } else { - Log.d("MASTG-TEST", "Saved data WITHOUT HMAC.") - } - } - } - - /** - * Loads data for a given key. If HMAC is enabled, it first verifies the data's integrity. - */ - private fun loadData(key: String, defaultValue: String, useHmac: Boolean): String { - val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) - val value = prefs.getString(key, null) ?: return defaultValue - - if (!useHmac) { - Log.d("MASTG-TEST", "Loaded data without HMAC check. Value is: $value") - return value - } - - val storedHmac = prefs.getString("${key}_hmac", null) - if (storedHmac == null) { - Log.w("MASTG-TEST", "HMAC verification failed: No HMAC found for key '$key'.") - return defaultValue - } - - val calculatedHmac = calculateHmac(value) - - return if (storedHmac == calculatedHmac) { - Log.d("MASTG-TEST", "HMAC verification SUCCESS. Value is: $value") - value - } else { - Log.e("MASTG-TEST", "HMAC verification FAILED! Data has been tampered with.") - defaultValue - } - } - - /** - * Calculates the HMAC for a given piece of data. - */ - private fun calculateHmac(data: String): String? { - return try { - val mac = Mac.getInstance(HMAC_ALGORITHM) - val secretKeySpec = SecretKeySpec(SECRET_KEY.toByteArray(), HMAC_ALGORITHM) - mac.init(secretKeySpec) - val hmacBytes = mac.doFinal(data.toByteArray()) - bytesToHex(hmacBytes) - } catch (e: NoSuchAlgorithmException) { - Log.e("MASTG-TEST", "HMAC algorithm not found", e) - null - } catch (e: InvalidKeyException) { - Log.e("MASTG-TEST", "Invalid HMAC key", e) - null - } - } - - /** - * Helper function to convert a byte array to a hexadecimal string. - */ - private fun bytesToHex(bytes: ByteArray): String { - val hexChars = "0123456789abcdef" - val result = StringBuilder(bytes.size * 2) - bytes.forEach { - val i = it.toInt() - result.append(hexChars[i shr 4 and 0x0f]) - result.append(hexChars[i and 0x0f]) - } - return result.toString() - } -} diff --git a/demos/android/MASVS-CODE/MASTG-DEMO-0054/MastgTest_reversed.java b/demos/android/MASVS-CODE/MASTG-DEMO-0054/MastgTest_reversed.java deleted file mode 100644 index 93fa2000f9a..00000000000 --- a/demos/android/MASVS-CODE/MASTG-DEMO-0054/MastgTest_reversed.java +++ /dev/null @@ -1,59 +0,0 @@ -package org.owasp.mastestapp; - -import android.content.Context; -import android.content.SharedPreferences; -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;", "", "(Landroid/content/Context;)V", "mastgTest", "", "app_debug"}, k = 1, mv = {2, 0, 0}, xi = 48) -/* loaded from: classes3.dex */ -public final class MastgTest_reversed { - public static final int $stable = 8; - private final Context context; - - public MastgTest_reversed(Context context) { - Intrinsics.checkNotNullParameter(context, "context"); - this.context = context; - } - - public final String mastgTest() { - SharedPreferences prefs = this.context.getSharedPreferences("app_settings", 0); - if (!prefs.contains("setup_complete")) { - SecureSharedPreferences insecurePrefs = new SecureSharedPreferences(this.context, false); - insecurePrefs.saveData("user_role_insecure", "user"); - SecureSharedPreferences securePrefs = new SecureSharedPreferences(this.context, true); - securePrefs.saveData("user_role_secure", "user"); - Intrinsics.checkNotNull(prefs); - SharedPreferences.Editor editor$iv = prefs.edit(); - editor$iv.putBoolean("setup_complete", true); - editor$iv.commit(); - return "INITIAL SETUP COMPLETE.\n\nThe role for both secure and insecure tests has been set to 'user'.\n\nACTION REQUIRED:\n1. Use a file explorer or ADB shell on a rooted device.\n2. Go to: /data/data/org.owasp.mastestapp/shared_prefs/\n3. Open the file: app_settings.xml\n4. Change BOTH user values to admin.\n5. Save the file and run this test again to see the results."; - } - StringBuilder results = new StringBuilder(); - results.append("--- VERIFYING SCENARIO 1: 'kind: fail' (No HMAC Protection) ---\n"); - SecureSharedPreferences insecurePrefs2 = new SecureSharedPreferences(this.context, false); - String insecureRole = insecurePrefs2.loadData("user_role_insecure", "error"); - results.append("Loaded role from 'user_role_insecure': '" + insecureRole + "'\n"); - if (Intrinsics.areEqual(insecureRole, "admin")) { - results.append(">> OUTCOME: VULNERABLE. The application accepted the tampered 'admin' role because there was no integrity check.\n"); - } else { - results.append(">> OUTCOME: NOT EXPLOITED. The role is still '" + insecureRole + "'. Please ensure you changed it to 'admin' in the XML file.\n"); - } - results.append("\n--- VERIFYING SCENARIO 2: 'kind: pass' (HMAC Protection Enabled) ---\n"); - SecureSharedPreferences securePrefs2 = new SecureSharedPreferences(this.context, true); - String secureRole = securePrefs2.loadData("user_role_secure", "tampering_detected"); - results.append("Loaded role from 'user_role_secure': '" + secureRole + "'\n"); - if (Intrinsics.areEqual(secureRole, "tampering_detected")) { - results.append(">> OUTCOME: SECURE. The application detected that the data was tampered with and correctly rejected the invalid 'admin' role.\n"); - } else if (Intrinsics.areEqual(secureRole, "admin")) { - results.append(">> OUTCOME: UNEXPECTED. The role is 'admin', which means the HMAC check failed. This should not happen.\n"); - } else { - results.append(">> OUTCOME: NOT TAMPERED. The role is still '" + secureRole + "', and its HMAC signature is valid.\n"); - } - results.append("\n\nTest complete. To run the setup again, please clear the application's data in Android Settings and restart the test."); - String sb = results.toString(); - Intrinsics.checkNotNullExpressionValue(sb, "toString(...)"); - return sb; - } -} diff --git a/demos/android/MASVS-CODE/MASTG-DEMO-0054/output.txt b/demos/android/MASVS-CODE/MASTG-DEMO-0054/output.txt deleted file mode 100644 index 13191368bde..00000000000 --- a/demos/android/MASVS-CODE/MASTG-DEMO-0054/output.txt +++ /dev/null @@ -1,18 +0,0 @@ - - -┌─────────────────┐ -│ 2 Code Findings │ -└─────────────────┘ - - MastgTest_reversed.java - ❯❱rules.mastg-android-local-storage-input-validation - [MASVS-CODE-4] The application reads data from SharedPreferences without - an integrity check (like HMAC). This data could be tampered with by an - attacker on a rooted device, leading to privilege escalation or other - vulnerabilities. - - 23┆ SecureSharedPreferences insecurePrefs = new - SecureSharedPreferences(this.context, false); - ⋮┆---------------------------------------- - 35┆ SecureSharedPreferences insecurePrefs2 = new - SecureSharedPreferences(this.context, false); diff --git a/demos/android/MASVS-CODE/MASTG-DEMO-0054/run.sh b/demos/android/MASVS-CODE/MASTG-DEMO-0054/run.sh deleted file mode 100755 index f055560cbf1..00000000000 --- a/demos/android/MASVS-CODE/MASTG-DEMO-0054/run.sh +++ /dev/null @@ -1 +0,0 @@ -NO_COLOR=true semgrep -c ../../../../rules/mastg-android-local-storage-input-validation.yml ./MastgTest_reversed.java --text -o output.txt \ No newline at end of file diff --git a/rules/mastg-android-Local-Storage-for-Input-Validation.yml b/rules/mastg-android-Local-Storage-for-Input-Validation.yml deleted file mode 100644 index 10945fb705d..00000000000 --- a/rules/mastg-android-Local-Storage-for-Input-Validation.yml +++ /dev/null @@ -1,11 +0,0 @@ -rules: - - id: mastg-android-local-storage-for-input-validation - severity: WARNING - languages: - - java - metadata: - summary: Detects SharedPreferences usage without an integrity check. - message: "[MASVS-CODE-4] The application reads data from SharedPreferences without an integrity check (like HMAC). This data could be tampered with by an attacker on a rooted device, leading to privilege escalation or other vulnerabilities." - patterns: - - pattern: new SecureSharedPreferences($CONTEXT, false) - diff --git a/tests-beta/android/MASVS-CODE/MASTG-TEST-0281.md b/tests-beta/android/MASVS-CODE/MASTG-TEST-0281.md deleted file mode 100644 index cb191f1149f..00000000000 --- a/tests-beta/android/MASVS-CODE/MASTG-TEST-0281.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Use of Local Storage for Input Validation -platform: android -id: MASTG-TEST-0281 -type: [static] -weakness: MASWE-0082 -profiles: [L1, L2] ---- - -## Overview - -Data stored in Android's `SharedPreference`s can be tampered with on a rooted device. If an application reads this data without verifying its integrity (e.g., with an HMAC signature), it can lead to security vulnerabilities. This test checks if the application properly validates data read from local storage. - -## Steps - -1. Run a static analysis tool such as @MASTG-TOOL-0110 on the code and look for patterns where data is read from `SharedPreferences` without a corresponding integrity check. - -## Observation - -The static analysis tool identifies code where `SharedPreferences` data is loaded without an integrity check. - -## Evaluation - -The test fails if the application reads data from `SharedPreferences` without verifying its integrity using a mechanism like HMAC. From a855378d645e9838f901c77648cca55084203e01 Mon Sep 17 00:00:00 2001 From: Jeel Patel <84757990+jeel38@users.noreply.github.com> Date: Wed, 17 Sep 2025 17:33:09 +0530 Subject: [PATCH 15/16] updated changes --- tests-beta/android/MASVS-CODE/MASTG-TEST-0288.md | 2 +- tests/android/MASVS-CODE/MASTG-TEST-0002.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests-beta/android/MASVS-CODE/MASTG-TEST-0288.md b/tests-beta/android/MASVS-CODE/MASTG-TEST-0288.md index 40a30480a60..0f6060c0e38 100644 --- a/tests-beta/android/MASVS-CODE/MASTG-TEST-0288.md +++ b/tests-beta/android/MASVS-CODE/MASTG-TEST-0288.md @@ -21,4 +21,4 @@ The output identifies code where `SharedPreferences` data is loaded without an i ## Evaluation -The test fails if the application reads data from `SharedPreferences` without verifying its integrity using a mechanism like `HMAC`. +The test fails as the application does not verify the integrity of data loaded from `SharedPreferences`. Without an integrity check like `HMAC`, an attacker with root access can directly edit the SharedPreferences XML file, modifying values to grant themselves higher privileges. The app then reads and acts on this tampered data, leading to a critical security failure, like privilege escalation. diff --git a/tests/android/MASVS-CODE/MASTG-TEST-0002.md b/tests/android/MASVS-CODE/MASTG-TEST-0002.md index a79471307a8..a321550653b 100644 --- a/tests/android/MASVS-CODE/MASTG-TEST-0002.md +++ b/tests/android/MASVS-CODE/MASTG-TEST-0002.md @@ -10,7 +10,7 @@ masvs_v1_levels: - L2 profiles: [L1, L2] status: deprecated -covered_by: [MASTG-TEST-0281] +covered_by: [MASTG-TEST-0288] deprecation_note: New version available in MASTG V2 --- From f8639f8b7717a589f772756f3985a6706590b4b6 Mon Sep 17 00:00:00 2001 From: Jeel Patel <84757990+jeel38@users.noreply.github.com> Date: Wed, 17 Sep 2025 17:35:36 +0530 Subject: [PATCH 16/16] updated changes in demo file --- demos/android/MASVS-CODE/MASTG-DEMO-0061/MASTG-DEMO-0061.md | 1 - 1 file changed, 1 deletion(-) diff --git a/demos/android/MASVS-CODE/MASTG-DEMO-0061/MASTG-DEMO-0061.md b/demos/android/MASVS-CODE/MASTG-DEMO-0061/MASTG-DEMO-0061.md index 96d719c2ca5..fd478a8150a 100644 --- a/demos/android/MASVS-CODE/MASTG-DEMO-0061/MASTG-DEMO-0061.md +++ b/demos/android/MASVS-CODE/MASTG-DEMO-0061/MASTG-DEMO-0061.md @@ -4,7 +4,6 @@ title: Local Storage for Input Validation with semgrep id: MASTG-DEMO-0061 code: [kotlin] test: MASTG-TEST-0288 -profiles: [L1, L2] --- ### Sample