官网更新了v11.5版本后,漏洞分析和PoC逐渐浮出了水面,其实这漏洞结合后台的一些功能是可以进一步实现匿名RCE的。
漏洞说明
伪造任意用户(含管理员)登录漏洞的触发点在扫码登录功能,服务端只取了UID来做用户身份鉴别,由于UID是整型递增ID,从而导致可以登录指定UID用户(admin的缺省UID为1)。
影响版本
通达OA v2017、v11.x < v11.5 支持扫码登录版本。
漏洞分析
v11.5更新修复了2个地方:
- 客户端扫码登录接口;
- Web端扫码登录接口。
Web端扫码登录过程
Web端扫码登录流程大致是这样:
- 第一步
Web端访问 /general/login_code.php?codeuid=随机字符串
生成一个二维码,codeuid
作为这个二维码凭证。
- 第二步
Web端通过循环请求/general/login_code_check.php
,将codeuid
发送到服务端,判断是否有人扫了这个二维码。
- 第三步
移动端扫码这个二维码,然后将codeuid
等数据发送到/general/login_code_scan.php
服务端进行保存。
- 第四步
Web端通过login_code_check.php
取得codeuid
等扫码数据后(其实取数据这一步已经产生$_SESSION["LOGIN_UID"]
登录了),再通过Web端发送到logincheck_code.php
进行登录。
Web端登录请求脚本如下:
可以看到最终的登录数据只发送了UID
到服务端,从而导致了任意用户登录。
Web端补丁分析
这里对比一下v11.4
和v11.5
修复前和修复后的源代码:
源文件: /logincheck_code.php
修复后的版本对UID进行了初始化,并增加token的校验。
从redis中取得token并进行td_authcode()
解密,然后从中取出UID
,避免了前段直接控制UID
。
再看下token的生成方式,源文件: /general/login_code_scan.php
通过PHPSESSID
从在线用户表user_online
中取UID
,然后封装数据MD5哈希后存入redis中。
客户端补丁分析
客户端和Web端大同小异,登录也加了token
校验:
1 | /ispirit/interface/login.php |
同样是增加了token
校验。
漏洞利用
手动漏洞复现可以在浏览器直接打断点修改数据或者BurpSuite修改。
脚本构造数据包的时候只需两步即可:
- 将数据写入缓存: /general/login_code.php
- 从缓存读取登录: /logincheck_code.php
任意用户登录 PoC
知道了漏洞过程,那么实现PoC就很简单了,以获取Web目录绝对路径为例:
- tongda_v11.4_get_webroot_poc.py
修改脚本里的oa_addr
为目标OA地址,成功后会输出当前OA的安装绝对路径。
1 | $ python3 tongda_v11.4_get_webroot_poc.py |
匿名 RCE ExP
有了后台管理权限和Web目录绝对路径,可以利用MySQL日志进一步写Shell,实现匿名RCE:
- tongda_v11.4_rce_exp.py
修改脚本里的oa_addr
为目标OA地址,成功后会在/api/
目录下生成一个test.php
,密码为:cmd
,GET
请求方式。
1 | $ python3 tongda_v11.4_rce_exp.py |
扩展思考
我们脑洞一下这个补丁是否可以绕过:
认证地址一:
源文件: /logincheck_code.php
1 | $authInfo = $redis->get("OA:authcode:token:" . $token); |
如果我们能找到一个可控的:
1 | TRedis::redis()->setex($xxx, $xxx); |
而td_authcode()
的key是固定的,从而就可以伪造token
进行任意用户登录。
认证地址二:
源文件: /ispirit/login_code_check.php
1 | $login_codeuid = TD::get_cache("CODE_LOGIN_PC" . $codeuid); |
如果我们能找到一个可控的:
1 | TD::set_cache($xxx, $xxx); |
也是可以伪造任意用户登录的。
PoC & ExP 传送门: https://github.com/zrools/tools/tree/master/python