TL;DR: Die API-Dokumentation war öffentlich, und sie war die beste Karte, die man sich wünschen kann: jeder Pfad, Feldname und Validierungsregel, auch die administrativen Routen, auf die keine Oberfläche verweist. Ich zeige, wie man sie außerhalb der Entwicklung abschaltet.
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.
Ein Softwareprodukt auf Basis eines automatisierten Anwendungsframeworks, mit separatem Client-Frontend, Drittanbieter-Diensten für Zahlungen, KI und E-Mail sowie Object Storage für Medien. Geprüft wurden öffentliche Schema- und Dokumentationsendpunkte, Header-Policy, Skript-Integrität, E-Mail-Authentifizierung und die Vertrauenskette.
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:
Frameworks, die einen Client aus einer Typdefinition generieren, erzeugen gerne Dokumentation aus derselben Definition. Das ist ein Feature: Die Dokumentation kann nicht von der Implementierung abweichen. Die Folge ist, dass das Veröffentlichen der Dokumentation eine einzelne Umgebungsentscheidung ist, und der Standard ist in mehreren verbreiteten Frameworks, sie in jeder Umgebung einschliesslich Produktion zu veröffentlichen.
Eine interaktive Dokumentationsseite ist mehr als eine Bequemlichkeit. Sie rendert das echte Schema mit Feldnamen, Typen, Pflichtfeldern und Validierungsregeln, also genau die Information, die ein Angreifer braucht, um korrekte Anfragen zu bauen.
Das tiefere Muster sind Standardkonfigurationen. Jede Kontrolle in diesem Mandat war ein Framework-Standard statt einer Entscheidung: Dokumentation an, Inline-Skripte in der Policy erlaubt, Analytics von einer selbst gehosteten Instanz auf einem separaten Server, Medien von einem Storage-Endpunkt mit Wildcard-Origin-Policy.
Befunde
| Schweregrad | Befund | Nachweis |
|---|---|---|
| MITTEL | Interaktive Dokumentation öffentlich ausgeliefert | Volles Schema mit Feldnamen und Validierungsregeln |
| MITTEL | Maschinenlesbares Schema listet interne und administrative Pfade | Vollständiges Routen-Inventar inklusive Pfade ohne Oberflächenbezug |
| MITTEL | Inline-Skripte in der Content Security Policy erlaubt | Keine Einschränkung der Inline-Ausführung |
| MITTEL | Drittanbieter-Skript ohne Integrität und von einem separaten Host | Kompromittierung dieses Hosts bedeutet volle Kontrolle der Seite |
| NIEDRIG | Medien-Endpunkt mit Wildcard-Origin-Policy ausgeliefert | Jeder Origin darf gespeicherte Dateien lesen |
| NIEDRIG | Transport-Policy ohne Preload und Subdomain-Scope | Hardening vorhanden, aber unvollständig |
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 bekannten Dokumentationspfade des eigenen Dienstes prüfen
# Ziel: Ihr eigener Labordienst auf localhost
$ for p in /docs /redoc /openapi.json /api-docs /swagger /swagger.json; do
printf '%-16s %s\n' "$p" "$(curl -so /dev/null -w '%{http_code}' "http://localhost:8000$p")"
done
# wenn das Schema öffentlich ist, das Routen-Inventar daraus lesen
$ curl -s http://localhost:8000/openapi.json | python3 -c '
import json,sys
spec = json.load(sys.stdin)
for path, ops in spec.get("paths", {}).items():
print(path, ",".join(k.upper() for k in ops))' | head -40
Erkennung in eigener Infrastruktur
Inline-Ausführungserlaubnisse in der eigenen Policy suchen
$ curl -sI https://example.org/ | grep -i 'content-security-policy' | tr ';' '\n' \
| grep -E 'script-src|unsafe-inline|unsafe-eval|\*'
Jeden Drittanbieter-Skript-Origin auflisten, den Ihre Seite lädt
$ curl -s https://example.org/ | grep -oE '<script[^>]*src="https?://[^"]*"[^>]*>' \
| grep -oE 'src="https?://[^/]+' | sort -u
# und wie viele davon Integrität tragen
$ curl -s https://example.org/ | grep -oE '<script[^>]*src="https?://[^"]*"[^>]*>' \
| grep -c integrity
Behebung
Dokumentation außerhalb der Entwicklung abschalten
# FastAPI: die Dokumentation ist opt-in, also Produktion opt-out machen
import os
ENV = os.environ.get("APP_ENV", "production")
app = FastAPI(
title="Internal service",
# beide stehen standardmäßig auf True; sie nicht zu setzen veröffentlicht das Schema
docs=None if ENV == "production" else "/docs",
redoc=None if ENV == "production" else "/redoc",
openapi_url=None if ENV == "production" else "/openapi.json",
)
# und in einem Test absichern, damit eine spätere Default-Änderung es nicht zurückbringt
def test_no_schema_in_production(client):
app.openapi_url = None
assert client.get("/openapi.json").status_code == 404
assert client.get("/docs").status_code == 404
Sobald ein Angreifer Zugriff hat, kann er folgende Aktionen durchführen:
Fazit — was ich daraus mitnehme
- Ein generiertes Schema ist eine Angriffsflächenkarte für einen Request.
- Dokumentation ist im Framework opt-in und in Ihrer Umgebung opt-out. Setzen Sie es explizit.
- Sichern Sie die Abwesenheit im Test ab; eine Default-Änderung kündigt sich nicht an.
- Auditieren Sie die Drittanbieter-Skript-Origins. Ein eigener Server ist keine Kontrolle.