security_rust/protocol/
dns_rebinding.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)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(&PATTERNS, self.name(), AttackCategory::Protocol, Severity::High, "DNS rebinding attack detected", input)
29 }
30}
31
32#[cfg(test)]
33mod tests {
34 use super::*;
35
36 fn assert_detected(input: &str) {
37 crate::test_helpers::assert_detected(
38 &DnsRebindingDetector,
39 input,
40 AttackCategory::Protocol,
41 Severity::High,
42 );
43 }
44
45 fn assert_clean(input: &str) {
46 crate::test_helpers::assert_clean(&DnsRebindingDetector, input);
47 }
48
49 #[test]
50 fn name_is_dns_rebinding() {
51 assert_eq!(DnsRebindingDetector.name(), "dns_rebinding");
52 }
53
54 #[test]
55 fn detects_loopback_host() {
56 assert_detected("Host: 127.0.0.1");
57 }
58
59 #[test]
60 fn detects_private_hosts() {
61 assert_detected("Host: 10.0.0.2");
62 assert_detected("Host: 192.168.1.1");
63 assert_detected("Host: 172.16.0.1");
64 assert_detected("Host: 172.31.255.255");
65 }
66
67 #[test]
68 fn detects_local_names() {
69 assert_detected("Host: localhost");
70 assert_detected("Host: [::1]");
71 assert_detected("Host: 0.0.0.0");
72 }
73
74 #[test]
75 fn detects_mixed_case() {
76 assert_detected("host: 127.0.0.1");
77 }
78
79 #[test]
80 fn rejects_public_hosts() {
81 assert_clean("Host: example.com");
82 assert_clean("Host: 8.8.8.8");
83 assert_clean("Host: 172.32.0.1");
84 }
85
86 #[test]
87 fn rejects_hostname_variants() {
88 assert_clean("Hostname: 127.0.0.1");
89 assert_clean("Hostname: localhost");
90 }
91
92 #[test]
93 fn rejects_near_misses() {
94 assert_clean("Host: 12.7.0.1");
95 assert_clean("Host: 172.15.0.1");
96 }
97
98 #[test]
99 fn rejects_empty_and_whitespace() {
100 assert_clean("");
101 assert_clean(" ");
102 }
103
104 #[test]
105 fn rejects_unicode_text() {
106 assert_clean("主机名解析测试,无攻击");
107 }
108}