ORG.OPENMRS.WEB:OPENMRS-WEB · CVE-2026-40076 · Package
NO FIX YET
The OpenMRS REST endpoint for uploading modules extracts ZIP entries without checking that paths stay inside the module directory. An authenticated attacker can write a JSP file into the web application root and run code on the server.
first public May 4, 2026 · 2 sources
Check it with your coding agent
It reads this page and tells you if your code or app is affected.
Any OpenMRS deployment running openmrs-web in the affected ranges where an administrator can upload modules through the REST API. The flaw is in the module upload path, so deployments that do not expose module upload to admins are less exposed. Versions outside the affected ranges are not affected.
Not affected if versions-outside-range
Who cares java, openmrs, tomcat
openmrs-web version in the deployed WAR or WEB-INF/lib; it is affected at <= 2.7.8 and 2.8.0 through 2.8.5.version ≤ 2.7.8 (latest version at time of disclosure)
https://github.com/openmrs/openmrs-core
The endpoint POST /openmrs/ws/rest/v1/module is vulnerable to a path traversal (Zip Slip) attack. An authenticated attacker can upload a crafted .omod archive containing ZIP entries with directory traversal sequences. Upon automatic extraction by the server, the incomplete path validation in WebModuleUtil.startModule() fails to prevent entries such as web/module/../../../../malicious.jsp from being written outside the intended module directory. If the traversal target falls within the web application root (e.g., /usr/local/tomcat/webapps/openmrs/), the attacker achieves arbitrary file write and subsequent Remote Code Execution.
Notably, other extraction methods in the same codebase (ModuleUtil.expandJar(), TestInstallUtil.addZippedTestModules()) are properly protected with normalize().startsWith() checks — this vulnerability is an oversight where the same fix was not applied.
Are you affected?
Ask from here. InfraStack checks the affected versions against your stack and answers in your inbox.
InfraStack checks the affected versions against your stack and answers in Slack, with the evidence and the fix.
InfraStack checks it against your stack and answers on your report, with the evidence and the fix.
POST /openmrs/ws/rest/v1/moduleUnknown from the sources.
Furthermore, the module.allow_web_admin runtime property, which is intended to restrict administrators from managing modules via the web interface, only gates the Legacy UI controller entry point. The REST API endpoint POST /openmrs/ws/rest/v1/module does not check this property, allowing this restriction to be fully bypassed.
.omod file (which is a ZIP/JAR archive) containing a ZIP entry with a path traversal payload in its entry name, such as web/module/../../../../<target_filename>. Upload this file to POST /openmrs/ws/rest/v1/module with valid admin credentials via Basic Auth.WebModuleUtil.startModule(), entries under web/module/ are automatically extracted. The existing check Paths.get(name).startsWith("..") only blocks entries beginning with .., so an entry starting with web/module/ passes the check. The ../ sequences in the remaining path cause the file to be written outside the intended WEB-INF/view/module/ directory — for example, into the web application root at /usr/local/tomcat/webapps/openmrs/.The vulnerability exists in WebModuleUtil.startModule() (web/src/main/java/org/openmrs/module/web/WebModuleUtil.java).
Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
String name = entry.getName();
// ❌ Incomplete check — only blocks entries starting with ".."
if (Paths.get(name).startsWith("..")) {
throw new UnsupportedOperationException("...");
}
if (name.startsWith("web/module/")) {
String filepath = name.substring(11);
StringBuilder absPath = new StringBuilder(realPath + "/WEB-INF");
absPath.append("/view/module/");
absPath.append(mod.getModuleIdAsPath()).append("/").append(filepath);
// ❌ No normalize() or startsWith() boundary check before writing
File outFile = new File(absPath.toString().replace("/", File.separator));
outStream = new FileOutputStream(outFile, false);
inStream = jarFile.getInputStream(entry);
OpenmrsUtil.copyFile(inStream, outStream);
}
}
Why the check fails: For an entry named web/module/foo/../../../../evil.jsp, Paths.get(name) starts with web, not .., so the check passes. After name.substring(11), the filepath foo/../../../../evil.jsp is concatenated directly into the output path without normalization, resulting in a write outside the intended directory.
ModuleUtil.expandJar():
// ✅ Correct — uses normalize().startsWith()
if (!parent.toPath().normalize().startsWith(docBase)) {
throw new UnsupportedOperationException("...");
}
TestInstallUtil.addZippedTestModules():
// ✅ Correct — uses normalize().startsWith()
if (!zipEntryFile.toPath().normalize().startsWith(moduleRepository.toPath().normalize())) {
throw new IOException("Bad zip entry");
}
The fix pattern is already known and applied elsewhere in the codebase. WebModuleUtil.startModule() is an oversight.
module.allow_web_adminThe module.allow_web_admin property only restricts module operations at the Legacy UI layer (ModuleListController). The REST API endpoint does not consult this property:
Legacy UI: POST /admin/modules/moduleList.form → allowAdmin() check → [BLOCKED]
REST API: POST /ws/rest/v1/module → No allowAdmin() check → [ALLOWED]
↓
ModuleFactory.loadModule()
↓
WebModuleUtil.startModule() ← Zip Slip here, no allowAdmin check
↓
FileOutputStream.write() ← Arbitrary file write
Add normalize().startsWith() boundary validation before writing, consistent with the existing pattern in ModuleUtil.expandJar():
File outFile = new File(absPath.toString().replace("/", File.separator));
// ✅ Add this check
if (!outFile.toPath().normalize().startsWith(
Paths.get(realPath, "WEB-INF").normalize())) {
throw new UnsupportedOperationException(
"Zip entry '" + name + "' would be written outside the allowed directory.");
}
Additionally, enforce the module.allow_web_admin restriction consistently across all module upload entry points, including the REST API.
This page as .md · .json · CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:N
GitHub Advisory Database, CC-BY 4.0 · NVD data is provided by NIST and is public domain. Posts quoted belong to their authors.