致远OA A8-V5 任意用户登录漏洞分析
漏洞环境
致远A8 V7.0
漏洞利用
接口 /seeyon/thirdpartyController.do
| 1 | GET /seeyon/thirdpartyController.do?method=access&enc=TT5uZnR0YmhmL21qb2wvY2N0L3BxZm8nTj4uODM4NDE0MzEyNDM0NTg1OTI3OSdVPjI3OTM2MjU4MjQ4ODY= HTTP/1.1 | 
可以在响应包中得到一个JSESSIONID,在携带JSESSIONID 访问/seeyon/online.do接口进行验证。
| 1 | GET /seeyon/online.do?method=showOnlineUser HTTP/1.1 | 
|  | 
|---|
可用该JSESSIONID访问需要登录的接口,配合后台一些RCE漏洞进行利用。
漏洞分析
在源码中全局搜索thirdpartyController 关键字
|  | 
|---|
可以根据路由接口找到对应配置文件中类文件的映射,跟入到com.seeyon.ctp.portal.sso.thirdpartyintegration.controller.ThirdpartyController中。
根据exp可以得知调用了该类的access方法。
在这个方法中代码还是比较多,我做了一些简化,保留了关键的几个代码片段。
| 1 | public ModelAndView access(HttpServletRequest request, HttpServletResponse response) throws Exception { | 
|  | 
|---|
首先,在代码的276行处,接收了enc参数并使用LightWeightEncoder.decodeString进行解码,跟入到该方法中。
| 1 | public static String decodeString(String encodeString) { | 
这个方法功能是对传入的字符串进行base64解码,然后再将解码后的字符串每一个字符向后移动一位。
如传入bcd->base64编码->调用decodeString->abc。
|  | 
|---|
很简单,接下来往下面看。
在286-307行处
|  | 
|---|
这段代码是对经过经过decodeString解码后的值做一个分割操作,首先是将enc的值通过&分割成一个字符串列表,然后再进行遍历,再根据=再次分割字符串,将=前的值作为key放入encMap中,=后面的作为key的值 。如test=1&test2=2&test3=3,就会被拆成{"test": 1,"test2": 2, "test3": 3}。
再往下边看。
| 1 | Constants.login_useragent_from userAgentFrom = login_useragent_from.pc; | 
上面这段代码是从encMap中根据键L、P、T、M拿到对应的值分别赋值给linkType、path、startTimeStr、_memberId。
其中linkType、startTimeStr、_memberId所获取到的值很关键,也很重要。
在316-344行处
|  | 
|---|
这里之所以要进入这个if语句体中,是因为需要执行333这条语句,从encMap中获取到_memberId的值,后这个值将会有很大的作用。
在执行在拿到_memberId值之前,先是判断当前的时间是否大于了指定的时间,如果是直接返回了,很显然,我们肯定不想直接返回。
| 1 | if ((System.currentTimeMillis() - timeStamp) / 1000L > (long)(this.messageMailManager.getContentLinkValidity() * 60 * 60)) { | 
这里的stimeStamp直接可控,所以可以直接传入一个很大的值,可以传入一个跟System.currentTimeMillis()获取到的一样的值,这个条件不成立就不会进入。然后就会在encMap中拿到_memberId的值。
最后
| 1 | link = (String)UserMessageUtil.getMessageLinkType().get(linkType); | 
这里也是一个很关键的地方,如果link的值为空,那么会直接返回,所以这里必须通过linkType拿到点什么。
先跟入getMessageLinkType方法中。
|  | 
|---|
调用了userMessageManager.getMessageLinkTypes()获取值,继续跟入
|  | 
|---|
找到对应的实现类。
|  | 
|---|
继续跟入
|  | 
|---|
发现直接返回了messageLinkTypes,接下来在这个类中看看哪儿给messageLinkTypes传入了参数。
|  | 
|---|
在129-136行处,加载了/base/message-link.properties配置文件,然后把值赋put进messageLinkTypes中。
在message-link.properties文件中
|  | 
|---|
随便挑一个给linkType赋值就可以绕过最后一个条件。
接下来就来到最关键的一步
|  | 
|---|
这段代码通过我们拿到的memberId作为参数调用了this.orgManager.getMemberById,这个方法大致就是通过memberId查找对应的用户,从别的师傅文章中得知,致远中存在几个默认的用户。
| 1 | "5725175934914479521" "集团管理员" | 
我们只需要通过以上memberId就能查询出管理员用户信息,在422行,用新创建的User对象重新设置了session,并且将查询出来的用户信息设置到了currentUser对象中,这才导致了任意用户登录漏洞。