• 通达OA任意文件上传并利用文件包含导致远程代码执行漏洞分析


    前言

    初次投稿,代码审计新手,分析过程有问题大佬们多多指点

    关于这个漏洞,利用方式大致有两种,一种是包含日志文件,一种是绕过身份验证文件上传然后在文件包含。第一种利用方式没什么说的,第二种绕过身份验证和上传以后文件的路径有点意思,写篇文章记录一下。

    文件上传

    入口文件/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";
    }

    这里主要需要个参数P,P存在不为空然后获取session,否则的话进入auth.php身份认证

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

    这里是第20行到第32行,这里说明需要DEST_UID参数,不能为空,这里涉及到td_verify_ids,大致意思是这个参数需要是0-9开头的任意字符串。

    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,要不然就报错

    然后继续往下

    if (1 <= count($_FILES)) {
    
        $ATTACHMENTS = upload("ATTACHMENT", $MODULE, false);
    
        $ATTACHMENT_ID = substr($ATTACHMENTS["ID"], 0, -1);
        $ATTACHMENT_NAME = substr($ATTACHMENTS["NAME"], 0, -1);
    }
    else {
        $dataBack = array("status" => 0, "content" => "-ERR " . _("无文件上传"));
        echo json_encode(data2utf8($dataBack));
        exit();
    }

    如果有文件上传进入upload,否则报错无文件上传

    跟进upload函数,在utility_file.php第1665行,里面实现了一个上传功能,结果返回一个数组ATTACHMENTS

    里面$ATTACHMENTS["id"]值是从add_attach函数返回。

    跟进add_attach函数在第1854行

    function add_attach($SOURCE_FILE, $ATTACH_NAME, $MODULE, $YM, $ATTACH_SIGN, $ATTACH_ID)
    {
        $ATTACH_PARA_ARRAY = TD::get_cache("SYS_ATTACH_PARA");
        $ATTACH_POS_ACTIVE = $ATTACH_PARA_ARRAY["SYS_ATTACH_POS_ACTIVE"];
        $ATTACH_PATH_ACTIVE = $ATTACH_PARA_ARRAY["SYS_ATTACH_PATH_ACTIVE"];
    
        if (!file_exists($SOURCE_FILE)) {
            return false;
        }
    
        if ($MODULE == "") {
            $MODULE = attach_sub_dir();
        }
    
        if ($YM == "") {
            $YM = date("ym");
        }
    
        $PATH = $ATTACH_PATH_ACTIVE . $MODULE;
        if (!file_exists($PATH) || !is_dir($PATH)) {
            @mkdir($PATH, 448);
        }
    
        $PATH = $PATH . "/" . $YM;
        if (!file_exists($PATH) || !is_dir($PATH)) {
            @mkdir($PATH, 448);
        }
    
        $ATTACH_NAME = (is_default_charset($ATTACH_NAME) ? $ATTACH_NAME : iconv("utf-8", MYOA_CHARSET, $ATTACH_NAME));
        $EXT_NAME = substr($ATTACH_NAME, strrpos($ATTACH_NAME, "."));
        $ATTACH_NAME = str_replace($EXT_NAME, strtolower($EXT_NAME), $ATTACH_NAME);
        $ATTACH_FILE = (MYOA_ATTACH_NAME_FORMAT ? md5($ATTACH_NAME) . ".td" : $ATTACH_NAME);
        $ATTACH_ID = mt_rand();
        $FILENAME = $PATH . "/" . $ATTACH_ID . "." . $ATTACH_FILE;
    
        if (file_exists($FILENAME)) {
            $ATTACH_ID = mt_rand();
            $FILENAME = $PATH . "/" . $ATTACH_ID . "." . $ATTACH_FILE;
        }
    
        $AID = mysql_insert_id();
        $ATTACH_ID_NEW = $AID . "@" . $YM . "_" . $ATTACH_ID;
        return $ATTACH_ID_NEW;
    }

    可以看到返回值$ATTACH_ID_NEW有三部分组成$AID,$YM,$ATTACH_ID

    这里注意一下,$ATTACH_ID_NEW只有文件名和一部分路径。完整的路径是$PATH,这里可以看到$PATH先拼接$MODULE然后拼接$YM,可以往上追溯到$SYS_ATTACH_PATH_ACTIVE

    utility_file.php第403行

    定义了$SYS_ATTACH_PATH_ACTIVE值

    $SYS_ATTACH_PATH_ACTIVE = MYOA_ATTACH_PATH2;

    跟踪到td_config.php 第134行

    define("MYOA_ATTACH_PATH2", $ATTACH_PATH2);

    这里定义常量MYOA_ATTACH_PATH2,内容是 $ATTACH_PATH2

    往上翻第3行到第15行

    $ROOT_PATH = (isset($_SERVER["DOCUMENT_ROOT"]) ? $_SERVER["DOCUMENT_ROOT"] : "");
    
    if ($ROOT_PATH == "") {
        $ROOT_PATH = str_replace("\", "/", realpath(dirname(__FILE__) . "/../"));
    }
    
    if (substr($ROOT_PATH, -1) != "/") {
        $ROOT_PATH .= "/";
    }
    $ATTACH_PATH2 = realpath($ROOT_PATH . "../") . "/attach/";

    这里跟到/inc/../../然后拼接/attach/

    然后UPLOAD_MODE值随便为1,2,3中的任意一个数字都可以返回文件名字和部分路径

    文件包含漏洞

    这部分比较简单

    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格式的值,然后转化为数组,当键值为url并且值不为空是include_once包含url

    最后
    感谢大佬分享的payload
    附上大佬的GitHub https://github.com/jas502n/OA-tongda-RCE

  • 相关阅读:
    获取汉字和字母的长度
    JavaScript 获取对象中第一个属性
    JS函数的length属性
    原型模式 -- JavaScript语言的灵魂
    建造者模式
    阻止form元素内的input标签回车提交表单
    工厂方法模式(安全的工厂方法)
    微信自研生产级paxos类库PhxPaxos实现原理介绍
    图解分布式一致性协议Paxos
    Paxos协议超级详细解释+简单实例
  • 原文地址:https://www.cnblogs.com/0daybug/p/12573941.html
Copyright © 2020-2023  润新知