TL:DR
IDORs sind die häufigste Ursache für Datenlecks bei Multi-Tenant-SaaS-Unternehmen und werden in der Regel erst nach der Bereitstellung entdeckt. Aikido macht die Isolierung von Mandanten zur Pflicht. Zen analysiert alle Ihre SQL-Abfragen zur Laufzeit mit einem geeigneten SQL-Parser (geschrieben in Rust), überprüft, ob jede Abfrage den richtigen Mandanten filtert, und gibt einen Fehler aus, wenn dies nicht der Fall ist. Entwickler können somit nicht mehr versehentlich mandantenübergreifende Zugriffe freigeben. Es ist ab sofort für Node.js verfügbar, Python, PHP, Go, Ruby, Java und .NET folgen in Kürze.
Warum IDORs jetzt gefährlicher sind
IDOR-Schwachstellen, auch bekannt als Insecure Direct Object References (unsichere direkte Objektverweise), sind eine der häufigsten und gefährlichsten Schwachstellen in Multi-Tenant-Anwendungen. Sie treten auf, wenn eine Abfrage vergisst, nach Mandanten zu filtern, sodass ein Konto auf die Daten eines anderen Kontos zugreifen kann.
Lange Zeit waren IDORs schwer zu finden. Sie tauchten bei Code-Scans nicht auf und erforderten viel manuellen Aufwand. Aus diesem Grund wurden viele IDOR-Fehler erst bei kostspieligen, mühsamen Penetrationstests entdeckt oder wenn Sicherheitsforscher sie im Rahmen von Bug-Bounty-Programmen fanden.
Das hat sich jedoch geändert. Agentische Sicherheitstest-Tools können sich nun wie echte Benutzer verhalten, sich durch Workflows klicken, Rollen wechseln und automatisch versuchen, auf Ressourcen zuzugreifen. Dadurch lassen sich IDOR-Schwachstellen viel leichter erkennen. Dies ist jedoch ein zweischneidiges Schwert: Wenn diese Schwachstellen leichter zu finden sind, lassen sie sich auch leichter ausnutzen. Aus diesem Grund sollten sich Unternehmen nicht nur auf die Erkennung von IDORs konzentrieren, sondern auch auf deren Prävention.
Warum Erkennung nicht ausreicht
Auf der Erkennungsseite kann Aikido KI-Pentest bereits IDOR-Schwachstellen finden, was mit herkömmlichen musterbasierten SAST nie zuverlässig möglich war, da IDOR das Verständnis des Autorisierungskontexts und nicht nur von Codemustern erfordert. AI Pentest authentifiziert sich als echter Benutzer, führt komplette Workflows durch und verwendet Objektkennungen rollenübergreifend wieder, um tatsächlich ausnutzbare IDOR-Schwachstellen zu finden. Aus diesem Grund sind viele Unternehmen, die unsere AI Pentest-Funktion nutzen, vor allem daran interessiert, IDORs zu finden.
Das Aufspüren von IDOR-Schwachstellen ist jedoch nur die halbe Miete. Im Idealfall würden Sie deren Entstehung von vornherein verhindern. Darum geht es in diesem Beitrag: IDOR-Schutz in Zen, unserer In-App-Firewall. Sie analysiert jede SQL-Abfrage zur Laufzeit und gibt eine Fehlermeldung aus, wenn in einer Abfrage ein Mandantenfilter fehlt oder eine falsche Mandanten-ID verwendet wird. So wird der Fehler bereits während der Entwicklung und beim Testen entdeckt, bevor er in die Produktion gelangt.
In vielen Unternehmensumgebungen, insbesondere bei Sicherheitsüberprüfungen oder Lieferantenbewertungen, stellt sich immer wieder die Frage, wie die Mandantenfähigkeit durchgesetzt und der mandantenübergreifende Datenzugriff verhindert wird.
Sicherheitsteams und Führungskräfte wünschen sich klare technische Zusicherungen, dass die Grenzen zwischen Mietern systematisch und nicht nur durch Konventionen durchgesetzt werden.
Ein Mechanismus, der die Mieter-Scoping auf Abfrageebene automatisch validiert, liefert eine einfache und zuverlässige Antwort. Damit verschiebt sich die Diskussion von „wir verlassen uns darauf, dass Entwickler dies beachten“ zu „das System setzt dies automatisch durch“.
So sieht die Konfiguration aus:
import Zen from "@aikidosec/firewall";
// 1. Tell Zen which column identifies the tenant
Zen.enableIdorProtection({
tenantColumnName: "tenant_id",
excludedTables: ["users"],
});
// 2. Set the tenant ID per request (e.g., in middleware after authentication)
app.use((req, res, next) => {
Zen.setTenantId(req.user.organizationId);
next();
});
// 3. Optionally bypass for specific queries (e.g., admin dashboards)
const result = await Zen.withoutIdorProtection(async () => {
return await db.query("SELECT count(*) FROM orders WHERE status = 'active'");
});
Wie sieht eine IDOR-Sicherheitslücke aus?
Wenn Ihre App über Konten, Organisationen, Arbeitsbereiche oder Teams verfügt, haben Sie wahrscheinlich eine Spalte wie Mieter-ID die die Daten jedes Kontos getrennt hält. Wenn die Abfrage jedoch vergisst, nach dieser Spalte zu filtern, oder nach dem falschen Wert filtert, bedeutet dies, dass ein Konto auf die Daten eines anderen Kontos zugreifen kann. Dies ist eine IDOR-Sicherheitslücke.
Hier ist ein einfaches Beispiel. Sie haben einen Endpunkt, der die Bestellungen eines Benutzers zurückgibt:
app.get("/orders/:orderId", async (req, res) => {
const order = await db.query(
"SELECT * FROM orders WHERE id = $1",
[req.params.orderId]
);
res.json(order);
});Sehen Sie das Problem? Es gibt keine Mieter-ID Filter. Wenn Alice sendet GET /orders/42 und Bestellung 42 gehört Bob, Alice erhält Bobs Bestellung. Das ist ein IDOR.
Die Lösung ist ganz einfach: Fügen Sie ein WO tenant_id = $2 Klausel. Der Fehler ist jedoch leicht einzufügen und schwer zu finden, insbesondere in einer großen Codebasis mit Hunderten von Abfragen in Dutzenden von Dateien. Ein einziger übersehener Filter reicht schon aus.
IDOR ist eine weit gefasste Kategorie. Dazu gehören auch Dinge wie der Zugriff auf Dateien anderer Benutzer über URL-Manipulation oder API-Endpunkte, die keine Eigentumsrechte überprüfen. Dieser Beitrag konzentriert sich speziell auf die Untergruppe der SQL-Mandantenfilterung, die sicherstellt, dass jede Datenbankabfrage ordnungsgemäß auf den aktuellen Mandanten beschränkt ist. Für einen tieferen Einblick in IDOR im Allgemeinen lesen Sie unseren Beitrag „IDOR-Schwachstellen erklärt “.
Was gibt es heute auf dem Markt?
Neben Sicherheitsscans, bei denen es eher darum geht, vorhandene Fehler zu finden, gibt es noch andere Methoden, um die Entstehung neuer IDORs zu verhindern: Bibliotheken auf Framework-Ebene und Durchsetzung auf Datenbank-Ebene. Beide haben ihre Stärken und Grenzen.
Bibliotheken auf Framework-Ebene
Mehrere Frameworks verfügen über Bibliotheken, die Abfragen automatisch auf den aktuellen Mandanten beschränken:
- Ruby on Rails: acts_as_tenant fügt ActiveRecord-Modellen automatische Mandanten-Scoping hinzu. Deklarieren Sie acts_as_tenant(:account) und alle Abfragen zu diesem Modell werden nach dem aktuellen Mandanten gefiltert.
- Django: django-multitenant macht dasselbe für Djangos ORM. Leg den aktuellen Mandanten in der Middleware fest, und Product.objects.all() wird automatisch mandantenbezogen.
- Laravel: Tenancy for Laravel bietet sowohl Single-Database- als auch Multi-Database-Multi-Tenancy mit automatischer Kontextumschaltung.
- .NET / EF Core: Globale Abfragefilter Sie können sich bewerben
WHERE tenant_id = Xauf jede Abfrage automatisch auf Modellebene.
Diese Bibliotheken funktionieren gut innerhalb ihres eigenen ORM. Die Einschränkung besteht darin, dass sie nur Abfragen schützen, die über die Abstraktion des ORM laufen. Rohe SQL-Abfragen, Abfragen aus anderen Bibliotheken oder Abfragen, die mit einem anderen Abfrage-Builder im selben Projekt erstellt wurden, werden nicht berücksichtigt. Sie sind außerdem optional, man muss daran denken, die Anmerkung zu jedem Modell hinzuzufügen, und neue Modelle können unbemerkt durchrutschen. Um fair zu sein, als_Mieter_agieren hat ein require_tenant Konfiguration, die einen Fehler auslöst, wenn kein Mandant festgelegt ist, wodurch das Risiko, die Festlegung des Mandanten zu vergessen, erheblich gemindert wird.
Es gibt auch subtile Fußfallen. In Rails zum Beispiel als_Mieter_agieren funktioniert durch Hinzufügen eines StandardbereichWenn ein Entwickler anruft Projekt.unscoped um einen anderen Standardbereich zu entfernen, wie z. B. einen archiviert filter entfernt alle Standardbereiche, einschließlich des Mandantenfilters, ohne Fehler oder Warnung. Rails hat unscope (ohne die d) für die chirurgische Entfernung eines einzelnen Endoskops, aber dafür muss man zunächst einmal wissen, dass das Endoskop überhaupt vorhanden ist. In einer Codebasis mit vielen Entwicklern wird irgendwann jemand nach nicht umschlossenund die Grenze zum Mieter verschwindet lautlos.
Durchsetzung auf Datenbankebene
PostgreSQL-Sicherheit auf Zeilenebene (RLS) geht noch einen Schritt weiter, indem es die Isolierung der Mandanten auf Datenbankebene erzwingt. Anstatt sich darauf zu verlassen, dass Ihre Anwendung WO tenant_id = ? Bei jeder Abfrage weisen Sie Postgres selbst an, dies durchzusetzen:
-- 1. RLS für jede Tabelle aktivieren
ALTER TABLE projects ENABLE ROW LEVEL SECURITY;
-- 2. Erstellen Sie eine Richtlinie: Erlauben Sie nur Zeilen, die mit der Sitzungsvariablen übereinstimmen
CREATE POLICY tenant_isolation ON projects
FOR ALL
VERWENDUNG (tenant_id = current_setting('app.current_tenant_id')::uuid);
-- 3. Legen Sie pro Anfrage den Mandantenkontext fest, bevor Sie Abfragen ausführen.
SET app.current_tenant_id = 'aaaa-aaaa-aaaa';
-- Jetzt gibt sogar ein einfacher SELECT nur die Zeilen dieses Mandanten zurück
SELECT * FROM projects;
RLS ist die stärkste Garantie für die hier aufgeführten Ansätze. Ob Raw-SQL- oder ORM-Abfragen spielt keine Rolle. Die Datenbank setzt dies durch. Und im Gegensatz zu als_Mieter_agierenWenn Sie vergessen, die Sitzungsvariable festzulegen, werden keine Daten zurückgegeben, anstatt alle Daten. Das ist eine viel sicherere Standardeinstellung.
Aber das hat auch Nachteile. RLS gibt keine Fehlermeldungen aus; Abfragen geben stillschweigend weniger Zeilen zurück oder haben gar keine Auswirkungen. Das ist sicherer als die Rückgabe aller Daten, erschwert aber die Fehlersuche. Ein AKTUALISIERUNG Eine Änderung, die eigentlich 100 Zeilen betreffen sollte, kann aufgrund einer Richtlinieninkongruenz unbemerkt nur 0 Zeilen betreffen, und es ist schwierig, dies von „keine Daten vorhanden“ zu unterscheiden.
Auch Connection Pooling erhöht die Komplexität. RLS mit SET funktioniert nicht ordnungsgemäß mit pgBouncer im Statement- oder Transaction-Pooling-Modus, was dazu führen kann, dass Zeilen für den falschen Mandanten zurückgegeben werden. Dies tritt möglicherweise nur in der Produktion auf.
Es gibt auch strukturelle Einschränkungen. Superuser umgehen alle Richtlinien vollständig, und Ansichten umgehen standardmäßig RLS, sodass Ihre App als Nicht-Superuser-Rolle verbunden sein muss. Schließlich ist es nur für Postgres verfügbar. Wenn Sie MySQL, SQLite für die Entwicklung oder einen anderen Datenspeicher unterstützen müssen, können Sie Ihre Sicherheitsebene nicht mitnehmen.
Die pragmatische Schlussfolgerung: RLS eignet sich hervorragend als Sicherheitsnetz für die Isolierung von Mandanten, aber aufgrund der operativen Komplexität und der Schwierigkeiten bei der Fehlerbehebung ist es nicht für jedes Team eine sofort einsetzbare Lösung.
Wo Zen hineinpasst
All dies sind gültige Ansätze, und wenn Sie einen davon verwenden, ist das großartig. Der IDOR-Schutz von Zen ist für ein anderes Szenario konzipiert: Ihre Abfragen laufen direkt oder über ein ORM über einen Datenbanktreiber, und Sie möchten ein Sicherheitsnetz, das unabhängig davon funktioniert, welches ORM, welchen Query Builder oder welches Raw-SQL-Muster Sie verwenden, ohne dass Sie Ihre Datenbankkonfiguration ändern oder eine bestimmte Framework-Bibliothek übernehmen müssen.
Zen hat seine eigenen Kompromisse, über die man ehrlich sein muss. Wie als_Mieter_agieren, müssen Sie setTenantId bei jeder Anfrage. Wenn Sie dies vergessen, gibt Zen eine Fehlermeldung aus, sodass der Fehler lautstark statt stillschweigend auftritt, aber es handelt sich um dieselbe Art von Einrichtung pro Anfrage. Und im Gegensatz zu RLS deckt Zen nur Abfragen ab, die innerhalb Ihrer Anwendung ausgeführt werden. Wenn jemand direkt mit der Datenbank verbunden ist, beispielsweise über psql oder einen separaten Dienst ohne Zen, werden diese Abfragen nicht überprüft.
Es ist außerdem sprachunabhängig konzipiert. Da die SQL-Analyse-Engine in Rust geschrieben ist, kompilieren wir sie zu WebAssembly für Node.js und Go sowie zu einer nativen Bibliothek, die andere Agenten über FFI aufrufen. Der IDOR-Schutz wird speziell auch für die Agenten Python, PHP, Go, Ruby, Java und .NET verfügbar sein.
Wie Zen vor IDORs schützt
Zen ist in Ihre Anwendung integriert und analysiert SQL-Abfragen zur Laufzeit, wobei der vollständige Kontext darüber vorliegt, wer die Anfrage stellt.
Ein ordentlicher SQL-Parser, geschrieben in Rust
Das Herzstück des IDOR-Schutzes von Zen ist ein echter SQL-Parser, der auf der sqlparser-Crate in Rust basiert und für Node.js und Go zu WebAssembly kompiliert wurde. Er analysiert SQL auf die gleiche Weise wie eine Datenbank, indem er einen vollständigen abstrakten Syntaxbaum (AST) Ihrer Abfrage erstellt und dann den Baum durchläuft, um Folgendes zu extrahieren:
- Welche Tabellen die Abfrage betrifft (einschließlich Aliase)
- Welche Gleichheitsfilter befinden sich in der WHERE-Klausel?
- Welche Spalten und Werte sind in INSERT-Anweisungen enthalten?
Warum nicht Regex? Regex funktioniert gut für einfache Abfragen wie SELECT * FROM orders WHERE tenant_id = ?. In realen Anwendungen gibt es jedoch CTEs, UNIONs, Unterabfragen, JOINs mit Aliasen und alle möglichen gültigen SQL-Ausdrücke, mit denen ein auf regulären Ausdrücken basierender Ansatz Probleme hat. Je komplexer die Abfragen werden, desto anfälliger wird die auf regulären Ausdrücken basierende Analyse. Das ist nicht unbedingt falsch, aber schwer zu warten und kann zu Überraschungen führen.
Ein geeigneter Parser erledigt all dies ohne weiteres. Er erkennt auch korrekt Anweisungen, die nicht überprüft werden müssen, wie beispielsweise DDL-Anweisungen (TABELLE ERSTELLEN, TABELLE ÄNDERN), Transaktionssteuerung (BEGIN, COMMIT, ROLLBACK) und Sitzungsbefehle (EINSTELLEN, ANZEIGEN).
So sieht die Analyse unter der Haube aus. Angenommen, wir haben folgende Abfrage:
AUSWÄHLEN * FROM Aufträge
LINKS VERBINDEN order_items ON orders.id = order_items.order_id
WHERE Aufträge.Mieter_ID = $1
UND Aufträge.Status = 'active';
Der Parser erzeugt:
[
{
"kind": "select",
"tables": [
{ "name": "orders" },
{ "name": "order_items" }
],
"filters": [
{ "table": "orders", "column": "tenant_id", "value": "$1" },
{ "table": "orders", "column": "status", "value": "active" }
]
}
]
Zen überprüft dann, ob jede Tabelle in der Abfrage einen Filter hat. Mieter-IDund ob der Filterwert mit dem aktuellen Mandanten übereinstimmt.
Das Gleiche gilt für EINFÜGEN, AKTUALISIERUNG, und LÖSCHENZen stellt sicher, dass die Spalte „tenant” immer vorhanden ist und immer den richtigen Wert enthält. Diese werden als Fehler gemeldet und nicht nur protokolliert. IDOR ist ein Entwicklerfehler und kein externer Angriff, daher sollte er während der Entwicklung und beim Testen deutlich sichtbar werden und nicht in die Produktion gelangen.
Performance
Das Parsen von SQL bei jeder Abfrage klingt aufwendig, ist in der Praxis jedoch schnell. Der entscheidende Punkt ist, dass die meisten Anwendungen vorbereitete Anweisungen oder parametrisierte Abfragen verwenden. Die SQL-Zeichenfolge bleibt unverändert, nur die Parameterwerte ändern sich. Also SELECT * FROM orders WHERE tenant_id = $1 AND status = $2 wird einmal geparst, und jede nachfolgende Ausführung derselben Abfrage ist ein Cache-Treffer.
Wenn Zen zum ersten Mal eine neue Abfragezeichenfolge sieht, erstellt der Rust-Parser den AST und extrahiert die Tabellen und Filter. Danach handelt es sich jedes Mal nur noch um einen Cache-Lookup und einen Vergleich der Mandanten-ID mit dem aufgelösten Platzhalterwert.
Wenn Sie Werte direkt in SQL-Zeichenfolgen einbetten, beispielsweise durch Zeichenfolgenverkettung anstelle von parametrisierten Abfragen, muss jede einzelne Zeichenfolge neu geparst werden. Aber das sollten Sie wahrscheinlich ohnehin nicht tun. Parametrisierte Abfragen schützen vor SQL-Injection und beschleunigen zudem die IDOR-Prüfung.
Der Weg zur Produktion: Wir essen unser eigenes Hundefutter
Wir haben den IDOR-Schutz von Zen auf mehreren internen Diensten von Aikido eingesetzt. Dadurch wurden sofort Randfälle sichtbar, die behandelt werden mussten.
Die Transaktionsunterstützung war anfangs ein Hindernis. Reale Anwendungen verwenden BEGIN, VERPFLICHTEN, und RÜCKGABEZen musste diese als sichere Anweisungen erkennen, die keine Mieterfilterung erfordern, anstatt sie als Fehler zu behandeln. Wir haben dies schnell hinzugefügt, nachdem wir bei unserer ersten internen Bereitstellung einen Fehler festgestellt hatten.
Common Table Expressions (CTEs) waren eine weitere Herausforderung. Ein CTE wie WITH active AS (SELECT * FROM orders WHERE tenant_id = $1) Erstellt eine virtuelle Tabelle, auf die nachgelagerte Abfragen verweisen. Zen musste CTE-Namen verfolgen und sie aus der Liste der „echten Tabellen” ausschließen, während es gleichzeitig den CTE-Körper für eine ordnungsgemäße Filterung analysierte.
Der ohne ID oder Schutz escape erwies sich ebenfalls als unverzichtbar. Nicht jede Abfrage erfordert eine Mandantenfilterung, wie beispielsweise Admin-Dashboards, Hintergrundaufgaben oder mandantenübergreifende Analysen. Wir haben zunächst versucht, eine Nächste Abfrage ignorieren Ansatz, bei dem Sie vor der Abfrage eine Funktion aufrufen würden, um die Überprüfung für die nächste SQL-Anweisung zu überspringen:
Zen.ignoreNextQuery();
const result = await db.query(„SELECT count(*) FROM orders”);
In der Praxis erwies sich dies als problematisch. Bei Verbindungspools war die „nächste Abfrage” einer bestimmten Verbindung möglicherweise nicht diejenige, die Sie überspringen wollten. Die Callback-basierte ohne ID oder Schutz ist hinsichtlich des Geltungsbereichs eindeutig. Der IDOR-Schutz ist nur für die Dauer des Callbacks deaktiviert und sonst nirgendwo.
Wie wir unsere API geschützt haben, die Cloud-Asset-Daten bereitstellt
Einer der Dienste, die wir frühzeitig geschützt haben, war die interne API, die Cloud-Asset-Daten plattformübergreifend bereitstellt.
Diese API wird von der Benutzeroberfläche, Hintergrundprozessen und mehreren Sicherheitsmodulen verwendet, wenn diese Informationen über die Infrastruktur eines Kunden lesen müssen. Sie bildet den Kern des Systems und verarbeitet Tausende von Anfragen pro Sekunde.
Da die Plattform vollständig mandantenfähig ist, ist eine strikte Mandantentrennung von entscheidender Bedeutung. Jede Abfrage muss auf die richtige Organisation beschränkt sein, und wir können uns nicht darauf verlassen, dass Entwickler daran denken, in jedem Codepfad den richtigen Filter hinzuzufügen.
Bevor Zen IDOR-Schutz nativ unterstützte, hatten wir eine Benutzerdefinierte Implementierung, die die Mandantenbeschränkung auf Abfrageebene durchsetzte. Nachdem Zen erstklassige Unterstützung für dieses Verhalten eingeführt hatte, migrierten wir von der selbst entwickelten Lösung zur integrierten Funktionalität, wodurch wir deutlich weniger Code warten müssen.
Heute überprüft Zen automatisch, ob Abfragen auch unter hoher Last korrekt auf den aktuellen Mandanten beschränkt sind. Nach der Einführung des IDOR-Schutzes von Zen konnten wir keine nennenswerten Auswirkungen auf die Leistung feststellen.
Erkennung und Prävention
Der AI Pentest Aikido findet IDOR-Schwachstellen in Ihrer laufenden Anwendung, indem er reale Angriffe simuliert. Der IDOR-Schutz von Zen verhindert, dass diese überhaupt erst auftreten, indem er fehlende Mandantenfilter bereits in der Entwicklungsphase erkennt.
Zusammen decken sie beide Seiten ab. AI Pentest überprüft, ob Ihr bestehender Code sicher ist, und Zen sorgt dafür, dass neuer Code sicher bleibt. Verwenden Sie AI Pentest, um bereits implementierten Code zu überprüfen. Verwenden Sie Zen, um Fehler beim Schreiben zu erkennen.
Erste Schritte
IDOR-Schutz ist ab sofort in @aikidosec/firewall für Node.js verfügbar. Lesen Sie die Einrichtungsanleitung, um loszulegen. Die Unterstützung für andere Sprachen folgt in Kürze!

