文章前言
在一次做项目的时候本来是想去点击Burpsuite的Proxy界面的HTTP History选项卡来查看HTTP历史请求记录信息并做测试的,但是在查看的时候却下意识的点击到了HTTP Proxy右侧的"WebSockets History"选项卡中,从界面的交互历史中发现网站有使用WebSocket进行通信,虽然之前有对Websocket有一些简单的了解(比如:跨越问题),但是未对此进行深入研究,这让我产生了需要深入研究一下的想法
历史背景
在过去的很长一段时间,创建客户端和服务器之间双向通信的WEB应用程序(例如:即时消息和游戏应用程序)大多都是通过HTTP协议来轮询服务器以获取更新,同时将上游通知作为不同的HTTP调用进行发送,由此也导致了以下问题:
客户端脚本被迫维护从传出连接到传入连接的映射以跟踪消息回复
Wire Protocol(线协议)的开销很高,每个客户端到服务器的消息都有一个HTTP报头
服务器被迫为每个客户端使用许多不同的底层TCP连接:一个用于向客户端发送信息,另一个从客户端用于接受消息
WebSockets协议的面世很好的解决了以上问题,它提出了一个简单的解决方案——使用单个TCP连接来实现双向通信,并通过结合WebSocket API[WSAPI]为从网页到远程服务器的双向通信提供了HTTP轮询的替代方案,该项技术目前被广泛的用于各种WEB应用程序:游戏、股票行情器、具有同时编辑功能的多用户应用程序、实时公开服务器端服务的用户界面等
基本介绍
WebSocket协议旨在取代使用HTTP作为传输层的现有双向通信技术,以从现有基础设施(代理、过滤、身份验证)中获益,因为HTTP最初并不打算用于双向通信,所以这项技术也被视为提高效率和可靠性之间的权衡
WebSocket协议试图在现有HTTP基础设施的上下文中解决现有双向HTTP技术的目标,因此它被设计为在HTTP端口80和443上工作并支持HTTP代理和中介,即使这意味着某些特定于当前环境的复杂性,然而该设计并没有将WebSocket限制为HTTP,未来的实现可以在专用端口上使用更简单的握手,而无需重新设计整个协议,该协议允许在受控环境中运行不受信任代码的客户端与选择和该代码进行通信的远程主机之间进行双向通信,它使用的安全模型为WEB浏览器常用的源模型(origin model)
备注:全双工是在微处理器与外围设备之间采用发送线和接受线各自独立的方法,可以使数据在两个方向上同时进行传送操作,指在发送数据的同时也能够接收数据且两者同步进行,这好像我们平时打电话一样,说话的同时也能够听到对方的声音
协议概览
WebSocket协议有两个部分:握手和数据传输
开启握手
握手请求
开放握手(Opening Handshake)旨在与基于HTTP的服务器端软件和中介兼容,这样与该服务器通信的HTTP客户端和与该服务器进行通信的WebSocket客户端都可以使用单个端口,为此WebSocket客户端的握手是一个HTTP升级请求,简易实例如下:
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
GET方法的"Request-URI"用于标识WebSocket连接的端点,既允许从一个IP地址服务多个域,也允许单个服务器服务多个WebSocket端点,客户端在握手的"Host"头字段中包含主机名以便客户端和服务器都可以验证他们是否同意使用哪个主机,其他头字段用于选择WebSocket协议中的选项,在当前的版本中可用的典型选项主要包括子协议选择器(Sec-WebSocket-Protocol)、客户端支持的扩展列表(Sec-WebSocket-extensions)、Origin头字段,可以使用Sec-WebSocket-Protocol请求头字段来指示客户端可以接受哪些子协议(在WebSocket协议上分层的应用程序级协议),而后服务器选择一个或任何一个可接受的协议并在其握手中回显该值,以指示其已选择该协议
Sec-WebSocket-Protocol: chat
Origin字段用于防止在Web浏览器中使用WebSocket API的脚本未经授权跨源使用WebSocketServer,Origin将通知服务器生成WebSocket连接请求的脚本源,如果服务器不希望接受来自此源的连接则可以选择通过发送适当的HTTP错误代码来拒绝连接,此标头字段由浏览器客户端发送,对于非浏览器客户端,如果在这些客户端的上下文中有意义则可以发送此头字段
最后服务器必须向客户机证明它收到了客户机的WebSocket握手以便服务器不接受非WebSocket连接的连接,这可以防止攻击者通过使用XMLHttpRequest[XMLHttpRequest]或表单提交发送精心制作的数据包来欺骗WebSocket服务器,而为了证明握手已被接收,服务器必须获取两条信息并将它们组合起来形成响应,第一条信息来自客户端握手中的Sec-WebSocket-Key头字段:
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
对于此标头字段,服务器必须获取值(如标头字段中所示,例如:base64编码的版本减去任何前导和尾随空格)并将其与字符串形式的全局唯一标识符(GUID)“258EAFA5-E914-47DA-95CA-C5AB0DC85B11"连接起来,这对不理解WebSocket协议的网络端点而言不太可能会使用,然后在服务器的握手中返回这种连接的SHA-1散列(160位),base64编码,具体地说,如果与上面的示例一样,其中Sec-WebSocket-Key头字段的值为"dGhlIHNhbXBsZSBub25jZQ==”,则服务器将串接字符串"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"以形成字符串"dGhlIHNhbxbsZSBub25jZQ==258EAPA5-E914-73A-95CA-C5ABODC85B111",然后服务器将获取此的SHA-1哈希并给出值0xb3 0x7a 0x4f 0x2c 0xc0 0x62 0x4f 0x16 0x90 0xf6 0x46 0x06 0xcf 0x38 0x59 0x45 0xb2 0xbe 0xc4 0xea,然后对该值进行base64编码,给出值"s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",然后该值将在Sec-WebSocket-Accept标头字段中回显
握手响应
来自服务器的握手其第一行是HTTP状态行,状态代码为101,如果服务器返回除101之外的任何状态代码则都表明WebSocket握手尚未完成:
HTTP/1.1 101 Switching Protocols
响应中的Connection和Upgrade头字段完成HTTP升级,Sec-WebSocket-Accept标头字段指示服务器是否愿意接受连接,如果存在则此标头字段必须包含在Sec-WebSocket Key中发送的客户端随机数的哈希值以及预定义的GUID,任何其他值都不得解释为服务器接受连接
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
如果Sec-WebSocket-Accept值与预期值不匹配,或者缺少标头字段以及HTTP状态代码不是101,则不会建立连接,也不会发送WebSocket帧,在此版本的协议中主选项字段是Sec-WebSocket-protocol,它指示服务器选择的子协议,WebSocket客户端验证服务器是否包含WebSocket客户机握手中指定的值之一,使用多个子协议的服务器必须确保它基于客户端的握手选择一个子协议,并在握手中指定它:
Sec-WebSocket-Protocol: chat
完整示例
握手请求与握手响应的简易示例如下:
之后此时网络连接保持打开状态,并且可以用于向任一方向发送WebSocket消息
请求头的Connection:Upgrade标头表示进行协议切换
请求头的Upgrade:websocket标头标识切换协议至Websocket
请求头的Sec-WebSocket-Version指定WebSocket协议版本的客户端希望使用,通常是13
请求头的Sec-WebSocket-Key包含Base64编码的随机值,这应该在每个握手请求是随机产生的
响应头的Sec-WebSocket-Accept包含在提交的值的散列Sec-WebSocket-Key请求头,具有在协议规范中定义的特定的字符串串联,从而防止由于服务器配置错误或代理缓存错误而引起的误导响应
数据传输
数据帧
WebSocket协议中数据是使用帧序列传输的,在WebSocket开启握手完成之后以及端点发送结束帧之前,客户端或服务器可以随时发送数据帧,其中帧按照基本成帧协议规范来指定,该协议定义了一种帧类型,包括操作码、有效载荷长度以及"扩展数据"和"应用数据"的指定位置,它们一起定义了"有效载荷数据",某些位和操作码是为将来扩展协议而保留的,下图给出了具体的阐述:<