C3P0原生反序列化的JNDI打法

复习一下c3p0的反序列化打法,网上公开的大概就三种:

  • 原生反序列化,加载Reference指定的类工厂的字节码,需要出网

  • 配合json反序列化,有两个可以利用的setter

    • 一个打二次反序列化

    • 一个打JNDI

这里啰嗦地说一下原生反序列化的打法

PoolBackedDataSourceBase#readObject

image-20240308231523639

如果读取的对象是IndirectlySerialized的实例,调用getObject来恢复连接池数据源connectionPoolDataSource这个属性。 顾名思义,IndirectlySerialized是间接序列化,这是一个接口,我们看它的实现子类ReferenceSerialized#getObject

image-20240308231500846
image-20240308231450415

referenceToObject通过获取引用(Reference)中指定的工厂类地址(fClassLocation),构造一个URLClassLoader,再去加载工厂类(fClassName)。这个引用作为ReferenceSerialized对象的属性,我们是可以控制的。

再看一下这个IndirectlySerialized是怎么写进去序列化流的 PoolBackedDataSourceBase#writeObject image-20240308231429409

首先尝试序列化连接池数据源connectionPoolDataSource 若它不可被序列化,经过Indirector处理后再进行序列化,这是一个接口类,我们看它的实现子类ReferenceIndirector 下面看一下它是怎么处理这个不可被序列化的对象的,其实看到这里也能猜到,顾名思义它会包装一个引用(Reference)

image-20240308231413980

这里要求这个对象是可被引用的(Referenceable),获取它的引用后封装到ReferenceSerialized中,也就是上文反序列化中提到的类。 可以构造如下POC:

evil不能被反序列化,会经过ReferenceIndirector处理,拿到它的引用放入ReferenceSerialized,反序列化时会提取引用的工厂类地址,去远程加载工厂类。

上面反序列化的过程有一行特别醒目的代码 ReferenceSerialized#getObject

熟悉的JNDI sink,但之前传入lookup的都是字符串,这里要求是个Name接口类。对比一下,其实就是要让Name.get(0)返回那串神奇的URLldap://xxx/rmi://xxx/

image-20240308231350806

翻了一遍Name的实现类,挑了一个好用的javax.naming.CompoundName 其他实现类可能序列化和反序列化的时候会麻烦的,感兴趣的可以尝试。 主要关注下面几个方法

image-20240308231321375
image-20240308231335224

implmySyntax都是transient,所以序列化和反序列化时肯定有另外的处理。

image-20240308231304347

大概就是遍历了implcomponents属性(这是一个Vector),每个都写入序列化流。

image-20240308231241230

反序列化的时候,多次调用readObject再add进impl 大概知道了这个逻辑后我们简单构造一下

ok成功触发。 接下来的问题时,这个contextName属性我们在反序列化时是控制不了的 image-20240308231216847

默认为null 有很多解决方法,最简单就是调试的时候直接改掉这个值为我们构造的CompoundName 复杂一点的就是用javassist修改ReferenceIndirector这个类的无参构造函数,在里面对contextName属性进行赋值。 考虑到writeObject还得触发NotSerializableException,这里采取一个折中的方法,干脆直接本地重写PoolBackedDataSourceBase这个类,由上面的流程可知序列化本质就写入了一个ReferenceSerialized对象。

image-20240308231154816

其实原来那条URLClassLoader的链就挺好打了,这个打法复杂化了

大家看个乐

Last updated

Was this helpful?