TL;DR: Fehlerseiten sind ein unbeabsichtigter API-Dokumentations-Endpunkt. In einer gemischten Landschaft fand ich Stack Traces, Framework-Banner und absolute Dateisystempfade, und ich zeige die Drei-Zeilen-Fixes an der Edge.
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 gemischte Umgebung aus mehreren Webplattform-Generationen, in einem Durchgang geprüft, darunter ein Legacy-CMS, eine moderne Framework-Anwendung und eine statische Marketingseite. Im Fokus stand, was die Plattform einem unauthentifizierten Besucher bei Fehlern verrat.
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:
Information Disclosure wird meist als Liste von Zeichenketten berichtet, die in Antworten gefunden wurden: eine Versionsnummer, ein Dateipfad, ein Framework-Name. So geführt wirkt es wie Hausarbeit. Die Zeichenketten sind das Symptom. Der Mechanismus ist allgemein: die Plattform unterscheidet nicht zwischen dem Verursacher des Fehlers und dem Leser der Antwort.
Drei Formen tauchen wiederkehrend auf. Entwicklungsmodus aktiv, wo das Framework einen vollständigen Trace mit Quellkontext rendert. Generische Fehlerseiten, die im Footer weiterhin Produkt und Version nennen. Und Catch-all-Routen, die jeden unbekannten Pfad mit Status zwei beantworten.
Nichts davon gewährt Zugriff. Zusammen komprimieren sie eine mehrstündige Aufklärung auf einen Nachmittag, weil der Angreifer Framework-Versionen, Plugin-Verzeichnisse und Deployment-Layout nicht mehr erraten muss.
Befunde
| Schweregrad | Befund | Nachweis |
|---|---|---|
| HOCH | Vollständiger Stack Trace für anonyme Besucher | Quellkontext und lokale Werte enthalten |
| MITTEL | Framework-Banner und Version in generischen Fehlerseiten | Plattform und Release identifizierbar |
| MITTEL | Absolute Dateisystempfade preisgegeben | Deployment-Layout und Benutzernamen |
| MITTEL | Catch-all-Routing beantwortet jeden Pfad mit Erfolg | Kein Not-Found-Status in der Anwendung |
| NIEDRIG | Debug-Werkzeug-Endpunkte erreichbar | Diagnose-Seiten ohne Zugangsschutz |
| NIEDRIG | Interne Hostnamen in Response-Headern | Namenskonvention des Backends sichtbar |
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.
Fehler in eigener Lab-Umgebung provozieren und die Antwort lesen
# Ziel: Ihre eigene Lab-Umgebung auf localhost
# 1. fehlerhafte Requests, die typischerweise den Debug-Pfad auslösen
$ for p in '/%' '/?q[]=' '/index.php?file=../../../../etc/passwd' '/nonexistent/deep/path'; do
printf '%s -> %s\n' "$p" "$(curl -s -o /dev/null -w '%{http_code}' "http://localhost:8080$p")"
done
# 2. den Body der fehlgeschlagenen Anfrage lesen
$ curl -s 'http://localhost:8080/?q[]=' | grep -iE 'traceback|stack trace|\.py"|\.php"|File "|/var/www|/home/' | head
# 3. Header, die die Plattform beschreiben
$ curl -sI http://localhost:8080/ | grep -iE 'x-powered-by|x-generator|server|x-aspnet|via:'
Erkennung in eigener Infrastruktur
Disclosure-Sweep über eine eigene Hostliste
# schreibgeschützter Sweep, eine Zeile je Befund
$ for h in example.org example.com; do
printf '%s: ' "$h"
curl -sI "https://$h/" | grep -icE 'x-powered-by|x-generator|x-aspnet-version' | tr -d '\n'
printf ' banner-treffer, '
curl -s "https://$h/does-not-exist-$(date +%s)" | grep -icE 'traceback|stack trace|/var/www|/home/[a-z]+/' | tr -d '\n'
printf ' trace-treffer\n'
done
Catch-all-Routing erkennen, das fehlende Seiten versteckt
# jeder Pfad muss 404 liefern, nicht 200
$ for p in /nope /nope/deeper /admin /wp-content/nope; do
printf '%s %s\n' "$p" "$(curl -s -o /dev/null -w '%{http_code}' https://example.org$p)"
done
Behebung
Debug-Ausgabe abschalten und den Fehler-Body anpassen
# Django: die Debug-Seite niemals in Produktion ausliefern
DEBUG = False
ALLOWED_HOSTS = ["example.org"]
# Flask: echten Error-Handler statt des interaktiven Debuggers
@app.errorhandler(500)
def server_error(err):
app.logger.exception("unhandled error")
return "Internal error", 500 # kein Trace, keine Pfade
Statische Fehlerseite an der Edge ausliefern, ohne Upstream-Detail
server {
error_page 500 502 503 504 /errors.html;
location = /errors.html { internal; root /var/www/html; }
}
# und die Plattform nicht mehr verraten
server_tokens off;
proxy_hide_header X-Powered-By;
proxy_hide_header X-AspNet-Version;
Sobald ein Angreifer Zugriff hat, kann er folgende Aktionen durchführen:
Fazit — was ich daraus mitnehme
- Versionsstrings sind Aufklärung, die Sie kostenlos übergeben.
- Entwicklungsmodus in Produktion ist ein Konfigurationsfehler mit grosser Reichweite.
- Catch-all-200-Antworten zerstören auch Ihr eigenes Monitoring.
- Beheben Sie die Freigabe an der Edge, wo Sie sie kontrollieren, nicht in jeder Anwendung.