一.概述
WebSocket 是HTML5一种新的网络传输协议,位于 OSI 模型的应用层,可在单个TCP连接上进行全双工通信。
1.1HTTP 和 WebScoket
段落的首行要缩进,表格居中放置,如表1。 字体通一用一种雅黑或宋体,文字字号统一为5号Web 客户端和服务端之间的大多数通信使用HTTP(HTTPS也是在传输层和应用层中间加了一层SSL/TLS协议,在这里没有太大影响,故不作区分)。HTTP协议是请求-响应式的,在 HTTP1.1 开始,TCP连接可被复用。即使网络连接保持打开状态,这也将用于请求和响应的单独事务。
WebSocket和HTTP都是应用层协议,并且都基于TCP协议。WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就可以建立持久性的连接,并进行双向数据传输。此外,WebSocket基于二进制帧进行传输。
RFC 6455中规定:
WebSocket通过HTTP端口80和443进行工作,并支持HTTP代理和中介,从而使其与HTTP协议兼容。
TIPS:ws协议默认80端口,wss默认443端口
为了实现兼容性,WebSocket握手使用HTTP Upgrade 头从HTTP协议更改为WebSocket协议。与HTTP协议类似, `wss` 协议建立在一个加密的TLS连接的WebSocket,而 `ws` 协议使用未加密的连接。
1.2Demo
- 安装依赖
npm install ws
- server.js
// 导入WebSocket模块:
const WebSocket = require('ws');
// 引用Server类:
const WebSocketServer = WebSocket.Server;
// 实例化:
const ws = new WebSocketServer({
port: 3000
});
// connection事件用于处理接入的WebSocket
ws.on('connection', function (ws) {
console.log("Connected!!!");
});
node tt.js 启动服务端
- client
直接在浏览器的console界面输入,在服务端的终端会打印相关的日志信息
var ws = new WebSocket("ws://127.0.0.1:3000");
1.3握手过程
服务端:
<html>
<body>
<script>var ws = new WebSocket("ws://127.0.0.1:3000");</script>
Loaded!!
</body>
</html>
客户端发起握手请求
GET / HTTP/1.1
Host: 127.0.0.1:3000
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:99.0) Gecko/20100101 Firefox/99.0
Accept: */*
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Sec-WebSocket-Version: 13
Origin: http://127.0.0.1
Sec-WebSocket-Key: 8PbhQOV5ykV3eYf2biw52A==
Connection: keep-alive, Upgrade
Sec-Fetch-Dest: websocket
Sec-Fetch-Mode: websocket
Sec-Fetch-Site: same-site
Pragma: no-cache
Cache-Control: no-cache
Upgrade: websocket
如果服务端接受这个连接,它会返回一个WebSocket回复
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: dBaGq6Oh1nXvQd+sJRKFK9GnsGI=
之后,这个WebSocket的网络连接会保持开启状态,任意一方都可以直接发送WebSocket信息:
<html>
<body>
<script>
var ws = new WebSocket("ws://127.0.0.1:3000");
// 连接打开事件
ws.onopen = function() {
// 发送数据给服务端
ws.send(JSON.stringify({"message":"hello"}));
};
// 监听服务端信息
ws.onmessage = function(e){
alert(e.data);
};
</script>
Loaded!!
</body>
</html>
通过 message 监听消息
// 导入WebSocket模块:
const WebSocket = require('ws');
// 引用Server类:
const WebSocketServer = WebSocket.Server;
// 实例化:
const ws = new WebSocketServer({
port: 3000
});
// connection事件用于处理接入的WebSocket
ws.on('connection', function (ws) {
// 处理客户端的消息
ws.on('message', function (data) {
console.log(JSON.parse(data));
});
// 向客户端发送消息
ws.send("OK");
});
原则上,WebSocket消息可以包含任何内容或数据格式。在实际的应用程序中,通常WebSocket消息都是通过JSON进行发送结构化数据。
关于WebSocket握手时的一些特性:
- Connection和Upgrade头部用来标识这是一个WebSocket握手消息。
- Sec-WebSocket-Version请求头明确了一个客户端希望使用的WebSocket协议版本。版本13最常用。
- Sec-WebSocket-Key请求头包含了一个base64编码的随机值,在每个WebSocket握手请求中,它一定是随机生成的。
- Sec-WebSocket-Accept响应头的值是客户端发送的握手请求中Sec-WebSocket-key的哈希值,并与协议规范中定义的特定字符串连接。这样做的目的是匹配每一对握手请求,防止由于错误的配置或者缓存代理导致的连接错误。
1.4握手过程
基于WebSocket全双工、延迟的特性,应用场景比较广泛。
- 聊天机器人
- 弹幕
- 协同编辑
- 股票报价实施更新
- 位置更新
- 直播实况段落的首行要
二. 安全分析
根据上面的分析可知,WebSocket仅仅是Web程序中的一种通信协议,并不会解决Web应用中存在的安全问题。因此,原则上任何Web漏洞都有可能出现在使用WebSocket的应用中。
2.1Web漏洞
WebSocket中,用户可控的请求数据,都会涉及输入校验问题,用来规范常见的Web漏洞,如XSS、SQL Inject、RCE等。
2.1.1XSS
靶场环境:https://acb51fa71e7c52cbc04a0bac00b7009c.web-security-academy.net/
题目环境是一个商城,其中有个在线聊天功能,是基于WebSocket
to server
{"message":"<img src=1 onerror='alert(1)'>"}
to client
{"user":"You","content":"<img src=1 onerror='alert(1)'>"}
页面渲染后触发js
TIPS:新版的Burp 支持WebSocket包重放
2.2 WebSocket 安全
2.2.1认证
WebSocket 协议并没有在握手阶段对客户端的身份进行认证,但服务端可以采用HTTP服务器的客户端身份认证机制,如cookie认证,HTTP 基础认证,TLS 身份认证等。因此,认证实现方面的安全问题与基于HTTP的Web认证并无区别。
- CVE-2015-0201
Spring框架的Java SockJS客户端生成可预测的会话ID,攻击者可利用该漏洞向其他会话发送消息
- CVE-2015-1482
Ansible Tower未对用户身份进行认证,远程攻击者通过websocket连接获取敏感信息
2.2.2授权
WebSocket 协议依然没有指定任何授权方式,因此关于权限的相关策略依然得依赖开发者在服务端实现,依然面临着垂直权限提升和水平权限提升的风险。
2.2.3跨域请求/CSWSH
WebSocket使用基于源的安全模型,在发起WebSocket握手请求时,浏览器会在请求中添加一个名为Origin的HTTP头,Oringin字段表示发起请求的源,以此来防止未经授权的跨站点访问请求。
WebSocket 的客户端不仅仅局限于浏览器,因此 WebSocket 规范没有强制规定握手阶段的 Origin 头是必需的,并且WebSocket不受浏览器同源策略、CORS机制的限制。如果服务端没有针对Origin头部进行验证可能会导致跨站点WebSocket劫持攻击。
CSWSH全称Cross-site WebSocket Hijacking,跨站点WebSocket劫持漏洞。
该漏洞最早在 2013 年被Christian Schneider 发现并公开,Christian 将之命名为跨站点 WebSocket 劫持 (Cross Site WebSocket Hijacking)(CSWSH)。跨站点 WebSocket 劫持危害大,但容易被开发人员忽视。相关案例可以参考: IPython Notebook(CVE-2014-3429), OpenStack Compute(CVE-2015-0259), Zeppelin WebSocket服务器等跨站WebSocket劫持。
攻击过程:
需要注意的是,Origin 和 Sec-WebSocket-Key 都是由浏览器自动生成的,浏览器再次发起请求访问目标服务器会自动带上Cookie 等身份认证参数。
漏洞利用的关键点是,服务端没有对Origin头部进行校验,才能成功握手并切换到 WebSocket 协议,恶意网页就可以成功绕过身份认证连接到 WebSocket 服务器,进而窃取到服务器端发来的信息,或者发送伪造信息到服务器端篡改服务器端数据。
检测
由于跨站点WebSocket劫持攻击本质上是WebSocket握手上的CSRF漏洞,因此,首先需要确认在WebSocket握手过程中,是否进行了CSRF防护,确定是否仅仅依赖HTTP cookie进行权限鉴定。
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Cookie: session=KOsEJNuflw4Rxxxxxxxxxxxxxx
Sec-WebSocket-Accept: dBaGq6Oh1nXvQd+sJRKFK9GnsGI=
TIPS:Sec-WebSocket-Key头包含一个随机值,以防止缓存代理的错误,而不是用于身份验证或会话处理的目的。
在确定握手请求存在CSRF风险的情况下,寻找使用WebSocket进行敏感数据检索的功能点。
过程:
- 重放确认是否存在CSRF
- 修改Origin头部,判断是否存在校验
对比CSRF
与传统跨站请求伪造(CSRF)攻击相比,CSRF 主要是通过恶意网页悄悄发起数据修改请求,而跨站 WebSocket 伪造攻击不仅可以修改服务器数据,还可以控制整个双向通信通道。也正是因为这个原因,Christian 将这个漏洞命名为劫持(Hijacking),而不是请求伪造(Request Forgery)。
防御
- 检查客户端请求中的Origin信息是否跨域
- 防止Origin头部伪造,还可以借鉴CSRF的防御机制,如增加Token验证
2.2.4 拒绝服务攻击/DOS
WebSocket设计为面向连接的协议,如果不限制连接数,可能会导致DoS风险。
F5 BIG-IP远程拒绝服务漏洞(CVE-2016-9253
- 客户端拒绝服务
WebSocket 连接限制不同于HTTP连接限制,WebSocket有一个更高的连接限制,不同的浏览器的最大连接数也存在差异。如:火狐浏览器默认最大连接数为200。
通过发送恶意内容,占用所有Websocket,导致浏览器资源耗尽,引起拒绝服务。
- 服务端拒绝服务
- 发起大量连接
WebSocket建立的是持久连接,只有客户端或服务端其中一方发起关闭连接的请求,连接才会关闭。攻击者可以通过发起请求并建立大量的连接,导致服务器资源耗尽,引发拒绝服务攻击。
防御:服务端可以进行单一IP的最大连接数进行防御
- 大数据帧占用
攻击者可以发送一个庞大的数据帧,占用服务端的内存,引发拒绝服务攻击,
防御:限制帧的大小
demo
- js
var WebSocketServer = require('ws').Server,
wss = new WebSocketServer({ port: 3000 });
wss.on('connection', function (ws) {
console.log('[*]Client connected!');
ws.on('message', function (message) {
console.log(message.toString('utf8'));
});
});
- 正常客户端
<html>
<body>
<script>
var ws = new WebSocket("ws://127.0.0.1:3000");
// 连接打开事件
ws.onopen = function() {
// 发送数据给服务端
ws.send("Hello");
};
// 监听服务端信息
ws.onmessage = function(e){
alert(e.data);
};
</script>
Loaded!!
</body>
</html>
- exp
python -m pip install ws4py from ws4py.client.threadedclient import WebSocketClient
class WS_Client(WebSocketClient):
# 需要重写以下三个方法
def opened(self):
reqData = "Hello"
self.send(reqData)
def closed(self, code, reason=None):
print("[-] Closed down:", code, reason)
def received_message(self, resp):
resp = json.loads(str(resp))
print(resp)
if __name__ == '__main__':
while True:
ws = WS_Client("ws://127.0.0.1:3000")
ws.connect()
2.2.5 中间人攻击
WebSocket使用HTTP或HTTPS协议进行握手请求,在使用HTTP协议的情况下,若存在中间人可以嗅探HTTP流量,那么中间人可以获取并篡改WebSocket握手请求,通过伪造客户端信息与服务器建立WebSocket连接。短文章可以不引用目录
三.总结
WebSocket 是HTML5中一个及时的全双工通讯协议,在性能上有着明显的优势,但他并不能解决安全问题,同时也需要开发者考虑其安全威胁面。
四.参考
https://christian-schneider.net/CrossSiteWebSocketHijacking.html
https://www.liaoxuefeng.com/wiki/1022910821149312/1103327377678688
https://segmentfault.com/q/1010000020661067
https://www.mi1k7ea.com/2019/10/04/CSWSH%E6%BC%8F%E6%B4%9E%E6%80%BB%E7%BB%93/#0x02-CSWSH%E6%BC%8F%E6%B4%9E
https://security.tencent.com/index.php/blog/msg/119
https://www.mi1k7ea.com/2021/01/30/%E6%B5%85%E6%9E%90WebSocket%E5%AE%89%E5%85%A8/
https://www.freebuf.com/articles/web/252298.html
https://wiki.wgpsec.org/knowledge/web/websocket-sec.html
https://www.mi1k7ea.com/2021/01/30/%E6%B5%85%E6%9E%90WebSocket%E5%AE%89%E5%85%A8/