Frappe LMS - v2.35.0 - Cross-Site Scripting as student

By 0xhamy 05:24 AM - October 14th 2025
Type software
Product Environment web
Product Name Frappe LMS
Product Vendor Frappe
Product Version 2.35.0
Product Link https://github.com/frappe/lms
Vulnerability Name Cross-Site Scripting
Severity High
CVSS String
CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:H/I:H/A:N
CVSS Score 7.3
CVE ID CVE-2025-11282
Vendor Acknowledgement Yes
Affected digital Assets
10
Affected Users
50000
Date of Reporting Sep 21, 2025
PoC Exploit -
Credit 0xhamy,KhanMarshai

Description

Frappe LMS version 2.35.0 is vulnerable to a file upload flaw that enables stored cross-site scripting (XSS).
The application incorrectly handles uploaded HTML and SVG files. Although the UI shows visual error messages, malicious files can still be uploaded and later executed in users’ browsers.

Vulnerability Details

The file upload feature allows users to bypass file-type restrictions by switching from “Image Files” to “All Files” and uploading crafted payloads.
While the platform presents a visual error, the files are still saved, and references to them can be accessed.
When other users or administrators view the uploaded file, arbitrary JavaScript payloads execute in their browser.

While this did not initially lead us to account takeover via making POST requests like this report, at the time of reporting, we were still able to steal user's email addresses and admin status.

Further research (must read)

A few weeks after this vulnerability was patched, I figured that instead of making a POST request to change admin's password, why not just mimic user clicks?

For example, with JavaScript we can technically find elements and fill them with data, we can also target elements and click on them, no mouse required, JavaScript can do it just fine.
This led me to the creation of the following payload:

https://gist.github.com/0xHamy/02b03a5264ac30bcc24bc4bf306cfb3f?short_path=69ba2bd

This SVG image, when loaded in a browser, triggers a JavaScript function via its onload event to automate a password change for an "Administrator" user on Frappe LMS. It creates a hidden iframe to load the user's settings tab, waits for it to fully load, then accesses the iframe's document (or falls back to the main document if access fails due to same-origin policy). Inside the target document, it locates a collapsible "Change Password" section, expands it if collapsed, fills the new password field with newPassword-123 and dispatches input/change events to simulate user interaction and trigger any validation.

After a short delay, it finds and clicks the "Save" button to apply the change, logging progress to the console throughout, and finally removes the iframe after 10 seconds for cleanup; if the iframe creation fails entirely, it attempts the same automation directly on the current page's document.

Why did we fail to escalate severity initially?

Our existing XSS severity escalation methods has always been these two:

  1. Performing state-changing GET requests
  2. Performing state-changing POST requests

But both of these rely on the absence of CSRF protection, however the new payload we created can bypass CSRF because it has nothing to do with CSRF, the more efficient way to block it would be using strict CSP rules and preventing XSS itself. Because as of right now, we are able to bypass all of Frappe's framework's security protections.

Initial severity vs. current severity

The initial severity was 4.6 (Medium) with a CVSS string of CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:L/I:L/A:N, but now that we are able to takeover admin's account if they visit a malicious SVG/HTML file, the severity increases to:

  • 7.3 (High)
  • CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:H/I:H/A:N

Steps to Reproduce

  1. Log in as administrator.
    Navigate to:
    http://127.0.0.1:8000/app/user?enabled=1

  2. Create a student account.

    • Add a new user.
    • Assign the role: LMS Student.
  3. Create an assignment.

    • Go to: http://127.0.0.1:8000/lms/assignments
    • Create a new assignment with type set to Text.
  4. Create a course and attach the assignment.

    • Navigate to: http://127.0.0.1:8000/lms/courses
    • Create a course.
    • Add a chapter and attach the assignment.
    • Publish the course (optional, the issue persists even if unpublished).
  5. Log in as the student user.

    • Open the course assignment page:
      http://127.0.0.1:8000/lms/courses/MyGrandCourse/learn/2-1
  6. Upload a malicious file.

    • In the editor, click the Image icon to browse files.
    • Switch file type filter from Image FilesAll Files.
    • Upload a crafted HTML payload (example: https://gist.github.com/0xHamy/44ea8308361cc0e5c84666118167e1af).

    Note: An error appears, but the file is still saved.

  7. Trigger the payload.

    • Click Save in the editor.
    • Add a caption (e.g., x) when prompted.
    • An invalid image icon will appear — right-click it and open in a new tab.
  8. Set up a server to capture data.

    • Host a PHP listener, such as: https://gist.github.com/0xHamy/827831b5e3f26b1fd715a5c1aeaa58bd
    • When the malicious file is opened, sensitive data such as user email, administrator status, and full name are exfiltrated to the attacker’s server.
  9. Alternative vector: SVG uploads.

    • SVG files can also be uploaded in the same way.
    • Although CVE-2025-55006 claimed to address this, the patch only prevents visual rendering errors and does not properly enforce backend validation.

Recommendation

  • Enforce strict server-side validation of uploaded file types.
  • Reject non-image files at the backend, not just through the UI.
  • Sanitize and neutralize SVG/HTML uploads or disallow them entirely.
  • Implement Content Security Policy (CSP) headers to mitigate XSS impact.