<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>xmldecoder &#8211; ChaBug安全</title>
	<atom:link href="/tags/xmldecoder/feed" rel="self" type="application/rss+xml" />
	<link>/</link>
	<description>一个分享知识、结识伙伴、资源共享的博客</description>
	<lastBuildDate>Thu, 09 Apr 2020 03:33:57 +0000</lastBuildDate>
	<language>zh-CN</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=5.5.5</generator>
	<item>
		<title>XMLDecoder反序列化分析</title>
		<link>/audit/1425.html</link>
		
		<dc:creator><![CDATA[Y4er]]></dc:creator>
		<pubDate>Thu, 09 Apr 2020 03:33:57 +0000</pubDate>
				<category><![CDATA[代码审计]]></category>
		<category><![CDATA[java]]></category>
		<category><![CDATA[xmldecoder]]></category>
		<category><![CDATA[反序列化]]></category>
		<guid isPermaLink="false">/?p=1425</guid>

					<description><![CDATA[简介 XMLDecoder是java自带的以SAX方式解析xml的类，其在反序列化经过特殊构造的数据时可执行任意命令。在Weblogic中由于多个包wls-wast、wls9_as...]]></description>
										<content:encoded><![CDATA[<h2>简介</h2>
<p>XMLDecoder是<span class="wpcom_tag_link"><a href="/tags/java" title="java" target="_blank">java</a></span>自带的以SAX方式解析xml的类，其在<span class="wpcom_tag_link"><a href="/tags/%e5%8f%8d%e5%ba%8f%e5%88%97%e5%8c%96" title="反序列化" target="_blank">反序列化</a></span>经过特殊构造的数据时可执行任意命令。在Weblogic中由于多个包<code>wls-wast</code>、<code>wls9_async_response war</code>、<code>_async</code>使用了该类进行反序列化操作，出现了了多个RCE漏洞。</p>
<p>本文不会讲解weblogic的xml相关的洞，只是分析下Java中xml反序列化的流程，采用JDK2U21。</p>
<h2>什么是SAX</h2>
<p>SAX全称为<code>Simple API for XML</code>，在Java中有两种原生解析xml的方式，分别是SAX和DOM。两者区别在于：<br />
1. Dom解析功能强大，可增删改查，操作时会将xml文档以文档对象的方式读取到内存中，因此适用于小文档<br />
2. Sax解析是从头到尾逐行逐个元素读取内容，修改较为不便，但适用于只读的大文档</p>
<p>SAX采用事件驱动的形式来解析xml文档，简单来讲就是触发了事件就去做事件对应的回调方法。</p>
<p>在SAX中，读取到文档开头、结尾，元素的开头和结尾以及编码转换等操作时会触发一些回调方法，你可以在这些回调方法中进行相应事件处理：</p>
<ul>
<li>startDocument()</li>
<li>endDocument()</li>
<li>startElement()</li>
<li>endElement()</li>
<li>characters()</li>
</ul>
<p>自己实现一个基于SAX的解析可以帮我们更好的理解XMLDecoder</p>
<pre><code class="language-java ">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 &lt; 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);
    }
}
</code></pre>
<p>输出了</p>
<pre><code class="">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()
</code></pre>
<p>可以看到，我们通过继承SAX的DefaultHandler类，重写其事件方法，就能拿到XML对应的节点、属性和值。那么XMLDecoder也是基于SAX实现的xml解析，不过他拿到节点、属性、值之后通过Expression创建对象及调用方法。接下来我们就来分析下XMLDecoder将XML解析为对象的过程。</p>
<h2>XMLDecoder反序列化分析</h2>
<p>所有的xml处理代码均在<code>com.sun.beans.decoder</code>包下。先弹一个计算器</p>
<pre data-language=XML><code class="language-markup ">&lt;java&gt;
    &lt;object class="java.lang.ProcessBuilder"&gt;
        &lt;array class="java.lang.String" length="1" &gt;
            &lt;void index="0"&gt;
                &lt;string&gt;calc&lt;/string&gt;
            &lt;/void&gt;
        &lt;/array&gt;
        &lt;void method="start"/&gt;
    &lt;/object&gt;
&lt;/java&gt;
</code></pre>
<pre><code class="language-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();
    }
}
</code></pre>
<p>运行弹出计算器，在java.lang.ProcessBuilder#start打断点，堆栈如下：</p>
<pre><code class="">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)
</code></pre>
<p>XMLDecoder跟进readObject()<br />
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/593424/8ca41da5-71ae-1dc2-269e-c00a60eddc11.png" alt="image.png" /></p>
<p>跟进parsingComplete()<br />
<img src="/wp-content/uploads/2020/04/8af767d7-2cb6-67e0-ffbd-8af757d6c6fb.png" alt="image.png" /></p>
<p>其中<code>this.handler</code>为<code>DocumentHandler</code><br />
<img src="/wp-content/uploads/2020/04/3cb12103-0220-df47-88ca-d683b1035731.png" alt="image.png" /></p>
<p>到这里进入<code>com.sun.beans.decoder.DocumentHandler#parse</code></p>
<p><img src="/wp-content/uploads/2020/04/6a686b4b-944d-332d-6d2e-2391deadedf5.png" alt="image.png" /></p>
<p>圈住的代码其实和我们写的<code>DemoHandler</code>里一模一样，通过<code>SAXParserFactory</code>工厂创建了实例，进而<code>newSAXParser</code>拿到SAX解析器，调用<code>parse</code>解析，那么接下来解析的过程，我们只需要关注DocumentHandler的几个事件函数就行了。</p>
<p>在<code>DocumentHandler</code>的构造函数中指定了可用的标签类型<br />
<img src="/wp-content/uploads/2020/04/19c3cebe-7013-707d-30f6-20157e5321d9.png" alt="image.png" /></p>
<p>对应了<code>com.sun.beans.decoder</code>包中的几个类<br />
<img src="/wp-content/uploads/2020/04/582e09fe-f9b2-c748-7e8a-7f7d020785a8.png" alt="image.png" /></p>
<p>在startElement中首先解析<code>java</code>标签，然后设置Owner和Parent。<br />
<img src="/wp-content/uploads/2020/04/833ef0cf-260b-d0f6-d8b0-f790528414d6.png" alt="image.png" /></p>
<p><code>this.getElementHandler(var3)</code>对应的就是从构造方法中放入<code>this.handlers</code>的hashmap取出对应的值，如果不是构造方法中的标签，会抛出异常。<br />
<img src="/wp-content/uploads/2020/04/918aa1ae-5b29-03da-e2c6-d515d003b6eb.png" alt="image.png" /></p>
<p>然后解析<code>object</code>标签，拿到属性之后通过addAttribute()设置属性<br />
<img src="/wp-content/uploads/2020/04/51c1f952-27fd-d15c-9bbd-3655c87cfcdc.png" alt="image.png" /></p>
<p>在addAttribute()没有对class属性进行处理，抛给了父类<code>com.sun.beans.decoder.NewElementHandler#addAttribute</code><br />
<img src="/wp-content/uploads/2020/04/05069f7a-c40e-28f8-3fd3-5d30e662077b.png" alt="image.png" /></p>
<p>会通过findClass()去寻找<code>java.lang.ProcessBuilder</code>类<br />
<img src="/wp-content/uploads/2020/04/fd98350b-cb96-d2c6-d713-d44f1051593f.png" alt="image.png" /></p>
<p>通过classloader寻找类赋值给type<br />
<img src="/wp-content/uploads/2020/04/c8089179-0e94-a8ea-8948-ac8d79e19055.png" alt="image.png" /></p>
<p>赋值完之后跳出for循环进入<code>this.handler.startElement()</code>，不满足条件。<br />
<img src="/wp-content/uploads/2020/04/41969012-d7b6-0bbc-0847-b0e2180d02c7.png" alt="image.png" /></p>
<p>接下来解析<code>array</code>标签，同样使用addAttribute对属性赋值<br />
<img src="/wp-content/uploads/2020/04/af6dda79-d0aa-d1e5-805d-8c09bf737c00.png" alt="image.png" /></p>
<p>同样抛给父类<code>com.sun.beans.decoder.NewElementHandler#addAttribute</code>处理<br />
<img src="/wp-content/uploads/2020/04/cf22d636-801c-3cac-0bec-f1ba80833477.png" alt="image.png" /></p>
<p>继续抛给父类<code>com.sun.beans.decoder.NewElementHandler#addAttribute</code><br />
<img src="/wp-content/uploads/2020/04/e924202e-606d-9155-64b5-32c5277056ea.png" alt="image.png" /></p>
<p>接下来继续设置length属性<br />
<img src="/wp-content/uploads/2020/04/d4e6dcad-d12c-55ba-8685-c48996fc4dc4.png" alt="image.png" /></p>
<p>最后进入<code>com.sun.beans.decoder.ArrayElementHandler#startElement</code><br />
<img src="/wp-content/uploads/2020/04/338d4b22-b22a-43af-a653-64311ae5d02c.png" alt="image.png" /></p>
<p>因为ArrayElementHandler类没有0个参数的getValueObject()重载方法，但是它继承了NewElementHandler，所以调用<code>com.sun.beans.decoder.NewElementHandler#getValueObject()</code><br />
<img src="/wp-content/uploads/2020/04/988b4d6d-d335-4f0c-61d9-5cfcabde7930.png" alt="image.png" /></p>
<p>这个getValueObject重新调用<code>ArrayElementHandler#getValueObject</code>两个参数的重载方法<br />
<img src="/wp-content/uploads/2020/04/931356c7-f9cb-7dda-5a7f-64ca3b946483.png" alt="image.png" /></p>
<p><code>ValueObjectImpl.create(Array.newInstance(var1, this.length))</code>创建了长度为1、类型为String的数组并返回，到此处理完array标签。</p>
<p>接着处理void，创建VoidElementHandler，设置setOwner和setParent。<br />
<img src="/wp-content/uploads/2020/04/b10fe71d-b6ef-e9ff-5db3-8816cacdc8f7.png" alt="image.png" /></p>
<p>调用父类<code>com.sun.beans.decoder.ObjectElementHandler#addAttribute</code>设置index属性<br />
<img src="/wp-content/uploads/2020/04/6b22f905-cea3-b4c9-4909-c9ae03474071.png" alt="image.png" /><br />
<img src="/wp-content/uploads/2020/04/69abeaf4-7f8e-237b-644e-baa3f219d57f.png" alt="image.png" /></p>
<p>继续解析string标签，不再赘述。</p>
<p>解析完所有的开始标签之后，开始解析闭合标签，最开始就是</string>，进入到endElement()<br />
<img src="/wp-content/uploads/2020/04/b4f65da3-f906-b923-7ca2-1b866f8039bd.png" alt="image.png" /></p>
<p>StringElementHandler没有endElement()，调用父类ElementHandler的endElement()<br />
<img src="/wp-content/uploads/2020/04/5ad1d68d-3f09-f7ba-2448-d7e978c95904.png" alt="image.png" /></p>
<p>调用本类的getValueObject()<br />
<img src="/wp-content/uploads/2020/04/6f2f4b9f-b262-3586-68d0-a2ae86a87686.png" alt="image.png" /><br />
设置value为calc。</p>
<p>接着闭合void<br />
<img src="/wp-content/uploads/2020/04/c30e5275-c570-f477-f3f3-4a88bf7bdb23.png" alt="image.png" /></p>
<p>闭合array<br />
<img src="/wp-content/uploads/2020/04/ac735251-6387-7516-a473-ea21929c05a2.png" alt="image.png" /></p>
<p>然后开始解析<code>&lt;void method="start"/&gt;</code><br />
<img src="/wp-content/uploads/2020/04/f6f5bcdc-cb12-a3f7-4d36-de1ba748edad.png" alt="image.png" /></p>
<p>通过父类的addAttribute将this.method赋值为start<br />
<img src="/wp-content/uploads/2020/04/4648a8a0-9e7c-60c2-e016-4335bcb6600e.png" alt="image.png" /></p>
<p>随后闭合void标签<br />
<img src="/wp-content/uploads/2020/04/d67caaa5-a26b-8204-66e3-d1c5fdaea5f6.png" alt="image.png" /></p>
<p>调用<code>endElement</code>，<code>VoidElementHandler</code>类没有，所以调用父类<code>ObjectElementHandler.endElement</code><br />
<img src="/wp-content/uploads/2020/04/78c08738-2a52-180f-1656-3cc2e960ec0c.png" alt="image.png" /></p>
<p>调用<code>NewElementHandler</code>类无参<code>getValueObject</code><br />
<img src="/wp-content/uploads/2020/04/2277d6d4-e914-3ba2-80a9-d8e1059af4d4.png" alt="image.png" /></p>
<p>然后调用<code>VoidElementHandler</code>类有参<code>getValueObject</code>，但是<code>VoidElementHandler</code>没有这个方法，所以调用<code>VoidElementHandler</code>父类<code>ObjectElementHandler</code>的有参<code>getValueObject</code></p>
<pre><code class="language-java ">protected final ValueObject getValueObject(Class&lt;?&gt; 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 &lt; this.property.length()) {
                    var4 = var4 + this.property.substring(0, 1).toUpperCase(Locale.ENGLISH) + this.property.substring(1);
                }
            } else {
                var4 = this.method != null &amp;&amp; 0 &lt; this.method.length() ? this.method : "new";
            }

            Expression var5 = new Expression(var3, var4, var2);
            return ValueObjectImpl.create(var5.getValue());
        }
    }
</code></pre>
<p>跟进<code>Object var3 = this.getContextBean()</code>，因为本类没有getContextBean()，所以调用父类NewElementHandler的getContextBean()<br />
<img src="/wp-content/uploads/2020/04/14dab24b-6989-1b40-04ca-61846ec61015.png" alt="image.png" /></p>
<p>继续调用NewElementHandler父类ElementHandler的getContextBean()<br />
<img src="/wp-content/uploads/2020/04/1c2ca5e4-4b54-793e-e2f5-e78e30fe8797.png" alt="image.png" /></p>
<p>会调用<code>this.parent.getValueObject()</code>也就是ObjectElementHandler类，而ObjectElementHandler没有无参getValueObject()方法，会调用其父类NewElementHandler的方法<br />
<img src="/wp-content/uploads/2020/04/fe81829f-64bc-2932-ab4b-295fc432e0f7.png" alt="image.png" /><br />
然后将值赋值给value返回</p>
<p>最终var3的值为<code>java.lang.ProcessBuilder</code>。<br />
<img src="/wp-content/uploads/2020/04/ea7e2b86-5a4b-b7f7-11d3-3eb2fc7a28fa.png" alt="image.png" /></p>
<p>var4赋值为start<br />
<img src="/wp-content/uploads/2020/04/24595096-5024-9acf-e183-3738aae6d572.png" alt="image.png" /></p>
<p>通过Expression的getValue()方法反射调用start，弹出计算器。</p>
<h2>Expression和Statement</h2>
<p>两者都是Java对反射的封装，举个例子</p>
<pre><code class="language-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);
    }
}
</code></pre>
<pre><code class="language-java ">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();
        }
    }
}
</code></pre>
<p>运行结果</p>
<pre><code class="">张三
你好 小明!
</code></pre>
<p>Expression是可以获得返回值的，方法是getValue()。Statement不能获得返回值。</p>
]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>
