From 3d24415c9a72c387250e6846113f8e3792a858fe Mon Sep 17 00:00:00 2001 From: Edu4rdSHL Date: Sun, 9 May 2021 02:13:21 -0500 Subject: [PATCH] Allow ports ranges, lists, or both using the --ports option. Update the code to match the new dependencies versions. Simplify some functions. Improve IP discovery speed calling ipv4_lookup() to search only for A records. Signed-off-by: Edu4rdSHL --- src/args.rs | 5 ++- src/cli.yml | 12 ++----- src/main.rs | 2 +- src/networking.rs | 43 ++++++---------------- src/nmap.rs | 82 +++++++++++++++++------------------------- src/resolver_engine.rs | 56 ++++++++++++----------------- src/structs.rs | 5 ++- 7 files changed, 74 insertions(+), 131 deletions(-) diff --git a/src/args.rs b/src/args.rs index fd02905..a16a6e3 100644 --- a/src/args.rs +++ b/src/args.rs @@ -50,8 +50,7 @@ pub fn get_args() -> Args { value_t!(matches, "threads", usize).unwrap_or_else(|_| 50) }, version: clap::crate_version!().to_string(), - initial_port: value_t!(matches, "initial-port", usize).unwrap_or_else(|_| 1), - last_port: value_t!(matches, "last-port", usize).unwrap_or_else(|_| 65535), + ports: value_t!(matches, "ports", String).unwrap_or_else(|_| String::new()), with_output: matches.is_present("output") || matches.is_present("unique-output"), unique_output_flag: matches.is_present("unique-output"), from_file_flag: matches.is_present("files"), @@ -61,7 +60,7 @@ pub fn get_args() -> Args { fast_scan: matches.is_present("fast-scan"), keep_nmap_logs: matches.is_present("keep-nmap-logs"), files: return_matches_vec(&matches, "files"), - min_rate: value_t!(matches, "min-rate", usize).unwrap_or_else(|_| 30000), + min_rate: value_t!(matches, "min-rate", String).unwrap_or_else(|_| String::new()), resolvers: if matches.is_present("custom-resolvers") { return_matches_vec(&matches, "custom-resolvers") } else { diff --git a/src/cli.yml b/src/cli.yml index b375b48..17f50e5 100644 --- a/src/cli.yml +++ b/src/cli.yml @@ -55,15 +55,9 @@ args: takes_value: true multiple: true - - initial-port: - help: Initial port to scan. Default 0. - long: iport - takes_value: true - multiple: false - - - last-port: - help: Last port to scan. Default 1000. - long: lport + - ports: + help: 'Ports to scan. You can specify a range of ports, a list, or both. Put them inside double quotes, for example: "22, 80, 443, 1000-5000"' + long: ports takes_value: true multiple: false diff --git a/src/main.rs b/src/main.rs index b0af3f8..49b95f7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -28,7 +28,7 @@ fn run() -> Result<()> { .unwrap(); if !arguments.target.is_empty() || !arguments.files.is_empty() { - resolver_engine::async_resolver_all(&mut arguments) + resolver_engine::parallel_resolver_all(&mut arguments) } else { error!("Error: Target is empty or invalid!\n"); std::process::exit(1) diff --git a/src/networking.rs b/src/networking.rs index 1527fc2..9828dc5 100644 --- a/src/networking.rs +++ b/src/networking.rs @@ -1,44 +1,19 @@ use { log::error, - rand::Rng, + rand::{seq::SliceRandom, thread_rng as rng}, std::net::{IpAddr, Ipv4Addr}, trust_dns_resolver::{ config::{NameServerConfigGroup, ResolverConfig, ResolverOpts}, - proto::rr::RecordType, Resolver, }, }; -pub fn get_records(resolver: &Resolver, domain: &str, record_type: RecordType) -> String { - if let Ok(rdata) = resolver.lookup(&domain, record_type) { - let mut record_data: Vec = Vec::new(); - if record_type == RecordType::AAAA { - record_data = rdata - .iter() - .filter_map(|rdata| rdata.as_aaaa()) - .map(|ipv6| ipv6.to_string()) - .collect(); - } else if record_type == RecordType::A { - record_data = rdata - .iter() - .filter_map(|rdata| rdata.as_a()) - .map(|ipv4| ipv4.to_string()) - .collect(); - } else if record_type == RecordType::CNAME { - record_data = rdata - .iter() - .filter_map(|rdata| rdata.as_cname()) - .map(|name| { - let name = name.to_string(); - name[..name.len() - 1].to_owned() - }) - .collect(); - } - record_data - .iter() +pub fn get_records(resolver: &Resolver, domain: &str) -> String { + if let Ok(ips) = resolver.ipv4_lookup(domain) { + ips.iter() + .map(|x| x.to_string()) .next() - .expect("Failed retrieving records data.") - .to_owned() + .expect("Failed to get IPV4.") } else { String::new() } @@ -51,9 +26,13 @@ pub fn get_resolver(resolvers_ips: &[Ipv4Addr], opts: &ResolverOpts) -> Resolver vec![], NameServerConfigGroup::from_ips_clear( &[IpAddr::V4( - resolvers_ips[rand::thread_rng().gen_range(0, resolvers_ips.len())], + resolvers_ips + .choose(&mut rng()) + .expect("failed to read ipv4 string") + .to_owned(), )], 53, + false, ), ), *opts, diff --git a/src/nmap.rs b/src/nmap.rs index 12596ec..5352ad7 100644 --- a/src/nmap.rs +++ b/src/nmap.rs @@ -111,65 +111,47 @@ pub struct Service { pub fn get_nmap_data( filename: &str, host: &str, - min_rate: usize, - initial_port: usize, - last_port: usize, + min_rate: &str, + ports: &str, fast_scan: bool, ) -> Result { - let ports_range = format!("{}-{}", initial_port, last_port); let min_rate = min_rate.to_string(); - let nmap_args = if fast_scan { - vec![ - "nmap", - "-n", - "--dns-servers", - &NMAP_DNS_RESOLVERS, - "-Pn", - "--host-timeout", - "5m", - "--min-rate", - &min_rate, - "-sS", - "--open", - "-dd", - "-T4", - "--max-retries", - "2", - "-oX", - filename, - host, - ] - } else { - vec![ - "nmap", - "-n", - "--dns-servers", - &NMAP_DNS_RESOLVERS, - "-Pn", - "--host-timeout", - "10m", - "-sV", - "--min-rate", - &min_rate, - "-sS", - "-p", - &ports_range, - "--open", - "-dd", - "-T4", - "--max-retries", - "2", - "-oX", - filename, - host, - ] - }; + let mut nmap_args = vec![ + "nmap", + "--dns-servers", + &NMAP_DNS_RESOLVERS, + "-Pn", + "-sS", + "--open", + "-dd", + "-T4", + "--max-retries", + "3", + "-oX", + filename, + ]; + + if !min_rate.is_empty() { + nmap_args.append(&mut vec!["--min-rate", &min_rate]) + } + + if fast_scan { + nmap_args.append(&mut vec!["--host-timeout", "20m"]) + } + + if !ports.is_empty() { + nmap_args.append(&mut vec!["-p", &ports]) + } + + nmap_args.push(host); + match Command::new("nmap").args(&nmap_args).output() { Ok(_) => { if Path::new(&filename).exists() && Path::new(&filename).is_file() { serde_xml_rs::from_str(&std::fs::read_to_string(filename).unwrap_or_default()) } else { error!("Error executing nmap, possible causes: Nmap is not installed or you need root/administrator permissions. Leaving.\n"); + println!(); std::process::exit(1) } } diff --git a/src/resolver_engine.rs b/src/resolver_engine.rs index 86a55c9..c4cb4ad 100644 --- a/src/resolver_engine.rs +++ b/src/resolver_engine.rs @@ -15,7 +15,7 @@ use { net::Ipv4Addr, time::Duration, }, - trust_dns_resolver::{config::ResolverOpts, proto::rr::RecordType}, + trust_dns_resolver::config::ResolverOpts, }; lazy_static! { @@ -47,17 +47,17 @@ lazy_static! { }; } -pub fn async_resolver_all(args: &mut Args) -> Result<()> { +pub fn parallel_resolver_all(args: &mut Args) -> Result<()> { files::check_full_path(&args.logs_dir); if !args.quiet_flag { info!( - "Performing asynchronous resolution for {} targets with {} threads, it will take a while...\n", + "Performing parallel resolution for {} targets with {} threads, it will take a while...\n", args.targets.len(), args.threads ) } - let data = async_resolver_engine(&args, args.targets.clone()); + let data = parallel_resolver_engine(&args, args.targets.clone()); let mut table = Table::new(); table.set_titles(row![ @@ -111,47 +111,44 @@ pub fn async_resolver_all(args: &mut Args) -> Result<()> { } } - if args.with_output && !args.targets.is_empty() { - if files::table_to_file(&table, files::return_output_file(&args)).is_err() - && !args.quiet_flag - { - error!( - "An error occurred while writing the output file {}.\n", - args.file_name - ) - } + if args.with_output + && !args.targets.is_empty() + && files::table_to_file(&table, files::return_output_file(&args)).is_err() + && !args.quiet_flag + { + error!( + "An error occurred while writing the output file {}.\n", + args.file_name + ) } if !args.quiet_flag { table.printstd(); } - if ((args.with_output && !args.unique_output_flag) || args.unique_output_flag) - && !args.quiet_flag - { + if (args.with_output || args.unique_output_flag) && !args.quiet_flag { info!( "Job finished in {} seconds.\n", args.time_wasted.elapsed().as_secs() ); info!("Logfile saved in {}\n\n", args.file_name); } - + println!(); Ok(()) } -fn async_resolver_engine(args: &Args, targets: HashSet) -> HashMap { - let mut opts = ResolverOpts::default(); - opts.timeout = Duration::from_secs(2); +fn parallel_resolver_engine(args: &Args, targets: HashSet) -> HashMap { + let opts = ResolverOpts { + timeout: Duration::from_secs(2), + ..Default::default() + }; let resolv_data: HashMap = targets .par_iter() .map(|target| { let fqdn_target = format!("{}.", target); let mut resolv_data = ResolvData::default(); - resolv_data.ip = networking::get_records( - &networking::get_resolver(&RESOLVERS, &opts), - &fqdn_target, - RecordType::A, - ); + resolv_data.ip = + networking::get_records(&networking::get_resolver(&RESOLVERS, &opts), &fqdn_target); (target.to_owned(), resolv_data) }) .collect(); @@ -167,14 +164,7 @@ fn async_resolver_engine(args: &Args, targets: HashSet) -> HashMap { nmap_data .host diff --git a/src/structs.rs b/src/structs.rs index feba6ff..6407654 100644 --- a/src/structs.rs +++ b/src/structs.rs @@ -10,11 +10,10 @@ pub struct Args { pub version: String, pub logs_dir: String, pub threads: usize, - pub initial_port: usize, - pub last_port: usize, + pub ports: String, pub with_output: bool, pub unique_output_flag: bool, - pub min_rate: usize, + pub min_rate: String, pub from_file_flag: bool, pub quiet_flag: bool, pub custom_resolvers: bool,