API Security Best Practices: Teil 1 - Broken Object Level Authorization

APIs sind heute das Rückgrat moderner Anwendungen. Sie treiben Web-Frontends, Mobile-Apps und IoT-Geräte an. Doch wer glaubt, API-Sicherheit sei lediglich „Web- App-Sicherheit in klein“, der irrt!

Dieser Artikel ist zuerst im Java Magazin 2.2026 unter dem Titel „Kein Griff in fremde Warenkörbe“ erschienen.

APIs haben eigene, unterschiedlich ausgeprägte Fehler- und Bedrohungslagen. So speichern sie oft keinen Sitzungszustand auf dem Server, sondern vertrauen auf vom Client übermittelte Parameter (z. B. Objekt-IDs), um zu entscheiden, auf welche Daten zugegriffen wird. Das erleichtert Skalierung, aber eröffnet Angreifern neue Möglichkeiten, fehlende Autorisierung auszunutzen.

Ein Paradebeispiel dafür ist Broken Object Level Authorization (BOLA). Hinter dem sperrigen Begriff verbirgt sich ein alltägliches Risiko: fehlende Prüfungen auf Objektebene, die es angemeldeten Angreifern erlauben, durch ID-Manipulation auf fremde Daten zuzugreifen. Greift ein Nutzer auf ein Objekt zu, für das seine Rechte nicht überprüft (oder nicht vorhanden) sind, entsteht eine kritische Sicherheitslücke. Kein Wunder also, dass BOLA in den „OWASP API Security Top 10“ auf Platz 1 steht (Abb. 1).


Figure 10: Abb. 1: BOLA-Risiko in Zahlen – trivial auszunutzen, aber potenziell verheerend

Aufgrund der Ähnlichkeit wird BOLA häufig als moderner Name für das klassische IDOR (Insecure Direct Object Reference) genutzt. Streng genommen liegt der Unterschied vor allem im Kontext: IDOR wurde meist im Rahmen traditioneller Webanwendungen diskutiert, BOLA bezeichnet speziell Autorisierungslücken in API-gestützten Szenarien.

Ohne weitere Umschweife schauen wir uns nun praxisnah an, wie BOLA-Lücken entstehen, wie man sie in Java/Spring APIs vermeiden kann und welche Schutzund Detection-Strategien für Entwickler, Architekten und CISOs sinnvoll sind.

Was bedeutet BOLA konkret?

Stellen wir uns folgendes vor: Ein Supermarkt-API bietet Endpunkte, um den Warenkorb eines Kunden zu verwalten – Artikel hinzufügen, entfernen etc. Jeder Kunde soll nur seinen eigenen Warenkorb bearbeiten können. Bei einer BOLA-Schwachstelle fehlt jedoch genau diese Sicherstellung auf Objektebene. Ergebnis: Ein authentifizierter Nutzer kann durch simple ID-Tricks fremde Warenkörbe einsehen oder manipulieren. Bildlich gesprochen greift Kunde 1 in den Warenkorb von Kunde 2, indem er im API Call seine ID durch die von Kunde 2 ersetzt.

Diese Attacke erfordert weder besonderes Knowhow noch spezielle Tools. Wenn Ressourcen-IDs erratbar oder fortlaufend sind (z.B. kunde/1, kunde/2 …), braucht ein Angreifer bloß diese zu verändern. Ist das API auf Objektebene nicht abgesichert, erhält er unbefugt Zugriff. BOLA ist also letztlich alter Wein in neuen Schläuchen – das altbekannte IDOR-Problem in modernen APIs. Der Unterschied: In APIs tritt es besonders häufig auf, weil hier Clients eigenständig Objektreferenzen übermitteln (statt wie in klassischen Web-Apps überwiegend serverseitig gesteuert).

Beispiel 1: fremder Warenkorb im Shop-API

Betrachten wir ein Shop-API (etwa für einen Onlinesupermarkt), das von einer Web- oder Mobile-App genutzt wird. Ein Endpunkt erlaubt es, Artikel in den Warenkorb eines Kunden zu legen (Listing 1).

POST /api/kunden/{kundenId}/warenkorb { "produktId": 4711, "anzahl": 2 }


Der Fehler: Wenn das Backend blind der kundenId aus dem URL vertraut, kann ein angemeldeter Nutzer seinen Request manipulieren. Benutzer Kunde 1 (ID=1) ändert z. B. den URL auf /api/kunden/2/warenkorb und fügt einen Artikel hinzu. Ohne weitere Prüfung würde das API den Artikel nun im Warenkorb von Kunde 2 ablegen: Eine Autorisierungslücke!

Diese horizontale Rechteausweitung ist ein klassischer BOLA-Fall. Sie passiert nach erfolgter Authentifizierung (der Nutzer ist angemeldet), aber ohne korrekte Autorisierung auf Objektebene. Ein analoger vertikaler Angriff (z. B. Nutzer greift auf Adminressource zu) fällt hingegen eher unter Broken Function Level Authorization und wird hier nicht betrachtet.

Wieso entsteht so eine Lücke?

Solche Bugs entstehen oft aus Bequemlichkeit oder Unachtsamkeit. Man überlässt dem Client die Angabe der Objekt- oder Benutzer-ID, um sich im Servercode einen scheinbar überflüssigen Schritt zu sparen. Im Beispiel könnte ein unerfahrener Entwickler etwa das in Listing 2 Gezeigte implementiert haben.

// Unsichere Umsetzung – keine Besitzprüfung @PostMapping("/api/kunden/{id}/warenkorb") public ResponseEntity addItem( @PathVariable("id") Long kundenId, @RequestBody Artikel artikel, Authentication auth) { warenkorbService.fuegeHinzu(kundenId, artikel); return ResponseEntity.ok().build(); }


Diese Methode nimmt die Kunden-ID direkt aus dem Pfadparameter, nutzt sie ungeprüft im Service und ignoriert den angemeldeten Nutzer (auth). Wenn warenkorbService.fuegeHinzu nicht selbst noch prüft, ob der aktuelle User zu kundenId gehört, ist das ein offenes Scheunentor. Und in der Praxis fehlt diese Prüfung leider oft. Sei es, weil sie vergessen wurde, oder weil sie bei Refactorings/Library-Updates versehentlich entfernt wurde. Schließlich funktioniert es ja scheinbar: Der reguläre Client übergibt immer die eigene ID, im normalen Betrieb fällt der Fehler nicht auf. Erst gezielte Tests (oder ein Angreifer) decken auf, dass hier jeder in jedermanns Warenkorb wirtschaften kann.

In traditionellen Webanwendungen fängt manchmal ein zentraler Mechanismus solche Fehler ab (z. B. ein Servlet-Filter oder MVC Interceptor, der Pfade auf Mitgliedschaft prüft). Bei Microservices APIs jedoch liegt die Verantwortung meist beim Service selbst. Ein API Gateway kann zwar auf Pfadmuster reagieren, weiß aber in diesem Fall noch nicht, welche Kunden- ID der anfragende User haben sollte. Deshalb gilt: Objektberechtigungen müssen in der Regel innerhalb des Service-Codes geprüft werden, möglichst nah an der Datenbankabfrage oder dem Zugriff auf das Objekt.

Besonders herausfordernd wird es, wenn mehrere Microservices dieselbe Zugriffs-Policy umsetzen müssen – etwa bei domänenübergreifenden Ressourcen. Die Logik konsistent zu halten, etwa über eine gemeinsame Library oder Policy-Definition, ist technisch anspruchsvoll und fehleranfällig. Schon kleine Abweichungen oder Versionsunterschiede können zu inkonsistentem Verhalten und Sicherheitslücken führen.

Lösungen im Shop-Beispiel

Im Folgenden sollen beispielhafte Lösungen für unser Shop-API-Beispiel gezeigt werden.

Besitzprüfung im Code

Der simpelste Fix ist, die übergebene ID gegen den eingeloggten Nutzer zu validieren. In Java/Spring zieht man z. B. die User-ID aus dem Security-Context (Session oder JWT) und vergleicht (Listing 3).

@PostMapping("/api/kunden/{id}/warenkorb") public ResponseEntity addItem( @PathVariable("id") Long kundenId, @RequestBody Artikel artikel, Authentication auth) { Long aktuelleKundenId = AuthUtil.leseUserId(auth); if (!kundenId.equals(aktuelleKundenId)) { // Option A: 403 (klar, aber verrät Existenz) // return ResponseEntity.status(HttpStatus.FORBIDDEN).build(); // Option B: 404 (vernebelt, ob die ID existiert) return ResponseEntity.status(HttpStatus.NOT_FOUND).build(); } warenkorbService.fuegeHinzu(kundenId, artikel); return ResponseEntity.ok().build(); }


Hier wird vor der Warenkorboperation geprüft, ob die Pfad-ID mit der ID des aktuellen Nutzers übereinstimmt. Falls nicht, wird mit „404 Not Found“ abgebrochen. Dieses Muster folgt dem Prinzip: „Traue nie den vom Client gesendeten Daten!“ – egal ob im URLPfad, Query-Param oder Request Body, jede vom Client kommende ID muss als potenziell manipuliert angesehen werden. Entscheidend ist, die Serverlogik darauf aufzubauen, welche Objekte ein Benutzer besitzen oder bearbeiten darf.

Framework-gestützte Autorisierung

Spring Security erlaubt, solche Checks deklarativ per Annotation zu lösen. Zum Beispiel lässt sich festlegen, dass der Methodenaufruf nur erlaubt ist, wenn die Pfad- ID mit der User-ID im Token übereinstimmt (Listing 4).

@PostMapping("/api/kunden/{id}/warenkorb") @PreAuthorize("#id == authentication.principal.id") public ResponseEntity addItem( @PathVariable("id") Long kundenId, @RequestBody Artikel artikel) { // ... in Warenkorb einfügen }


Der SpEL-Ausdruck in @PreAuthorize prüft zur Laufzeit, ob die übergebene ID (#id) gleich der ID des angemeldeten Nutzers (authentication.principal.id) ist. Falls false, wird die Methode gar nicht ausgeführt – Spring Security gibt automatisch „403“ zurück. Vorteil: Der Businesscode bleibt sauber, die Sicherheitslogik ist zentral konfigurierbar. Voraussetzung ist, dass der principal eine id-Property hat (bei eigenen Userobjekten i. d. R. der Fall). Alternativ ließe sich ein eigener PermissionEvaluator nutzen oder eine Service-Methode, z. B. @PreAuthorize("@userService.hatZugriff(#id)"), die intern den Besitz prüft.

Unerratbare IDs als Defence in Depth

Ein ergänzender Schutz besteht darin, die Identifier weniger erratbar zu machen. Im Warenkorb-Beispiel heißt das: Statt offener, sequenzieller Kunden-IDs könnte jeder Nutzer einen zufälligen Warenkorb-Key haben, z.B. /api/warenkoerbe/ASDF-XYZ-1234 anstelle von /api/kunden/2/warenkorb. Selbst wenn hier versehentlich keine Prüfung auf den Besitzer erfolgt, ist es für Angreifer deutlich schwieriger, gültige fremde IDs zu finden. Wichtig: Das ist nur zusätzliche Sicherheit, kein Ersatz für Autorisierungsprüfungen. Falls ein Angreifer doch einen gültigen Key erlangt (z. B. via Leak oder Brute Force), nützt die Random-ID allein nichts. Trotzdem: Unvorhersehbare GUIDs oder ausreichend lange, zufällige IDs erschweren Massenangriffe erheblich und sind daher Best Practice, wo es möglich ist.

Sorgfältig testen

Entwickler sollten gezielt Tests schreiben, um BOLA frühzeitig aufzudecken. Am besten in Form automatisierter Integrationstests: Ein Testuser A versucht auf Daten von User B zuzugreifen (oder umgekehrt) und muss dabei einen Fehler erhalten. Solche Szenarien sollten Teil der Testsuite sein. Zusätzlich kann man – bei verdächtigem Verhalten oder in regelmäßigen Audits – auch manuell prüfen, ob Endpunkte gegen ID-Tampering anfällig sind. Tools wie Postman oder Burp Suite ermöglichen es, identische Requests mit verschiedenen IDs auszuprobieren. Achtung: Manuelles Testen sollte nur ergänzend erfolgen; es ersetzt keinesfalls automatisierte Tests, sondern ist eher als „Notnagel“ oder für spezielle Penetrationstests gedacht. Im Team sollte es zum Qualitätsanspruch gehören, dass bei jedem Endpunkt mit Objekt-IDs ein entsprechender Negativtest existiert.

Fazit aus Beispiel 1: APIs müssen auf mehreren Ebenen die Türen abschließen. Auf Endpunktebene regelt z. B. Spring Security über Rollen/Authorities, ob ein Benutzer einen Pfad überhaupt aufrufen darf. Aber auf Objektebene muss jede Anfrage zusätzlich validieren, dass Benutzer X tatsächlich auf Objekt Y zugreifen darf. Fehlt diese Kontrolle, ist der „fremde Warenkorb“ offen – ein Super-GAU für Vertrauen und Sicherheit.

Beispiel 2: Bank-API – Konten mit Vollmachten absichern

Nun ein komplexeres Szenario aus der Finanzwelt: Ein Banking-API bietet einen Endpunkt /api/konten/{kontoNr} an, über den ein Kunde seine Kontodetails abrufen kann. Hier gilt ebenfalls: Nur Berechtigte dürfen auf ein Konto zugreifen.

Angenommen, die Kontonummern sind vollständig numerisch und beinhalten die Kunden-ID in den letzten sechs Stellen. Zum Beispiel: Kontonummer 550000 123456, hier stellen die letzten sechs Ziffern (123456) die Kundennummer dar. Eine simple Policy-Idee wäre nun: „Erlaube den Zugriff nur, wenn die Kunden-ID im JWT mit den letzten Stellen der Kontonummer übereinstimmt.“ Anders ausgedrückt: Der Kontopfad /api/konten/550000123456 darf nur von Kunde 123456 selbst abgefragt werden, von niemandem sonst. (Abb. 2)


Figure 11: Abb. 2: Netzwerk-Policies erlauben nur Backend-Zugriffe über das API Gateway

In Microsoft Azure könnte dazu eine API-Management- Policy, wie in Listing 5 gezeigt, realisiert werden.

<inbound> <base /> <!-- JWT validieren --> <validate-jwt header-name="Authorization" failed-validation-httpcode="401" failedvalidation- error-message="Ungültiges Token."> <openid-config url="https://<dein-idp>.well-known/openid-configuration" /> <required-claims> <claim name="kundeId" /> </required-claims> </validate-jwt> <!-- JWT-Claim 'kundeId' extrahieren --> <set-variable name="jwtKundeId" value="@(context.Request.Headers.GetValueOrDefault( "Authorization") != null ? ((Jwt)context.Request.Headers.GetValueOrDefault ("Authorization")).Claims["kundeId"] : null)" /> <!-- Kontonummer aus Query-Parameter extrahieren --> <set-variable name="kontoNummer" value="@(context.Request.Url.Query.GetValueOrDefault("kontoNummer"))" /> <!-- Letzte6 Stellen der Kontonummer extrahieren --> <set-variable name="kontoSuffix" value="@(context.Variables.GetValueOrDefault <string>("kontoNummer")?.Length >= 6 ? context.Variables.GetValueOrDefault <string>("kontoNummer").Substring(context.Variables.GetValueOrDefault<string> ("kontoNummer").Length - 6) : null)" /> <!-- Vergleich: stimmen die letzten 6 Stellen mit dem JWT-Claim überein? --> <choose> <when condition="@(context.Variables.GetValueOrDefault<string>("kontoSuffix") != context.Variables.GetValueOrDefault<string>("jwtKundeId"))"> <return-response> <set-status code="403" reason="Zugriff verweigert" /> <set-header name="Content-Type" exists-action="override"> <value>application/json</value> </set-header> <set-body>{ "error": "Die Kundennummer im Token stimmt nicht mit der Kontonummer überein." }</set-body> </return-response> </when> </choose> </inbound>


In vielen Fällen funktioniert das. Aber was ist, wenn Kontovollmachten ins Spiel kommen? In der Praxis haben Kunden häufig bevollmächtigte Vertreter (z. B. eine Assistenz oder ein Familienmitglied), die ebenfalls Kontozugriff haben. Nehmen wir an, Kunde A (ID 111111) hat eine Vollmacht für das Konto von Kunde B (ID 987654). Die Kontonummer von B lautet etwa 770000 987654. Nach obiger Policy dürfte Kunde A dieses Konto nicht abrufen, da seine eigene ID (111111) nicht mit den letzten Stellen der Kontonummer (987654) übereinstimmt. Die starre Prüfung entsprechend dem Schema würde also legitime Zugriffe verwehren.

Kurz: Das Postfix-Muster „ID im Pfad muss gleich User-ID sein“ greift zu kurz, wenn Zugriffsrechte komplexer sind.

Dynamische Lösung: JWT Claims + Gateway Policy

Statt mit Regex oder Namenskonventionen kann man die Autorisierung dynamischer lösen. Hier kommen JSON Web Tokens (JWT) und API Gateways ins Spiel.

Beim Log-in erhält jeder Nutzer ein JWT, das neben seiner User-ID auch alle Konten, auf die er Zugriff hat, als Claim enthält. Für Kunde A mit Vollmacht auf Bs Konto könnte ein dekodiertes JWT etwa so aussehen, wie in Listing 6 gezeigt.

{ "sub": "kundeA", "kundeId": 111111, "berechtigteKonten": ["770000987654", "660000111111"], "roles": ["USER"] }


In diesem Token signalisiert "berechtigteKonten", dass der Inhaber Zugriff auf zwei Konto-IDs hat: Auf das eigene Konto (660000 111111, das mit 111111 endet) und auf ein fremdes (770000 987654, das mit 987654 endet).

Das API Gateway (z. B. Membrane API Gateway) prüft bei jedem Request das JWT (Signatur, Gültigkeit etc.) und liest die Claims aus. Nun kann es auf Basis des Pfads und der Claims entscheiden, ob der Zugriff erlaubt wird. Konkret: Ist die angefragte Kontonummer im Claim berechtigteKonten enthalten? Eine beispielhafte Policy-Konfiguration in Membrane könnte so aussehen, wie in Listing 7 gezeigt.

<api name="KontoService" port="2001"> <!-- JWT prüfen --> <jwtAuth expectedAud="bank-api"> <jwks> <jwk location="bank_jwk.json"/> </jwks> </jwtAuth> <!-- Autorisierungsregel: --> <if test="!exc.properties.jwt['berechtigteKonten'].contains(exc.request.uri.path.split('/')[2])" language="groovy"> <static>Kein Zugriff auf dieses Konto</static> <return statusCode="403"/> </if> <!-- Weiterleitung an Backend --> <target host="kontoserver" port="8080"/> </api>


Dieser Ausschnitt bewirkt Folgendes: Zunächst wird ein gültiges JWT vorausgesetzt (<jwtAuth> blockiert sonst schon). Dann prüft ein Groovy-Ausdruck, ob die Liste berechtigteKonten aus dem Token die angefragte Kontonummer (den Pfadteil [2] in /api/konten/{nr}) nicht enthält. Ist dem so (das ! negiert die Bedingung), wird die Anfrage mit „403“ geblockt. Ansonsten passiert sie und wird ans Backend weitergereicht.

Hinweis: Die genaue Syntax variiert je nach Gateway. Im Membrane-Beispiel oben greift exc.properties.jwt auf die JWT-Daten zu. Ähnliche Mechanismen bieten z.B. Apigee, Kong oder das AWS Gateway via custom Authorizer.

Analog könnte man diese Regel auch mit Open Policy Agent (OPA) implementieren, etwa als Rego-Snippet (Listing 8). Hier definiert die Policy, dass allow nur dann gültig ist, wenn die angefragte kontoId in der Liste der berechtigten Konten des JWTs auftaucht. Diese OPA-Logik könnte im Gateway oder im Microservice angewendet werden, um den Zugriff entsprechend zu steuern.

package konto.auth default allow = false # In einer vollständigen Policy müsste input.kontoId aus dem Pfad # extrahiert werden. # (z.B. kontoId := input.request.path_segments[2]) # Erlaube Zugriff, wenn die Konto-ID im Token-Claim berechtigteKonten # enthalten ist allow { input.jwt.berechtigteKonten[_] == input.kontoId }


Vorteile dieser Methode: Die Autorisierungslogik wird zentral im Gateway durchgesetzt, noch bevor der eigentliche Service anspringt. In größeren Architekturen entlastet das die Microservices – die Standardchecks (JWT gültig? Darf Nutzer X theoretisch auf Ressource Y zugreifen?) werden am Eingang erledigt. Die Security-Policy ist unabhängig vom Service-Code verwaltbar, was dem CISO bzw. Security-Team ermöglicht, einheitliche Regeln durchzusetzen.

Die Zugriffsregeln lassen sich beispielsweise über Specification Extensions in OpenAPI nicht nur dokumentieren, sondern auch aktiv referenzieren. Durch standardisiert benannte Parameter und eigene Erweiterungen wie x-access-control können API-Gateways die entsprechende Regel automatisch erkennen und die passende Prüfung aktivieren – vorausgesetzt, sowohl die OpenAPI-Spezifikation als auch das zugehörige Regelwerk liegen dort vor. Mit Hilfe von Lintern lässt sich zudem automatisiert sicherstellen, dass OpenAPI-Dokumente solche Zugriffs-Policies konsistent referenzieren. Ein Beispiel wird in Listing 9 gezeigt.

paths: /api/konten/{kontoNr}: get: summary: Hole Kontoinformationen parameters: - name: kontoNr in: path required: true schema: type: string x-access-control: "verify-account-access" responses: '200': description: Erfolgreiche Antwort


Herausforderungen: Die Ausstellung solcher JWTs muss gut geplant sein. Der Identity Provider (z. B. OAuth2-Server) muss beim Log-in alle Berechtigungen kennen und ins Token packen. Bei sehr vielen Berechtigungen können JWTs groß werden. Alternativ könnte das Gateway benötigte Infos aus einem User- Info-Service nachladen, was aber Latenz bringt. In unserem Beispiel mit Kontovollmachten ist das JWT-Modell praktikabel, weil jeder Nutzer nur wenige Kontenberechtigungen hat.

Ein weiterer Punkt: Trotz Gateway-Sicherung sollten die Backend-Services eigene Prüfungen haben (Prinzip „Defense in Depth“). Im Konto-Backend würde man z. B. beim Datenbankzugriff stets überprüfen: Gehört diese Kontonummer zu einem Benutzer, der in der Anfrage steckt? Das kostet etwas Performance, bringt aber Sicherheit, falls jemand das Gateway umgehen sollte (z. B. interner Aufruf oder Fehlkonfiguration).

Zum Abschluss dieser Bank-API-Betrachtung sei gesagt: Nicht jede Situation erfordert ein so aufwändiges Set-up. Im Shop-Beispiel war die Lage simpler – in der Regel hat jeder nur seinen eigenen Warenkorb. Dort reicht die Codeprüfung im Service völlig aus. Den Overhead eines Claims-Systems im JWT lohnt sich vor allem bei vielen Objektberechtigungen pro Nutzer (wie bei Konten mit Vollmachten). Wichtig ist, die richtigen Daten im richtigen Schritt mitzuführen: Im Fall der Bank also Autorisierungsinformationen bereits im Token, damit nachgelagerte Komponenten gezielt filtern können. In jedem Fall müssen aber die Objekte selbst immer die letzte Verteidigungslinie haben (z.B. WHERE konto.id IN (Liste erlaubter Konten) in der Query).

Weitere Absicherung und Detection-Strategien

Schon im Design kann man BOLA vorbeugen – etwa indem API-Spezifikationen (OpenAPI) klar festlegen, welche Endpunkte schützenswert sind. Linter wie Spectral können automatisiert prüfen, ob sensible Pfade ein Authentifizierungs- und/oder Zugriffs-Schema zugewiesen bekommen. So wird vermieden, dass ein kritischer Endpunkt versehentlich ohne Schutz veröffentlicht wird. Zwar erkennt ein solches Werkzeug in der Standardkonfiguration keine fehlende Objektprüfung, doch die Konfiguration ist erweiterbar: Beispielsweise ließe sich eine Regel definieren, die bei Pfaden mit einem Parameter wie kontoNr zwingend eine referenzierte Zugriffs-Policy (z.B. x-access-control) verlangt. Damit wird nicht nur Konsistenz sichergestellt, sondern auch die Durchsetzung konkreter Autorisierungsregeln unterstützt – direkt aus der Spezifikation heraus.

Attributbasierte Zugriffskontrolle (ABAC): In komplexen Fällen (wie dem Bankbeispiel) kann man statt starrer Rollen-/Besitzprüfungen auch Policies definieren, die auf Attributen basieren. Zum Beispiel: „Erlaube Zugriff, wenn (request.user.abteilung == konto.abteilung) ODER (kontoId in request.user. vollmachten).“ Solche Regeln ließen sich mit OPA oder XACML formulieren und zentral prüfen. ABAC erhöht Flexibilität und Nachvollziehbarkeit bei komplizierten Berechtigungskonstellationen, erfordert aber Disziplin bei der Implementierung, damit keine Lücke übersehen wird.

Logging und Monitoring: Da Prävention nie hundertprozentig ist, sollte man BOLA-Versuche schnell erkennen. Eine Idee ist, API-Logs ins Data Warehouse oder SIEM zu speisen und auf Anomalien zu prüfen. Ungewöhnliche Zugriffsmuster sind verdächtig: Wenn eine Benutzersession in kurzer Zeit sehr viele verschiedene Objekt-IDs anspricht, deutet das auf ID-Bruteforcing hin. Ein Beispiel-SQL (Pseudocode) wird in Listing 10 gezeigt.

SELECT subject, COUNT(*) total, SUM(CASE WHEN status IN (403,404) THEN 1 ELSE 0 END) AS forbidden_like FROM api_calls WHERE path LIKE '/carts/%/items' AND ts >= NOW() - INTERVAL '7 days' GROUP BY subject HAVING forbidden_like > 0 ORDER BY forbidden_like DESC;


Moderne Sicherheitslösungen markieren so etwas automatisch. Zum Beispiel stuft Cloudflare einen Client, der „drastisch viele einzigartige IDs“ abfragt, als verdächtig ein (Risk Score: BOLA Attack). Ein anderes Indiz: Parameter an unerwarteter Stelle. Taucht ein Wert mehrfach in einer Anfrage auf (z.B. KontoNr im Pfad und nochmal als Query-Param), könnte jemand versuchen, alternative Codepfade ohne Auth zu triggern (Parameter Pollution).

Spezifische Test-Tools: OWASP ZAP und ähnliche Tools können per Skript systematisch IDs in Requests austauschen und prüfen, ob unautorisierte Zugriffe gelingen. Solche halbautomatisierten Pentests sollten regelmäßig stattfinden, insbesondere bei Releases mit sicherheitsrelevanten Änderungen. Auch Bug-Bounty- Programme sind hilfreich – IDOR/BOLA ist ein beliebtes Ziel für Security-Researcher, weil es oft übersehen wird und doch leicht aufzuspüren ist (Tabelle 1).

Tabelle 1: Typische BOLA-Anzeichen
AnzeichenBeschreibung/Risiko
fortlaufende IDsRessourcen-IDs sind einfach und sequenziell (z. B. Benutzer 1000,1001,1002). Risiko: Erleichtert ID-Guessing und massenhaftes Ausprobieren.
keine Owner-Prüfungim Code Endpunkte nutzen vom Client gelieferte IDs, ohne den Besitzer gegen den eingeloggten User zu verifizieren. Risiko: Autorisierung komplett umgehbar (klassischer BOLA-Fall).
einseitige FehlercodesBei Zugriff mit falschen IDs kommt immer derselbe Fehler (z. B. stets 403, nie 404). Risiko: Hinweis, dass keine objektspezifische Prüfung erfolgt – legitime, aber fremde IDs werden nicht unterschiedlich behandelt.
ungewöhnliche AufrufmusterEine Session ruft sehr viele verschiedene IDs oder fremde Ressourcen auf. Risiko: Hinweis auf IDEnumeration- Angriff (Bruteforcing verschiedener IDs).
Parameter PollutionGleicher Wert taucht mehrfach in Anfrage auf (z. B. KontoNr im Pfad und als Query-Param). Risiko: Versuch, alternative Pfade/Logik zu nutzen, evtl. mit fehlender Auth (Parameter Pollution).
inkonsistente DokuIn der OpenAPI-Doku sind einige sensible Endpunkte nicht als secured markiert. Risiko: Möglicher Design- Lapse – Endpunkt könnte offengelassen worden sein (oder Doku hinkt hinterher).

Take-aways für Entwickler/Architekten

Zusammengefasst sollten Entwickler und Architekten ein besonderes Augenmerk auf die folgenden Punkte richten:

  • Keine Autorisierung „vergessen“: Stelle sicher, dass jeder objektbezogene API Call eine Berechtigungsprüfung durchläuft. Im Zweifel immer auf den Datenbankzugriff runterbrechen, z. B. SELECT ... FROM objekte WHERE id=? AND owner_id=? statt nur WHERE id=?.
  • Frameworks nutzen: Verwende die Sicherheitsfunktionen des Frameworks (z.B. Spring Security @PreAuthorize) wo möglich, statt überall manuell if-Bedingungen zu bauen. Das verringert menschliche Fehler.
  • Schwierige IDs einsetzen: Verwende UUIDs oder ähnliche IDs statt fortlaufender Nummern. Das schreckt Angreifer ab, weil gültige Werte schwer zu raten sind. Aber trotzdem immer prüfen.
  • Regelmäßig testen: Richte automatische Integrationstests ein, die Cross-User-Zugriffe ausprobieren. Ergänze sie bei Bedarf durch gezielte Pentests mit Tools (ZAP, Burp). Führe diese Tests besonders nach größeren Refactorings oder Dependency-Upgrades durch.
  • Codereviews und Linters: Lass deinen Code auf solche Schwachstellen prüfen. Ein anderer Blick erkennt oft fehlende Checks. Nutze Linters (z. B. Spectral für OpenAPI), um sicherzustellen, dass sicherheitskritische Endpunkte konsistent behandelt werden.

Takeaways für CISOs/Security-Verantwortliche

Wer für die Security verantwortlich ist, sollte besonders Folgendes im Hinterkopf behalten:

  • Security by Design einfordern: Sensibilisiere Entwicklerteams früh für API-spezifische Risiken. BOLA sollte in Threat Models und Architekturdokus von Anfang an vorkommen, nicht erst nach dem ersten Vorfall.
  • Zentrale Policies etablieren: Nutze API Gateways oder Zero-Trust Frontdoors, um wo immer möglich zentrale Auth-Policies durchzusetzen (siehe JWT-/Gateway-Beispiel). Lege fest, wann solche Mechanismen Standard sind (z. B. „bei Account-IDs immer Gateway-Policy“).
  • Monitoring und Response: Implementiere Monitoring auf BOLA-Indikatoren. Alarme werden ausgelöst, wenn z. B. eine IP innerhalb kurzer Zeit viele 403 erzeugt oder zig Ressourcen-IDs durchprobiert. Prüfe Optionen wie Cloudflare API Shield oder WAF-Regeln, die speziell auf IDOR abzielen.
  • Schulungen und Kultur: Macht BOLA zum festen Bestandteil eurer Security-Schulungen. Zeige intern einfache Beispiele (wie den fremden Warenkorb) und Best Practices zu deren Vermeidung. Fördere eine Kultur, in der das Team mögliche BOLA-Stellen proaktiv aufspürt und behebt, bevor es ein Angreifer tut.

Fazit

Broken Object Level Authorization mag als Begriff sperrig sein, doch die dahinterstehende Lücke ist simpel und gerade deshalb so gefährlich. Eine vergessene oder falsch implementierte Objektprüfung kann dazu führen, dass vertrauliche Daten in falsche Hände geraten oder Kundenkonten gekapert werden.

Die Beispiele mit dem Warenkorb und den Bankkonten zeigen, wie wichtig es ist, auf allen Ebenen zu kontrollieren, wer was darf. Die gute Nachricht: Mit klarem Bewusstsein und den richtigen Mitteln lässt sich BOLA effektiv verhindern. Sei es durch stringente Codeprüfungen in Spring, clevere JWT Claims plus Gateway Policies oder vorausschauende API Governance – es gibt keinen Grund, dem „Griff in den fremden Warenkorb“ tatenlos zuzusehen.

Am Ende zahlt sich Umsicht aus: Unsere Nutzer verlassen sich darauf, dass „ihr Konto“ auch ihr Konto bleibt – und nicht zum Selbstbedienungsladen für andere wird. Deshalb sollten wir als Entwickler und Sicherheitsverantwortliche stets fragen: Habe ich die Tür zum Datenobjekt fest verschlossen? Wenn ja, können wir beruhigt sagen: In meinem Supermarkt-API bleibt der Warenkorb dort, wo er hingehört, und geschützt vor neugierigen Zugriffen.

Von: Tobias Polley
Datum: 7. Juni 2026

API Gateway eBook

Erfahre im kostenlosen eBook wie du mit OAuth2, API-Keys, JWT und einem Gateway APIs schützen kannst.

API Gateway eBook Cover
Training

Lerne von unseren Autoren in der API Security Schulung.

Online Training
11. - 12. 6.2026
12. - 13. 10.2026
Jetzt anmelden
für 1.340,- €*

Schulungen in Bonn
Jetzt anmelden
für 1.470,- €*