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