web_analyzer/
subdomain_takeover_mobile.rs1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Serialize, Deserialize)]
4pub struct DnsCheckResult {
5 pub a_records: Vec<String>,
6 pub aaaa_records: Vec<String>,
7 pub cname_records: Vec<String>,
8 pub mx_records: Vec<String>,
9 pub txt_records: Vec<String>,
10 pub ns_records: Vec<String>,
11 pub has_valid_dns: bool,
12}
13
14#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct TakeoverVulnerability {
16 pub subdomain: String,
17 pub service: String,
18 pub vulnerability_type: String,
19 pub cname: Option<String>,
20 pub confidence: String,
21 pub description: String,
22 pub exploitation_difficulty: String,
23 pub mitigation: String,
24 pub dns_info: DnsCheckResult,
25 pub http_status: Option<u16>,
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
29pub struct ScanStatistics {
30 pub subdomains_scanned: usize,
31 pub vulnerable_count: usize,
32 pub high_confidence: usize,
33 pub medium_confidence: usize,
34 pub low_confidence: usize,
35 pub scan_time_secs: f64,
36 pub services_checked: usize,
37}
38
39#[derive(Debug, Clone, Serialize, Deserialize)]
40pub struct TakeoverResult {
41 pub domain: String,
42 pub statistics: ScanStatistics,
43 pub vulnerable: Vec<TakeoverVulnerability>,
44}
45
46use hickory_resolver::config::*;
47use hickory_resolver::AsyncResolver;
48use std::time::Instant;
49use tokio::task::JoinSet;
50
51pub async fn check_subdomain_takeover(
52 domain: &str,
53 subdomains: &[String],
54 progress_tx: Option<tokio::sync::mpsc::Sender<crate::ScanProgress>>,
55) -> Result<TakeoverResult, Box<dyn std::error::Error + Send + Sync>> {
56 let start_time = Instant::now();
57 let resolver = AsyncResolver::tokio(ResolverConfig::cloudflare(), ResolverOpts::default());
58
59 if let Some(t) = &progress_tx {
60 let _ = t.send(crate::ScanProgress {
61 module: "Subdomain Takeover".into(),
62 percentage: 10.0,
63 message: format!("Checking {} subdomains for dangling CNAMEs natively", subdomains.len()),
64 status: "Info".into(),
65 }).await;
66 }
67
68 let mut vulnerable_list = Vec::new();
69 let mut set = JoinSet::new();
70 let semaphore = std::sync::Arc::new(tokio::sync::Semaphore::new(10));
71
72 for d in subdomains {
73 let subdomain = d.clone();
74 let res = resolver.clone();
75 let permit = semaphore.clone().acquire_owned().await.unwrap();
76 set.spawn(async move {
77 use hickory_resolver::proto::rr::RecordType;
78 let mut cname_records = vec![];
79 let mut a_records = vec![];
80
81 if let Ok(response) = res.lookup(subdomain.as_str(), RecordType::CNAME).await {
82 for record in response.iter() {
83 if let Some(cname) = record.as_cname() {
84 cname_records.push(cname.to_string());
85 }
86 }
87 }
88
89 if let Ok(response) = res.ipv4_lookup(subdomain.as_str()).await {
90 for ip in response.iter() {
91 a_records.push(ip.to_string());
92 }
93 }
94
95 let result = if !cname_records.is_empty() && a_records.is_empty() {
96 Some(TakeoverVulnerability {
97 subdomain: subdomain.clone(),
98 service: "Unknown".into(),
99 vulnerability_type: "Dangling CNAME".into(),
100 cname: Some(cname_records[0].clone()),
101 confidence: "High".into(),
102 description: format!("CNAME points to {} which doesn't resolve to an IP.", cname_records[0]),
103 exploitation_difficulty: "Medium".into(),
104 mitigation: "Remove the DNS record or claim the external resource.".into(),
105 dns_info: DnsCheckResult {
106 a_records,
107 aaaa_records: vec![],
108 cname_records,
109 mx_records: vec![],
110 txt_records: vec![],
111 ns_records: vec![],
112 has_valid_dns: true,
113 },
114 http_status: None,
115 })
116 } else {
117 None
118 };
119 drop(permit);
120 result
121 });
122 }
123
124 let mut scanned = 0;
125 while let Some(vuln_opt) = set.join_next().await {
126 scanned += 1;
127 if let Ok(Some(v)) = vuln_opt {
128 vulnerable_list.push(v);
129 }
130 }
131
132 if let Some(t) = &progress_tx {
133 let _ = t.send(crate::ScanProgress {
134 module: "Subdomain Takeover".into(),
135 percentage: 100.0,
136 message: "Finished native CNAME checking".into(),
137 status: "Info".into(),
138 }).await;
139 }
140
141 Ok(TakeoverResult {
142 domain: domain.to_string(),
143 statistics: ScanStatistics {
144 subdomains_scanned: scanned,
145 vulnerable_count: vulnerable_list.len(),
146 high_confidence: vulnerable_list.len(),
147 medium_confidence: 0,
148 low_confidence: 0,
149 scan_time_secs: start_time.elapsed().as_secs_f64(),
150 services_checked: 0,
151 },
152 vulnerable: vulnerable_list,
153 })
154}