图来自 https://www.anquanke.com/member.html?memberId=149714 师傅的文章,跟着重新复习了几条常见 CC 链,同时捋了一下 java 反序列化 gadget 链的挖掘思路。

  • 挖掘思路


    一般从可执行命令的地方开始(任意代码执行/动态加载字节类),一步一步往上找,直到进到一个能反序列化类的 readObject 方法。
  • CC1


    t012f7027c5f2e5990b
    执行命令的部分来自 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 几乎是完全不同的思路。
    t019438ead74d456810
    CC2 中触发了命令利用了 TemplatesImpl 动态加载字节码
    image-20220223103137275
    其中要将字节码以二维数组的形式存储在 _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 方式找调用的方法,就会发现还有很多可以用的方式。
    t01181aaf1ddb8ae282
    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 方法,
    image-20220223112713411
    transform 方法调用时会反射执行其传递的参数 class 类的有参构造函数,就可以触发TrAXFilter 的构造方法。至于如何调用 InstantiateTransformer 的 transform 方法也是通过 CC1 的前半段完成的。
  • CC4


    cc2 的前半段 + cc3 的后半段
    t01e85b30f42ab156c7
  • CC5


    执行命令部分用的 CC1 后半段,在触发方式上做了修改
    t0131f14bdb6104db76
    在 LazyMap 中的 getValue 会调用 get 方法,在 TiedMapEntry 的 toString 中发现了 getValue
    public String toString() {
            return this.getKey() + "=" + this.getValue();
        }
    

    然后就是要找一个 可控类.toString 方法,CC5 用的是 BadAttributeValueExpException 的 readObject,刚好也可以触发反序列化。
    image-20220223121510425
    其中的 valObj 也是可以通过反射被我们控制的。
  • CC6


    和 CC5 的后半段一样,不过是用 TiedMapEntry 中的 hashCode 调用的Lazymap 的 getValue 方法
    t0117e939ee71ef5422
        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
    t010cf1e49b1a3d5af4
    在 AbstractMap 中 equals 方法中可以找到可控的 get 方法,
    image-20220223124353975
    再找一个调用 equals 方法的位置,用了 Hashtable 中的 reconstitutionPut 方法,而 reconstitutionPut 就在 Hashtable 的 readObject 中调用,也就完成了反序列的链接。
  • 参考文献


 

标签: none

添加新评论