@@ -9,6 +9,7 @@ use std::collections::HashMap;
9
9
use std:: fs:: File ;
10
10
use std:: io:: { BufRead , BufReader } ;
11
11
use std:: net:: { Ipv4Addr , SocketAddr } ;
12
+ use std:: process:: Command ;
12
13
use std:: str:: FromStr ;
13
14
use std:: sync:: Arc ;
14
15
use std:: time:: Duration ;
@@ -21,7 +22,7 @@ use scraper::{Html, Selector};
21
22
#[ tokio:: main]
22
23
async fn main ( ) {
23
24
let matches = App :: new ( "CloudFade" )
24
- . version ( "1.2 " )
25
+ . version ( "1.3 " )
25
26
. author ( "boring" )
26
27
. about ( "Unmask real IP address of a domain hidden behind Cloudflare by IPs bruteforcing" )
27
28
. arg (
@@ -144,7 +145,27 @@ async fn main() {
144
145
return ;
145
146
}
146
147
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 ( ) ;
148
169
149
170
let pb = ProgressBar :: new ( total_tasks as u64 ) ;
150
171
pb. set_style (
@@ -164,10 +185,9 @@ async fn main() {
164
185
vec ! [ "Mozilla/5.0 (compatible; CloudFade/1.0; +https://example.com)" . to_string( ) ]
165
186
} ;
166
187
167
- // Extraction des motifs uniques pour chaque domaine
168
188
let mut target_patterns = HashMap :: new ( ) ;
169
189
170
- for domain in & domains {
190
+ for domain in & valid_domains {
171
191
let target_pattern = match get_target_pattern ( domain, timeout_duration) . await {
172
192
Some ( pattern) => pattern,
173
193
None => {
@@ -188,14 +208,13 @@ async fn main() {
188
208
189
209
let tasks_list: Vec < ( String , String ) > = target_patterns
190
210
. 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 ( ) ) ) )
192
212
. collect ( ) ;
193
213
194
214
let semaphore = Arc :: new ( Semaphore :: new ( max_threads) ) ;
195
215
196
216
let mut tasks = FuturesUnordered :: new ( ) ;
197
217
198
- // Préparation du fichier de sortie
199
218
let output_file = matches. value_of ( "output" ) ;
200
219
let output_mutex = if let Some ( output_path) = output_file {
201
220
Some ( Arc :: new ( tokio:: sync:: Mutex :: new (
@@ -214,7 +233,6 @@ async fn main() {
214
233
let semaphore = semaphore. clone ( ) ;
215
234
let pb = pb. clone ( ) ;
216
235
217
- // Récupérer le motif cible pour ce domaine
218
236
let target_pattern = target_patterns. get ( & domain) . unwrap ( ) . clone ( ) ;
219
237
220
238
let permit = semaphore. clone ( ) . acquire_owned ( ) . await . unwrap ( ) ;
@@ -307,6 +325,30 @@ async fn test_ip(
307
325
None
308
326
}
309
327
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
+
310
352
async fn get_target_pattern ( domain : & str , timeout_duration : Duration ) -> Option < String > {
311
353
let client = reqwest:: Client :: builder ( )
312
354
. danger_accept_invalid_certs ( true )
@@ -318,11 +360,17 @@ async fn get_target_pattern(domain: &str, timeout_duration: Duration) -> Option<
318
360
319
361
let response = timeout ( timeout_duration, request. send ( ) ) . await ;
320
362
if let Ok ( Ok ( resp) ) = response {
363
+ if resp. status ( ) != 200 {
364
+ eprintln ! ( "Domain {} returned status code {}" , domain, resp. status( ) ) ;
365
+ return None ;
366
+ }
321
367
if let Ok ( text) = resp. text ( ) . await {
322
368
if let Some ( title) = extract_title ( & text) {
323
369
return Some ( title) ;
324
370
}
325
371
}
372
+ } else {
373
+ eprintln ! ( "Domain {} is unresponsive" , domain) ;
326
374
}
327
375
None
328
376
}
@@ -382,3 +430,58 @@ fn parse_ip_range(ip_range: &str) -> Result<Vec<String>, String> {
382
430
383
431
Ok ( ips)
384
432
}
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