Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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 @@
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.

Check failure on line 35 in demos/android/MASVS-STORAGE/MASTG-DEMO-0069/MASTG-DEMO-0069.md

View workflow job for this annotation

GitHub Actions / markdown-lint-check

Custom rule

demos/android/MASVS-STORAGE/MASTG-DEMO-0069/MASTG-DEMO-0069.md:35:195 search-replace Custom rule [trailing-spaces: Avoid trailing spaces] [Context: "column: 195 text:' '"] https://github.com/OnkarRuikar/markdownlint-rule-search-replace
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;
}
53 changes: 49 additions & 4 deletions tests-beta/android/MASVS-STORAGE/MASTG-TEST-0305.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,56 @@
---
title: Sensitive Data Stored Unencrypted via DataStore
platform: android
title: Sensitive Data Stored Unencrypted via 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]
weakness: MASWE-0006
best-practices: []
best-practices: [MASTG-BEST-0074]
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.
status: new
---

## Overview

This test verifies whether an app stores sensitive data — such as tokens, passwords, or personally identifiable information (PII) — using Jetpack DataStore without encryption.
Both Preferences DataStore (backed by `.preferences_pb` file) and Proto DataStore (backed by `.proto` file) persist data in plaintext by default unless developers explicitly implement and apply an encryption layer (e.g., using `SecurityCrypto` or a custom serializer).
The goal of this test is to detect insecure storage of sensitive information in DataStore files within the app sandbox.

---
Comment thread
macik09 marked this conversation as resolved.
Outdated

## Steps

### Static Analysis

Check failure on line 22 in tests-beta/android/MASVS-STORAGE/MASTG-TEST-0305.md

View workflow job for this annotation

GitHub Actions / markdown-lint-check

Headings should be surrounded by blank lines

tests-beta/android/MASVS-STORAGE/MASTG-TEST-0305.md:22 MD022/blanks-around-headings Headings should be surrounded by blank lines [Expected: 1; Actual: 0; Below] [Context: "### Static Analysis"] https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md022.md
1. Obtain the application package (e.g., APK file) using @MASTG-TECH-0003.

Check failure on line 23 in tests-beta/android/MASVS-STORAGE/MASTG-TEST-0305.md

View workflow job for this annotation

GitHub Actions / markdown-lint-check

Lists should be surrounded by blank lines

tests-beta/android/MASVS-STORAGE/MASTG-TEST-0305.md:23 MD032/blanks-around-lists Lists should be surrounded by blank lines [Context: "1. Obtain the application pack..."] https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md032.md
2. Use a static analysis technique (@MASTG-TECH-0014) to identify references to DataStore APIs such as:
- `androidx.datastore.preferences.preferencesDataStore`
- `androidx.datastore.core.DataStore` (or usage of generated Proto classes).
- `dataStore.edit`, `updateData`, or `write` operations.
3. Inspect the code to determine whether:
- sensitive data is stored using the default, unencrypted implementation.
- a secure mechanism (e.g., applying an `EncryptedFile.Builder` for Preferences DataStore or using an encrypted custom serializer for Proto DataStore) is explicitly applied to the sensitive fields.

### Dynamic Analysis

Check failure on line 32 in tests-beta/android/MASVS-STORAGE/MASTG-TEST-0305.md

View workflow job for this annotation

GitHub Actions / markdown-lint-check

Headings should be surrounded by blank lines

tests-beta/android/MASVS-STORAGE/MASTG-TEST-0305.md:32 MD022/blanks-around-headings Headings should be surrounded by blank lines [Expected: 1; Actual: 0; Below] [Context: "### Dynamic Analysis"] https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md022.md
1. Install and run the app on a rooted or emulated device (@MASTG-TECH-0005).

Check failure on line 33 in tests-beta/android/MASVS-STORAGE/MASTG-TEST-0305.md

View workflow job for this annotation

GitHub Actions / markdown-lint-check

Lists should be surrounded by blank lines

tests-beta/android/MASVS-STORAGE/MASTG-TEST-0305.md:33 MD032/blanks-around-lists Lists should be surrounded by blank lines [Context: "1. Install and run the app on ..."] https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md032.md
2. Trigger app functionality that processes or stores sensitive data.
3. Access the app’s private storage (typically `/data/data/<package_name>/datastore/`) and locate the DataStore files. This requires accessing the app data directories (@MASTG-TECH-0008). File names usually end with:

Check failure on line 35 in tests-beta/android/MASVS-STORAGE/MASTG-TEST-0305.md

View workflow job for this annotation

GitHub Actions / markdown-lint-check

Custom rule

tests-beta/android/MASVS-STORAGE/MASTG-TEST-0305.md:35:18 search-replace Custom rule [curly-single-quotes: Don't use curly single quotes] [Context: "column: 18 text:'’'"] https://github.com/OnkarRuikar/markdownlint-rule-search-replace
- `.preferences_pb` (Preferences DataStore)
- `.proto` (Proto DataStore)
4. Extract the DataStore files from the device using @MASTG-TECH-0003.
5. Inspect the file content using a suitable tool, applying the technique for Dynamic Analysis (@MASTG-TECH-0015) to confirm whether sensitive data is stored in plaintext. *Note: Proto DataStore files require a Proto decoder for inspection.*

Check failure on line 39 in tests-beta/android/MASVS-STORAGE/MASTG-TEST-0305.md

View workflow job for this annotation

GitHub Actions / markdown-lint-check

Emphasis style

tests-beta/android/MASVS-STORAGE/MASTG-TEST-0305.md:39:241 MD049/emphasis-style Emphasis style [Expected: underscore; Actual: asterisk] https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md049.md

Check failure on line 39 in tests-beta/android/MASVS-STORAGE/MASTG-TEST-0305.md

View workflow job for this annotation

GitHub Actions / markdown-lint-check

Emphasis style

tests-beta/android/MASVS-STORAGE/MASTG-TEST-0305.md:39:173 MD049/emphasis-style Emphasis style [Expected: underscore; Actual: asterisk] https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md049.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.

For all the new PRs:

We require separate tests.

See for example:


---

## Observation

The output should indicate:
- which DataStore files the app creates,

Check failure on line 46 in tests-beta/android/MASVS-STORAGE/MASTG-TEST-0305.md

View workflow job for this annotation

GitHub Actions / markdown-lint-check

Lists should be surrounded by blank lines

tests-beta/android/MASVS-STORAGE/MASTG-TEST-0305.md:46 MD032/blanks-around-lists Lists should be surrounded by blank lines [Context: "- which DataStore files the ap..."] https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md032.md
- whether sensitive data (tokens, secrets, PII) is present inside these files,
- whether the stored values appear in plaintext (or easily reversible format).

---

## Evaluation

The test fails if:
- sensitive data is stored in DataStore files without encryption.

Check failure on line 55 in tests-beta/android/MASVS-STORAGE/MASTG-TEST-0305.md

View workflow job for this annotation

GitHub Actions / markdown-lint-check

Lists should be surrounded by blank lines

tests-beta/android/MASVS-STORAGE/MASTG-TEST-0305.md:55 MD032/blanks-around-lists Lists should be surrounded by blank lines [Context: "- sensitive data is stored in ..."] https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md032.md
- plaintext tokens, secrets, or PII can be read from the DataStore files through static or dynamic analysis.
Loading