致远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
对象中,这才导致了任意用户登录漏洞。