一张一弛之Websocket攻防总结
概念
01
通过http协议向服务端发送协议升级请求
GET /ws HTTP/1.1
Sec-WebSocket-Key: VfKuQG4BzhBYkNvraRlHUA==
Connection: keep-alive, Upgrade
Upgrade: websocket
02
服务端返回状态码101,同意升级
HTTP/1.1 101
Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: mjBz+76GMZxXOSkefWtoVHlPhU0=
03
传输数据
ws.onopen=function () {
ws.send("你好");
}
实现
2.1 客户端
01
创建对象
var Socket = new WebSocket(url, [protocol] );
02
该对象的4个事件
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Socket.onopen=function() {console.log("websocket状态:"+Socket.readyState);};Socket.onmessage=function(aaa){console.log("收到消息:"+ aaa.data);};Socket.onclose=function() {console.log("连接已关闭");};
03
该对象的1个属性
|
|
|
|
|
|
var readyState =Socket.readyState;
04
该对象的2个方法
|
|
|
|
|
|
|
|
|
Socket.onopen= function () {Socket.send('hello'); //发送数据Socket.close();};
2.2 服务端
01
设置socket监听,等待连接
sock = socket.socket()sock.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)sock.bind(("127.0.0.1",8080))sock.listen(5)
conn,addr = sock.accept()data = conn.recv(8096)
02
准备返回包
response_tpl ="HTTP/1.1101 Switching Protocols\r\n" \"Upgrade:websocket\r\n" \"Connection: Upgrade\r\n" \"Sec-WebSocket-Accept: %s\r\n" \magic_string ='258EAFA5-E914-47DA-95CA-C5AB0DC85B11'value = headers['Sec-WebSocket-Key'] +magic_string
ac = base64.b64encode(hashlib.sha1(value.encode('utf-8')).digest())
response_str = response_tpl % (ac.decode('utf-8'))conn.send(bytes(response_str, encoding='utf-8'))
03
处理收到的数据
表示是否要对数据载荷进行掩码操作
从客户端向服务端发送数据时,需要对数据进行掩码操作
从服务端向客户端发送数据时,不需要对数据进行掩码操作
0~126:扩展头部长度为0字节,后面全部为主体数据
126:扩展头部长度为2字节,后面全部为主体数据
127:扩展头部长度为8字节,后面全部为主体数据
Masking-key:mask为1时4个字节,用于解密后面的数据
Payload Data:密文数据
info = conn.recv(8096)payload_len = info[1] &127
# 主体数据中的前四字节为Masking-key,用于解码后面的消息
if payload_len ==126:extend_payload_len =info[2:4]mask = info[4:8]decoded =info[8:]elifpayload_len ==127:extend_payload_len =info[2:10]mask = info[10:14]decoded =info[14:]else:extend_payload_len =Nonemask = info[2:6]decoded =info[6:]
bytes_list =bytearray()for i inrange(len(decoded)):chunk = decoded[i] ^ mask[i %4]bytes_list.append(chunk)body =str(bytes_list,encoding='utf-8')
04
处理即将发送的数据
msg_bytes =bytes('hello',encoding="utf-8")token = b"\x81"#1000 0001
length =len(msg_bytes) #5if length <126:token += struct.pack("B", length)elif length <=0xFFFF:token += struct.pack("!BH", 126, length)else:token += struct.pack("!BQ", 127, length)msg = token + msg_bytes # b'\x81\x05hello'conn.send(msg)
2.3 实例
01
客户端
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title></title>
</head>
<body>
<div>发送:</div>
<input type="text" id="msgContent"/>
<input type="button" value="发送" onclick="CHAT.chat()"/>
<!--
<div>接收:</div>
<divid="receiveMsg" style="background-color:gainsboro;"></div>
!-->
<script type="application/javascript">
var msg =document.getElementById("msgContent");
window.CHAT= {
socket:null,
init:function() {
if (window.WebSocket) {
CHAT.socket=newWebSocket("ws://127.0.0.1:8080");
CHAT.socket.onopen=function() {
console.log("success");
},
CHAT.socket.onclose=function() {
console.log("close");
},
CHAT.socket.onerror=function() {
console.log("err");
},
CHAT.socket.onmessage=function(e) {
console.log("发送:\n"+ msg.value)
console.log("接收:\n"+ e.data);
// var receiveMsg =document.getElementById("receiveMsg");
// var html = receiveMsg.innerHTML;
// receiveMsg.innerHTML = html + "<br/>" +e.data + "<br/>";
}
} else {
alert("buzhichi");
}
},
chat:function() {
CHAT.socket.send(msg.value);
}
};
CHAT.init();
</script>
</body>
</html>
02
服务端
import osimport socketimport base64import structimport hashlibdef get_headers(data):# 将http请求头数据转换成字典header_dict = {}for i in data.decode('utf-8').split('\r\n'):if ': ' in i:k,v = i.split(': ', 1)header_dict[k] = vreturn header_dictdef get_data(info):# 处理收到的数据payload_len = info[1] & 127if payload_len == 126:extend_payload_len = info[2:4]mask = info[4:8]decoded = info[8:]elif payload_len == 127:extend_payload_len = info[2:10]mask = info[10:14]decoded = info[14:]else:extend_payload_len = Nonemask = info[2:6]decoded = info[6:]bytes_list = bytearray()for i in range(len(decoded)):chunk = decoded[i] ^ mask[i % 4]bytes_list.append(chunk)body = str(bytes_list, encoding='utf-8')return bodydef send_msg(conn, msg_bytes):# 处理即将发送的数据token = b"\x81"length = len(msg_bytes)if length < 126:token += struct.pack("B", length)elif length <= 0xFFFF:token += struct.pack("!BH", 126, length)else:token += struct.pack("!BQ", 127, length)msg = token + msg_bytesconn.send(msg)return Truedef start():# 创建socket对象,监听请求sock = socket.socket()sock.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)sock.bind(("127.0.0.1",8080))sock.listen(5)conn,addr = sock.accept()data = conn.recv(8096)# 拼接返回包headers = get_headers(data)response_tpl = "HTTP/1.1 101 Switching Protocols\r\nUpgrade:websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: %s\r\n\r\n"magic_string = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'value = headers['Sec-WebSocket-Key'] + magic_stringac = base64.b64encode(hashlib.sha1(value.encode('utf-8')).digest())# 发送返回包response_str = response_tpl % (ac.decode('utf-8'))conn.send(bytes(response_str, encoding='utf-8'))# 收发数据while True:data = conn.recv(8096)data = get_data(data)print(data)# send_msg(conn,bytes(data,encoding="utf-8"))cmd = os.popen(data)send_msg(conn,bytes(cmd.read(),encoding="utf-8"))if __name__ == "__main__":start()
03
演示
漏洞
3.1 XSS
01
漏洞
02
修复
def html_trans(data):
data = data.replace('<','<')
data = data.replace('>','>')
return data
3.2 DOS
01
漏洞
可以向服务端发起大量申请建立websocket连接的请求,建立持久连接,消耗服务器资源
可以发送一个单一的庞大的数据帧(如:2^16),或者发送一个长流的分片消息的小帧,消耗服务器资源
02
修复
设置单IP可建立连接的最大连接数
限制帧大小和多个帧重组后的总消息大小
01
漏洞
02
修复
if headers['Cookies'] == xxx:
...
conn.send(bytes(response_str, encoding='utf-8'))
3.4 CSWSH
01
漏洞
01
修复
if headers['Token'] == xxx:
...
conn.send(bytes(response_str, encoding='utf-8'))
参考
https://www.cnblogs.com/ssyfj/p/9245150.html
https://www.cnblogs.com/lichmama/p/3931212.html
https://security.tencent.com/index.php/blog/msg/119
https://www.cnblogs.com/songwenjie/p/8575579.html
http://www.ruanyifeng.com/blog/2017/05/websocket.html
https://developer.ibm.com/zh/articles/j-lo-websocket-cross-site/
