基础CC链汇总
图来自 https://www.anquanke.com/member.html?memberId=149714 师傅的文章,跟着重新复习了几条常见 CC 链,同时捋了一下 java 反序列化 gadget 链的挖掘思路。
挖掘思路
一般从可执行命令的地方开始(任意代码执行/动态加载字节类),一步一步往上找,直到进到一个能反序列化类的 readObject 方法。CC1
执行命令的部分来自 ChainedTransformer 的执行链,其中 InvokerTransformer 可以执行任意类的任意方法,然后利用 ChainedTransformer 把 InvokerTransformer 串起了,达到任意命令执行的效果。
下一步就是找一个 可控类.transform()(触发命令执行),这里用的是 LazyMap.decorate 方法,第二个参数接受一个 Transformer(ChainedTransformer的父类),在 LazyMap 的 get 方法中触发了命令的执行:
public Object get(Object key) { if (!super.map.containsKey(key)) { Object value = this.factory.transform(key); super.map.put(key, value); return value; } else { return super.map.get(key); } }
接着就是找 可控类.get ,在 AnnotationInvocationHandler invoke方法中找到了可以用的方法
Object var6 = this.memberValues.get(var4);
关于 invoke 方法的调用,就可以用 AnnotationInvocationHandler 的代理来触发,代理成功之后在调用原来类方法的时候,首先会调用 InvocationHandler实现类(也就是 AnnotationInvocationHandler)的 invoke 方法。所以最后只要把 memberValues 设置为代理类,在 AnnotationInvocationHandler.readObject 中调用了 entrySet 方法时就会触发 invoke 方法。CC2
cc2 依赖的时 commons-collections4.0,和 CC1 几乎是完全不同的思路。
CC2 中触发了命令利用了 TemplatesImpl 动态加载字节码
其中要将字节码以二维数组的形式存储在 _bytecodes 中,字节码的生成可以直接读一个 class 文件或者用 Javassist 修改,注意要继承抽象类 AbstractTranslet
public static class StubTransletPayload extends AbstractTranslet implements Serializable { public void transform (DOM document, SerializationHandler[] handlers ) throws TransletException {} @Override public void transform (DOM document, DTMAxisIterator iterator, SerializationHandler handler ) throws TransletException {} }
调用 Templateslmpl 也可以用 InvokerTransformer 来完成,那么下一步还是要找 可控类.transform(),CC2 中用的是 TransformingComparator.compare 方法,
public int compare(I obj1, I obj2) { O value1 = this.transformer.transform(obj1); O value2 = this.transformer.transform(obj2); return this.decorated.compare(value1, value2); }
接着在 PriorityQueue 中找到了 siftDownUsingComparator 方法可以调用 compare 方法,参数存放在 queue 中(这一部分其实是一个二叉堆的实现),在 siftDown 中找到了调用,
private void siftDown(int k, E x) { if (comparator != null) siftDownUsingComparator(k, x); else siftDownComparable(k, x); }
下一步可以在 heapify 中找到 siftDown 的调用,在 readObject 中调用 heapify,链子这就串起来了。CC3
如果前两条链都用 Find Usage 方式找调用的方法,就会发现还有很多可以用的方式。
CC3 和 CC2 主要的区别是调用加载字节码的方式不同,InvokerTransformer 类调用任意对象的任意方法(调用的templates
中的newTransformer
方法)在 commons-collections:4.4 中被修补,CC3 利用了 TraXFilter,同样是调用templates
中的newTransformer
方法实现字节码的加载。
public TrAXFilter(Templates templates) throws TransformerConfigurationException { _templates = templates; _transformer = (TransformerImpl) templates.newTransformer(); _transformerHandler = new TransformerHandlerImpl(_transformer); _overrideDefaultParser = _transformer.overrideDefaultParser(); }
如何触发 TrAXFilter 的构造方法有两种思路,第一种是利用 CC1 中 ChainedTransformer 执行链可以执行任意类的任意方法这一特点,直接调用 TrAXFilter 中的 templates 中的 newTransformer,剩下的部分就和 CC1 一样了(相当于修改了 CC1 命令执行的方式)。
第二种方式引入了新的 InstantiateTransformer 类,用其中的 transform 方法,
transform 方法调用时会反射执行其传递的参数 class 类的有参构造函数,就可以触发TrAXFilter 的构造方法。至于如何调用 InstantiateTransformer 的 transform 方法也是通过 CC1 的前半段完成的。CC4
cc2 的前半段 + cc3 的后半段
CC5
执行命令部分用的 CC1 后半段,在触发方式上做了修改
在 LazyMap 中的 getValue 会调用 get 方法,在 TiedMapEntry 的 toString 中发现了 getValue
public String toString() { return this.getKey() + "=" + this.getValue(); }
然后就是要找一个 可控类.toString 方法,CC5 用的是 BadAttributeValueExpException 的 readObject,刚好也可以触发反序列化。
其中的 valObj 也是可以通过反射被我们控制的。CC6
和 CC5 的后半段一样,不过是用 TiedMapEntry 中的 hashCode 调用的Lazymap 的 getValue 方法
public int hashCode() { Object value = this.getValue(); return (this.getKey() == null ? 0 : this.getKey().hashCode()) ^ (value == null ? 0 : value.hashCode()); }
这里是通过 HashMap 中的 hash 调用的 key.hashCode,在 put 中调用 hash
static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
public V put(K key, V value) { return putVal(hash(key), key, value, false, true); }
然后就是要找 可控类.put 方法,正好在 HashSet 的 readObject 中就可以直接调用。
for (int i=0; i<size; i++) { @SuppressWarnings("unchecked") E e = (E) s.readObject(); map.put(e, PRESENT); }
其中的 s 就是传入的字节流。CC7
后半段还是 CC1 不变,又换了中方式调用 LazyMap.get
在 AbstractMap 中 equals 方法中可以找到可控的 get 方法,
再找一个调用 equals 方法的位置,用了 Hashtable 中的 reconstitutionPut 方法,而 reconstitutionPut 就在 Hashtable 的 readObject 中调用,也就完成了反序列的链接。参考文献