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
61 .send(crate::ScanProgress {
62 module: "Subdomain Takeover".into(),
63 percentage: 10.0,
64 message: format!(
65 "Checking {} subdomains for dangling CNAMEs natively",
66 subdomains.len()
67 ),
68 status: "Info".into(),
69 })
70 .await;
71 }
72
73 let mut vulnerable_list = Vec::new();
74 let mut set = JoinSet::new();
75 let semaphore = std::sync::Arc::new(tokio::sync::Semaphore::new(10));
76
77 for d in subdomains {
78 let subdomain = d.clone();
79 let res = resolver.clone();
80 let permit = semaphore.clone().acquire_owned().await.unwrap();
81 set.spawn(async move {
82 use hickory_resolver::proto::rr::RecordType;
83 let mut cname_records = vec![];
84 let mut a_records = vec![];
85
86 if let Ok(response) = res.lookup(subdomain.as_str(), RecordType::CNAME).await {
87 for record in response.iter() {
88 if let Some(cname) = record.as_cname() {
89 cname_records.push(cname.to_string());
90 }
91 }
92 }
93
94 if let Ok(response) = res.ipv4_lookup(subdomain.as_str()).await {
95 for ip in response.iter() {
96 a_records.push(ip.to_string());
97 }
98 }
99
100 let result = if !cname_records.is_empty() && a_records.is_empty() {
101 Some(TakeoverVulnerability {
102 subdomain: subdomain.clone(),
103 service: "Unknown".into(),
104 vulnerability_type: "Dangling CNAME".into(),
105 cname: Some(cname_records[0].clone()),
106 confidence: "High".into(),
107 description: format!(
108 "CNAME points to {} which doesn't resolve to an IP.",
109 cname_records[0]
110 ),
111 exploitation_difficulty: "Medium".into(),
112 mitigation: "Remove the DNS record or claim the external resource.".into(),
113 dns_info: DnsCheckResult {
114 a_records,
115 aaaa_records: vec![],
116 cname_records,
117 mx_records: vec![],
118 txt_records: vec![],
119 ns_records: vec![],
120 has_valid_dns: true,
121 },
122 http_status: None,
123 })
124 } else {
125 None
126 };
127 drop(permit);
128 result
129 });
130 }
131
132 let mut scanned = 0;
133 while let Some(vuln_opt) = set.join_next().await {
134 scanned += 1;
135 if let Ok(Some(v)) = vuln_opt {
136 vulnerable_list.push(v);
137 }
138 }
139
140 if let Some(t) = &progress_tx {
141 let _ = t
142 .send(crate::ScanProgress {
143 module: "Subdomain Takeover".into(),
144 percentage: 100.0,
145 message: "Finished native CNAME checking".into(),
146 status: "Info".into(),
147 })
148 .await;
149 }
150
151 Ok(TakeoverResult {
152 domain: domain.to_string(),
153 statistics: ScanStatistics {
154 subdomains_scanned: scanned,
155 vulnerable_count: vulnerable_list.len(),
156 high_confidence: vulnerable_list.len(),
157 medium_confidence: 0,
158 low_confidence: 0,
159 scan_time_secs: start_time.elapsed().as_secs_f64(),
160 services_checked: 0,
161 },
162 vulnerable: vulnerable_list,
163 })
164}