Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 68670b4

Browse files
committedNov 6, 2024
Add support for websockets (secdev#4578)
1 parent ab975bf commit 68670b4

File tree

4 files changed

+76
-9
lines changed

4 files changed

+76
-9
lines changed
 

‎scapy/contrib/websocket.py

+11-5
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,12 @@
1212
import base64
1313
import zlib
1414
from hashlib import sha1
15-
from scapy.fields import (BitFieldLenField, Field, BitField, BitEnumField, ConditionalField, XIntField, FieldLenField, XNBytesField)
15+
from scapy.fields import (BitFieldLenField, Field, BitField, BitEnumField, ConditionalField, XNBytesField)
1616
from scapy.layers.http import HTTPRequest, HTTPResponse
1717
from scapy.layers.inet import TCP
1818
from scapy.packet import Packet
1919
from scapy.error import Scapy_Exception
20+
import logging
2021

2122

2223
class PayloadLenField(BitFieldLenField):
@@ -94,16 +95,21 @@ def getfield(self, pkt, s):
9495
payloadData = (data_int ^ mask_int).to_bytes(len(payloadData), 'big')
9596

9697
if("permessage-deflate" in pkt.extensions):
97-
try:
98+
try:
9899
payloadData = pkt.decoder[0](payloadData + b"\x00\x00\xff\xff")
99100
except Exception:
100-
# Failed to decompress payload
101-
pass
101+
logging.debug("Failed to decompress payload", payloadData)
102102

103103
return s[length:], payloadData
104104

105105
def addfield(self, pkt, s, val):
106-
# Ensure val is bytes and append the data to the packet
106+
if pkt.mask:
107+
key = struct.pack("I", pkt.maskingKey)[::-1]
108+
data_int = int.from_bytes(val, 'big')
109+
mask_repeated = key * (len(val) // 4) + key[: len(val) % 4]
110+
mask_int = int.from_bytes(mask_repeated, 'big')
111+
val = (data_int ^ mask_int).to_bytes(len(val), 'big')
112+
107113
return s + bytes(val)
108114

109115
def i2len(self, pkt, val):

‎scapy/layers/http.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -529,10 +529,10 @@ def do_dissect(self, s):
529529
"""From the HTTP packet string, populate the scapy object"""
530530
first_line, body = _dissect_headers(self, s)
531531
try:
532-
version_status_reason = re.split(br"\s+", first_line, maxsplit=2) + [None]
533-
self.setfieldval('Http_Version', version_status_reason[0])
534-
self.setfieldval('Status_Code', version_status_reason[1])
535-
self.setfieldval('Reason_Phrase', version_status_reason[2])
532+
method_path_version = re.split(br"\s+", first_line, maxsplit=2) + [None]
533+
self.setfieldval('Method', method_path_version[0])
534+
self.setfieldval('Path', method_path_version[1])
535+
self.setfieldval('Http_Version', method_path_version[2])
536536
except ValueError:
537537
pass
538538
if body:

‎test/contrib/websocket.uts

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# WebSocket layer unit tests
2+
# Copyright (C) 2024 Lucas Drufva <lucas.drufva@gmail.com>
3+
#
4+
# Type the following command to launch start the tests:
5+
# $ test/run_tests -P "load_contrib('websocket')" -t test/contrib/websocket.uts
6+
7+
+ Syntax check
8+
= Import the WebSocket layer
9+
from scapy.contrib.websocket import *
10+
11+
+ WebSocket protocol test
12+
= Packet instantiation
13+
pkt = WebSocket(wsPayload=b"Hello, world!", opcode="text", mask=True, maskingKey=0x11223344)
14+
pkt.show()
15+
import binascii
16+
print(binascii.hexlify(bytes(pkt)))
17+
assert pkt.wsPayload == b"Hello, world!"
18+
assert pkt.mask == True
19+
assert pkt.maskingKey == 0x11223344
20+
21+
22+
= Packet dissection
23+
raw = b'\x01\x0dHello, world!'
24+
pkt = WebSocket(raw)
25+
pkt.show()
26+
27+
assert pkt.fin == 0
28+
assert pkt.rsv == 0
29+
assert pkt.opcode == 0x1
30+
assert pkt.mask == False
31+
assert pkt.payloadLen == 13
32+
assert pkt.wsPayload == b'Hello, world!'
33+
34+
= Dissect masked packet
35+
raw = b'\x01\x8d\x11\x22\x33\x44\x59\x47\x5f\x28\x7e\x0e\x13\x33\x7e\x50\x5f\x20\x30'
36+
pkt = WebSocket(raw)
37+
pkt.show()
38+
39+
assert pkt.fin == 0
40+
assert pkt.rsv == 0
41+
assert pkt.opcode == 0x1
42+
assert pkt.mask == True
43+
assert pkt.payloadLen == 13
44+
assert pkt.wsPayload == b'Hello, world!'
45+
46+
= Session with compression
47+
48+
bind_layers(TCP, WebSocket, dport=5000)
49+
bind_layers(TCP, WebSocket, sport=5000)
50+
51+
from scapy.sessions import TCPSession
52+
53+
filename = scapy_path("/test/pcaps/websocket_compressed_session.pcap")
54+
pkts = sniff(offline=filename, session=TCPSession)
55+
56+
assert len(pkts) == 13
57+
58+
assert pkts[7][WebSocket].wsPayload == b'Hello'
59+
assert pkts[8][WebSocket].wsPayload == b'"Hello"'
60+
assert pkts[10][WebSocket].wsPayload == b'Hello2'
61+
assert pkts[11][WebSocket].wsPayload == b'"Hello2"'
1.51 KB
Binary file not shown.

0 commit comments

Comments
 (0)
Please sign in to comment.