Skip to main content

security_rust/protocol/
websocket.rs

1// Copyright (c) 2026 erik <erik@erik.xyz> — https://erik.xyz
2
3use regex::Regex;
4use std::sync::LazyLock;
5
6use crate::{regex_detect, AttackCategory, DetectionResult, Detector, Severity};
7
8static PATTERNS: LazyLock<Vec<Regex>> = LazyLock::new(|| {
9    vec![
10        Regex::new(r"(?i)Upgrade:\s*websocket").unwrap(),
11        Regex::new(r"(?i)Sec-WebSocket-Key:").unwrap(),
12        Regex::new(r"(?i)Origin:\s*null.*Upgrade").unwrap(),
13        Regex::new(r"(?i)ws://").unwrap(),
14    ]
15});
16
17pub struct WebSocketDetector;
18
19impl Detector for WebSocketDetector {
20    fn name(&self) -> &'static str {
21        "websocket"
22    }
23
24    fn detect(&self, input: &str) -> Option<DetectionResult> {
25        regex_detect(&PATTERNS, self.name(), AttackCategory::Protocol, Severity::High, "WebSocket hijack attempt detected", input)
26    }
27}
28
29#[cfg(test)]
30mod tests {
31    use super::*;
32
33    fn assert_detected(input: &str) {
34        crate::test_helpers::assert_detected(
35            &WebSocketDetector,
36            input,
37            AttackCategory::Protocol,
38            Severity::High,
39        );
40    }
41
42    fn assert_clean(input: &str) {
43        crate::test_helpers::assert_clean(&WebSocketDetector, input);
44    }
45
46    #[test]
47    fn name_is_websocket() {
48        assert_eq!(WebSocketDetector.name(), "websocket");
49    }
50
51    #[test]
52    fn detects_upgrade_header() {
53        assert_detected("Upgrade: websocket");
54    }
55
56    #[test]
57    fn detects_sec_websocket_key() {
58        assert_detected("Connection: Upgrade\r\nSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==");
59    }
60
61    #[test]
62    fn detects_ws_scheme() {
63        assert_detected("ws://evil.com/socket");
64    }
65
66    #[test]
67    fn detects_null_origin_upgrade() {
68        assert_detected("Origin: null Upgrade: websocket");
69    }
70
71    #[test]
72    fn detects_mixed_case() {
73        assert_detected("upgrade: WebSocket");
74        assert_detected("SEC-WEBSOCKET-KEY: abc==");
75    }
76
77    #[test]
78    fn rejects_benign_requests() {
79        assert_clean("Upgrade: h2c");
80        assert_clean("Sec-WebSocket-Protocol: chat");
81        assert_clean("wss://example.com/socket");
82        assert_clean("Origin: https://example.com");
83        assert_clean("GET /chat HTTP/1.1\r\nConnection: keep-alive");
84    }
85
86    #[test]
87    fn rejects_empty_and_whitespace() {
88        assert_clean("");
89        assert_clean("   ");
90    }
91
92    #[test]
93    fn rejects_unicode_text() {
94        assert_clean("WebSocket 握手信息");
95    }
96}