OpenMRS Core CVE-2026-40076
CRITICALSeverity by source
CVSS:4.0/AV:N/AC:L/AT:N/PR:H/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X
Primary rating from GitHub Advisory · only source for this CVE.
CVSS VectorGitHub Advisory
CVSS:4.0/AV:N/AC:L/AT:N/PR:H/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X
Lifecycle Timeline
7DescriptionGitHub Advisory
Affected Versions
version ≤ 2.7.8 (latest version at time of disclosure)
https://github.com/openmrs/openmrs-core
Impact
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.
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.
Steps to Reproduce
- Construct a malicious
.omodfile (which is a ZIP/JAR archive) containing a ZIP entry with a path traversal payload in its entry name, such asweb/module/../../../../<target_filename>. Upload this file toPOST /openmrs/ws/rest/v1/modulewith valid admin credentials via Basic Auth.
<img width="1986" height="1102" alt="image" src="https://github.com/user-attachments/assets/647f15de-7e8c-40b9-aba9-d4db5d2e0b52" />
<img width="2048" height="1078" alt="image" src="https://github.com/user-attachments/assets/301412a0-e3b0-4afb-91c2-e9739de3080d" />
- The server parses and loads the module. During
WebModuleUtil.startModule(), entries underweb/module/are automatically extracted. The existing checkPaths.get(name).startsWith("..")only blocks entries beginning with.., so an entry starting withweb/module/passes the check. The../sequences in the remaining path cause the file to be written outside the intendedWEB-INF/view/module/directory - for example, into the web application root at/usr/local/tomcat/webapps/openmrs/.
<img width="1439" height="141" alt="image" src="https://github.com/user-attachments/assets/4bda3b1e-a80e-42ed-af2b-a1da53e8db03" />
- The traversed file is now accessible under the web application root. If the written file is a JSP script, accessing it via the browser triggers server-side execution, achieving RCE.
<img width="1482" height="300" alt="image" src="https://github.com/user-attachments/assets/61936002-78cd-4203-80f0-f0a8702b216c" />
Root Cause Analysis
The vulnerability exists in WebModuleUtil.startModule() (web/src/main/java/org/openmrs/module/web/WebModuleUtil.java).
Vulnerable code:
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.
Correctly protected code in the same codebase:
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.
Bypass of module.allow_web_admin
The 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 writeRemediation
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.
AnalysisAI
Path traversal (Zip Slip) vulnerability in OpenMRS Core ≤ 2.7.8 and 2.8.0-2.8.5 allows authenticated administrators to achieve remote code execution by uploading a malicious .omod module archive to the REST API endpoint POST /openmrs/ws/rest/v1/module. Attackers can write arbitrary JSP files to the Tomcat webroot via crafted ZIP entries containing directory traversal sequences (e.g., web/module/../../../../malicious.jsp), which bypass incomplete path validation in WebModuleUtil.startModule(). The vulnerability also bypasses the module.allow_web_admin security control, as the REST API does not enforce this restriction despite Legacy UI being protected. No vendor-released patch identified at time of analysis for either affected version range.
Technical ContextAI
OpenMRS (Open Medical Record System) is a Java-based electronic health record platform commonly deployed on Apache Tomcat. The vulnerability resides in the module upload functionality that processes .omod files (custom ZIP/JAR archives containing OpenMRS plugins). CWE-22 (Path Traversal) manifests here because WebModuleUtil.startModule() performs an incomplete boundary check using Paths.get(name).startsWith(".."), which only blocks entries beginning with literal ".." but fails to detect traversal sequences embedded after a valid prefix like "web/module/". After substring extraction, the filepath is concatenated directly into the output path without normalization, allowing an attacker to write files outside the intended WEB-INF/view/module/ directory. Ironically, other extraction methods in the same codebase (ModuleUtil.expandJar(), TestInstallUtil.addZippedTestModules()) correctly implement normalize().startsWith() boundary checks, making this a localized oversight rather than a systematic design flaw. The Maven package org.openmrs.web:openmrs-web (confirmed by CPE data) is affected in two version ranges: legacy versions ≤ 2.7.8 and newer versions 2.8.0-2.8.5.
RemediationAI
No vendor-released patch identified at time of analysis for either affected version range (≤ 2.7.8 or 2.8.0-2.8.5). OpenMRS community advisory at https://github.com/openmrs/openmrs-core/security/advisories/GHSA-78fc-9688-w8xw recommends applying normalize().startsWith() boundary validation in WebModuleUtil.startModule() before writing extracted files, consistent with the pattern already used in ModuleUtil.expandJar(). Until a patched release is available, implement compensating controls: (1) Disable the REST API module upload endpoint POST /openmrs/ws/rest/v1/module at the reverse proxy or servlet filter level - trade-off is administrators must use CLI or filesystem-based module deployment; (2) Enforce strict module.allow_web_admin=false in runtime properties AND patch the REST API to honor this property by adding the allowAdmin() check before calling ModuleFactory.loadModule() - note this only prevents upload via web, not exploitation if malicious modules already exist; (3) Restrict administrative access to the REST API using network segmentation, IP allowlisting, or mutual TLS to minimize attack surface - does not prevent insider threats or credential compromise; (4) Enable Tomcat SecurityManager with strict file write permissions to prevent writes outside WEB-INF/view/module/ - may break legitimate module functionality and requires extensive testing. Monitor for suspicious .omod uploads and unexpected JSP files in webroot using file integrity monitoring. Patching guidance in advisory suggests adding outFile.toPath().normalize().startsWith(Paths.get(realPath, "WEB-INF").normalize()) check before FileOutputStream instantiation.
Oracle Java SE 7 Update 6 and earlier contains multiple sandbox bypass vulnerabilities via the ClassFinder and forName m
Remote code execution in IBM Sterling B2B Integrator, Sterling Integrator, and Tivoli Common Reporting allows unauthenti
Java Runtime Environment sandbox bypass via incorrect image channel verification in 2D component allows remote unauthent
Oracle Java SE JDK/JRE 7 and 6 Update 27 and earlier allows remote code execution with complete system compromise throug
JBoss Seam 2 in Red Hat JBoss EAP 4.3.0 fails to sanitize JBoss Expression Language inputs, allowing remote attackers to
Unspecified vulnerability in the Java Runtime Environment (JRE) component in Oracle Java SE 7 update 4 and earlier, 6 up
Multiple vulnerabilities in Oracle Java 7 before Update 11 allow remote attackers to execute arbitrary code by (1) using
Unspecified vulnerability in the Java Runtime Environment (JRE) component in Oracle Java SE 7 Update 2 and earlier, 6 Up
The WLS Security component in Oracle WebLogic Server 10.3.6.0, 12.1.2.0, 12.1.3.0, and 12.2.1.0 allows remote attackers
Unspecified vulnerability in the Java Runtime Environment (JRE) component in Oracle Java SE 7 Update 7 and earlier allow
Remote unauthenticated attackers can execute arbitrary code on Adobe ColdFusion servers through Java deserialization fla
The ExceptionDelegator component in Apache Struts before 2.2.3.1 interprets parameter values as OGNL expressions during
Same weakness CWE-22 – Path Traversal
View allSame technique Path Traversal
View allShare
External POC / Exploit Code
Leaving vuln.today
GHSA-78fc-9688-w8xw