Skip to content

Commit 68670b4

Browse files
committed
Add support for websockets (#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

Lines changed: 11 additions & 5 deletions
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

Lines changed: 4 additions & 4 deletions
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

Lines changed: 61 additions & 0 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy