Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
30 changes: 28 additions & 2 deletions demos/android/MASVS-STORAGE/MASTG-DEMO-0069/MASTG-DEMO-0069.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,32 @@ title: Sensitive Data Stored Unencrypted via DataStore
id: MASTG-DEMO-0069
code: [kotlin]
test: MASTG-TEST-0305
status: placeholder
note: This placeholder reserves the ID for a demo illustrating the insecure storage of sensitive data using the modern Jetpack DataStore API without implementing secure encryption mechanisms. It directly corresponds to the MASTG-TEST-0305.
status: new
---

### Sample

The snippet below shows sample code that uses Jetpack DataStore to store sensitive data, including PII (email) and secrets (access token or password), in **plaintext** without encryption.

{{ MastgTest.kt # user_preferences.proto }}

### Steps

1. Install the app on a device (@MASTG-TECH-0005)
2. Make sure you have @MASTG-TOOL-0004 installed on your machine
3. Click the **Start** button
4. Execute `run.sh`.

The script pulls the DataStore files (`sensitive_datastore_proto.pb` for Proto DataStore and `sensitive_datastore_prefs.preferences_datastore` for Preferences DataStore) from the app sandbox and queries their content:

{{ run.sh }}

### Observation

The output contains the extracted sensitive data, showing PII (email address) and secrets (password or access token) stored in **plaintext** in the DataStore files.

{{ output.txt }}

### Evaluation

This test fails because the app uses DataStore without encryption, storing sensitive data such as an access token (secret) and the user's email address (PII) in **plaintext** within the sandbox.
88 changes: 88 additions & 0 deletions demos/android/MASVS-STORAGE/MASTG-DEMO-0069/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.util.Log
import androidx.datastore.core.DataStore
import androidx.datastore.core.Serializer
import androidx.datastore.dataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import com.google.protobuf.InvalidProtocolBufferException
import kotlinx.coroutines.runBlocking
import java.io.InputStream
import java.io.OutputStream


object UserPreferencesSerializer : Serializer<UserPreferences> {
override val defaultValue: UserPreferences = UserPreferences.getDefaultInstance()

override suspend fun readFrom(input: InputStream): UserPreferences {
try {
return UserPreferences.parseFrom(input)
} catch (exception: InvalidProtocolBufferException) {
Log.e("MASTG-PROTO", "Cannot read proto: ${exception.message}")
throw exception
}
}

override suspend fun writeTo(t: UserPreferences, output: OutputStream) {
t.writeTo(output)
}
}

private val Context.protoStore: DataStore<UserPreferences> by dataStore(
fileName = "sensitive_datastore_proto.pb",
serializer = UserPreferencesSerializer
)


private object PrefsKeys {
val KEY1 = stringPreferencesKey("k1")
val KEY2 = stringPreferencesKey("k2")
}

private val Context.prefsStore: DataStore<Preferences> by preferencesDataStore(
name = "sensitive_datastore_prefs"
)

class MastgTest(private val context: Context) {


private fun writeToProtoDataStore() {
val userEmail = "[email protected]"
val accessToken = "ghp_1234567890abcdefghijklmnopqrstuvABCD"

runBlocking {
context.protoStore.updateData {
UserPreferences.newBuilder()
.setName("John Doe")
.setEmail(userEmail)
.setAccessToken(accessToken)
.build()
}
Log.i("MASTG-PROTO", "Proto DataStore written: sensitive_datastore_proto.pb")
}
}


private fun writeToPrefsDataStore() {
val pii = "[email protected]"
val password = "s3cr3tp4ssw0rd"

runBlocking {
context.prefsStore.edit { prefs ->
prefs[PrefsKeys.KEY1] = pii
prefs[PrefsKeys.KEY2] = password
}
Log.i("MASTG-PREFS", "Preferences DataStore written: sensitive_datastore_prefs.preferences_datastore")
}
}

fun mastgTest(): String {
writeToProtoDataStore()
writeToPrefsDataStore()
return "DataStore Demo Complete. Proto + Preferences DataStore created with PII/Secrets in plaintext."
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package org.owasp.mastestapp;

import android.content.Context;
import android.util.Log;
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;", "<init>", "(Landroid/content/Context;)V", "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;

public MastgTest(Context context) {
Intrinsics.checkNotNullParameter(context, "context");
this.context = context;
}

public final String mastgTest() throws Exception {
DemoResults r = new DemoResults("0000");
try {
Log.d("MASTG-TEST", "Hello from the OWASP MASTG Test app.");
r.add(Status.PASS, "The app implemented a demo which passed the test with the following value: 'Hello from the OWASP MASTG Test app.'");
r.add(Status.FAIL, "The app implemented a demo which failed the test.");
throw new Exception("Example exception: Method not implemented.");
} catch (Exception e) {
r.add(Status.ERROR, e.toString());
return r.toJson();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
implementation("androidx.datastore:datastore:1.1.1")
implementation("androidx.datastore:datastore-preferences:1.1.1")
implementation("com.google.protobuf:protobuf-javalite:3.25.3")
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
id("com.google.protobuf") version "0.9.4"
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
protobuf {
protoc {
artifact = "com.google.protobuf:protoc:3.25.3"
}

generateProtoTasks {
all().configureEach {
builtins {
create("java") {
option("lite")
}
}
}
}
}
10 changes: 10 additions & 0 deletions demos/android/MASVS-STORAGE/MASTG-DEMO-0069/output.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
--- DataStore Extracted Content ---
[FILE] sensitive_datastore_prefs.preferences_pb
[email protected]
s3cr3tp4ssw0rd

[FILE] sensitive_datastore_proto.pb
John Doe
[email protected]
(ghp_1234567890abcdefghijklmnopqrstuvABCD

17 changes: 17 additions & 0 deletions demos/android/MASVS-STORAGE/MASTG-DEMO-0069/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/bin/bash

PACKAGE="org.owasp.mastestapp"
APP_DIR="/data/data/$PACKAGE/files/datastore"
OUTPUT_TXT_FILE="output.txt"

echo "[*] Pulling DataStore files directly from app sandbox..."
echo "--- DataStore Extracted Content ---" > "$OUTPUT_TXT_FILE"

# Wyciągamy wszystkie pliki bezpośrednio do strings
for f in $(adb exec-out run-as $PACKAGE ls "$APP_DIR"); do
echo "[FILE] $f" >> "$OUTPUT_TXT_FILE"
adb exec-out run-as $PACKAGE cat "$APP_DIR/$f" | strings >> "$OUTPUT_TXT_FILE"
echo >> "$OUTPUT_TXT_FILE"
done

echo "[*] Output written to $OUTPUT_TXT_FILE"
10 changes: 10 additions & 0 deletions demos/android/MASVS-STORAGE/MASTG-DEMO-0069/user_preferences.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
syntax = "proto3";

option java_package = "org.owasp.mastestapp";
option java_multiple_files = true;

message UserPreferences {
string name = 1;
string email = 2;
string access_token = 3;
}
37 changes: 31 additions & 6 deletions tests-beta/android/MASVS-STORAGE/MASTG-TEST-0305.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,36 @@
---
title: Sensitive Data Stored Unencrypted via DataStore
platform: android
title: Static Analysis for Unencrypted Sensitive Data in DataStore
id: MASTG-TEST-0305
type: [static, dynamic]
type: [static]
weakness: MASWE-0006
best-practices: []
profiles: [L1, L2]
status: placeholder
note: This test checks if the app uses the modern Jetpack DataStore API (Preferences DataStore or Proto DataStore) to store sensitive data (e.g., tokens, PII) without encryption. It confirms the absence of secure serializers or mechanisms to protect data integrity and confidentiality.
best-practices: [MASTG-BEST-0025]
profiles: [L2]
status: new
---

## Overview

This test verifies whether the app's code uses Jetpack DataStore APIs to store sensitive data — such as tokens, passwords, or PII — without encryption. By default, both Preferences DataStore (`.preferences_pb`) and Proto DataStore (`.proto`) persist data in plaintext.

## Steps

1. Obtain the application package (APK) using @MASTG-TECH-0003.

2. Use static analysis (@MASTG-TECH-0014) to review code for calls to DataStore APIs, including:
- `androidx.datastore.preferences.preferencesDataStore`
- `androidx.datastore.core.DataStore` or usage of generated Proto classes
- `dataStore.edit`, `updateData`, or `write` operations

3. Inspect whether:
- sensitive data is stored using default, unencrypted DataStore,
- no encryption layer (e.g., `EncryptedFile.Builder` for Preferences DataStore or encrypted custom serializer for Proto DataStore) is applied to sensitive fields.

## Observation

- Identify DataStore files referenced in the code.
- Determine whether sensitive data is stored without encryption.

## Evaluation

The test fails if the app references DataStore APIs and stores sensitive data without applying encryption.
39 changes: 39 additions & 0 deletions tests-beta/android/MASVS-STORAGE/MASTG-TEST-0308.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
---
platform: android
title: Runtime Verification of Sensitive Data Stored Unencrypted in DataStore
id: MASTG-TEST-0308
type: [dynamic, filesysttem]
weakness: MASWE-0006
best-practices: [MASTG-BEST-0025]
profiles: [L2]
status: new
---

## Overview

This test checks at runtime whether sensitive data — tokens, passwords, or PII — is stored in DataStore files in plaintext. The goal is to verify that sensitive information is not left unencrypted in the app's sandbox.

## Steps

1. Install and run the app on a rooted or emulated device (@MASTG-TECH-0005).

2. Trigger app functionality that processes or stores sensitive data.

3. Access the app's private storage (typically `/data/data/<package_name>/datastore/`) to locate DataStore files:
- `.preferences_pb` (Preferences DataStore)
- `.proto` (Proto DataStore)
(@MASTG-TECH-0008)

4. Extract the DataStore files to the host machine using @MASTG-TECH-0003.

5. Inspect file contents using dynamic analysis techniques (@MASTG-TECH-0015) to determine whether sensitive data is stored in plaintext.
_Note: Proto DataStore files require a Proto decoder for inspection._

## Observation

- Which DataStore files exist on the device.
- Whether sensitive data (tokens, secrets, PII) is present in plaintext or easily reversible format.

## Evaluation

The test fails if sensitive data is stored in DataStore files without encryption and can be read in plaintext through static or dynamic analysis.