- 使用Chrome浏览器来捕获websocket报文:
- 过滤器
- 按类型: 选择WS按钮(和XHR平级的按钮)
- 属性过滤: is:running
- 表格列
- Data: 消息负载。如果消息为纯文本,则在此处显示。 对于二进制操作码,此列将显示操作码的名称和代码。 支持以下操作码:Continuation Frame, Binary Frame, Connection Close Frame, Ping Frame和Pong Frame。
- Length: 消息负载的长度(以字节为单位)
- Time: 收到或发送消息的时间
- 消息颜色
- 发送至服务器的文本消息为浅绿色
- 接收到的文本消息为白色
- WebSocket操作码为浅黄色
- 错误为浅红色
- 流程:
客户端 服务器端
| |
-----------|---upgrade------ |-------------
| \------>|
握手 | / | HTTP
| /---切换协议--------/ |
-----------|< |-------------
| __/|
|\__\__ __/ |
数据传输 | \__\__ ____/ |
| \__\__/ |
| \_/\data | WebSocket
| / \__\__ |
V<--------/ \_ \---->V
V \----->V
___________V V____________
- 扩展webSocket相对http1.1更加复杂
客户端 客户端
| | 请求1 |
请求1 | | 请求2 请求2 |
V V V
负载均衡 负载均衡
| | 请求1 | | 请求2
|--| |--| |--| |--|
V V V V
HttpServer1 HttpServer2 WebSocket WebSocket
接入Server1 接入Server2
V V
消息分发系统
V V
WebSocket WebSocket
实现Server2 实现Server1
- 设计哲学: 在Web约束下暴露TCP给上层
- 元数据去哪了
- HTTP协议: 头部会存放元数据
- WebSocket: 由WebSocket之上的应用层存放元数据
- 基于帧:不是基于流(HTTP、TCP)
- 每一帧要么承载字符数据,要么承载二进制数据
- 基于浏览器的同源策略模型(非浏览器无效)
- 可以使用Access-Control-Allow-Origin等头部
- 基于URI、子协议支持同主机同端口上的多个服务
- 什么是子协议 请求头:Sec-WebSocket-Protocol:x-kaazing-handshake 响应头:Sec-WebSocket-Protocol:x-kaazing-handshake
1. WebSocket报文格式
|0 |1 |2 |3 |
+--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|
|F |R |R |R |opcode(4) |M |payload len | Extended payload length |
|I |S |S |S | |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 ... |
+--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|
前两个字节是必然存在的帧首部,Chrome浏览器抓到的包不会显示帧头部(也不会显示控制帧),但是wireshark可以
FIN: 值为1表示这条消息已经结束了,1条消息由1个或者多个帧组成,这些数据帧属于同一类型
如果消息只有一个帧,则FIN的值必须为1,像PING/PONG类型的消息(因为消息太小,不被分片)。
RSV1/RSV2/RSV3: 默认为0, 仅当使用extension扩展时,由扩展决定其值
opcode: 表示帧类型
* 持续帧
* 0: 继续前一帧(这个帧是什么类型,我不知道,但是我是和前一个帧的类型是一致的)
* 非控制帧(用来传输数据)
* 1: 文本帧(UTF8)
* 2: 二进制帧
* 3-7: 为非控制帧保留
* 控制帧
* 8: 关闭帧
* 9: 心跳帧ping
* A: 心跳帧pong
* B-F: 为控制帧保留
3种消息内容长度:
* <= 125字节
* 仅使用Payload len
* 126至2^16-1
* Payload len值为126
* Extended payload length 16位表示长度
* 2^16至2^64-1
* Payload len值为127
* Extended payload length共8字节64位表示长度
masking-key掩码:
* 客户端消息: MASK位1(包括控制帧),传递32位无法预测的、随机的Masking-key
* 服务器端消息:MASK为0
2. 如何从HTTP升级到WebSocket
- URI格式
|
|
- 建立握手
访问网站:http://www.websocket.org/echo.html,并使用Chrome可以捕获到如下的报文
请求:
GET /?encoding=text HTTP/1.1
Host:websocket.taohui.tech
Accept-Encoding:gzip,deflate
Sec-WebSocket-Version: 13 #目前所有的WebSocket协议都应该是13
Origin: http://www.websocket.org
Sec-WebSocket-Extensions: permessage-deflate
Sec-WebSocket-Key: c3SkgVxVCDhVCp69PJFf3A== #客户端发给随机数给服务器
Connection: keep-alive,Upgrade
Pragma: no-cache
Cache-Control: no-cache
Upgrade: websocket
响应:
HTTP/1.1 101 Web Socket Protocol Handshake
Server: openresty/1.13.6.2
Date: Mon,10 Dec 2018 08:14:29 GMT
Connection: upgrade
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: content-type
Access-Control-Allow-Headers: authorization
Access-Control-Allow-Headers: x-websocket-extensions
Access-Control-Allow-Headers: x-websocket-version
Access-Control-Allow-Headers: x-websocket-protocol
Access-Control-Allow-Origin: http://www.websocket.org
#服务器根据收到的随机数生成一个新的随机数给客户端
Sec-WebSocket-Accept: yA905xGLp8SbwCV//0epMPw7pEI=
upgrade: websocket
- 如何证明握手被服务器接受? sha1编码网址: https://emn178.github.io/online-tools/sha1.html hexbase64编码网址: http://en.1mu.info/tools/hexbase64.html
- 请求中的Sec-WebSocket-Key随机数
- 例如Sec-WebSocket-Key: A1EEou7Nnq6+BBZoAZqWlg==
- 响应中的Sec-WebSocket-Accept证明值
- GUID (RFC4122): 258EAFA5-E914-47DA-95CA-C5AB0DC85B11
- 值构造规则:BASE64(SHA1(${Sec-WebSocket-Key}${GUID}))
- 最终头部: Sec-WebSocket-Accept: cT8V7OIhhhL8rbFZgoGjU4DReQ8=
- 发送消息
- 确保WebSocket会话出于OPEN状态
- 以帧来承载消息,一条消息可以拆分多个数据帧
- 客户端发送的帧必须基于掩码编码,以解决缓存污染攻击。
- 一旦发送或者接收到关闭帧,连接出于CLOSING状态
- 一旦发送了关闭帧,且接收到关闭帧,连接出于CLOSED状态
- TCP连接关闭后,WebSocket连接才完全被关闭
- 什么是代理服务器缓存污染攻击 可能是正向代理,也可能是反向代理。 代理服务器误认为WebSocket连接是HTTP连接, 故1、3误认为是2个HTTP请求,但复用同一连接。
恶意页面 正常浏览器
↘↖ ↙↗
↘↖7.返回 ↙↗
↘↖ 8.访问资源 ↙↗
1.WebSocket握手 ↘↖ ↙↗ 10.获取伪造信息
3.伪造HTTP GET请求 ↘↖ ↙↗
(Host:被攻击服务域名) 不支持websocket
的代理服务器
↙↗ | 6.缓存响应实际属于被攻击服务
2.WebSocket握手 ↙↗ V 9.发现缓存后,返回伪造响应
4.伪造请求 ↙↗ 缓存
↙↗
↙↗ 5.伪造被攻击服务的响应
恶意服务器 被伪造服务器
- 掩码如何防止缓存污染攻击?
-
16进制数异或操作的网站: http://xor.pw
-
目的:防止恶意页面上的代码, 可以经由浏览器构造出合法的GET请求, 使得代理服务器可以识别出请求并缓存响应。
-
强制浏览器执行以下方法:
- 生成随机的32位masking-key,不能让JS代码猜出(否则可以反向构造)
- 对传输的包体按照masking-key执行可对称解密的XOR异或操作,使代理服务器不识别
- 消息编码算法: 对原始消息,每4个字节,进行一次异或操作。
3. 如何保持会话心跳
在HTTP1.1的长连接中,其实就是一个简单的定时器, 定时器达到以后,还没有请求过来的话,就直接把连接关闭了。
那么WebSocket,它要维护自己的长连接会话呢,将会更复杂些。 定时器到达以后呢,先发一个ping,再回一个pong。 如果心跳仍然存在,那么就仍然保持连接; 如果心跳不存在,再关闭连接。
- 心跳帧可以穿插在数据帧中传输
- ping帧
- opcode=9
- 可以含有数据
- pong帧
- opcode=A
- 必须与ping帧数据相同
- ping帧
4. 如何关闭会话
WebSocket是一个双向传输的协议,所有我们在关闭WebSocket会话的时候,也是需要进行双向关闭的。 而且WebSocket协议是建立在TCP协议之上,所以,通常,我们应该在TCP协议关闭之前,首先关闭WebSocket会话。
- 关闭会话的方式
-
控制帧中的关闭帧:在TCP关闭之前的双向关闭
- 发送关闭帧后,不能再发送任何数据,但是还是可以接收消息的。
- 接收到关闭帧后,不再接收任何到达的数据
-
TCP连接意外中断
- 关闭帧格式
- opcode = 8
- 可以含有数据,但仅用于解释关闭会话的原因
- 前2个字节为无符号整型,表示关闭帧的错误码
- 遵循mask掩码规则
- 关闭帧的错误码
错误码 含义 1000 正常关闭 1001 表示浏览器页面跳转或者服务器将要关机 1002 发现协议错误 1003 接收到不能处理的数据帧(例如某端不能处理二进制消息) 1004 预留 1005 预留(不能用在关闭帧里),期望但没有接收到错误码 1006 预留(不能用在关闭帧里),期望给出非正常关闭的错误码 1007 消息格式不符合opcode(例如文本帧里消息没有用UTF8编码) 1008 接收到的消息不遵守某些策略(比1003、1009更一般的错误) 1009 消息超出能处理的最大长度 1010 客户端明确需要使用扩展,但服务器没有给出扩展的协商消息 1011 服务器遇到未知条件不能完成请求 1015 预留(不能用在关闭帧里),表示TLS握手失败
本文发表于 0001-01-01,最后修改于 0001-01-01。
本站永久域名「 jiavvc.top 」,也可搜索「 后浪笔记一零二四 」找到我。