Multi-Tenant-Leckage: Wenn "Row-Level Security" in SaaS versagt

Im Bereich Software-as-a-Service (SaaS) gibt es keinen katastrophaleren Vorfall als “Data Bleed”. Es ist der “Code Red” der Branche—ein katastrophaler Ausfall der Multi-Tenant-Isolation, bei dem Kunde A sich anmeldet und die sensiblen Finanzdaten, PII oder privaten Strategien von Kunde B sieht.
Seit Jahren weisen Entwickler auf Row-Level Security (RLS) als die endgültige Lösung hin. Das Prinzip ist einfach: “Tagge jede Zeile mit einer tenant_id und lasse die Datenbank den Rest erledigen.” Doch mit zunehmender Komplexität der SaaS-Architekturen wird die ausschließliche Abhängigkeit von RLS zu einem gefährlichen Glücksspiel.
In diesem Deep Dive gehen wir über einfache Broken Object Level Authorization (BOLA)-Angriffe hinaus und untersuchen architektonische Fehler—wie Connection Pool Contamination, Shared Cache Poisoning und Async Context Leaks—die dazu führen können, dass RLS stillschweigend versagt und massive Datenlecks verursachen.
1. Die Illusion der Row-Level Security
Row-Level Security (RLS) ist eine Datenbankfunktion (insbesondere in PostgreSQL, SQL Server und Oracle), die es ermöglicht, Richtlinien zu definieren, um zu beschränken, welche Zeilen ein Benutzer sehen oder ändern darf, basierend auf seiner Identität.
Warum es sich wie eine Silberkugel anfühlt
In einer typischen RLS-Konfiguration stellt die Anwendung eine Verbindung her und führt einen Befehl aus wie:
SET app.current_tenant_id = 'kunde_abc';
Ab diesem Zeitpunkt wird jeder SELECT * FROM rechnungen automatisch zu SELECT * FROM rechnungen WHERE tenant_id = 'kunde_abc'. Es verschiebt die Sicherheitslogik vom Anwendungscode (wo Entwickler eine WHERE-Klausel vergessen könnten) in die Datenbank-Engine.
Der fatale Fehler: Es ist nur so gut wie der Kontext
Das Kernproblem von RLS ist, dass es im Kern zustandslos ist, aber in der Praxis zustandsbehaftet. Die Datenbank weiß nicht, wer “Kunde A” ist, bis die Anwendung es ihr mitteilt. Wenn die Verbindung zwischen der Identität der Anwendung und dem Sitzungs-Kontext der Datenbank bricht, kollabiert das gesamte Sicherheitsmodell.
Neueste Forschung: CVE-2024-10976 und mehr
Aktuelle Schwachstellen zeigen, dass RLS nicht unfehlbar ist. CVE-2024-10976 zeigte eine Situation in PostgreSQL, bei der Zeilensicherheitsrichtlinien unterhalb von Unterabfragen die Benutzer-ID-Änderungen ignorieren konnten. Zudem enthüllte die CVE-2025-8713, dass Optimizer-Statistiken geleakte Stichprobendaten aus Zeilen enthalten können, die RLS eigentlich verbergen sollte. Diese “Informationslecks” erlauben cleveren Angreifern, den Inhalt anderer Mandanten durch Side-Channel-Analysen von Abfrageplänen und Fehlermeldungen zu erschließen.
2. Der Geist in der Maschine: Connection Pool Contamination
In modernen SaaS-Anwendungen öffnen wir nicht für jede Anfrage eine neue Datenbankverbindung; das wäre zu langsam. Stattdessen verwenden wir Connection Pooling (z.B. PgBouncer, HikariCP oder Prisma). Hier tritt der erste große architektonische Fehler auf.
Wie Kontamination passiert
Stellen Sie sich eine hochfrequente SaaS-Anwendung vor.
- Anfrage 1 (Kunde A) kommt an. Die App greift auf Connection #42 im Pool zu.
- Die App setzt den Sitzungs-Kontext:
SET app.tenant_id = 'Kunde_A'. - Die Abfrage läuft, Daten werden zurückgegeben, und die Anfrage ist fertig.
- Der kritische Fehler: Aufgrund eines unbehandelten Fehlers oder eines Programmierfehlers wird die Verbindung nicht “gereinigt”, bevor sie an den Pool zurückgegeben wird.
- Anfrage 2 (Kunde B) kommt. Sie greift auf Connection #42 zu.
- Die App geht davon aus, dass es sich um eine frische Verbindung handelt, oder überschreibt die tenant_id nicht sofort.
- Der Leak: Anfrage 2 führt
SELECT * FROM secretsaus und—weil Connection #42 noch den Zustand von Kunde A hat—liefert die Daten von Kunde A an Kunde B.
Das “Cleanup”-Versagen
Viele Entwickler verlassen sich auf “Middleware”, um tenant_ids zu setzen und zurückzusetzen. Wenn jedoch ein Backend-Service abstürzt oder eine Datenbanktransaktion mitten im Ablauf abgebrochen wird, wird die “Reset”-Logik möglicherweise nie ausgeführt. Ohne einen harten RESET ALL oder DISCARD ALL, der vom Pool-Proxy selbst erzwungen wird, bleibt die Verbindung “vergiftet” mit der vorherigen Benutzer-Identität.
3. Shared Cache Poisoning: Wenn Redis zum Leck wird
Um Millisekunden-Latenz zu erreichen, setzen SaaS-Apps stark auf geteilte Caches wie Redis oder Memcached. Das führt zu einer zweiten, oft unsichtbaren, Schicht des Multi-Tenant-Risikos.
Keyspace-Kollisionen
Der häufigste Cache-Fehler ist einfach: Keys nicht mit der tenant_id zu prefixen.
- Schlecht:
GET user_profile_123 - Gut:
GET tenant_A:user_profile_123
Aber auch bei Prefixing können “Race Conditions” in der Architektur auftreten.
Das Backend-Race-Condition
Stellen Sie sich ein Szenario vor, bei dem die Anwendung ein “Cache-Aside”-Muster nutzt. Bei einer Anfrage prüft die App Redis. Wenn es ein Miss ist, fragt sie die Datenbank ab und schreibt sie in Redis.
- Kunde A fordert sein Dashboard an.
- Die App berechnet die Dashboard-Daten, aber aufgrund eines Bugs in der asynchronen Logik schreibt sie das Ergebnis in einen generischen Schlüssel wie
latest_dashboard_stats. - Kunde B fordert sein Dashboard Millisekunden später an. Er trifft den Cache und erhält die gerade von Kunde A geschriebenen Daten.
Shared Cache Poisoning (Perspektive 2025)
2024 und 2025 entstand eine neue Front im Cache-Leakage: Multi-Tenant LLM-Serving. Forschungen zum KV-Cache-Sharing (wie der “PROMPTPEEK” Angriff) haben gezeigt, dass, wenn mehrere Nutzer denselben GPU-Cache für Effizienz teilen, ein Nutzer die Prompts eines anderen durch Analyse von Cache-Hits und Timing-Side-Channels rekonstruieren kann. Obwohl dies spezifisch für KI ist, verdeutlicht es eine breitere Wahrheit: Jede geteilte Ressource, die für Optimierungen genutzt wird, ist potenziell eine Leckage.
4. Der stille Killer: Async Context Leaks
Moderne SaaS-Backends sind fast ausschließlich asynchron (Node.js, Go, Python FastAPI). Diese Sprachen verwenden “Context”-Objekte, um Tenant-IDs durch eine Kette von Funktionsaufrufen zu übergeben, ohne “Prop-Drilling”.
Die Single-Thread-Falle
In Node.js ist AsyncLocalStorage die Standardmethode, um den Tenant-Status zu verfolgen. Wenn ein Entwickler jedoch eine globale Variable oder ein schlecht abgegrenztes Singleton benutzt, um eine tenant_id zu speichern, entsteht ein großes Risiko für Datenlecks.
- Das Szenario: Node.js verarbeitet Tausende gleichzeitiger Anfragen auf einem einzigen Thread.
- Das Versagen: Wenn ein Entwickler versehentlich während eines
await-Blocks für eine Millisekunde in eine globale Variable schreibt, könnten alle anderen gleichzeitigen Anfragen auf diesem Thread diesen Wert übernehmen.
Ein Race Condition in einem Backend-Service kann zu Identity Swapping führen, bei dem der “Kontext” für Request A versehentlich durch Request B überschrieben wird, weil beide auf eine gemeinsam genutzte Ressource zugreifen, die nicht richtig isoliert ist auf Thread- oder Task-Ebene.
5. Über RLS hinaus: Strategien für robuste Multi-Tenancy
Wenn RLS nicht ausreicht, was dann? Um Datenlecks zu verhindern, müssen SaaS-Architekten einen Defense-in-Depth-Ansatz verfolgen.
1. Das “Reset”-Mandat für Connection Pools
Verlassen Sie sich nicht auf Ihren Anwendungscode, um Verbindungen zu säubern. Konfigurieren Sie Ihren Pool-Proxy (wie PgBouncer) so, dass er Session Pooling mit einer verpflichtenden server_reset_query nutzt. Jedes Mal, wenn eine Verbindung zurückgegeben wird, sollte der Proxy DISCARD ALL ausführen, um temporäre Tabellen, Sitzungsvariablen und vorbereitete Anweisungen zu löschen.
2. Kryptographische Isolierung (Der “Goldstandard”)
Der ultimative Schutz besteht darin, sicherzustellen, dass selbst wenn ein Tenant die Daten eines anderen sieht, er sie nicht lesen kann.
Application-Layer Encryption (ALE): Verschlüsseln Sie sensible Spalten mit einem Schlüssel, der nur dem Tenant gehört.
Wenn Tenant B versehentlich die Zeile von Tenant A durch eine kontaminierte Verbindung zieht, sieht er nur noch eine Chiffre-Blob. Er besitzt nicht den Entschlüsselungsschlüssel von Tenant A (der in einem separaten KMS wie AWS KMS oder HashiCorp Vault gespeichert sein sollte).
3. Logikbasierte Doppelüberprüfungen
Verlassen Sie sich niemals ausschließlich auf die Datenbank. Auch bei aktivierter RLS sollte Ihr Anwendungscode eine manuelle Überprüfung durchführen:
if record.tenant_id != current_user.tenant_id:
raise SecurityLeakError("Cross-tenant access detected!")
Das mag redundant erscheinen, aber in einer Multi-Tenant-Umgebung ist Redundanz der einzige Weg zur Sicherheit.
4. Tenant-abhängiges Caching mit ACLs
Wenn Sie Redis 6.0 oder später verwenden, verzichten Sie auf ein einzelnes “Admin”-Passwort. Nutzen Sie Redis ACLs, um tenant-spezifische Nutzer zu erstellen, die nur auf bestimmte Schlüssel-Muster zugreifen dürfen:
ACL SETUSER tenant_A on epassword ~tenant_A:* +get +set
Durch die Durchsetzung der Isolierung auf Cache-Ebene stellen Sie sicher, dass selbst bei einem Bug in Ihrer Anwendungslogik kein Cross-Tenant-“GET” möglich ist.
6. Fazit: Das Paradox der SaaS-Sicherheit
Je effizienter wir unsere SaaS-Architekturen gestalten—durch Connection Pooling, geteiltes Caching und asynchrone Ausführung—desto höher ist das Risiko für Multi-Tenant-Lecks.
Row-Level Security ist ein mächtiges Werkzeug, aber kein vollständiges. Es ist ein “Sicherungsnetz”, kein “Festungsmauer”. Wahre Datenisolation erfordert einen ganzheitlichen Ansatz, bei dem das Zustandsmanagement die primäre Sicherheitsgrenze bildet.
Mit Blick auf 2026 wird die Komplexität von SaaS nur zunehmen. Organisationen, die sich auf eine einzelne Sicherheitsschicht (wie RLS) verlassen, werden letztlich mit “Data Bleed” konfrontiert. Die Überlebenden werden diejenigen sein, die ihre Architektur so gestaltet haben, dass sie davon ausgehen, dass der Datenbankkontext versagen, der Cache vergiftet wird und die Verbindung kontaminiert ist—und sie trotzdem darauf vorbereitet sind.
Related InstaTunnel pages
Continue from this article into the most relevant product guides and workflows.
Related Topics
Keep building with InstaTunnel
Read the docs for implementation details or compare plans before you ship.