die häufigste OWASP-Top-10-Schwachstelle
Broken Access Control steht seit 2021 auf Platz 1 der OWASP Top 10 und betrifft die meisten getesteten Webanwendungen. Dieser Artikel erklärt IDOR, fehlende Function-Level Access Control und Privilege Escalation durch Parameter Tampering an konkreten Beispielen und zeigt, wie sich Zugriffskontrolle in Magento über ACL-Patterns und das Principle of Least Privilege sauber umsetzen lässt.
Inhaltsverzeichnis
- 1. Warum Broken Access Control auf Platz 1 der OWASP Top 10 steht
- 2. IDOR: Insecure Direct Object References verstehen und verhindern
- 3. Fehlende Function-Level Access Control
- 4. Privilege Escalation durch Parameter Tampering
- 5. Principle of Least Privilege als Grundprinzip
- 6. ACL-Implementierung im Magento-Adminbereich
- 7. Zugriffskontrolle im Customer- und Storefront-Kontext
- 8. CORS- und API-Fehlkonfigurationen als Zugriffskontroll-Lücke
- 9. Testen und Erkennen von Broken Access Control
- 10. Zusammenfassung
- 11. FAQ
1. Warum Broken Access Control auf Platz 1 der OWASP Top 10 steht
Broken Access Control ist im OWASP-Top-10-Report 2021 von Platz 5 auf Platz 1 aufgestiegen, basierend auf Daten aus über hunderttausend getesteten Anwendungen. Der Grund liegt nicht darin, dass diese Schwachstelle spektakulärer wäre als etwa Injection, sondern darin, dass sie in der überwiegenden Mehrheit aller getesteten Applikationen in irgendeiner Form vorkommt. Zugriffskontrolllogik ist über dutzende Controller, API-Endpunkte und Service-Methoden verteilt, und jede einzelne Stelle muss korrekt implementiert sein. Ein einziger vergessener Check reicht für eine ausnutzbare Lücke.
Automatisierte Scanner erkennen SQL-Injection oder XSS zuverlässig anhand von Mustern in der Antwort, scheitern bei Zugriffskontrolle aber häufig, weil eine fehlerhafte Autorisierung technisch wie eine korrekte Antwort aussieht: Der Server liefert Status 200 mit validen Daten, nur eben Daten, die dem anfragenden Nutzer nicht gehören. Die Folgen reichen von Datenabfluss über fremde Bestellungen und Rechnungen bis zur vollständigen Kontoübernahme, wenn sich über eine Lücke sogar Admin-Rechte erschleichen lassen. Genau diese Kombination aus hoher Prävalenz, schwerer automatisierter Erkennbarkeit und potenziell kritischer Auswirkung erklärt den Spitzenplatz in der aktuellen OWASP-Rangliste.
2. IDOR: Insecure Direct Object References verstehen und verhindern
Eine Insecure Direct Object Reference (IDOR) entsteht, wenn eine Anwendung einen internen Objektbezeichner wie eine Datenbank-ID, eine Rechnungsnummer oder einen Dateinamen direkt in einer URL oder einem API-Parameter offenlegt und sich darauf verlässt, dass Nutzer nur ihre eigenen IDs kennen. Ein angemeldeter Kunde ruft /customer/order/view/order_id/1042 auf und sieht seine eigene Bestellung. Ändert er die ID manuell auf 1043, bekommt er ohne jede weitere Prüfung die Bestellung eines fremden Kunden angezeigt, wenn der Server nur die Authentifizierung, aber nicht den Besitz des Objekts prüft. Das ist der klassische horizontale Fall von Broken Access Control.
Die naheliegende, aber unzureichende Reaktion ist, IDs durch UUIDs zu ersetzen, um sie schwerer erratbar zu machen. Das verschiebt das Problem nur, denn es handelt sich weiterhin um Security through Obscurity, sobald eine UUID einmal bekannt ist, etwa durch einen geteilten Link oder ein Referrer-Leck, bleibt der Zugriff ungeschützt. Die einzig belastbare Lösung ist eine serverseitige Autorisierungsprüfung bei jedem Objektzugriff: Gehört die angefragte Ressource tatsächlich dem aktuell authentifizierten Nutzer oder seiner Berechtigungsgruppe? Diese Prüfung muss auf Repository- oder Service-Ebene erzwungen werden, nicht nur im Controller, damit sie nicht versehentlich umgangen werden kann.
<?php
declare(strict_types=1);
namespace Mironsoft\SecurityDemo\Controller\Order;
use Magento\Framework\App\Action\HttpGetActionInterface;
use Magento\Framework\App\RequestInterface;
use Magento\Customer\Model\Session as CustomerSession;
use Magento\Sales\Api\OrderRepositoryInterface;
use Magento\Framework\Exception\NotFoundException;
/**
* VULNERABLE: loads any order by ID without checking ownership (IDOR).
* An authenticated customer can change order_id in the URL and read
* another customer's order data.
*/
final class ViewVulnerable implements HttpGetActionInterface
{
public function __construct(
private readonly RequestInterface $request,
private readonly OrderRepositoryInterface $orderRepository
) {
}
public function execute()
{
$orderId = (int) $this->request->getParam('order_id');
// No ownership check: any authenticated user can read any order.
return $this->orderRepository->get($orderId);
}
}
/**
* FIXED: authorization is enforced on every single object access,
* not only once at login. Ownership is verified server side.
*/
final class ViewFixed implements HttpGetActionInterface
{
public function __construct(
private readonly RequestInterface $request,
private readonly OrderRepositoryInterface $orderRepository,
private readonly CustomerSession $customerSession
) {
}
public function execute()
{
$orderId = (int) $this->request->getParam('order_id');
$order = $this->orderRepository->get($orderId);
// Explicit ownership check: deny by default unless the order
// actually belongs to the current customer.
if ((int) $order->getCustomerId() !== (int) $this->customerSession->getCustomerId()) {
throw new NotFoundException(__('The requested order does not exist.'));
}
return $order;
}
}
3. Fehlende Function-Level Access Control
Authentifizierung beantwortet die Frage, wer ein Nutzer ist. Autorisierung beantwortet die separate Frage, was dieser Nutzer tun darf, und diese zweite Prüfung muss bei jeder einzelnen Aktion erneut stattfinden, nicht nur einmalig beim Login. Ein häufiges Muster in gewachsenen Codebasen: Ein Admin-Menüpunkt wird im Frontend ausgeblendet, wenn der Nutzer keine passende Rolle hat, der zugehörige Controller-Endpunkt bleibt aber ungeschützt erreichbar. Wer die URL kennt oder errät, kann die Aktion direkt aufrufen, ganz ohne den Umweg über das versteckte Menü. Das Ausblenden von UI-Elementen ist Usability, keine Sicherheitsmaßnahme.
Besonders kritisch wird das bei REST- und GraphQL-APIs, weil dort keine UI existiert, die zufällig etwas verdeckt. Ein DELETE-Endpoint für Produkte oder Kundenkonten muss serverseitig unabhängig prüfen, ob die aufrufende Rolle diese Aktion ausführen darf, unabhängig davon, ob ein Frontend-Button dafür überhaupt existiert. In Magento bedeutet das konkret: Jeder Admin-Controller muss die Konstante ADMIN_RESOURCE setzen und diese über die ACL-Prüfung des Frameworks durchsetzen lassen, statt sich auf menu.xml oder JavaScript-Sichtbarkeit zu verlassen. Fehlt dieser Schritt bei nur einem neuen Controller, ist die gesamte übrige Absicherung wirkungslos.
<?php
declare(strict_types=1);
namespace Mironsoft\SecurityDemo\Controller\Adminhtml\Report;
use Magento\Backend\App\Action;
use Magento\Backend\App\Action\Context;
use Magento\Framework\Controller\ResultFactory;
/**
* Admin controller enforcing function-level access control via
* the Magento ACL system. Without ADMIN_RESOURCE (or with a wrong
* value), any authenticated admin user could reach this action
* regardless of their assigned role.
*/
final class Export extends Action
{
// Must match a resource ID declared in acl.xml. This alone
// is what protects the endpoint, not the admin menu entry.
public const ADMIN_RESOURCE = 'Mironsoft_SecurityDemo::report_export';
public function execute()
{
$result = $this->resultFactory->create(ResultFactory::TYPE_RAW);
$result->setContents($this->generateSensitiveReport());
return $result;
}
/**
* Generates the confidential export payload.
*
* @return string
*/
private function generateSensitiveReport(): string
{
// Report generation logic omitted for brevity.
return '';
}
}
// Framework enforcement, roughly what Action::dispatch() performs
// before execute() is ever called:
//
// if (!$this->_authorization->isAllowed(static::ADMIN_RESOURCE)) {
// throw new \Magento\Framework\Exception\AuthorizationException(
// __('You do not have permission for this action.')
// );
// }
4. Privilege Escalation durch Parameter Tampering
Bei Parameter Tampering verändert ein Angreifer Werte in Request-Parametern, versteckten Formularfeldern oder JSON-Payloads, die die Anwendung eigentlich als vom Server kontrolliert erwartet. Man unterscheidet zwei Ausprägungen von Privilege Escalation: Horizontale Eskalation bedeutet Zugriff auf Daten eines anderen Nutzers auf derselben Berechtigungsstufe, wie im IDOR-Beispiel. Vertikale Eskalation bedeutet, dass ein Nutzer mit niedrigen Rechten Funktionen einer höheren Berechtigungsstufe erlangt, etwa indem ein normaler Kunde effektiv Admin-Rechte bekommt.
Ein besonders verbreitetes Einfallstor ist Mass Assignment: Ein Registrierungs- oder Profil-Update-Endpoint nimmt ein komplettes JSON-Objekt entgegen und schreibt es ungefiltert in die Datenbank-Entity, statt nur die tatsächlich erlaubten Felder zu übernehmen. Erwartet der Endpoint Felder wie firstname und email, testet ein Angreifer zusätzlich Felder wie group_id, is_admin oder role. Übernimmt der Server diese Felder blind, weil ein ORM oder Repository das gesamte Objekt speichert, reicht ein einziger manipulierter Request für eine vollständige Rechteausweitung. Die Verteidigung ist eine explizite Allowlist erlaubter Felder pro Endpunkt, niemals ein generisches „alles speichern, was ankommt".
// Legitimate profile update request the frontend actually sends
{
"customer": {
"firstname": "Anna",
"lastname": "Beispiel",
"email": "anna@example.com"
}
}
// Tampered request an attacker sends directly to the API,
// adding fields the UI never exposes, hoping the backend
// assigns them blindly (mass assignment -> vertical escalation)
{
"customer": {
"firstname": "Anna",
"lastname": "Beispiel",
"email": "anna@example.com",
"group_id": 1,
"is_admin": true,
"website_id": 0
}
}
// Server-side defense: an explicit allowlist, never a blind
// $entity->setData($request->getParams()) or equivalent
// $allowedFields = ['firstname', 'lastname', 'email'];
// foreach ($allowedFields as $field) { ... }
5. Principle of Least Privilege als Grundprinzip
Das Principle of Least Privilege besagt, dass jeder Nutzer, jeder Prozess und jeder API-Schlüssel ausschließlich die minimal notwendigen Rechte erhält, um seine konkrete Aufgabe zu erfüllen, nicht mehr. Die Umkehrung, ein Deny-by-Default-Ansatz, ist der entscheidende Architekturentscheid: Zugriff ist standardmäßig verboten und muss explizit gewährt werden, statt standardmäßig erlaubt zu sein und punktuell eingeschränkt zu werden. Ein System, das mit „alles erlaubt, außer explizit gesperrt" startet, vergisst mit hoher Wahrscheinlichkeit irgendwann eine Sperre. Ein System, das mit „alles verboten, außer explizit erlaubt" startet, macht fehlende Rechte sofort sichtbar, weil legitime Aktionen dann schlicht fehlschlagen und auffallen.
In der Praxis bedeutet das: feingranulare Rollen statt eines einzigen geteilten Admin-Kontos für das gesamte Team, Datenbank-Nutzer mit nur den tatsächlich benötigten Rechten statt eines universellen Root-Zugangs für die Applikation, und API-Schlüssel mit eng begrenztem Scope statt eines Generalschlüssels für alle Endpunkte. Regelmäßige Zugriffsreviews sind Pflicht, denn Berechtigungen wachsen über Zeit fast immer nur, sie werden selten aktiv wieder entzogen, wenn sich Aufgaben oder Teammitglieder ändern. Temporär erhöhte Rechte für einzelne Wartungsaufgaben sollten zeitlich befristet und protokolliert vergeben werden, statt dauerhaft bestehen zu bleiben.
6. ACL-Implementierung im Magento-Adminbereich
Magento setzt das Principle of Least Privilege im Adminbereich über einen deklarativen ACL-Ressourcenbaum um, der in acl.xml jedes Moduls definiert wird. Jede Ressource bekommt eine eindeutige ID, die hierarchisch unter einer Eltern-Ressource eingehängt wird, zum Beispiel unter Magento_Backend::admin. Admin-Rollen werden über System > Permissions > User Roles mit einer Teilmenge dieses Baums verknüpft, und jeder Controller prüft über die Konstante ADMIN_RESOURCE, ob die Rolle des aktuell angemeldeten Admin-Nutzers Zugriff auf genau diese Ressource hat. Dieser Mechanismus greift automatisch bei jedem Request, bevor execute() überhaupt aufgerufen wird.
Der häufigste Implementierungsfehler ist, einen neuen Admin-Controller ohne oder mit einer falschen ADMIN_RESOURCE-Konstante auszuliefern. Fehlt sie komplett, greift Magentos Default-Verhalten, das je nach Version und Elternklasse unterschiedlich streng ausfällt, und im schlimmsten Fall ist der Controller für jeden angemeldeten Admin-Nutzer erreichbar, unabhängig von dessen Rolle. Ebenso riskant ist es, eine bereits existierende, breite Ressource wie Magento_Backend::admin wiederzuverwenden, statt eine eigene, feingranulare Ressource für das neue Modul anzulegen. Jedes neue Modul sollte einen eigenen ACL-Zweig mit möglichst spezifischen Unterressourcen definieren.
<!-- app/code/Mironsoft/SecurityDemo/etc/acl.xml -->
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:Acl/etc/acl.xsd">
<acl>
<resources>
<resource id="Magento_Backend::admin">
<resource id="Mironsoft_SecurityDemo::main" title="Security Demo" sortOrder="200">
<!-- Fine-grained sub-resource per capability, not one
single broad resource for the whole module -->
<resource id="Mironsoft_SecurityDemo::report_view"
title="View Reports" sortOrder="10"/>
<resource id="Mironsoft_SecurityDemo::report_export"
title="Export Reports" sortOrder="20"/>
<resource id="Mironsoft_SecurityDemo::settings"
title="Module Settings" sortOrder="30"/>
</resource>
</resource>
</resources>
</acl>
</config>
7. Zugriffskontrolle im Customer- und Storefront-Kontext
Im Storefront-Kontext geht es nicht um Admin-Rollen, sondern um die Isolation von Kundendaten untereinander sowie zwischen Websites und Store Views in einem Multi-Site-Setup. Jede Repository-Methode, die eine Ressource anhand einer ID lädt, etwa eine Bestellung, eine Adresse oder ein gespeichertes Zahlungsmittel, muss zusätzlich zur reinen Existenzprüfung verifizieren, dass die geladene Entität tatsächlich zur aktuell angemeldeten customer_id der laufenden Session gehört. Diese Prüfung gehört in die Service- oder Repository-Schicht, nicht nur in den Controller, damit sie automatisch greift, egal von welcher Stelle im Code die Methode aufgerufen wird.
Bei REST- und GraphQL-APIs kommt eine weitere Dimension hinzu: Customer-Tokens müssen strikt auf den ausstellenden Kunden begrenzt sein, und die API darf niemals anhand einer im Request mitgeschickten customer_id arbeiten, sondern ausschließlich anhand der aus dem validierten Token abgeleiteten Identität. Zusätzlich sollten fortlaufend nummerierte, leicht enumerierbare interne IDs in API-Antworten vermieden werden, wo es die Datenmodellierung zulässt, weil sie Angreifern das systematische Durchprobieren von IDOR-Kandidaten erheblich erleichtern, auch wenn die eigentliche Autorisierungsprüfung korrekt implementiert ist.
8. CORS- und API-Fehlkonfigurationen als Zugriffskontroll-Lücke
Eine falsch konfigurierte Cross-Origin Resource Sharing (CORS)-Policy zählt ebenfalls zu Broken Access Control, weil sie effektiv festlegt, welche fremden Domains authentifizierte Requests im Namen eines eingeloggten Nutzers stellen dürfen. Besonders gefährlich ist die Kombination aus Access-Control-Allow-Origin, das den Origin-Header blind zurückspiegelt, mit Access-Control-Allow-Credentials: true. Diese Kombination erlaubt praktisch jeder beliebigen Website, im Browser des angemeldeten Opfers authentifizierte Requests an die API zu senden und die Antwort auszulesen, was den Session-Schutz durch Same-Origin-Policy vollständig aushebelt.
Weitere häufige API-Fehlkonfigurationen im selben Themenfeld: JWT-Tokens, deren aud- oder scope-Claims serverseitig nicht geprüft werden, sodass ein für einen anderen Dienst ausgestelltes Token auch bei der eigenen API funktioniert, sowie fehlende Rate-Limits, die automatisiertes Durchprobieren von IDOR-Kandidaten oder Parameter-Tampering-Payloads praktisch unbegrenzt ermöglichen. Ein API-Gateway oder Reverse Proxy, der Origin, Token-Scope und Anfragerate zentral vor der eigentlichen Anwendungslogik prüft, reduziert die Angriffsfläche erheblich, ersetzt aber nicht die objektbezogene Autorisierungsprüfung in der Anwendung selbst.
9. Testen und Erkennen von Broken Access Control
Weil automatisierte Scanner Broken Access Control strukturell schwer erkennen, braucht es gezieltes manuelles und teilautomatisiertes Testen. Die Grundtechnik: Jeden Endpunkt mit mehreren unterschiedlich berechtigten Sessions aufrufen, etwa einem regulären Kunden, einem Kunden ohne Login und, wo sinnvoll, einem Admin-Nutzer mit eingeschränkter Rolle, und dabei systematisch prüfen, ob die Antwort dem erwarteten Berechtigungsniveau entspricht. Burp Suites Autorize-Erweiterung automatisiert diesen Vergleich, indem sie Requests einer hoch privilegierten Session automatisch mit den Session-Cookies einer niedriger privilegierten Session wiederholt und Abweichungen markiert.
Ergänzend gehören Integrationstests, die explizit einen 403- oder 404-Status für unautorisierte Zugriffe erwarten, in jede Test-Suite, direkt neben den Tests für den Erfolgsfall. Ein Code-Review-Checklistenpunkt, der bei jedem neuen Controller, jeder neuen API-Route und jeder neuen Repository-Methode explizit nach der Autorisierungsprüfung fragt, verhindert, dass diese Kontrolle in der Hektik eines Feature-Deadlines vergessen wird. Deny-by-Default als Architekturprinzip bedeutet hier auch: Ein fehlender Test für einen Endpunkt sollte als offene Sicherheitsfrage behandelt werden, nicht als stillschweigend akzeptierte Lücke.
#!/usr/bin/env bash
# Simple IDOR probe: compare responses for the same resource ID
# across two different authenticated sessions.
set -euo pipefail
RESOURCE_URL="https://shop.example.com/rest/V1/orders/mine/1042"
TOKEN_OWNER="eyJhbGciOiJI...owner-token"
TOKEN_OTHER="eyJhbGciOiJI...other-customer-token"
echo "[TEST] Request as resource owner"
curl -s -o /tmp/owner.json -w "%{http_code}\n" \
-H "Authorization: Bearer ${TOKEN_OWNER}" "$RESOURCE_URL"
echo "[TEST] Same resource ID, different authenticated customer"
status=$(curl -s -o /tmp/other.json -w "%{http_code}" \
-H "Authorization: Bearer ${TOKEN_OTHER}" "$RESOURCE_URL")
if [[ "$status" == "200" ]]; then
echo "[FAIL] Order 1042 was returned to a customer who does not own it"
exit 1
else
echo "[OK] Access correctly denied with status $status"
fi
Die folgende Übersicht fasst die wichtigsten Schwachstellenklassen aus diesem Artikel zusammen und zeigt, welches unsichere Muster zu welchem sicheren Muster führt.
| Schwachstelle | Unsicheres Muster | Sicheres Muster | Risiko-Impact |
|---|---|---|---|
| IDOR | ID aus Request laden ohne Besitzprüfung | Ownership-Check bei jedem Objektzugriff | Datenabfluss fremder Kundendaten |
| Function-Level AC | Nur UI-Button ausgeblendet | ADMIN_RESOURCE + isAllowed() serverseitig | Ungeschützte Admin-Aktionen ausführbar |
| Mass Assignment | Komplettes JSON-Objekt blind speichern | Explizite Allowlist erlaubter Felder | Vertikale Privilege Escalation |
| CORS | Origin blind spiegeln + Credentials true | Explizite Origin-Allowlist | Cross-Site-Zugriff auf Session-Daten |
| Rechtevergabe | Allow-by-Default, ein geteiltes Admin-Konto | Deny-by-Default, feingranulare Rollen | Unkontrollierte Rechteausweitung |
Auffällig ist, dass alle fünf Muster derselben Grundregel folgen: Sicherheit entsteht nicht durch das Verbergen einer Funktion, sondern durch eine explizite, serverseitige Prüfung, die bei jedem einzelnen Request erneut ausgeführt wird. Wer diese Regel konsequent auf Repository- und Service-Ebene statt nur im Controller durchsetzt, schließt die meisten Broken-Access-Control-Lücken bereits strukturell, bevor ein einzelner Test überhaupt läuft.
Mironsoft
Security-Audits, ACL-Konzepte und Access-Control-Reviews für Magento-Shops
Zugriffskontrolle in eurem Shop professionell prüfen lassen?
Wir analysieren Controller, API-Endpunkte und ACL-Konfiguration eures Magento-Shops auf IDOR, fehlende Function-Level Access Control und Privilege-Escalation-Risiken und setzen die Fixes gemeinsam mit eurem Team um.
Access-Control-Audit
Manuelle Prüfung aller Endpunkte mit unterschiedlich privilegierten Sessions
ACL-Konzeption
Feingranulare Admin-Rollen und ACL-Ressourcenbäume nach Least Privilege
Fix & Retest
Ownership-Checks, Allowlists und Integrationstests gegen Regressionen
10. Zusammenfassung
Broken Access Control steht auf Platz 1 der OWASP Top 10, weil Zugriffskontrolle über viele Endpunkte verteilt ist und ein einziger vergessener Check reicht, um Daten oder Funktionen fremder Nutzer offenzulegen. IDOR entsteht, wenn eine Objekt-ID ohne Besitzprüfung akzeptiert wird. Fehlende Function-Level Access Control entsteht, wenn nur die UI, nicht aber der Server, eine Aktion einschränkt. Privilege Escalation durch Parameter Tampering, insbesondere über Mass Assignment, erlaubt Angreifern, Felder wie Rollen oder Gruppen-IDs zu manipulieren, die eigentlich serverseitig kontrolliert sein sollten.
Das Principle of Least Privilege mit einem konsequenten Deny-by-Default-Ansatz ist die architektonische Antwort auf alle drei Problemklassen. In Magento setzt sich das über feingranulare ACL-Ressourcen in acl.xml, korrekt gesetzte ADMIN_RESOURCE-Konstanten in jedem Admin-Controller und explizite Ownership-Checks in jeder Repository-Methode um. Kein automatisierter Scanner ersetzt gezieltes Testen mit unterschiedlich privilegierten Sessions, deshalb gehört dieser Test in jede Security-Review-Routine, nicht nur in einen jährlichen Penetrationstest.
Broken Access Control, das Wichtigste auf einen Blick
IDOR verhindern
Jede Objekt-ID serverseitig gegen den authentifizierten Nutzer prüfen, nie nur auf Authentifizierung vertrauen.
Function-Level AC
ADMIN_RESOURCE und isAllowed() in jedem Controller, UI-Ausblendung ist keine Absicherung.
Least Privilege
Deny-by-Default, feingranulare Rollen statt geteilter Admin-Konten, regelmäßige Zugriffsreviews.
Testen & Monitoring
Mehrere privilegierte Sessions je Endpunkt testen, 403/404-Tests in jede Test-Suite integrieren.