TL;DR: Ein Befund, maximale Wirkung: eine Wildcard-Cross-Origin-Policy auf der öffentlichen Datenschnittstelle. Keine Versionsprüfung, kein Exploit — der Browser erledigt die Arbeit. Ich zeige den exakten Request und den Allowlist-Fix.
Ausgangslage
Ich habe das Ziel als Black-Box geprüft: keine Zugangsdaten, nur die öffentlich sichtbare Fläche. Das ist, was ich sah, bevor ich etwas angefasst habe.
Eine kommunale Content-Plattform auf einem unterstützten Content-System hinter einem modernen Reverse Proxy. Die Bewertung war bewusst eng gefasst und prüfte die öffentliche Datenschnittstelle sowie die Cross-Origin-Policy für den Browserzugriff darauf.
Ich habe alle identifizierenden Informationen entfernt. Das Ziel wird ausschließlich nach Branche und Technologieklasse beschrieben, damit das Muster übertragbar bleibt.
Das Muster
Das Muster, das ich erkannt habe — und das ich in ähnlichen Landschaften immer wieder finde:
Cross-Origin-Policy ist ein Browser-Mechanismus, und genau das macht sie mächtig und billig. Antwortet ein Server auf eine Cross-Origin-Anfrage mit permissiver Policy, setzt der Browser sie durch: Die aufrufende Seite darf die Antwort lesen. Der Server hat aus seiner Sicht nichts falsch gemacht, keine Version ist veraltet, und kein Payload ist nötig. Eine einzelne HTML-Datei auf beliebiger Domain ist der gesamte Angriff.
Entscheidend ist der Unterschied zwischen Wildcard und reflektiertem Origin. Eine Wildcard auf einem öffentlichen, unauthentifizierten Lese-Endpunkt ist oft eine bewusste Bequemlichkeit: die Daten sind öffentlich, also könne jeder sie abrufen. Die Überlegung stimmt für die Daten und irrt sich beim Browser.
Darum kann ein einziger Befund eine lange Liste von Versionshinweisen übertreffen. Er entscheidet für jede künftige Injektion auf jeder Seite, ob der Angreifer die Antwort liest, ohne Fuss zu fassen.
Befunde
| Schweregrad | Befund | Nachweis |
|---|---|---|
| KRITISCH | Wildcard-Cross-Origin-Policy auf der öffentlichen Datenschnittstelle | Jede Seite auf jeder Domain kann die Antwort lesen |
| MITTEL | Wildcard-Policy auch auf schreibfähige Pfade angewandt | Permissiver Header nicht auf den Lese-Endpunkt begrenzt |
| MITTEL | Wildcard kombiniert mit Credentials auf einer Route | Antwort mit angehängten Cookies lesbar |
| NIEDRIG | Kein Vary-Header auf dem reflektierten Origin | Cache-Poisoning-Fläche |
| NIEDRIG | Preflight ohne Routen-Allowlist beantwortet | Methoden- und Header-Aushandlung nicht begrenzt |
| INFO | Plattform vollständig gepatcht | Keine ausnutzbare Schwachstelle in der Anwendung selbst |
Der Angriffspfad
Reproduktion im Labor
Jeder Befehl unten zielt auf einen Lab-Container, den ich selbst kontrolliere. Nichts davon ist auf ein laufendes System gerichtet.
Die eigene Schnittstelle von einem fremden Origin aus prüfen
# Ziel: Ihre eigene Labor-Schnittstelle
# 1. was sagt der Server für einen fremden Origin
$ curl -s -o /dev/null -D - -H 'Origin: https://evil.example' \
'https://example.org/api/public/items' | grep -i 'access-control'
# 2. reflektiert er jeden Origin, auch eine nicht existierende Subdomain
$ curl -s -o /dev/null -D - -H 'Origin: https://angreifer-kontrolliert.example.org' \
'https://example.org/api/public/items' | grep -i 'access-control'
# 3. und lassen sich Credentials anhängen
$ curl -s -o /dev/null -D - -H 'Origin: https://evil.example' \
--cookie 'session=beliebig' 'https://example.org/api/private/items' | grep -i 'access-control'
Erkennung in eigener Infrastruktur
Die eigene Landschaft auf reflektierte Origin-Header durchsuchen
$ for p in /api/public/items /api/private/items /graphql /api/me; do
printf '%s -> %s\n' "$p" \
"$(curl -s -o /dev/null -D - -H 'Origin: https://probe.example' "https://example.org$p" \
| grep -i 'access-control-allow-origin' | tr -d '\r')"
done
# und auf die Kombination, die wirklich zählt
$ curl -s -o /dev/null -D - -X OPTIONS -H 'Origin: https://probe.example' \
-H 'Access-Control-Request-Method: DELETE' -H 'Access-Control-Request-Headers: authorization' \
'https://example.org/api/public/items' | grep -i 'access-control'
Behebung
Die Policy auf eine Allowlist begrenzen und niemals blind reflektieren
# eine explizite Allowlist, serverseitig ausgewertet
map $http_origin $cors_origin {
default "";
"https://example.org" $http_origin;
"https://www.example.org" $http_origin;
}
add_header Access-Control-Allow-Origin $cors_origin always;
add_header Vary Origin always;
# Credentials nur dort, wo der Endpunkt wirklich authentifiziert ist und die
# Origin-Liste bereits auf eigene Hosts begrenzt ist
# add_header Access-Control-Allow-Credentials "true" always;
Die korrekte Ablehnung statt eines permissiven Headers zurückgeben
// eine Allowlist schlägt eine Wildcard, immer
const ALLOWED = new Set(['https://example.org', 'https://www.example.org']);
export function cors(req, res, next) {
const origin = req.headers.origin;
if (origin && ALLOWED.has(origin)) {
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Vary', 'Origin');
}
// kein Origin-Header, wenn die Anfrage nicht von einem erlaubten Origin kommt
next();
}
Sobald ein Angreifer Zugriff hat, kann er folgende Aktionen durchführen:
Fazit — was ich daraus mitnehme
- Eine zu permissive Cross-Origin-Policy ist ein Multiplikator für jeden anderen Bug.
- Öffentliche Daten bedeuten nicht, von jeder Seite lesbar zu sein.
- Nie einen Origin ohne Allowlist und ohne Vary reflektieren.
- Ein Befund, der die Schwere aller anderen verändert, gehört an die Spitze des Berichts.