Skip to content

Commit 2083edf

Browse files
committed
1.3.0
1 parent ce60450 commit 2083edf

File tree

3 files changed

+112
-9
lines changed

3 files changed

+112
-9
lines changed

Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "cloud_fade"
3-
version = "0.1.2"
3+
version = "0.1.3"
44
authors = ["boring <boringthegod@tutanota.com>"]
55
edition = "2021"
66
description = "Unmask real IP address of a domain hidden behind Cloudflare by IPs bruteforcing"

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ Examples:
3737
## prerequisites
3838

3939
- [Rust](https://www.rust-lang.org/tools/install)
40-
40+
- whois
4141
## installation
4242

4343
```

src/main.rs

+110-7
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use std::collections::HashMap;
99
use std::fs::File;
1010
use std::io::{BufRead, BufReader};
1111
use std::net::{Ipv4Addr, SocketAddr};
12+
use std::process::Command;
1213
use std::str::FromStr;
1314
use std::sync::Arc;
1415
use std::time::Duration;
@@ -21,7 +22,7 @@ use scraper::{Html, Selector};
2122
#[tokio::main]
2223
async fn main() {
2324
let matches = App::new("CloudFade")
24-
.version("1.2")
25+
.version("1.3")
2526
.author("boring")
2627
.about("Unmask real IP address of a domain hidden behind Cloudflare by IPs bruteforcing")
2728
.arg(
@@ -144,7 +145,27 @@ async fn main() {
144145
return;
145146
}
146147

147-
let total_tasks = domains.len() * ips.len();
148+
let filtered_ips = filter_cloudflare_ips(ips).await;
149+
if filtered_ips.is_empty() {
150+
eprintln!("No non-Cloudflare IP addresses to process.");
151+
return;
152+
}
153+
154+
let mut valid_domains = Vec::new();
155+
for domain in &domains {
156+
if is_domain_valid(domain, timeout_duration).await {
157+
valid_domains.push(domain.clone());
158+
} else {
159+
eprintln!("Ignoring domain {} due to unresponsiveness or invalid status code", domain);
160+
}
161+
}
162+
163+
if valid_domains.is_empty() {
164+
eprintln!("No valid domains to process.");
165+
return;
166+
}
167+
168+
let total_tasks = valid_domains.len() * filtered_ips.len();
148169

149170
let pb = ProgressBar::new(total_tasks as u64);
150171
pb.set_style(
@@ -164,10 +185,9 @@ async fn main() {
164185
vec!["Mozilla/5.0 (compatible; CloudFade/1.0; +https://example.com)".to_string()]
165186
};
166187

167-
// Extraction des motifs uniques pour chaque domaine
168188
let mut target_patterns = HashMap::new();
169189

170-
for domain in &domains {
190+
for domain in &valid_domains {
171191
let target_pattern = match get_target_pattern(domain, timeout_duration).await {
172192
Some(pattern) => pattern,
173193
None => {
@@ -188,14 +208,13 @@ async fn main() {
188208

189209
let tasks_list: Vec<(String, String)> = target_patterns
190210
.keys()
191-
.flat_map(|domain| ips.iter().map(move |ip| (domain.clone(), ip.clone())))
211+
.flat_map(|domain| filtered_ips.iter().map(move |ip| (domain.clone(), ip.clone())))
192212
.collect();
193213

194214
let semaphore = Arc::new(Semaphore::new(max_threads));
195215

196216
let mut tasks = FuturesUnordered::new();
197217

198-
// Préparation du fichier de sortie
199218
let output_file = matches.value_of("output");
200219
let output_mutex = if let Some(output_path) = output_file {
201220
Some(Arc::new(tokio::sync::Mutex::new(
@@ -214,7 +233,6 @@ async fn main() {
214233
let semaphore = semaphore.clone();
215234
let pb = pb.clone();
216235

217-
// Récupérer le motif cible pour ce domaine
218236
let target_pattern = target_patterns.get(&domain).unwrap().clone();
219237

220238
let permit = semaphore.clone().acquire_owned().await.unwrap();
@@ -307,6 +325,30 @@ async fn test_ip(
307325
None
308326
}
309327

328+
async fn is_domain_valid(domain: &str, timeout_duration: Duration) -> bool {
329+
let client = reqwest::Client::builder()
330+
.danger_accept_invalid_certs(true)
331+
.build()
332+
.unwrap();
333+
334+
let url = format!("http://{}/", domain);
335+
let request = client.get(&url);
336+
337+
let response = timeout(timeout_duration, request.send()).await;
338+
if let Ok(Ok(resp)) = response {
339+
let status = resp.status();
340+
if status == 200 {
341+
true
342+
} else {
343+
eprintln!("Domain {} returned status code {}", domain, status);
344+
false
345+
}
346+
} else {
347+
eprintln!("Domain {} is unresponsive", domain);
348+
false
349+
}
350+
}
351+
310352
async fn get_target_pattern(domain: &str, timeout_duration: Duration) -> Option<String> {
311353
let client = reqwest::Client::builder()
312354
.danger_accept_invalid_certs(true)
@@ -318,11 +360,17 @@ async fn get_target_pattern(domain: &str, timeout_duration: Duration) -> Option<
318360

319361
let response = timeout(timeout_duration, request.send()).await;
320362
if let Ok(Ok(resp)) = response {
363+
if resp.status() != 200 {
364+
eprintln!("Domain {} returned status code {}", domain, resp.status());
365+
return None;
366+
}
321367
if let Ok(text) = resp.text().await {
322368
if let Some(title) = extract_title(&text) {
323369
return Some(title);
324370
}
325371
}
372+
} else {
373+
eprintln!("Domain {} is unresponsive", domain);
326374
}
327375
None
328376
}
@@ -382,3 +430,58 @@ fn parse_ip_range(ip_range: &str) -> Result<Vec<String>, String> {
382430

383431
Ok(ips)
384432
}
433+
434+
async fn filter_cloudflare_ips(ips: Vec<String>) -> Vec<String> {
435+
let semaphore = Arc::new(Semaphore::new(50));
436+
let mut tasks = FuturesUnordered::new();
437+
let asn_to_filter = "AS13335";
438+
439+
for ip in ips {
440+
let ip = ip.clone();
441+
let permit = semaphore.clone().acquire_owned().await.unwrap();
442+
tasks.push(tokio::spawn(async move {
443+
let result = is_cloudflare_ip(&ip, asn_to_filter).await;
444+
drop(permit);
445+
if !result {
446+
Some(ip)
447+
} else {
448+
None
449+
}
450+
}));
451+
}
452+
453+
let mut filtered_ips = Vec::new();
454+
while let Some(result) = tasks.next().await {
455+
if let Ok(Some(ip)) = result {
456+
filtered_ips.push(ip);
457+
}
458+
}
459+
460+
filtered_ips
461+
}
462+
463+
async fn is_cloudflare_ip(ip: &str, asn_to_filter: &str) -> bool {
464+
let output = Command::new("whois")
465+
.arg(ip)
466+
.output();
467+
468+
match output {
469+
Ok(output) => {
470+
if output.status.success() {
471+
let data = String::from_utf8_lossy(&output.stdout).to_lowercase();
472+
if data.contains(&asn_to_filter.to_lowercase()) {
473+
true
474+
} else {
475+
false
476+
}
477+
} else {
478+
eprintln!("WHOIS command failed for IP {}: {}", ip, String::from_utf8_lossy(&output.stderr));
479+
false
480+
}
481+
}
482+
Err(e) => {
483+
eprintln!("Failed to execute WHOIS command for IP {}: {}", ip, e);
484+
false
485+
}
486+
}
487+
}

0 commit comments

Comments
 (0)