CVE-2026-39405
Description
Frappe Learning Management System (LMS) is a learning system that helps users structure their content. In versions 2.50.0 and below, a user with course editing role could upload a SCORM ZIP package to write files outside the intended directory. This issue has been resolved in version 2.50.1.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Frappe LMS <=2.50.0 suffers from a path traversal vulnerability in SCORM package upload, allowing file write outside the intended directory.
Vulnerability
In Frappe Learning Management System (LMS) versions 2.50.0 and below, a path traversal vulnerability exists when processing SCORM ZIP packages. A user with course editing privileges can craft a malicious ZIP file that writes files to arbitrary locations outside the intended upload directory, due to insufficient validation of file paths within the archive. [2]
Exploitation
An attacker needs a valid account with course editing role. They upload a specially crafted SCORM ZIP package containing path traversal sequences (e.g., ../../) in filenames. The application extracts the archive without properly sanitizing paths, allowing the attacker to write files to arbitrary directories on the server. [2]
Impact
Successful exploitation enables file write outside the designated directory, potentially leading to remote code execution if the attacker overwrites system files (e.g., web server scripts). The attacker gains arbitrary file write capabilities at the privilege level of the application process. [2]
Mitigation
The vulnerability is fixed in version 2.50.1, released on 2026-05-20. Users should upgrade to 2.50.1 or later. No workaround is available if upgrade is not possible. [1][2]
AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected products
2Patches
2c690dbbacfd4Merge pull request #2275 from frappe/mergify/bp/main/pr-2274
1 file changed · +13 −2
lms/lms/api.py+13 −2 modified@@ -1003,9 +1003,20 @@ def upsert_chapter( def extract_package(course: str, title: str, scorm_package: dict): package = frappe.get_doc("File", scorm_package.name) zip_path = package.get_full_path() - # check_for_malicious_code(zip_path) + scorm_root = os.path.realpath(frappe.get_site_path("public", "scorm")) extract_path = frappe.get_site_path("public", "scorm", course, title) - zipfile.ZipFile(zip_path).extractall(extract_path) + + if not os.path.realpath(extract_path).startswith(scorm_root + os.sep): + frappe.throw(_("Invalid course or chapter name")) + + with zipfile.ZipFile(zip_path, "r") as zf: + dest = os.path.realpath(extract_path) + for name in zf.namelist(): + target = os.path.realpath(os.path.join(extract_path, name)) + if not target.startswith(dest + os.sep) and target != dest: + frappe.throw(_("Invalid file path in package")) + zf.extractall(extract_path) + return extract_path
fb1d72ab410efix: prevent path transversals in scorm upload
1 file changed · +13 −2
lms/lms/api.py+13 −2 modified@@ -1003,9 +1003,20 @@ def upsert_chapter( def extract_package(course: str, title: str, scorm_package: dict): package = frappe.get_doc("File", scorm_package.name) zip_path = package.get_full_path() - # check_for_malicious_code(zip_path) + scorm_root = os.path.realpath(frappe.get_site_path("public", "scorm")) extract_path = frappe.get_site_path("public", "scorm", course, title) - zipfile.ZipFile(zip_path).extractall(extract_path) + + if not os.path.realpath(extract_path).startswith(scorm_root + os.sep): + frappe.throw(_("Invalid course or chapter name")) + + with zipfile.ZipFile(zip_path, "r") as zf: + dest = os.path.realpath(extract_path) + for name in zf.namelist(): + target = os.path.realpath(os.path.join(extract_path, name)) + if not target.startswith(dest + os.sep) and target != dest: + frappe.throw(_("Invalid file path in package")) + zf.extractall(extract_path) + return extract_path
Vulnerability mechanics
Root cause
"Missing path traversal validation in SCORM ZIP extraction allows an attacker to write files outside the intended extraction directory."
Attack vector
An attacker with course editing privileges uploads a SCORM ZIP package containing entries with path traversal sequences (e.g., `../` in filenames). The vulnerable `extract_package` function in `lms/lms/api.py` calls `zipfile.ZipFile.extractall()` without validating that extracted file paths remain under the intended `scorm/<course>/<title>` directory [patch_id=877094][patch_id=877095]. This allows writing arbitrary files to the server's filesystem within the `public/scorm` parent directory or beyond, depending on the ZIP contents.
Affected code
The vulnerable code is in the `extract_package` function in `lms/lms/api.py`. The original code called `zipfile.ZipFile(zip_path).extractall(extract_path)` without validating that individual ZIP entries stay within the intended `scorm/<course>/<title>` directory. The patch adds path traversal checks using `os.path.realpath()` on both the destination root and each extracted file target.
What the fix does
The patch adds two layers of path validation. First, it resolves `extract_path` with `os.path.realpath()` and checks that it starts with the `scorm_root` path plus a separator, preventing course/chapter names that escape the SCORM root [patch_id=877094][patch_id=877095]. Second, before calling `extractall()`, it iterates over every entry in the ZIP archive, resolves each target path with `os.path.realpath()`, and verifies it starts with the destination directory. If any entry would write outside the intended directory, the operation is aborted with an error.
Preconditions
- authAttacker must have a course editing role in the Frappe LMS application
- inputAttacker must be able to upload a SCORM ZIP package to the system
Generated on May 20, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
2News mentions
0No linked articles in our index yet.