Skip to main content

security_rust/protocol/
host_header.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)\r\n.*Host:").unwrap(),
11        Regex::new(r"(?i)\r\n.*X-Forwarded").unwrap(),
12        Regex::new(r"(?i)\r\n.*X-Original-URL").unwrap(),
13        Regex::new(r"(?i)\r\n.*X-Rewrite-URL").unwrap(),
14    ]
15});
16
17pub struct HostHeaderDetector;
18
19impl Detector for HostHeaderDetector {
20    fn name(&self) -> &'static str {
21        "host_header"
22    }
23
24    fn detect(&self, input: &str) -> Option<DetectionResult> {
25        regex_detect(&PATTERNS, self.name(), AttackCategory::Protocol, Severity::High, "Host header attack 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            &HostHeaderDetector,
36            input,
37            AttackCategory::Protocol,
38            Severity::High,
39        );
40    }
41
42    fn assert_clean(input: &str) {
43        crate::test_helpers::assert_clean(&HostHeaderDetector, input);
44    }
45
46    #[test]
47    fn name_is_host_header() {
48        assert_eq!(HostHeaderDetector.name(), "host_header");
49    }
50
51    #[test]
52    fn detects_x_forwarded_host() {
53        assert_detected("Host: example.com\r\nX-Forwarded-Host: evil.com");
54    }
55
56    #[test]
57    fn detects_injected_host_line() {
58        assert_detected("GET / HTTP/1.1\r\nHost: evil.com");
59    }
60
61    #[test]
62    fn detects_x_original_url() {
63        assert_detected("Host: a.com\r\nX-Original-URL: /admin");
64    }
65
66    #[test]
67    fn detects_x_rewrite_url() {
68        assert_detected("Host: a.com\r\nX-Rewrite-URL: /admin");
69    }
70
71    #[test]
72    fn detects_x_forwarded_for() {
73        assert_detected("Host: a.com\r\nX-Forwarded-For: 1.2.3.4");
74    }
75
76    #[test]
77    fn detects_mixed_case() {
78        assert_detected("host: example.com\r\nx-forwarded-host: evil.com");
79    }
80
81    #[test]
82    fn rejects_single_header_lines() {
83        assert_clean("Host: example.com");
84        assert_clean("X-Forwarded-Host: evil.com");
85        assert_clean("Host: example.com\r\nAccept: */*");
86    }
87
88    #[test]
89    fn rejects_lf_only_newlines() {
90        assert_clean("Host: example.com\nX-Forwarded-Host: evil.com");
91    }
92
93    #[test]
94    fn rejects_empty_and_whitespace() {
95        assert_clean("");
96        assert_clean("   ");
97    }
98
99    #[test]
100    fn rejects_unicode_text() {
101        assert_clean("主机头示例,无攻击");
102    }
103}