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的修复还要不靠谱!