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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The DEMO must follow the content guidelines (https://mas.owasp.org/contributing/writing-content/mastg-demo.instructions).

For example, the "Evaluation" section should explain each relevant line of the output that is used as an evidence to give a FAIL to the test.

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.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

There is an open issue #3781 that already shows the need of creating a MASTG-KNOW file that explains Android DataStore, the several storing mechanisms (I see you use two different mechanisms in this DEMO); it would be great if you create the corresponding KNOW file, using the knowledge that you already have by doing this DEMO. This is not needed for this PR and can be done later, but just in case :)

See https://mas.owasp.org/contributing/writing-content/mastg-knowledge.instructions/ for more information on how to craft KNOW files.


{{ 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 = "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."
}
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This Java file does not seem to be the Java file resulting from decompiling the DEMO app. As commented previously, follow the DEMO guidelines (https://mas.owasp.org/contributing/writing-content/mastg-demo.instructions/) to provide these files accordingly.

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
john.doe@proto-demo.com
s3cr3tp4ssw0rd

[FILE] sensitive_datastore_proto.pb
John Doe
john.doe@proto-demo.com
(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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Please, provide all comments / code lines in English :)

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Use a temporal ID to avoid drifts

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.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
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.
This test verifies whether the app's code uses [Jetpack DataStore](https://developer.android.com/jetpack/androidx/releases/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.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
- Determine whether sensitive data is stored without encryption.
- Whether encryption is being applied in the code to the sensitive data before being stored.


## 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).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Starting a device is not anymore needed for the tests.


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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
2. Trigger app functionality that processes or stores sensitive data.
1. Exercise all the functionalities of the app that process or store 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)
Comment on lines +22 to +25

@jacobocasado jacobocasado May 10, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
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)
2. Access the app's private storage (@MASTG-TECH-0008) to locate DataStore files (normally at `/data/data/<package_name>/datastore/`):
- `.preferences_pb` (Preferences DataStore)
- `.proto` (Proto DataStore)


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

@jacobocasado jacobocasado May 10, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
4. Extract the DataStore files to the host machine using @MASTG-TECH-0003.
3. Extract the DataStore files of the app to the host machine (@MASTG-TECH-0002).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

MASTG-TECH-0003 is about dumping the APK, but not their associated storage files.


5. Inspect file contents using dynamic analysis techniques (@MASTG-TECH-0015) to determine whether sensitive data is stored in plaintext.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
5. Inspect file contents using dynamic analysis techniques (@MASTG-TECH-0015) to determine whether sensitive data is stored in plaintext.
4. Inspect the database content to determine if sensitive data is stored in plaintext.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

There is not a 1:1 TECH for this step, it's better to just enforce that the databases must be inspected.

_Note: Proto DataStore files require a Proto decoder for inspection._

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

It would be fine if we create a MASTG-TOOL for this purpose, I think there is not such tool right now.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

If you don't want to create the MASTG-TOOL file, you can create an issue (like this one #3728) so that we have it tracked.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the suggestion. I'll create an issue to keep track of it. If it doesn't get picked up by someone else and I find the time, I'll take a stab at implementing the tool myself.


## Observation

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
- Whether sensitive data (tokens, secrets, PII) is present in plaintext or easily reversible format.
- Occurrences inside the DataStore file where the sensitive data (tokens, secrets, PII) is stored in plaintext.


## 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.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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