WebSocket作为HTML5的新特性之一格外吸引着开发人员的注意,因为它的出现使得客户端(主要指浏览器)提供对Socket的支持成为可能,从而在客户端和服务器之间提供了一个基于单TCP连接的双向通道。对于实时性要求比较高的应用而言,譬如在线证券、在线游戏,以及不同设备之间信息同步。信息实时同步一直是技术难题,在WebSocket出现之前,常见解决方案一般就是轮询(Polling)和Comet技术,但这些技术增加了设计复杂度,也造成了网络和服务器的额外负担,在负载较大的情况下效率相对低下,导致应用的可伸缩行收到制约。对于此类应用的开发者来说,WebSocket技术简直就是神兵利器,读者可以登陆websocket.org网站观看特色案例,以及它提供的WebSocket和Comet的性能对比分析报告。最近几年内WebSocket技术被开发人员广泛应用到各类实际应用中。不幸的是,WebSocket相关的安全漏洞也逐步被披露出来,其中最容易发生的就是跨站点WebSocket劫持漏洞。
WebSocket与HTTP的关系
了解过WebSocket技术的读者都知道ws://和http://,那么WebSocket和HTTP是什么关系呢。WebSocket是HTML5推出的新协议,跟HTTP协议内容本身没有关系。WebSocket是持久化的协议,而HTTP是非持久连接。正如前文所述,WebSocket提供了全双工沟通,俗称Web的TCP连接,但TCP通常处理字节流(跟消息无关),而WebSocket基于TCP实现了消息流。WebSocket也类似于TCP一样进行握手连接,跟TCP不同的是,WebSocket是基于HTTP协议进行的握手。
具体的握手协议大家可以抓取websocket.org网站的Echo测试服务的协议握手请求和响应,协议中最核心的就是Connection:Upgrade和Upgrade:websocket这两行。这两行相当于告诉服务器端:我要申请切换到WebSocket协议。
一旦服务器端返回101响应,即可完成WebSocket协议切换。服务器端即可以基于相同端口,将通信协议从http://或https://切换到ws://或wss://。协议切换完成后,浏览器和服务器端即可以使用WebSocketAPI互相发送和收取文本和二进制消息
WebSocket安全相关的重要头部参数
Sec-WebSocket-Key和Sec-WebSocket-Accept。这涉及一个WebSocket安全特性,客户端负责生成一个Base64编码过的随机数字作为Sec-WebSocket-Key,服务器则会将一个GUID和这个客户端的随机数一起生成一个散列Key作为Sec-WebSocket-Accept返回给客户端。这个工作机制可以用来避免缓存代理(cachingproxy),也可以用来避免请求重播(requestreplay)。
大家可能也注意到很多其他“Sec-”开头的WebSocket相关的Header。这其实也是WebSocket设计者为了安全的特意设计,以“Sec-”开头的Header可以避免被浏览器脚本读取到,这样攻击者就不能利用XMLHttpRequest伪造WebSocket请求来执行跨协议攻击,因为XMLHttpRequest接口不允许设置Sec-开头的Header。
常见的WebSocket安全漏洞
Wireshark的漏洞CVE-2013-3562
Wireshark1.8.7之前的1.8.x版本中的Websocket解析器中的epan/dissectors/packet-websocket.c中的tvb_unmasked函数中存在多个整数符号错误,远程攻击者可通过恶意的数据包利用这些漏洞造成拒绝服务
Asterisk WebSocket Server的DoS漏洞CVE-2014-9374
该WebSocket Server某模块中存在双重释放漏洞,远程攻击者可通过发送零长度的帧利用该漏洞造成拒绝服务
OpenStack Nova console的WebSocket漏洞CVE-2015-0259
这个漏洞得到广泛关注,并且被在很多WebSocket应用中发现。这种漏洞早在2013年就被一个德国的白帽黑客 Christian Schneider 发现并公开,Christian将之命名为跨站点WebSocket劫持Cross Site WebSocket Hijacking(CSWSH)。跨站点WebSocket劫持相对危害较大,也更容易被开发人员忽视。
跨站点WebSocket劫持漏洞原理
为了创建全双工通信,客户端需要基于HTTP进行握手切换到WebSocket协议,这个升级协议的过程正是潜在的阿喀琉斯之踵。大家仔细观察上文的握手Get请求,可以看到Cookie头部把域名下的Cookie都发送到服务器端。如果有机会阅读WebSocket协议你可能就会发现,WebSocket协议没有规定服务器在握手阶段应该如何认证客户端身份。服务器可以采用任何HTTP服务器的客户端身份认证机制,譬如cookie,HTTP基础认证,TLS身份认证等。因此,对于绝大多数Web应用来说,客户端身份认证应该都是SessionID等Cookie或者HTTP Auth头部参数等。熟悉跨站点请求伪造攻击Cross Site Request Forgery(CSRF)的朋友到这里应该就可以联想到黑客可能伪造握手请求来绕过身份认证。
因为WebSocket的客户端不仅仅局限于浏览器,因此WebSocket规范没有规范Origin必须相同。所有的浏览器都会发送Origin请求头,如果服务器端没有针对Origin头部进行验证可能会导致跨站点WebSocket劫持攻击。譬如,某个用户已经登录了应用程序,如果他被诱骗访问某个社交网站的恶意网页,恶意网页在某元素中植入一个WebSocket握手请求申请跟目标应用建立WebSocket连接。Origin和Sec-WebSocket-Key都是由浏览器自动生成,Cookie等身份认证参数也都是由浏览器自动上传到目标应用服务器端。如果服务器端疏于检查Origin,该请求则会成功握手切换到WebSocket协议,恶意网页就可以成功绕过身份认证连接到 WebSocket 服务器,进而窃取到服务器端发来的信息,抑或发送伪造信息到服务器端篡改服务器端数据。有兴趣的读者可以将这个漏洞跟CSRF进行对比,CSRF主要是通过恶意网页悄悄发起数据修改请求,不会导致信息泄漏问题,而跨站点WebSocket伪造攻击不仅可以修改服务器数据,还可以控制整个读取/修改双向沟通通道。正是因为这个原因,Christian将这个漏洞命名为劫持(Hijacking),而不是请求伪造(Request Forgery)。
到这里也许熟悉JavaScript跨域资源访问的读者可能会怀疑以上观点。如果HTTP Response没有指定Access-Control-Allow-Origin的话,浏览器端的脚本是无法访问跨域资源的啊,是的,这就是众所周知的跨域资源共享Cross-Origin Resource Sharing(CORS),这确实也是HTML5带来的新特性之一。但是很不幸,跨域资源共享不适应于WebSocket,WebSocket没有明确规定跨域处理的方法。
参考资料
WebSocketRRFC6455规范
WebSocket测试指南
跨站点WebSocket劫持漏洞的过程
版权声明:本文为博主原创文章,未经博主允许不得转载。
: » 详解跨站点WebSocket劫持漏洞的原理
原创文章,作者:dweifng,如若转载,请注明出处:https://blog.ytso.com/tech/aiops/251147.html