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::{AttackCategory, DetectionResult, Detector, Severity, regex_detect};
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(
26            &PATTERNS,
27            self.name(),
28            AttackCategory::Protocol,
29            Severity::High,
30            "Host header attack detected",
31            input,
32        )
33    }
34}
35
36#[cfg(test)]
37mod tests {
38    use super::*;
39
40    fn assert_detected(input: &str) {
41        crate::test_helpers::assert_detected(
42            &HostHeaderDetector,
43            input,
44            AttackCategory::Protocol,
45            Severity::High,
46        );
47    }
48
49    fn assert_clean(input: &str) {
50        crate::test_helpers::assert_clean(&HostHeaderDetector, input);
51    }
52
53    #[test]
54    fn name_is_host_header() {
55        assert_eq!(HostHeaderDetector.name(), "host_header");
56    }
57
58    #[test]
59    fn detects_x_forwarded_host() {
60        assert_detected("Host: example.com\r\nX-Forwarded-Host: evil.com");
61    }
62
63    #[test]
64    fn detects_injected_host_line() {
65        assert_detected("GET / HTTP/1.1\r\nHost: evil.com");
66    }
67
68    #[test]
69    fn detects_x_original_url() {
70        assert_detected("Host: a.com\r\nX-Original-URL: /admin");
71    }
72
73    #[test]
74    fn detects_x_rewrite_url() {
75        assert_detected("Host: a.com\r\nX-Rewrite-URL: /admin");
76    }
77
78    #[test]
79    fn detects_x_forwarded_for() {
80        assert_detected("Host: a.com\r\nX-Forwarded-For: 1.2.3.4");
81    }
82
83    #[test]
84    fn detects_mixed_case() {
85        assert_detected("host: example.com\r\nx-forwarded-host: evil.com");
86    }
87
88    #[test]
89    fn rejects_single_header_lines() {
90        assert_clean("Host: example.com");
91        assert_clean("X-Forwarded-Host: evil.com");
92        assert_clean("Host: example.com\r\nAccept: */*");
93    }
94
95    #[test]
96    fn rejects_lf_only_newlines() {
97        assert_clean("Host: example.com\nX-Forwarded-Host: evil.com");
98    }
99
100    #[test]
101    fn rejects_empty_and_whitespace() {
102        assert_clean("");
103        assert_clean("   ");
104    }
105
106    #[test]
107    fn rejects_unicode_text() {
108        assert_clean("主机头示例,无攻击");
109    }
110}