Krytyczna

OpenMRS Module Upload Vulnerable to Path Traversal (Zip Slip)

  • Ujawniono: 04.05.2026
  • Aktualizacja: 11.10.2026 12:22

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

  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/../../../../<target_filename>. 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" />
  1. 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" />
  1. 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.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 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.allow_web_admin restriction consistently across all module upload entry points, including the REST API.

Czego dotyczy

Kliknij technologię, aby zobaczyć wszystkie dotyczące jej podatności.

PlatformaPaczkaPodatne wersjePoprawka w wersji
Java / Kotlin (Maven) org.openmrs.web:openmrs-web <= 2.7.8, >= 2.8.0, <= 2.8.5 brak poprawki

Odnośniki

Ocena CVSS 8.7/10
Wektor CVSS
CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:N
Słabość (CWE)
CWE-22
Platforma
Java / Kotlin (Maven)
Paczka
org.openmrs.web:openmrs-web
Ujawniono
04.05.2026
Aktualizacja
11.10.2026 12:22

Źródła i licencje

OSV / GitHub Advisory Database - GHSA-78fc-9688-w8xw - CC BY 4.0

Historia podatności: org.openmrs.web:openmrs-web

Wszystkie opisane podatności tej paczki (Java / Kotlin (Maven)), od najnowszej. Widać, czy to jednorazowa wpadka, czy paczka, której trzeba pilnować.

Wszystkich wpisów
2
Pierwsza → ostatnia
05.2026 05.2026
Bez poprawki
1
Wykorzystywana w atakach
0

Rozkład istotności

  • Krytyczna 1
  • Wysoka 1

Ujawnienia w latach

  1. 2026: 2
  1. OpenMRS Module Upload Vulnerable to Path Traversal (Zip Slip) ten wpis
    Krytyczna · 8.7 CVE-2026-40076 <= 2.7.8, >= 2.8.0, <= 2.8.5 brak poprawki
  2. Wysoka · 7.5 CVE-2026-40075 <= 2.7.8, >= 2.8.0, < 2.8.6 2.8.6

Dowiedz się, które podatności dotyczą Twojej aplikacji

Po podłączeniu integracji DockRay i włączeniu raportowania oprogramowania DockRay porównuje zgłoszone wersje komponentów ze znanymi podatnościami i powiadamia o potwierdzonych dopasowaniach. Zakres zależy od obsługiwanej technologii i dostępnych biuletynów.

Podłącz swoją aplikację
Newsletter DockRay

Bezpieczeństwo aplikacji w Twojej skrzynce

Najważniejsze podatności, konkretne zalecenia i praktyczne wskazówki dla Twoich aplikacji. Wybierz technologie i śledź to, co ma znaczenie dla Twojego projektu.

  • Codzienne zestawienie nowych podatności, także krytycznych.
  • Powiadomienia o ważnych aktualizacjach.
  • Praktyczne porady o bezpieczeństwie i monitoringu aplikacji.

Pozostaw puste, aby otrzymywać informacje o wszystkich technologiach.

Zapis jest bezpłatny. Wypiszesz się jednym kliknięciem w wiadomości.