diff --git a/shell_completions/_feroxbuster b/shell_completions/_feroxbuster index 8563354..517f16d 100644 --- a/shell_completions/_feroxbuster +++ b/shell_completions/_feroxbuster @@ -53,9 +53,9 @@ _feroxbuster() { '*--status-codes=[Status Codes to include (allow list) (default\: All Status Codes)]:STATUS_CODE: ' \ '-T+[Number of seconds before a client'\''s request times out (default\: 7)]:SECONDS: ' \ '--timeout=[Number of seconds before a client'\''s request times out (default\: 7)]:SECONDS: ' \ -'--server-cert=[Add a custom root certificate to connect to servers with a self-signed certificate]:PEM/DER:_files' \ -'--client-cert=[Use a custom client SSL certificate for mutual authentication]:PEM:_files' \ -'--client-key=[Use a custom client SSL key file for mutual authentication]:PEM:_files' \ +'--server-certs=[Add custom root certificate(s) for servers with unknown certificates]:PEM|DER:_files' \ +'--client-cert=[Add a PEM encoded certificate for mutual authentication (mTLS)]:PEM:_files' \ +'--client-key=[Add a PEM encoded private key for mutual authentication (mTLS)]:PEM:_files' \ '-t+[Number of concurrent threads (default\: 50)]:THREADS: ' \ '--threads=[Number of concurrent threads (default\: 50)]:THREADS: ' \ '-d+[Maximum recursion depth, a depth of 0 is infinite recursion (default\: 4)]:RECURSION_DEPTH: ' \ diff --git a/shell_completions/_feroxbuster.ps1 b/shell_completions/_feroxbuster.ps1 index b20d71a..eb46c7e 100644 --- a/shell_completions/_feroxbuster.ps1 +++ b/shell_completions/_feroxbuster.ps1 @@ -59,9 +59,9 @@ Register-ArgumentCompleter -Native -CommandName 'feroxbuster' -ScriptBlock { [CompletionResult]::new('--status-codes', 'status-codes', [CompletionResultType]::ParameterName, 'Status Codes to include (allow list) (default: All Status Codes)') [CompletionResult]::new('-T', 'T', [CompletionResultType]::ParameterName, 'Number of seconds before a client''s request times out (default: 7)') [CompletionResult]::new('--timeout', 'timeout', [CompletionResultType]::ParameterName, 'Number of seconds before a client''s request times out (default: 7)') - [CompletionResult]::new('--server-cert', 'server-cert', [CompletionResultType]::ParameterName, 'Add a custom root certificate to connect to servers with a self-signed certificate') - [CompletionResult]::new('--client-cert', 'client-cert', [CompletionResultType]::ParameterName, 'Use a custom client SSL certificate for mutual authentication') - [CompletionResult]::new('--client-key', 'client-key', [CompletionResultType]::ParameterName, 'Use a custom client SSL key file for mutual authentication') + [CompletionResult]::new('--server-certs', 'server-certs', [CompletionResultType]::ParameterName, 'Add custom root certificate(s) for servers with unknown certificates') + [CompletionResult]::new('--client-cert', 'client-cert', [CompletionResultType]::ParameterName, 'Add a PEM encoded certificate for mutual authentication (mTLS)') + [CompletionResult]::new('--client-key', 'client-key', [CompletionResultType]::ParameterName, 'Add a PEM encoded private key for mutual authentication (mTLS)') [CompletionResult]::new('-t', 't', [CompletionResultType]::ParameterName, 'Number of concurrent threads (default: 50)') [CompletionResult]::new('--threads', 'threads', [CompletionResultType]::ParameterName, 'Number of concurrent threads (default: 50)') [CompletionResult]::new('-d', 'd', [CompletionResultType]::ParameterName, 'Maximum recursion depth, a depth of 0 is infinite recursion (default: 4)') diff --git a/shell_completions/feroxbuster.bash b/shell_completions/feroxbuster.bash index 185a1dc..2df3ed9 100644 --- a/shell_completions/feroxbuster.bash +++ b/shell_completions/feroxbuster.bash @@ -19,7 +19,7 @@ _feroxbuster() { case "${cmd}" in feroxbuster) - opts="-u -p -P -R -a -A -x -m -H -b -Q -f -S -X -W -N -C -s -T -r -k -t -n -d -e -L -w -D -E -B -g -I -v -q -o -U -h -V --url --stdin --resume-from --burp --burp-replay --smart --thorough --proxy --replay-proxy --replay-codes --user-agent --random-agent --extensions --methods --data --headers --cookies --query --add-slash --dont-scan --filter-size --filter-regex --filter-words --filter-lines --filter-status --filter-similar-to --status-codes --timeout --redirects --insecure --server-cert --client-cert --client-key --threads --no-recursion --depth --force-recursion --extract-links --dont-extract-links --scan-limit --parallel --rate-limit --time-limit --wordlist --auto-tune --auto-bail --dont-filter --collect-extensions --collect-backups --collect-words --dont-collect --verbosity --silent --quiet --json --output --debug-log --no-state --update --help --version" + opts="-u -p -P -R -a -A -x -m -H -b -Q -f -S -X -W -N -C -s -T -r -k -t -n -d -e -L -w -D -E -B -g -I -v -q -o -U -h -V --url --stdin --resume-from --burp --burp-replay --smart --thorough --proxy --replay-proxy --replay-codes --user-agent --random-agent --extensions --methods --data --headers --cookies --query --add-slash --dont-scan --filter-size --filter-regex --filter-words --filter-lines --filter-status --filter-similar-to --status-codes --timeout --redirects --insecure --server-certs --client-cert --client-key --threads --no-recursion --depth --force-recursion --extract-links --dont-extract-links --scan-limit --parallel --rate-limit --time-limit --wordlist --auto-tune --auto-bail --dont-filter --collect-extensions --collect-backups --collect-words --dont-collect --verbosity --silent --quiet --json --output --debug-log --no-state --update --help --version" if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 @@ -177,7 +177,7 @@ _feroxbuster() { COMPREPLY=($(compgen -f "${cur}")) return 0 ;; - --server-cert) + --server-certs) COMPREPLY=($(compgen -f "${cur}")) return 0 ;; diff --git a/shell_completions/feroxbuster.elv b/shell_completions/feroxbuster.elv index 7fafc2c..fc36a6f 100644 --- a/shell_completions/feroxbuster.elv +++ b/shell_completions/feroxbuster.elv @@ -56,9 +56,9 @@ set edit:completion:arg-completer[feroxbuster] = {|@words| cand --status-codes 'Status Codes to include (allow list) (default: All Status Codes)' cand -T 'Number of seconds before a client''s request times out (default: 7)' cand --timeout 'Number of seconds before a client''s request times out (default: 7)' - cand --server-cert 'Add a custom root certificate to connect to servers with a self-signed certificate' - cand --client-cert 'Use a custom client SSL certificate for mutual authentication' - cand --client-key 'Use a custom client SSL key file for mutual authentication' + cand --server-certs 'Add custom root certificate(s) for servers with unknown certificates' + cand --client-cert 'Add a PEM encoded certificate for mutual authentication (mTLS)' + cand --client-key 'Add a PEM encoded private key for mutual authentication (mTLS)' cand -t 'Number of concurrent threads (default: 50)' cand --threads 'Number of concurrent threads (default: 50)' cand -d 'Maximum recursion depth, a depth of 0 is infinite recursion (default: 4)' diff --git a/src/banner/container.rs b/src/banner/container.rs index 13cbccf..344b58c 100644 --- a/src/banner/container.rs +++ b/src/banner/container.rs @@ -65,7 +65,7 @@ pub struct Banner { client_cert: BannerEntry, /// represents Configuration.server_cert - server_cert: BannerEntry, + server_certs: BannerEntry, /// represents Configuration.replay_proxy replay_proxy: BannerEntry, @@ -331,7 +331,7 @@ impl Banner { let auto_bail = BannerEntry::new("🙅", "Auto Bail", &config.auto_bail.to_string()); let cfg = BannerEntry::new("💉", "Config File", &config.config); let proxy = BannerEntry::new("💎", "Proxy", &config.proxy); - let server_cert = BannerEntry::new("🏅", "Server Certificate", &config.server_cert); + let server_certs = BannerEntry::new("🏅", "Server Certificates", &format!("[{}]", config.server_certs.join(", "))); let client_cert = BannerEntry::new("🏅", "Client Certificate", &config.client_cert); let client_key = BannerEntry::new("🔑", "Client Key", &config.client_key); let threads = BannerEntry::new("🚀", "Threads", &config.threads.to_string()); @@ -415,7 +415,7 @@ impl Banner { proxy, client_cert, client_key, - server_cert, + server_certs, replay_codes, replay_proxy, headers, @@ -578,8 +578,8 @@ by Ben "epi" Risher {} ver: {}"#, writeln!(&mut writer, "{}", self.client_key)?; } - if !config.server_cert.is_empty() { - writeln!(&mut writer, "{}", self.server_cert)?; + if !config.server_certs.is_empty() { + writeln!(&mut writer, "{}", self.server_certs)?; } if !config.replay_proxy.is_empty() { diff --git a/src/client.rs b/src/client.rs index c06e34e..31fd74c 100644 --- a/src/client.rs +++ b/src/client.rs @@ -9,17 +9,21 @@ use std::time::Duration; /// Create and return an instance of [reqwest::Client](https://docs.rs/reqwest/latest/reqwest/struct.Client.html) /// For now, silence clippy for this one #[allow(clippy::too_many_arguments)] -pub fn initialize( +pub fn initialize( timeout: u64, user_agent: &str, redirects: bool, insecure: bool, headers: &HashMap, proxy: Option<&str>, - server_cert: Option<&str>, + server_certs: Option, client_cert: Option<&str>, client_key: Option<&str>, -) -> Result { +) -> Result +where + I: IntoIterator, + I::Item: AsRef, +{ let policy = if redirects { Policy::limited(10) } else { @@ -46,33 +50,35 @@ pub fn initialize( } } - if let Some(cert_path) = server_cert { - let cert_path = Path::new(cert_path); + if let Some(cert_paths) = server_certs { + for cert_path in cert_paths { + let cert_path = Path::new(&cert_path); - // if the root certificate path is not empty, open it - // and read it into a buffer + // if the root certificate path is not empty, open it + // and read it into a buffer - let buf = std::fs::read(cert_path)?; - let cert = match cert_path - .extension() - .map(|s| s.to_str().unwrap_or_default()) - { - // depending upon the extension of the file, create a - // certificate object from it using either the "pem" or "der" parser - Some("pem") => reqwest::Certificate::from_pem(&buf)?, - Some("der") => reqwest::Certificate::from_der(&buf)?, + let buf = std::fs::read(cert_path)?; + let cert = match cert_path + .extension() + .map(|s| s.to_str().unwrap_or_default()) + { + // depending upon the extension of the file, create a + // certificate object from it using either the "pem" or "der" parser + Some("pem") => reqwest::Certificate::from_pem(&buf)?, + Some("der") => reqwest::Certificate::from_der(&buf)?, - // if we cannot determine the extension, do nothing - _ => { - log::warn!( - "unable to determine extension: assuming PEM format for root certificate" - ); - reqwest::Certificate::from_pem(&buf)? - } - }; + // if we cannot determine the extension, do nothing + _ => { + log::warn!( + "unable to determine extension: assuming PEM format for root certificate" + ); + reqwest::Certificate::from_pem(&buf)? + } + }; - // in either case, add the root certificate to the client - client = client.add_root_certificate(cert); + // in either case, add the root certificate to the client + client = client.add_root_certificate(cert); + } } if let (Some(cert_path), Some(key_path)) = (client_cert, client_key) { diff --git a/src/config/container.rs b/src/config/container.rs index 4f4847a..38beb9f 100644 --- a/src/config/container.rs +++ b/src/config/container.rs @@ -106,9 +106,9 @@ pub struct Configuration { /// Path to a custom root certificate for connecting to servers with a self-signed certificate #[serde(default)] - pub server_cert: String, + pub server_certs: Vec, - /// Path to a client's PEM encoded X509 certificate(s) used during mutual authentication + /// Path to a client's PEM encoded X509 certificate used during mutual authentication #[serde(default)] pub client_cert: String, @@ -336,7 +336,7 @@ impl Default for Configuration { fn default() -> Self { let timeout = timeout(); let user_agent = user_agent(); - let client = client::initialize( + let client = client::initialize::>( timeout, &user_agent, false, @@ -391,7 +391,6 @@ impl Default for Configuration { force_recursion: false, update_app: false, proxy: String::new(), - server_cert: String::new(), client_cert: String::new(), client_key: String::new(), config: String::new(), @@ -401,6 +400,7 @@ impl Default for Configuration { time_limit: String::new(), resume_from: String::new(), replay_proxy: String::new(), + server_certs: Vec::new(), queries: Vec::new(), extensions: Vec::new(), methods: methods(), @@ -857,7 +857,6 @@ impl Configuration { // organizational breakpoint; all options below alter the Client configuration //// update_config_if_present!(&mut config.proxy, args, "proxy", String); - update_config_if_present!(&mut config.server_cert, args, "server_cert", String); update_config_if_present!(&mut config.client_cert, args, "client_cert", String); update_config_if_present!(&mut config.client_key, args, "client_key", String); update_config_if_present!(&mut config.replay_proxy, args, "replay_proxy", String); @@ -930,6 +929,12 @@ impl Configuration { } } + if let Some(certs) = args.get_many::("server_certs") { + for val in certs { + config.server_certs.push(val.to_string()); + } + } + config } @@ -947,10 +952,10 @@ impl Configuration { Some(configuration.proxy.as_str()) }; - let server_cert = if configuration.server_cert.is_empty() { + let server_certs = if configuration.server_certs.is_empty() { None } else { - Some(configuration.server_cert.as_str()) + Some(&configuration.server_certs) }; let client_cert = if configuration.client_cert.is_empty() { @@ -972,7 +977,7 @@ impl Configuration { || configuration.insecure || !configuration.headers.is_empty() || configuration.resumed - || server_cert.is_some() + || server_certs.is_some() || client_cert.is_some() || client_key.is_some() { @@ -983,7 +988,7 @@ impl Configuration { configuration.insecure, &configuration.headers, proxy, - server_cert, + server_certs, client_cert, client_key, ) @@ -1000,7 +1005,7 @@ impl Configuration { configuration.insecure, &configuration.headers, Some(&configuration.replay_proxy), - server_cert, + server_certs, client_cert, client_key, ) @@ -1037,7 +1042,11 @@ impl Configuration { update_if_not_default!(&mut conf.target_url, new.target_url, ""); update_if_not_default!(&mut conf.time_limit, new.time_limit, ""); update_if_not_default!(&mut conf.proxy, new.proxy, ""); - update_if_not_default!(&mut conf.server_cert, new.server_cert, ""); + update_if_not_default!( + &mut conf.server_certs, + new.server_certs, + Vec::::new() + ); update_if_not_default!(&mut conf.client_cert, new.client_cert, ""); update_if_not_default!(&mut conf.client_key, new.client_key, ""); update_if_not_default!(&mut conf.verbosity, new.verbosity, 0); diff --git a/src/extractor/container.rs b/src/extractor/container.rs index 1cb80b7..13a4221 100644 --- a/src/extractor/container.rs +++ b/src/extractor/container.rs @@ -641,10 +641,10 @@ impl<'a> Extractor<'a> { Some(self.handles.config.proxy.as_str()) }; - let server_cert = if self.handles.config.server_cert.is_empty() { + let server_certs = if self.handles.config.server_certs.is_empty() { None } else { - Some(self.handles.config.server_cert.as_str()) + Some(&self.handles.config.server_certs) }; let client_cert = if self.handles.config.client_cert.is_empty() { @@ -666,7 +666,7 @@ impl<'a> Extractor<'a> { self.handles.config.insecure, &self.handles.config.headers, proxy, - server_cert, + server_certs, client_cert, client_key, )?; diff --git a/src/parser.rs b/src/parser.rs index fc6aadc..24cfef5 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -392,35 +392,33 @@ pub fn initialize() -> Command { .help("Disables TLS certificate validation in the client"), ) .arg( - Arg::new("server_cert") - .long("server-cert") - .value_name("PEM/DER") + Arg::new("server_certs") + .long("server-certs") + .value_name("PEM|DER") .value_hint(ValueHint::FilePath) - .num_args(1) + .num_args(1..) .help_heading("Client settings") - .help( - "Add a custom root certificate to connect to servers with a self-signed certificate", - ), - ).arg( + .help("Add custom root certificate(s) for servers with unknown certificates"), + ) + .arg( Arg::new("client_cert") .long("client-cert") .value_name("PEM") .value_hint(ValueHint::FilePath) .num_args(1) + .requires("client_key") .help_heading("Client settings") - .help( - "Use a custom client SSL certificate for mutual authentication", - ), - ).arg( + .help("Add a PEM encoded certificate for mutual authentication (mTLS)"), + ) + .arg( Arg::new("client_key") .long("client-key") .value_name("PEM") .value_hint(ValueHint::FilePath) .num_args(1) + .requires("client_cert") .help_heading("Client settings") - .help( - "Use a custom client SSL key file for mutual authentication", - ), + .help("Add a PEM encoded private key for mutual authentication (mTLS)"), ); /////////////////////////////////////////////////////////////////////