WebSocket 的誕生
做客戶端開發時,接觸最多的應用層網絡協議,就是 HTTP 協議,而今天介紹的 WebSocket,下層和 HTTP 一樣也是基於 TCP 協議,這是一種輕量級網絡通信協議,也屬於應用層協議。
WebSocket 與 HTTP/2 一樣,其實都是為了解決 HTTP/1.1 的一些缺陷而誕生的,而 WebSocket 針對的就是「請求-應答」這種"半雙工"的模式的通信缺陷。
「請求-應答」是"半雙工"的通信模式,數據的傳輸必須經過一次請求應答,這個完整的通信過程,通信的同一時刻數據只能在一個方向上傳遞。它最大的問題在於,HTTP 是一種被動的通信模式,服務端必須等待客戶端請求才可以返回數據,無法主動向客戶端發送數據。
這也導致在 WebSocket 出現之前,一些對實時性有要求的服務,通常是基於輪詢(Polling)這種簡單的模式來實現。輪詢就是由客戶端定時發起請求,如果服務端有需要傳遞的數據,可以借助這個請求去響應數據。輪詢的缺點也非常明顯,大量空閑的時間,其實是在反復發送無效的請求,這顯然是一種資源的損耗。
雖然在之后的 HTTP/2、HTTP/3 中,針對這種半雙工的缺陷新增了 Stream、Server Push 等特性,但是「請求-應答」依然是 HTTP 協議主要的通信方式。
WebSocket 協議是由 HTML5 規范定義的,原本是為了瀏覽器而設計的,可以避免同源的限制,瀏覽器可以與任意服務端通信,現代瀏覽器基本上都已經支持 WebSocket。
雖然 WebSocket 原本是被定義在 HTML5 中,但它也適用於移動端,盡管移動端也可以直接通過 Socket 與服務端通信,但借助 WebSocket,可以利用 80(HTTP) 或 443(HTTPS)端口通信,有效的避免一些防火牆的攔截。
WebSocket 是真正意義上的全雙工模式,也就是我們俗稱的「長連接」。當完成握手連接后,客戶端和服務端均可以主動的發起請求,回復響應,並且兩邊的傳輸都是相互獨立的。
一、WebSocket 的特點
WebSocket 的數據傳輸,是基於 TCP 協議,但是在傳輸之前,還有一個握手的過程,雙方確認過眼神,才能夠正式的傳輸數據。
WebSocket 的握手過程,符合其 "Web" 的特性,是利用 HTTP 本身的 "協議升級" 來實現。
在建立連接前,客戶端還需要知道服務端的地址,WebSocket 並沒有另辟蹊徑,而是沿用了 HTTP 的 URL 格式,但協議標識符變成了 "ws" 或者 "wss",分別表示明文和加密的 WebSocket 協議,這一點和 HTTP 與 HTTPS 的關系類似。

1 ws://cxmydev.com/some/path 2 ws://cxmydev.com:8080/some/path 3 wss://cxmydev.com:443?uid=xxx
二、由http/https---->WebSocket的切換瀏覽器調試模式可見
--------------------------------------------------------------------------------
Websocket的世界
通信協議格式是WebSocket格式,服務器端采用Tcp Socket方式接收數據,進行解析,協議格式如下:
首先我們需要知道數據在物理層,數據鏈路層是以二進制進行傳遞的,而在應用層是以16進制字節流進行傳輸的。
第一個字節:
FIN:1位,用於描述消息是否結束,如果為1則該消息為消息尾部,如果為零則還有后續數據包;
RSV1,RSV2,RSV3:各1位,用於擴展定義的,如果沒有擴展約定的情況則必須為0
OPCODE:4位,用於表示消息接收類型,如果接收到未知的opcode,接收端必須關閉連接。長連接探活包就是這里標識的。
OPCODE定義的范圍:
0x0表示附加數據幀
0x1表示文本數據幀
0x2表示二進制數據幀
0x3-7暫時無定義,為以后的非控制幀保留
0x8表示連接關閉
0x9表示ping
0xA表示pong
0xB-F暫時無定義,為以后的控制幀保留
第二個字節以及以后字節:
或者
MASK:1位,用於標識PayloadData是否經過掩碼處理,客戶端發出的數據幀需要進行掩碼處理,所以此位是1。數據需要解碼。
Payload length === x,如果
如果 x值在0-125,則是payload的真實長度。
如果 x值是126,則后面2個字節形成的16位無符號整型數的值是payload的真實長度。
如果 x值是127,則后面8個字節形成的64位無符號整型數的值是payload的真實長度。
此外,如果payload length占用了多個字節的話,payload length的二進制表達采用網絡序(big endian,重要的位在前)。
舉例抓到客戶端返回包:
上圖是客戶端發送給服務端的數據包,其中PayloadData的長度為二進制:01111110——>十進制:126;如果值是126,則后面2個字節形成的16位無符號整型數的值是payload的真實長度。也就是圈紅的十六進制:00C1——>十進制:193 byte。所以PayloadData的真實數據長度是193 bytes;
根據我們的分析,客戶端到服務端數據包的websocket幀圖應該為:
舉例抓到服務端返回包:========================================================
可以發現服務器發送給客戶端的數據包中第二個字節中MASK位為0,這說明服務器發送的數據幀未經過掩碼處理,這個我們從客戶端和服務端的數據包截圖中也可以發現,客戶端的數據被加密處理,而服務端的數據則沒有。(如果服務器收到客戶端發送的未經掩碼處理的數據包,則會自動斷開連接;反之,如果客戶端收到了服務端發送的經過掩碼處理的數據包,也會自動斷開連接)。
根據我們的分析,服務端到客戶端數據包的websocket幀圖應該為:
====保活數據包ping/pong + TCP-ACK===============
-------------------------------------------------------------------------------------------------------------------------------------------
詳細分析:
WebSocket的ping包總長度2個字節;
WebSocket的Pong包總長度6個字節,包括4個字節的
而一個ping包WireShark抓包的Length總長度為56個字節。其中WebSocket包長度2字節;TCP頭20字節;IP頭20字節;以太網幀長度14個字節;
TCP頭20字節:
IPV4頭20字節:
以太網幀頭14字節:
目的/源MAC地址共計12字節,類型2字節。共計14字節。
TCP ACK 確認包:
Seq每次+2 說明WebSocket的2字節ping數據包發送成功,Ack每次+6說明成功接收了WebSocket的6字節Pong回復包。
捕獲WebSocket包
關於掩碼:
=======客戶端加密和服務端數據不加密,抓包區別明顯==========
看客戶端有掩碼的數據 服務端無掩碼的數據
掩碼算法了解
掩碼鍵(Masking-key)是由客戶端挑選出來的32位的隨機數。掩碼操作不會影響數據載荷的長度。掩碼、反掩碼操作都采用如下算法:
首先,假設:
-
- original-octet-i:為原始數據的第i字節。
- transformed-octet-i:為轉換后的數據的第i字節。
- j:為i mod 4的結果。
- masking-key-octet-j:為mask key第j字節。
算法描述為: original-octet-i 與 masking-key-octet-j 異或后,得到 transformed-octet-i。
掩碼的誕生---------
WebSocket協議中,數據掩碼的作用是增強協議的安全性。但數據掩碼並不是為了保護數據本身,因為算法本身是公開的,運算也不復雜。除了加密通道本身,似乎沒有太多有效的保護通信安全的辦法。
那么為什么還要引入掩碼計算呢,除了增加計算機器的運算量外似乎並沒有太多的收益(這也是不少同學疑惑的點)。
答案還是兩個字:安全。但並不是為了防止數據泄密,而是為了防止早期版本的協議中存在的代理緩存污染攻擊(proxy cache poisoning attacks)等問題。
1、代理緩存污染攻擊
下面摘自2010年關於安全的一段講話。其中提到了代理服務器在協議實現上的缺陷可能導致的安全問題。猛擊出處。
“We show, empirically, that the current version of the WebSocket consent mechanism is vulnerable to proxy cache poisoning attacks. Even though the WebSocket handshake is based on HTTP, which should be understood by most network intermediaries, the handshake uses the esoteric “Upgrade” mechanism of HTTP [5]. In our experiment, we find that many proxies do not implement the Upgrade mechanism properly, which causes the handshake to succeed even though subsequent traffic over the socket will be misinterpreted by the proxy.”
[TALKING] Huang, L-S., Chen, E., Barth, A., Rescorla, E., and C.
Jackson, "Talking to Yourself for Fun and Profit", 2010,
在正式描述攻擊步驟之前,我們假設有如下參與者:
-
- 攻擊者、攻擊者自己控制的服務器(簡稱“邪惡服務器”)、攻擊者偽造的資源(簡稱“邪惡資源”)
- 受害者、受害者想要訪問的資源(簡稱“正義資源”)
- 受害者實際想要訪問的服務器(簡稱“正義服務器”)
- 中間代理服務器
攻擊步驟一:
-
- 攻擊者瀏覽器 向 邪惡服務器 發起WebSocket連接。根據前文,首先是一個協議升級請求。
- 協議升級請求 實際到達 代理服務器。
- 代理服務器 將協議升級請求轉發到 邪惡服務器。
- 邪惡服務器 同意連接,代理服務器 將響應轉發給 攻擊者。
由於 upgrade 的實現上有缺陷,代理服務器以為之前轉發的是普通的HTTP消息。因此,當協議服務器同意連接,代理服務器以為本次會話已經結束。
攻擊步驟二:
-
- 攻擊者 在之前建立的連接上,通過WebSocket的接口向 邪惡服務器 發送數據,且數據是精心構造的HTTP格式的文本。其中包含了 正義資源 的地址,以及一個偽造的host(指向正義服務器)。(見后面報文)
- 請求到達 代理服務器 。雖然復用了之前的TCP連接,但 代理服務器 以為是新的HTTP請求。
- 代理服務器 向 邪惡服務器 請求 邪惡資源。
- 邪惡服務器 返回 邪惡資源。代理服務器 緩存住 邪惡資源(url是對的,但host是 正義服務器 的地址)。
到這里,受害者可以登場了:
-
- 受害者 通過 代理服務器 訪問 正義服務器 的 正義資源。
- 代理服務器 檢查該資源的url、host,發現本地有一份緩存(偽造的)。
- 代理服務器 將 邪惡資源 返回給 受害者。
- 受害者 卒!
附:前面提到的精心構造的“HTTP請求報文”。
2、當前解決方案
最初的提案是對數據進行加密處理。基於安全、效率的考慮,最終采用了折中的方案:對數據載荷進行掩碼處理。
需要注意的是,這里只是限制了瀏覽器對數據載荷進行掩碼處理,但是壞人完全可以實現自己的WebSocket客戶端、服務端,不按規則來,攻擊可以照常進行。
但是對瀏覽器加上這個限制后,可以大大增加攻擊的難度,以及攻擊的影響范圍。如果沒有這個限制,只需要在網上放個釣魚網站騙人去訪問,一下子就可以在短時間內展開大范圍的攻擊。
總結:
WebSocket 是一個獨立的基於 TCP 的協議,它與 HTTP 之間的唯一關系就是它的握手請求可以作為一個升級請求(Upgrade request)經由 HTTP 服務器解釋。再嚴謹一點:WebSocket是一個網絡通訊協議, 只要理解上面的數據幀格式和握手流程, 都可以完成基於websokect的即時通訊。
-------請關注原著,感謝!-------------------------
博客園 https://www.cnblogs.com/plokmju/p/okhttp_weisocket.html
CSDN https://blog.csdn.net/u014252478/article/details/84380643
CSDN https://blog.csdn.net/xiongping_/article/details/47746953
搜狐 https://www.sohu.com/a/227600866_472869