CSWSH漏洞总结
/0x01 WebSocket
基本概念
一般的,Web应用的交互过程通常是客户端通过浏览器发出一个请求,服务器端接收请求后进行处理并返回结果给客户端,客户端浏览器将信息呈现。这种机制对于信息变化不是特别频繁的应用尚可,但却不适用于高并发与用户实时响应的场景,比如股票的实时信息、地图导航等。
于是,基于HTML5规范的、有Web TCP之称的WebSocket应运而生。
WebSocket是HTML5一种新的协议,它实现了浏览器和服务器全双工通信,更好地节省服务器资源和宽带并达到实时通讯,它建立在TCP之上,同HTTP一样通过TCP来传输数据,但和HTTP协议的不同点在于:
- WebSocket是持久化的协议,而HTTP是非持久连接;
- WebSocket是一种双向通信协议,在建立连接后,WebSocket服务器和浏览器/客户端代理都能主动地向对方发送或接收数据,就像Socket一样,而HTTP是单向通信协议;
- WebSocket需要类似TCP的三次握手连接,但和TCP不同的是,WebSocket是基于HTTP协议进行的握手,连接成功后才能相互通信;
- WebSocket具有功能强大、双向、低延迟等特征,特别是针对实时的、事件驱动的Web应用程序而言,不惜要的网络流量和延迟得以显著减少,通信效率和应用程序表现大大提升;
WebSocket定义了两种URI格式:ws://和wss://,类似于HTTP和HTTPS,ws://使用明文传输,默认端口为80,wss://使用TLS加密传输,默认端口为443。
协议转换与报文特征
WebSocket协议是基于HTTP协议进行的握手连接之后才转换过来的。通信协议从http://或https://切换到ws://或wss://后,表示应用已经切换到了WebSocket协议通信状态了。
websocket.org页面上,点击Connect会发现请求的协议为ws://,并且响应码是101,一旦服务器返回101响应即意味着完成了WebSocket协议的切换:
该站点也提供了客户端的HTML与JS代码来访问WebSocket,JS建立WebSocket连接的接口为new WebSocket(url, [protocol] )
:
1 |
|
访问该HTML文件就会自己发送WebSocket请求,在Frames一栏可看到进行交互的WebSocket协议信息:
Burp能抓取到协议转换的这个101报文,但之后ws://或wss://协议的通信报文就抓不到了:
看到两个关键的头字段Connection和Upgrade,相当于告诉服务端要申请切换到WebSocket协议。其中Connection头字段指定Upgrade、申请切换协议,而Upgrade头字段指定为websocket、具体告诉服务端想切换的协议为WebSocket。
整个WebSocket协议切换报文如下:
其他一些头字段解释如下:
HTTP头 | 是否必须 | 解释 |
---|---|---|
Host | 是 | 服务端主机名 |
Upgrade | 是 | 固定值,”websocket” |
Connection | 是 | 固定值,”Upgrade” |
Sec-WebSocket-Key | 是 | 客户端临时生成的16字节随机值, base64编码 |
Sec-WebSocket-Version | 是 | WebSocket协议版本 |
Origin | 否 | 可选, 发起连接请求的源 |
Sec-WebSocket-Accept | 是(服务端) | 服务端识别连接生成的随机值 |
Sec-WebSocket-Protocol | 否 | 可选,客户端支持的协议 |
Sec-WebSocket-Extensions | 否 | 可选, 扩展字段 |
两个重要的安全头,Sec-WebSocket-Key与Sec-WebSocket-Accept:客户端负责生成一个Base64编码过的随机数字作为Sec-WebSocket-Key,服务器则会将一个GUID和这个客户端的随机数一起生成一个散列Key作为Sec-WebSocket-Accept返回给客户端。这个工作机制可以用来避免缓存代理(caching proxy),也可以用来避免请求重播(request replay)。
出于安全考虑而设计的,以“Sec-”开头的头字段可以避免被浏览器脚本读取到,这样攻击者就不能利用XHR来伪造WebSocket请求来执行跨协议攻击,因为XHR接口不允许设置Sec-开头的Header。
WebSocket属性
属性 | 描述 |
---|---|
Socket.readyState | 只读属性 readyState 表示连接状态,可以是以下值:0 - 表示连接尚未建立。1 - 表示连接已建立,可以进行通信。2 - 表示连接正在进行关闭。3 - 表示连接已经关闭或者连接不能打开。 |
Socket.bufferedAmount | 只读属性 bufferedAmount 已被 send() 放入正在队列中等待传输,但是还没有发出的 UTF-8 文本字节数。 |
WebSocket事件
事件 | 事件处理程序 | 描述 |
---|---|---|
open | Socket.onopen | 连接建立时触发 |
message | Socket.onmessage | 客户端接收服务端数据时触发 |
error | Socket.onerror | 通信发生错误时触发 |
close | Socket.onclose | 连接关闭时触发 |
WebSocket方法
方法 | 描述 |
---|---|
Socket.send() | 使用连接发送数据 |
Socket.close() | 关闭连接 |
跨域
跨域是WebSocket与生俱来的能力。
由前面协议转换可知,WebSocket客户端不仅仅局限于浏览器,因此WebSocket协议没有规范Origin必须相同,未指定ACAO,也没有规定服务器在握手阶段应该如何认证客户端身份,因而同源策略、CORS机制并不适用于WebSocket协议。
0x02 CSWSH漏洞
CSWSH全称Cross-site WebSocket Hijacking,跨站点WebSocket劫持漏洞。
漏洞场景
支持WebSocket协议的Web站点如股票实时查询、地图导航等,并且未对请求的Origin头字段进行校验。
漏洞原理
CSWSH漏洞类似于全能型的CSRF漏洞,可读可写。
漏洞根源是WebSocket天生可跨域,不受同源策略的影响。在此基础上,若目标服务端未对WebSocket协议请求的Origin头字段进行校验,则会导致WebSocket协议请求可被攻击者劫持,从而窃取敏感信息。
下面看个修改过的图,是目标站点存在cookie校验机制的场景:
- 用户首先登录stock.com实时查询股票信息,其中该站点支持WebSocket,需要用户携带cookie访问;
- 接着用户被诱使在当前的浏览器访问beauty.com,其中加载了恶意JS代码到用户的浏览器中执行;
- 恶意JS代码通过WebSocket协议向stock.com站点发起请求,此时请求是用户浏览器发起的、是自动带上cookie信息的;
- stock.com收到恶意JS发送的WebSocket请求,由于未校验ws://请求的Origin头字段,在检测cookie合法后,返回敏感信息到用户浏览器;
- 用户浏览器中的恶意JS收到stock.com响应的WebSocket协议响应信息后,发往攻击者服务器,从而造成跨站点WebSocket劫持攻击;
漏洞挖掘
进行CSWSH漏洞挖掘前需要准备好一款可以重放WebSocket协议报文的代理工具,Burp是做不到的,但是我们可以选择OWASP ZAP来实现。
一般的漏洞挖掘步骤:
- 找到支持WebSocket的站点;
- 使用ZAP等代理工具重放切换WebSocket协议的报文,其中修改Origin头查看服务端是否校验Origin头;
- 若未校验Origin头,则进一步发送WebSocket连接报文查看能否成功利用;
当然,切换协议的请求报文依然是可以使用Burp来完成的,这里修改Origin头之后再重放报文,发现成功响应101报文,证明该站点未校验Origin,可能存在CSWSH漏洞:
由于未找到合适的靶场环境,下面以https://demos.kaazing.com/echo/index.html
为例演示,该站点建立的WebSocket连接是无需带cookie的。
先用ZAP代理抓取到101响应报文:
建立WebSocket连接后,通过该协议发送信息,在ZAP的WebSockets一栏可以查看到发送的内容:
使用ZAP重放切换WebSocket协议请求的报文,修改Origin头:
发送过去后响应101,说明协议切换成功,服务端并未校验Origin头:
下面就编写PoC ws_exp.html,直接拿前面的代码修改下,放置在攻击者的服务器上,原理就是XHR发起建立WebSocket协议请求,建立成功后尝试发送”Mi1k7ea“字符串信息通信,若返回内容为”Mi1k7ea“则证明能够正常进行WebSocket通信,即能够被跨站点劫持进行WebSocket通信:
1 |
|
当然,PoC很简单,具体操作可自行发挥。
诱使已登录目标站点的用户在同一浏览器访问攻击者服务器上的ws_exp.html(当然这里是假设场景),看到能正常建立WebSocket连接并正常通信:
此时Origin头是指向攻击者服务器的,由于后台未校验Origin导致可被跨站点劫持:
0x03 检测与防御
检测方法
修改请求报文中的Origin头字段,重放该WebSocket协议升级请求,若服务器返回101响应则表示连接成功即未对源进行检测,则可能存在CSWSH漏洞。
最好是进一步测试是否可以发送WebSocket消息,若这个WebSocket连接能够发送/接受消息的话,则完全证明CSWSH漏洞的存在。
防御方法
- 使用token机制;
- 使用白名单校验请求报文的Origin头字段;