0x00 前言
趁着五一休息,分析了一下Spring的历史漏洞,这里记录一下对Spring-Messaging远程代码执行漏洞(CVE-2018-1270)的分析。该漏洞涉及到如下概念:
1、WebSocket协议,是HTML5提供的一种可在单个TCP连接上进行全双工通讯的协议。即允许服务端主动向客户端推送数据,详情可以参考这里,讲的挺好的。
2、SockJS,一个JavaScript库,它在浏览器和Web服务器之间创建了一个低延迟、全双工、跨域通信通道。另外由于WebSocket是HTML5新增的特性,一些浏览器可能不支持,因此回退机制就很必要。SockJS就提供了该功能,即优先使用WebSocket,当浏览器不支持WebSocket时,会自动降为轮询或其他方式。
3、STOMP(Simple Text Orientated Messaging Protocol),简单面向文本的消息传递协议,可以理解为它是对WebSocket协议的封装,可以类比http与tcp的关系。
0x01 环境搭建
使用Spring提供的基于WebSocket协议构建交互式Web应用程序的demo。
1、将其下载到本地,如下的complete文件夹下是一个完整的SpringBoot项目,可以使用Maven或Gradle的方式在本地构建该项目:
2、将complete文件夹导入到IDEA中,我用的Maven构建的项目,等到依赖都下载完后,就可以运行项目了,项目不大,其中的每个类的作用在github上都有说明,这里就不赘述了:
0x02 漏洞利用
这里参考的这篇文章。
该漏洞影响的Spring的版本如下:
Spring Framework 5.0 to 5.0.4
Spring Framework 4.3 to 4.3.14
1、由于在2.2.2.RELEASE版本的SpringBoot中,使用的Spring版本为5.2.2.RELEASE,因此需修改上面环境中的SpringBoot版本为2.0.0.RELEASE:
这时对应的Spring的版本为5.0.4.RELEASE。
2、在static/app.js文件中的connect方法中添加如下代码:
var header = {
"selector":"T(java.lang.Runtime).getRuntime().exec('calc.exe')"};
如下图所示:
T(java.lang.Runtime).getRuntime().exec('calc.exe')
是个Spring表达式,可通过如下方式来解析SpEL:
String selector = "T(java.lang.Runtime).getRuntime().exec('calc.exe')";
Expression expression = new SpelExpressionParser().parseExpression(selector);
expression.getValue();
3、访问该项目,先点击Connect按钮与服务端建立连接,这时selector头也发送到了服务端:
4、发送消息与服务端通信时,之前发送的Spring表达式会被解析,从而造成rce。
0x03 漏洞原理
1、从上面参考的这篇文章知道,解析SpEL发生在org.springframework.messaging.simp.broker.DefaultSubscriptionRegistry#filterSubscriptions方法中,查看该方法发现只有selectorHeaderInUse属性值为true时,才能执行后续逻辑,但该属性值默认为false:
2、通过搜索发现在org.springframework.messaging.simp.broker.DefaultSubscriptionRegistry#addSubscriptionInternal方法中将selectorHeaderInUse属性值设置成了true,于是在如下位置设置断点,当在页面点击Connect按钮时,会执行到该断点处:
从上面的代码可以看到sessinId(“txaqe0mm”)、subsId(“sub-0”)、订阅地址(“/topic/greetings”)、selector值(“T(java.lang.Runtime).getRuntime().exec(‘calc.exe’)”)均被使用addSubscription方法加入到了this.subscriptionRegistry属性中。
3、将断点放行,在org.springframework.messaging.simp.broker.DefaultSubscriptionRegistry#filterSubscriptions方法中设置断点,在页面点击Send按钮往服务端发送消息,程序则会运行至该断点处,如下先从参数中拿到sessionId(“txaqe0mm”):
4、然后根据该sessionId(“txaqe0mm”)值最终从this.subscriptionRegistry属性中获取到了前面用addSubscription方法添加进去的那些信息:
5、获取到前面添加的使用selector值(“T(java.lang.Runtime).getRuntime().exec(‘calc.exe’)”)创建的Expression对象,在执行该对象的getValue方法时,表达式即被解析:
需要注意到上图中执行expression.getValue方法时传入的EvaluationContext对象类型为StandardEvaluationContext,该Context类型的对象支持执行任意SpEL表达式。
0x04 漏洞修复
1、修改SpringBoot的版本为2.0.1.RELEASE,其对应的Spring版本为5.0.5.RELEASE,在该版本中漏洞已被修复:
2、查看org.springframework.messaging.simp.broker.DefaultSubscriptionRegistry#filterSubscriptions方法中解析SpEL处代码如下:
可以看到调用expression.getValue方法时传入的Context对象类型替换为了SimpleEvaluationContext:
3、通过调试最终发现在org.springframework.expression.spel.ExpressionState#findType方法中获取SpEL表达式中的被解析的类的类型(本例中为java.lang.Runtime)时,会调用SimpleEvaluationContext类的findType方法:
4、该findType方法的实现即为下图所示的Lambda表达式,可以看到只有一个抛出异常的操作,因此在使用SimpleEvaluationContext后,即阻止了在SpEL表达式中对java.lang.Runtime、java.lang.ProcessBuilder等类的解析:
0x05 参考
- https://www.zhihu.com/question/20215561
- https://github.com/spring-guides/gs-messaging-stomp-websocket
- https://cert.360.cn/warning/detail?id=3efa573a1116c8e6eed3b47f78723f12