-
-
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 all 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 @@ 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. | ||
|
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. | ||
| 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,36 @@ | ||||||
| --- | ||||||
| title: Sensitive Data Stored Unencrypted via DataStore | ||||||
| platform: android | ||||||
| title: Static Analysis for Unencrypted Sensitive Data in 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] | ||||||
| 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. | ||||||
|
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.
Suggested change
|
||||||
|
|
||||||
| ## 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. | ||||||
|
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.
Suggested change
|
||||||
|
|
||||||
| ## Evaluation | ||||||
|
|
||||||
| The test fails if the app references DataStore APIs and stores sensitive data without applying encryption. | ||||||
| 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). | ||||||||||||||||
|
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.
Suggested change
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. Starting a device is not anymore needed for the tests. |
||||||||||||||||
|
|
||||||||||||||||
| 2. Trigger app functionality that processes or stores sensitive data. | ||||||||||||||||
|
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.
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) | ||||||||||||||||
|
Comment on lines
+22
to
+25
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.
Suggested change
|
||||||||||||||||
|
|
||||||||||||||||
| 4. Extract the DataStore files to the host machine using @MASTG-TECH-0003. | ||||||||||||||||
|
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.
Suggested change
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. 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. | ||||||||||||||||
|
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.
Suggested change
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 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._ | ||||||||||||||||
|
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. It would be fine if we create a MASTG-TOOL for this purpose, I think there is not such tool right now.
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. 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.
Contributor
Author
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. 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. | ||||||||||||||||
|
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.
Suggested change
|
||||||||||||||||
|
|
||||||||||||||||
| ## 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. | ||||||||||||||||
|
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.
Suggested change
|
||||||||||||||||
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.