-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Add Tests and Demos about "Sensitive Data Stored Unencrypted via DataStore" #3540
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 4 commits
15c8bd4
625d095
9fc4c8b
855f228
4a930bc
61544d1
fb0f6be
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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. | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
|
||
| 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." | ||
| } | ||
| } |
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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") | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } |
| 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 | ||
|
|
| 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 | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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" | ||
| 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; | ||
| } |
| 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 | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. | ||
|
|
||
| --- | ||
|
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
|
||
| 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
|
||
| 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
|
||
| 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
|
||
| 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
|
||
| - `.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
|
||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
|
||
| - 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
|
||
| - plaintext tokens, secrets, or PII can be read from the DataStore files through static or dynamic analysis. | ||
There was a problem hiding this comment.
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.