security_rust/protocol/
host_header.rs1use 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}