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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions demos/android/MASVS-CODE/MASTG-DEMO-0054/MASTG-DEMO-0054.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
platform: android
title: Local Storage for Input Validation with semgrep
id: MASTG-DEMO-0054
code: [kotlin]
test: MASTG-TEST-0281
---

### Sample

The code snippet shows the improper use of local storage via `putString()` and `getString()` for storing sensitive data.

{{ 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 found in the code.

- Line 48, 49, 67, 68 contains the `putString()`.
- Line 81, 83 contains the `getString()`.
88 changes: 88 additions & 0 deletions demos/android/MASVS-CODE/MASTG-DEMO-0054/MastgTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
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 <b>user</b>")
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}"
}
}

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 <b>user</b>")
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"
}
}
92 changes: 92 additions & 0 deletions demos/android/MASVS-CODE/MASTG-DEMO-0054/MastgTest_reversed.java
Original file line number Diff line number Diff line change
@@ -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;", "<init>", "(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", "<div>Safe content</div>");
$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", "<script>alert('XSS')</script>");
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;
}
}
24 changes: 24 additions & 0 deletions demos/android/MASVS-CODE/MASTG-DEMO-0054/output.txt
Original file line number Diff line number Diff line change
@@ -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 input validation in the code.

48┆ $this$storeStrings_u24lambda_u241.putString("userJson",
"{\"name\":\"John\",\"admin\":false}");
⋮┆----------------------------------------
49┆ $this$storeStrings_u24lambda_u241.putString("htmlContent", "<div>Safe content</div>");
⋮┆----------------------------------------
67┆ $this$simulateTampering_u24lambda_u242.putString("userJson",
"{\"name\":\"John\",\"admin\":true}");
⋮┆----------------------------------------
68┆ $this$simulateTampering_u24lambda_u242.putString("htmlContent",
"<script>alert('XSS')</script>");
⋮┆----------------------------------------
81┆ String userJson = this.sharedPref.getString("userJson", "");
⋮┆----------------------------------------
83┆ String htmlContent = this.sharedPref.getString("htmlContent", "");
1 change: 1 addition & 0 deletions demos/android/MASVS-CODE/MASTG-DEMO-0054/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
NO_COLOR=true semgrep -c ../../../../rules/mastg-android-local-storage-for-input-validation.yml ./MastgTest_reversed.java --text -o output.txt
12 changes: 12 additions & 0 deletions rules/mastg-android-Local-Storage-for-Input-Validation.yml
Original file line number Diff line number Diff line change
@@ -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 input validation in the code."
patterns:
- pattern-either:
- pattern: $EDITOR.putString($KEY, $VALUE)
- pattern: $PREF.getString($KEY, $DEFAULT)
24 changes: 24 additions & 0 deletions tests-beta/android/MASVS-CODE/MASTG-TEST-0281.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
title: Use of Local Storage for Input Validation
platform: android
id: MASTG-TEST-0281
type: [static]
weakness: MASWE-0082
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.

## Steps

1. Run a static analysis tool such as @MASTG-TOOL-0110 on the code and look for uses of the `putString` and `getString`.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is an old file


## Observation

The output file shows usages of the input validation using `putString` and `getString` in the code.

## Evaluation

The test fails if the `putString()` and `getString()` was found in the code.
3 changes: 3 additions & 0 deletions tests/android/MASVS-CODE/MASTG-TEST-0002.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ masvs_v1_levels:
- L1
- L2
profiles: [L1, L2]
status: deprecated
covered_by: [MASTG-TEST-0281]
Copy link

Copilot AI Aug 31, 2025

Choose a reason for hiding this comment

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

The deprecation references MASTG-TEST-0281, but the actual replacement test created in this PR is MASTG-TEST-0288. This should be corrected to reference the correct test ID.

Suggested change
covered_by: [MASTG-TEST-0281]
covered_by: [MASTG-TEST-0288]

Copilot uses AI. Check for mistakes.
deprecation_note: New version available in MASTG V2
---

## Overview
Expand Down
Loading