作者:Hu3sky@360CERT
0x01 漏洞简述
2020年10月29日,360CERT监测发现 Weblogic ConSole HTTP 协议代码执行漏洞 相关 POC已经公开,相关漏洞编号为 CVE-2020-14882,CVE-2020-14883 ,漏洞等级:严重,漏洞评分:9.8。
远程攻击者可以构造特殊的HTTP请求,在未经身份验证的情况下接管 WebLogic Server Console ,并执行任意代码。
对此,360CERT建议广大用户及时将 Weblogic 升级到最新版本。与此同时,请做好资产自查以及预防工作,以免遭受黑客攻击。
0x02 风险等级
360CERT对该漏洞的评定结果如下
| 评定方式 | 等级 |
|---|---|
| 威胁等级 | 严重 |
| 影响面 | 广泛 |
| 360CERT评分 | 9.8 |
0x03 影响版本
Oracle:Weblogic:
- 10.3.6.0.0
- 12.1.3.0.0
- 12.2.1.3.0
- 12.2.1.4.0
- 14.1.1.0.0
0x04 漏洞详情
目前网上的分析都没有说清楚权限绕过具体是怎么访问到console.portal路径并且触发handle执行的,在与@Lucifaer的共同深入研究下,大概掌握了原理,于是有了此文。
CVE-2020-14883 是一个 Console 的未授权访问,而 CVE-2020-14883 是在利用未授权访问的前提下,在 Console 进行代码执行,于是远程攻击者可以构造特殊的 HTTP 请求,在未经身份验证的情况下接管 WebLogic Server Console ,并在 WebLogic Server Console 执行任意代码。
通过diff补丁,console.jar 里主要修改有两个类,能够定位到漏洞触发点。
CVE-2020-14883: com.bea.console.utils.MBeanUtilsInitSingleFileServlet
CVE-2020-14882: com.bea.console.handles.HandleFactory
下面对漏洞进行逐个分析。
CVE-2020-14882
首先要明白,漏洞的触发是在 console 组件,而console对应着webapp服务,路径:wlserver/server/lib/consoleapp/webapp。并且存在web.xml,于是查看与MBeanUtilsInitSingleFileServlet相关的web.xml信息:
<servlet>
<servlet-name>AppManagerServlet</servlet-name>
<servlet-class>weblogic.servlet.AsyncInitServlet</servlet-class>
<init-param>
<param-name>weblogic.servlet.AsyncInitServlet.servlet-class-name</param-name>
<param-value>com.bea.console.utils.MBeanUtilsInitSingleFileServlet</param-value>
</init-param>
...
<servlet-mapping>
<servlet-name>AppManagerServlet</servlet-name>
<url-pattern>*.portal</url-pattern>
</servlet-mapping>
Request处理
从上面的 web.xml 内容中可以得出:
-
MBeanUtilsInitSingleFileServlet是AppManagerServlet的servlet-class-name初始化的值。 - 访问
*.portal会经过AppManagerServlet的分派处理(通过认证后访问console的路径是/console/console.portal)。weblogic所有的请求都会经过weblogic.servlet.internal.ServletRequestImpl的预处理。跟到doSecuredExecute方法。
这里会调用WebAppSecurity#checkAccess进行权限的校验。
第一次请求的时候checkAllResources为false,于是调用getConstraint方法。 传入参数为请求的/console下的资源的路径和请求的方法。这里以请求/console.portal为例。
静态资源列表获取
看下前面的逻辑:
- 获取一个
mapping如下
- 以当前请求方法作为
key值匹配value,而第一行获取到的map的key是"",所以匹配结果为null。
- 判断当前请求的
url是否匹配mapping里的路径,如果匹配不到,那么返回默认的rcForAllMethods(注意unrestricted为false,unrestricted是权限验证的一个关键点),也就是:
现在的结果就是rcForAllMethods为默认值,而rcForOneMethod为null。所以返回rcForAllMethods。
执行到这里可以得出的是,如果请求的路径在matchMap列表里,那么unrestricted值就为true,这些是属于静态资源,没有做资源的限制和身份校验。
接着做if判断,resourceConstraint不为null。
接着进入else,调用SecurityModule#isAuthorized。
继续调用ChainedSecurityModule#checkAccess。
权限校验
然后调用hasPermission开始判断是否有权限。
在hasPermission方法首先判断unrestricted,这里我们通过修改请求/console/css/console.protal访问静态资源使值为true。
然后 checkAcess方法返回 true。
重定向登陆界面
如果checkAcess方法返回false。那么不会进入后续的分派,会结束doSecuredExecute方法的执行。一路return到执行ServletRequestImpl#runInternal的finally分支。
这里会调用send方法,在该方法会将没有分派的请求重定向到login界面。
请求分派
如果checkAcess方法返回true。进入后续请求的分派,经过几个filterchain的分派,最终调用ServletStubImpl的excute方法。这里会根据web.xml的配置来获取对应的具体的Servlet。
注意,根据web.xml,请求如下路径所对应的servlet不一样,因为几个路径都是之前所提到的静态路径,没有身份验证,但是我们需要利用到AsyncInitServlet来处理,因为我们diff到的修补点在MBeanUtilsInitSingleFileServlet,这个类是通过AsyncInitServlet来设置的。
servlet对应关系部分如下:
/framework/skeletons/wlsconsole/js/* -> FileDefault
/css/* -> AsyncInitServlet
/images/* -> AsyncInitServlet
/common/* -> JSPCServlet
...
于是,请求/css/*会调用AsyncInitServlet的service方法,
这里的delegate就是在web.xml被初始化的MBeanUtilsInitSingleFileServlet。 接下来以漏洞URL为例。
/css/%252E%252E%252Fconsole.portal
这里要二次编码的原因是,发过去的时候http会解一次码,也就是说如果我们传的是/css/%2E%2E%2Fconsole.portal,那么解码后就是/css/../console.portal,这样发到服务端就没办法匹配到静态资源了,直接处理成了/console.portal。
如果http解码后的url里没有;,那么就会继续调用super.service,而官方的补丁修复也是在这,通过一个黑名单列表检测路径里的非法字符,不过官方给出的黑名单字符不够完善,能够被绕过。
一路到达UIServlet#service,根据请求method调用不同的方法,doGet最终也会调用到doPost。
url解码
在doPost里调用createUIContext。
UIContext会根据请求中的参数作对应属性值的设置,比如后面会说到的_nfpb。
创建完之后,会返回一个UIContext对象。 这里的tree也就是createUIContext传入的第三个参数,初始值为null。
跟入UIServletInternal#getTree,这里会对requestPattern解码。
解码后。
请求portal文件,构建控件树
将解码后的url传入processStream方法。
然后SingleFileProcessor#getMergedControlFromFile。
关于.portal的加载方式singleFile:简单来说,在访问.portal时,是从文件系统加载的而不是数据库中,解析.portal文件的XML,并将呈现的.portal返回到浏览器。
将请求路径同时当作file路径传入,接着创建了SAXParser,准备将文件解析。
接着调用下方getControlFactoryFromFile,一直跟进,会从本地获取请求的文件.
在这里目录穿越起了效果,获取到的文件也就是webapp下的console.portal。
并且以WarSource对象存入缓存
之后调用sax解析xml文件console.portal,并从中生成控制树,也就是getTree返回的ControlTreeRoot对象,然后存入UIContext。
树的生命周期
控件树被构建后,就会进入生命周期的运行,回到UIServlet#doPost,调用runLifecycle,运行生命周期。
这里会根据UIContext里的两个值来判断执行runInbound还是runOutbound,后面细说
生命周期可以看作是控件上的一组方法,这些方法按定义的顺序调用。生命周期方法如下
init()
loadState()
handlePostbackData()
raiseChangeEvents()
preRender()
saveState()
render()
dispose()
控件的具体解析流程如下
对应了调用栈里的调用,从ROOT开始,第一个子节点是Desktop,而接下来:

当然,这个顺序也就是console.portal文件里的xml嵌套顺序。
因为是深度优先,在console.portal里的所有引用的portal文件也会按顺序解析,比如

CVE-2020-14883
接下来也就是造成代码执行的点,com.bea.console.handles.HandleFactory 要触发getHandle方法有两个触发点
触发点一
回到之前创建UIContext的时候,有一个setServletRequest方法。
如果请求中存在_nfpb=true的时候,会把postback选项设置为true。
那么,之后在运行树的生命周期时,由于outbound选项默认false,而postback为true进入判断。
会调用runInbound方法,因为runInbound会把types设置为_inboundLifecycle。
_inboundLifecycle如下,注意不同的type对应了不同的静态类
当然,如果没有_nfpb=true,会调用runOutbound,type设置为_outboundNewLifecycle。
这决定了在深度遍历的时候先调用的方法,上面说过生命周期方法,于是这里就会先调用所有节点的init方法。因为在运行生命周期的时候,这里会调用ControlTreeWalker#walk方法,第一个参数,也就是type[0],是init。
继续跟入walkRecursive方法

- 如果当前是
Root节点,那么调用visitRoot,这个方法只会调用一次,如果不是,则调用当前visit的visit方法,当前visit也就是type里提到的静态类。init是ControlLifecycle$1,也就是第一个静态类,而这里的control就是当前节点。也就是说,如果当前type是init,深度解析所有节点的时候,都会把init方法调用一次。也就有了漏洞触发点Portlet#init。
- 调用完之后,如果深度遍历发现还有子节点,那么继续调用
walkRecursive,重复1的步骤,直到所有节点解析完。
当调用到Portlet节点的init方法时,会一直调用super.init。
调用栈:
直到AdministeredBackableControl#init,会调用initializeBackingFile
最终会调用到BreadcrumbBacking#init,而这里会获取请求中的handle参数,调用getHandle方法。
触发点二
在调用完init之后,会根据type里的顺序,继续调用生命周期方法(都对应着ControlLifecycle里的visitor)。 如果是_nfpb=true,调用完runInbound->runOutbound
由于postback为true。
之后流程类似,不过调用的visitor最开始是ControlLifecycle#preRenderVisitor 在调用到StrutsContet节点的时候,这个是在解析到引用PortalConfig/contentheader/ContentHeader_messages.portlet的时候。
这时候会调用preRenderVisitor#preRender,preRenderVisitor没有该方法,去父类NetuiContent#preRender。
并且在文件里会设置action/refreshAction为MessagesAction。
后续调用栈:
当然,不止StrutsContet节点会调用到这里,还有Book,Portlet节点,而在深度遍历的时候,会有很多Book,Portlet,StrutsContet的子节点,于是就会执行getHandle很多次,这也是为什么在使用计算器进行poc测试的时候,会多次弹出的原因。
最终的利用结果如下:
总结
- 通过静态资源来绕过权限验证,防止被重定向到登陆界面。
- 通过请求
.portal,控制处理的Servlet是渲染UI的MBeanUtilsInitSingleFileServlet。 - 通过编码后的
../,让最终渲染的模版是console.portal。
综合起来,才造成了最终的未授权访问。
0x05 时间线
2020-10-21 360CERT发布Oracle补丁日通告
2020-10-28 360CERT监测到POC公开
2020-10-29 360CERT发布通告
2020-11-05 360CERT发布分析



































































发表评论
您还未登录,请先登录。
登录