最近爆了个通达 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 也是可以任意控制的) 就退出程序了,我们可以全局搜索一下这个缓存在哪里设置了。
很快找到一处: ispiritlogin_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 "
";
?>
这里给重要的三句话写了注释。我们只要直接访问一次这个文件就可以伪造了。
复现测试
首先访问一次 /ispirit/login_code.php:
存下这个 codeuid。然后访问 /logincheck_code.php:
UID 设置成 1,这个 ID 默认是管理员。然后 CODEUID 设置成: _PC+codeuid:
随便访问个需要验证的 url:/pda/main.php
第二处任意登录
一样的思路,我们全局搜索会找到在文件 ispiritlogin_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 的代码,在文件 generallogin_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 的地方,或者找到一处可以控制键值的缓存,即可绕过。
第二处修复 generallogin_code_scan.php
在设置 CODE_INFO_PC前进行了权限验证,这里根据传入的 session 查询此 session 是否登陆过,如果没登陆过就退出程序
思考及总结
这个漏洞其实挺简单的,但是到现在才发现,看来挖掘这样的洞更需要一些耐心和细心。由于这个程序用了全局覆盖,我们可以直接覆盖 _SESSION 里的数据,但是 _SESSION 是存在 redis 中的。所以如果有一处先开启 session_start 然后引入了 session.php 文件,即可直接覆盖 _SESSION 里的数据。








