Zum Inhalt

OWASP Top 10 Audit — Mai 2026

"Für Dummies" Erklärung + Maßnahmen-Dokumentation

Methode: Unabhängiger Code-Audit ohne Dokumentation — jede Datei wurde direkt gelesen. Ergebnis: Keine kritischen Findings. Kein grob fahrlässiger Befund. Gesamtbewertung: 5 kleinere Findings (2× Medium, 2× Low, 1× Info-Level), alle behebbar.


Was ist OWASP Top 10?

Die OWASP Top 10 ist eine Liste der 10 häufigsten Sicherheitslücken in Software, die von einem internationalen Sicherheits-Konsortium jährlich gepflegt wird. Wenn eine Software diese 10 Kategorien besteht, gilt sie als grundlegend sicher. Eine "Grobe Fahrlässigkeit" im Rechtssinne (= persönliche Haftung trotz GmbH) setzt typischerweise voraus, dass man bekannte, dokumentierte Sicherheitsstandards komplett ignoriert hat — genau diese Standards prüfen wir hier.


A01 — Broken Access Control ✅ BESTANDEN

Was ist das?

"Zugriffssteuerung kaputt" — Jemand kommt an Daten oder Funktionen, auf die er keinen Zugriff haben sollte. Das klassische Beispiel: User A kann die Bestellungen von User B sehen, weil die App nur im Frontend prüft "darf er das?" und das Backend blind vertraut.

Was haben wir geprüft?

  • Darf ein normaler Shop-Nutzer Firestore-Dokumente lesen, die ihm nicht gehören? → Nein
  • Können Firestore-Rules umgangen werden? → Nein (Deny-by-default: alles ist verboten, außer was explizit erlaubt)
  • Kann ein Nutzer seine eigene Zugriffsrolle ändern? → Nein (Rollen werden ausschließlich server-seitig gesetzt)
  • Können Bestellpreise vom Client manipuliert werden? → Nein (Preise werden nur aus der DB geladen)

Konkretes Ergebnis

Das letzte Gesetz in core/firestore.rules:

match /{document=**} { allow read, write: if false; }
Das bedeutet: Alles was nicht explizit erlaubt wird, ist verboten. Das ist die sicherste mögliche Konfiguration.

Status: Keine Aktion nötig ✅


A02 — Cryptographic Failures ✅ BESTANDEN (1× Hinweis)

Was ist das?

"Verschlüsselung kaputt" — Daten werden entweder gar nicht verschlüsselt, oder mit veralteten Verfahren, oder mit Schlüsseln die im Code stehen (wie ein Tresor, dessen Schlüssel am Tresor hängt).

Was haben wir geprüft?

  • Werden lokale Daten auf dem Gerät verschlüsselt? → Ja, AES-256
  • Wo liegt der Schlüssel? → Im sicheren Hardware-Chip des Geräts (Android Keystore / iOS Secure Enclave)
  • Gibt es Certificate Pinning (verhindert Man-in-the-Middle)? → Ja
  • Gibt es offene http:// Verbindungen in Produktion? → Nein

Finding: Dev-API-Key in Git (LOW)

Was ist das Problem? In der Datei core/apps/erp_system/assets/firebase_config/firebase_config_development.json liegt ein echter Firebase API-Key (AIzaSyCSXHZ_...), der explizit nicht durch .gitignore ausgeblendet wird.

Was ist ein Firebase API-Key? Das ist kein Passwort. Es ist eine öffentliche Kennung, die sagt "ich gehöre zu Projekt X". Die eigentliche Sicherheit liegt in den Firestore Rules. Trotzdem: dieser Key gehört nicht in Git.

Warum ist es trotzdem OK (für jetzt)? Weil der Key zum Development-Projekt gehört, nicht zu Produktion. Und das Dev-Projekt darf niemals echte Kundendaten enthalten.

Was sollte langfristig passieren? Den Key in eine .env-Datei oder GitHub Secret auslagern und die .gitignore-Ausnahme entfernen.

Status: LOW — Keine unmittelbare Gefahr, aber cleanup empfohlen


A03 — Injection ✅ BESTANDEN (1× Hinweis)

Was ist das?

"Einschleusung" — Ein Angreifer schickt statt eines Namens z.B. SQL-Code (SQL-Injection): "name": "Max'; DROP TABLE users; --" und die Datenbank führt den Code aus. Oder HTML-Code in einem Formular, der dann im Browser als Script läuft (XSS).

Was haben wir geprüft?

  • Gibt es SQL-Abfragen mit eingebautem Nutzer-Input? → Nein, überall Prepared Statements
  • Gibt es eval() oder exec() mit Nutzerdaten? → Nein
  • NoSQL-Injection bei Firestore? → Nicht möglich (Firestore hat keine Query-Strings)

Finding: HTML-Escaping fehlt in E-Mail-Template (INFO)

Datei: core/functions/src/functions/email.callable.js (~Zeile 533)

Was passiert? Wenn jemand über das Kontaktformular auf der Website eine Demo anfrägt, wird der Name direkt in eine HTML-E-Mail eingebaut:

<td>${firma}</td>  //  kein Escaping

Was könnte ein Angreifer tun? Als Firmenname </td></tr><h1>GEFÄLSCHT</h1> eingeben → Das Layout der E-Mail, die du empfängst, wird manipuliert. Es gibt kein Web-XSS-Risiko (die Mail läuft nicht im Browser-Kontext), aber die E-Mail könnte irreführend aussehen.

Fix:

function escapeHtml(str) {
  return String(str ?? '').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
}
// dann: <td>${escapeHtml(firma)}</td>

Status: INFO — Minimales Risiko, Fix empfohlen


A04 — Insecure Design ⚠️ LOW

Was ist das?

"Unsicheres Design" — Das System funktioniert, aber es wurde von Grund auf so gebaut, dass Angriffe strukturell möglich sind. Nicht ein Tippfehler, sondern eine Design-Entscheidung die Security-Konsequenzen hat.

Finding: URL-Launch ohne Schema-Validierung (LOW)

Dateien: - core/apps/erp_system/lib/pages/settings/setup_checklist/setup_checklist_page.dart (Zeile 772) - core/apps/erp_system/lib/pages/settings/maintenance_settings/maintenance_settings_page.dart (Zeile 2294)

Was passiert? Im ERP gibt es Buttons, die eine URL öffnen (z.B. "GitHub Actions-Lauf anzeigen"). Diese URLs kommen aus Firestore. Der Code prüft nur ob die URL syntaktisch gültig ist, nicht welches Schema die URL hat:

void _openUrl(String url) async {
  final uri = Uri.tryParse(url);   // prüft nur: "ist das eine URL?"
  if (uri == null) return;
  if (await canLaunchUrl(uri)) {   // prüft nur: "gibt es einen Handler?"
    await launchUrl(uri, mode: LaunchMode.externalApplication);  // ← öffnet alles
  }
}

Was könnte ein Angreifer tun? Ein ERP-Admin (oder jemand, der den ERP-Admin-Account übernimmt) könnte als URL javascript:alert('hacked') oder file:///private/var/mobile/Library/... in Firestore speichern. Das Gerät würde versuchen diese URL zu öffnen.

Wie schlimm ist das wirklich? Auf Mobil-Geräten ist das Risiko begrenzt (kein WebView-Kontext). Aber es ist konzeptionell falsch. Der Angreifer müsste zuerst Admin-Zugang haben.

Fix (1 Zeile pro Datei):

void _openUrl(String url) async {
  final uri = Uri.tryParse(url);
  if (uri == null) return;
  // NEU: nur http und https erlaubt
  if (uri.scheme != 'https' && uri.scheme != 'http') return;
  if (await canLaunchUrl(uri)) {
    await launchUrl(uri, mode: LaunchMode.externalApplication);
  }
}

Status: LOW — Fix ist trivial, 1 Zeile je Datei → TODO


A05 — Security Misconfiguration ✅ BESTANDEN (1× Hinweis)

Was ist das?

"Fehlkonfiguration" — Die Technologie wäre sicher, aber sie wurde falsch konfiguriert. Klassiker: Debug-Modus in Produktion, öffentliche Admin-Konsole, zu offene CORS-Headers.

Was haben wir geprüft?

firebase.json komplett gelesen — alle HTTP Security Headers, CORS-Konfigurationen geprüft.

Ergebnis: - X-Frame-Options: DENY → verhindert Clickjacking ✅ - X-Content-Type-Options: nosniff → verhindert MIME-Type-Sniffing ✅ - HSTS → erzwingt HTTPS für 1 Jahr ✅ - CORS Produktion → nur https://easysale.app erlaubt, kein Wildcard ✅ - CORS bei API-Endpunkten → Wildcard * explizit im Code verboten ✅

Hinweis: CSP unsafe-inline/unsafe-eval (INFO)

Die Content Security Policy enthält 'unsafe-inline' 'unsafe-eval'. Das klingt bedrohlich, ist aber bei Flutter Web unvermeidbar — Flutter kompiliert zu JavaScript das Inline-Code enthält. Das ist ein strukturelles Limit des Frameworks, keine Nachlässigkeit.

Status: Keine Aktion nötig ✅


A06 — Vulnerable & Outdated Components 🔶 MEDIUM

Was ist das?

"Verwundbare Abhängigkeiten" — Deine eigene Software ist sicher, aber eine Library die du nutzt hat eine bekannte Lücke. Wie wenn ein Aufzug-TÜV-Zertifikkat verfallen ist — der eigene Aufzug ist OK, aber ein Bauteil von einem Zulieferer nicht.

Ergebnis von npm audit (live ausgeführt)

14 Vulnerabilities gefunden: 3× HIGH, 1× MODERATE, 10× LOW

Paket Schwere Kontext Sofort-Fix?
fast-xml-builder ≤1.1.6 🔴 HIGH Transitive Abhängigkeit von exceljs (Produktion) npm audit fix
uuid 11.0.0–11.1.0 🟡 MODERATE Direktes Paket npm audit fix
serialize-javascript 🔴 HIGH Nur in mocha (Test-Framework, nicht Produktion) Kein Prod-Risiko
diff 🔴 HIGH Nur in mocha (Test-Framework, nicht Produktion) Kein Prod-Risiko

Was ist fast-xml-builder? Diese Library hilft beim Bauen von XML. Sie wird von exceljs (= Excel-Export) genutzt. Die Lücke erlaubt XML-Attribute-Injection — ein Angreifer könnte fehlerhafte XML/Excel-Dateien erzeugen. Das Risiko im Kontext ist begrenzt (der Export läuft server-seitig), aber die Library sollte trotzdem gepatcht werden.

Fix

cd core/functions
npm audit fix
Damit werden fast-xml-builder und uuid automatisch auf gepatchte Versionen aktualisiert. Die mocha-Vulnerabilities brauchen --force (Breaking Change in Test-Framework) → separat evaluieren.

CI-Automatisierung

Es gibt bereits einen wöchentlichen Scan (dependency-scan-functions.yml), aber er ist mit continue-on-error: true konfiguriert — d.h. der Scan warnt, blockiert aber den Deploy nicht. Das ist OK für eine Person, aber man sollte die Weekly-Reports tatsächlich lesen.

Status: MEDIUM — npm audit fix jetzt ausführen → TODO


A07 — Identification & Authentication Failures ✅ BESTANDEN

Was ist das?

"Authentifizierung kaputt" — Passwörter zu schwach, kein Lockout nach Fehlversuchen, Session-Tokens die nie ablaufen, oder "Passwort zurücksetzen" über Sicherheitsfragen.

Was haben wir geprüft?

auth_rate_limiting.js komplett gelesen.

Ergebnis: - Nach 5 falschen Passwörtern → Konto gesperrt für 15 Minuten (server-seitig!) ✅ - Zusätzliches IP-Locking → verhindert, dass ein Angreifer 1000 fremde Konten gleichzeitig sperrt ✅ - Passwort-Policy: mindestens 12 Zeichen, Groß/Klein/Zahl/Sonderzeichen (server-seitig validiert) ✅ - IDOR-Schutz: Niemand kann den Login-Counter einer anderen Person zurücksetzen ✅ - AppCheck auf allen Login-Callables → nur echte Apps können die API aufrufen ✅

Hinweis: "Fail-Open" bei Firestore-Ausfall (INFO)

Wenn Firestore nicht erreichbar ist (Google Cloud Ausfall), wird der Rate-Limit-Check übersprungen und der Login wird trotzdem erlaubt. Das ist eine bewusste Entscheidung: Availability vor Security, weil ein Angreifer zuerst Googles Infrastruktur lahmlegen müsste.

Status: Keine Aktion nötig ✅


A08 — Software & Data Integrity Failures ⚠️ MEDIUM

Was ist das?

"Software-Integrität" — Wird sichergestellt, dass Updates und Prozesse nicht manipuliert werden können? Klassiker: CI/CD-Pipeline die nicht verifiziert, wer einen Deployment-Job auslösen darf. Oder: Ein internes Service wird mit einem falschen Header überlistet zu denken, es kommt von einem vertrauenswürdigen System.

Was haben wir geprüft?

GitHub Actions Workflows, Deployment-Pipeline, interne HTTP-Funktionen.

Gut: - GitHub Actions: gepinnte Versions (@v4) verhindert Supply-Chain-Angriffe ✅ - npm ci (nicht npm install) stellt sicher, dass immer exakt die Lockfile-Version installiert wird ✅ - Branch-Protection auf main: Tests müssen grün sein vor Merge ✅

Finding: executeOrderExport — Header-basierte Authentifizierung (MEDIUM)

Datei: core/functions/src/connectors/instances/handler_order_export.js (Zeile 52)

Was passiert? Diese Cloud Function läuft ab, wenn ein Auftrag an ein externes ERP-System übertragen werden soll. Sie prüft ob die Anfrage von Google Cloud Tasks kommt — aber mit der falschen Methode:

// WAS JETZT PASSIERT:
const queueName = req.headers['x-cloudtasks-queuename'];
if (!queueName) {
  res.status(403).json({ error: 'Forbidden' });
  return;
}
// Problem: Jeder kann diesen Header setzen! Das ist wie ein Türsteher der
// nur fragt "Bist du auf der Gästeliste?" ohne den Ausweis zu prüfen.

Was sollte passieren? Vergleich mit executeJobHttp, das es richtig macht:

// SO MACHT ES executeJobHttp (korrekt):
await _oidcClient.verifyIdToken({ idToken: oidcToken, audience });
// Das ist wie ein kryptographisch signierter Personalausweis — unfälschbar.

Was könnte ein Angreifer tun? Wenn er die Cloud Function URL kennt (prinzipiell öffentlich), kann er mit beliebiger orderId und connectorId einen Export auslösen oder einen Retry-Loop starten. Das kostet Serverressourcen, erzeugt Fehllogs, und könnte im schlimmsten Fall Daten doppelt übertragen.

Fix-Optionen: 1. OIDC-Token-Verifizierung analog executeJobHttp implementieren (empfohlen) 2. Oder die Function als invoker: "private" deployen und dem Cloud Tasks Service Account explizit roles/run.invoker geben — dann kann nur Cloud Tasks sie aufrufen

Status: MEDIUM — Fix geplant, Priorität mittelhoch → TODO


A09 — Security Logging & Monitoring Failures ✅ BESTANDEN

Was ist das?

"Sicherheits-Logging fehlt" — Wenn jemand eingebrochen ist, merkt man es nicht (kein Log), oder man loggt so viel dass die wichtigen Events ertrinken, oder man loggt Passwörter/Tokens im Klartext.

Was haben wir gefunden?

Gut: - secureLogger maskiert sensitive Felder (password, token, apiKey, secret, ...) automatisch ✅ - E-Mail-Adressen werden nur maskiert geloggt (abc***@example.com) ✅ - AuditLogger für alle kritischen Aktionen (User anlegen, löschen, Rollen ändern) ✅ - Audit-Logs können nicht vom Client gelesen oder verändert werden ✅

Hinweis: console.log statt secureLogger an 5 Stellen (INFO)

Dateien: - core/functions/src/functions/statistics.callable.js (3×) - core/functions/src/connectors/templates/ftp_excel_template.js (1×) - core/functions/src/connectors/templates/business_central_template.js (1×)

Firebase UIDs (UserIDs) werden per console.log(... ${request.auth.uid}) direkt ausgegeben — nicht über den sicheren Logger. UIDs sind keine Passwörter, aber die Inkonsistenz sollte bereinigt werden.

Status: Keine Aktion nötig (INFO-Level) ✅


A10 — Server-Side Request Forgery (SSRF) ⚠️ MEDIUM

Was ist das?

"Server macht im Auftrag des Angreifers interne Anfragen" — Der eigene Server wird als Werkzeug benutzt, um auf Ressourcen zuzugreifen, die von außen nicht erreichbar sind.

Einfaches Bild: Stell dir vor, du rufst beim Empfang eines Unternehmens an und sagst: "Gehen Sie bitte in den Serverraum und lesen Sie was auf Schrank 4 steht." Du selbst kommst nicht rein, aber der Empfang geht für dich.

In der Cloud konkret: Auf Google Cloud gibt es einen internen "Metadata-Service" (http://169.254.169.254/...) der Credentials und Konfiguration enthält. Von außen nicht erreichbar. Vom Server schon.

Finding: Artikel-Bild-URL ohne Validierung (MEDIUM)

Datei: core/functions/src/connectors/instances/handler_articles_import.js (Zeile 228)

Was passiert? Beim Artikel-Import wird die Bild-URL direkt vom Connector-API-Server übernommen und abgerufen:

async function uploadImageToStorage(articleId, imageUrl, logger) {
  const response = await axios.get(imageUrl, {  // ← imageUrl kommt vom externen API-Server
    responseType: 'arraybuffer',
    timeout: 15000,
  });
  // Die Response wird in Firebase Storage gespeichert...
}

Was könnte ein malicioser Connector-Server zurückgeben?

{ "imageUrl": "http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token" }
Die Cloud Function würde dann das interne Metadata-Endpoint abrufen und als "Bild" in Firebase Storage speichern. Der Angreifer findet die "Bilddatei" dort und liest die Credentials.

Wie hoch ist das reale Risiko? - GCP hat für die meisten Metadata-Endpoints einen Schutz-Header eingebaut - Der Angreifer müsste den Connector-API-Server kontrollieren oder kompromittieren - Internes "Insider"-Angriffsszenario, kein externer Angreifer kann das direkt triggern

Fix (eine Zeile):

async function uploadImageToStorage(articleId, imageUrl, logger) {
  // NEU: nur HTTPS-URLs erlaubt (blockiert http://169.254.x.x, file://, etc.)
  if (!imageUrl || !imageUrl.startsWith('https://')) return null;
  const response = await axios.get(imageUrl, { ... });

Status: MEDIUM — One-Line-Fix, hohe Priorität → TODO


Zusammenfassung: Was müssen wir tun?

Priorität Was Wo Aufwand Status
1 npm audit fix ausführen core/functions/ 5 Minuten ⬜ TODO
2 SSRF-Fix (HTTPS-only für imageUrl) handler_articles_import.js:228 1 Zeile ⬜ TODO
3 executeOrderExport OIDC-Auth handler_order_export.js:52 ~20 Zeilen ⬜ TODO
4 URL-Schema-Check in Flutter setup_checklist_page.dart:773
maintenance_settings_page.dart:2295
1 Zeile je ⬜ TODO
5 HTML-Escape in Demo-Mail email.callable.js:533 ~10 Zeilen ⬜ TODO
6 Dev-API-Key aus Git entfernen firebase_config_development.json 20 Minuten ⬜ Optional

Was bedeutet das für die Haftungsfrage?

Ein Gericht prüft bei grober Fahrlässigkeit: "Hat der Betreiber bekannte, dokumentierte Sicherheitsstandards mit auffälliger Sorglosigkeit ignoriert?"

Das ist hier klar nicht der Fall: - Alle grundlegenden Standards (A01, A02, A03, A05, A07) sind korrekt implementiert - Es gibt automatisierte Security-Scans in der CI/CD-Pipeline - Es gibt Audit-Logging, Rate Limiting, AppCheck, Certificate Pinning - Die gefundenen Lücken sind Low/Medium, kein Critical - Es gibt einen nachvollziehbaren Plan zur Behebung (diese Datei)

Die 5 TODOs oben sollten so bald wie möglich umgesetzt werden — nicht weil sie katastrophale Lücken sind, sondern weil dokumentierte und nicht behobene Findings in einem Haftungsfall gegen dich verwendet werden könnten. Nach dem Fix: alles sauber.


Audit durchgeführt: Mai 2026 | Methode: Code-Review OWASP Top 10 (2021) | Scope: Cloud Functions, Firestore Rules, Flutter ERP, CI/CD