diff --git a/demos/android/MASVS-STORAGE/MASTG-DEMO-0069/MASTG-DEMO-0069.md b/demos/android/MASVS-STORAGE/MASTG-DEMO-0069/MASTG-DEMO-0069.md index 5340f2333d1..0f9c4c5242f 100644 --- a/demos/android/MASVS-STORAGE/MASTG-DEMO-0069/MASTG-DEMO-0069.md +++ b/demos/android/MASVS-STORAGE/MASTG-DEMO-0069/MASTG-DEMO-0069.md @@ -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. diff --git a/demos/android/MASVS-STORAGE/MASTG-DEMO-0069/MastgTest.kt b/demos/android/MASVS-STORAGE/MASTG-DEMO-0069/MastgTest.kt new file mode 100644 index 00000000000..f771a3b5c95 --- /dev/null +++ b/demos/android/MASVS-STORAGE/MASTG-DEMO-0069/MastgTest.kt @@ -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 { + 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 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 by preferencesDataStore( + name = "sensitive_datastore_prefs" +) + +class MastgTest(private val context: Context) { + + + private fun writeToProtoDataStore() { + val userEmail = "john.doe@proto-demo.com" + 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 = "john.doe@proto-demo.com" + 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." + } +} diff --git a/demos/android/MASVS-STORAGE/MASTG-DEMO-0069/MastgTest_reversed.java b/demos/android/MASVS-STORAGE/MASTG-DEMO-0069/MastgTest_reversed.java new file mode 100644 index 00000000000..38c87895352 --- /dev/null +++ b/demos/android/MASVS-STORAGE/MASTG-DEMO-0069/MastgTest_reversed.java @@ -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;", "", "(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(); + } + } +} diff --git a/demos/android/MASVS-STORAGE/MASTG-DEMO-0069/build.gradle.kts.libs b/demos/android/MASVS-STORAGE/MASTG-DEMO-0069/build.gradle.kts.libs new file mode 100644 index 00000000000..328a5fadd10 --- /dev/null +++ b/demos/android/MASVS-STORAGE/MASTG-DEMO-0069/build.gradle.kts.libs @@ -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") \ No newline at end of file diff --git a/demos/android/MASVS-STORAGE/MASTG-DEMO-0069/build.gradle.kts.plugins b/demos/android/MASVS-STORAGE/MASTG-DEMO-0069/build.gradle.kts.plugins new file mode 100644 index 00000000000..6988457e7a5 --- /dev/null +++ b/demos/android/MASVS-STORAGE/MASTG-DEMO-0069/build.gradle.kts.plugins @@ -0,0 +1 @@ +id("com.google.protobuf") version "0.9.4" \ No newline at end of file diff --git a/demos/android/MASVS-STORAGE/MASTG-DEMO-0069/build.gradle.kts.sections b/demos/android/MASVS-STORAGE/MASTG-DEMO-0069/build.gradle.kts.sections new file mode 100644 index 00000000000..99ebc56e25a --- /dev/null +++ b/demos/android/MASVS-STORAGE/MASTG-DEMO-0069/build.gradle.kts.sections @@ -0,0 +1,15 @@ +protobuf { + protoc { + artifact = "com.google.protobuf:protoc:3.25.3" + } + + generateProtoTasks { + all().configureEach { + builtins { + create("java") { + option("lite") + } + } + } + } +} \ No newline at end of file diff --git a/demos/android/MASVS-STORAGE/MASTG-DEMO-0069/output.txt b/demos/android/MASVS-STORAGE/MASTG-DEMO-0069/output.txt new file mode 100644 index 00000000000..038bae4eec1 --- /dev/null +++ b/demos/android/MASVS-STORAGE/MASTG-DEMO-0069/output.txt @@ -0,0 +1,10 @@ +--- DataStore Extracted Content --- +[FILE] sensitive_datastore_prefs.preferences_pb +john.doe@proto-demo.com +s3cr3tp4ssw0rd + +[FILE] sensitive_datastore_proto.pb +John Doe +john.doe@proto-demo.com +(ghp_1234567890abcdefghijklmnopqrstuvABCD + diff --git a/demos/android/MASVS-STORAGE/MASTG-DEMO-0069/run.sh b/demos/android/MASVS-STORAGE/MASTG-DEMO-0069/run.sh new file mode 100755 index 00000000000..832d460cb71 --- /dev/null +++ b/demos/android/MASVS-STORAGE/MASTG-DEMO-0069/run.sh @@ -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" diff --git a/demos/android/MASVS-STORAGE/MASTG-DEMO-0069/user_preferences.proto b/demos/android/MASVS-STORAGE/MASTG-DEMO-0069/user_preferences.proto new file mode 100644 index 00000000000..c5d7a17da90 --- /dev/null +++ b/demos/android/MASVS-STORAGE/MASTG-DEMO-0069/user_preferences.proto @@ -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; +} diff --git a/tests-beta/android/MASVS-STORAGE/MASTG-TEST-0305.md b/tests-beta/android/MASVS-STORAGE/MASTG-TEST-0305.md index 27216f0ee10..2cf5bea15e6 100644 --- a/tests-beta/android/MASVS-STORAGE/MASTG-TEST-0305.md +++ b/tests-beta/android/MASVS-STORAGE/MASTG-TEST-0305.md @@ -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. diff --git a/tests-beta/android/MASVS-STORAGE/MASTG-TEST-0308.md b/tests-beta/android/MASVS-STORAGE/MASTG-TEST-0308.md new file mode 100644 index 00000000000..1de3cfe84a8 --- /dev/null +++ b/tests-beta/android/MASVS-STORAGE/MASTG-TEST-0308.md @@ -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//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.