原理
反序列化,核心就是从一个字节流中还原为对象的状态。
JDK 是存储着每一个成员变量的值、类型。反序列化时,递归获取出来,然后调用。
JSON 同理,只不过类似 Jackson 之类的框架,将 Class
作为序列化类型的一部分,从而间接支持多态。
由于扩大了反序列化的范围,因此,当无法限制反序列化的具体类型时,就容易出现问题。
JDK 漏洞
低级漏洞-利用当前存在问题的反序列化类
利用存在的类,直接反序列化对应的类时,触发相关的方法。
private void readObject(java.io.ObjectInputStream in) {
Runtime.getRuntime.exec("xx");
}
高级漏洞-传入不存在的 class
通过已经存在的逻辑中会调用 ObjectInputStream
的类,构造攻击链,来传入不存在的 class
从而进行攻击
com.fr.third.org.apache.commons.collections4.bag.TreeBag#readObject
com.fr.third.org.apache.commons.collections4.bag.AbstractMapBag#doReadObject(Map<>)
- 传入TreeMap
java.util.TreeMap#put
java.util.TreeMap#compare
java.util.Comparator#compare
com.fr.base.core.serializable.SerialableLocalCollator#compare
com.fr.json.JSONArray#toString
- 调用getXXX
java.security.SignedObject#getObject
public Object getObject()
throws IOException, ClassNotFoundException
{
// creating a stream pipe-line, from b to a
ByteArrayInputStream b = new ByteArrayInputStream(this.content);
ObjectInput a = new ObjectInputStream(b);
Object obj = a.readObject();
b.close();
a.close();
return obj;
}
JDK 反序列化漏洞的危险性是成倍提升的。 因为支持了 inputstream
所以可以传入不存在的 class
进行攻击。
总结
JDK 漏洞当满足下面的任意条件即可出现
- 当前存在可以直接利用 逻辑存在问题的
readObject
的类 - 存在 调用
ObjectInputStream
的类
JSON 漏洞
这里只分析 Jackson 的问题, FastJson 直接忽略。臭名昭著。完全不考虑。
原理 enableDefaultTyping
- 操作
com.fasterxml.jackson.databind.ObjectMapper#enableDefaultTyping()
- 输出信息
{'object':["classpath", {"value": "test"}]}
- 反序列化时,就会通过 classpath 反序列化对应的类,并触发
setXX
- 服务端 classpath 存在 任意可以利用的类 ,如
com.sun.rowset.JdbcRowSetImpl
,target 类满足setXX
可控触发 JDNI 注入利用。
原理 JsonTypeInfo.Id.CLASS
com.fr.third.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector
protected TypeResolverBuilder<?> _findTypeResolver(MapperConfig<?> config,
Annotated ann, JavaType baseType)
{
// First: maybe we have explicit type resolver?
TypeResolverBuilder<?> b;
JsonTypeInfo info = _findAnnotation(ann, JsonTypeInfo.class);
JsonTypeResolver resAnn = _findAnnotation(ann, JsonTypeResolver.class);
}
这个类中会获取到该类型的 TypeResolverBuilder
如果这个类是支持 JsonTypeInfo.Id.CLASS
, 那么就会用这个方法去处理对象。
此时存在两种类型。
1、属性不为 Object 类时
要进行反序列化的类的属性所属类的构造函数或 setter 方法本身存在漏洞,当然这种场景开发几乎不会这么写。
public class MySex implements Sex {
int sex;
public MySex() {
System.out.println("MySex构造函数");
}
@Override
public int getSex() {
System.out.println("MySex.getSex");
return sex;
}
@Override
public void setSex(int sex) {
System.out.println("MySex.setSex");
this.sex = sex;
try {
Runtime.getRuntime().exec("calc");
} catch (Exception e) {
e.printStackTrace();
}
}
}
public class Person {
public int age;
public String name;
@JsonTypeInfo(use = JsonTypeInfo.Id.MINIMAL_CLASS)
public Sex sex;
public Person() {
System.out.println("Person构造函数");
}
@Override
public String toString() {
return String.format("Person.age=%d, Person.name=%s, %s", age, name, sex == null ? "null" : sex);
}
}
2、属性为 Object 类时
当属性类型为 Object 时,因为 Object 类型是任意类型的父类,因此扩大了我们的攻击面,我们只需要寻找出在目标服务端环境中存在的且构造函数或 setter 方法存在漏洞代码的类即可进行攻击利用。
public class Evil {
String cmd;
public void setCmd(String cmd) {
this.cmd = cmd;
try {
Runtime.getRuntime().exec(this.cmd);
} catch (Exception e) {
e.printStackTrace();
}
}
}
public class Person {
public int age;
public String name;
@JsonTypeInfo(use = JsonTypeInfo.Id.MINIMAL_CLASS)
public Object object;
public Person() {
System.out.println("Person构造函数");
}
@Override
public String toString() {
return String.format("Person.age=%d, Person.name=%s, %s", age, name, object == null ? "null" : object);
}
}
见上面的 Person 中存在对 Object 的反序列化支持。所以 通过下面的方式,即可模拟调用出计算器。
public class JSTest {
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTyping();
String json = "{\"age\":6,\"name\":\"mi1k7ea\",\"object\":[\"com.evil.Evil\",{\"cmd\":\"calc\"}]}";
Person p2 = mapper.readValue(json, Person.class);
System.out.println(p2);
}
}
总结
Jackson 满足下面三个条件之一即可开启 Jackson 反序列化漏洞:
- 调用了 ObjectMapper.enableDefaultTyping()函数;
- 存在可以利用的类,比如
com.sun.rowset.JdbcRowSetImpl
- 存在可以利用的类,比如
- 对要进行反序列化的类的属性使用了值为
JsonTypeInfo.Id.CLASS
的@JsonTypeInfo
注解; - 对要进行反序列化的类的属性使用了值为
JsonTypeInfo.Id.MINIMAL_CLASS
的@JsonTypeInfo
注解;
和 JDK 的对比
可以看到,JDK 是从 all classes
里面找到可能存在的漏洞(逻辑漏洞+自定义漏洞)。
但是 Jackson 是有分层的。
如果
- 开启
enableDefaultTypeing
- 对 Object 开启
JsonTypeInfo.Id
的注解 那么等同于 “JDK + 利用存在的逻辑漏洞“
如果仅对必须的业务对象开启开启 JsonTypeInfo.Id
的注解,那么整个的范围立刻就缩小到开启注解的 biz-classes
。整个漏洞的范围一下就缩小到可控。
只要开发人员不对业务逻辑的 get/set/construct
做后门,一般不会存在问题。
最坏最坏,就是将相关的业务对象改写为不需要开启 JsonTypeInfo.Id
的逻辑即可。
综上可以发现,0 << json-属性不为 object << json-属性为 object << JDK
参考
JavaSec Jackjson反序列化漏洞利用原理 | thonsun’s blog
Jackson 反序列化漏洞基本原理 [ Mi1k7ea]
JDK反序列化漏洞