
PoC Docker-Labor: Kombination von Datei-Upload-Bypass + gespeichertem XSS, um Administratorkonten zu erstellen. Lehrmittel für Pentester.
Eine bewusst verwundbare Webanwendung, die zeigt, wie ein File-Upload-Bypass mit Stored-XSS verkettet wird, um Backdoor-Admin-Konten zu erstellen, selbst wenn CSP-, CORS- und CSRF-Schutzmaßnahmen vorhanden sind.
Ausführlicher Blogbeitrag: KurtiseBear Blog
Dieses Labor dient zu Bildungszwecken für defensive Sicherheitsschulungen. Stellen Sie diese Anwendung nicht öffentlich zugänglich bereit.
Die Anwendung verfügt über echte Sicherheitskontrollen:
'self' ein (allerdings mit 'unsafe-inline' und 'unsafe-eval')Ein Angreifer mit einem Benutzerkonto mit niedrigen Privilegien verkettet zwei Schwachstellen, um alle zu umgehen:
File-Upload-Bypass – Das Upload-Formular schränkt auf .pdf ein über ein clientseitiges accept-Attribut, aber der Server führt keine Dateityp-Validierung durch. Ein Angreifer lädt eine .js-Datei mit JavaScript hoch. Der Download-Endpunkt liefert sie vom selben Ursprung aus, sodass CSP und CORS sie nicht blockieren.
Stored XSS über den Nachrichtenbetreff – Die Nachrichtenfunktion speichert Benutzereingaben ohne Bereinigung. Der Admin-Posteingang rendert den Nachrichtenbetreff als rohes HTML. Das XSS-Payload verwendet einen ``-Handler, um das hochgeladene Skript abzurufen und mit eval() auszuführen. CSP erlaubt dies, da 'unsafe-inline' und 'unsafe-eval' zulässig sind.
Fehlender CSRF-Schutz am API-Endpunkt – Die Benutzerverwaltungs-API (/api/manage-user.php) validiert keine CSRF-Token, obwohl dies bei Formular-Endpunkten der Fall ist. Das XSS-Payload ruft diese API mit der gleichherkunfts-Sitzung des Admins auf. Selbst wenn CSRF vorhanden wäre, könnte gleichherkunfts-JavaScript das Token aus dem DOM auslesen.
Das Ergebnis: Wenn ein Admin seinen Posteingang öffnet, feuert das XSS, das JavaScript erstellt ein Backdoor-Admin-Konto mit der Sitzung des Admins. Jede Verteidigung ist vorhanden und funktioniert. Die Kette funktioniert, weil sie den Ursprung nie verlässt.
docker-compose up -d
Warten Sie 10-15 Sekunden, bis MySQL initialisiert ist, und besuchen Sie dann http://localhost:8080
| Rolle | Passwort | |
|---|---|---|
| Admin | [email protected] | admin |
| User | [email protected] | user |
Navigieren Sie zu http://localhost:8080 und melden Sie sich mit [email protected] / user an.
Gehen Sie zu Dateien hochladen (engl. Upload Files). Das Formular sagt „Nur PDF“, erzwingt dies aber nur clientseitig. Entweder:
accept=".pdf" aus dem Datei-Eingabefeld zu entfernen, oderLaden Sie die bereitgestellte payload.js (oder Ihre eigene) hoch. Notieren Sie die zurückgegebene Datei-ID (z.B. 1).
Die hochgeladene Datei wird nun von /api/download.php?file_id=1 auf demselben Ursprung bereitgestellt. CSP wird Abrufe an diesen Endpunkt nicht blockieren, da er 'self' ist.
Gehen Sie zu Nachricht senden (engl. Send Message). Geben Sie im Betrefffeld ein:
r.blob()).then(b=>b.text()).then(eval)">
(Ersetzen Sie 1 durch die tatsächliche Datei-ID aus Schritt 2.)
Geben Sie beliebiges in den Nachrichtentext ein. Setzen Sie das Häkchen bei Priorität, wenn die Nachricht oben im Posteingang erscheinen soll. Senden.
Der onerror-Handler funktioniert, weil CSP 'unsafe-inline' erlaubt. Die eval()-Funktion funktioniert, weil CSP 'unsafe-eval' erlaubt. Der Abruf des Download-Endpunkts funktioniert, weil er gleichherkunfts ist.
Melden Sie sich ab. Melden Sie sich als [email protected] / admin an. Gehen Sie zu Posteingang (engl. Inbox).
Der Nachrichtenbetreff wird als rohes HTML gerendert. Das ``-Tag kann nicht geladen werden, der onerror-Handler feuert, ruft das hochgeladene Payload ab und eval() führt es aus. Das Payload sendet eine POST-Anfrage an /api/manage-user.php mit dem Sitzungscookie des Admins (wird automatisch für gleichherkunfts-Anfragen angehängt). Kein CSRF-Token erforderlich, da der API-Endpunkt keines prüft.
Gehen Sie zu Benutzer (engl. Users). Sie sollten einen neuen Benutzer sehen: BackdoorAdmin mit der Rolle admin und der E-Mail [email protected].
Melden Sie sich ab und melden Sie sich mit [email protected] / Compromised1! an, um zu bestätigen.
CSP blocks external scripts
--> But the payload is hosted on the same origin via file upload
--> And unsafe-inline/unsafe-eval allow the onerror handler and eval()
CORS blocks cross-origin requests
--> But every request in the chain is same-origin
CSRF tokens protect form submissions
--> But the API endpoint doesn't validate them
--> And even if it did, same-origin JS can read tokens from the DOM
Session cookies have standard protections
--> But same-origin requests carry them automatically
Die Verteidigungsmaßnahmen funktionieren alle korrekt. Sie sollen Cross-Origin-Angriffe stoppen. Diese Kette verlässt den Ursprung nie.
Was diese Kette tatsächlich durchbrechen würde:
Serverseitige Dateityp-Validierung – Prüfen Sie MIME-Typ, Dateierweiterung und Magic Bytes. Vertrauen Sie dem Client nicht. Dies verhindert, dass der Angreifer ein Payload auf Ihrem Ursprung hostet.
Ausgabe-Kodierung – Verwenden Sie htmlspecialchars() für alle benutzergesteuerten Ausgaben. Der Posteingang rendert $row['subject'] roh. Das tötet das XSS vollständig.
Strenges CSP – Entfernen Sie 'unsafe-inline' und 'unsafe-eval'. Verwenden Sie Nonces oder Hashes für legitime Inline-Skripte. Dies blockiert den onerror-Handler und eval().
Content-Disposition: attachment – Erzwingen Sie Downloads anstelle von Inline-Rendering für benutzerhochgeladene Dateien. Dies verhindert, dass der Browser hochgeladene Inhalte interpretiert.
CSRF auf allen zustandsändernden Endpunkten – Einschließlich API-Endpunkten, nicht nur Formularen.
Zugriffskontrollen bei Uploads – Die Download-API stellt jede Datei jedem authentifizierten Benutzer zur Verfügung. Dateien sollten auf ihren Besitzer beschränkt sein.
docker-compose down -v
Diese Anwendung ist bewusst verwundbar. Sie dient ausschließlich zu Bildungs- und defensiven Sicherheitsschulungszwecken. Stellen Sie sie nicht in einem Netzwerk bereit, das für nicht vertrauenswürdige Benutzer zugänglich ist. Verwenden Sie diese Techniken nicht gegen Systeme ohne ausdrückliche schriftliche Genehmigung.