Tomcat版本对JNDI注入的影响

0x00 前言

​ 最近在更新我的github工具,但是其实在注入的时候因为springboot内置的tomcat版本导致注入的失败,具体的区别就是Apache Tomcat/9.0.62可以注入成功但是之后的就不行了,于是在想知其然也要知其所以然,本文就简单探究一下这两者之间的区别。

0x01 版本测试

​ 经过我的测试,在使用RMI协议的时候具体可以分别实现的版本信息如下:

JDK版本 Spingboot版本 Tomcat版本 RCE EL表达式BypassRCE 内存马注入
JDK8u111 2.7.14 Apache Tomcat/9.0.78 ✅ ❌ ✅
JDK8u111 2.5.14 Apache Tomcat/9.0.63 ✅ ❌ ✅
JDK8u111 2.5.13 Apache Tomcat/9.0.62 ✅ ✅ ✅
JDK8u191 2.7.14 Apache Tomcat/9.0.78 ❌ ❌ ❌
JDK8u191 2.5.14 Apache Tomcat/9.0.63 ❌ ❌ ❌
JDK8u191 2.5.13 Apache Tomcat/9.0.62 ❌ ✅ ❌
JDK8u361 2.5.14 Apache Tomcat/9.0.63 ❌ ❌ ❌
JDK8u361 2.5.13 Apache Tomcat/9.0.62 ❌ ✅ ❌

​ 可以看出来正常的RCE和内存马的注入收到JDK版本的影响,在RMI协议的层面是从JDK8u113之后,系统属性 com.sun.jndi.rmi.object.trustURLCodebase、com.sun.jndi.cosnaming.object.trustURLCodebase 的默认值变为false,即默认不允许从远程的Codebase加载Reference工厂类。于是我们来探究一下Tomcat9.0.62-63版本之间的变化,为什么EL表达式的RCE也不行了。

​ 这里我选择JDK8u361 & Springboot2.5.13版本来进行测试成功的BypassRCE,来看一下RCE的调用栈

image-20230907101344230

定位到一个关键类org.apache.naming.factory.BeanFactory来进行注入210行的method.invoke(bean, valueArray);这里是我们通过反射来调用的关键函数。

0x02 对比代码

下图左边是9.0.62版本的代码,右边是9.0.63的代码。红框中关键的method.invoke(bean,valueArray)方法不见了。

image-20230907103403988

其实不只是method.invoke()方法不再存在而是整个关于forceString部分的处理都被删除了。

image-20230907104250399

那么我们其实可以思考一下为什么一定要用BeanFactory呢?

  • 实现ObjectFactory是一个对象工厂
  • 因为是ObjectFactory就会存在getObjectInstance()方法
  • getObjectInstance()方法中存在自定义的反射可以自由控制

这样思路就来了,看到javax.naming.spi.ObjectFactory的实现类中可以尝试一个一个的寻找,并且查询其中的getObjectInstance()方法。之后的文章中会更新一下关于LDAP反序列化的知识,以及尝试使用LDAP反序列化来绕过高版本限制。