WebSocket了解

admin 2025年1月22日09:08:18评论6 views字数 8520阅读28分24秒阅读模式

WebSocket了解

定义

之前都是轮询与长轮询,都是由浏览器每隔一段时间去请求服务端,服务端再响应新数据

现在到它了,用于在 Web 浏览器和服务器之间创建快速双向通道的协议,说是不局限于每次由客户端主动请求了

过程

  1. 1. 以http握手启动,请求体升级成websocket,仅GET
  2. 2. 服务端回一个确认包,回一个101
  3. 3. 两边就可以发消息了,最小单位是帧(frame)
  4. 4. 维护一个持久的连接

http对比

http传输由客户端先发起(http、https)

无连接(即一次性连接,想要提高效率,要么keep-alive)

无状态(一般每次的请求响应之间是独立的,不记忆,需要cookie、session、token)

一请求一响应

websocket连接建立后可双向发起(ws://,wss://,也是80和443端口)

长连接

有状态,貌似是因为可以实时更新?

连接建立后,双向通信

WebSocket了解

为什么WS的第一次请求是GET,见WebSocket 协议的规范 RFC 6455

https://www.rfc-editor.org/rfc/rfc6455.html

WebSocket了解

它还要求必须包含

host头

Upgrade头(而且其值必须包含websocket)

Sec-WebSocket-Key头(其值是随机的16 个字节组成的数据值,且base64编码),比如0x0102,这就是2字节了;0x01,即8位二进制,是一字节

Origin

Sec-WebSocket-Version: 13

......等

https://www.rfc-editor.org/rfc/rfc6455.html

一般应用在聊天室、多人在线这种实时需求的场景

https://cloud.tencent.com/developer/article/1887095

python-websockets库

这里自己找G老师生成了,随便看看

server.py

import asyncioimport websockets# WebSocket server handlerasyncdefecho(websocket, path):print("Client connected")try:asyncfor message in websocket:print(f"Received: {message}")await websocket.send(f"Echo: {message}")  # Echo back the messageexcept websockets.ConnectionClosedOK:print("Client disconnected")# Start the WebSocket serverasyncdefmain():    server = await websockets.serve(echo, "localhost"8765)print("WebSocket server started on ws://localhost:8765")await server.wait_closed()asyncio.run(main())

这里是 调用的websockets.serve,建立好了,只负责打印

client.py

import asyncioimport websocketsasyncdefclient():    uri = "ws://localhost:8765"# Server URIasyncwith websockets.connect(uri) as websocket:print("Connected to server")# Send a message to the server        message = "Hello, WebSocket Server!"print(f"Sending: {message}")await websocket.send(message)# Wait for a response        response = await websocket.recv()print(f"Received: {response}")asyncio.run(client())

因为是直接调的库,毕竟封装好了,请求发的http已经被抽象掉了,所以看不到具体实现

WebSocket了解

实际上更应该偏向这样的(客户端视角)

WebSocket了解

go代码的话,应该是这部分

https://github.com/HavocFramework/Havoc/blob/main/teamserver/pkg/service/agent.go

https://github.com/HavocFramework/Havoc/blob/main/teamserver/pkg/service/service.go

帧处理

http的传输格式是报文,而ws的传输数据格式是帧

WebSocket了解

这个就看看,具体的实现交给G老师

主要是有一个场景,目前有一个服务器是ws协议的,那客户端想要和它建立连接并发送数据

客户端要求:

1.发送http请求

2.解析响应包

3.发送/接收帧

服务器的要求:

1.解析http请求

2.发送响应包

3.发送/接收帧

python代码

client.py
import socketimport base64import hashlibdefgenerate_handshake_request():"""    生成 WebSocket 的握手请求    """    key = base64.b64encode(b"random_key_123").decode()    request = ("GET /chat HTTP/1.1rn""Host: localhost:8000rn""Upgrade: websocketrn""Connection: Upgradern"f"Sec-WebSocket-Key: {key}rn""Sec-WebSocket-Version: 13rnrn"    )return requestdefparse_handshake_response(response):"""    解析服务器的握手响应    """    lines = response.split("rn")    status_line = lines[0]ifnot status_line.startswith("HTTP/1.1 101 Switching Protocols"):raise Exception("Failed to upgrade to WebSocket")for line in lines[1:]:if line.startswith("Sec-WebSocket-Accept"):# 这里可以添加对 Sec-WebSocket-Accept 的验证逻辑passdefsend_frame(sock, payload, opcode=0x1, final=True, mask=True):"""    发送 WebSocket 帧    """    first_byte = (final << 7) | opcode    second_byte = 0x00if mask:        second_byte |= 0x80    payload_length = len(payload)if payload_length <= 125:        second_byte |= payload_length        header = bytes([first_byte, second_byte])elif payload_length <= 65535:        second_byte |= 126        header = bytes([first_byte, second_byte, (payload_length >> 8) & 0xFF, payload_length & 0xFF])else:        second_byte |= 127        payload_length_bytes = payload_length.to_bytes(8, byteorder='big')        header = bytes([first_byte, second_byte]) + payload_length_bytesif mask:import os        mask_key = os.urandom(4)        masked_payload = bytearray(payload.encode('utf-8'))for i inrange(len(payload)):            masked_payload[i] ^= mask_key[i % 4]        frame = header + mask_key + masked_payloadelse:        frame = header + payload.encode('utf-8')    sock.sendall(frame)defreceive_frame(sock):"""    接收 WebSocket 帧    """    first_byte = sock.recv(1)[0]    final = bool(first_byte >> 7)    opcode = first_byte & 0x0F    second_byte = sock.recv(1)[0]    masked = bool(second_byte >> 7)    payload_length = second_byte & 0x7F    offset = 2if payload_length == 126:        payload_length = (int.from_bytes(sock.recv(2), byteorder='big'))        offset = 4elif payload_length == 127:        payload_length = int.from_bytes(sock.recv(8), byteorder='big')        offset = 10if masked:        mask_key = sock.recv(4)        offset += 4        payload = bytearray(sock.recv(payload_length))for i inrange(payload_length):            payload[i] ^= mask_key[i % 4]else:        payload = sock.recv(payload_length)return final, opcode, payloaddefmain():# 发送 HTTP 请求    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)    sock.connect(("localhost"8000))    request = generate_handshake_request()    sock.sendall(request.encode())# 解析响应包    response = sock.recv(1024).decode()    parse_handshake_response(response)print("Let's talk now!")# 发送/接收帧whileTrue:        message=input("Client: ")        send_frame(sock, message)if message == 'exit':break        final, opcode, payload = receive_frame(sock)print(f"Received: {payload.decode()}")if __name__ == "__main__":    main()
server.py
import socketimport base64import hashlibdefparse_handshake_request(request):"""    解析客户端的握手请求    """    lines = request.split("rn")    key = Nonefor line in lines:if line.startswith("Sec-WebSocket-Key"):            key = line.split(": ")[1]return keydefgenerate_handshake_response(key):"""    生成握手响应    """    accept = base64.b64encode(hashlib.sha1((key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").encode()).digest()).decode()    response = ("HTTP/1.1 101 Switching Protocolsrn""Upgrade: websocketrn""Connection: Upgradern"f"Sec-WebSocket-Accept: {accept}rnrn"    )return responsedefsend_frame(sock, payload, opcode=0x1, final=True, mask=True):"""    发送 WebSocket 帧    """    first_byte = (final << 7) | opcode    second_byte = 0x00if mask:        second_byte |= 0x80    payload_length = len(payload)if payload_length <= 125:        second_byte |= payload_length        header = bytes([first_byte, second_byte])elif payload_length <= 65535:        second_byte |= 126        header = bytes([first_byte, second_byte, (payload_length >> 8) & 0xFF, payload_length & 0xFF])else:        second_byte |= 127        payload_length_bytes = payload_length.to_bytes(8, byteorder='big')        header = bytes([first_byte, second_byte]) + payload_length_bytesif mask:import os        mask_key = os.urandom(4)        masked_payload = bytearray(payload.encode('utf-8'))for i inrange(len(payload)):            masked_payload[i] ^= mask_key[i % 4]        frame = header + mask_key + masked_payloadelse:        frame = header + payload.encode('utf-8')    sock.sendall(frame)defreceive_frame(sock):"""    接收 WebSocket 帧    """    first_byte = sock.recv(1)[0]    final = bool(first_byte >> 7)    opcode = first_byte & 0x0F    second_byte = sock.recv(1)[0]    masked = bool(second_byte >> 7)    payload_length = second_byte & 0x7F    offset = 2if payload_length == 126:        payload_length = (int.from_bytes(sock.recv(2), byteorder='big'))        offset = 4elif payload_length == 127:        payload_length = int.from_bytes(sock.recv(8), byteorder='big')        offset = 10if masked:        mask_key = sock.recv(4)        offset += 4        payload = bytearray(sock.recv(payload_length))for i inrange(payload_length):            payload[i] ^= mask_key[i % 4]else:        payload = sock.recv(payload_length)return final, opcode, payloaddefmain():    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)    server_socket.bind(("localhost"8000))    server_socket.listen(1)print("Listening on port 8000...")whileTrue:        client_socket, client_address = server_socket.accept()print(f"Accepted connection from {client_address}")        request = b""whileTrue:            data = client_socket.recv(1024)            request += dataifb"rnrn"in request:break# 解析 HTTP 请求        key = parse_handshake_request(request.decode())if key:# 发送响应包            response = generate_handshake_response(key)            client_socket.sendall(response.encode())print("Let's talk!")# 发送/接收帧whileTrue:                final, opcode, payload = receive_frame(client_socket)print(f"Received: {payload.decode()}")                message=input("Server: ")if message =='exit':break                send_frame(client_socket, message)if __name__ == "__main__":    main()

这里的发送和接收写的简单,所以看起来是按顺序的,必须等待对方,实际上实时部分需要靠多线程来处理一下,主要是第一次连接建立完后,哪边先发数据都可以,随时发,知道就行

WebSocket了解

这里示例代码两边都用的socket,因为ws和http都基于tcp协议

???

主要是有个poc,它在客户端仅用request.post就能完成对目标websocket的连接和传输(代码部分略过),看不进去。。。

该poc是个SSRF,它这里是相当于对外网的一个文件读取,内容是12313

WebSocket了解

但如果是对它内部的40056(比如外网是https://,有个内网的wss://..40056),会有tls的阻碍导致无法读取文件,所以复现的话

client/src/Havoc/Connector.cc里:

url改成ws://

含Socket->setSslConfiguration()的都注释掉

teamserver/cmd/server/teamserver.go里:

替换如下:

if err = t.Server.Engine.Run(Host+":"+Port);

ws://

https://4xura.com/ctf/htb/

make ts-build重新编译即可

./havoc server --profile ~/se*/b*/havoc.yaotl -v --debug

这样就可以正常访问了

WebSocket了解

总结

websocket主要用于双向通信,高效、实时

http与websocket的关系和区别

数据传输为帧(frame),比如把http的json格式转换成frame,看看就行

参考

https://cloud.tencent.com/developer/article/1887095

https://www.cnblogs.com/mq0036/p/18643983

https://www.rfc-editor.org/rfc/rfc6455.html

https://www.openmymind.net/WebSocket-Framing-Masking-Fragmentation-and-More/

原文始发于微信公众号(羽泪云小栈):WebSocket了解

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年1月22日09:08:18
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   WebSocket了解https://cn-sec.com/archives/3657640.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息