🔐 EasySale - Detaillierte OWASP Security Analyse¶
ERP-System & Shop-System¶
Audit Datum: 24. Februar 2026
Letztes Update: 7. April 2026 (Dokumentation #11: LOW-7 Security Training – ALLE 37 FINDINGS GESCHLOSSEN ✅)
Umfang: Vollständige Code-Level Analyse beider Systeme
Framework: OWASP Top 10 2021 + OWASP MASVS (Mobile)
Systeme: ERP-System (apps/erp_system) + Shop-System (apps/shop_system)
📊 Executive Summary¶
Diese detaillierte Analyse identifiziert 37 Security Findings auf Code-Level, sortiert nach OWASP Top 10 Kategorien und Schweregrad.
Severity Distribution¶
- 🔴 KRITISCH: 5 Findings → 5 ✅ behoben
- 🟠 HOCH: 12 Findings → 12 ✅ behoben
- 🟡 MITTEL: 13 Findings → 13 ✅ behoben
- 🟢 NIEDRIG: 7 Findings → 7 ✅ behoben (alle)
Gesamtfortschritt: 37 vollständig behoben | 0 teilweise | 0 offen ✅
Letzter Scan: 07.04.2026 – ALLE FINDINGS GESCHLOSSEN 🎉
OWASP Top 10 Coverage¶
Alle 10 Kategorien wurden analysiert, 8 davon haben aktive Findings.
📋 FINDINGS ÜBERSICHT (ALLE 37 FINDINGS)¶
| ID | Finding | Severity | OWASP | System | CVSS | CWE | Priorität |
|---|---|---|---|---|---|---|---|
| CRIT-1 | Ungeschützte Firestore Queries | ✅ BEHOBEN | A01 | Shop | 9.1 | CWE-284 | ✅ Umgesetzt 24.02.2026 |
| CRIT-2 | Passwort/E-Mail in Plaintext (SharedPreferences) | ✅ BEHOBEN | A02 | ERP | 8.8 | CWE-312 | ✅ Umgesetzt 24.02.2026 |
| CRIT-3 | Fehlende Input Sanitization | ✅ BEHOBEN | A03 | Beide | 8.2 | CWE-89 | ✅ Umgesetzt 24.02.2026 |
| CRIT-4 | currentUser ohne Null-Check | ✅ BEHOBEN | A01 | Shop | 7.5 | CWE-476 | ✅ Umgesetzt 24.02.2026 |
| CRIT-5 | SSL Certificate Pinning fehlt | ✅ BEHOBEN | A02 | Shop | 7.4 | CWE-295 | ✅ Umgesetzt 24.02.2026 |
| HIGH-1 | Fehlende Rate Limiting | ✅ BEHOBEN | A04 | Shop | 7.2 | CWE-307 | ✅ Umgesetzt 24.02.2026 |
| HIGH-2 | Token in Debug-Logs | ✅ BEHOBEN | A09 | Shop | 6.8 | CWE-532 | ✅ Umgesetzt 24.02.2026 |
| HIGH-3 | Unvalidierte File Downloads | ✅ BEHOBEN | A03, A08 | Shop | 6.5 | CWE-494 | ✅ Umgesetzt 24.02.2026 |
| HIGH-4 | Session Timeout fehlt | ✅ BEHOBEN | A07 | Beide | 6.2 | CWE-613 | ✅ Umgesetzt 24.02.2026 |
| HIGH-5 | Fehlende Biometric Auth | ✅ BEHOBEN | A07 | Beide | 5.8 | CWE-287 | ✅ Umgesetzt 24.02.2026 |
| HIGH-6 | Unverschlüsselte App State | ✅ BEHOBEN | A02 | ERP | 5.5 | CWE-312 | ✅ Umgesetzt 24.02.2026 |
| HIGH-7 | Fehlende Request Signing | ✅ BEHOBEN | A08 | Beide | 5.3 | CWE-345 | ✅ Umgesetzt 24.02.2026 |
| HIGH-8 | Insufficient Transport Security | ✅ BEHOBEN | A02 | Shop | 5.2 | CWE-319 | ✅ Umgesetzt 24.02.2026 |
| HIGH-9 | Weak Password Policy | ✅ BEHOBEN | A07 | Beide | 5.0 | CWE-521 | ✅ Umgesetzt 24.02.2026 |
| HIGH-10 | Insecure Random Number Gen | ✅ BEHOBEN | A02 | Beide | 4.9 | CWE-338 | ✅ Umgesetzt 24.02.2026 |
| HIGH-11 | Missing Security Headers (Web) | ✅ BEHOBEN | A05 | Beide | 4.8 | CWE-693 | ✅ Umgesetzt 24.02.2026 |
| HIGH-12 | Unprotected Backup Files | ✅ BEHOBEN | A02 | Beide | 4.7 | CWE-530 | ✅ Umgesetzt 24.02.2026 |
| MED-1 | Fehlende Error Boundary | ✅ BEHOBEN | A04 | Beide | 4.5 | CWE-755 | ✅ Umgesetzt 24.02.2026 |
| MED-2 | XSS-Risiko bei Web-Ansichten | ✅ BEHOBEN | A03 | Beide | 4.3 | CWE-79 | ✅ Umgesetzt 24.02.2026 |
| MED-3 | Unzureichende Rollback-Mechanismen | ✅ BEHOBEN | A08 | Beide | 4.2 | CWE-664 | ✅ Umgesetzt 30.03.2026 |
| MED-4 | Fehlende Biometric Integration | ✅ BEHOBEN | A07 | Beide | 4.0 | CWE-287 | ✅ Umgesetzt 24.02.2026 |
| MED-5 | Unverschlüsselte lokale Caches (Hive) | ✅ BEHOBEN | A02 | Beide | 3.9 | CWE-312 | ✅ HiveAesCipher + Schlüssel in FlutterSecureStorage |
| MED-6 | Fehlende Jailbreak/Root Detection | ✅ BEHOBEN | A04 | Beide | 3.8 | CWE-693 | ✅ Umgesetzt 24.02.2026 – shared-Package |
| MED-7 | Unangemessene Permission Requests | ✅ BEHOBEN | A04 | Beide | 3.7 | CWE-250 | ✅ Umgesetzt 24.02.2026 |
| MED-8 | Fehlende Code Obfuscation | ✅ BEHOBEN | A04 | Beide | 3.6 | CWE-656 | ✅ Umgesetzt 24.02.2026 |
| MED-9 | Debug-Modus in Production | ✅ BEHOBEN | A05 | Beide | 3.5 | CWE-489 | ✅ Umgesetzt 24.02.2026 |
| MED-10 | Unvalidierte Deep Links | ✅ BEHOBEN | A03 | Beide | 3.4 | CWE-20 | ✅ Umgesetzt 24.02.2026 |
| MED-11 | Fehlende Integrity Checks | ✅ BEHOBEN | A08 | Beide | 3.3 | CWE-353 | ✅ Umgesetzt 24.02.2026 |
| MED-12 | Race Conditions bei State Updates | ✅ BEHOBEN | A04 | Beide | 3.2 | CWE-362 | ✅ Umgesetzt 24.02.2026 |
| MED-13 | Memory Leaks bei Streams | ✅ BEHOBEN | A04 | Beide | 3.1 | CWE-401 | ✅ Umgesetzt 24.02.2026 |
| LOW-1 | Fehlende Security.md | ✅ BEHOBEN | - | Beide | 2.0 | - | ✅ Umgesetzt 07.04.2026 |
| LOW-2 | Veraltete Dependencies / Dependency Audit | ✅ BEHOBEN | A06 | Beide | 2.0 | CWE-1104 | ✅ Umgesetzt 24.02.2026 |
| LOW-3 | Fehlende Pentest-Doku | ✅ BEHOBEN | - | Beide | 1.8 | - | ✅ Umgesetzt 07.04.2026 |
| LOW-4 | Unvollständige Security Headers | ✅ BEHOBEN | A05 | Web | 1.7 | CWE-693 | ✅ Umgesetzt 24.02.2026 |
| LOW-5 | Fehlende CSP | ✅ BEHOBEN | A05 | Web | 1.6 | CWE-1021 | ✅ Umgesetzt 24.02.2026 |
| LOW-6 | Keine Disclosure Policy | ✅ BEHOBEN | - | Beide | 1.5 | - | ✅ Umgesetzt 07.04.2026 (SECURITY.md Sektion 12) |
| LOW-7 | Fehlende Security Training Doku | ✅ BEHOBEN | - | Beide | 1.0 | - | ✅ Umgesetzt 07.04.2026 |
Legende¶
- OWASP: A01-A10 = OWASP Top 10 2021 Kategorien
- System: ERP = nur ERP-System, Shop = nur Shop-System, Beide = beide betroffen
- Priorität: 🔥 = Sofort, 📅 = Geplant, 📋 = Backlog, ℹ️ = Informational
🔴 KRITISCHE FINDINGS¶
✅ CRIT-1: Ungeschützte Firestore Queries im Shop-System (BEHOBEN 24.02.2026)¶
OWASP: A01:2021 – Broken Access Control
CWE: CWE-284 (Improper Access Control)
CVSS Score: 9.1 (Critical)
System: Shop-System
Status: ✅ BEHOBEN – Neue Firestore Security Rules für beide Systeme deployed. Shop-System hat jetzt hasCustomerAccess()-Prüfung auf Serverseite, Deny-by-Default am Ende.
Problem (Ursprünglich)¶
Mehrere Firebase Queries im Shop-System verlassen sich ausschließlich auf Client-seitige Filterung ohne serverseitige Security Rules Validierung:
Betroffene Datei: apps/shop_system/lib/services/firebase_services/customer_firebase_service.dart
// Line 30-31: UNSICHER - Client-seitige Filterung
final querySnapshot = await FirebaseFirestore.instance
.collection(customerAccessPermissionCollection)
.where('email', isEqualTo: FirebaseAuth.instance.currentUser?.email)
.get();
Ähnliche Patterns in:
- Line 65: getCustomers()
- Line 104: getCustomerAccessPermission()
- Line 122: getCustomerUserLanguage()
- Line 136: getCustomerUserName()
- Line 157: updateCustomerUserLanguage()
- Line 221: Multiple weitere Queries
- Line 356-473: Delivery breaks, purchase lists, customer feed
Warum ist das kritisch?¶
- Client-seitige Filterung kann umgangen werden durch modifizierte Clients
- Ein Angreifer kann die App dekompilieren und direkt Firestore Queries senden
- Firestore Rules in
firestore.rulessind zu permissiv:
Risiko¶
- Zugriff auf Kundendaten anderer Nutzer
- Zugriff auf andere Tenant-Daten (Multi-Tenancy-Verletzung)
- DSGVO-Verstoß durch unautorisierten Datenzugriff
- Manipulation von Bestellungen anderer Kunden
Exploit Scenario¶
// Angreifer kann eigene Query senden:
FirebaseFirestore.instance
.collection('customers')
.get(); // Zugriff auf ALLE Kunden ohne Filterung!
Lösung¶
1. Firestore Security Rules härten:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
function isSignedIn() {
return request.auth != null;
}
function hasCustomerAccess(customerId) {
return isSignedIn() &&
exists(/databases/$(database)/documents/customerUsers/$(customerId + '_' + request.auth.token.email)) &&
get(/databases/$(database)/documents/customerUsers/$(customerId + '_' + request.auth.token.email)).data.registrationState == 1;
}
// Customer Access Permissions
match /customerUsers/{userId} {
allow read: if isSignedIn() &&
resource.data.email == request.auth.token.email;
allow create: if isSignedIn() &&
request.resource.data.email == request.auth.token.email &&
request.resource.data.registrationState == 0;
allow update, delete: if false; // Nur via Admin SDK
}
// Customers - nur mit Zugriffsberechtigung
match /customers/{customerId} {
allow read: if hasCustomerAccess(customerId);
allow write: if false; // Nur via Admin SDK
}
// Explizit: Kein Zugriff ohne spezifische Regel
match /{document=**} {
allow read, write: if false;
}
}
}
2. Code-seitige Validierung hinzufügen:
Future<List<Customer>> getCustomers() async {
final user = FirebaseAuth.instance.currentUser;
if (user == null) {
throw Exception('User not authenticated');
}
// Query bleibt gleich, aber Rules erzwingen serverseitig die Validierung
final querySnapshot = await FirebaseFirestore.instance
.collection(customerAccessPermissionCollection)
.where('email', isEqualTo: user.email)
.get()
.timeout(
Duration(seconds: 10),
onTimeout: () => throw TimeoutException('Query timeout'),
);
// ... rest of code
}
Priorität: 🔥 SOFORT - Production System kompromittiert
✅ CRIT-2: Passwort im Plaintext in SharedPreferences (ERP) (BEHOBEN 24.02.2026)¶
OWASP: A02:2021 – Cryptographic Failures
CWE: CWE-312 (Cleartext Storage of Sensitive Information)
CVSS Score: 8.8 (High)
System: ERP-System
Status: ✅ BEHOBEN – E-Mail wird jetzt ebenfalls in FlutterSecureStorage gespeichert. SharedPreferences wird nicht mehr für Credentials verwendet.
Problem (Ursprünglich)¶
Datei: apps/erp_system/lib/blocs/auth_bloc/auth_bloc.dart
// Line 204-208: KRITISCH - Password könnte in SharedPreferences landen
Future<void> _saveCredentials({
required String email,
required String password,
required bool rememberMe,
}) async {
const storage = FlutterSecureStorage();
final prefs = await SharedPreferences.getInstance();
if (rememberMe) {
await prefs.setString('saved_email', email); // ⚠️ Email in Plaintext
await storage.write(key: 'saved_password', value: password); // ✅ Encrypted
await prefs.setBool('remember_me', true);
}
}
Warum ist das ein Problem?¶
- SharedPreferences speichert unverschlüsselt in XML (Android) / plist (iOS)
- E-Mail-Adresse ist ebenfalls sensibel (DSGVO Personenbezug)
- Bei rooted/jailbroken Geräten direkt lesbar
- Backup-Tools können diese Daten extrahieren
Risiko¶
- Password ist korrekt in FlutterSecureStorage (verschlüsselt) ✅
- ABER: E-Mail im Klartext ermöglicht Account Enumeration
- ABER: Kombination E-Mail + Verschlüsseltes Passwort im selben Speicher ist schwächer als beide verschlüsselt
Lösung¶
Future<void> _saveCredentials({
required String email,
required String password,
required bool rememberMe,
}) async {
const storage = FlutterSecureStorage();
if (rememberMe) {
// ✅ Beide Werte verschlüsselt speichern
await storage.write(key: 'saved_email', value: email);
await storage.write(key: 'saved_password', value: password);
await storage.write(key: 'remember_me', value: 'true');
} else {
// Alle löschen
await storage.delete(key: 'saved_email');
await storage.delete(key: 'saved_password');
await storage.delete(key: 'remember_me');
}
}
Future<void> _onLoadSavedCredentials(...) async {
const storage = FlutterSecureStorage();
final savedEmail = await storage.read(key: 'saved_email');
final savedPassword = await storage.read(key: 'saved_password');
final rememberMeStr = await storage.read(key: 'remember_me');
final rememberMe = rememberMeStr == 'true';
emit(CredentialsLoaded(
email: savedEmail,
password: savedPassword,
rememberMe: rememberMe,
));
}
Priorität: 🔥 SOFORT
✅ CRIT-3: Fehlende Input Sanitization bei Firestore Writes (BEHOBEN 24.02.2026)¶
OWASP: A03:2021 – Injection
CWE: CWE-89 (NoSQL Injection)
CVSS Score: 8.2 (High)
System: Beide Systeme
Status: ✅ BEHOBEN – InputValidator-Klasse im shared-Package implementiert (sanitizeName, sanitizeEmail, sanitizePhone, sanitizeMap). In customer_firebase_service.dart und weiteren Services integriert.
Problem¶
User-Input wird direkt in Firestore geschrieben ohne Validierung oder Sanitization:
Shop-System: apps/shop_system/lib/services/firebase_services/customer_firebase_service.dart
// Line 238-246: KEINE Validierung vor Firestore Write
Future<void> updateCustomerUserName({
required String customerId,
required String firstName,
required String lastName,
}) async {
// ❌ firstName und lastName werden DIREKT übernommen
final doc = /* ... get doc ... */;
await doc.reference.update({
'firstName': firstName, // ❌ Keine Validierung!
'lastName': lastName, // ❌ Keine Validierung!
});
}
Risiko¶
- NoSQL Injection in Firestore Queries
- XSS wenn Daten später in Web-Views angezeigt werden
- Data Corruption durch Sonderzeichen
- Firestore Rule Bypass durch manipulierte Felder
Exploit Scenarios¶
// Scenario 1: Field Injection
updateCustomerUserName(
customerId: 'cust123',
firstName: 'Robert"; admin: true; role: "superadmin',
lastName: 'Tables'
);
// Scenario 2: Size-based DoS
updateCustomerUserName(
customerId: 'cust123',
firstName: 'A' * 1000000, // 1MB String -> Storage/Kosten Explosion
lastName: 'B' * 1000000
);
// Scenario 3: Special Characters
updateCustomerUserName(
customerId: 'cust123',
firstName: '<script>alert("XSS")</script>',
lastName: '${document.cookie}'
);
Lösung¶
1. Input Validation Service erstellen:
// shared/lib/validators/input_validator.dart
class InputValidator {
// Max length für verschiedene Feldtypen
static const int maxNameLength = 100;
static const int maxEmailLength = 254;
static const int maxPhoneLength = 20;
// Verbotene Zeichen
static final RegExp _htmlTags = RegExp(r'<[^>]*>');
static final RegExp _scriptTags = RegExp(r'<script.*?</script>', caseSensitive: false);
static final RegExp _sqlKeywords = RegExp(r'\b(SELECT|INSERT|UPDATE|DELETE|DROP|UNION)\b', caseSensitive: false);
static String sanitizeName(String input) {
if (input.isEmpty) return input;
// Länge prüfen
if (input.length > maxNameLength) {
throw ArgumentError('Name too long (max: $maxNameLength)');
}
// HTML Tags entfernen
String sanitized = input.replaceAll(_htmlTags, '');
sanitized = sanitized.replaceAll(_scriptTags, '');
// SQL Keywords entfernen (auch für NoSQL relevant)
sanitized = sanitized.replaceAll(_sqlKeywords, '');
// Nur erlaubte Zeichen: Buchstaben, Zahlen, Leerzeichen, Bindestriche, Apostrophe
final RegExp allowed = RegExp(r"^[a-zA-ZäöüÄÖÜß0-9\s\-']+$");
if (!allowed.hasMatch(sanitized)) {
throw ArgumentError('Name contains invalid characters');
}
// Führende/nachfolgende Leerzeichen entfernen
sanitized = sanitized.trim();
return sanitized;
}
static String sanitizeEmail(String input) {
if (input.isEmpty) return input;
if (input.length > maxEmailLength) {
throw ArgumentError('Email too long');
}
// Email Regex Validation
final emailRegex = RegExp(r'^[\w\.-]+@[\w\.-]+\.\w{2,}$');
if (!emailRegex.hasMatch(input)) {
throw ArgumentError('Invalid email format');
}
return input.toLowerCase().trim();
}
}
2. In Services verwenden:
Future<void> updateCustomerUserName({
required String customerId,
required String firstName,
required String lastName,
}) async {
// ✅ Validierung und Sanitization
final sanitizedFirstName = InputValidator.sanitizeName(firstName);
final sanitizedLastName = InputValidator.sanitizeName(lastName);
// Zusätzliche Checks
if (sanitizedFirstName.isEmpty || sanitizedLastName.isEmpty) {
throw ArgumentError('Names cannot be empty');
}
final doc = /* ... get doc ... */;
await doc.reference.update({
'firstName': sanitizedFirstName,
'lastName': sanitizedLastName,
'updatedAt': FieldValue.serverTimestamp(),
});
}
Priorität: 🔥 HOCH - In 1 Woche beheben
✅ CRIT-4: Direkter Zugriff auf currentUser ohne Null-Check (BEHOBEN 24.02.2026)¶
OWASP: A01:2021 – Broken Access Control
CWE: CWE-476 (NULL Pointer Dereference)
CVSS Score: 7.5 (High)
System: Shop-System
Status: ✅ BEHOBEN – AuthHelper-Klasse im shared-Package implementiert (getRequiredUser(), getRequiredEmail(), getRequiredUid()). Alle 14+ unsicheren currentUser-Zugriffe in Services und BLoCs durch AuthHelper ersetzt. Wirft UnauthorizedException bei fehlendem Auth-State statt silent-null-Query oder App-Crash.
Problem¶
Mehrfacher direkter Zugriff auf FirebaseAuth.instance.currentUser ohne Null-Checks oder Error Handling:
Datei: apps/shop_system/lib/services/firebase_services/order_firebase_service.dart
// Line 9: ❌ KEINE Null-Prüfung
Future<List<Order>> getOrders(String customerId) async {
final user = FirebaseAuth.instance.currentUser; // Kann NULL sein!
return await FirebaseFirestore.instance
.collection(_ordersCollection)
.where('customerId', isEqualTo: customerId)
// ❌ user.email wird nicht geprüft - Crash wenn user == null
.get();
}
Geänderte Dateien:
- packages/shared/lib/helpers/auth_helper.dart – NEU – AuthHelper + UnauthorizedException
- packages/shared/lib/shared.dart – AuthHelper jetzt offiziell re-exportiert
- apps/shop_system/lib/services/firebase_services/customer_firebase_service.dart – 12 Stellen: currentUser?.email → AuthHelper.getRequiredEmail()
- apps/shop_system/lib/services/firebase_services/user_firebase_service.dart – currentUser!.uid → AuthHelper.getRequiredUid()
- apps/shop_system/lib/services/firebase_services/order_firebase_service.dart – addOrder() wirft jetzt UnauthorizedException statt silent null return
- apps/shop_system/lib/blocs/customer/customers_bloc.dart – currentUser!.email! → AuthHelper.getRequiredEmail()
// packages/shared/lib/helpers/auth_helper.dart
class AuthHelper {
static User getRequiredUser() {
final user = FirebaseAuth.instance.currentUser;
if (user == null) throw const UnauthorizedException('...');
return user;
}
static String getRequiredEmail() { /* ✅ wirft wenn null */ }
static String getRequiredUid() { /* ✅ wirft wenn null */ }
}
Priorität: ~~🔥 HOCH - In 2 Wochen~~ → ✅ ERLEDIGT 24.02.2026
✅ CRIT-5: Fehlende SSL Certificate Pinning Implementierung (BEHOBEN)¶
OWASP: A02:2021 – Cryptographic Failures
CWE: CWE-295 (Improper Certificate Validation)
CVSS Score: 7.4 (High)
System: Shop-System
Status: ✅ VOLLSTÄNDIG BEHOBEN – SSLPinningConfig-Klasse vollständig implementiert mit echten SHA-256-Fingerprints. Fingerprints am 24.02.2026 via openssl s_client ermittelt und eingetragen:
- Primary: firebasestorage.googleapis.com → 26a1502190a7c505612f4b99ee0cba2824e5bfcb0037f94200d8724c00379bb9
- Fallback: storage.googleapis.com → 9f1e63805b9abc367cffeeec24d90b9f043b322680b560d9394787ea6a8e3727
Problem¶
SSL Certificate Pinning ist vorhanden aber nicht vollständig implementiert:
Datei: apps/shop_system/lib/services/mobile/document_download_service.dart
// Line 16: SSL Pinning Service wird REFERENZIERT
final Dio _dio = SSLPinningConfig.createSecureDio();
ABER: Analyse der ssl_pinning_service.dart zeigt:
// services/ssl_pinning_service.dart
class SSLPinningConfig {
static Dio createSecureDio() {
final dio = Dio();
// TODO: Implementiere Certificate Pinning
// Aktuell: Keine Pinning-Implementierung!
return dio;
}
// Für Development
static Dio createSecureDioWithoutPinning() {
return Dio(); // Komplett ungeschützt
}
}
Risiko¶
- Man-in-the-Middle (MITM) Angriffe möglich
- Angreifer kann TLS-Traffic abfangen
- Download-URLs können manipuliert werden
- Sensible Dokumente können abgefangen werden
Lösung¶
Vollständige SSL Pinning Implementierung:
import 'package:dio/dio.dart';
import 'package:dio/adapter.dart';
class SSLPinningConfig {
// SHA-256 Fingerprints der vertrauenswürdigen Zertifikate
// Firebase Storage Zertifikate
static const List<String> _trustedCertificates = [
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=', // Firebase Storage Cert 1
'BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=', // Firebase Storage Cert 2 (Backup)
];
static Dio createSecureDio() {
final dio = Dio(BaseOptions(
connectTimeout: 30000,
receiveTimeout: 30000,
validateStatus: (status) => status! < 500,
));
// Certificate Pinning konfigurieren
(dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate = (client) {
client.badCertificateCallback = (cert, host, port) {
// SHA-256 des Zertifikats berechnen
final certSha256 = cert.sha256.toString();
// Gegen bekannte Zertifikate prüfen
if (_trustedCertificates.contains(certSha256)) {
return true; // ✅ Vertrauenswürdiges Zertifikat
}
// Log für Debugging (nur Development)
debugPrint('⚠️ Untrusted certificate for $host:$port');
debugPrint(' SHA-256: $certSha256');
return false; // ❌ Nicht vertrauenswürdiges Zertifikat
};
return client;
};
return dio;
}
// Nur für lokales Testing
static Dio createSecureDioWithoutPinning() {
assert(() {
debugPrint('⚠️ WARNING: SSL Pinning deaktiviert - nur für Development!');
return true;
}());
return Dio();
}
}
Zertifikat-Fingerprints ermitteln:
# Firebase Storage Certificate abrufen
openssl s_client -connect firebasestorage.googleapis.com:443 -showcerts < /dev/null 2>/dev/null | openssl x509 -outform PEM > firebase_cert.pem
# SHA-256 Fingerprint berechnen
openssl x509 -in firebase_cert.pem -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64
Priorität: 🔥 HOCH - In 2 Wochen
🟠 HOHE FINDINGS¶
HIGH-1: ✅ Fehlende Rate Limiting im Shop-System IMPLEMENTIERT¶
OWASP: A04:2021 – Insecure Design
CWE: CWE-307 (Improper Restriction of Excessive Authentication Attempts)
System: Shop-System
Status: ✅ IMPLEMENTIERT am 24. Februar 2026
Implementierung¶
Rate Limiting wurde vollständig in den Shop-System Login-Flow integriert. Der bestehende RateLimiterService aus packages/shared wird jetzt von beiden Systemen genutzt.
Geänderte Dateien:
- apps/shop_system/lib/blocs/auth/auth_bloc.dart – Rate Limiter in _onSignIn integriert
- apps/shop_system/lib/main.dart – RateLimiterService.initialize() + dispose()
- apps/erp_system/lib/blocs/auth_bloc/auth_bloc.dart – minutes + 1 UX-Fix
- packages/shared/lib/shared.dart – RateLimiterService jetzt offiziell re-exportiert
// apps/shop_system/lib/blocs/auth/auth_bloc.dart
Future<void> _onSignIn(SignIn event, Emitter<BaseBlocState> emit) async {
emit(BlocLoading());
// 🔒 SECURITY HIGH-1: Rate Limiting gegen Brute-Force-Angriffe
if (!RateLimiterService.canAttemptLogin(event.email)) {
final remaining = RateLimiterService.getRemainingLockoutTime(event.email);
final minutes = remaining.inMinutes + 1; // +1: verhindert "0 Minuten"
emit(AuthBlocError(
'Zu viele fehlgeschlagene Anmeldeversuche. '
'Bitte warten Sie $minutes Minuten und versuchen Sie es erneut.',
));
emit(AuthBlocLoaded(false));
return;
}
try {
await _auth.signInWithEmailAndPassword(
email: event.email,
password: event.password,
);
// Login erfolgreich - Rate Limiter zurücksetzen
RateLimiterService.resetAttempts(event.email);
emit(AuthBlocLoaded(true));
} on FirebaseAuthException catch (e) {
// Login fehlgeschlagen - Versuch registrieren
RateLimiterService.recordFailedAttempt(event.email);
// ...
}
}
Konfiguration (packages/shared/lib/helpers/rate_limiter_service.dart)¶
- Max. Versuche: 5
- Lockout-Dauer: 15 Minuten
- Zeitfenster: 30 Minuten
- Automatisches Cleanup alle 5 Minuten
Konsistenz ERP vs. Shop¶
| Shop | ERP | |
|---|---|---|
| Rate Limiting Check | ✅ | ✅ |
| Failed attempts zählen | ✅ | ✅ |
| Reset bei Erfolg | ✅ | ✅ |
minutes + 1 UX-Fix |
✅ | ✅ (nachgezogen) |
| Initialize in main() | ✅ | ✅ |
Status: ✅ HIGH-1 VOLLSTÄNDIG IMPLEMENTIERT
Priorität: ~~📅 2 Wochen~~ → ✅ ERLEDIGT
HIGH-2: ✅ Token in Debug-Logs IMPLEMENTIERT¶
OWASP: A09:2021 – Security Logging and Monitoring Failures
CWE: CWE-532 (Insertion of Sensitive Information into Log File)
System: Shop-System
Status: ✅ IMPLEMENTIERT am 24. Februar 2026
Implementierung¶
Alle unsicheren debugPrint-Aufrufe, die FCM Tokens oder Device-IDs ausgeben, wurden durch SecureLogger ersetzt. Der SecureLogger aus packages/shared ist jetzt offiziell über shared.dart exportiert und in beiden Systemen verwendbar.
Geänderte Dateien:
- apps/shop_system/lib/services/device_info_service.dart – alle debugPrint → SecureLogger
- apps/shop_system/lib/services/firebase_services/customer_firebase_service.dart – alle debugPrint → SecureLogger
- packages/shared/lib/shared.dart – SecureLogger jetzt offiziell re-exportiert
// apps/shop_system/lib/services/device_info_service.dart
// 🔒 SECURITY HIGH-2: FCM Token NICHT loggen (CWE-532)
fcmToken = await FirebaseMessaging.instance.getToken();
if (kDebugMode) {
if (fcmToken != null) {
SecureLogger.debug('✅ FCM Token retrieved successfully');
// Token selbst wird NICHT geloggt
} else {
SecureLogger.warning('⚠️ FCM Token is null');
}
}
// Device ID wird vor dem Logging sanitized:
SecureLogger.debug('Device ID: ${SecureLogger.sanitize(deviceId ?? "null")}');
Was wurde verhindert¶
- ❌
debugPrint('✅ FCM Token retrieved: ${fcmToken.substring(0, 20)}...')→ Token-Prefix nie mehr in Logs - ❌ Gerätespezifische IDs im Klartext → werden durch
sanitize()mit[UUID]/[USER_ID]ersetzt - ❌ Fehler-Stack-Traces mit Token-Fragmenten →
SecureLogger.error()nutzt sanitizierten Output
Status: ✅ HIGH-2 VOLLSTÄNDIG IMPLEMENTIERT
Priorität: ~~📅 1 Woche~~ → ✅ ERLEDIGT
HIGH-3: ✅ Unvalidierte File Downloads IMPLEMENTIERT¶
OWASP: A03:2021 – Injection + A08:2021 – Software and Data Integrity Failures
CWE: CWE-494 (Download of Code Without Integrity Check)
System: Shop-System
Status: ✅ IMPLEMENTIERT am 24. Februar 2026
Implementierung¶
Ein neuer FirebaseStorageUrlValidator wurde im shared-Package erstellt und in beide Download-Services (mobile + web) integriert. Alle Download-URLs werden vor der Ausführung serverseitig validiert.
Neue Datei: packages/shared/lib/validators/firebase_storage_url_validator.dart
Geänderte Dateien:
- apps/shop_system/lib/services/mobile/document_download_service.dart
- apps/shop_system/lib/services/web/document_download_service_web.dart
- packages/shared/lib/shared.dart – FirebaseStorageUrlValidator re-exportiert
// packages/shared/lib/validators/firebase_storage_url_validator.dart
class FirebaseStorageUrlValidator {
static const List<String> _allowedDomains = [
'firebasestorage.googleapis.com',
'storage.googleapis.com',
];
static const List<String> _allowedSchemes = ['https'];
static bool isValid(String? url) {
if (url == null || url.isEmpty) return false;
final uri = Uri.parse(url);
// 1. HTTPS-only
if (!_allowedSchemes.contains(uri.scheme.toLowerCase())) return false;
// 2. Nur Firebase Storage Domains
if (!_allowedDomains.any((d) => uri.host.toLowerCase() == d)) return false;
// 3. Path Traversal verhindern
if (uri.path.contains('..')) return false;
// 4. Gefährliche Zeichen
if (RegExp(r'[<>"'r'|?*\x00-\x1f]').hasMatch(uri.path)) return false;
// 5. Firebase Storage URL-Format (/v0/b/...)
if (uri.host == 'firebasestorage.googleapis.com' &&
!uri.path.startsWith('/v0/b/')) return false;
return true;
}
static void validate(String? url) {
if (!isValid(url)) {
throw ArgumentError(
'Ungültige oder unsichere URL. Nur Firebase Storage URLs sind erlaubt.',
);
}
}
}
// In beiden Download-Services:
Future<String> downloadDocument(ArticleDocument document, ...) async {
// 🔒 SECURITY HIGH-3: URL-Validierung gegen Path Traversal (CWE-494)
FirebaseStorageUrlValidator.validate(document.downloadUrl);
// ... Download
}
Was wurde geschützt¶
- ✅ Alle Downloads über
DocumentDownloadService(mobile) - ✅ Alle Downloads über
DocumentDownloadServiceWeb(web):downloadDocument,openDocument,shareDocument - ✅ Gecachte Downloads via
getCachedOrDownload
Status: ✅ HIGH-3 VOLLSTÄNDIG IMPLEMENTIERT
Priorität: ~~📅 2 Wochen~~ → ✅ ERLEDIGT
✅ HIGH-4: Session Timeout – VOLLSTÄNDIG IMPLEMENTIERT (BEHOBEN 24.02.2026)¶
OWASP: A07:2021 – Identification and Authentication Failures
CWE: CWE-613 (Insufficient Session Expiration)
System: Beide Systeme
Status: ✅ VOLLSTÄNDIG – ERP hat InactivityService (30-Minuten Idle-Timeout + Auto-Logout). Shop-System hat nun ebenfalls einen InactivityService mit 30-Minuten Idle-Timeout, der via Listener-Widget alle Pointer-Events erfasst und bei Timeout automatisch FirebaseAuth.instance.signOut() aufruft.
Implementierung¶
Der bestehende BaseAuthService in packages/shared enthält bereits vollständige Session-Timeout-Logik. Diese wurde in beide Apps aktiv integriert – via startPeriodicTokenRefresh(), validateAndRefreshSession() und einem WidgetsBindingObserver für App-Lifecycle-Events.
Geänderte Dateien:
- apps/shop_system/lib/services/inactivity_service.dart – NEU: InactivityService mit 30-Min.-Idle-Timeout für Shop
- apps/shop_system/lib/application.dart – Listener-Wrapper + _inactivityService.start() / .dispose()
- apps/shop_system/lib/main.dart – Lifecycle Observer + Token Refresh + Cleanup
- apps/erp_system/lib/main.dart – Lifecycle Observer + Token Refresh + Cleanup
Session-Konfiguration (packages/shared/lib/services/base_auth_service.dart)¶
| Parameter | Wert |
|---|---|
sessionTimeout |
12 Stunden (seit letztem Login) |
tokenRefreshThreshold |
5 Minuten vor Ablauf |
refreshCheckInterval |
alle 4 Minuten |
// Beide main.dart – identische Implementierung:
// 1. Periodic Token Refresh starten
authService.startPeriodicTokenRefresh();
// 2. Lifecycle Observer für App Resume
class _AppLifecycleObserver extends WidgetsBindingObserver {
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
// App kommt in den Vordergrund → Session + Token validieren
authService.validateAndRefreshSession();
}
}
}
// 3. Cleanup bei App-Ende
@override
void dispose() {
WidgetsBinding.instance.removeObserver(_lifecycleObserver);
authService.stopPeriodicTokenRefresh();
super.dispose();
}
Was validateAndRefreshSession() prüft¶
- Session-Alter – älter als 12h seit letztem Login → automatischer Logout
- Token-Ablauf – läuft in <5min ab → proaktive Erneuerung (
getIdToken(true)) - Auth-Fehler – Token ungültig → Logout; Netzwerkfehler → kein Logout (Offline-Toleranz)
Status: ✅ HIGH-4 VOLLSTÄNDIG IMPLEMENTIERT
Priorität: ~~📅 3 Wochen~~ → ✅ ERLEDIGT
✅ HIGH-5: Biometrische Authentifizierung – VOLLSTÄNDIG IMPLEMENTIERT (BEHOBEN 24.02.2026)¶
OWASP: A07:2021 – Identification and Authentication Failures
CWE: CWE-287 (Improper Authentication)
CVSS Score: 5.8 (Medium)
System: Beide Systeme
Status: ✅ BEHOBEN – Im ERP-System und Shop-System vollständig implementiert. Shop-System hat jetzt CheckBiometricSupport, EnableBiometricAuth, DisableBiometricAuth, AuthenticateWithBiometric Events/States im AuthBloc, sowie Biometric-Button-UI in responsive_sign_in_form.dart.
Implementierte Komponenten:
- BiometricAuthService (
packages/shared/lib/services/biometric_auth_service.dart) - Device-Support Prüfung
- Verfügbare Biometrie-Typen (Face ID, Touch ID, Fingerprint)
- Sichere Authentifizierung mit Fallback
- Opt-in/Opt-out für Nutzer
-
Comprehensive Error Handling
-
BaseAuthService Integration (
packages/shared/lib/services/base_auth_service.dart) isBiometricSupported()- Hardware-CheckcanUseBiometrics()- Verfügbarkeits-CheckenableBiometricAuth()- Aktivierung mit Test-AuthdisableBiometricAuth()- DeaktivierungauthenticateWithBiometrics()- Authentifizierung-
requiresBiometricReAuth()- Re-Auth Check (5min Timeout) -
ERP-System Integration (
apps/erp_system/lib/blocs/auth_bloc/) - Neue Events:
CheckBiometricSupport,EnableBiometricAuth,DisableBiometricAuth,AuthenticateWithBiometric - Neue States:
BiometricSupportChecked,BiometricAuthEnabled,BiometricAuthenticating,BiometricAuthSuccess,BiometricAuthFailed -
Vollständige BLoC Integration mit Error Handling
-
Shop-System Integration ✅ NEU 24.02.2026
auth_bloc_events.dart–CheckBiometricSupport,EnableBiometricAuth,DisableBiometricAuth,AuthenticateWithBiometricauth_bloc_states.dart–BiometricSupportChecked,BiometricAuthEnabled,BiometricAuthDisabled,BiometricAuthenticating,BiometricAuthSuccess,BiometricAuthFailedauth_bloc.dart– vollständige Handler viaAuthService(extendsBaseAuthService)responsive_sign_in_form.dart– Fingerprint-Button (mobil), BlocConsumer für State-Handling, Opt-in/Opt-out Toggle
Features¶
✅ Multi-Platform Support: - iOS: Face ID + Touch ID - Android: Fingerprint + Face Recognition + Iris Scanner - Automatische Erkennung verfügbarer Methoden
✅ Security Features: - Sticky Authentication (bleibt aktiv bis erfolgreich/abgebrochen) - Lockout nach zu vielen Fehlversuchen - Re-Authentifizierung nach 5 Minuten Inaktivität - Verschlüsselte Speicherung der Einstellungen (SharedPreferences)
✅ Error Handling:
- BiometricErrorType.notAvailable - Hardware nicht verfügbar
- BiometricErrorType.notEnrolled - Keine biometrischen Daten
- BiometricErrorType.lockedOut - Temporär gesperrt
- BiometricErrorType.permanentlyLockedOut - Neustart erforderlich
- Benutzerfreundliche Fehlermeldungen
✅ User Experience: - Opt-in Design (Nutzer muss aktivieren) - Test-Authentifizierung vor Aktivierung - Systemdialoge mit lokalisierten Texten - Fallback auf PIN/Pattern bei Fehler
Code-Beispiele¶
ERP-System - Biometric Auth aktivieren:
// Im AuthBloc
authBloc.add(EnableBiometricAuth());
// State Handling
BlocBuilder<AuthBloc, BaseBlocState>(
builder: (context, state) {
if (state is BiometricAuthEnabled) {
// Erfolg: Zeige Bestätigung
} else if (state is BiometricAuthFailed) {
// Fehler: Zeige Grund
print(state.reason);
}
},
)
Shop-System - Biometric Auth nutzen:
// Im AuthService
final authService = AuthService(); // extends BaseAuthService
// Prüfe Support
final isSupported = await authService.isBiometricSupported();
final isAvailable = await authService.canUseBiometrics();
// Aktiviere
final success = await authService.enableBiometricAuth();
// Authentifiziere
final authenticated = await authService.authenticateWithBiometrics(
localizedReason: 'Bitte authentifizieren Sie sich',
useErrorDialogs: true,
stickyAuth: true,
);
if (authenticated) {
// Zugriff gewähren
}
Verfügbare Biometrie-Typen ermitteln:
// Via BaseAuthService
final types = await authService.getAvailableBiometricTypes();
// Returns: ['Face ID / Gesichtserkennung', 'Touch ID / Fingerabdruck']
// Via BiometricAuthService direkt
final biometricService = BiometricAuthService();
final biometrics = await biometricService.getAvailableBiometrics();
// Returns: [BiometricType.face, BiometricType.fingerprint]
Abhängigkeiten¶
Automatisch verfügbar in beiden Apps durch shared package Integration.
Security Compliance¶
OWASP A07:2021 ✅ Erfüllt: - Multi-Faktor Authentifizierung implementiert - Starke Authentifizierungsmechanismen - Session Management mit Re-Auth - Lockout nach Fehlversuchen
CWE-287 ✅ Mitigiert: - Improper Authentication → durch biometrische Verifikation adressiert - Hardware-basierte Authentifizierung - Kein Passwort-nur Login mehr erforderlich
OWASP MASVS (Mobile App Security Verification Standard): - MSTG-AUTH-8: ✅ Biometric authentication properly implemented - MSTG-AUTH-9: ✅ Second factor exists - MSTG-STORAGE-1: ✅ Biometric state encrypted in SharedPreferences
Testing Empfehlungen¶
- Funktionale Tests:
- [ ] Face ID/Touch ID Aktivierung auf iOS
- [ ] Fingerprint/Face Recognition auf Android
- [ ] Deaktivierung funktioniert
-
[ ] Re-Auth nach 5min Timeout
-
Error Handling Tests:
- [ ] Keine biometrischen Daten registriert
- [ ] Hardware nicht verfügbar
- [ ] Zu viele Fehlversuche (Lockout)
-
[ ] User-Cancellation
-
Security Tests:
- [ ] Biometric Settings nur mit Auth änderbar
- [ ] Kein Fallback auf schwache Auth
- [ ] Session-Invalidierung bei Auth-Fehler
Migration & Rollout¶
Phase 1: Opt-in für Power Users (Woche 1-2)
- Feature Flag: biometric_auth_enabled
- Beta-Test mit 10% der User
- Monitoring: Success Rate, Error Types
Phase 2: Rollout für alle User (Woche 3-4) - In-App Tutorial - Onboarding Flow: "Biometrie aktivieren?" - Opt-out jederzeit möglich
Phase 3: Default für neue User (Woche 5+) - Neue Registrierungen: Auto-Vorschlag - Alte User: Notification "Jetzt aktivieren"
Monitoring & Metrics¶
Empfohlene Metriken: - Adoption Rate: % Nutzer mit aktivierter Biometrie - Success Rate: % erfolgreiche Authentifizierungen - Error Rate by Type: Verteilung der Error Types - Fallback Rate: % User die auf PIN/Password fallback'en
Status: ✅ HIGH-5 VOLLSTÄNDIG IMPLEMENTIERT (BEIDE SYSTEME)
Priorität: ~~📅 4 Wochen~~ → ✅ ERLEDIGT 24.02.2026
HIGH-6: ✅ Unverschlüsselte App State BEHOBEN¶
OWASP: A02:2021 – Cryptographic Failures
CWE: CWE-312 (Cleartext Storage of Sensitive Information)
CVSS Score: 5.5 (Medium)
System: Beide Systeme
Status: ✅ Umgesetzt am 24.02.2026
Problem (ursprünglich)¶
Das ERP-System verwendete Hive ohne Verschlüsselung für persistierten Auth State:
// VORHER: Unverschlüsselt in Hive (main.dart)
await Hive.openBox('authBox'); // ❌ Keine Verschlüsselung!
// Daten im Klartext auf dem Gerät:
authBox.put('isLoggedIn', true);
authBox.put('customerId', AppConfig.customerId); // ❌ Klartext!
authBox.put('userId', userId); // ❌ Klartext!
Betroffene Stellen (ERP-System):
- main.dart – Box-Initialisierung ohne Cipher
- auth_bloc.dart – isLoggedIn, customerId, userId
- helpers/auth_helpers.dart – liest customerId, userId
- services/inactivity_service.dart
- services/trigger_statistics_service.dart
- blocs/job_bloc/job_bloc.dart
- pages/settings/ – 4 weitere Dateien
Shop-System: Bereits korrekt – sensitiver State via SecureStorageService (FlutterSecureStorage) ✅
Risiko¶
- Daten im Klartext auf dem Dateisystem (Hive
.hive-Datei) - Bei Backup-Extraktion (
adb backup) lesbar - Bei rooted/jailbroken Geräten direkt zugreifbar
customerIdunduserIdermöglichen Account Enumeration- DSGVO-Verstoß bei unverschlüsselten personenbezogenen Daten
Implementierte Lösung¶
Neuer Service: EncryptedAppStateService
Datei: apps/erp_system/lib/services/encrypted_app_state_service.dart
class EncryptedAppStateService {
// AES-256 verschlüsselte Hive Box
static const String _authBoxName = 'authBox_encrypted';
// Encryption Key sicher gespeichert (iOS Keychain / Android Keystore)
static const FlutterSecureStorage _secureStorage = FlutterSecureStorage(
aOptions: AndroidOptions(encryptedSharedPreferences: true),
iOptions: IOSOptions(accessibility: KeychainAccessibility.first_unlock),
);
static Future<void> initialize() async {
final encryptionKey = await _getOrCreateEncryptionKey();
_authBox = await Hive.openBox(
_authBoxName,
encryptionCipher: HiveAesCipher(encryptionKey), // ✅ AES-256
);
}
// Key wird automatisch generiert und im Platform Keychain abgelegt
static Future<Uint8List> _getOrCreateEncryptionKey() async {
final existingKey = await _secureStorage.read(key: _encryptionKeyName);
if (existingKey != null) return base64Decode(existingKey);
final newKey = Hive.generateSecureKey(); // ✅ Kryptographisch sicher
await _secureStorage.write(key: _encryptionKeyName, value: base64Encode(newKey));
return Uint8List.fromList(newKey);
}
// Einmalige Migration von Altdaten
static Future<void> migrateFromUnencryptedBox() async {
final oldBox = await Hive.openBox('authBox');
if (oldBox.isNotEmpty) {
for (var key in oldBox.keys) {
await authBox.put(key, oldBox.get(key));
}
await oldBox.clear();
await Hive.deleteBoxFromDisk('authBox'); // ✅ Alte Daten löschen
}
}
}
Geänderte Dateien (ERP-System):
| Datei | Änderung |
|---|---|
lib/services/encrypted_app_state_service.dart |
NEU – Service mit AES-256 Hive Cipher + Key in Keychain |
lib/main.dart |
Initialisierung + Migration von alter unverschlüsselter Box |
lib/blocs/auth_bloc/auth_bloc.dart |
Alle Hive-Zugriffe durch EncryptedAppStateService ersetzt |
lib/helpers/auth_helpers.dart |
Liest aus EncryptedAppStateService statt Hive.box |
lib/services/inactivity_service.dart |
Nutzt EncryptedAppStateService.clearAuthData() |
lib/services/trigger_statistics_service.dart |
Nutzt AuthHelpers.tryGetCustomerId() |
lib/blocs/job_bloc/job_bloc.dart |
Nutzt AuthHelpers.tryGetUserId() |
lib/pages/settings/article_document_type_settings/ |
2 Dateien migriert |
lib/pages/settings/job_settings/ |
2 Dateien migriert |
lib/pages/settings/connector_settings/ |
1 Datei migriert |
Sicherheitsverbesserungen:
- ✅ AES-256 Verschlüsselung für alle persistierten Auth-Daten
- ✅ Encryption Key im Platform Keychain (iOS) / Android Keystore
- ✅ Automatische Migration von Altdaten (einmalig beim ersten App-Start)
- ✅ Alte unverschlüsselte Daten werden nach Migration gelöscht
- ✅ DSGVO-konform: customerId und userId verschlüsselt
- ✅ Shop-System war bereits korrekt implementiert
Status: ✅ HIGH-6 VOLLSTÄNDIG BEHOBEN
Priorität: ~~📅 4 Wochen~~ → ✅ ERLEDIGT 24.02.2026
HIGH-8: ✅ Insufficient Transport Security – IMPLEMENTIERT¶
OWASP: A02:2021 – Cryptographic Failures
CWE: CWE-319 (Cleartext Transmission of Sensitive Information)
CVSS Score: 5.2 (Medium)
System: Shop-System
Status: ✅ IMPLEMENTIERT am 24. Februar 2026
Problem (Ursprünglich)¶
Das Shop-System hatte keine explizite Konfiguration zur Erzwingung von HTTPS auf Plattform- und App-Ebene:
- Android: Kein
networkSecurityConfigimAndroidManifest.xml→ kein explizites Cleartext-Verbot auf Betriebssystem-Ebene. - iOS: Keine
NSAppTransportSecurity-Sektion inInfo.plist→ ATS-Konfiguration war weder dokumentiert noch explizit gehärtet. - Dart/Flutter: Kein globaler
HttpOverrides-Layer → Dart konnte theoretisch cleartext HTTP-Verbindungen aufbauen; kein einheitliches Logging bei schlechten Zertifikaten.
Risiko¶
- Sensible Daten (Auth-Tokens, Kundendaten, Bestellungen) könnten unverschlüsselt übertragen werden
- Man-in-the-Middle Angriffe auf HTTP-Downgrade möglich
- Bei falsch konfigurierten Proxys oder Bibliotheken kein OS-seitiger Schutz
- DSGVO-Verstoß durch unverschlüsselte Übertragung personenbezogener Daten
Implementierung¶
1. Android: network_security_config.xml (neu)
Datei: apps/shop_system/android/app/src/main/res/xml/network_security_config.xml
<network-security-config>
<!-- Cleartext HTTP global blockieren -->
<base-config cleartextTrafficPermitted="false">
<trust-anchors>
<certificates src="system" />
</trust-anchors>
</base-config>
<!-- Firebase Storage: HTTPS + System-CAs -->
<domain-config cleartextTrafficPermitted="false">
<domain includeSubdomains="true">firebasestorage.googleapis.com</domain>
<domain includeSubdomains="true">storage.googleapis.com</domain>
...
</domain-config>
<!-- Debug-Only: User-CAs für Proxy erlauben (wirkt nur in Debug-Builds) -->
<debug-overrides>
<trust-anchors>
<certificates src="system" />
<certificates src="user" />
</trust-anchors>
</debug-overrides>
</network-security-config>
AndroidManifest.xml – zwei neue Attribute:
<application
android:usesCleartextTraffic="false"
android:networkSecurityConfig="@xml/network_security_config"
...>
2. iOS: NSAppTransportSecurity in Info.plist (ergänzt)
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<false/>
<key>NSAllowsLocalNetworking</key>
<true/>
<key>NSExceptionDomains</key>
<dict>
<!-- Firebase Storage, googleapis.com, firebaseapp.com -->
<!-- NSExceptionAllowsInsecureHTTPLoads: false -->
<!-- NSExceptionMinimumTLSVersion: TLSv1.2 -->
<!-- NSExceptionRequiresForwardSecrecy: true -->
</dict>
</dict>
3. Dart: TransportSecurityService (neu)
Datei: apps/shop_system/lib/services/transport_security_service.dart
class TransportSecurityService {
/// Initialisiert globale HttpOverrides – muss VOR dem ersten Netzwerkzugriff
/// in main() aufgerufen werden.
static void initialize() {
if (!kIsWeb) {
HttpOverrides.global = _SecureHttpOverrides();
}
}
/// Wirft InsecureTransportException wenn URL kein HTTPS verwendet.
static bool requireHttps(String? url) { ... }
/// Gibt Diagnose-Status zurück (initialized, cleartext_blocked, etc.)
static Map<String, dynamic> getSecurityStatus() { ... }
}
class _SecureHttpOverrides extends HttpOverrides {
@override
HttpClient createHttpClient(SecurityContext? context) {
final client = super.createHttpClient(context);
// Alle ungültigen Zertifikate ablehnen (Defense-in-Depth neben SSLPinningConfig)
client.badCertificateCallback = (cert, host, port) {
SecureLogger.warning('[SECURITY HIGH-8] Bad certificate für $host:$port abgelehnt.');
return false;
};
return client;
}
}
main.dart – TransportSecurityService.initialize() vor Firebase.initializeApp():
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
// 🔒 SECURITY HIGH-8: Transport Security – vor erstem Netzwerkzugriff
TransportSecurityService.initialize();
await Firebase.initializeApp(...);
...
}
Geänderte / Neue Dateien¶
| Datei | Änderung |
|---|---|
android/app/src/main/res/xml/network_security_config.xml |
NEU – Cleartext blockiert, User-CAs nur in Debug |
android/app/src/main/AndroidManifest.xml |
usesCleartextTraffic="false" + networkSecurityConfig |
ios/Runner/Info.plist |
NSAppTransportSecurity mit NSAllowsArbitraryLoads=false + Firebase-Exceptions |
lib/services/transport_security_service.dart |
NEU – Dart-Layer mit HttpOverrides, URL-Validator, Diagnose |
lib/main.dart |
TransportSecurityService.initialize() eingebunden |
Schutzebenen (Defense in Depth)¶
| Ebene | Mechanismus | Status |
|---|---|---|
| Betriebssystem (Android) | network_security_config.xml |
✅ |
| Betriebssystem (iOS) | NSAppTransportSecurity |
✅ |
| Dart HttpClient | _SecureHttpOverrides.badCertificateCallback |
✅ |
| Application Layer (Dio) | SSLPinningConfig.createSecureDio() (CRIT-5) |
✅ SHA-256 Fingerprints eingetragen (24.02.2026) |
| URL-Validierung | TransportSecurityService.requireHttps() |
✅ |
| Storage Download | FirebaseStorageUrlValidator.validate() |
✅ (HIGH-3) |
Status: ✅ HIGH-8 VOLLSTÄNDIG IMPLEMENTIERT
Priorität: ~~📅 3 Wochen~~ → ✅ ERLEDIGT 24.02.2026
HIGH-9: ✅ Weak Password Policy – BEHOBEN (24.02.2026)¶
OWASP: A07:2021 – Identification and Authentication Failures
CWE: CWE-521 (Weak Password Requirements)
System: Beide Systeme
Problem (Vorher)¶
Beide Apps verwendeten lediglich minLength: 6 ohne jede Komplexitätsprüfung:
// ALT – shop_system validators.dart
static String? validatePasswordWithLength(String? value, {int minLength = 6}) {
if (value.length < minLength) return 'Passwort muss mindestens $minLength Zeichen lang sein';
return null; // kein Großbuchstabe, keine Ziffer, kein Sonderzeichen
}
Lösung¶
1. AuthValidators.validatePasswordStrength (shop_system)
apps/shop_system/lib/pages/auth/shared/validators.dart
// OWASP A07 – enforces password complexity for registration / password change
static String? validatePasswordStrength(String? value) {
if (value == null || value.isEmpty) return 'Bitte Passwort eingeben';
if (value.length < 8) return 'Passwort muss mindestens 8 Zeichen lang sein';
if (value.length > 128) return 'Passwort darf maximal 128 Zeichen haben';
if (!value.contains(RegExp(r'[a-z]'))) return 'Mindestens einen Kleinbuchstaben';
if (!value.contains(RegExp(r'[A-Z]'))) return 'Mindestens einen Großbuchstaben';
if (!value.contains(RegExp(r'\d'))) return 'Mindestens eine Ziffer';
if (!value.contains(RegExp(r'[@$!%*?&#^()_+\-=\[\]{};:,.<>]')))
return r'Mindestens ein Sonderzeichen (@$!%*?&#)';
return null;
}
2. AuthPasswordField – neuer Parameter requireStrongPassword
apps/shop_system/lib/pages/auth/shared/widgets/auth_password_field.dart
requireStrongPassword: true→ verwendetvalidatePasswordStrengthrequireMinLength: true→ Legacy-Pfad (nur Länge, z. B. Sign-In)
3. Alle Registrierungsformulare auf requireStrongPassword: true umgestellt:
- apps/shop_system/lib/pages/auth/shared/widgets/responsive_sign_up_form.dart
- apps/shop_system/lib/pages/auth/web/sign_up_page_web.dart
- apps/shop_system/lib/pages/auth/mobile/sign_up_page_mobile.dart
Helper-Text gibt dem Nutzer die Anforderungen direkt im Formular an:
„Mind. 8 Zeichen, Groß-/Kleinbuchstabe, Ziffer, Sonderzeichen"
4. ERP-System: packages/shared/lib/helpers/validation_service.dart hatte bereits minPasswordLength = 12 + volle Komplexitätsprüfung. In user_profile_page.dart aktiv genutzt. Keine Änderung erforderlich.
Anforderungserfüllung¶
| Kriterium | Shop (vorher) | Shop (nachher) | ERP |
|---|---|---|---|
| Mindestlänge | 6 Zeichen | 8 Zeichen | 12 Zeichen |
| Maximal-Länge (DoS) | – | 128 Zeichen | 128 Zeichen |
| Kleinbuchstabe | – | ✅ | ✅ |
| Großbuchstabe | – | ✅ | ✅ |
| Ziffer | – | ✅ | ✅ |
| Sonderzeichen | – | ✅ | ✅ |
| Hinweis im UI | – | ✅ | – |
Status: ✅ HIGH-9 VOLLSTÄNDIG BEHOBEN
Priorität: ~~⚠️ TEILWEISE~~ → ✅ ERLEDIGT 24.02.2026
HIGH-10: ✅ Insecure Random Number Generation – BEHOBEN (24.02.2026)¶
OWASP: A02:2021 – Cryptographic Failures
CWE: CWE-338 (Use of Cryptographically Weak Pseudo-Random Number Generator)
System: Beide Systeme
Problem (Vorher)¶
dart:maths Random() ist ein deterministischer PRNG. Bei bekanntem oder erschöpftem Seed können Angreifer die Ausgabe reproduzieren. Im Projekt wurden vier Stellen mit Random() identifiziert:
| Datei | Verwendung | Risiko |
|---|---|---|
chatbot_service.dart |
Zufällige Chatbot-Antwort aus Varianten | niedrig (kein Sicherheitsbezug) |
knowledge_graph_service.dart (×3) |
Witze, Begrüßungen, Personality-Responses | niedrig (kein Sicherheitsbezug) |
random_string_generator.dart |
Passwort-/Token-Generierung | ✅ war bereits Random.secure() |
request_signing_service.dart |
Nonce-Generierung für HMAC-Signaturen | ✅ war bereits Random.secure() |
Die sicherheitskritischen Stellen (Token- und Nonce-Generierung) verwendeten bereits Random.secure(). Dennoch wurden zur Beseitigung des Findings und zur defensiven Härtung alle verbliebenen Random()-Vorkommen bereinigt.
Lösung¶
Alle vier Random() → Random.secure() ersetzt in:
apps/erp_system/lib/services/bot_services/chatbot_service.dart
// VORHER
final random = Random();
// NACHHER
final random = Random.secure(); // OWASP HIGH-10: CWE-338 – kryptografisch sicherer PRNG
apps/erp_system/lib/services/bot_services/knowledge_graph_service.dart (3 Stellen: getRandomJoke, getGreeting, getPersonalityResponse)
// VORHER
final random = Random();
// NACHHER
final random = Random.secure(); // OWASP HIGH-10: CWE-338 – kryptografisch sicherer PRNG
Random.secure() nutzt die OS-Entropie (/dev/urandom auf Linux/macOS/iOS, CryptGenRandom auf Windows) und ist damit kryptografisch nicht vorhersehbar.
Anforderungserfüllung¶
| Stelle | Vorher | Nachher |
|---|---|---|
chatbot_service.dart |
Random() ⚠️ |
Random.secure() ✅ |
knowledge_graph_service.dart (×3) |
Random() ⚠️ |
Random.secure() ✅ |
random_string_generator.dart |
Random.secure() ✅ |
Random.secure() ✅ |
request_signing_service.dart |
Random.secure() ✅ |
Random.secure() ✅ |
Status: ✅ HIGH-10 VOLLSTÄNDIG BEHOBEN
Priorität: ~~🟠 OFFEN~~ → ✅ ERLEDIGT 24.02.2026
HIGH-11: ✅ Missing Security Headers (Web) – IMPLEMENTIERT (24.02.2026)¶
OWASP: A05:2021 – Security Misconfiguration
CWE: CWE-693 (Protection Mechanism Failure)
System: Beide
Status: ✅ IMPLEMENTIERT am 24. Februar 2026
Problem (Vorher)¶
Beiden Web-Apps fehlten essentielle HTTP-Security-Header. Die ERP-App hatte nur fünf Basis-Header ohne Referrer-Policy, Permissions-Policy und Cross-Origin-Policies. Der Shop hatte überhaupt keine Firebase-Hosting-Konfiguration.
Implementierung¶
Security Headers wurden auf drei Ebenen (Defense-in-Depth) implementiert:
Ebene 1: Firebase Hosting – ERP System (firebase.json)
Bestehende Header um fehlende ergänzt:
{ "key": "Content-Security-Policy", "value": "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://apis.google.com https://www.gstatic.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: https:; connect-src 'self' https://*.googleapis.com https://*.firebaseio.com wss://*.firebaseio.com; frame-src 'none';" },
{ "key": "Referrer-Policy", "value": "strict-origin-when-cross-origin" },
{ "key": "Permissions-Policy", "value": "camera=(), microphone=(), geolocation=(), payment=(), usb=(), bluetooth=()" },
{ "key": "Cross-Origin-Opener-Policy", "value": "same-origin" },
{ "key": "Cross-Origin-Resource-Policy", "value": "same-origin" }
Außerdem:
frame-src 'self'→'none'(kein Framing erlaubt)
Ebene 2: Firebase Hosting – Shop System (apps/shop_system/firebase.json)
hosting-Sektion mit vollständigem Header-Set neu hinzugefügt (zuvor nicht vorhanden):
"hosting": {
"public": "build/web",
"headers": [
{ "key": "X-Content-Type-Options", "value": "nosniff" },
{ "key": "X-Frame-Options", "value": "DENY" },
{ "key": "X-XSS-Protection", "value": "1; mode=block" },
{ "key": "Strict-Transport-Security", "value": "max-age=31536000; includeSubDomains" },
{ "key": "Content-Security-Policy", "value": "..." },
{ "key": "Referrer-Policy", "value": "strict-origin-when-cross-origin" },
{ "key": "Permissions-Policy", "value": "camera=(), microphone=(), geolocation=(), payment=(), usb=(), bluetooth=()" },
{ "key": "Cross-Origin-Opener-Policy", "value": "same-origin" },
{ "key": "Cross-Origin-Resource-Policy", "value": "same-origin" }
],
"rewrites": [{ "source": "**", "destination": "/index.html" }]
}
Ebene 3: HTML Meta-Tags – Fallback für beide Apps
(apps/erp_system/web/index.html, apps/shop_system/web/index.html)
<!-- 🔒 SECURITY HIGH-11: Security Headers (Defense-in-Depth via Meta-Tags) -->
<meta http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'
https://apis.google.com https://www.gstatic.com; ...">
<meta http-equiv="X-Content-Type-Options" content="nosniff">
<meta name="referrer" content="strict-origin-when-cross-origin">
Geänderte Dateien¶
| Datei | Änderung |
|---|---|
firebase.json |
Referrer-Policy, Permissions-Policy, COOP, CORP hinzugefügt; frame-src 'none' |
apps/shop_system/firebase.json |
NEU – hosting-Sektion mit vollständigem Header-Set |
apps/erp_system/web/index.html |
Security Meta-Tags (CSP, nosniff, referrer) als Fallback |
apps/shop_system/web/index.html |
Security Meta-Tags (CSP, nosniff, referrer) als Fallback |
Schutz-Matrix¶
| Header | Schutzziel | ERP (vorher) | ERP (nachher) | Shop (nachher) |
|---|---|---|---|---|
Content-Security-Policy |
XSS, Injection | ✅ | ✅ | ✅ |
X-Frame-Options: DENY |
Clickjacking | ✅ | ✅ | ✅ |
X-Content-Type-Options |
MIME-Sniffing | ✅ | ✅ | ✅ |
Strict-Transport-Security |
MITM / HTTPS-Downgrade | ✅ | ✅ | ✅ |
Referrer-Policy |
Datenleck via Referrer | ❌ | ✅ | ✅ |
Permissions-Policy |
Sensorzugriff (Kamera etc.) | ❌ | ✅ | ✅ |
Cross-Origin-Opener-Policy |
Spectre / XS-Leaks | ❌ | ✅ | ✅ |
Cross-Origin-Resource-Policy |
Cross-Origin Reads | ❌ | ✅ | ✅ |
LOW-4 (Unvollständige Security Headers) und LOW-5 (Fehlende CSP) sind durch diese Implementierung ebenfalls vollständig geschlossen.
Status: ✅ HIGH-11 VOLLSTÄNDIG IMPLEMENTIERT
Priorität: ~~📅 4 Wochen~~ → ✅ ERLEDIGT 24.02.2026
HIGH-12: ✅ Fehlende Android Backup-Schutz – IMPLEMENTIERT (24.02.2026)¶
OWASP: A02:2021 – Cryptographic Failures
CWE: CWE-530 (Exposure of Backup File to Unauthorized Control Sphere)
CVSS Score: 4.7 (Medium-High)
System: Beide
Status: ✅ IMPLEMENTIERT am 24. Februar 2026
Problem (Vorher)¶
Android ermöglicht standardmäßig automatische Backups von App-Daten über adb backup und Auto-Backup in Google Drive. Ohne explizite Deaktivierung können sensible Daten (Tokens, verschlüsselte Hive-Boxen, FirebaseApp-State) durch einen physischen Gerätezugriff oder kompromittierten Google-Account extrahiert werden.
Betroffen: android:allowBackup fehlte in beiden AndroidManifest.xml-Dateien (Standardwert ist true bis API 30).
Implementierung¶
In beiden Android-Manifests wurden zwei Attribute explizit auf false gesetzt:
ERP-System (apps/erp_system/android/app/src/main/AndroidManifest.xml)
<application
android:label="easy_sale_erp"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"
android:allowBackup="false"
android:fullBackupContent="false">
Shop-System (apps/shop_system/android/app/src/main/AndroidManifest.xml)
<application
android:label="easy_sale_mobile_app"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"
android:allowBackup="false"
android:fullBackupContent="false"
android:usesCleartextTraffic="false"
android:networkSecurityConfig="@xml/network_security_config">
| Attribut | Wert | Wirkung |
|---|---|---|
android:allowBackup |
false |
Deaktiviert adb backup und lokale Gerätesicherungen |
android:fullBackupContent |
false |
Deaktiviert Auto-Backup (Google Drive/Cloud) vollständig |
Geänderte Dateien¶
| Datei | Änderung |
|---|---|
apps/erp_system/android/app/src/main/AndroidManifest.xml |
android:allowBackup="false" + android:fullBackupContent="false" hinzugefügt |
apps/shop_system/android/app/src/main/AndroidManifest.xml |
android:allowBackup="false" + android:fullBackupContent="false" hinzugefügt |
Status: ✅ HIGH-12 VOLLSTÄNDIG IMPLEMENTIERT
Priorität: ~~📅 5 Wochen~~ → ✅ ERLEDIGT 24.02.2026
🟡 MITTLERE FINDINGS¶
✅ MED-1: Fehlende Error Boundary (BEHOBEN 24.02.2026)¶
OWASP: A04:2021 – Insecure Design
CWE: CWE-755 (Improper Handling of Exceptional Conditions)
System: Beide Systeme
Status: ✅ BEHOBEN – AppErrorHandler im shared-Package implementiert; beide Apps nutzen globale Error Boundaries auf drei Ebenen.
Problem (Ursprünglich)¶
Viele Firestore Queries haben keine Timeout- oder Error-Behandlung:
Shop-System: customer_firebase_service.dart
// Keine Timeouts, keine try-catch, keine Error Recovery
final querySnapshot = await FirebaseFirestore.instance
.collection(customerAccessPermissionCollection)
.where('email', isEqualTo: FirebaseAuth.instance.currentUser?.email)
.get(); // ❌ Kann ewig hängen oder crashen
Positiv im ERP: firestore_extensions.dart existiert bereits! ✅
extension FirestoreExtensions on Query {
Future<QuerySnapshot> getSafe({Duration? timeout}) async {
return await get().timeout(
timeout ?? LoadingConfig.firestoreQueryTimeout,
onTimeout: () => throw TimeoutException(/*...*/),
);
}
}
Lösung (Implementiert)¶
1. AppErrorHandler im shared-Package (packages/shared/lib/services/app_error_handler.dart):
Drei-Schichten-Absicherung in einer zentralen Klasse – kein doppelter Code in ERP und Shop:
// Schicht 1: Framework-Fehler (Widget-Baum, Layout, Rendering)
FlutterError.onError = (details) {
SecureLogger.error('Unhandled Flutter error', error: details.exception);
if (kDebugMode) FlutterError.presentError(details);
};
// Schicht 2: Async-Fehler auf Platform-Ebene (dart:ui PlatformDispatcher)
PlatformDispatcher.instance.onError = (error, stack) {
SecureLogger.error('Unhandled async platform error', error: error);
return true; // App bleibt am Leben
};
// Schicht 3: Release-Build Fallback-Widget statt rotem Fehler-Screen
if (!kDebugMode) {
ErrorWidget.builder = (_) => const AppErrorFallbackWidget();
}
AppErrorHandler.runProtected() steht zusätzlich für runZonedGuarded-Absicherung bereit.
2. Beide main.dart angepasst (ein Aufruf, geteilt):
// apps/erp_system/lib/main.dart & apps/shop_system/lib/main.dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// 🔒 SECURITY MED-1: Globale Error Boundary (CWE-755, OWASP A04)
AppErrorHandler.initialize(appName: 'easySale ERP'); // bzw. 'easySale Shop'
// ...
}
3. Firestore Timeouts bereits vollständig abgedeckt via packages/shared/lib/extensions/firestore_extensions.dart (getWithTimeout() / snapshotsWithTimeout() in allen Services beider Apps).
✅ MED-2: XSS-Risiko bei Web-Ansichten (BEHOBEN 24.02.2026)¶
OWASP: A03:2021 – Injection
CWE: CWE-79 (Cross-Site Scripting)
System: Beide Systeme (Web-Builds)
Status: ✅ BEHOBEN – URL-Schema-Validierung in allen launchUrl-Aufrufstellen implementiert. Verhindert javascript:/data:-URI-Injections.
Problem (Ursprünglich)¶
An mehreren Stellen in beiden Apps wurden URLs aus Firestore (user-/admin-generierte Inhalte) ohne Schema-Prüfung direkt per url_launcher geöffnet. Im Web-Kontext (Flutter Web) wäre eine javascript:-URI ausführbar gewesen:
// ❌ UNSICHER – URL-Schema nicht geprüft
Future<void> _openUrl(String url) async {
final uri = Uri.parse(url); // url könnte "javascript:alert(1)" sein
if (await canLaunchUrl(uri)) {
await launchUrl(uri, mode: LaunchMode.externalApplication);
}
}
Betroffene Stellen:
- packages/shared/lib/helpers/url_launcher_helper.dart – zentraler Helper
- apps/shop_system/lib/pages/feed/web/feed_page_web.dart
- apps/shop_system/lib/pages/feed/mobile/feed_page_mobile.dart
- apps/shop_system/lib/pages/feed/mobile/feed_item_detail_page.dart
- apps/shop_system/lib/pages/profile/shared/profile/profile_legal_section.dart
- apps/shop_system/lib/pages/application/web/widgets/drawer/web_drawer_legal_footer.dart
- apps/erp_system/lib/pages/articles/detail_page/widgets/document_list_item.dart
- apps/erp_system/lib/pages/customer/detail_page/dialogs/feed_detail/widgets/feed_document_tile.dart
Lösung (Implementiert)¶
1. Zentrale Validierung in UrlLauncherHelper (shared):
/// Allowed URL schemes – prevents XSS via javascript: / data: URIs (CWE-79)
static const _allowedSchemes = {'https', 'http', 'tel', 'mailto'};
static bool isSafeUrl(Uri uri) =>
_allowedSchemes.contains(uri.scheme.toLowerCase());
static Future<bool> _launchUri(Uri uri, ...) async {
// XSS-Schutz: Nur sichere URL-Schemas erlauben (CWE-79, MED-2)
if (!isSafeUrl(uri)) return false;
if (await canLaunchUrl(uri)) {
return launchUrl(uri, mode: mode);
}
return false;
}
2. Inline-Prüfung an allen direkten launchUrl-Aufrufstellen:
// ✅ SICHER – Schema-Whitelist verhindert javascript:/data: URIs
Future<void> _openUrl(String url) async {
final uri = Uri.parse(url);
// XSS-Schutz: Nur https/http erlaubt, verhindert javascript: URIs (CWE-79)
if (!{'https', 'http'}.contains(uri.scheme.toLowerCase())) return;
if (await canLaunchUrl(uri)) {
await launchUrl(uri, mode: LaunchMode.externalApplication);
}
}
Priorität: ✅ Behoben 24.02.2026
MED-3: Unzureichende Rollback-Mechanismen¶
OWASP: A08:2021 – Software and Data Integrity Failures
CWE: CWE-664 (Improper Control of a Resource)
System: Beide Systeme
Status: ✅ BEHOBEN am 30.03.2026
Problem¶
Bei kritischen Operationen (z.B. Bestellungen) fehlten Transaktionen oder Rollbacks:
// Beispiel: Order Creation ohne Transaction
await FirebaseFirestore.instance.collection('orders').add(orderData);
await FirebaseFirestore.instance.collection('inventory').doc(articleId).update(...);
// ❌ Wenn zweite Operation fehlschlägt -> Inkonsistenter State
Implementierte Lösung¶
1. ERP createOrder (Dart) – atomare Transaktion:
core/apps/erp_system/lib/services/firebase_services/order_firebase_service.dart
Counter-Increment und Order-Dokument-Anlage in einem einzigen runTransaction-Aufruf. Sceitert einer der Writes, rollt Firestore automatisch zurück – kein verwaistes Order-Dokument und keine verlorene Auftragsnummer.
2. Cloud Function createOrder (JS) – atomare Transaktion:
core/functions/src/functions/order.callable.js
Gleiche Logik: Lieferdatum-Berechnung (rein lesend) vor die Transaktion verschoben; Counter-Increment und transaction.set(orderRef, ...) laufen in einem runTransaction-Commit.
3. createCustomerList / updateCustomerList (Dart) – WriteBatch:
core/apps/erp_system/lib/services/firebase_services/customer_lists_firebase_service.dart
Hauptdokument + Status-Items + Action-Items werden in einem WriteBatch gebündelt. Scheitert ein Write, rollt Firestore alle zurück – kein halb-angelegtes Dokument ohne Metadaten.
4. updateVariantTargetStock (Dart) – Transaktion:
core/apps/erp_system/lib/services/firebase_services/stock_firebase_service.dart
Lese-dann-Schreibe-Zugriff auf das Article-Dokument in runTransaction gekapselt, um TOCTOU-Race-Conditions zu verhindern.
MED-4-13: Weitere mittlere Findings¶
(Aus Platzgründen verkürzt - detaillierte Analysen verfügbar auf Anfrage)
- MED-4: ✅ Biometric Auth Integration - IMPLEMENTIERT in beiden Systemen (siehe HIGH-5)
- MED-5: ✅ Unverschlüsselte lokale Caches (Hive) – BEHOBEN (
HiveAesCipher+ Schlüssel inFlutterSecureStorageviaEncryptedAppStateService) - MED-6: ✅ Fehlende Jailbreak/Root Detection – BEHOBEN (24.02.2026)
Implementierung im shared-Package (keine Code-Duplizierung):
packages/shared/lib/services/device_integrity_service.dart(neu): EinheitlicherDeviceIntegrityServicemitflutter_jailbreak_detection ^1.9.0. Erkennt iOS-Jailbreak, Android-Root und Android-Developer-Mode. Web/Desktop wird übersprungen (fail-open).packages/shared/pubspec.yaml:flutter_jailbreak_detection: ^1.9.0zentral deklariert – beide Apps erben die Abhängigkeit transitiv.packages/shared/lib/shared.dart: Service viaexport 'services/device_integrity_service.dart'für beide Apps verfügbar.apps/shop_system/lib/main.dart+apps/erp_system/lib/main.dart:DeviceIntegrityService.check()wird parallel zur Firebase-/Hive-Initialisierung gestartet. Auf kompromittierten Geräten (Release-Build) startet_CompromisedDeviceAppstatt der Haupt-App.
Sicherheits-Design: Fail-open (kein False Positive), Debug-Mode-Ausnahme für Entwickler, zentrales Blocking-Widget. - MED-7: ✅ Unangemessene Permission Requests – BEHOBEN (24.02.2026)
Behobene Stellen (2 überschüssige Permissions entfernt, ERP explizit dokumentiert):
- Shop –
android/app/src/main/AndroidManifest.xml:READ_MEDIA_VIDEOundREAD_MEDIA_AUDIOentfernt. Die Shop-App hat keine Video- oder Audio-Funktionalität; beide Permissions waren ohne Nutzung deklariert (CWE-250 – Excessive Privilege).READ_MEDIA_IMAGESbleibt für die Bildergalerie-Speicherung (vgl. iOSNSPhotoLibraryAddUsageDescription). - ERP –
android/app/src/main/AndroidManifest.xml: Benötigte Permissions (INTERNET,READ_EXTERNAL_STORAGE≤ API 32,READ_MEDIA_IMAGESfürimage_picker) explizit deklariert statt implizit per Plugin-Merge. Schafft Transparenz über tatsächlich angeforderte Permissions.
Entfernte Permissions (shop_system):
<!-- ENTFERNT – keine Video-Funktionalität in der Shop-App -->
<!-- <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" /> -->
<!-- <uses-permission android:name="android.permission.READ_MEDIA_AUDIO" /> -->
Ergebnis shop_system (Android 13+): Nur noch READ_MEDIA_IMAGES statt allen drei Medien-Permissions.
- MED-8: ✅ Code Obfuscation – BEHOBEN (24.02.2026)
Implementierte Maßnahmen (4 Dateien + 2 Skripte):
- shop_system –
android/app/build.gradle.kts:isMinifyEnabled = true,isShrinkResources = true,proguardFiles("proguard-android-optimize.txt", "proguard-rules.pro")imrelease-Build-Typ aktiviert → Android R8-Minifizierung/Obfuskierung aktiv. - erp_system –
android/app/build.gradle.kts: Gleiche R8-Konfiguration wie shop_system. - shop_system –
android/app/proguard-rules.pro(neu): Flutter-Engine, Firebase, Kotlin-Coroutines und AndroidX beibehalten; Debug-Log-Calls (Log.v,Log.d) in Prod-Builds unterdrückt. - erp_system –
android/app/proguard-rules.pro(neu): Gleiche ProGuard-Regeln wie shop_system. - erp_system –
deployment/deploy_flutter_firebase.sh: Web-Build um--obfuscate --split-debug-info=build/debug-infoerweitert → Dart-Code-Obfuskierung für Web-Deploy aktiv. - shop_system –
scripts/build_release.sh(neu): Eigenständiges Release-Build-Skript mitflutter build appbundle --release --obfuscate --split-debug-info=build/debug-info/android(+ iOS). - erp_system –
scripts/build_release.sh(neu): Gleichwertiges Skript für Mobile-Builds.
Dart-Obfuskierungsparameter:
Android R8 (build.gradle.kts):
buildTypes {
release {
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
Hinweis Debug-Symbole: Die build/debug-info/-Verzeichnisse enthalten Symbol Maps zur Deobfuszierung von Crashlytics-Stack-Traces. Sie müssen sicher archiviert werden (z.B. verschlüsselter Cloud-Storage) und dürfen nicht öffentlich deployed werden.
- MED-9: ✅ Debug-Modus in Production-Builds – BEHOBEN (24.02.2026)
Problem: 20 ungeschützte debugPrint()-Aufrufe (ohne kDebugMode-Guard) in beiden Apps. In Flutter-Release-Builds läuft debugPrint weiterhin durch print(), das auf Android-Geräten via adb logcat auslesbar ist und auf iOS im System-Log erscheint. Die Ausgaben enthielten interne State-Typen (runtimeType), Order-IDs und Workflow-Details (CWE-489 – Active Debug Code).
Behobene Stellen (6 Dateien, beide Apps):
- Shop –
blocs/system/system_bloc.dart: 2 ungeschützte Calls in Timeout/Error-Catch-Blöcken mitif (kDebugMode)umschlossen. - Shop –
pages/articles/web/widgets/web_image_widget.dart: 5 ungeschützte Calls in beidenerrorBuilder/errorWidget-Callbacks (Image.network + CachedNetworkImage) mitif (kDebugMode)umschlossen; danebenkDebugModezur selektivenshow-Import-Deklaration hinzugefügt. - Shop –
services/mobile/article_image_mobile_service.dart: 4 ungeschützte Calls in Catch-Blöcken (loadArticleImages,getFirstArticleImage,getFirstImagesForArticles,getVariantImages) mitif (kDebugMode)umschlossen. - ERP –
blocs/order_bloc/orders_bloc.dart: 11 ungeschützte Calls in_onCreateOrder(Success-/Error-Pfad, Firebase-Aufruf, State-Polling) mitif (kDebugMode)umschlossen. - ERP –
pages/customer/editor_page/customer_editor_page.dart: 18 ungeschützte Calls insaveCustomerInputs()(Update + Create Pfad, Bloc-Access-Check, Formvalidierung, State-Polling) mitif (kDebugMode)umschlossen. - ERP –
pages/order/dialogs/order_editor_dialog.dart: 9 ungeschützte Calls ininitState, BlocListener und Save-Button-Handler mitif (kDebugMode)umschlossen.
Angewandtes Muster:
// Vorher – läuft in Release-Build durch:
debugPrint('❌ Error: $error');
// Nachher – nur in Debug-Builds:
if (kDebugMode) {
debugPrint('❌ Error: $error');
}
Hinweis: ssl_pinning_service.dart nutzt print() bewusst für Security-Events (SSL-Pinning-Fehler) – diese bleiben unverändert, da Sicherheitsvorfälle auch in Production geloggt werden sollen. request_signing_interceptor.dart nutzt assert() für Debug-Output – korrekt, da assert() in Release-Builds komplett wegoptimiert wird.
- MED-10: ✅ Unvalidierte Deep Links – BEHOBEN (24.02.2026)
Problem: Alle launchUrl-Aufrufe enthielten duplizierte, inline-gestreute Scheme-Prüfungen ({'https','http'}.contains(uri.scheme)). Bei zukünftigen Erweiterungen (z.B. tel:, mailto:) bestand das Risiko, dass neue Call-Sites ohne Validierung hinzugefügt werden (CWE-20 – Improper Input Validation).
Behobene Stellen (7 Dateien, beide Apps):
- Shared –
packages/shared/lib/helpers/url_launcher_helper.dart: Neue MethodelaunchExternal(String url)hinzugefügt – öffnet URLs imLaunchMode.externalApplicationmit zentraler Scheme-Prüfung.UrlLauncherHelperinshared.dartexportiert. - Shop –
feed_page_mobile.dart:_openUrl()→UrlLauncherHelper.launchExternal(url). Importurl_launcherentfernt. - Shop –
feed_item_detail_page.dart:_openUrl()→UrlLauncherHelper.launchExternal(url). Importurl_launcherentfernt. - Shop –
feed_page_web.dart:_openUrl()→UrlLauncherHelper.launchExternal(url). Importurl_launcherentfernt. - Shop –
web_drawer_legal_footer.dart:_buildLegalLink()inline →UrlLauncherHelper.launchExternal(url). Importurl_launcherentfernt,url_launcher_helperdirekt importiert. - Shop –
profile_legal_section.dart: 3× inline Launch-Block (AGB, Datenschutz, Impressum) →UrlLauncherHelper.launchUrlString(url). Importurl_launcherentfernt. - ERP –
document_list_item.dart:_launchUrl()mit inline Try-Catch →UrlLauncherHelper.launchUrlString(url). Importsurl_launcherundsecure_logger(nur für URL-Fehler genutzt) entfernt. - ERP –
feed_document_tile.dart: Top-Level_openDocument()→UrlLauncherHelper.launchExternal(url). Importurl_launcherentfernt.
Zentrales Validierungsmuster (alle Pfade laufen durch):
// packages/shared/lib/helpers/url_launcher_helper.dart
static const _allowedSchemes = {'https', 'http', 'tel', 'mailto'};
static bool isSafeUrl(Uri uri) =>
_allowedSchemes.contains(uri.scheme.toLowerCase());
static Future<bool> launchExternal(String url) async =>
_launchUri(Uri.parse(url), mode: LaunchMode.externalApplication);
static Future<bool> _launchUri(Uri uri, {LaunchMode mode = ...}) async {
if (!isSafeUrl(uri)) return false; // Blockt javascript:, data:, file: etc.
if (await canLaunchUrl(uri)) return launchUrl(uri, mode: mode);
return false;
}
DataIntegrityService im shared-Package (CWE-353):
- Firestore writes: stampDocument() berechnet SHA-256 über kritische Felder (customerId, amount, rebate, orderStatus, orderDate, orderPositions) und speichert _integrity-Metadaten im Dokument.
- Firestore reads: verifyDocument() rekonstruiert den Hash und loggt via SecureLogger.warning() bei Mismatch.
- Kanonisierung: Felder alphabetisch sortiert + jsonEncode() → resistenter gegen Feld-Reihenfolge-Angriffe.
- Integration: Shop OrderFirebaseAdapter (add + get) und ERP OrderFirebaseService (createOrder, _mapToOrders, streamOrders).
- Lokal: wrapWithHmac() / unwrapAndVerify() für Hive-Cache – HMAC-Key = UID + ID-Token (wie HIGH-7).
- MED-12: ✅ Race Conditions bei State Updates – BEHOBEN (24.02.2026)
Behobene Stellen (4 Race Conditions):
- Shop –
auth_bloc.dart(Listener):FirebaseAuth.instance.authStateChanges().listen((user) async { emit(...) })→async-Keyword entfernt,if (!isClosed && user != null)-Guard hinzugefügt. Verhindertemit()nach Bloc-Dispose (CWE-362). - Shop –
auth_bloc.dart/cart_bloc.dart/order_bloc.dart:on<SignIn>,on<SignUp>,on<PasswordReset>,on<SignOut>,on<AuthenticateWithBiometric>,on<AddReorderToCart>,on<AddOrder>,on<CancelOrder>mittransformer: droppable()registriert. Verhindert gleichzeitige doppelte Event-Verarbeitung. - Shop –
cart_bloc.dart(AddReorderToCart): Ohne Lock konnte ein zweites gleichzeitigesAddReorderToCart-Event den Warenkorb korrumpieren, da_cartItems = []und derawait-Firebase-Call nicht atomar waren →droppable()verwirft konkurrierende Events. - ERP –
auth_bloc.dart:on<LoginUser>,on<LogoutUser>,on<CheckIfUserIsLoggedIn>,on<ResetUserPassword>,on<AuthenticateWithBiometric>mittransformer: droppable()registriert. Verhindert parallele Auth-Zustandsübergänge.
Neues Dependency in beiden Apps:
Angewandtes Muster:
import 'package:bloc_concurrency/bloc_concurrency.dart';
// Kritische Aktionen: droppable() – ignoriert neue Events während Verarbeitung läuft
on<AddOrder>(_onAddOrder, transformer: droppable());
on<LoginUser>(_onLoginUser, transformer: droppable());
// Listener außerhalb von Emitter: isClosed-Guard
stream.listen((data) {
if (!isClosed) emit(NewState(data)); // ✅ Kein emit nach dispose
});
Behobene Stellen (3 Leaks):
- Shop –
auth_bloc.dart:FirebaseAuth.instance.authStateChanges().listen()→StreamSubscription<User?>? _authStateSubscriptiongespeichert,close()Override mitcancel()hinzugefügt. - ERP –
app_bloc_providers.dart/nav_bar_bloc.dart:ConfigEventBus.stream.listen()war nicht gespeichert → Subscription inNavBarBlocverschoben (_configEventBusSubscription),close()Override mitcancel()hinzugefügt. Nicht mehr benötigte Imports entfernt. - ERP –
system_settings_bloc.dart:_pushNotificationSettingsSubscriptionwar gespeichert aber nie gecancelt (keinclose()Override) →close()Override mit_pushNotificationSettingsSubscription?.cancel()hinzugefügt.
Muster in allen Fixes:
// Subscription als Feld speichern
StreamSubscription<T>? _subscription;
// Im Konstruktor/Handler zuweisen
_subscription = stream.listen(...);
// In close() canceln
@override
Future<void> close() async {
await _subscription?.cancel();
return super.close();
}
🟢 NIEDRIGE FINDINGS¶
LOW-1-7: Informational Findings¶
- LOW-1: ✅ Fehlende Security.md Datei – BEHOBEN (07.04.2026)
- Vollständige SECURITY.md ergänzt mit allen Industry-Standard-Sektionen:
-
- Incident Response Procedure (P0-P3 Klassifizierung, 6-Stufen-Workflow, Data Breach Response nach GDPR Art. 33/34)
-
- Security Update & Patch Process (Emergency Hotfix bis Regular Updates, Staged Rollout für Mobile)
-
- Security Advisory History (ESA-2026-001, ESA-2026-002)
-
- Third-Party Security & Dependencies (Dependabot, npm audit, flutter pub outdated, Supply Chain Security)
-
- Compliance & Certifications (GDPR, OWASP Top 10 2021, OWASP MASVS, Google Cloud ISO/SOC-Zertifizierungen)
-
- Advanced Security Contact & PGP (Verschlüsselte Kommunikation, Bug Bounty Programm in Planung)
-
- Inhaltsverzeichnis aktualisiert (Sektionen 1-20)
- Datum aktualisiert auf 7. April 2026
- LOW-2: ✅ Veraltete Dependencies / Dependency Audit – BEHOBEN (24.02.2026)
flutter pub upgradein allen Paketen durchgeführt (shop_system, erp_system, shared)- shop_system: 13 Dependencies aktualisiert (u.a.
get_it9.2.0→9.2.1,google_fonts8.0.1→8.0.2,uuid4.5.2→4.5.3,json_annotation4.10.0→4.11.0) - erp_system: 26 Dependencies aktualisiert (u.a.
build_runner2.7.1→2.11.1,uuid4.5.2→4.5.3,ffi2.1.5→2.2.0,analyzer8.4.1→10.0.1) - shared: 2 Dependencies aktualisiert (
url_launcher_ios,url_launcher_web) - Verbleibende Major-Version-Upgrades (Breaking Changes) zur manuellen Migration dokumentiert:
flutter_secure_storage9.2.4 → 10.0.0 (beide Apps)local_auth2.3.0 → 3.0.0 (beide Apps)flutter_map7.0.2 → 8.2.2 (erp_system)google_fonts6.3.3 → 8.0.2 (erp_system)http1.2.2 → 1.6.0 (erp_system)js0.6.7 → 0.7.2 (discontinued – Migration zudart:js_interopnötig)
- LOW-3: ✅ Fehlende Penetration Test Dokumentation – BEHOBEN (07.04.2026)
- Vollständige Pentest-Dokumentation erstellt: penetration-testing.md
- Enthält:
- Pentest-Framework & Methodik (OWASP WSTG, MSTG, PTES)
- Geplanter Pentest (nach Kundengewinnung) mit Zeitplan-Framework
- Testing Scope (Web + Mobile Apps + Cloud Functions + Firestore/Storage Rules)
- Rules of Engagement (Testing-Fenster, Autorisierung, Kommunikation)
- Finding Template (CVSS-basiert, Severity Classification)
- Remediation Workflow & Retest Policy
- Historical Results Section (vorbereitet für zukünftige Pentests)
- Referenz zu bestehendem OWASP Code-Audit (35/37 Findings behoben)
- LOW-4: ✅ Unvollständige Security Headers (Web) – BEHOBEN durch HIGH-11 (24.02.2026)
- LOW-5: ✅ Fehlende Content-Security-Policy – BEHOBEN durch HIGH-11 (24.02.2026)
- LOW-6: ✅ Keine Responsible Disclosure Policy – BEHOBEN (07.04.2026)
- Vollständige Responsible Disclosure Policy in SECURITY.md Sektion 12 implementiert:
- Meldewege (E-Mail security@easysale.de + GitHub Security Advisories)
- Response-Zeiten nach Schweregrad (CRITICAL < 24h bis LOW < 14 Tage)
- Scope Definition (In-Scope vs. Out-of-Scope)
- Koordinierte Offenlegung (Coordinated Disclosure)
- Anerkennung für verantwortungsvolle Meldung
- LOW-7: ✅ Fehlende Security Training Dokumentation – BEHOBEN (07.04.2026)
- Vollständiges Security Training Framework erstellt: security-training.md
- 10 Hauptsektionen:
- Überblick & Training-Struktur (Onboarding, Quarterly Reviews, Ad-hoc)
- Onboarding Security Training (2-Tages-Programm mit Hands-on)
- OWASP Top 10 2021 Developer Guide (alle 10 Kategorien mit easySale-Beispielen)
- Secure Coding Guidelines (Flutter/Dart + Node.js Best Practices)
- Mobile Security OWASP MASVS (STORAGE, CRYPTO, AUTH, NETWORK, RESILIENCE)
- Firebase & Cloud Security (Firestore Rules, Cloud Functions, Secrets Management)
- Incident Response Training (Melde-Workflow, Checklisten, Muster-E-Mails)
- Security Tools & Resources (git-secrets, Dependabot, OWASP-Links, Zertifizierungen)
- Quarterly Security Awareness (Q1-Q4 2026 Zeitplan)
- Compliance & Zertifizierungen (Security Champion Program)
- Code-Beispiele für jedes OWASP-Finding (❌ FALSCH vs. ✅ RICHTIG)
- Onboarding-Checkliste für neue Team-Mitglieder
- Tägliche Security-Praktiken & PR-Checklisten
✅ POSITIVE FINDINGS (Best Practices)¶
1. Rate Limiting in beiden Systemen ✅¶
// Shop + ERP nutzen RateLimiterService aus shared-Package
if (!RateLimiterService.canAttemptLogin(event.email)) { /*...*/ }
// RateLimiterService ist jetzt offiziell über shared.dart exportiert
2. FlutterSecureStorage für Credentials ✅¶
// Passwörter werden verschlüsselt gespeichert
await storage.write(key: 'saved_password', value: password);
3. Token Refresh Logik ✅¶
4. SecureLogger in beiden Systemen ✅¶
// Sensible Daten werden nicht geloggt - jetzt Shop + ERP
SecureLogger.debug('✅ FCM Token retrieved successfully'); // Token selbst nie geloggt
SecureLogger.debug('Device ID: ${SecureLogger.sanitize(deviceId)}'); // IDs sanitized
// SecureLogger ist jetzt offiziell über shared.dart exportiert
5. Firestore Query Timeouts ✅¶
// Extension für sichere Queries
extension FirestoreExtensions on Query {
Future<QuerySnapshot> getSafe({Duration? timeout}) {/*...*/}
}
6. SSL Pinning Service vorhanden ✅¶
7. Multi-Tenancy-Aware Design ✅¶
8. Biometrische Authentifizierung ✅ (ERP + Shop)¶
// Shop: UI-Trigger via BlocConsumer in responsive_sign_in_form.dart
context.read<AuthBloc>().add(AuthenticateWithBiometric());
// Shared: BaseAuthService
final authenticated = await authService.authenticateWithBiometrics(
localizedReason: 'Bitte authentifizieren Sie sich für den Zugang zur App',
);
9. Firebase Storage URL-Validierung ✅ NEU¶
// Alle Downloads validiert gegen Firebase Storage Domains
FirebaseStorageUrlValidator.validate(document.downloadUrl);
// Schützt vor: Path Traversal, externe URLs, HTTP-Downgrade
10. Session Management aktiv in beiden Apps ✅ NEU¶
// Lifecycle-aware Session Validation
authService.startPeriodicTokenRefresh(); // alle 4 Minuten
authService.validateAndRefreshSession(); // bei App Resume
// Automatischer Logout nach 12h Session-Timeout
11. HMAC-SHA256 Request Signing ✅ NEU¶
// Dio HTTP-Requests werden automatisch signiert:
dio.interceptors.add(RequestSigningInterceptor(appId: 'easysale_shop'));
// X-Request-Timestamp / X-Request-Nonce / X-Request-Signature
// Cloud Function Payloads:
final signedData = await _requestSigning.signCloudFunctionPayload(
functionName: functionName, data: data,
);
// Replay-Schutz: ±5-Minuten-Fenster, einmalige Nonce, Auth-Token-gebunden
12. Transport Security (3-Layer Defense-in-Depth) ✅ NEU¶
<!-- Android: network_security_config.xml -->
<base-config cleartextTrafficPermitted="false">
<trust-anchors><certificates src="system" /></trust-anchors>
</base-config>
<!-- iOS: Info.plist -->
<!-- NSAllowsArbitraryLoads = false, NSExceptionMinimumTLSVersion = TLSv1.2 -->
// Dart: TransportSecurityService
TransportSecurityService.initialize(); // blockiert HTTP auf App-Ebene
TransportSecurityService.requireHttps(url); // URL-Validator vor Requests
📊 OWASP TOP 10 MAPPING¶
| OWASP Category | Findings | Severity | Status |
|---|---|---|---|
| A01: Broken Access Control | CRIT-1, CRIT-4, HIGH-1 | 🔴 Kritisch | ✅ Vollständig behoben |
| A02: Cryptographic Failures | ✅ CRIT-2, ✅ CRIT-5, ✅ HIGH-8, ✅ MED-5 | 🟢 Niedrig | ✅ Vollständig behoben (CRIT-2 ✅, CRIT-5 ✅, HIGH-8 ✅, MED-5 ✅) |
| A03: Injection | ✅ CRIT-3, ✅ MED-2 | 🟢 Niedrig | ✅ Vollständig behoben |
| A04: Insecure Design | ✅ HIGH-1, ✅ MED-1, ✅ MED-3 | ✅ Vollständig | ✅ Alle behoben |
| A05: Security Misconfiguration | ✅ MED-9, ✅ LOW-4, ✅ LOW-5 | 🟢 Niedrig | ✅ Vollständig behoben |
| A06: Vulnerable Components | ✅ LOW-2 | 🟢 Niedrig | ✅ Dependency Upgrade durchgeführt (24.02.2026) |
| A07: Auth Failures | ✅ HIGH-4, ✅ HIGH-5, ✅ MED-4 | 🟢 Niedrig | ✅ Vollständig implementiert (beide Systeme) |
| A08: Data Integrity | ✅ HIGH-3, ✅ HIGH-7, ✅ MED-3, ✅ MED-11 | ✅ Vollständig | ✅ Alle behoben |
| A09: Logging Failures | ✅ HIGH-2 | 🟢 Niedrig | ✅ Implementiert |
| A10: SSRF | - | - | ✅ Not Applicable |
🎯 PRIORISIERTER ACTION PLAN¶
Phase 1: SOFORT (0-1 Woche) 🔥¶
- CRIT-1: Firestore Security Rules härten (Shop-System)
- CRIT-2: E-Mail-Speicherung auf FlutterSecureStorage umstellen
- ~~HIGH-2: Debug-Logs bereinigen~~ → ✅ ERLEDIGT
Estimated Effort: 1-2 Entwicklertage (reduziert)
Phase 2: HOCH (2-4 Wochen) 🟠¶
- CRIT-3: Input Validation Service implementieren
- ~~CRIT-4: Zentrale Auth-Helper Klasse~~ → ✅ ERLEDIGT
- ~~CRIT-5: SSL Pinning vollständig implementieren~~ → ✅ ERLEDIGT 24.02.2026
- ~~HIGH-7: Request Signing~~ → ✅ ERLEDIGT
- ~~HIGH-1: Rate Limiting im Shop-System~~ → ✅ ERLEDIGT
- ~~HIGH-3: Secure Download Service~~ → ✅ ERLEDIGT
- ~~HIGH-4: Session Management~~ → ✅ ERLEDIGT
- ~~HIGH-8: Insufficient Transport Security~~ → ✅ ERLEDIGT
- ~~HIGH-11: Security Headers (Web)~~ → ✅ ERLEDIGT
Estimated Effort: ~1 Sprint (reduziert da HIGH-1/3/4/7/8/11 erledigt)
Phase 3: MITTEL (1-3 Monate) 🟡¶
- MED-1 bis MED-13: Alle mittleren Findings
- Code Reviews etablieren
- Security Testing in CI/CD Pipeline
- Penetration Testing durchführen
Estimated Effort: 1-2 Monate
Phase 4: KONTINUIERLICH 🔄¶
- Alle LOW-Findings behoben ✅ (LOW-1/2/3/4/5/6/7 alle erledigt)
- ~~LOW-2: Veraltete Dependencies~~ → ✅ ERLEDIGT
flutter pub upgradedurchgeführt (24.02.2026)
- ~~LOW-2: Veraltete Dependencies~~ → ✅ ERLEDIGT
- Regelmäßige Dependency Audits
- Quartalsweise Security Reviews
- Security Training für das Team
🔧 EMPFOHLENE TOOLS & SERVICES¶
Code Analysis¶
- flutter_lints - Linting Rules ✅ (bereits aktiv)
- Snyk - Vulnerability Scanner
- OWASP Dependency-Check - Dependency Audit
- SonarQube - Code Quality & Security
Runtime Protection¶
- Firebase App Check - Bot Protection
- Google Cloud Armor - DDoS Protection
- ProGuard/R8 - Code Obfuscation (Android)
Testing & Monitoring¶
- OWASP ZAP - Penetration Testing
- Burp Suite - API Security Testing
- Firebase Crashlytics - Runtime Monitoring ✅ (bereits aktiv)
- Sentry - Error Tracking
📝 COMPLIANCE & STANDARDS¶
DSGVO Compliance¶
- ✅ Passwort-Verschlüsselung
- ⚠️ E-Mail in Plaintext (CRIT-2)
- ⚠️ Logs mit personenbezogenen Daten (HIGH-2)
- ⚠️ Fehlende Data Retention Policies
OWASP MASVS (Mobile)¶
- MSTG-STORAGE: Teilweise erfüllt (FlutterSecureStorage ✅, SharedPreferences ⚠️)
- MSTG-CRYPTO: Teilweise erfüllt (SSL Pinning fehlt)
- MSTG-AUTH: Gut erfüllt (Rate Limiting ✅, Session Management ⚠️)
- MSTG-NETWORK: Teilweise erfüllt (Certificate Pinning missing)
- MSTG-RESILIENCE: Nicht erfüllt (No Jailbreak Detection)
📞 NEXT STEPS¶
Immediate Actions¶
- Security Team Meeting - Priorisierung der Findings
- Emergency Fixes - CRIT-1, CRIT-2 sofort beheben
- Firestore Rules Deployment - Neue Rules in Staging testen
Short Term¶
- Sprint Planning - HIGH Findings in nächste 2-3 Sprints
- Code Review Process - Security Checklist etablieren
- Developer Training - OWASP Top 10 Workshop
Long Term¶
- Security Roadmap - Quartalsweise Security Audits
- Penetration Testing - Externe Security Firma engagieren
- Bug Bounty Program - Community-basierte Security
📄 ZUSAMMENFASSUNG¶
Kritische Erkenntnisse¶
- Shop-System Security Rules sind zu permissiv - Höchste Priorität
- Credential Storage teilweise unsicher - E-Mail muss verschlüsselt werden
- Input Validation fehlt system-weit - NoSQL Injection Risiko
- SSL Pinning nicht vollständig - MITM Angriffe möglich
- ~~Rate Limiting nur im ERP~~ - Shop-System ungeschützt → ✅ BEHOBEN
Positive Aspekte¶
- ERP-System hat starke Security-Foundations (Rate Limiting, SecureLogger)
- FlutterSecureStorage wird richtig genutzt (Passwörter)
- Multi-Tenancy Design ist prinzipiell vorhanden
- Firestore Extensions für sichere Queries existieren
- ✅ Biometrische Authentifizierung vollständig implementiert (HIGH-5)
Implementierungsfortschritt¶
- ✅ CRIT-4: currentUser ohne Null-Check (14+ Stellen) - IMPLEMENTIERT am 24. Februar 2026
- ✅ HIGH-1: Rate Limiting Shop-System - IMPLEMENTIERT am 24. Februar 2026
- ✅ HIGH-2: Token aus Debug-Logs - IMPLEMENTIERT am 24. Februar 2026
- ✅ HIGH-3: File Download Validierung - IMPLEMENTIERT am 24. Februar 2026
- ✅ HIGH-4: Session Timeout (beide Systeme) - IMPLEMENTIERT am 24. Februar 2026
- ✅ HIGH-5: Biometric Auth (beide Systeme) - IMPLEMENTIERT am 24. Februar 2026
- ✅ HIGH-7: Request Signing HMAC-SHA256 - IMPLEMENTIERT am 24. Februar 2026
- ✅ HIGH-8: Insufficient Transport Security (Android NSC + iOS ATS + Dart HttpOverrides) - IMPLEMENTIERT am 24. Februar 2026
- ✅ MED-4: Biometric Integration (beide Systeme) - IMPLEMENTIERT
- ✅ MED-1: Fehlende Error Boundary –
AppErrorHandlerim shared-Package – IMPLEMENTIERT am 24. Februar 2026 - Status: 30 von 37 Findings behoben (81.1%)
- Trend: 🟢 Positiv - Security wird aktiv und schnell verbessert
Gesamt-Risiko-Bewertung¶
MITTEL - Kritische Findings in A01/A02/A03 erfordern noch Maßnahmen. HIGH-Findings (A07, A08, A09, A04) wurden vollständig behoben. Mit den empfohlenen weiteren Fixes wird System auf NIEDRIG Risiko reduziert.
Update 24.02.2026 (Initial): HIGH-5 (Biometric Auth ERP) und HIGH-1/2/3/4 implementiert. Gesamt-Risiko von HOCH auf MITTEL reduziert. ✅
Update 24.02.2026 (Folge-Sprint): HIGH-5 Shop-System vollständig implementiert – AuthBloc-Events/-States, Handler via AuthService.authenticateWithBiometrics(), Fingerprint-Button-UI in responsive_sign_in_form.dart. A07 nun vollständig in beiden Systemen abgedeckt. ✅
Update 24.02.2026 (Request Signing): HIGH-7 implementiert – RequestSigningService (HMAC-SHA256, shared-Package), RequestSigningInterceptor (Dio, Shop-System), Cloud Function Payload Signing (ERP). Replay-Schutz durch ±5-Minuten-Fenster + Nonce. A08 weiter gestärkt. ✅
Update 24.02.2026 (Transport Security): HIGH-8 implementiert – Android network_security_config.xml (cleartext blockiert, User-CAs nur in Debug), iOS NSAppTransportSecurity (TLS 1.2+, ForwardSecrecy, kein NSAllowsArbitraryLoads), Dart TransportSecurityService (HttpOverrides, URL-Validator). Defense-in-Depth Transport Security vollständig. ✅
- OWASP A07 (Auth): 🟢 Niedrig — alle 3 relevanten Findings implementiert
- OWASP A09 (Logging): 🟢 Niedrig — HIGH-2 implementiert
- OWASP A08 (Integrity): ✅ Vollständig — HIGH-3 + HIGH-7 + MED-11 + MED-3 ✅ alle behoben
Update 24.02.2026 (Error Boundaries): MED-1 implementiert – AppErrorHandler im shared-Package (FlutterError.onError + PlatformDispatcher.instance.onError + Release-ErrorWidget.builder + runZonedGuarded-Helfer). Beide Apps in einer Zeile integriert. Firestore-Timeout-Extensions (getWithTimeout/snapshotsWithTimeout) vollständig ausgerollt. A04 weiter gestärkt. ✅
- OWASP A04 (Design): ✅ Vollständig — HIGH-1 + MED-1 + MED-3 + MED-6 + MED-7 ✅ alle behoben
- OWASP A02 (Cryptographic Failures): � Niedrig — HIGH-8 ✅, CRIT-2 ✅, CRIT-5 ✅ — vollständig behoben
- OWASP A01 (Access Control): � Niedrig — CRIT-1 ✅, CRIT-4 ✅ — vollständig behoben
11. AuthHelper – Sicherer Firebase Auth-Zugriff ✅ NEU¶
// Verhindert App-Crashes und Silent-Null-Queries (CWE-476)
final email = AuthHelper.getRequiredEmail(); // wirft UnauthorizedException
final uid = AuthHelper.getRequiredUid(); // wirft UnauthorizedException
- OWASP A02/A03 (Crypto/Injection): ✅ Vollständig behoben — CRIT-2 ✅, CRIT-3 ✅, CRIT-5 ✅
Dieser Bericht ist vertraulich und nur für interne Nutzung bestimmt.
Für Fragen: security@easysale.com
📚 ANHANG: CODE-BEISPIELE¶
Anhang A: Sichere Firestore Rules (Komplett)¶
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// ... (siehe CRIT-1 für vollständige Rules)
}
}
Anhang B: Input Validator (Vollständig)¶
Anhang C: Secure Logger Service¶
Audit durchgeführt am 24. Februar 2026
Nächstes Audit empfohlen: Mai 2026