前言
XMLDecoder是JDK自带的处理XML文档的类库,和XMLEncoder组合起来可用于生成 JavaBean类的XML文本表示形式,使用方式也是和 ObjectOutputStream与 ObjectInputStream创建Serializable对象类似。
XMLDecoder功能相当丰富,可以对XML文档中表示的对象很方便的在内存反序列化为对象,当XML文档中存在方法调用的标签时,可以在反序列化的过程中以反射的形式自动执行,这个机制也给一些使用不当的程序带来了安全漏洞,如Weblogic相关的CVE( CVE-2017-3506、CVE-2019-2725)。
简单使用
调试环境为jdk7和IDEA 。
以下简单用代码演示XMLDecoder和XMLEncoder的使用。
Person类代码:
public class Person { private String name; private int age; //必须要有一个无参构造方法,要不会在序列化的过程中报错 public Person() { } 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类代码:
public class Main { public static Object decode() { File file = new File(Main.class.getClassLoader().getResource("").getPath() + "config.xml"); XMLDecoder xmlDecoder = null; try { xmlDecoder = new XMLDecoder(new BufferedInputStream(new FileInputStream(file))); //反序列化对象 Object o = xmlDecoder.readObject(); return o; } catch (Exception e) { e.printStackTrace(); return null; } finally { if (xmlDecoder != null) { xmlDecoder.close(); } } } public static void encode(Object o) { XMLEncoder xmlEncoder = null; try { File file = new File(Main.class.getClassLoader().getResource("").getPath() + "config.xml"); xmlEncoder = new XMLEncoder(new BufferedOutputStream(new FileOutputStream(file))); //序列化对象 xmlEncoder.writeObject(o); xmlEncoder.flush(); } catch (FileNotFoundException e) { e.printStackTrace(); } finally { xmlEncoder.close(); } } public static void main(String[] args) throws FileNotFoundException { Person person = new Person("test", 18); encode(person); Person result = (Person) decode(); System.out.println(result); } }
在encode序列化方法执行后可以在程序目录下config.xml文件中看到person对象以xml的形式存储在文件中。
在decode反序列化方法后,控制台成功打印出反序列化的对象。
XML标签说明
查看XMLDecoder的官方文档(https://www.oracle.com/technical-resources/articles/java/persistence3.html),可以看到几个主要标签的使用说明。
object标签的使用相当于new一个新对象,其中的string标签为构造方法需要的参数。
<object class="javax.swing.JButton"> <string>Press me</string> </object> new JButton("Press me");
void标签的method属性可以在反序列化的过程中自动执行,其中的string标签为方法调用的参数。
<object class="javax.swing.JButton"> <string>Press me</string> <void method="setName"> <string>Greeting</string> </void> </object> JButton button1 = new JButton("Press me"); button1.setName("Greeting");
array标签用来声明数组格式的对象,length属性为数组的长度,index属性为赋值的下标。
<array class="java.lang.String" length="3"> <void index="1"> <string>Hello, world</string> </void> </array> String[] s = new String[3]; s[1] = "Hello, world";
反序列化命令执行过程
使用网上找到POC,其中object反序列化类为java.lang.ProcessBuilder,构造方法参数为calc(windows操作系统上的计算器程序),void标签声明了反序列化过程中调用的方法为start方法。
直接在ProcessBuilder的start方法上打断点,可以看到方法调用栈的调用链。
在XMLDecoder类的readObject方法中进入parsingComplete方法。
在parsingComplete方法中进入DocumentHandler类的parse方法。
在DocumentHandler类中声明了各种元素的处理器。
之后打调用链大部分是对xml文件的解析和对象的生成,快进到DocumentHandler类的endElement方法,可以看到处理器实际的类型为VoidElementHandler。
在ObjectElementHandler类的getValueObject方法中生成了Expression对象并执行getValue方法。
在getValue方法中发现invoke方法调用。
一路跟踪到Statement类的invokeInternal方法可以看到其中调用了MethodUtil.invoke,并传入了start方法和ProcessBuilder对象,MethodUtil工具类会发射执行传入的方法。
至此POC成功执行,总结下个人认为发生反序列化漏洞本身不是XMLDecoder的责任,它只是提供了这种程序执行的机制,安全措施应该由类库的使用者去负责。
补充
在类的无参构造方法上下断点,发现反序列化过程会调用类的构造方法反射执行生成目标对象,而不是像ObjectInputStream那样直接生成对象。