5 Commits

Author SHA1 Message Date
pyro 82562b0106 Delete Cargo.lock 2026-05-12 18:24:32 +00:00
pyro 6127f4357b Delete directory 'target' 2026-05-12 18:24:15 +00:00
pyro dd6b7668d6 added git ignore 2026-05-12 13:23:33 -05:00
pyro d51d84b88a refactored for rayon and added input file and output file capabilities 2026-05-12 13:17:04 -05:00
pyro d8affb25d6 updated the tool to use rayon and have output capabilities 2026-05-12 13:15:27 -05:00
4 changed files with 356 additions and 1433 deletions
+24
View File
@@ -0,0 +1,24 @@
# Generated by Cargo
# will have compiled files and executables
debug
target
# These are backup files generated by rustfmt
**/*.rs.bk
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb
# Generated by cargo mutants
# Contains mutation testing data
**/mutants.out*/
# rustc will dump stack traces when hitting an internal compiler error to PWD
rustc-ice-*.txt
# RustRover
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
Generated
-1344
View File
File diff suppressed because it is too large Load Diff
+2
View File
@@ -8,4 +8,6 @@ edition = "2021"
[dependencies] [dependencies]
clap = { version = "4.5.4", features = ["derive"] } clap = { version = "4.5.4", features = ["derive"] }
dns-lookup = "2.0.4" dns-lookup = "2.0.4"
rayon = "1.12.0"
reqwest = { version = "0.12.4", features = ["blocking"] } reqwest = { version = "0.12.4", features = ["blocking"] }
trust-dns-resolver = "0.23.2"
+325 -84
View File
@@ -1,21 +1,31 @@
use std::fs;
use reqwest::{self, StatusCode};
use clap::Parser; use clap::Parser;
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
use rayon::ThreadPoolBuilder;
use reqwest::{self, StatusCode};
use std::fs::File;
use std::io::BufWriter;
use std::io::Write;
use std::path::PathBuf;
use std::process::exit;
use std::sync::mpsc::{channel, Sender};
use std::thread; use std::thread;
use dns_lookup::lookup_host; use std::{collections::HashMap, fs};
use trust_dns_resolver::config::*;
use trust_dns_resolver::Resolver;
enum OutputMessage {
UrlResult(String),
SubDomainResult(String),
Shutdown,
}
/// Subdomain Bruteforcer, and directory bruteforcer written in rust, and multi threaded!!! /// Subdomain Bruteforcer, and directory bruteforcer written in rust, and multi threaded!!!
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
#[command(version, about, long_about = None)] #[command(version, about, long_about = None)]
struct Args{ struct Args {
/// target to attack, can be a URL or a dns name, you can specify more than one by comma separating the targets /// target to attack, can be a URL or a dns name, you can specify more than one by comma separating the targets
#[arg(short, long)] #[arg(short, long)]
target: String, target: Option<String>,
///Threads to use, there is not upper limit so if you lock up your comptuer that's on you
#[arg(long)]
threads: usize,
/// wordlist to use for subdomains /// wordlist to use for subdomains
#[arg(short, long, default_value_t = String::from("none"))] #[arg(short, long, default_value_t = String::from("none"))]
@@ -24,42 +34,158 @@ struct Args{
///wordlist to use for directories ///wordlist to use for directories
#[arg(short, long, default_value_t = String::from("none"))] #[arg(short, long, default_value_t = String::from("none"))]
dirwordlist: String, dirwordlist: String,
///output files to write, will create two files yourinput_urls.txt and yourinput_subs.txt
#[arg(short, long)]
outfile: Option<String>,
///input file full of urls formatted to one url per line.
#[arg(short, long)]
urlfile: Option<PathBuf>,
///input file full of domains formatted to one domain per line.
#[arg(short, long)]
domainfile: Option<PathBuf>,
///number of threads to use by default it will use the rayon global pool default.
#[arg(long)]
threads: Option<usize>,
} }
fn try_sub(
fn try_sub(domain: String){ domain: String,
let ips = lookup_host(&domain); wildcard_reses: HashMap<&String, Vec<String>>,
if ips.is_ok(){ tx: Sender<OutputMessage>,
let mut ip_string = String::new(); output: bool,
for ip in ips.unwrap(){ ) {
ip_string = format!("{},{}",ip, ip_string); let mut opts = ResolverOpts::default();
opts.edns0 = true;
let resolver = Resolver::new(ResolverConfig::default(), opts).unwrap();
let mut ips = Vec::new();
if let Ok(ipv4s) = resolver.ipv4_lookup(&domain) {
ipv4s.iter().for_each(|ip| ips.push(ip.0.to_string()));
}
if let Ok(ipv6s) = resolver.ipv6_lookup(&domain) {
ipv6s.iter().for_each(|ip| ips.push(ip.0.to_string()));
}
let mut wild_card = None;
for target in wildcard_reses.keys() {
if domain.contains(&target.to_string()) {
wild_card = wildcard_reses.get(target);
} }
println!("{} {}", domain, ip_string); }
if !ips.is_empty() {
if let Some(wild_ips) = wild_card {
if ips.len() == wild_ips.len() {
let mut wild_only = true;
for ip in &ips {
if !wild_ips.contains(ip) {
wild_only = false;
}
}
if wild_only {
return;
}
}
}
if output {
if let Err(e) = tx.send(OutputMessage::SubDomainResult(format!(
"{}: {}",
&domain,
ips.join(", ")
))) {
eprintln!("error sending output! {}", e);
}
}
println!("{} {}", domain, ips.join(", "));
} }
} }
fn try_dir(url: String, tx: Sender<OutputMessage>, output: bool) {
fn try_dir(url: String){
let resp_stat = reqwest::blocking::get(&url); let resp_stat = reqwest::blocking::get(&url);
if resp_stat.is_ok(){ if resp_stat.is_ok() {
let resp = resp_stat.unwrap().status(); let resp = resp_stat.unwrap().status();
match resp{ match resp {
StatusCode::OK => println!("{} {}",resp, url), StatusCode::OK => {
StatusCode::ACCEPTED => println!("{} {}", resp, url), if output {
StatusCode::CONTINUE => println!("{} {}", resp, url), if let Err(e) = tx.send(OutputMessage::UrlResult(format!("{} {}", resp, url))) {
StatusCode::CREATED => println!("{} {}", resp, url), eprintln!("error sending output! {}", e);
StatusCode::FOUND => println!("{} {}", resp, url), }
StatusCode::IM_A_TEAPOT => println!("And what a beautiful teapot you are {}", url), }
StatusCode::MOVED_PERMANENTLY => println!("{} {}", resp, url), println!("{} {}", resp, url);
StatusCode::PERMANENT_REDIRECT => println!("{} {}", resp, url), }
StatusCode::TEMPORARY_REDIRECT => println!("{} {}", resp, url), StatusCode::ACCEPTED => {
_ => () if output {
if let Err(e) = tx.send(OutputMessage::UrlResult(format!("{} {}", resp, url))) {
eprintln!("error sending output! {}", e);
}
}
println!("{} {}", resp, url);
}
StatusCode::CONTINUE => {
if output {
if let Err(e) = tx.send(OutputMessage::UrlResult(format!("{} {}", resp, url))) {
eprintln!("error sending output! {}", e);
}
}
println!("{} {}", resp, url);
}
StatusCode::CREATED => {
if output {
if let Err(e) = tx.send(OutputMessage::UrlResult(format!("{} {}", resp, url))) {
eprintln!("error sending output! {}", e);
}
}
println!("{} {}", resp, url);
}
StatusCode::FOUND => {
if output {
if let Err(e) = tx.send(OutputMessage::UrlResult(format!("{} {}", resp, url))) {
eprintln!("error sending output! {}", e);
}
}
println!("{} {}", resp, url);
}
StatusCode::IM_A_TEAPOT => {
if output {
if let Err(e) = tx.send(OutputMessage::UrlResult(format!("{} {}", resp, url))) {
eprintln!("error sending output! {}", e);
}
}
println!("And what a beautiful teapot you are {}", url);
}
StatusCode::MOVED_PERMANENTLY => {
if output {
if let Err(e) = tx.send(OutputMessage::UrlResult(format!("{} {}", resp, url))) {
eprintln!("error sending output! {}", e);
}
}
println!("{} {}", resp, url);
}
StatusCode::PERMANENT_REDIRECT => {
if output {
if let Err(e) = tx.send(OutputMessage::UrlResult(format!("{} {}", resp, url))) {
eprintln!("error sending output! {}", e);
}
}
println!("{} {}", resp, url);
}
StatusCode::TEMPORARY_REDIRECT => {
if output {
if let Err(e) = tx.send(OutputMessage::UrlResult(format!("{} {}", resp, url))) {
eprintln!("error sending output! {}", e);
}
}
println!("{} {}", resp, url);
}
_ => (),
} }
} }
} }
fn main() { fn main() {
print!(" print!(
"
@@ -129,74 +255,189 @@ fn main() {
╙█▄▄▄▀▀▀▓▄▄▄▄▄▄▄▓▀▀ ╙█▄▄▄▀▀▀▓▄▄▄▄▄▄▄▓▀▀
"); "
);
let mut dirs_to_try = Vec::new(); let mut dirs_to_try = Vec::new();
let mut subs_to_try = Vec::new(); let mut subs_to_try = Vec::new();
let args = Args::parse(); let args = Args::parse();
let mut targets = Vec::new(); let mut targets = Vec::new();
for target in args.target.split(",").collect::<Vec<&str>>(){ let (tx, rx) = channel();
targets.push(target.to_owned()); if let Some(given_targets) = args.target {
} for target in given_targets.split(",").collect::<Vec<&str>>() {
if args.dirwordlist != String::from("none"){ targets.push(target.to_owned());
let dirwordlist = fs::read_to_string(args.dirwordlist).expect("error reading directory wordlist");
for dir in dirwordlist.split("\n").collect::<Vec<&str>>(){
dirs_to_try.push(dir.trim_end().trim_start().to_owned());
} }
} }
if args.subwordlist != String::from("none"){ if let Some(url_file) = args.urlfile {
let subwordlist = fs::read_to_string(args.subwordlist).expect("error reading subdomain word list"); if let Ok(url_string) = fs::read_to_string(url_file) {
for sub in subwordlist.split("\n").collect::<Vec<&str>>(){ url_string.lines().into_iter().for_each(|url| {
subs_to_try.push(sub.trim_end().trim_start().to_owned()) targets.push(url.to_string());
});
} }
} }
let mut domains_to_try = Vec::new(); if let Some(domain_file) = args.domainfile {
let mut urls_to_try = Vec::new(); if let Ok(domain_string) = fs::read_to_string(domain_file) {
println!("loading targets, and wordlists..."); domain_string.lines().into_iter().for_each(|domain| {
for target in targets{ targets.push(domain.to_string());
if target.contains("/"){ });
for dir in &dirs_to_try{ }
let url = format!("{}/{}", target, dir); }
urls_to_try.push(url.to_owned()); if targets.is_empty() {
println!("no targets provided!");
exit(1);
}
if let Some(thread) = args.threads {
ThreadPoolBuilder::new()
.num_threads(thread)
.build_global()
.unwrap();
}
println!(
"INFO:::{} threads will be used:::",
rayon::current_num_threads()
);
println!("detecting if a wild card record exists...");
let mut opts = ResolverOpts::default();
opts.edns0 = true;
let resolver = Resolver::new(ResolverConfig::default(), opts).unwrap();
let mut wild_card_results = HashMap::new();
for target in &targets {
if !target.contains("http") {
subs_to_try.push(target.clone());
let mut ips = Vec::new();
if let Ok(ipv4s) = resolver.ipv4_lookup(&format!("burstpyrofoo.{}", target)) {
ipv4s.iter().for_each(|ip| {
ips.push(ip.0.to_string());
});
println!("INFO:::wildcard found: {} {}:::", target, ips.join(", "));
} }
} if let Ok(ipv6s) = resolver.ipv6_lookup(&format!("burstpyrofoo.{}", target)) {
else if target.contains(".") == true{ println!("wildcard found!");
for sub in &subs_to_try{ ipv6s.iter().for_each(|ip| {
let domain = format!("{}.{}", sub, target); ips.push(ip.0.to_string());
domains_to_try.push(domain.to_owned()); });
} }
} if !ips.is_empty() {
else{ wild_card_results.insert(target, ips);
println!("{} is not a valid target, please supply either a domain or url", target); }
} else {
dirs_to_try.push(target.clone());
} }
} }
let mut threads = Vec::new(); if args.dirwordlist != String::from("none") {
let urls_len = urls_to_try.len(); let dirwordlist =
let domains_len = domains_to_try.len(); fs::read_to_string(args.dirwordlist).expect("error reading directory wordlist");
println!("DONE!, {} URLS to try, and {} subdomains to try", &urls_len, &domains_len); for dir in dirwordlist.split("\n").collect::<Vec<&str>>() {
if urls_len > 0{ for target in &targets {
let dir_chunk_size = urls_len / &args.threads; if target.contains("http") {
let url_vecs: Vec<Vec<String>> = urls_to_try.chunks(dir_chunk_size).map(|x| x.to_vec()).collect(); dirs_to_try.push(format!("{}/{}", target, dir.trim()));
for urlvec in url_vecs{ }
for url in urlvec{
threads.push(thread::spawn(move || {
try_dir(url)
}));
} }
} }
} }
if domains_len > 0 { if args.subwordlist != String::from("none") {
let domain_chunk_size = domains_len /&args.threads; let subwordlist =
let domain_vecs: Vec<Vec<String>> = domains_to_try.chunks(domain_chunk_size).map(|x| x.to_vec()).collect(); fs::read_to_string(args.subwordlist).expect("error reading subdomain word list");
for domainvec in domain_vecs{ for sub in subwordlist.split("\n").collect::<Vec<&str>>() {
for domain in domainvec{ for target in &targets {
threads.push(thread::spawn(move || { if !target.contains("http") {
try_sub(domain); subs_to_try.push(format!("{}.{}", sub.trim(), target));
})) }
} }
} }
for thread in threads{
let _ = thread.join();
}
} }
println!("done bruteforcing, happy hunting!"); println!("INFO:::loading targets, and wordlists...:::");
let urls_len = dirs_to_try.len();
let domains_len = subs_to_try.len();
println!(
"INFO:::DONE!, {} URLS to try, and {} subdomains to try:::",
&urls_len, &domains_len
);
let mut output = false;
let mut urlpath = PathBuf::new();
let mut subpath = PathBuf::new();
if args.outfile.is_some() {
output = true;
let path = args.outfile.clone().unwrap();
urlpath = PathBuf::from(format!("{}_urls.txt", &path));
subpath = PathBuf::from(format!("{}_subs.txt", &path));
println!(
"INFO:::results will be saved to {}_urls.txt and {}_subs.txt:::",
path, path
);
}
let write_handler = thread::spawn(move || {
if output {
let urlfile = File::create(urlpath).unwrap();
let subfile = File::create(subpath).unwrap();
let mut urlwrite = BufWriter::new(urlfile);
let mut subwrite = BufWriter::new(subfile);
const FLUSHEVERY: usize = 10;
let mut url_counter = 0;
let mut sub_counter = 0;
loop {
let msg = rx.recv().unwrap();
match msg {
OutputMessage::UrlResult(res) => {
if let Err(e) = writeln!(urlwrite, "{}", res) {
println!("error writing url file! {}", e);
} else {
url_counter += 1;
}
}
OutputMessage::SubDomainResult(res) => {
if let Err(e) = writeln!(subwrite, "{}", res) {
println!("error writing sub file! {}", e);
} else {
sub_counter += 1;
}
}
OutputMessage::Shutdown => {
urlwrite.flush().unwrap();
subwrite.flush().unwrap();
break;
}
}
if url_counter == FLUSHEVERY {
if let Err(e) = urlwrite.flush() {
println!("error writing url file! {e}");
}
}
if sub_counter == FLUSHEVERY {
if let Err(e) = subwrite.flush() {
println!("error writing sub file! {e}");
}
}
}
} else {
println!("INFO:::results will be printed to console and not saved...:::");
}
});
rayon::scope(|s| {
if !dirs_to_try.is_empty() {
s.spawn(|_| {
dirs_to_try.par_iter().for_each(|url| {
try_dir(url.to_string(), tx.clone(), output);
});
});
}
if !subs_to_try.is_empty() {
s.spawn(|_| {
subs_to_try.par_iter().for_each(|sub| {
try_sub(
sub.to_string(),
wild_card_results.clone(),
tx.clone(),
output,
);
});
})
}
});
println!("INFO:::enumeration finished!:::");
if output {
println!("INFO:::waiting for output writer to finish...:::");
tx.send(OutputMessage::Shutdown).unwrap();
}
write_handler.join().unwrap();
println!("INFO:::done bruteforcing, happy hunting!:::");
} }