diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..9866420 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "DarkScout" +version = "0.1.0" +authors = ["DarkSuite"] +edition = "2021" +description = "A reliable, nimble, cross-platform subdomain enumerator." +repository = "https://github.com/DarkSuite/DarkScout/" +license = "GPL-3.0-or-later" +keywords = ["discover-subdomains", "ct-logs", "search-subdomains", "enumerate-subdomains", "subdomain-scanner"] +readme = "README.md" +rust-version = "1.58" +resolver = "1" + +[dependencies] +serde = { version = "1.0.152", features = ["derive"] } +serde_derive = "1.0.152" +reqwest = { version = "0.11.14", features = ["blocking", "json", "gzip"] } +rand = "0.8.5" +rayon = "1.6.1" +addr = "0.15.6" +serde_json = "1.0.91" +rusolver = { git = "https://github.com/Edu4rdSHL/rusolver", rev = "cf75cafee7c9d0c257c0b5a361441efc4e247e9c" } +fhc = { git = "https://github.com/Edu4rdSHL/fhc", rev = "c6ea4c6ad810061312f4b380d0ab7d51775950b1" } +tokio = "1.25.0" +crossbeam = "0.8.2" +futures = "0.3.26" +anyhow = "1.0.68" +itertools = "0.10.5" +native-tls = "0.2.11" +clap = { version = "4.1.8", features = ["derive", "env"] } +dotenv = "0.15.0" +indicatif = "0.17.2" diff --git a/README.md b/README.md index 2edd101..79cbfa1 100644 --- a/README.md +++ b/README.md @@ -1 +1,128 @@ -# DarkScout \ No newline at end of file +# DarkScout +darkscout is a simple, nimble subdomain enumeration tool written in Rust language. It is designed to help bug bounty hunters, security professionals and penetration testers discover subdomains of a given target domain. + + + +[![Contributors][contributors-shield]][contributors-url] +[![Forks][forks-shield]][forks-url] +[![Stargazers][stars-shield]][stars-url] +[![Issues][issues-shield]][issues-url] +[![GNU License][license-shield]][license-url] + +Sources: +- Alienvault +- Anubis +- Crtsh +- Hackertarget +- Omnisint (FYI - This site is down often.) +- Threatminer + + +## Usage +```console +$ ./darkscout -t hackthissite.org +``` +```console +$ ./darkscout -t hackthissite.org -o hackthesite.txt +``` + + +## Build +```console +$ git clone https://github.com/DarkSuite/DarkScout +$ cd darkscout +$ cargo build --release +$ cd target/release +$ ./darkscout -t hackthissite.org +``` + + +## Output +```console +$ ./darkscout -t facebook.com +www.m.facebook.com------------step1-----acc---verify.digi-worx.com +cpanel.the--facebook.com +mail.the--facebook.com +the--facebook.com +webdisk.the--facebook.com +webmail.the--facebook.com +www.the--facebook.com +proxygen_verifier.facebook.com +m.facebook.com-----------n.slickgt.com.br +www.m.facebook.com-----------n.slickgt.com.br +m.facebook.com---------terms-of-service.digi-worx.com +www.m.facebook.com---------terms-of-service.digi-worx.com +m.facebook.com----------step1---confirm.sorgu2.com +www.m.facebook.com----------step1---confirm.sorgu2.com +m.facebook.com------login---step1.akuevi.net +www.m.facebook.com------login---step1.akuevi.net +m.facebook.com-----validate---read---new---tos.yudumay.com +www.m.facebook.com-----validate---read---new---tos.yudumay.com +m.facebook.com----securelogin--confirm.wpthm.ir +www.m.facebook.com----securelogin--confirm.wpthm.ir +news--facebook.com +tihonoff@facebook.com +china--facebook.com +www.china--facebook.com +thefacebook.com + +[darkscout]> Successfully scraped 11712 subdomains from facebook.com in 81.238776082s +``` + + +## Roadmap + +* More passive sources for domain reconnaissance +* Builtwith API integration +* HTTP response code checks +* Improved exception handling +* IP validation +* URI parameter parsing +* DB integration via PostgreSQL + +See the [open issues](https://github.com/DarkSuite/DarkScout/issues) for a list of proposed features (and known issues). + + +## Contributing + +Contributions are what make the open source community such an amazing place to be learn, inspire, and create. Any contributions you make are **greatly appreciated**. + +1. Fork the Project +2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`) +3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`) +4. Push to the Branch (`git push origin feature/AmazingFeature`) +5. Open a Pull Request + + +## Issues and requests + +If you have a problem or a feature request, open an [issue](https://github.com/DarkSuite/DarkScout/issues). + + + +## Stargazers over time + +[![Stargazers over time](https://starchart.cc/DarkSuite/DarkScout.svg)](https://starchart.cc/DarkSuite/DarkScout) + + +## Contributors +This project exists thanks to all the people who contribute. [See the contributors list](https://github.com/DarkSuite/DarkScout/graphs/contributors). + + + +[contributors-shield]: https://img.shields.io/github/contributors/DarkSuite/DarkScout.svg?style=for-the-badge +[contributors-url]: https://github.com/DarkSuite/DarkScout/graphs/contributors +[forks-shield]: https://img.shields.io/github/forks/DarkSuite/DarkScout.svg?style=for-the-badge +[forks-url]: https://github.com/DarkSuite/DarkScout/network/members +[stars-shield]: https://img.shields.io/github/stars/DarkSuite/DarkScout.svg?style=for-the-badge +[stars-url]: https://github.com/DarkSuite/DarkScout/stargazers +[issues-shield]: https://img.shields.io/github/issues/DarkSuite/DarkScout.svg?style=for-the-badge +[issues-url]: https://github.com/DarkSuite/DarkScout/issues +[license-shield]: https://img.shields.io/github/license/DarkSuite/DarkScout.svg?style=for-the-badge +[license-url]: https://github.com/DarkSuite/DarkScout/blob/master/LICENSE.txt diff --git a/src/alienvault.rs b/src/alienvault.rs new file mode 100644 index 0000000..37c82c0 --- /dev/null +++ b/src/alienvault.rs @@ -0,0 +1,83 @@ +use crate::structs::{AlientVaultDNS, Subdomain}; + +use std::thread; +use std::time::Duration; + +use indicatif::ProgressBar; +use indicatif::ProgressStyle; + +// Scrape subdomains from alienvault +pub async fn get_alienvault_subdomains( + domain: &str, +) -> Result, Box> { + + println!("[darkscout]> Grabbing domains from AlienVault..."); + println!(); + + let results: AlientVaultDNS = reqwest::get(format!( + "https://otx.alienvault.com/api/v1/indicators/domain/{}/passive_dns", + domain + )) + .await? + .json() + .await?; + + // Generate progress bar + let pb = ProgressBar::new_spinner(); + pb.enable_steady_tick(Duration::from_millis(120)); + pb.set_style( + ProgressStyle::with_template("{spinner:.blue} {msg}") + .unwrap() + .tick_strings(&[ + "▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▸▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▸▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▸▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▸▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▸▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▸▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▸▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▸▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▸▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▸▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▸▹▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▸▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▸▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▹▸▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▹▹▸▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▸▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▸▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▸▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▸▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▸▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▸▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▸▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▸▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▸▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▸", + "▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪", + ]), + ); + + pb.set_message("Scraping Alienvault..."); + thread::sleep(Duration::from_secs(5)); + + // Parse response from AlienVault + let subdomains: Vec = results + .passive_dns + .into_iter() + .filter(|sub| sub.hostname.is_some()) + .map(|sub| Subdomain { + url: sub.hostname.unwrap(), + }) + .collect(); + + // Stop progress bar once task completes + pb.finish_with_message("Done: Alienvault Complete!"); + + //println!("[darkscout]> Finished grabbing domains from AlienVault..."); + + Ok(subdomains) + +} + diff --git a/src/anubis.rs b/src/anubis.rs new file mode 100644 index 0000000..70ad1cf --- /dev/null +++ b/src/anubis.rs @@ -0,0 +1,76 @@ +use crate::structs::Subdomain; + +use std::thread; +use std::time::Duration; + +use indicatif::ProgressBar; +use indicatif::ProgressStyle; + +// Scrapes subdomains from jonlu.ca anubis +pub async fn get_anubis_subdomains( + domain: &str, +) -> Result, Box> { + + println!("[darkscout]> Grabbing domains from Anubis..."); + println!(); + + let results: Vec = + reqwest::get(format!("https://jonlu.ca/anubis/subdomains/{}", domain)) + .await? + .json() + .await?; + + // Generate progress bar + let pb = ProgressBar::new_spinner(); + pb.enable_steady_tick(Duration::from_millis(120)); + pb.set_style( + ProgressStyle::with_template("{spinner:.blue} {msg}") + .unwrap() + .tick_strings(&[ + "▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▸▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▸▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▸▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▸▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▸▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▸▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▸▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▸▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▸▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▸▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▸▹▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▸▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▸▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▹▸▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▹▹▸▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▸▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▸▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▸▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▸▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▸▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▸▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▸▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▸▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▸▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▸", + "▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪", + ]), + ); + + pb.set_message("Scraping Anubis..."); + thread::sleep(Duration::from_secs(5)); + + // Parse response from Anubis + let subdomains: Vec = results + .into_iter() + .map(|sub| Subdomain { url: sub }) + .collect(); + + // Stop progress bar once task completes + pb.finish_with_message("Done: Anubis Complete!"); + + //println!("[darkscout]> Finished grabbing domains from Anubis..."); + + Ok(subdomains) +} + diff --git a/src/crtsh.rs b/src/crtsh.rs new file mode 100644 index 0000000..4789f32 --- /dev/null +++ b/src/crtsh.rs @@ -0,0 +1,85 @@ +use crate::structs::{Certificate, Subdomain}; + +use std::thread; +use std::time::Duration; + +use indicatif::ProgressBar; +use indicatif::ProgressStyle; + +// Scrape subdomains from crt.sh +pub async fn get_crt_domains(domain: &str) -> Result, Box> { + + println!("[darkscout]> Grabbing domains from CrtSH..."); + println!(); + + let certificates: Vec = + reqwest::get(format!("https://crt.sh/?q={}&output=json", domain)) + .await? + .json() + .await?; + + // Generate progress bar + let pb = ProgressBar::new_spinner(); + pb.enable_steady_tick(Duration::from_millis(120)); + pb.set_style( + ProgressStyle::with_template("{spinner:.blue} {msg}") + .unwrap() + .tick_strings(&[ + "▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▸▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▸▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▸▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▸▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▸▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▸▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▸▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▸▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▸▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▸▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▸▹▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▸▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▸▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▹▸▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▹▹▸▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▸▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▸▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▸▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▸▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▸▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▸▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▸▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▸▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▸▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▸", + "▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪", + ]), + ); + + pb.set_message("Scraping CrtSH..."); + thread::sleep(Duration::from_secs(5)); + + // Parse response from Crt.sh + let subdomains: Vec = certificates + .into_iter() + .flat_map(|cert| { + cert.name_value + .split('\n') + .map(|dstr| dstr.trim().to_string()) + .collect::>() + }) + .filter(|dstr: &String| dstr != domain) + .filter(|dstr: &String| !dstr.contains('*')) + .collect(); + + let subdomains: Vec = subdomains + .into_iter() + .map(|dstr| Subdomain { url: dstr }) + .collect(); + + // Stop progress bar once task completes + pb.finish_with_message("Done: Crt.sh Complete!"); + + //println!("[darkscout]> Finished grabbing domains from CrtSH..."); + + Ok(subdomains) +} diff --git a/src/files.rs b/src/files.rs new file mode 100644 index 0000000..340c592 --- /dev/null +++ b/src/files.rs @@ -0,0 +1,58 @@ +use std::fs; +use std::fs::File; +use std::io::prelude::*; +use std::path::{Path, PathBuf}; + +use crate::structs::Subdomain; + +pub fn create_output_dir() -> Result<(), std::io::Error> { + let dir_name = "output"; + match fs::metadata(dir_name) { + Ok(metadata) => { + if metadata.is_dir() { + // Output directory already exists + Ok(()) + } else { + // Output directory is a file, not a directory + Err(std::io::Error::new( + std::io::ErrorKind::AlreadyExists, + "A file with the name 'output' already exists", + )) + } + } + Err(_error) => { + // Output directory doesn't exist, so create it + fs::create_dir(dir_name)?; + Ok(()) + } + } +} + +pub fn create_output_file(output_file: &str, subdomains: &Vec<&Subdomain>) -> std::io::Result<()> { + let dir_name = "output"; + let dir_path = Path::new(dir_name); + if !dir_path.exists() { + fs::create_dir(dir_name)?; + } + + let file_path = PathBuf::from(dir_path).join(output_file); + let mut file_exists = file_path.exists(); + let mut new_file_path = file_path.clone(); + + // Check if the file already exists + while file_exists { + println!("File already exists. Please enter a new filename:"); + let mut input = String::new(); + std::io::stdin().read_line(&mut input)?; + new_file_path = PathBuf::from(dir_path).join(input.trim().to_string() + ".txt"); + file_exists = new_file_path.exists(); + } + + // Create the new file + let mut file = File::create(&new_file_path)?; + for subdomain in subdomains.iter() { + writeln!(file, "{}", subdomain.url)?; + } + + Ok(()) +} diff --git a/src/hackertarget.rs b/src/hackertarget.rs new file mode 100644 index 0000000..8681378 --- /dev/null +++ b/src/hackertarget.rs @@ -0,0 +1,83 @@ +use crate::structs::Subdomain; + +use std::thread; +use std::time::Duration; + +use indicatif::ProgressBar; +use indicatif::ProgressStyle; + +// Gets subdomains from hackertarget.com +pub async fn get_hackertarget_domains( + domain: &str, +) -> Result, Box> { + + println!("[darkscout]> Grabbing domains from HackerTarget..."); + println!(); + + let response = reqwest::get(format!( + "https://api.hackertarget.com/hostsearch/?q={}", + domain + )) + .await? + .text() + .await?; + + // Generate progress bar + let pb = ProgressBar::new_spinner(); + pb.enable_steady_tick(Duration::from_millis(120)); + pb.set_style( + ProgressStyle::with_template("{spinner:.blue} {msg}") + .unwrap() + .tick_strings(&[ + "▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▸▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▸▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▸▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▸▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▸▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▸▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▸▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▸▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▸▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▸▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▸▹▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▸▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▸▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▹▸▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▹▹▸▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▸▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▸▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▸▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▸▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▸▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▸▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▸▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▸▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▸▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▸", + "▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪", + ]), + ); + + pb.set_message("Scraping HackerTarget..."); + thread::sleep(Duration::from_secs(5)); + + // Parse response from HackerTarget + let subdomains: Vec = response + .lines() + .into_iter() + .map(|sub| sub.split(',').collect::>()[0].to_string()) + .collect(); + + let subdomains: Vec = subdomains + .into_iter() + .map(|sub| Subdomain { url: sub }) + .collect(); + + // Stop progress bar once task completes + pb.finish_with_message("Done: HackerTarget Complete!"); + + //println!("[darkscout]> Finished grabbing domains from HackerTarget..."); + + Ok(subdomains) +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..a7015c3 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,148 @@ +use itertools::Itertools; +use clap::Parser; +use dotenv::dotenv; + +mod alienvault; +mod anubis; +mod crtsh; +mod hackertarget; +mod omnisint; +mod threatminer; + +mod structs; +mod utils; +mod files; + +#[derive(Parser, Debug)] +#[command(author, version, about)] + +pub struct Arguments { + + #[arg(short, long, env("TARGET_URL"), default_value = "https://hackthissite.org/")] + pub target_url: String, + + #[arg(short, long, env("OUTPUT_FILE"))] + pub output_file: Option +} + +impl Arguments { + pub fn from_env_and_args() -> Self{ + dotenv().ok(); + Self::parse() + } +} + +/// Prints the opening title of darkscout +fn print_opening() { + let s = format!( + "{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}", + r#" _____ _ _____ _ "#, + r#" | __ \ | | / ____| | | "#, + r#" | | | | __ _ _ __| | _| (___ ___ ___ _ _| |_ "#, + r#" | | | |/ _` | '__| |/ /\___ \ / __/ _ \| | | | __| "#, + r#" | |__| | (_| | | | < ____) | (_| (_) | |_| | |_ "#, + r#" |_____/ \__,_|_| |_|\_\_____/ \___\___/ \__,_|\__| "#, + r#" "#, + r#" A Simple, Modern Subdomain Enumerator Written In Rust "#, + ); + println!("{}", s); + println!(); + let info = format!( + "{}\n{}\n{}\n{}", + r#"*****************************************************"#, + r#"| https://github.com/DarkSuite/DarkScout |"#, + r#"| Welcome To The Dark Side... |"#, + r#"*****************************************************"# + ); + println!("{}", info); +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + + print_opening(); + + let start = std::time::Instant::now(); + + let raw_target = Arguments::from_env_and_args().target_url; + + let cleaned_target = utils::sanitize_target_url_string(raw_target); + + println!("\n[darkscout]> Starting subdomain enumeration...\n"); + println!("[darkscout]> Be patient as this can take a while!\n"); + println!("|***************************************************|\n"); + + let ( + + alienvault, + anubis, + crtsh, + hackertarget, + omnisint, + threatminer + + ) = futures::join!( + + alienvault::get_alienvault_subdomains(&cleaned_target), + anubis::get_anubis_subdomains(&cleaned_target), + crtsh::get_crt_domains(&cleaned_target), + hackertarget::get_hackertarget_domains(&cleaned_target), + omnisint::get_omnisint_subdomains(&cleaned_target), + threatminer::get_threatminer_subdomains(&cleaned_target), + ); + + let duration = start.elapsed(); + + let subdomains: Vec<_> = alienvault + .iter() + .flatten() + .chain(anubis.iter().flatten()) + .chain(crtsh.iter().flatten()) + .chain(hackertarget.iter().flatten()) + .chain(omnisint.iter().flatten()) + .chain(threatminer.iter().flatten()) + .unique_by(|s| &s.url) + .collect(); + + let total = subdomains.len(); + + let sub_clone = subdomains.clone(); + + println!("\n"); + + println!("[darkscout]> Here are your domains...\n"); + println!("|***************************************************|\n"); + + for sub in subdomains.into_iter() { + println!("{}", sub.url); + + } + + if let Some(output_file) = Arguments::from_env_and_args().output_file { + // do something with the output_file argument + + files::create_output_dir()?; + + files::create_output_file(&output_file, &sub_clone)?; + println!("\n[darkscout]> Output successfully writen."); + + println!("\n|***************************************************|"); + + println!( + "\n[darkscout]> Successfully scraped {} subdomains from {} in {:?}", + total, cleaned_target, duration + ); + + } else { + + println!("\n|***************************************************|"); + + println!( + "\n[darkscout]> Successfully scraped {} subdomains from {} in {:?}", + total, cleaned_target, duration + ); + + println!(); +}; + Ok(()) +} diff --git a/src/omnisint.rs b/src/omnisint.rs new file mode 100644 index 0000000..f0a5072 --- /dev/null +++ b/src/omnisint.rs @@ -0,0 +1,75 @@ +use crate::structs::Subdomain; + +use std::thread; +use std::time::Duration; + +use indicatif::ProgressBar; +use indicatif::ProgressStyle; + +// get subdomains from omnisint.io +pub async fn get_omnisint_subdomains( + domain: &str, +) -> Result, Box> { + + println!("[darkscout]> Grabbing domains from OmnisInt..."); + println!(); + + let response: Vec = + reqwest::get(format!("https://sonar.omnisint.io/subdomains/{}", domain)) + .await? + .json() + .await?; + + // Generate progress bar + let pb = ProgressBar::new_spinner(); + pb.enable_steady_tick(Duration::from_millis(120)); + pb.set_style( + ProgressStyle::with_template("{spinner:.blue} {msg}") + .unwrap() + .tick_strings(&[ + "▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▸▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▸▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▸▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▸▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▸▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▸▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▸▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▸▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▸▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▸▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▸▹▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▸▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▸▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▹▸▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▹▹▸▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▸▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▸▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▸▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▸▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▸▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▸▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▸▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▸▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▸▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▸", + "▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪", + ]), + ); + + pb.set_message("Scraping OmnisInt..."); + thread::sleep(Duration::from_secs(5)); + + // Parse response from OmnisInt + let subdomains: Vec = response + .into_iter() + .map(|sub| Subdomain { url: sub }) + .collect(); + + // Stop progress bar once task completes + pb.finish_with_message("Done: Omnisint Complete!"); + + //println!("[darkscout]> Finished grabbing domains from OmnisInt..."); + + Ok(subdomains) +} diff --git a/src/structs.rs b/src/structs.rs new file mode 100644 index 0000000..959005d --- /dev/null +++ b/src/structs.rs @@ -0,0 +1,39 @@ +extern crate serde; +use serde::Deserialize; + +#[derive(Debug, Deserialize)] +pub struct Certificate { + pub issuer_ca_id: u32, + pub issuer_name: String, + pub common_name: String, + pub name_value: String, + pub id: u64, + pub serial_number: String, +} + +#[derive(Debug, Deserialize)] +pub struct DNSEntry { + pub address: Option, + pub hostname: Option, + pub record_type: Option, + pub asset_type: Option, + pub asn: Option, +} + +#[derive(Debug, Deserialize)] +pub struct AlientVaultDNS { + pub passive_dns: Vec, + pub count: u64, +} + +#[derive(Debug, Deserialize)] +pub struct ThreatminerResults { + pub status_code: String, + pub status_message: String, + pub results: Vec, +} + +#[derive(Debug)] +pub struct Subdomain { + pub url: String, +} diff --git a/src/threatminer.rs b/src/threatminer.rs new file mode 100644 index 0000000..7571e2a --- /dev/null +++ b/src/threatminer.rs @@ -0,0 +1,78 @@ +use crate::structs::{Subdomain, ThreatminerResults}; + +use std::thread; +use std::time::Duration; + +use indicatif::ProgressBar; +use indicatif::ProgressStyle; + +// get subdomains from threatminer +pub async fn get_threatminer_subdomains( + domain: &str, +) -> Result, Box> { + + println!("[darkscout]> Grabbing domains from ThreatMiner..."); + println!(); + + let response: ThreatminerResults = reqwest::get(format!( + "https://api.threatminer.org/v2/domain.php?q={}&rt=5", + domain + )) + .await? + .json() + .await?; + + // Generate progress bar + let pb = ProgressBar::new_spinner(); + pb.enable_steady_tick(Duration::from_millis(120)); + pb.set_style( + ProgressStyle::with_template("{spinner:.blue} {msg}") + .unwrap() + .tick_strings(&[ + "▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▸▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▸▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▸▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▸▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▸▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▸▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▸▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▸▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▸▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▸▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▸▹▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▸▹▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▸▹▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▹▸▹▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▹▹▸▹▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▸▹▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▸▹▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▸▹▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▸▹▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▸▹▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▸▹▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▸▹▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▸▹▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▸▹", + "▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▹▸", + "▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪", + ]), + ); + + pb.set_message("Scraping ThreatMiner..."); + thread::sleep(Duration::from_secs(5)); + + // Parse response from AlienVault + let subdomains: Vec = response + .results + .into_iter() + .map(|sub| Subdomain { url: sub }) + .collect(); + + // Stop progress bar once task completes + pb.finish_with_message("Done: ThreatMiner Complete!"); + + //println!("[darkscout]> Finished grabbing domains from ThreatMiner..."); + + Ok(subdomains) +} diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..9754eea --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,9 @@ +// Remove beginning protocols from the target url +pub fn sanitize_target_url_string(raw_target: String) -> String { + raw_target + .replace("www.", "") + .replace("https://", "") + .replace("http://", "") + .replace("/", "") + .replace("https://www.", "") +} \ No newline at end of file