CVE-2020-14882:Weblogic Console 权限绕过深入解析

原创 360CERT [三六零CERT](javascript:void(0)😉 今天

img

报告编号:B6-2020-110501

报告来源:360CERT

报告作者:Hu3sky

更新日期:2020-11-05

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 里主要修改有两个类,能够定位到漏洞触发点。

img

CVE-2020-14883: com.bea.console.utils.MBeanUtilsInitSingleFileServlet

img

CVE-2020-14882: com.bea.console.handles.HandleFactory

img

下面对漏洞进行逐个分析。

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 内容中可以得出:1. MBeanUtilsInitSingleFileServletAppManagerServletservlet-class-name 初始化的值。2. 访问 *.portal 会经过 AppManagerServlet 的分派处理(通过认证后访问 console 的路径是 /console/console.portal )。weblogic 所有的请求都会经过 weblogic.servlet.internal.ServletRequestImpl 的预处理。跟到 doSecuredExecute 方法。

img

这里会调用 WebAppSecurity#checkAccess 进行权限的校验。

img

第一次请求的时候 checkAllResourcesfalse ,于是调用 getConstraint 方法。传入参数为请求的 /console 下的资源的路径和请求的方法。这里以请求 /console.portal 为例。

img

静态资源列表获取

看下前面的逻辑:1. 获取一个 mapping 如下

img

\2. 以当前请求方法作为 key 值匹配 value ,而第一行获取到的 mapkey"" ,所以匹配结果为 null

img

\3. 判断当前请求的 url 是否匹配 mapping 里的路径,如果匹配不到,那么返回默认的 rcForAllMethods注意unrestrictedfalseunrestricted是权限验证的一个关键点 ),也就是:

img

现在的结果就是 rcForAllMethods 为默认值,而 rcForOneMethodnull 。所以返回 rcForAllMethods

执行到这里可以得出的是,如果请求的路径在 matchMap 列表里,那么 unrestricted 值就为 true ,这些是属于静态资源,没有做资源的限制和身份校验。

img

接着做 if 判断, resourceConstraint 不为 null

img

接着进入 else ,调用 SecurityModule#isAuthorized

img

继续调用 ChainedSecurityModule#checkAccess

img

权限校验

然后调用 hasPermission 开始判断是否有权限。

img

hasPermission 方法首先判断 unrestricted ,这里我们通过修改请求 /console/css/console.protal 访问静态资源使值为 true

img

然后 checkAcess 方法返回 true

重定向登陆界面

如果checkAcess方法返回false。那么不会进入后续的分派,会结doSecuredExecute方法的执行。一路 return 到执行 ServletRequestImpl#runInternalfinally 分支。

img

这里会调用 send 方法,在该方法会将没有分派的请求重定向到 login 界面。

请求分派

如果 checkAcess 方法返回 true 。进入后续请求的分派,经过几个 filterchain 的分派,最终调用 ServletStubImplexcute 方法。这里会根据 web.xml 的配置来获取对应的具体的 Servlet

img

注意,根据 web.xml ,请求如下路径所对应的 servlet 不一样,因为几个路径都是之前所提到的静态路径,没有身份验证,但是我们需要利用到 AsyncInitServlet 来处理,因为我们diff到的修补点在 MBeanUtilsInitSingleFileServlet ,这个类是通过 AsyncInitServlet 来设置的。

servlet 对应关系部分如下:

/framework/skeletons/wlsconsole/js/* -> FileDefault
/css/* -> AsyncInitServlet
/images/* -> AsyncInitServlet
/common/* -> JSPCServlet
...

于是,请求 /css/* 会调用 AsyncInitServletservice 方法,

img

这里的 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 ,而官方的补丁修复也是在这,通过一个黑名单列表检测路径里的非法字符,不过官方给出的黑名单字符不够完善,能够被绕过。

img

一路到达 UIServlet#service ,根据请求 method 调用不同的方法, doGet 最终也会调用到 doPost

img

url解码

doPost 里调用 createUIContext

img

UIContext会根据请求中的参数作对应属性值的设置,比如后面会说到的 _nfpb

img

创建完之后,会返回一个 UIContext 对象。这里的 tree 也就是 createUIContext 传入的第三个参数,初始值为 null

img

跟入 UIServletInternal#getTree ,这里会对 requestPattern 解码。

img

解码后。

img

请求portal文件,构建控件树

将解码后的 url 传入 processStream 方法。

img

然后 SingleFileProcessor#getMergedControlFromFile

img

关于.portal的加载方式singleFile:简单来说,在访问.portal时,是从文件系统加载的而不是数据库中,解析.portal文件的XML,并将呈现的.portal返回到浏览器。

img

将请求路径同时当作 file 路径传入,接着创建了 SAXParser ,准备将文件解析。

img

接着调用下方 getControlFactoryFromFile ,一直跟进,会从本地获取请求的文件.

img

在这里目录穿越起了效果 ,获取到的文件也就是 webapp 下的 console.portal

img

并且以 WarSource 对象存入缓存

img

之后调用 sax 解析 xml 文件 console.portal ,并从中生成控制树,也就是 getTree 返回的 ControlTreeRoot 对象,然后存入 UIContext

树的生命周期

控件树被构建后,就会进入生命周期的运行,回到 UIServlet#doPost ,调用 runLifecycle ,运行生命周期。

img

这里会根据 UIContext 里的两个值来判断执行 runInbound 还是 runOutbound ,后面细说

img

生命周期可以看作是控件上的一组方法,这些方法按定义的顺序调用。生命周期方法如下

init()
loadState()
handlePostbackData()
raiseChangeEvents()
preRender() 
saveState()
render()  
dispose()

控件的具体解析流程如下

img

对应了调用栈里的调用,从ROOT开始,第一个子节点是 Desktop ,而接下来:

img

然后,深度优先遍历子节点。

img

当然,这个顺序也就是 console.portal 文件里的 xml 嵌套顺序。

img

因为是深度优先,在 console.portal 里的所有引用的 portal 文件也会按顺序解析,比如

img

直到所有标签解析完。

CVE-2020-14883

接下来也就是造成代码执行的点, com.bea.console.handles.HandleFactory 要触发 getHandle 方法有两个触发点

触发点一

回到之前创建 UIContext 的时候,有一个 setServletRequest 方法。

img

如果请求中存在 _nfpb=true 的时候,会把 postback 选项设置为 true

img

那么,之后在运行树的生命周期时,由于 outbound 选项默认 false ,而 postbacktrue 进入判断。

img

会调用 runInbound 方法,因为 runInbound 会把 types 设置为 _inboundLifecycle

imgimg

_inboundLifecycle 如下,注意不同的type对应了不同的静态类

img

当然,如果没有 _nfpb=true ,会调用 runOutboundtype 设置为 _outboundNewLifecycle

img

这决定了在深度遍历的时候先调用的方法,上面说过生命周期方法,于是这里就会先调用所有节点的 init 方法。因为在运行生命周期的时候,这里会调用 ControlTreeWalker#walk 方法,第一个参数,也就是 type[0] ,是 init

img

继续跟入 walkRecursive 方法

img

注意两处:1. 如果当前是 Root 节点,那么调用 visitRoot ,这个方法只会调用一次,如果不是,则调用当前 visitvisit 方法,当前 visit 也就是 type 里提到的静态类。initControlLifecycle$1 ,也就是第一个静态类,而这里的control就是当前节点。也就是说,如果当前 typeinit ,深度解析所有节点的时候,都会把 init 方法调用一次。也就有了漏洞触发点 Portlet#init

img

\2. 调用完之后,如果深度遍历发现还有子节点,那么继续调用 walkRecursive ,重复 1 的步骤,直到所有节点解析完。

img

当调用到 Portlet 节点的 init 方法时,会一直调用 super.init

img

调用栈:

img

直到 AdministeredBackableControl#init ,会调用 initializeBackingFile

img

最终会调用到 BreadcrumbBacking#init ,而这里会获取请求中的 handle 参数,调用 getHandle 方法。

img

触发点二

在调用完init之后,会根据type里的顺序,继续调用生命周期方法(都对应着 ControlLifecycle 里的 visitor )。如果是 _nfpb=true ,调用完 runInbound -> runOutbound

img

由于 postbacktrue

img

之后流程类似,不过调用的 visitor 最开始是 ControlLifecycle#preRenderVisitor 在调用到 StrutsContet 节点的时候,这个是在解析到引用 PortalConfig/contentheader/ContentHeader_messages.portlet 的时候。

img

这时候会调用 preRenderVisitor#preRenderpreRenderVisitor 没有该方法,去父类 NetuiContent#preRender

img

并且在文件里会设置 action/refreshActionMessagesAction

img

后续调用栈:

img

当然,不止 StrutsContet 节点会调用到这里,还有 Book , Portlet 节点,而在深度遍历的时候,会有很多 Book , Portlet , StrutsContet 的子节点,于是就会执行 getHandle 很多次,这也是为什么在使用计算器进行poc测试的时候,会多次弹出的原因。

img

最终的利用结果如下:

img

总结

\1. 通过静态资源来绕过权限验证,防止被重定向到登陆界面。2. 通过请求 .portal ,控制处理的 Servlet 是渲染 UIMBeanUtilsInitSingleFileServlet 。3. 通过编码后的 ../ ,让最终渲染的模版是 console.portal

综合起来,才造成了最终的未授权访问。

0x05 时间线

2020-10-21  360CERT发布Oracle补丁日通告

2020-10-28  360CERT监测到POC公开

2020-10-29  360CERT发布通告

2020-11-05  360CERT发布分析

转载自https://mp.weixin.qq.com/s/VUILchDQxOjKbnuF7nUvIQ

admin-root 系统安全 web安全 安全架构
已标记关键词 清除标记