深入理解 Websocket 協議,常見攻擊手法及防護策略

Websocket 是主流的即時通訊協定,在這裡將針對過去的一些經驗,介紹關於 Websocket 的一些內部網路技術原理,以及如何防禦攻擊的做法進行說明。

Websocket 協議通訊流程 簡介

Websocket 是一個基於 TCP 的傳輸協議,主要都應用在瀏覽器與 server 雙向溝通,具有靈活及高效能。

ws 表示基於TCP的 Websocket,預設使用 80埠。 wss 則表示執行在 TLS 之上的 Websocket,預設使用 443埠。

HTTP協議溝通過程都需要攜帶 Head,在傳統網站通訊都是透過輪巡來向 server 發出請求,容易造成 server端的負擔。

Websocket 在首次建立連結是透過 HTTP GET 發出請求,與 server 取得溝通後,接續就會使用websocket協議來進行溝通。 未攜帶數據情況下,client 僅需要少量的掩碼(4字節),server 端則只需要 2-10 Byte。相較於 polling 與 log-polling ,websocket 可以在更短的時間傳送更多資料。

客戶端的格式如下:

GET / HTTP/1.1
Host: localhost:8080
Origin: [url=http://127.0.0.1:3000]http://127.0.0.1:3000[/url]
Connection: Upgrade 
Upgrade: WebSocket 
Sec-WebSocket-Version: 13  
Sec-WebSocket-Key: xxxxx

關於客戶端首次發起的 HTTP 握手請求,僅支援 GET 方法, 由客戶端隨機產生 Sec-WebSocket-Key,server 端會用此key與 258EAFA5-E914-47DA-95CA-C5AB0DC85B11

CVT2HUGO: 產生 sha1 ,以及進行 base64 編碼,再透過 Sec-WebSocket-Accept 回覆給客戶端,
toBase64( sha1( Sec-WebSocket-Key + 258EAFA5-E914-47DA-95CA-C5AB0DC85B11 )  )

Connection: Upgrade 表示要升級協議 Upgrade: WebSocket 表示要升級到 websocket 協議 Sec-WebSocket-Version: 13 表示要升級版本,目前在這之前的版本都已棄用。

Server 端確認後,會回覆 101 格式如下:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat

Websocket 另外可以自行增加一些 extension,其中包括壓縮演算extension,例如 NodeJs pako 可直接推送 gzip 數據,client 端可透過 pako.inflate 解壓;另一種方式是預壓縮編譯,例如 C# pre-message compression,websocket-eventmachine-server,在傳輸前編譯為 binary 數據。

Websocket 通訊傳輸層次

在 OSI 模型中,Http 與 Websocket (FTP, SMTP, POP3..)都屬於第七層 應用層,基礎是建立於 TCP 協議之上。

此外,Websocket 在前面提過,首次握手是透過HTTP來達成。 後續才是用 Websocket 雙向溝通。

Socket 與 Websocket 之間的關係

Socket 是在應用層和傳輸層之間的一個抽象層,原則上,Socket並非協議方式存在,主要是用於搭起 TCP/IP 與UDP/IP 或 ICMP 的連接服務,當兩個服務進行通訊時,首先就須要透過 socket 連線,例如, TCP 或 UDP 就是透過 socket 先建立起連線,在這連線過程會依照協議來做溝通。 所以,Socket 屬於傳輸控制層的抽象層,Websocket 則屬於應用層的協議。

Websocket 數據傳送單位

Client 與 server 數據以 frame (幀) 為單位,通常會由frame來堆疊出 message,基本上在接收端與發送端的流程為:

  • 發送端: 將訊息切割為多個 frame 發送給 server
  • 接收端: 陸續將 frame 接收並且組成完整訊息

好了,提到這裡,可能會再想怎麼知道 websocket 一則訊息正確傳送完畢?

Websocket Frame 幀格式

一個 frame 格式占用 1 bit,會包含幾種組成,FIN (0表示訊息還未傳完,1表示這是該訊息最後一個 frame)

RSV1, RSV2, RSV3 通常當作保留字段,各 1bit,在有自定義 extension 使用這些保留字段時,如果傳送的是非 0 ,就會造成連線失敗。

Opcode 佔用 4bits,數據類型為:

  • %x1 表示為文本
  • %x2 表示為二進制
  • %x8 表示關閉連線
  • %x9 表示為 ping frame
  • %xA 表示為 pong frame

Application data: 表示為傳輸的資料。 Mask: 1 bit 表示為掩碼。 Payload length: 7 bits, 7+16 bits, or 7+64 bits 表示為數據長度。

格式如下:

  Frame format:  
​​
      0                   1                   2                   3
      0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
     +-+-+-+-+-------+-+-------------+-------------------------------+
     |F|R|R|R| opcode|M| Payload len |    Extended payload length    |
     |I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
     |N|V|V|V|       |S|             |   (if payload len==126/127)   |
     | |1|2|3|       |K|             |                               |
     +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
     |     Extended payload length continued, if payload len == 127  |
     + - - - - - - - - - - - - - - - +-------------------------------+
     |                               |Masking-key, if MASK set to 1  |
     +-------------------------------+-------------------------------+
     | Masking-key (continued)       |          Payload Data         |
     +-------------------------------- - - - - - - - - - - - - - - - +
     :                     Payload Data continued ...                :
     + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
     |                     Payload Data continued ...                |
     +---------------------------------------------------------------+

再傳送消息時,在 Server 端主要會偵測到 FIN=1 存在後,就會將當前訊息進行處理,並且檢查訊息的類型,例如 %x2 就表示為二進位訊息。

心跳機制保持 Websocket 連線

在 Websocket 建立後,服務其實相當脆弱,只要遇到連線問題,客戶端就會發送關閉連線的 Fin,很容易就會造成斷開,造成服務中斷。有時,也可能是服務端有發生異常,但客戶端仍持續連線,或者是客戶端異常,但server端仍以為客戶端存在,造成zonbit connection。因此,會透過心跳機制來維持雙向通訊,能持續提供服務。 基本機制是由兩方透過 ping, pong 的接收與回應,持續確認連線正常。比較推薦的通常是在客戶端定期發送心跳,讓 server 進行監聽,當客戶端經過一段時間沒有發送訊息,server端就會自動斷開。關於心跳機制部分,可參考 - 即時通訊架構-心跳檢測原理說明

常見攻擊與安全防護

在前面我們提到 Sec-WebSocket-Key/Sec-WebSocket-Accept 可以作為一個基本的認證,過濾掉非 websocket 連線。只有當客戶端發送 Sec-WebSocket-Key 時,server端會進行確認目前協議為 Websocket,通常還會有其他機制共同防治惡意連線。 在 Ajax 或 curl 都可以指定 Header 偽裝成 Sec-WebSocket-Key,這樣的連線都會被阻擋。 另外,搭建 proxy 時,可以阻擋同時收到多個 ws 升級請求。

另外,在協議溝通過程會攜帶的掩碼,也屬於公開的加密方式,請求過程會消耗些微的運算資源,在設計掩碼主要是為了防止舊版本的代理緩存汙染攻擊(proxy cache poisoning attacks)。

基本上,從 Sec-WebSocket-Key/Sec-WebSocket-Accept 協議的內容,公開的加密方式說明在傳輸過程並非用於確保數據的安全隱私,而是要用這樣的加密方式來確保一些非合法的連結,原則上可以帶來基本的保障。

由於這些交握過程並沒有辦法保證客戶端的身分,惡意攻擊者可以向 Websocket server 進行溝通,將攻擊資源緩存在 server 中,此時緩存的資源是帶有攻擊的內容,但url是 Websocket server的地址,此時 client 向 websocket server 發出請求時,就有機會將攻擊的資源誤認為 client 資源,將內容返回給 client。

在 2013 年由德國的白帽駭客公布了一個 Cross Site WebSocket Hijacking(CSWSH) 攻擊方式,透過 tvb_unmasked 函數所存在多整數符號錯誤的情況,可以利用這個錯誤簡單的造成拒絕服務回應,攻擊者可以透過發送0長度的frame造成拒絕服務,來達成 DDoS 攻擊。

此外,由於 Websocket 沒有Cross-Origin Resource Sharing(CORS)的限制,因此可透過串改 Websocket 升級協議來進行攻擊,例如:

GET ws://server.url.example/?encoding=text HTTP/1.1
Host: echo.websocket.org
Connection: Upgrade
Pragma: no-cache
Cache-Control: no-cache
Upgrade: websocket
Origin: http://www.malicious
website.com
Sec-WebSocket-Version: 13
Accept-Encoding: gzip, deflate, sdch
Accept-Language: en-US,en;q=0.8,zh-CN;q=0.6
Cookie: _gat=1; _ga=GA1.2.290430972.14547651; JSESSIONID=adsfasfasdfasdfadfasdfasfdasdfasdf
Sec-WebSocket-Key: asdfasfasdfasdf1KKQ==
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits

基本防護通常是透過 server 檢查 origin,或者發送令牌方式來阻擋駭客。

如果要自我測試是否安全,可以透過 OWASP ZAP v2.4.3 攔截 websocket 並且修改 Header在轉發,若返回連線失敗就表示有成功防護劫持攻擊。

參考: 即時通訊架構-心跳檢測原理說明 Writing WebSocket servers WebSocket handshake library in C++ Writing WebSocket client applications Tutorial: Websocket server in C# Tutorial: Websocket server in VB.NET Tutorial: Websocket server in Java