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的调用栈

定位到一个关键类org.apache.naming.factory.BeanFactory来进行注入210行的method.invoke(bean, valueArray);这里是我们通过反射来调用的关键函数。
0x02 对比代码
下图左边是9.0.62版本的代码,右边是9.0.63的代码。红框中关键的method.invoke(bean,valueArray)方法不见了。

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

那么我们其实可以思考一下为什么一定要用BeanFactory呢?
- 实现ObjectFactory是一个对象工厂
- 因为是ObjectFactory就会存在
getObjectInstance()方法 getObjectInstance()方法中存在自定义的反射可以自由控制
这样思路就来了,看到javax.naming.spi.ObjectFactory的实现类中可以尝试一个一个的寻找,并且查询其中的getObjectInstance()方法。之后的文章中会更新一下关于LDAP反序列化的知识,以及尝试使用LDAP反序列化来绕过高版本限制。