Skip to content
Closed
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
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-0041/MASTG-DEMO-0041.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
platform: android
title: Uses of Object Persistence with semgrep
id: MASTG-DEMO-0041
code: [kotlin]
test: MASTG-TEST-0266
---

### Sample

The code snippet shows that object persistence being used for storing sensitive information on the device using `org.json.JSONObject` and `org.json.JSONArray`.

{{ MastgTest.kt # MastgTest_reversed.java }}

### Steps

Let's run @MASTG-TOOL-0110 rules against the sample code.

{{ ../../../../rules/mastg-android-object-persistence.yml }}

{{ run.sh }}

### Observation

The output file shows usages of the object persistence in the code.

{{ output.txt }}

### Evaluation

The test fails because `org.json.JSONObject` and `org.json.JSONArray` were found in the code.

- Line 15 contains the import of `JSONArray`.
- Line 16 contains the import of `JSONObject`.
83 changes: 83 additions & 0 deletions demos/android/MASVS-CODE/MASTG-DEMO-0041/MastgTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package org.owasp.mastestapp

import android.content.Context
import android.util.Log
import org.json.JSONObject
import org.json.JSONArray
import java.io.File

class MastgTest(private val context: Context) {

private val sensitiveData = mapOf(
"username" to "admin",
"password" to "SuperSecret123!",
"api_key" to "AKIAIOSFODNN7EXAMPLE",
"auth_token" to "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
)

// Vulnerable function: stores sensitive data in an insecure JSON file
fun storeSensitiveDataInsecurely(): Boolean {
try {
val jsonData = JSONObject(sensitiveData as Map<*, *>)
val file = File(context.filesDir, "config.json")
file.writeText(jsonData.toString())
Log.d("MASTG-TEST", "Sensitive data stored insecurely at: ${file.absolutePath}")
return true
} catch (e: Exception) {
Log.e("MASTG-TEST", "Error storing data", e)
return false
}
}

// Vulnerable function: loads sensitive data without proper validation
fun loadSensitiveDataInsecurely(): Map<String, String> {
try {
val file = File(context.filesDir, "config.json")
val jsonString = file.readText()
val jsonData = JSONObject(jsonString)

val result = mutableMapOf<String, String>()
val keys = jsonData.keys()
while (keys.hasNext()) {
val key = keys.next()
result[key] = jsonData.getString(key)
}

Log.d("MASTG-TEST", "Loaded sensitive data: $result")
return result
} catch (e: Exception) {
Log.e("MASTG-TEST", "Error loading data", e)
return emptyMap()
}
}

// Another vulnerable example: storing serialized JSON array with sensitive data
fun storeSensitiveArrayInsecurely(): Boolean {
try {
val jsonArray = JSONArray()
jsonArray.put(JSONObject(mapOf("credit_card" to "4111111111111111", "cvv" to "123")))
jsonArray.put(JSONObject(mapOf("credit_card" to "5555555555554444", "cvv" to "456")))

val file = File(context.filesDir, "transactions.json")
file.writeText(jsonArray.toString())
Log.d("MASTG-TEST", "Sensitive array stored insecurely at: ${file.absolutePath}")
return true
} catch (e: Exception) {
Log.e("MASTG-TEST", "Error storing array", e)
return false
}
}

fun mastgTest(): String {
// Store sensitive data
storeSensitiveDataInsecurely()

// Load sensitive data
val loadedData = loadSensitiveDataInsecurely()

// Store sensitive array
storeSensitiveArrayInsecurely()

return "MASTG Test completed successfully. Check logs for details."
}
}
91 changes: 91 additions & 0 deletions demos/android/MASVS-CODE/MASTG-DEMO-0041/MastgTest_reversed.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package org.owasp.mastestapp;

import android.content.Context;
import android.util.Log;
import androidx.autofill.HintConstants;
import java.io.File;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import kotlin.Metadata;
import kotlin.TuplesKt;
import kotlin.collections.MapsKt;
import kotlin.io.FilesKt;
import kotlin.jvm.internal.Intrinsics;
import org.json.JSONArray;
import org.json.JSONObject;

/* 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\u0010$\n\u0002\u0010\u000e\n\u0000\n\u0002\u0010\u000b\n\u0002\b\u0004\b\u0007\u0018\u00002\u00020\u0001B\u000f\u0012\u0006\u0010\u0002\u001a\u00020\u0003¢\u0006\u0004\b\u0004\u0010\u0005J\u0006\u0010\t\u001a\u00020\nJ\u0012\u0010\u000b\u001a\u000e\u0012\u0004\u0012\u00020\b\u0012\u0004\u0012\u00020\b0\u0007J\u0006\u0010\f\u001a\u00020\nJ\u0006\u0010\r\u001a\u00020\bR\u000e\u0010\u0002\u001a\u00020\u0003X\u0082\u0004¢\u0006\u0002\n\u0000R\u001a\u0010\u0006\u001a\u000e\u0012\u0004\u0012\u00020\b\u0012\u0004\u0012\u00020\b0\u0007X\u0082\u0004¢\u0006\u0002\n\u0000¨\u0006\u000e"}, d2 = {"Lorg/owasp/mastestapp/MastgTest;", "", "context", "Landroid/content/Context;", "<init>", "(Landroid/content/Context;)V", "sensitiveData", "", "", "storeSensitiveDataInsecurely", "", "loadSensitiveDataInsecurely", "storeSensitiveArrayInsecurely", "mastgTest", "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 Map<String, String> sensitiveData;

public MastgTest(Context context) {
Intrinsics.checkNotNullParameter(context, "context");
this.context = context;
this.sensitiveData = MapsKt.mapOf(TuplesKt.to(HintConstants.AUTOFILL_HINT_USERNAME, "admin"), TuplesKt.to(HintConstants.AUTOFILL_HINT_PASSWORD, "SuperSecret123!"), TuplesKt.to("api_key", "AKIAIOSFODNN7EXAMPLE"), TuplesKt.to("auth_token", "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."));
}

public final boolean storeSensitiveDataInsecurely() {
try {
Map<String, String> map = this.sensitiveData;
Intrinsics.checkNotNull(map, "null cannot be cast to non-null type kotlin.collections.Map<*, *>");
JSONObject jsonData = new JSONObject(map);
File file = new File(this.context.getFilesDir(), "config.json");
String jSONObject = jsonData.toString();
Intrinsics.checkNotNullExpressionValue(jSONObject, "toString(...)");
FilesKt.writeText$default(file, jSONObject, null, 2, null);
Log.d("MASTG-TEST", "Sensitive data stored insecurely at: " + file.getAbsolutePath());
return true;
} catch (Exception e) {
Log.e("MASTG-TEST", "Error storing data", e);
return false;
}
}

public final Map<String, String> loadSensitiveDataInsecurely() {
try {
File file = new File(this.context.getFilesDir(), "config.json");
String jsonString = FilesKt.readText$default(file, null, 1, null);
JSONObject jsonData = new JSONObject(jsonString);
Map result = new LinkedHashMap();
Iterator keys = jsonData.keys();
while (keys.hasNext()) {
String key = keys.next();
result.put(key, jsonData.getString(key));
}
Log.d("MASTG-TEST", "Loaded sensitive data: " + result);
return result;
} catch (Exception e) {
Log.e("MASTG-TEST", "Error loading data", e);
return MapsKt.emptyMap();
}
}

public final boolean storeSensitiveArrayInsecurely() {
try {
JSONArray jsonArray = new JSONArray();
jsonArray.put(new JSONObject(MapsKt.mapOf(TuplesKt.to("credit_card", "4111111111111111"), TuplesKt.to("cvv", "123"))));
jsonArray.put(new JSONObject(MapsKt.mapOf(TuplesKt.to("credit_card", "5555555555554444"), TuplesKt.to("cvv", "456"))));
File file = new File(this.context.getFilesDir(), "transactions.json");
String jSONArray = jsonArray.toString();
Intrinsics.checkNotNullExpressionValue(jSONArray, "toString(...)");
FilesKt.writeText$default(file, jSONArray, null, 2, null);
Log.d("MASTG-TEST", "Sensitive array stored insecurely at: " + file.getAbsolutePath());
return true;
} catch (Exception e) {
Log.e("MASTG-TEST", "Error storing array", e);
return false;
}
}

public final String mastgTest() {
storeSensitiveDataInsecurely();
loadSensitiveDataInsecurely();
storeSensitiveArrayInsecurely();
return "MASTG Test completed successfully. Check logs for details.";
}
}
13 changes: 13 additions & 0 deletions demos/android/MASVS-CODE/MASTG-DEMO-0041/output.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@


┌─────────────────┐
│ 2 Code Findings │
└─────────────────┘

MastgTest_reversed.java
❯❱ rules.mastg-android-object-persistence
[MASVS-CODE-4] The application make use of Object Serialization in the code.

15┆ import org.json.JSONArray;
⋮┆----------------------------------------
16┆ import org.json.JSONObject;
1 change: 1 addition & 0 deletions demos/android/MASVS-CODE/MASTG-DEMO-0041/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
NO_COLOR=true semgrep -c ../../../../rules/mastg-android-object-persistence.yml ./MastgTest_reversed.java --text -o output.txt
12 changes: 12 additions & 0 deletions rules/mastg-android-object-persistence.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
rules:
- id: mastg-android-object-persistence
severity: WARNING
languages:
- java
metadata:
summary: This rule looks for use of Object Serialization.
message: "[MASVS-CODE-4] The application make use of Object Serialization in the code."
patterns:
- pattern-either:
- pattern: import org.json.JSONArray;
- pattern: import org.json.JSONObject;
23 changes: 23 additions & 0 deletions tests-beta/android/MASVS-CODE/MASTG-TEST-0266.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
title: Use of Object Persistence using JSON
Copy link
Collaborator

Choose a reason for hiding this comment

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

We shouldn't simply test for the use of JSONObject and JSONArray as this isn't a vulnerability in itself. While it's true that JSON may hold sensitive data, its presence alone is not indicative of a security issue. What actually matters in the context of this MASVS-CODE control is the risk of unsafe deserialization, where attacker-controlled input is used to reconstruct objects, potentially triggering unintended behavior or code execution.

If you check the related MASWE-0088: Insecure Object Deserialization
you'll see that we corrected the scope there when we created it.

This is a common and well-documented threat in Android, especially when using custom classes with Serializable, Parcelable, or reflection-based deserialization.

The current proposal in this PR seems to deal with other concerns like data persistence or secure storage. These concerns are already covered under MASVS-STORAGE and MASVS-CRYPTO, so this test should focus specifically on identifying and mitigating deserialization risks in the app logic.

I totally see how this can be confusing when reading the original test (MASTG-TEST-0034). I went thought it now, took the relevant excerpts for you and explained why it relates to unsafe deserialization:

  1. "First identify all instances of object serialization and check if they carry any sensitive data. If yes, check if it is properly protected against eavesdropping or unauthorized modification."

This relates to unsafe deserialization because serialized data can be intercepted or modified by attackers, leading to code execution or data tampering if deserialized without validation.

  1. "Make sure that the data within the de-serialized object is carefully validated before it is actively used (e.g., no exploit of business/application logic)."

This is relevant because lack of validation of deserialized data may trigger unintended behavior or exploit logic flaws in the application.

  1. "Use Serializable only when the serialized classes are stable... not using reflection-based persistence..."

Reflection-based deserialization combined with Serializable can allow attackers to manipulate inputs and trigger arbitrary code paths, a classic unsafe deserialization vector.

  1. "Search the source code for... import java.io.Serializable, implements Serializable"

This indicates that the app is using Java serialization, which could enable a deserialization attack when handling untrusted data.

See "Risk: Unwanted Object Deserialization".

  1. "Use @MASTG-TOOL-0001 (Frida) to hook into the deserialization methods..."

Monitoring how objects are deserialized may help detecting unsafe behavior, such as the use of untrusted input to reconstruct objects. While relevant, I think you can skip this part of the test (the dynamic testing) for now and focus on static.

  1. "Make sure that appropriate security measures are taken when sensitive information is stored in an Intent via a Bundle that contains a Parcelable..."

Parcelables are Android’s custom serialization mechanism. If used without proper access control, attackers can inject malicious objects through IPC and exploit unsafe deserialization.

See Risk: Deserialization of untrusted input

Suggestion

platform: android
id: MASTG-TEST-0266
type: [static]
weakness: MASWE-0088
---

## Overview

Android provides the JSONObject and JSONArray classes, which are essential for handling JSON data. JSON or JavaScript Object Notation, is a lightweight format for data interchange that is both human-readable and easy for machines to parse and generate. One important characteristic of JSON representations is that they are based on strings, making them immutable. This immutability means that once a JSON object is created, it cannot be changed. As a result, if sensitive information is stored within a JSON structure, it becomes more difficult to completely remove that information from memory, posing potential security risks. Additionally, JSON data can be stored in various locations, including NoSQL databases, which are designed to handle unstructured data, or in files on a local or remote file system, providing flexibility in how data is managed and accessed.

## Steps

1. Run a static analysis tool such as @MASTG-TOOL-0110 on the code and look for uses of the `JSONObject` and `JSONArray`.

## Observation

The output file shows usages of the object Persistence using `JSONObject` and `JSONArray` in the code.

## Evaluation

The test fails if the `org.json.JSONObject` and `org.json.JSONArray` was found in the code.
2 changes: 2 additions & 0 deletions tests/android/MASVS-CODE/MASTG-TEST-0034.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ title: Testing Object Persistence
masvs_v1_levels:
- L1
- L2
status: deprecated
deprecation_note: New version available in MASTG V2
---

## Overview
Expand Down