java bean
public class Person { private String name; private Integer age; public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } @Override public String toString() { return "Person{" + "name='" + name + '/'' + ", age=" + age + '}'; } }
序列化
public static void main(String[] args) throws Exception{ Person p = new Person(); p.setAge(20); p.setName("kingkk"); ObjectMapper mapper = new ObjectMapper(); String json = mapper.writeValueAsString(p); System.out.println(json); // {"name":"kingkk","age":20} }
反序列化
ObjectMapper mapper = new ObjectMapper(); String json = "{/"name/":/"kingkk/",/"age/":20}"; System.out.println(mapper.readValue(json, Person.class)); // Person{name='kingkk', age=20}
这也就是最基础的json序列化与反序列化操作
当然,这样简单的demo是没有什么安全问题的,问题出在于一些可选配置上
public enum DefaultTyping { /** * This value means that only properties that have * {@link java.lang.Object} as declared type (including * generic types without explicit type) will use default * typing. */ JAVA_LANG_OBJECT, /** * Value that means that default typing will be used for * properties with declared type of {@link java.lang.Object} * or an abstract type (abstract class or interface). * Note that this does <b>not</b> include array types. *<p> * Since 2.4, this does NOT apply to {@link TreeNode} and its subtypes. */ OBJECT_AND_NON_CONCRETE, /** * Value that means that default typing will be used for * all types covered by {@link #OBJECT_AND_NON_CONCRETE} * plus all array types for them. *<p> * Since 2.4, this does NOT apply to {@link TreeNode} and its subtypes. */ NON_CONCRETE_AND_ARRAYS, /** * Value that means that default typing will be used for * all non-final types, with exception of small number of * "natural" types (String, Boolean, Integer, Double), which * can be correctly inferred from JSON; as well as for * all arrays of non-final types. *<p> * Since 2.4, this does NOT apply to {@link TreeNode} and its subtypes. */ NON_FINAL }
默认情况下, mapper.enableDefaultTyping()
什么参数都不加时,会默认调用 OBJECT_AND_NON_CONCRETE
public ObjectMapper enableDefaultTyping() { return enableDefaultTyping(DefaultTyping.OBJECT_AND_NON_CONCRETE); }
重点说下 OBJECT_AND_NON_CONCRETE
这个参数
考虑如下这一种情况,序列化的对象的成员变量中,数据类型是一个 抽象类/接口 (或者直接看代码来的易懂点
public class Person { private String name; private Integer age; private Cls cls; // getter setter toString } public class Cls1 { private String name="cls1"; // getter setter toString } public abstract class Cls { }
这时候对这个类进行反序列化的时候,可以看到是无法进行反序列化的
Person p = new Person(); p.setAge(20); p.setName("kingkk"); p.setCls(new Cls1()); ObjectMapper mapper = new ObjectMapper(); String json = mapper.writeValueAsString(p); System.out.println(json); // {"name":"kingkk","age":20,"cls":{"name":"cls1"}} System.out.println(mapper.readValue(json, Person.class));
大意就是说由于 Cls
这个变量是一个抽象类,所以反序列化失败。因为jackson也不知道要反序列化它的哪一个实现。
这时候就需要开启 enableDefaultTyping
,对数据类型进行指定
将上面的代码加一条 mapper.enableDefaultTyping();
,就可以看到反序列化成功了
Person p = new Person(); p.setAge(20); p.setName("kingkk"); p.setCls(new Cls1()); ObjectMapper mapper = new ObjectMapper(); mapper.enableDefaultTyping(); String json = mapper.writeValueAsString(p); System.out.println(json); // {"name":"kingkk","age":20,"cls":["cls.Cls1",{"name":"cls1"}]} System.out.println(mapper.readValue(json, Person.class)); // Person{name='kingkk', age=20, cls=Cls1{name='cls1'}}
比较两次序列化后的json字符串,可以看到,多了指定的数据类型。
未开启enableDefaultTyping // {"name":"kingkk","age":20,"cls":{"name":"cls1"}} 开启了enableDefaultTyping // {"name":"kingkk","age":20,"cls":["cls.Cls1",{"name":"cls1"}]}
jackson也就可以根据数据类型去自动反序列化 出对应的类
那假如有一个 Cls2
,将传入的json字符串变成 {"name":"kingkk","age":20,"cls":["cls.Cls2",{"name":"cls2"}]}
是否也可以反序列化出对应的类呢,显然是可以的。
修改下
String json = "{/"name/":/"kingkk/",/"age/":20,/"cls/":[/"cls.Cls1/",{/"name/":/"cls1/"}]}"; String json2 = "{/"name/":/"kingkk/",/"age/":20,/"cls/":[/"cls.Cls2/",{/"name/":/"cls2/"}]}"; System.out.println(mapper.readValue(json, Person.class)); //Person{name='kingkk', age=20, cls=Cls1{name='cls1'}} System.out.println(mapper.readValue(json2, Person.class)); //Person{name='kingkk', age=20, cls=Cls2{name='cls2'}}
那假如将数据类型 Cls
改成 Object
呢?是否就可以反序列化出任意类?
修改下 Person
类型
public class Person { private String name; private Integer age; private Object cls; // getter setter toString }
假设此时有一个存在危险函数的类
public class Vuln { String cmd; Vuln(){ System.out.println("init"); } public String getCmd() { System.out.println("get"); return cmd; } public void setCmd(String cmd) throws IOException { System.out.println("set"); this.cmd = cmd; Runtime.getRuntime().exec(cmd); } }
尝试去反序列化这个 Vuln
类
ObjectMapper mapper = new ObjectMapper(); mapper.enableDefaultTyping(); String json = "{/"name/":/"kingkk/",/"age/":20,/"cls/":[/"vuln.Vuln/",{/"cmd/":/"calc/"}]}"; System.out.println(mapper.readValue(json, Person.class));
可以看到是可以反序列化到任意类的,而且当 setter
或构造函数中存在危险函数时也会自动触发。
显然,实际代码中开发也几乎不可能写出那样不靠谱的 Vuln
类,也为了寻找一种更为通用的反序列化链,也就是所说的Gadget
CVE-2017-7525
中就是利用JDK7u21的 com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
作为Gadget。
具体的触发流程我也没具体跟进了,总的原理和之前 Vuln
类中的原理类似。
触发的调节为
enableDefaultTyping Object
在这篇文章中也了解到 http://www.leadroyal.cn/?p=630
当java bean的字段上加上了
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) @JsonTypeInfo(use = JsonTypeInfo.Id.MANIMAL_CLASS)
两个注解时,即使没有开启 enableDefaultTyping
也可以进行指定类的反序列化。亲测有效。
至于漏洞的修补比较惊讶的是基于黑名单的修复,仅仅限制了可以反序列化的类,导致之前的Poc失效。
https://github.com/FasterXML/jackson-databind/commit/60d459cedcf079c6106ae7da2ac562bc32dcabe1
也就意味着只要找到新的Gadget就还是可以利用这个洞(咋感觉比之前Weblogic XMLDecoder的修复还要不靠谱!