security_rust/protocol/
dns_rebinding.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)Host:\s*127\.").unwrap(),
11 Regex::new(r"(?i)Host:\s*10\.").unwrap(),
12 Regex::new(r"(?i)Host:\s*192\.168\.").unwrap(),
13 Regex::new(r"(?i)Host:\s*172\.(1[6-9]|2\d|3[01])").unwrap(),
14 Regex::new(r"(?i)Host:\s*localhost").unwrap(),
15 Regex::new(r"(?i)Host:\s*\[::1\]").unwrap(),
16 Regex::new(r"(?i)Host:\s*0\.0\.0\.0").unwrap(),
17 ]
18});
19
20pub struct DnsRebindingDetector;
21
22impl Detector for DnsRebindingDetector {
23 fn name(&self) -> &'static str {
24 "dns_rebinding"
25 }
26
27 fn detect(&self, input: &str) -> Option<DetectionResult> {
28 regex_detect(
29 &PATTERNS,
30 self.name(),
31 AttackCategory::Protocol,
32 Severity::High,
33 "DNS rebinding attack detected",
34 input,
35 )
36 }
37}
38
39#[cfg(test)]
40mod tests {
41 use super::*;
42
43 fn assert_detected(input: &str) {
44 crate::test_helpers::assert_detected(
45 &DnsRebindingDetector,
46 input,
47 AttackCategory::Protocol,
48 Severity::High,
49 );
50 }
51
52 fn assert_clean(input: &str) {
53 crate::test_helpers::assert_clean(&DnsRebindingDetector, input);
54 }
55
56 #[test]
57 fn name_is_dns_rebinding() {
58 assert_eq!(DnsRebindingDetector.name(), "dns_rebinding");
59 }
60
61 #[test]
62 fn detects_loopback_host() {
63 assert_detected("Host: 127.0.0.1");
64 }
65
66 #[test]
67 fn detects_private_hosts() {
68 assert_detected("Host: 10.0.0.2");
69 assert_detected("Host: 192.168.1.1");
70 assert_detected("Host: 172.16.0.1");
71 assert_detected("Host: 172.31.255.255");
72 }
73
74 #[test]
75 fn detects_local_names() {
76 assert_detected("Host: localhost");
77 assert_detected("Host: [::1]");
78 assert_detected("Host: 0.0.0.0");
79 }
80
81 #[test]
82 fn detects_mixed_case() {
83 assert_detected("host: 127.0.0.1");
84 }
85
86 #[test]
87 fn rejects_public_hosts() {
88 assert_clean("Host: example.com");
89 assert_clean("Host: 8.8.8.8");
90 assert_clean("Host: 172.32.0.1");
91 }
92
93 #[test]
94 fn rejects_hostname_variants() {
95 assert_clean("Hostname: 127.0.0.1");
96 assert_clean("Hostname: localhost");
97 }
98
99 #[test]
100 fn rejects_near_misses() {
101 assert_clean("Host: 12.7.0.1");
102 assert_clean("Host: 172.15.0.1");
103 }
104
105 #[test]
106 fn rejects_empty_and_whitespace() {
107 assert_clean("");
108 assert_clean(" ");
109 }
110
111 #[test]
112 fn rejects_unicode_text() {
113 assert_clean("主机名解析测试,无攻击");
114 }
115}