一:漏洞简介
Spring框架中通过spring-messaging模块来实现STOMP(Simple Text-Orientated Messaging Protocol),STOMP是一种封装WebSocket的简单消息协议。攻击者可以通过建立WebSocket连接并发送一条消息造成远程代码执行。
二:协议介绍
STOMP(Simple Text-Orientated Messaging Protocol) 面向消息的简单文本协议,用于服务器在客户端之间进行异步消息传递。STOMP帧由命令,一个或多个头信息、一个空行及负载(文本或字节)所组成
客户端可以使用SEND命令来发送消息以及描述消息的内容,用SUBSCRIBE命令来订阅消息以及由谁来接收消息。这样就可以建立一个发布订阅系统,消息可以从客户端发送到服务器进行操作,服务器也可以推送消息到客户端
通讯过程:
客户端与服务器进行HTTP握手连接
客户端通过发送CONNECT帧建立连接
服务器端接收到连接尝试返回CONNECTED帧
客户端通过SUBSCRIBE向服务端订阅消息主题
客户端通过SEND向服务端发送消息
要从浏览器连接,对于SockJS,可以使用sockjs-client。对于STOMP来说,许多应用程序都使用了jmesnil/stomp-websocket库(也称为STOMP.js),它是功能完备的,已经在生产中使用了多年,但不再被维护。目前jsteunou/webstom-client是该库最积极维护和发展的继承者
三:影响版本
Spring Framework 5.0 to 5.0.4
Spring Framework 4.3 to 4.3.14
四:漏洞复现
使用官方demo:https://github.com/spring-guides/gs-messaging-stomp-websocket
Git clone 后切换到指定分支,因为SpringBoot的版本问题需要使用旧版本
Git checkout 6958af0b02bf05282673826b73cd7a85e84c12d3
Checkout后目录如下:
其中complete文件夹下是一个完整的SpringBoot项目,可以使用Maven或Gradle的方式在本地构建该项目,这里使用Gradle的方式
1、将complete文件夹导入到IDEA中,等待依赖都下载完成
2、查看pom文件中spring-boot的版本是否是漏洞版本
3、修改resources/static/app.js文件(注意:这里修改app.js代码不是修改源码,appjs是返回给用户交给浏览器执行的,用户可以随意修改。之所以在代码中修改,是为了方便做复现)
4、然后启动项目,网页访问127.0.0.1:8080,点击connect,然后在send一个消息就可以执行命令
五:漏洞分析
1、根据参考链接中的相关文章我们知道,解析SpEL发生在org.springframework.messaging.simp.broker.DefaultSubscriptionRegistry#filterSubscriptions方法中,查看该方法只有selectorHeaderInUse为True时才能继续往下执行后续逻辑,但该属性默认值为False
2、然后通过搜索发现在org.springframework.messaging.simp.broker.DefaultSubscriptionRegistry#addSubscriptionInternal方法中将selectorHeaderInUse属性值设置成了True,于是在如下位置设置断点,页面点击connect按钮时,会执行到该断点处并且可以看到sessinId、subsId、订阅地址、selector等参数的值,我们传入的执行命令的类就是selector的值,然后这些值都被使用addSubscription方法加入到了this.subscriptionRegistry属性中。
3、我们直接去看命令是怎么执行的,在org.springframework.messaging.simp.broker.DefaultSubscriptionRegistry#filterSubscriptions方法中设置断点,在页面点击send按钮往服务器发送消息之后同时操作直接从上一个断点跳到这个断点,程序会运行到该断点处。
4、然后继续执行,程序会首先获取sessionId的值
5、然后根据sessionId的值最终从this.subscriptionRegistry属性中获取前边用addSubscription方法传进去的信息
6、然后查看获取到的信息发现有我们传入的selector的值(执行命令的类以及命令T(java.lang.Runtime).getRuntime().exec(‘calc.exe’))被创建为expression对象并且可以被执行
7、然后继续往下发现在执行expression对象的getvalue方法时,我们传入的表达式已经被解析
8、需要注意到上图中执行expression.getValue方法时传入的EvaluationContext对象类型为StandardEvaluationContext,该Context类型的对象支持执行任意SpEL表达式。
六:参考链接
https://blog.csdn.net/qsort_/article/details/105906256
https://4ra1n.love/post/ZWiWbaVfk/
https://github.com/spring-guides/gs-messaging-stomp-websocket/wiki