added functionality to use SSL key as client identity for mutual authentication

This commit is contained in:
Himadri Bhattacharjee
2023-05-02 12:43:45 +05:30
parent 3b8c6f6ba9
commit 4198a019d3
11 changed files with 213 additions and 29 deletions

106
Cargo.lock generated
View File

@@ -1292,6 +1292,19 @@ dependencies = [
"want",
]
[[package]]
name = "hyper-rustls"
version = "0.23.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1788965e61b367cd03a62950836d5cd41560c3577d90e40e0819373194d1661c"
dependencies = [
"http",
"hyper",
"rustls",
"tokio",
"tokio-rustls",
]
[[package]]
name = "hyper-tls"
version = "0.5.0"
@@ -2264,6 +2277,7 @@ dependencies = [
"http",
"http-body",
"hyper",
"hyper-rustls",
"hyper-tls",
"ipnet",
"js-sys",
@@ -2273,20 +2287,39 @@ dependencies = [
"once_cell",
"percent-encoding",
"pin-project-lite",
"rustls",
"rustls-pemfile",
"serde",
"serde_json",
"serde_urlencoded",
"tokio",
"tokio-native-tls",
"tokio-rustls",
"tokio-socks",
"tower-service",
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
"webpki-roots",
"winreg",
]
[[package]]
name = "ring"
version = "0.16.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"
dependencies = [
"cc",
"libc",
"once_cell",
"spin",
"untrusted",
"web-sys",
"winapi",
]
[[package]]
name = "rlimit"
version = "0.9.1"
@@ -2319,6 +2352,27 @@ dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "rustls"
version = "0.20.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f"
dependencies = [
"log",
"ring",
"sct",
"webpki",
]
[[package]]
name = "rustls-pemfile"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b"
dependencies = [
"base64 0.21.0",
]
[[package]]
name = "rustversion"
version = "1.0.12"
@@ -2363,6 +2417,16 @@ dependencies = [
"tendril",
]
[[package]]
name = "sct"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4"
dependencies = [
"ring",
"untrusted",
]
[[package]]
name = "seahash"
version = "4.1.0"
@@ -2607,6 +2671,12 @@ dependencies = [
"winapi",
]
[[package]]
name = "spin"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
[[package]]
name = "stable_deref_trait"
version = "1.2.0"
@@ -2844,6 +2914,17 @@ dependencies = [
"tokio",
]
[[package]]
name = "tokio-rustls"
version = "0.23.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59"
dependencies = [
"rustls",
"tokio",
"webpki",
]
[[package]]
name = "tokio-socks"
version = "0.5.1"
@@ -3008,6 +3089,12 @@ version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
[[package]]
name = "untrusted"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
[[package]]
name = "url"
version = "2.3.1"
@@ -3182,6 +3269,25 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "webpki"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd"
dependencies = [
"ring",
"untrusted",
]
[[package]]
name = "webpki-roots"
version = "0.22.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87"
dependencies = [
"webpki",
]
[[package]]
name = "winapi"
version = "0.3.9"

View File

@@ -35,7 +35,7 @@ tokio = { version = "1.26", features = ["full"] }
tokio-util = { version = "0.7", features = ["codec"] }
log = "0.4"
env_logger = "0.10"
reqwest = { version = "0.11", features = ["socks"] }
reqwest = { version = "0.11", features = ["socks", "rustls-tls"] }
# uses feature unification to add 'serde' to reqwest::Url
url = { version = "2.2", features = ["serde"] }
serde_regex = "1.1"

View File

@@ -53,7 +53,8 @@ _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: ' \
'--certificate=[Add a custom root certificate to connect to servers with a self-signed certificate]:PEM/DER:_files' \
'--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 key file for mutual authentication]: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: ' \

View File

@@ -59,7 +59,8 @@ 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('--certificate', 'certificate', [CompletionResultType]::ParameterName, 'Add a custom root certificate to connect to servers with a self-signed certificate')
[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 key file for mutual authentication')
[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)')

View File

@@ -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 --certificate --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-cert --client-cert --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,11 @@ _feroxbuster() {
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
--certificate)
--server-cert)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
--client-cert)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;

View File

@@ -56,7 +56,8 @@ 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 --certificate 'Add a custom root certificate to connect to servers with a self-signed certificate'
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 key file for mutual authentication'
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)'

View File

@@ -58,8 +58,11 @@ pub struct Banner {
/// represents Configuration.proxy
proxy: BannerEntry,
/// represents Configuration.certificate
certificate: BannerEntry,
/// represents Configuration.client_cert
client_cert: BannerEntry,
/// represents Configuration.server_cert
server_cert: BannerEntry,
/// represents Configuration.replay_proxy
replay_proxy: BannerEntry,
@@ -325,7 +328,8 @@ 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 certificate = BannerEntry::new("🏅", "Certificate", &config.certificate);
let server_cert = BannerEntry::new("🏅", "Server Certificate", &config.server_cert);
let client_cert = BannerEntry::new("🏅", "Client Certificate", &config.client_cert);
let threads = BannerEntry::new("🚀", "Threads", &config.threads.to_string());
let wordlist = BannerEntry::new("📖", "Wordlist", &config.wordlist);
let timeout = BannerEntry::new("💥", "Timeout (secs)", &config.timeout.to_string());
@@ -405,7 +409,8 @@ impl Banner {
auto_bail,
auto_tune,
proxy,
certificate,
client_cert,
server_cert,
replay_codes,
replay_proxy,
headers,
@@ -560,8 +565,12 @@ by Ben "epi" Risher {} ver: {}"#,
writeln!(&mut writer, "{}", self.proxy)?;
}
if !config.certificate.is_empty() {
writeln!(&mut writer, "{}", self.certificate)?;
if !config.client_cert.is_empty() {
writeln!(&mut writer, "{}", self.client_cert)?;
}
if !config.server_cert.is_empty() {
writeln!(&mut writer, "{}", self.server_cert)?;
}
if !config.replay_proxy.is_empty() {

View File

@@ -16,7 +16,8 @@ pub fn initialize(
insecure: bool,
headers: &HashMap<String, String>,
proxy: Option<&str>,
certificate: Option<&str>,
server_cert: Option<&str>,
client_cert: Option<&str>,
) -> Result<Client> {
let policy = if redirects {
Policy::limited(10)
@@ -44,7 +45,7 @@ pub fn initialize(
}
}
if let Some(cert_path) = certificate {
if let Some(cert_path) = server_cert {
let cert_path = Path::new(cert_path);
let mut buf = Vec::new();
@@ -73,6 +74,32 @@ pub fn initialize(
}
}
if let Some(cert_path) = client_cert {
let cert_path = Path::new(cert_path);
let mut buf = Vec::new();
// if the root certificate path is not empty, open it
// and read it into a buffer
File::open(cert_path)?.read_to_end(&mut buf)?;
// depending upon the extension of the file, create a
// certificate object from it using either the "pem" or "der" parser
// in either case, add the root certificate to the client
if let Some(extension) = cert_path.extension() {
match extension.to_str() {
Some("pem") => {
let cert = reqwest::tls::Identity::from_pem(&buf)?;
client = client.identity(cert);
}
// if we cannot determine the extension, do nothing
// or perhaps TODO: spew an error
_ => {}
}
}
}
Ok(client.build()?)
}
@@ -93,6 +120,7 @@ mod tests {
&headers,
Some("not a valid proxy"),
None,
None,
)
.unwrap();
}
@@ -102,6 +130,6 @@ mod tests {
fn client_with_good_proxy() {
let headers = HashMap::new();
let proxy = "http://127.0.0.1:8080";
initialize(0, "stuff", true, true, &headers, Some(proxy), None).unwrap();
initialize(0, "stuff", true, true, &headers, Some(proxy), None, None).unwrap();
}
}

View File

@@ -106,7 +106,11 @@ pub struct Configuration {
/// Path to a custom root certificate for connecting to servers with a self-signed certificate
#[serde(default)]
pub certificate: String,
pub server_cert: String,
/// Path to a custom client SSL key for mutual authentication with a server
#[serde(default)]
pub client_cert: String,
/// The target URL
#[serde(default)]
@@ -336,6 +340,7 @@ impl Default for Configuration {
&HashMap::new(),
None,
None,
None,
)
.expect("Could not build client");
let replay_client = None;
@@ -381,7 +386,8 @@ impl Default for Configuration {
force_recursion: false,
update_app: false,
proxy: String::new(),
certificate: String::new(),
server_cert: String::new(),
client_cert: String::new(),
config: String::new(),
output: String::new(),
debug_log: String::new(),
@@ -845,7 +851,8 @@ 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.certificate, args, "certificate", 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.replay_proxy, args, "replay_proxy", String);
update_config_if_present!(&mut config.user_agent, args, "user_agent", String);
update_config_with_num_type_if_present!(&mut config.timeout, args, "timeout", u64);
@@ -933,10 +940,16 @@ impl Configuration {
Some(configuration.proxy.as_str())
};
let certificate = if configuration.certificate.is_empty() {
let server_cert = if configuration.server_cert.is_empty() {
None
} else {
Some(configuration.certificate.as_str())
Some(configuration.server_cert.as_str())
};
let client_cert = if configuration.client_cert.is_empty() {
None
} else {
Some(configuration.client_cert.as_str())
};
if proxy.is_some()
@@ -946,7 +959,8 @@ impl Configuration {
|| configuration.insecure
|| !configuration.headers.is_empty()
|| configuration.resumed
|| certificate.is_some()
|| server_cert.is_some()
|| client_cert.is_some()
{
configuration.client = client::initialize(
configuration.timeout,
@@ -955,7 +969,8 @@ impl Configuration {
configuration.insecure,
&configuration.headers,
proxy,
certificate,
server_cert,
client_cert,
)
.expect("Could not rebuild client");
}
@@ -970,7 +985,8 @@ impl Configuration {
configuration.insecure,
&configuration.headers,
Some(&configuration.replay_proxy),
certificate,
server_cert,
client_cert,
)
.expect("Could not rebuild client"),
);
@@ -1005,7 +1021,8 @@ 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.certificate, new.certificate, "");
update_if_not_default!(&mut conf.server_cert, new.server_cert, "");
update_if_not_default!(&mut conf.client_cert, new.client_cert, "");
update_if_not_default!(&mut conf.verbosity, new.verbosity, 0);
update_if_not_default!(&mut conf.silent, new.silent, false);
update_if_not_default!(&mut conf.quiet, new.quiet, false);

View File

@@ -641,10 +641,16 @@ impl<'a> Extractor<'a> {
Some(self.handles.config.proxy.as_str())
};
let certificate = if self.handles.config.certificate.is_empty() {
let server_cert = if self.handles.config.server_cert.is_empty() {
None
} else {
Some(self.handles.config.certificate.as_str())
Some(self.handles.config.server_cert.as_str())
};
let client_cert = if self.handles.config.client_cert.is_empty() {
None
} else {
Some(self.handles.config.client_cert.as_str())
};
client = client::initialize(
@@ -654,7 +660,8 @@ impl<'a> Extractor<'a> {
self.handles.config.insecure,
&self.handles.config.headers,
proxy,
certificate,
server_cert,
client_cert,
)?;
}

View File

@@ -392,8 +392,8 @@ pub fn initialize() -> Command {
.help("Disables TLS certificate validation in the client"),
)
.arg(
Arg::new("certificate")
.long("certificate")
Arg::new("server_cert")
.long("server-cert")
.value_name("PEM/DER")
.value_hint(ValueHint::FilePath)
.num_args(1)
@@ -401,6 +401,16 @@ pub fn initialize() -> Command {
.help(
"Add a custom root certificate to connect to servers with a self-signed certificate",
),
).arg(
Arg::new("client_cert")
.long("client-cert")
.value_name("PEM")
.value_hint(ValueHint::FilePath)
.num_args(1)
.help_heading("Client settings")
.help(
"Use a custom client SSL key file for mutual authentication",
),
);
/////////////////////////////////////////////////////////////////////