Storage

Einleitung

Lokal gespeicherte Daten

  • Mobile Apps verarbeiten sensible Daten
  • Lokale Speicherung teilweise nötig
  • Beispiele:
    • Personenbezogene Daten
    • API-Schlüssel
    • Secrets

Speicherorte

  • In privaten Bereichen (interner App-Speicher)
  • In öffentlichen Ordnern (zugänglich für andere Nutzer oder andere Apps)

Risiko

  • Unbeabsichtigte Speicherung oder Exponierung sensibler Daten
  • Z.B. durch Seiteneffekt bei Nutzung bestimmter APIs oder Systemfunktionen (z.B. Backups, Logs)
  • Schutz vor ungewolltem Zugriff ist essenziell

Verification Controls

MASVS-STORAGE-1

The app securely stores sensitive data.

MASVS-STORAGE-2

The app prevents leakage of sensitive data.

Weakness Enumeration

MASVS-STORAGE-1

L1, P

  • MASWE-0007: Sensitive Data Stored Unencrypted in Shared Storage Requiring No User Interaction

L2

  • MASWE-0006: Sensitive Data Stored Unencrypted in Private Storage Locations

MASVS-STORAGE-2

L1, P

  • MASWE-0001: Insertion of Sensitive Data into Logs
  • MASWE-0002: Sensitive Data Stored With Insufficient Access Restrictions in Internal Locations
  • MASWE-0004: Sensitive Data Not Excluded From Backup

L2

Testing Guide

MASVS-STORAGE-1

MASVS-STORAGE-2

Beispiel MASTG-TEST-0231

Statischer Test

Test MASTG-TEST-0231 prüft, ob Weakness MASWE-0001 zu Control MASVS-STORAGE-2 vorhanden ist.

Weakness und Control

MASWE-0001

Insertion of Sensitive Data into Logs

MASVS-STORAGE-2

The app prevents leakage of sensitive data.

MASTG-TEST-0231

  • Suche mit semgrep nach Schlüsselwörtern
  • Mögliche Schlüsselwörter für Dart: print, logging:, Logger, .finest, .finer, .fine, .info, .warning, .severe, .shout

semgrep Rule

rules:
  - id: dart-logging-detected
    pattern-either:
      - pattern: print(...)
      - pattern: debugPrint(...)
      - pattern: $LOG.log(...)
      - pattern: $LOG.info(...)
      - pattern: $LOG.warning(...)
      - pattern: $LOG.severe(...)
      - pattern: $LOG.fine(...)
      - pattern: $LOG.finer(...)
      - pattern: $LOG.finest(...)
      - pattern: $LOG.shout(...)
    message: Logging statement detected. Ensure sensitive data is not logged in production.
    severity: WARNING
    languages:
      - dart
    metadata:
      category: security
      cwe: "CWE-532: Insertion of Sensitive Information into Log File"
      owasp: "MASWE-0001: Insertion of Sensitive Data into Logs"

Test

semgrep scan --config ./logging_reference.yaml

Ergebnis

Logging statement detected. Ensure sensitive data is not logged
in production.                                                 
                                                                
  23┆ print('${record.level.name}: ${record.time}:
      ${record.message}');                        
  ⋮┆----------------------------------------
  43┆ log.info('MASTG-DEMO-$demoId demonstrated a
      successful test: $message');               
  ⋮┆----------------------------------------
  46┆ log.info('MASTG-DEMO-$demoId demonstrated a failed
      test: $message');                                 
  ⋮┆----------------------------------------
  49┆ log.severe('MASTG-DEMO-$demoId failed: $message');

Beispiel MASTG-DEMO-0001

Ausgangspunkt Demo

Demo MASTG-DEMO-0001 beinhaltet Weakness MASWE-0007 zu Control MASVS-STORAGE-1. Dies kann durch Test MASTG-TEST-0200 getestet werden.

MASTG-DEMO-0001

fun mastgTestApi() {
    val externalStorageDir = context.getExternalFilesDir(null)
    val fileName = File(externalStorageDir, "secret.txt")
    val fileContent = "secr3tPa\$\$W0rd\n"

    try {
        FileOutputStream(fileName).use { output ->
            output.write(fileContent.toByteArray())
            Log.d("WriteExternalStorage", "File written to external storage successfully.")
        }
    } catch (e: IOException) {
        Log.e("WriteExternalStorage", "Error writing file to external storage", e)
    }
}

Weakness und Control

MASWE-0007

Sensitive Data Stored Unencrypted in Shared Storage Requiring No User Interaction

MASVS-STORAGE-1

The app securely stores sensitive data.

MASTG-TEST-0200

  1. Install the app
  2. Before running the app, get the current list of files in the external storage.
  3. Exercise the app.
  4. After running the app, retrieve the list of files in the external storage again.
  5. Calculate the difference between the two lists.

The test case fails if the files found above are not encrypted and leak sensitive data.

Installieren der App

emulator -avd Medium_Phone_API_34 -netdelay none -netspeed full
adb install ./MASTG-DEMO-0001.apk

Hilfsdatei anlegen

#!/bin/bash

# SUMMARY: This script creates a dummy file to mark a timestamp that we can use later
# on to identify files created during the app exercising

adb shell "touch /data/local/tmp/test_start"

App ausführen

SD Karte durchsuchen

#!/bin/bash

# SUMMARY: List all files created after the creation date of a file created in run_before

adb shell "find /sdcard/ -type f -newer /data/local/tmp/test_start" > output.txt
adb shell "rm /data/local/tmp/test_start"
mkdir -p new_files
while read -r line; do
  adb pull "$line" ./new_files/
done < output.txt

Ausgelesene Dateien

secret.txt

secr3tPa$$W0rd

MASTG-DEMO-0001 (Flutter)

Rahmenapp

Demos

MASTG-DEMO-0001

Future<void> mastgTestExternalPath() async {
    var path = await ExternalPath.getExternalStoragePublicDirectory(
      ExternalPath.DIRECTORY_DOCUMENTS,
    );
    final file = File('$path/secret.txt');
    await file.writeAsString('secr3tPa\$\$W0rd\n');
}

Bewertung

  • Das Beispiel zeigt einen Durchstich vom Demonstrationscode bis zur verletzten Control
  • Korrektur des Codes nötig
    • Brauchen wir die Funktionalität?
    • Was genau ist problematisch?
    • Was ist die minimale Änderung, die Sicherheit gewährleistet?

Lösungsvorschlag

Beispiel AES-CBC mit Dart

AES-CBC mit Dart

  • Wir greifen Beispiel 1 nochmal auf
  • Verschlüsselung vermeidet Weakness
  • AES-CBC ist ein beliebter Standard im Bereich symmetrischer Verschlüsselung

Exkurs - Symmetrische Verschlüsselung

  • Gleicher Schlüssel zum Ver- und Entschlüsseln
  • Umsetzung in Hardware oder Software
  • Aufteilung in Datenblöcke

Data Encryption Standard (DES)

  • 1977 von IBM entwickelt
  • Mischung von Transpositionen und Substitutionen
  • Passwortspeicherung in vielen Unix-Systemen
  • Ursprüngliche Form inzwischen unsicher
  • Verarbeitung von 64-Bit-Blöcken Klartext zu 64-Bit-Blöcken Chiffre
  • 56-Bit Schlüssel, 19 Umwandlungsschritte
  • Zum Entschlüsseln Schritte andersherum ausführen
  • Schritt 1 Schlüsselunabhängige Transposition
  • Schritte 2-17 Schlüsselfunktionen
  • Schritt 18 Tausch der Hälften
  • Schritt 19 Inversion von Schritt 1

Advanced Encryption Standard

  • Wettbewerb des National Institute of Standards and Technology (1997)
    • Symmetrisch
    • Blockbasiert
    • Öffentlich
    • 128, 192 oder 256 Bit
    • Soft-/Hardware
  • Aus 15 Vorschlägen wurde nach drei Jahren ein Sieger gekührt: Rijndael (benannt nach Rijmen und Daemen)
  • Als AES standardisiert
  • Heute meistgenutztes Verfahren

IV / Blockmodus

  • Die Verfahren arbeiten Blockweise
  • Aus dieser Perspektive reine Substitutionsverfahren
  • Das ist ein potentielles Sicherheitsproblem

Electronic Code Book (ECB)

  • Einfachstes Verfahren
  • Auftrennen in gleichgroße Blöcke
  • Auffüllen des letzten Blocks

Problem

  • Angenommen Datenstruktur ist bekannt (Alphabetisch sortierte Mitarbeiterliste mit Jahresbonus)
  • Manipulation ohne Knacken des Chiffres möglich

Cipher Block Chaining (CBC)

  • Erster Block wird mit Initialisierungsvektor geXORt
  • Folgende Blöcke mit dem jeweils vorherigen chiffrierten Datenblock
  • Gleicher Textblock bei gleichem Schlüssel gibt nicht mehr gleiche Chiffre

Counter (CTR)

  • Initialisierungsvektor plus Zähler werden verschlüsselt
  • XOR mit jeweiligem Datenblock
  • Damit wahlfreie Entschlüsselung von Blöcken

Beispiel mit PointyCastle

https://github.com/RobinNunkesser/dart-flutter-aes/tree/main/aes_cbc

Reverse Engineering

Übersicht

  • Das erste Beispiel war ein statischer White Box Test
  • Das zweite Beispiel war ein dynamischer Black Box Test
  • Gray Box Tests?

jadx

jadx ist sehr beliebt für Android

jadx -d decompiled MASTG-DEMO-0001.apk 

Resultat für Kotlin

public final void mastgTestApi() {
    File externalStorageDir = this.context.getExternalFilesDir(null);
    File fileName = new File(externalStorageDir, "secret.txt");
    try {
        FileOutputStream fileOutputStream = new FileOutputStream(fileName);
        try {
            FileOutputStream output = fileOutputStream;
            byte[] bytes = "secr3tPa$$W0rd\n".getBytes(Charsets.UTF_8);
            Intrinsics.checkNotNullExpressionValue(bytes, "getBytes(...)");
            output.write(bytes);
            Log.d("WriteExternalStorage", "File written to external storage successfully.");
            CloseableKt.closeFinally(fileOutputStream, null);
        } finally {
        }
    } catch (IOException e) {
        Log.e("WriteExternalStorage", "Error writing file to external storage", e);
    }
}

Resultat für Dart

reFlutter

  • Kann Flutter Apps mit modifizierter Flutter Engine neu verpacken
    • Netzwerkverkehr kann analysiert werden
    • Gute Zusammenarbeit mit Frida
    • Wir konzentrieren uns hier auf das einfachere B(l)utter
  • Beispiel BMW App

B(l)utter

python3 blutter.py app-release.apk decompiled

Resultat mit B(l)utter

├── asm
│   └── mas_test_app
│       ├── demo_results.dart
│       ├── main.dart
│       ├── mastg_test.dart
│       └── result.dart
├── blutter_frida.js
├── ida_script
│   ├── addNames.py
│   └── ida_dart_struct.h
├── objs.txt
└── pp.txt

Ausschnitt aus mastg_test.dart

_ mastgTestExternalPath(/* No info */) async {
    // ** addr: 0x23caf4, size: 0xf8
    // 0x23caf4: EnterFrame
    //     0x23caf4: stp             fp, lr, [SP, #-0x10]!
    //     0x23caf8: mov             fp, SP
    // 0x23cafc: AllocStack(0x28)
    //     0x23cafc: sub             SP, SP, #0x28
...
    // 0x23cbc8: r2 = "secr3tPa$$W0rd\n"

Bewertung

  • Nicht so gut lesbar wie Java-Code
  • Für Weaknesses aber dennoch nutzbar

Obfuscation

  • Obfuscation erschwert die Lesbarkeit weiter
  • Funktionsnamen, Klassennamen etc. werden ersetzt / anonymisiert
  • Betrifft nicht hart codierte Secrets
  • Symbol Map nötig für z.B. das Lesen von Stack Traces
flutter build apk --obfuscate --split-debug-info=symbols

Resultat mit B(l)utter

.
├── asm
│   ├── Abd.dart
│   ├── aed.dart
...
│   ├── zhd.dart
│   └── Zid.dart
├── blutter_frida.js
├── ida_script
│   ├── addNames.py
│   └── ida_dart_struct.h
├── objs.txt
└── pp.txt

Ausschnitt aus pp.txt

[pp+0x7fc8] String: "secr3tPa$$W0rd\n"

Beispiel MASTG-DEMO-0060

Ausgangspunkt Demo

Demo MASTG-DEMO-0060 betrifft Weakness MASWE-0006 zu Control MASVS-STORAGE-1. Dies kann durch Test MASTG-TEST-0287 getestet werden.

MASTG-DEMO-0060

package org.owasp.mastestapp

import android.content.Context
import androidx.core.content.edit
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKey

class MastgTest(private val context: Context) {
    // WARNING: In a real application, these keys should NOT be hardcoded. They should be stored securely, for instance, in the Android Keystore.
    private val awsKey = "AKIAABCDEFGHIJKLMNOP"
    private val githubToken = "ghp_1234567890abcdefghijklmnopqrstuvABCD"
    private val preSharedKeys = hashSetOf(
        "-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBALfX7kbfFv3pc3JjOHQ=\n-----END PRIVATE KEY-----",
        "-----BEGIN PRIVATE KEY-----\ngJXS9EwpuzK8U1TOgfplwfKEVngCE2D5FNBQWvNmuHHbigmTCabsA=\n-----END PRIVATE KEY-----"
    )
    private val sharedPrefsName = "MasSharedPref_Sensitive_Data"

    fun mastgTest(): String {
        return try {
            val masterKey = MasterKey.Builder(context)
                .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
                .build()

            val encryptedPrefs = EncryptedSharedPreferences.create(
                context,
                sharedPrefsName,
                masterKey,
                EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
                EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
            )

            encryptedPrefs.edit {
                putString("EncryptedAWSKey", awsKey)
                putString("GitHubToken", githubToken)
                putStringSet("preSharedKeys", preSharedKeys)
            }

            "Sensitive data has been written and deleted in the sandbox."
        } catch (e: Exception) {
            "Error during MastgTest: ${e.message ?: "Unknown error"}"
        }
    }
}

Weakness und Control

MASWE-0006

Sensitive Data Stored Unencrypted in Private Storage Locations

MASVS-STORAGE-1

The app securely stores sensitive data.

MASTG-TEST-0287

  • Wird SharedPreferences unverschlüsselt für sensible Daten genutzt?
  • Wird auf Alternativen wie EncryptedSharedPreferences verzichtet?
  • Anmerkung: EncryptedSharedPreferences aktuell deprecated ohne Ersatz

MASTG-DEMO-0060

  • Die Demo zeigt diesmal korrektes Verhalten
  • Während der Laufzeit wird Frida genutzt, um JavaScript zu injecten

Frida

  • Benötigt üblicherweise Root-Zugriff
  • Im Emulator z.B. über AOSP Android
adb root
adb push frida-server /data/local/tmp/
adb shell "chmod 755 /data/local/tmp/frida-server"
adb shell "/data/local/tmp/frida-server &"

Frida-Script

var target = {
  category: "STORAGE",
  demo: "0060",
  hooks: [
    {
      class: "androidx.security.crypto.EncryptedSharedPreferences$Editor",
      methods: [
        "putString",
        "putStringSet"
      ]
    },
    {
      class: "android.app.SharedPreferencesImpl$EditorImpl",
      methods: [
        "putString",
        "putStringSet"
      ]
    }
  ]
}

Ergebnis

{
  "id": "8a1fa598-4cb8-427e-ab73-4f24a1f76efe",
  "category": "STORAGE",
  "time": "2025-11-19T17:35:08.695Z",
  "class": "android.app.SharedPreferencesImpl$EditorImpl",
  "method": "putString",
  "stackTrace": [
    "android.app.SharedPreferencesImpl$EditorImpl.putString(Native Method)",
    "com.google.crypto.tink.integration.android.SharedPrefKeysetWriter.write(SharedPrefKeysetWriter.java:70)",
    "com.google.crypto.tink.KeysetHandle.writeWithAssociatedData(KeysetHandle.java:869)",
    "com.google.crypto.tink.KeysetHandle.write(KeysetHandle.java:858)",
    "com.google.crypto.tink.integration.android.AndroidKeysetManager$Builder.generateKeysetAndWriteToPrefs(AndroidKeysetManager.java:353)",
    "com.google.crypto.tink.integration.android.AndroidKeysetManager$Builder.build(AndroidKeysetManager.java:292)",
    "androidx.security.crypto.EncryptedSharedPreferences.create(EncryptedSharedPreferences.java:169)",
    "androidx.security.crypto.EncryptedSharedPreferences.create(EncryptedSharedPreferences.java:131)"
  ],
  "inputParameters": [
    {
      "type": "java.lang.String",
      "value": "__androidx_security_crypto_encrypted_prefs_key_keyset__"
    },
    {
      "type": "java.lang.String",
      "value": "12a70146597673f2b896ed619f976052ecf522726e8c922e2b5b8f396f64778a1141b44a02733ffe8824705a5ebf15be75807096ac69bd83276465aa5625d89f125d0ebe93c4f3cc389965cf0423ccf793cf399335fb56159adad48af44c0038fb99282439b2ccfddeda689f2a71d209099873c35427a1dd1aaeb82c8f62df695aa04f27fd22761275bbca7fb5a7829cf142dfe602910d44df1ff9dd31bf495aefb4509cc819114ba2a91a4208f5f8bc7a123b0a30747970652e676f6f676c65617069732e636f6d2f676f6f676c652e63727970746f2e74696e6b2e4165735369764b6579100118f5f8bc7a2001"
    }
  ],
  "returnValue": [
    {
      "type": "android.content.SharedPreferences$Editor",
      "value": "<instance: android.content.SharedPreferences$Editor, $className: android.app.SharedPreferencesImpl$EditorImpl>"
    }
  ]
}

MASTG-DEMO-0060 (Flutter)

flutter_secure_storage

flutter_secure_storage wird üblicherweise verwendet

  • EncryptedSharedPreferences auf Android
  • Keychain auf iOS

MASTG-DEMO-0060

import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:mas_test_app/demo_results.dart';

class MastgTest {
  final FlutterSecureStorage _storage = const FlutterSecureStorage();
  // WARNING: In a real application, these keys should NOT be hardcoded. They should be stored securely, for instance, in a Keystore.
  final String _awsKey = "AKIAABCDEFGHIJKLMNOP";
  final String _githubToken = "ghp_1234567890abcdefghijklmnopqrstuvABCD";

  DemoResults mastgTest() {
    DemoResults r = DemoResults(demoId: '0001');
    try {
      _write("EncryptedAWSKey", _awsKey);
      _write("GitHubToken", _githubToken);
      r.add(
        Status.pass,
        "Sensitive data has been written and deleted in the sandbox.",
      );
    } catch (e) {
      r.add(Status.error, e.toString());
    }
    return r;
  }

  Future<void> _write(String key, String value) async {
    await _storage.write(
      key: key,
      value: value,
      iOptions: _getIOSOptions(),
      aOptions: _getAndroidOptions(),
    );
  }

  IOSOptions _getIOSOptions() =>
      IOSOptions(accessibility: KeychainAccessibility.first_unlock);
  AndroidOptions _getAndroidOptions() =>
      const AndroidOptions(encryptedSharedPreferences: true);
}

Ergebnis

  • Das Frida-Script fängt die Aufrufe ebenfalls ab
  • Bis auf das statische Hinterlegen der Original-Keys in Ordnung

Beispiel - SQLite

Einführung

  • Datenbanken auf dem Client dürfen ebenfalls keine ungeschützen sensiblen Daten beinhaltet
  • Dies betrifft Weakness MASWE-0006 (Sensitive Data Stored Unencrypted in Private Storage Locations)
  • OWASP MAS sieht dafür keine eigenen Tests und Demos vor, da dies ein Spezialfall erzeugter Dateien ist

Beispiel - sqflite_sqlcipher

sqflite_sqlcipher

  • sqflite_sqlcipher bietet die Nutzung von SQLCipher an
  • 256 bit AES Verschlüsselung der Datenbank
  • Beispiel in GitHub

DB auslesen

adb pull /data/user/0/com.davidmartos96.example/databases/unencrypted.db
adb pull /data/user/0/com.davidmartos96.example/databases/encrypted.db 

Ergebnis