CVE-2026-40076: OpenMRS Core arbitrary file write and code execution via Zip Slip in module upload

Published May 4, 2026
·
Updated

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.allowwebadmin 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

1. Construct a malicious .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/../../../../<targetfilename>. Upload this file to POST /openmrs/ws/rest/v1/module with 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" />

2. The server parses and loads the module. During 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/.

<img width="1439" height="141" alt="image" src="https://github.com/user-attachments/assets/4bda3b1e-a80e-42ed-af2b-a1da53e8db03" />

3. 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:

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.

Correctly protected code in the same codebase:

ModuleUtil.expandJar():

java // ✅ Correct — uses normalize().startsWith() if (!parent.toPath().normalize().startsWith(docBase)) { throw new UnsupportedOperationException("..."); }

TestInstallUtil.addZippedTestModules():

java // ✅ 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.allowwebadmin

The module.allowwebadmin 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

Remediation

Add normalize().startsWith() boundary validation before writing, consistent with the existing pattern in ModuleUtil.expandJar():

java 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.allowwebadmin restriction consistently across all module upload entry points, including the REST API.

Other sources

OpenMRS Core is an open source electronic medical record system platform. In versions 2.7.8 and earlier and versions 2.8.0 through 2.8.5, the module upload endpoint at POST /openmrs/ws/rest/v1/module is vulnerable to a Zip Slip path traversal attack. During automatic extraction of uploaded .omod archives in WebModuleUtil.startModule(), ZIP entries under web/module/ are checked only to see whether the full entry path starts with .., and the remaining path is then concatenated into the destination path without normalization or a boundary check. A crafted archive can therefore include entries such as web/module/../../../../malicious.jsp and cause files to be written outside the intended module directory.

An authenticated attacker with module upload access can write arbitrary files to locations such as the web application root and achieve remote code execution by uploading a JSP file and then requesting it. The issue is compounded by the fact that the module.allowwebadmin runtime property is enforced in the legacy UI controller but not in the REST API upload path, so deployments relying on that property to block web-based module administration remain exposed through the REST endpoint. This issue has been fixed in versions after 2.7.8 in the 2.7.x line and in version 2.8.6 and later.

MITRE

Affected Software

4 affected components
maven/org.openmrs.web:openmrs-web>=2.8.0<=2.8.5
maven/org.openmrs.web:openmrs-web<=2.7.8
OpenMRS Openmrs<=2.7.8
OpenMRS Openmrs>=2.8.0<=2.8.5

Event History

May 4, 2026
Advisory Published
via GitHub·05:39 PM
Data Sourced
via GitHub·05:39 PM
DescriptionSeverityWeaknessAffected Software
May 6, 2026
CVE Published
via MITRE·07:32 PM
Data Sourced
via MITRE·07:32 PM
DescriptionWeakness
Data Sourced
via NVD·08:16 PM
DescriptionSeverityWeaknessAffected Software

Frequently Asked Questions

1

What is the severity of CVE-2026-40076?

CVE-2026-40076 has a high severity level due to its potential for exploitation through a path traversal attack.

2

How do I fix CVE-2026-40076?

To fix CVE-2026-40076, upgrade to OpenMRS version 2.8.0 or later.

3

Who is affected by CVE-2026-40076?

CVE-2026-40076 affects users of OpenMRS versions 2.7.8 and earlier.

4

What type of attack does CVE-2026-40076 involve?

CVE-2026-40076 involves a path traversal attack, also known as Zip Slip.

5

What is the impact of CVE-2026-40076?

The impact of CVE-2026-40076 allows authenticated attackers to upload malicious .omod archives leading to exploitation.

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203