TL;DR: Ein Negativbefund ist ein Ergebnis. Ich habe acht Standard-Angriffsvektoren gegen eine statische Single Page Application gefahren, jeden Fehlschlag dokumentiert und die Liste in ein Regressions-Gate verwandelt, das bei jedem Build läuft.
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 Datenplattform als vorgerenderte Single Page Application auf einem statischen Host mit Edge-Runtime davor. Alle acht geplanten Vektoren wurden ausgeführt und dokumentiert, darunter Path Traversal, Server-Side Template Injection, API-Missbrauch, Storage-Freigabe und Autorisierungsumgehung.
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:
Die meisten veröffentlichten Web-Fundamente setzen einen Server voraus, der etwas rendert. Diese Annahme macht sie billig: hier injizieren, dort ausführen, Antwort lesen. Entfällt der serverseitige Renderer, hat die Mehrzahl dieser Techniken nichts, woran sie andocken könnte.
Ein statischer Build ändert die Form des Problems, statt es zu entfernen. Es gibt keine Laufzeit zum Injizieren, damit entfallen Server-Side Template Injection, Deserialisierung und die meisten Injektionsklassen vollständig. Die Angriffsfläche reduziert sich auf Edge-Konfiguration, Client-Bundle und die API, mit der das Bundle spricht.
Das Festhalten der Fehlschläge ist so wichtig wie das Festhalten der Erfolge. Ein Negativbefund ist der einzige Weg, eine Architektur zu verteidigen, die ein späterer Prüfer sonst infrage stellt, und die Vektorliste wird zur Regressionssuite.
Befunde
| Schweregrad | Befund | Nachweis |
|---|---|---|
| INFO | Path Traversal nicht erreichbar | Statisches Routing liefert für jeden Pfad dasselbe Dokument |
| INFO | Server-Side Template Injection unmöglich | Kein serverseitiges Rendering im Request-Pfad |
| INFO | Administrative Routen nicht ausgeliefert | Unveröffentlichte Routen fehlen im Build-Output |
| INFO | Storage-Buckets nicht öffentlich | Medien werden über signierte, ablaufende URLs ausgeliefert |
| INFO | Autorisierung in der Datenschicht durchgesetzt | Direkter Objektzugriff mit fremden Kennungen getestet |
| INFO | Edge normalisiert Host und Pfad | Host-Header-Poisoning und Path-Confusion abgewiesen |
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 Regressionssuite gegen Ihre eigene statische Auslieferung laufen lassen
# Ziel: Ihre eigene statische Seite auf localhost
$ python3 -m http.server 8080 --directory dist
# 1. Traversal und Verwirrung
$ for p in '/../etc/passwd' '/%2e%2e/%2e%2e/etc/passwd' '/index.html?x=../../secret'; do
printf '%s -> %s\n' "$p" "$(curl -so /dev/null -w '%{http_code}' "http://localhost:8080$p")"
done
# 2. unveröffentlichte Routen dürfen keinen Inhalt liefern
$ for p in /admin /api/admin /internal; do
printf '%s -> %s\n' "$p" "$(curl -so /dev/null -w '%{http_code}' "http://localhost:8080$p")"
done
Erkennung in eigener Infrastruktur
Veröffentlichte Routenliste mit dem Build-Output vergleichen
# was der Build tatsächlich enthält
$ ls dist/assets | head
# und was die Edge für nicht enthaltene Pfade ausliefert
$ for p in /admin /api/admin /.env /config.json; do
printf '%s %s\n' "$p" "$(curl -s -o /dev/null -w '%{http_code}' https://example.org$p)"
done
API-Autorisierung von aussen prüfen
# ein Objekt verwenden, das Ihnen gehört, dann nur die Kennung tauschen
$ curl -s -o /dev/null -w '%{http_code}\n' 'https://api.example.org/v1/items/own-id'
$ curl -s -o /dev/null -w '%{http_code}\n' 'https://api.example.org/v1/items/other-id'
Behebung
Den Negativbefund als Regressions-Gate in der Pipeline halten
# statische Oberflächenprüfung bei jedem Build ausführen
name: static-surface
on: [push, pull_request]
jobs:
surface:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci && npm run build
- name: unveröffentlichte Routen dürfen nicht auflösen
run: |
for p in /admin /api/admin /.env /config.json; do
code=$(curl -s -o /dev/null -w '%{http_code}' "http://localhost:8080$p")
test "$code" = "404" || { echo "geleakte Route: $p ($code)"; exit 1; }
done
Fazit — was ich daraus mitnehme
- Ein Negativbefund ist ein Ergebnis, nicht das Fehlen eines Ergebnisses.
- Statische Auslieferung entfernt Injektionsklassen, nicht Autorisierung.
- Protokollieren Sie die Vektoren, die nachweislich scheitern, und wiederholen Sie sie dauerhaft.
- Bundle und API sind die gesamte Angriffsfläche, sobald der Server entfällt.