Skip to content

Commit d270183

Browse files
committed
markdown fix
1 parent 4715be1 commit d270183

File tree

7 files changed

+256
-7
lines changed

7 files changed

+256
-7
lines changed

demos/android/MASVS-STORAGE/MASTG-DEMO-0070/MASTG-DEMO-0070.md

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,31 @@ title: Sensitive Data Stored Unencrypted via Room Database
44
id: MASTG-DEMO-0070
55
code: [kotlin]
66
test: MASTG-TEST-0306
7-
status: placeholder
8-
note: This placeholder reserves the ID for a demo illustrating the insecure storage of sensitive data using the Android Room Persistence Library without integrating an external encryption solution (like SQLCipher). It directly corresponds to the MASTG-TEST-0306.
9-
---
7+
status: new
8+
9+
### Sample
10+
11+
The snippet below shows sample code that uses the Android Room Persistence Library to store sensitive data, including PII (email) and secrets (access token), in **plaintext** without any encryption.
12+
13+
{{ MastgTest.kt }}
14+
15+
### Steps
16+
17+
1. Install the app on a device (@MASTG-TECH-0005)
18+
2. Make sure you have @MASTG-TOOL-0004 installed on your machine
19+
3. Click the **Start** button
20+
4. Execute `run.sh`.
21+
22+
The script pulls the Room database (`PrivateUnencryptedRoomDB`) along with its WAL/SHM files and queries the `users` table content:
23+
24+
{{ run.sh }}
25+
26+
### Observation
27+
28+
The output contains the extracted content from the `users` table, showing the sensitive PII (email address) and the access token stored in **plaintext**.
29+
30+
{{ output.txt }}
31+
32+
### Evaluation
33+
34+
This test fails because the app uses Room without additional encryption, storing sensitive data such as an access token (secret) and the user's email address (PII) in its sandbox in **plaintext**.
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
package org.owasp.mastestapp
2+
3+
import android.content.Context
4+
import android.util.Log
5+
import androidx.room.*
6+
import java.io.File
7+
8+
@Entity(tableName = "users")
9+
data class UserEntity(
10+
@PrimaryKey(autoGenerate = true) val id: Int = 0,
11+
val username: String,
12+
val email: String,
13+
val token: String
14+
)
15+
16+
@Dao
17+
interface UserDao {
18+
@Insert
19+
fun insert(user: UserEntity)
20+
21+
@Query("DELETE FROM users")
22+
fun clear()
23+
24+
@Query("SELECT * FROM users")
25+
fun getAll(): List<UserEntity>
26+
}
27+
28+
@Database(entities = [UserEntity::class], version = 1)
29+
abstract class AppDatabase : RoomDatabase() {
30+
abstract fun userDao(): UserDao
31+
32+
companion object {
33+
fun getInstance(ctx: Context): AppDatabase {
34+
val dbPath = ctx.getDatabasePath("PrivateUnencryptedRoomDB")
35+
Log.i("MASTG-ROOM", "Database absolute path: ${dbPath.absolutePath}")
36+
37+
// Wymuś utworzenie katalogu databases jeśli nie istnieje
38+
dbPath.parentFile?.let {
39+
if (!it.exists()) {
40+
Log.i("MASTG-ROOM", "Creating database directory: ${it.absolutePath}")
41+
it.mkdirs()
42+
} else {
43+
Log.i("MASTG-ROOM", "Database directory exists: ${it.absolutePath}")
44+
}
45+
}
46+
47+
return Room.databaseBuilder(
48+
ctx,
49+
AppDatabase::class.java,
50+
"PrivateUnencryptedRoomDB"
51+
)
52+
.allowMainThreadQueries()
53+
.build()
54+
}
55+
}
56+
}
57+
58+
class MastgTest(private val context: Context) {
59+
60+
private fun writeSensitiveDataToUnencryptedRoom() {
61+
val username = "demoUser_room"
62+
val userEmail = "[email protected]"
63+
val accessToken = "ghp_1234567890abcdefghijklmnopqrstuvABCD"
64+
65+
try {
66+
val db = AppDatabase.getInstance(context)
67+
val dao = db.userDao()
68+
69+
Log.i("MASTG-ROOM", "Clearing existing users...")
70+
dao.clear()
71+
72+
Log.i("MASTG-ROOM", "Inserting new user...")
73+
dao.insert(
74+
UserEntity(
75+
username = username,
76+
email = userEmail,
77+
token = accessToken
78+
)
79+
)
80+
81+
val allUsers = dao.getAll()
82+
Log.i("MASTG-ROOM", "Users in DB after insert: ${allUsers.size}")
83+
allUsers.forEach {
84+
Log.i("MASTG-ROOM", "User: ${it.username}, ${it.email}, ${it.token}")
85+
}
86+
87+
Log.i("MASTG-ROOM", "Room DB written successfully: PrivateUnencryptedRoomDB")
88+
89+
} catch (e: Exception) {
90+
Log.e("MASTG-ROOM", "Error writing Room DB: ${e.message}")
91+
}
92+
}
93+
94+
fun mastgTest(): String {
95+
Log.i("MASTG-ROOM", "Starting MastgTest RoomDB demo...")
96+
writeSensitiveDataToUnencryptedRoom()
97+
val dbPath = context.getDatabasePath("PrivateUnencryptedRoomDB")
98+
Log.i("MASTG-ROOM", "Database final path: ${dbPath.absolutePath}")
99+
return "RoomDB Demo Complete. Plaintext PII + Token stored in unencrypted Room database."
100+
}
101+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package org.owasp.mastestapp;
2+
3+
import android.content.Context;
4+
import android.util.Log;
5+
import kotlin.Metadata;
6+
import kotlin.jvm.internal.Intrinsics;
7+
8+
/* compiled from: MastgTest.kt */
9+
@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)
10+
/* loaded from: classes3.dex */
11+
public final class MastgTest {
12+
public static final int $stable = 8;
13+
private final Context context;
14+
15+
public MastgTest(Context context) {
16+
Intrinsics.checkNotNullParameter(context, "context");
17+
this.context = context;
18+
}
19+
20+
public final String mastgTest() throws Exception {
21+
DemoResults r = new DemoResults("0000");
22+
try {
23+
Log.d("MASTG-TEST", "Hello from the OWASP MASTG Test app.");
24+
r.add(Status.PASS, "The app implemented a demo which passed the test with the following value: 'Hello from the OWASP MASTG Test app.'");
25+
r.add(Status.FAIL, "The app implemented a demo which failed the test.");
26+
throw new Exception("Example exception: Method not implemented.");
27+
} catch (Exception e) {
28+
r.add(Status.ERROR, e.toString());
29+
return r.toJson();
30+
}
31+
}
32+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
plugins {
2+
kotlin("plugin.serialization") version "2.1.20"
3+
kotlin("kapt")
4+
}
5+
dependencies {
6+
implementation("androidx.room:room-runtime:2.6.1")
7+
implementation("androidx.room:room-ktx:2.6.1")
8+
kapt("androidx.room:room-compiler:2.6.1")
9+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
--- Users Table Content ---
2+
2|demoUser_room|[email protected]|ghp_1234567890abcdefghijklmnopqrstuvABCD
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#!/bin/bash
2+
3+
PACKAGE="org.owasp.mastestapp"
4+
DB_NAME="PrivateUnencryptedRoomDB"
5+
DB_PATH="/data/data/$PACKAGE/databases"
6+
SDCARD_DIR="/sdcard/$PACKAGE-db"
7+
OUTPUT_TXT_FILE="output.txt"
8+
9+
echo "[*] Pulling RoomDB (WAL mode)..."
10+
11+
adb shell "rm -rf $SDCARD_DIR && mkdir -p $SDCARD_DIR"
12+
13+
for f in "$DB_NAME" "$DB_NAME-wal" "$DB_NAME-shm"; do
14+
if adb shell "run-as $PACKAGE cp $DB_PATH/$f $SDCARD_DIR/"; then
15+
echo "[*] run-as succeeded for $f"
16+
else
17+
echo "[*] run-as failed for $f, trying su"
18+
adb shell "su 0 sh -c 'cp $DB_PATH/$f $SDCARD_DIR/'"
19+
fi
20+
done
21+
22+
adb pull "$SDCARD_DIR/." .
23+
24+
echo "--- Users Table Content ---" > "$OUTPUT_TXT_FILE"
25+
sqlite3 "$DB_NAME" "SELECT * FROM users;" >> "$OUTPUT_TXT_FILE"
26+
27+
adb shell "rm -rf $SDCARD_DIR" 2>/dev/null
28+
rm "$DB_NAME" "$DB_NAME-wal" "$DB_NAME-shm" 2>/dev/null
29+
30+
echo "[*] Output written to $OUTPUT_TXT_FILE"
Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,61 @@
11
---
2-
title: Sensitive Data Stored Unencrypted via Android Room DB
32
platform: android
3+
title: Sensitive Data Stored Unencrypted via Android Room DB
44
id: MASTG-TEST-0306
55
type: [static, dynamic]
66
weakness: MASWE-0006
7-
best-practices: []
7+
best-practices: [MASTG-BEST-0074]
88
profiles: [L1, L2]
9-
status: placeholder
10-
note: This test checks if the app uses the Android Room Persistence Library to store sensitive data (e.g., tokens, PII) without integrating an encryption layer (e.g., SQLCipher). It confirms the database file is stored in plaintext within the app's private sandbox.
9+
status: new
1110
---
11+
12+
## Overview
13+
14+
This test verifies whether an app stores sensitive data — such as tokens, credentials, or personally identifiable information (PII) — using the Android Room Persistence Library without encryption.
15+
By default, Room persists data in an unencrypted SQLite database within the app sandbox. **Encryption is not built-in**; developers must manually integrate an encryption mechanism, such as SQLCipher for Android.
16+
The goal of this test is to detect insecure storage of sensitive information in Room database files within the application sandbox.
17+
18+
---
19+
20+
## Steps
21+
22+
### Static Analysis
23+
24+
1. Obtain the application package (e.g., APK file) using @MASTG-TECH-0003.
25+
2. Use a static analysis technique (@MASTG-TECH-0014) to identify references to Room APIs such as:
26+
- `androidx.room.Room`
27+
- `@Database`, `@Dao`, `@Entity` annotations
28+
- `databaseBuilder`, `build` calls, and `SupportSQLiteOpenHelper.Factory` implementations.
29+
3. Inspect the database creation logic to determine whether:
30+
- sensitive fields (tokens, secrets, PII) are defined in `@Entity` classes and stored in plaintext.
31+
- a secure factory or wrapper (e.g., passing an implementation of `SupportSQLiteOpenHelper.Factory` for SQLCipher) is explicitly applied to the database builder.
32+
33+
### Dynamic Analysis
34+
35+
1. Install and run the app on a rooted or emulated device (@MASTG-TECH-0005).
36+
2. Trigger app functionality that processes or stores sensitive data.
37+
3. Access the app's private storage and locate the Room database file and related files. This requires accessing the app data directories (@MASTG-TECH-0008). Files are typically found under:
38+
- `/data/data/<package_name>/databases/<database_name>`
39+
- `/data/data/<package_name>/databases/<database_name>-wal`
40+
- `/data/data/<package_name>/databases/<database_name>-shm`
41+
4. **Extract** the database files from the device to the host machine using @MASTG-TECH-0003.
42+
5. Inspect the database content using a SQLite client tool, applying the technique for Dynamic Analysis (@MASTG-TECH-0015) to confirm whether sensitive data is stored in plaintext.
43+
44+
---
45+
46+
## Observation
47+
48+
The output should indicate:
49+
50+
- which Room database files the app creates.
51+
- whether sensitive data (tokens, secrets, PII) is present in tables (mapped from `@Entity` classes).
52+
- whether the stored values appear in plaintext.
53+
54+
---
55+
56+
## Evaluation
57+
58+
The test fails if:
59+
60+
- sensitive data is stored in Room database files without explicit encryption integration (e.g., SQLCipher).
61+
- plaintext tokens, secrets, or PII can be read from the database files through static or dynamic analysis.

0 commit comments

Comments
 (0)