Where
-Infinity
0

OpenMRS is an open source electronic medical record system platform. Prior to versions 1.23.0 and 2.10.0, an authenticated user can trigger administrative DWR services. Specifically, the startHl7ArchiveMigration method is accessible, which should be restricted to admin-level accounts. Versions 1.23.0 and 2.10.0 patch the issue.

First published (updated )
Severity
8.2
Path Traversal
AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N

Affected Versions

version ≤ 2.7.8 (latest version at time of disclosure)

https://github.com/openmrs/openmrs-core

Impact

The /openmrs/moduleResources/{moduleid} endpoint in OpenMRS Core is vulnerable to a path traversal attack. The ModuleResourcesServlet does not properly validate user-supplied path input, allowing an attacker to traverse directories and read arbitrary files from the server filesystem (e.g., /etc/passwd, application configuration files containing database credentials).

This endpoint serves static module resources (CSS, JS, images) and is not protected by authentication filters, as these resources are required for rendering the login page. Therefore, this vulnerability can be exploited by an unauthenticated attacker.

Note: Successful exploitation requires the target deployment to run on Apache Tomcat < 8.5.31, where the ..; path parameter bypass is not mitigated by the container. Deployments on Tomcat ≥ 8.5.31 / ≥ 9.0.10 are protected at the container level, though the underlying code defect remains.

Steps to Reproduce

1. Identify a valid installed module ID on the target OpenMRS instance (e.g., legacyui). 2. Send the following HTTP request:

<img width="1038" height="798" alt="image" src="https://github.com/user-attachments/assets/7d10ee0e-4d81-4c01-bc84-a1bf5715f170" />

3. The server responds with HTTP 200 and the contents of /etc/passwd:

<img width="1028" height="843" alt="image" src="https://github.com/user-attachments/assets/b6806a7e-ff52-4f51-8f7f-7ea4e9754d10" />

Root Cause Analysis

The vulnerability exists in ModuleResourcesServlet.java (web/src/main/java/org/openmrs/module/web/ModuleResourcesServlet.java).

The getFile() method constructs a filesystem path from user-controlled input without performing path boundary validation:

java protected File getFile(HttpServletRequest request) { // Step 1: User-controlled path input String path = request.getPathInfo();

// Step 2: Extract module from path prefix Module module = ModuleUtil.getModuleForPath(path); if (module == null) { return null; }

// Step 3: Strip module ID prefix — no traversal check String relativePath = ModuleUtil.getPathForResource(module, path);

// Step 4: Concatenate into absolute path String realPath = getServletContext().getRealPath("") + MODULEPATH + module.getModuleIdAsPath() + "/resources" + relativePath; // contains "/../../../etc/passwd"

realPath = realPath.replace("/", File.separator);

// Step 5: No normalize().startsWith() boundary check File f = new File(realPath); if (!f.exists()) { return null; }

return f; // Arbitrary file returned to client }

The helper method ModuleUtil.getPathForResource() only strips the module ID prefix and performs no sanitization:

java public static String getPathForResource(Module module, String path) { if (path.startsWith("/")) { path = path.substring(1); } return path.substring(module.getModuleIdAsPath().length()); // Returns unsanitized remainder, e.g., "/../../../../../../etc/passwd" }

The resulting path resolves as:

{webapp}/WEB-INF/view/module/legacyui/resources/../../../../../../etc/passwd → /etc/passwd

Notably, the same codebase already implements correct path traversal protection in StartupFilter.java:

java // StartupFilter.java — correct protection fullFilePath = fullFilePath.resolve(httpRequest.getPathInfo()); if (!(fullFilePath.normalize().startsWith(filePath))) { log.warn("Detected attempted directory traversal..."); return; // Request rejected }

This check is absent from ModuleResourcesServlet.

Remediation

Add a path boundary check after constructing realPath and before returning the File object. The fix should use normalize() + startsWith() to ensure the resolved path stays within the allowed module resources directory:

java File f = new File(realPath); Path allowedBase = Paths.get(getServletContext().getRealPath(""), "WEB-INF", "view", "module"); if (!f.toPath().normalize().startsWith(allowedBase.normalize())) { log.warn("Blocked path traversal attempt: {}", request.getPathInfo()); return null; }

This is consistent with the existing pattern used in StartupFilter.java and TestInstallUtil.java within the same project.

1 / 2
Source: GitHub
First published (updated )
Severity
4.8
EPSS
0.03%
XSS
CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:C/C:L/I:L/A:N

A stored cross-scripting (XSS) vulnerability in Openmrs v2.4.3 Build 0ff0ed allows attackers to execute arbitrary web scripts or HTML via injecting a crafted payload into the personName.middleName parameter at /openmrs/admin/patients/shortPatientForm.form.

First published (updated )
Severity
6.8
EPSS
0.02%
CSRF
CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:U/C:H/I:H/A:H

A Cross-Site Request Forgery (CSRF) in Openmrs 2.4.3 Build 0ff0ed allows attackers to execute arbitrary operations via a crafted GET request.

First published (updated )
Severity
5.4
EPSS
0.03%
XSS
CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N

A reflected cross-site scripting (XSS) vulnerability in the component /legacyui/quickReportServlet of Openmrs 2.4.3 Build 0ff0ed allows attackers to execute arbitrary JavaScript in the context of a user's browser via a crafted payload injected into the reportType parameter.

First published (updated )
Severity
8
EPSS
0.03%
CSRF
CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:H/I:H/A:H

A Cross-Site Request Forgery (CSRF) in the component /admin/users/user.form of Openmrs 2.4.3 Build 0ff0ed allows attackers to execute arbitrary operations via a crafted request. In this case, an attacker could elevate a low-privileged account to an administrative role by leveraging the CSRF vulnerability at the /admin/users/user.form endpoint.

First published (updated )
Severity
9.4
Path Traversal
AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:N

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.

1 / 2
Source: GitHub
First published (updated )
Severity
10
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

OpenMRS before 2.24.0 is affected by an Insecure Object Deserialization vulnerability that allows an unauthenticated user to execute arbitrary commands on the targeted system via crafted XML data in a request body.

First published (updated )
Severity
6.1
Input Validation, XSS
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N

OpenMRS 2.9 and prior copies "Referrer" header values into an html element named "redirectUrl" within many webpages (such as login.htm). There is insufficient validation for this parameter, which allows for the possibility of cross-site scripting.

First published (updated )
Severity
6.1
XSS
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N

In OpenMRS 2.9 and prior, the UI Framework Error Page reflects arbitrary, user-supplied input back to the browser, which can result in XSS. Any page that is able to trigger a UI Framework Error is susceptible to this issue.

First published (updated )
Severity
6.1
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N

In OpenMRS 2.9 and prior, he import functionality of the Data Exchange Module does not properly redirect to a login page when an unauthenticated user attempts to access it. This allows unauthenticated users to use a feature typically restricted to administrators.

First published (updated )
Severity
6.1
XSS
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N

In OpenMRS 2.9 and prior, the app parameter for the ActiveVisit's page is vulnerable to cross-site scripting.

First published (updated )
Severity
6.1
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N

In OpenMRS 2.9 and prior, the export functionality of the Data Exchange Module does not properly redirect to a login page when an unauthenticated user attempts to access it. This allows the export of potentially sensitive information.

First published (updated )
Severity
6.1
XSS
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N

In OpenMRS 2.9 and prior, the sessionLocation parameter for the login page is vulnerable to cross-site scripting.

First published (updated )

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