Skip to content

Commit becef45

Browse files
committed
v1.6.0
1 parent f3b0a6d commit becef45

File tree

2 files changed

+108
-37
lines changed

2 files changed

+108
-37
lines changed

Cargo.toml

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "cloud_fade"
3-
version = "0.1.5"
3+
version = "0.1.6"
44
authors = ["boring <boringthegod@tutanota.com>"]
55
edition = "2021"
66
description = "Unmask real IP address of a domain hidden behind Cloudflare by IPs bruteforcing"
@@ -19,8 +19,10 @@ indicatif = "0.16"
1919
num_cpus = "1.13"
2020
rand = "0.8"
2121
reqwest = { version = "0.11", default-features = false, features = ["json", "gzip", "brotli", "deflate", "stream", "rustls-tls"] }
22-
tokio = { version = "1", features = ["macros", "rt-multi-thread", "time"] }
22+
tokio = { version = "1", features = ["macros", "rt-multi-thread", "time", "process"] }
2323
colored = "2.0"
2424
scraper = "0.14"
2525
rquest = "0.25"
2626
flate2 = "1.0"
27+
regex = "1.5"
28+
ipnet = "2.3"

src/main.rs

+104-35
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use clap::{App, Arg};
1+
use clap::{App, Arg, ArgGroup};
22
use futures::stream::FuturesUnordered;
33
use futures::stream::StreamExt;
44
use indicatif::{ProgressBar, ProgressStyle};
@@ -21,6 +21,9 @@ use colored::*;
2121
use scraper::{Html, Selector};
2222
use rquest::tls::Impersonate;
2323
use flate2::read::GzDecoder;
24+
use regex::Regex;
25+
use tokio::process::Command as TokioCommand;
26+
use ipnet::Ipv4Net;
2427

2528
#[tokio::main]
2629
async fn main() {
@@ -52,18 +55,27 @@ async fn main() {
5255
.long("ipfile")
5356
.short("i")
5457
.help("File containing list of IP addresses")
55-
.conflicts_with("iprange")
56-
.required_unless("iprange")
5758
.takes_value(true),
5859
)
5960
.arg(
6061
Arg::with_name("iprange")
6162
.long("iprange")
6263
.value_name("IP_RANGE")
63-
.help("Specifies a single IP address or a range of IP addresses (e.g., 51.15.0.0-51.15.10.255)")
64-
.conflicts_with("ipfile")
64+
.help("Specifies a single IP address, a range of IP addresses (e.g., 51.15.0.0-51.15.10.255), or a CIDR notation (e.g., 51.15.0.0/16)")
6565
.takes_value(true),
6666
)
67+
.arg(
68+
Arg::with_name("asn")
69+
.long("asn")
70+
.value_name("ASN_CODE")
71+
.help("Specify an ASN code (e.g., AS714) to scan all ranges associated with it")
72+
.takes_value(true),
73+
)
74+
.group(ArgGroup::with_name("ip_input")
75+
.args(&["ipfile", "iprange", "asn"])
76+
.required(true)
77+
.multiple(false)
78+
)
6779
.arg(
6880
Arg::with_name("useragents")
6981
.long("useragents")
@@ -110,6 +122,7 @@ async fn main() {
110122

111123
let ip_file = matches.value_of("ipfile");
112124
let ip_range = matches.value_of("iprange");
125+
let asn_code = matches.value_of("asn");
113126
let ua_file = matches.value_of("useragents");
114127
let timeout_secs: u64 = matches.value_of("timeout").unwrap().parse().unwrap();
115128
let timeout_duration = Duration::from_secs(timeout_secs);
@@ -138,8 +151,27 @@ async fn main() {
138151
return;
139152
}
140153
}
154+
} else if let Some(asn_code) = asn_code {
155+
let ranges = fetch_ranges_for_asn(asn_code).await;
156+
if ranges.is_empty() {
157+
eprintln!("No IP ranges found for ASN code {}", asn_code);
158+
return;
159+
} else {
160+
println!("Found the following ranges for ASN {}:", asn_code);
161+
for range in &ranges {
162+
println!("{}", range);
163+
}
164+
let mut ips = Vec::new();
165+
for range in ranges {
166+
match parse_ip_range(&range) {
167+
Ok(mut range_ips) => ips.append(&mut range_ips),
168+
Err(e) => eprintln!("Error parsing range {}: {}", range, e),
169+
}
170+
}
171+
ips
172+
}
141173
} else {
142-
eprintln!("You must specify either an IP address file with --ipfile, or an IP address range with --iprange.");
174+
eprintln!("You must specify either an IP address file with --ipfile, an IP address range with --iprange, or an ASN code with --asn.");
143175
return;
144176
};
145177

@@ -488,41 +520,78 @@ fn read_lines(filename: &str) -> Vec<String> {
488520
}
489521

490522
fn parse_ip_range(ip_range: &str) -> Result<Vec<String>, String> {
491-
let parts: Vec<&str> = ip_range.split('-').collect();
492-
if parts.len() == 1 {
493-
let ip = parts[0];
494-
let ip_addr = Ipv4Addr::from_str(ip).map_err(|_| "Adresse IP invalide.".to_string())?;
495-
Ok(vec![ip_addr.to_string()])
496-
} else if parts.len() == 2 {
497-
let start_ip =
498-
Ipv4Addr::from_str(parts[0]).map_err(|_| "Adresse IP de début invalide.".to_string())?;
499-
let end_ip =
500-
Ipv4Addr::from_str(parts[1]).map_err(|_| "Adresse IP de fin invalide.".to_string())?;
523+
if ip_range.contains('/') {
524+
// Handle CIDR notation
525+
let cidr = ip_range;
526+
let ipnet: Ipv4Net = match cidr.parse() {
527+
Ok(net) => net,
528+
Err(_) => return Err("Invalid CIDR notation.".to_string()),
529+
};
530+
let ips: Vec<String> = ipnet.hosts().map(|ip| ip.to_string()).collect();
531+
Ok(ips)
532+
} else if ip_range.contains('-') {
533+
// Handle start-end format
534+
let parts: Vec<&str> = ip_range.split('-').collect();
535+
if parts.len() == 2 {
536+
let start_ip =
537+
Ipv4Addr::from_str(parts[0]).map_err(|_| "Invalid start IP address.".to_string())?;
538+
let end_ip =
539+
Ipv4Addr::from_str(parts[1]).map_err(|_| "Invalid end IP address.".to_string())?;
540+
541+
let start: u32 = start_ip.into();
542+
let end: u32 = end_ip.into();
543+
544+
if start > end {
545+
return Err("Start IP address is greater than end IP address.".to_string());
546+
}
501547

502-
let start: u32 = start_ip.into();
503-
let end: u32 = end_ip.into();
548+
let ips: Vec<String> = (start..=end)
549+
.map(|ip_num| Ipv4Addr::from(ip_num).to_string())
550+
.collect();
504551

505-
if start > end {
506-
return Err("L'adresse IP de début est supérieure à l'adresse IP de fin.".to_string());
552+
Ok(ips)
553+
} else {
554+
Err("Invalid IP range format. Use 'start-end' format, CIDR notation, or specify a single IP address.".to_string())
507555
}
556+
} else {
557+
// Handle single IP address
558+
let ip = ip_range;
559+
let ip_addr = Ipv4Addr::from_str(ip).map_err(|_| "Invalid IP address.".to_string())?;
560+
Ok(vec![ip_addr.to_string()])
561+
}
562+
}
508563

509-
let max_ips = 1_000_000;
510-
let total_ips = end - start + 1;
511-
512-
if total_ips > max_ips {
513-
return Err(format!(
514-
"La plage d'adresses IP est trop grande ({} adresses). Veuillez spécifier une plage plus petite.",
515-
total_ips
516-
));
517-
}
564+
async fn fetch_ranges_for_asn(asn_code: &str) -> Vec<String> {
565+
let whois_arg = format!("-i origin {}", asn_code);
518566

519-
let ips: Vec<String> = (start..=end)
520-
.map(|ip_num| Ipv4Addr::from(ip_num).to_string())
521-
.collect();
567+
let output = TokioCommand::new("whois")
568+
.arg("-h")
569+
.arg("whois.radb.net")
570+
.arg("--")
571+
.arg(&whois_arg)
572+
.output()
573+
.await;
522574

523-
Ok(ips)
524-
} else {
525-
Err("Format de plage IP invalide. Utilisez le format 'début-fin' ou spécifiez une seule adresse IP.".to_string())
575+
match output {
576+
Ok(output) => {
577+
if output.status.success() {
578+
let data = String::from_utf8_lossy(&output.stdout);
579+
let re = Regex::new(r"(\d{1,3}\.){3}\d{1,3}/\d+").unwrap();
580+
let mut ranges = Vec::new();
581+
for cap in re.captures_iter(&data) {
582+
let range = cap.get(0).unwrap().as_str().to_string();
583+
ranges.push(range);
584+
}
585+
ranges
586+
} else {
587+
eprintln!("WHOIS command failed for ASN {}: {}", asn_code, String::from_utf8_lossy(&output.stderr));
588+
Vec::new()
589+
}
590+
}
591+
Err(e) => {
592+
eprintln!("Failed to execute WHOIS command for ASN {}: {}", asn_code, e);
593+
Vec::new()
594+
}
526595
}
527596
}
528597

0 commit comments

Comments
 (0)