译自:http://www.christian-schneider.net/CrossSiteWebSocketHijacking.html

新的HTML5 WebSocket技术可以在浏览器和服务器之间建立一个全双工的信道,这个特性吸引了越来越多的开发者和安全研究人员。开发者可以利用WebSocket从服务器向浏览器推送文本或是二进制消息,反之也可以。

在过去几个月里,我对利用WebSocket的后端程序进行了一些实验和渗透测试。然后我发现了一种开发人员在利用WebSocket过程中,使其应用出现漏洞的可能的情况,我称其为跨站WebSocket劫持攻击(CSWSH),也就是我接下来会介绍的内容。

协议升级(The Protocol Upgrade)

为了创建全双工的信道,WebSocket协议首先需要利用 http:// 或是 https:// 进行握手(handshake)来向WebSocket协议进行变换(switch)。通过握手可以有效地将通信协议升级至 ws://wss://(SSL保护下的信道)。但是,这个协议升级的过程却同样是一个可以被攻击的潜在目标,更是使用WebSocket来处理非公开数据应用的致命弱点,因为这种桥接/传输是基于http(s)的通信向基于ws(s)的WebSocket协议的转换。

典型的C/S WebSocket交互过程如下:

  1. 客户端发起建立在http(s)连接上的WebSocket握手请求(request)
  2. 服务端应答一个握手响应(全部由Web服务器透明的处理),并且响应的状态码是 101 Switching Protocols

之后,浏览器和服务端就都使用WebSocket API进行完全对称的通信(任何一方都可以发送或是接收文本或二进制消息)。在浏览器层面上,由规范W3's HTML5 WebSocket API specification定义;在协议层面上,由规范RFC 6455 "The WebSocket Protocol"定义。

接着来仔细分析握手请求,并且详细观察升级到WebSocket协议的请求头(这里虚构了一个股票投资组合管理应用程序,它使用WebSockets向登录用户快速推送新的股票报价并从用户处接收股票订单):

GET /trading/ws/stockPortfolio HTTP/1.1
Host: www.some-trading-application.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:23.0) Firefox/23.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: de-de,de;q=0.8,en-us;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
DNT: 1
Sec-WebSocket-Version: 13
Origin: https://www.some-trading-application.com
Sec-WebSocket-Key: x7nPlaiHMGDBuJeD6l7y/Q==
Cookie: JSESSIONID=1A9431CF043F851E0356F5837845B2EC
Connection: keep-alive, Upgrade
Pragma: no-cache
Cache-Control: no-cache
Upgrade: websocket

从http(s)握手请求的请求头可以看到,认证数据(例子中是Cookie项)与升级握手请求一起发送。HTTP-Authentication数据也是可行的。这两项在之间提到的规范和RFC中都是正确的。

在WebSocket握手请求成功之后,服务端会应答状态码为 101 Switching Protocols 的响应,并且紧接着浏览器与服务端之间就会建立一个基于 ws://wss:// 的连接。请求头中的 Sec-WebSocket-Key 是浏览器/服务端握手时内部机制的一部分(确认服务端已经接受并处理请求),该请求头在浏览器初始化WebSocket请求时被创建,且由浏览器自行维护。

关于在握手/升级阶段时的客户端认证,RFC 6455中相关内容如下:

该协议不指定在WebSocket握手过程中服务端验证客户端的方法。WebSocket服务端可以使用任意通用http服务端可用的客户端验证方式,例如cookies,HTTP authentication或是TLS authentication。

- RFC 6455 "The WebSocket Protocol", 第10.5章 WebSocket Client Authentication

这意味着开发者可以像常规http(s) web应用程序请求一样,使用例如cookies或者HTTP-Authentication来验证WebSocket握手请求。

跨站劫持

接着来考虑当开发人员在Web应用程序的登录(敏感)部分使用这种众所周知的session cookies来验证WebSocket握手/升级请求时,会发生什么情况:

因为WebSocket是不受同源策略(same-origin policy)限制的,攻击者可以非常容易的从一个恶意页面发起指向被攻击应用(例子中是股票应用)的 ws://wss:// 的URL的WebSocket请求(即握手/升级过程)。因为这是一个普通的http(s)请求,浏览器在发送请求时会携带cookies和HTTP-Authentication头,这对于跨站也是可行的。

看一下从恶意跨站页面发起的WebSocket的握手/升级请求(当已经登录股票交易应用的被攻击者访问页面)。WebSocket链接 wss://www.some-trading-application.com/trading/ws/stockPortfolio 从恶意页面 https://www.some-evil-attacker-application.com 上被访问。

GET /trading/ws/stockPortfolio HTTP/1.1
Host: www.some-trading-application.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:23.0) Firefox/23.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: de-de,de;q=0.8,en-us;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
DNT: 1
Sec-WebSocket-Version: 13
Origin: https://www.some-evil-attacker-application.com
Sec-WebSocket-Key: hP+ghc+KuZT2wQgRRikjBw==
Cookie: JSESSIONID=1A9431CF043F851E0356F5837845B2EC
Connection: keep-alive, Upgrade
Pragma: no-cache
Cache-Control: no-cache
Upgrade: websocket

可以看到,浏览器将验证信息(例子中使用的是session cookie)与WebSocket握手/升级请求一并发送。这与跨站请求伪造(Cross-Site Request Forgery)的攻击场景类似。但是这种攻击在WebSocket的情况下,可以建立一个与受害者相同认证的新WebSocket连接,从只写的CSRF攻击扩展到与WebSocket服务的完全读/写通信。因此,我将这种攻击向量称为跨站WebSocket劫持攻击(CSWSH)。

在这个场景下,攻击者可以接受到从WebSocket连接中推送过来的受害者股票投资组合更新,也可以通过WebSocket发送写请求来更新组合。这种攻击是可行的,因为服务端的WebSocket代码依赖于WebSocket握手/升级阶段过程中,浏览器发送来的session认证数据(cookie或HTTP-Authentication)。

同时还有另外一件很有意思的发现,WebSocket握手/升级请求会携带 Origin 头。这就像使用跨域资源共享的常规CORS请求一样:如果这是一个常规的http(s) CORS请求,在服务端没有显示允许的情况下(通过匹配 Access-Control-Allow-Origin 相应头),浏览器不允许恶意页面中的JavaScript获得响应。但是对于WebSocket,当服务端没有显示允许跨域请求时,这种缺省“限制响应访问”的“失效关闭”机制却正好相反:在例子中,服务端没有发送任何CORS响应头,但是浏览器仍旧处理跨站WebSocket请求响应并正确建立全双工WebSocket连接。这表名WebSocket不受同源策略(SOP)保护,所以当开发基于WebSocket的应用时,开发者不能依赖于SOP的保护。很明显,CORS与WebSocket没有任何关系,但是它们同样都使用了相同的请求头(Origin),因此服务端代码需要检查这个请求头。

防御手段

你可能已经注意到了,在应用程序中防御跨站WebSocket劫持攻击可以使用以下两种对策:

  1. 服务端检测WebSocket握手请求 Origin 头,因为该项旨在保护服务端免受攻击者利用受害者浏览器发起的跨站连接请求。
  2. 在握手请求中使用独立session随机令牌(类似CSRF-Tokens),并且在服务端进行验证。

如果你的应用使用WebSocket,并且通过访问服务端的Web Session,在WebSocket信道上交换或接收私有数据,那么必须尽快应用这些简单却有效的保护措施。

如果不需要从服务端WebSocket部分访问Web Session,那么只需要在你的WebSocket协议中使用自定义令牌或相关技术来处理认证或是授权,并且在握手请求阶段避免涉及Cookie或HTTP-Authentication等Web Session。

总结

渗透测试人员:

一旦你分析的应用程序中有任何基于WebSocket的通信,就去检测是否有跨站WebSocket劫持攻击。另外,如果已经在应用中发现有对 Origin 头进行校验,尝试绕过:当服务端期望Origin头为 https://www.some-trading-application.com 时,利用 https://www.some-trading-application.com.some-evil-attacker-application.com 来测试对Origin的校验是否完整。

安全顾问:

让您的客户意识到始终检查 Origin 头是必要的。告诉他们使用随机令牌保护所有WebSocket握手过程(像防止CSRF攻击一样),或者让他们将认证和授权部分嵌入WebSocket协议中(避免访问Web Session)。

开发人员:

确保你了解该攻击场景,并且知道如何在你的应用程序中部署安全措施(至少当您需要从使用WebSockets的应用程序部分访问Web Session时,或者您尝试通过该通道传输非公开数据时)。最好避免从服务器端WebSocket部分访问Web Session,并在WebSocket协议中使用令牌或类似技术处理认证和/或授权。