Skip to main content

security_rust/protocol/
dns_rebinding.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)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}