mirror of
https://github.com/epi052/feroxbuster.git
synced 2026-04-19 14:51:12 -03:00
907 dont skip dir listings (#1192)
* bumped version to 2.11.0 * updated deps * new cli options * added --request-file, --protocol, --scan-dir-listings * added tests / clippy * removed errant module definition * implemented visible bar limiter * many fixes; feature implemented i believe * added banner test for limit-bars * beginning troubleshooting of recursion panic * put a bandaid on trace-level logging bug * clippy
This commit is contained in:
1101
Cargo.lock
generated
1101
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
14
Cargo.toml
14
Cargo.toml
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "feroxbuster"
|
||||
version = "2.10.4"
|
||||
version = "2.11.0"
|
||||
authors = ["Ben 'epi' Risher (@epi052)"]
|
||||
license = "MIT"
|
||||
edition = "2021"
|
||||
@@ -25,13 +25,13 @@ maintenance = { status = "actively-developed" }
|
||||
clap = { version = "4.5", features = ["wrap_help", "cargo"] }
|
||||
clap_complete = "4.5"
|
||||
regex = "1.10"
|
||||
lazy_static = "1.4"
|
||||
lazy_static = "1.5"
|
||||
dirs = "5.0"
|
||||
|
||||
[dependencies]
|
||||
scraper = "0.19"
|
||||
futures = "0.3"
|
||||
tokio = { version = "1.38", features = ["full"] }
|
||||
tokio = { version = "1.39", features = ["full"] }
|
||||
tokio-util = { version = "0.7", features = ["codec"] }
|
||||
log = "0.4"
|
||||
env_logger = "0.11"
|
||||
@@ -40,13 +40,11 @@ reqwest = { version = "0.12", features = ["socks", "native-tls-alpn"] }
|
||||
url = { version = "2.5", features = ["serde"] }
|
||||
serde_regex = "1.1"
|
||||
clap = { version = "4.5", features = ["wrap_help", "cargo"] }
|
||||
lazy_static = "1.4"
|
||||
lazy_static = "1.5"
|
||||
toml = "0.8"
|
||||
serde = { version = "1.0", features = ["derive", "rc"] }
|
||||
serde_json = "1.0"
|
||||
uuid = { version = "1.8", features = ["v4"] }
|
||||
# last known working version of indicatif; 0.17.5 has a bug that causes the
|
||||
# scan menu to fail spectacularly
|
||||
uuid = { version = "1.10", features = ["v4"] }
|
||||
indicatif = { version = "0.17.8" }
|
||||
console = "0.15"
|
||||
openssl = { version = "0.10", features = ["vendored"] }
|
||||
@@ -69,7 +67,7 @@ self_update = { version = "0.40", features = [
|
||||
] }
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.10"
|
||||
tempfile = "3.12"
|
||||
httpmock = "0.7"
|
||||
assert_cmd = "2.0"
|
||||
predicates = "3.1"
|
||||
|
||||
@@ -14,7 +14,7 @@ rm ferox-*.state
|
||||
# dependency management
|
||||
[tasks.upgrade-deps]
|
||||
command = "cargo"
|
||||
args = ["upgrade", "--exclude", "indicatif, self_update"]
|
||||
args = ["upgrade", "--exclude", "self_update"]
|
||||
|
||||
[tasks.update]
|
||||
command = "cargo"
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
# dont_filter = true
|
||||
# extract_links = true
|
||||
# depth = 1
|
||||
# limit_bars = 3
|
||||
# force_recursion = true
|
||||
# filter_size = [5174]
|
||||
# filter_regex = ["^ignore me$"]
|
||||
@@ -57,6 +58,9 @@
|
||||
# server_certs = ["/some/cert.pem", "/some/other/cert.pem"]
|
||||
# client_cert = "/some/client/cert.pem"
|
||||
# client_key = "/some/client/key.pem"
|
||||
# request_file = "/some/raw/request/file"
|
||||
# protocol = "http"
|
||||
# scan_dir_listings = true
|
||||
|
||||
# headers can be specified on multiple lines or as an inline table
|
||||
#
|
||||
|
||||
@@ -15,17 +15,18 @@ _feroxbuster() {
|
||||
|
||||
local context curcontext="$curcontext" state line
|
||||
_arguments "${_arguments_options[@]}" : \
|
||||
'-u+[The target URL (required, unless \[--stdin || --resume-from\] used)]:URL:_urls' \
|
||||
'--url=[The target URL (required, unless \[--stdin || --resume-from\] used)]:URL:_urls' \
|
||||
'-u+[The target URL (required, unless \[--stdin || --resume-from || --request-file\] used)]:URL:_urls' \
|
||||
'--url=[The target URL (required, unless \[--stdin || --resume-from || --request-file\] used)]:URL:_urls' \
|
||||
'(-u --url)--resume-from=[State file from which to resume a partially complete scan (ex. --resume-from ferox-1606586780.state)]:STATE_FILE:_files' \
|
||||
'(-u --url)--request-file=[Raw HTTP request file to use as a template for all requests]:REQUEST_FILE:_files' \
|
||||
'-p+[Proxy to use for requests (ex\: http(s)\://host\:port, socks5(h)\://host\:port)]:PROXY:_urls' \
|
||||
'--proxy=[Proxy to use for requests (ex\: http(s)\://host\:port, socks5(h)\://host\:port)]:PROXY:_urls' \
|
||||
'-P+[Send only unfiltered requests through a Replay Proxy, instead of all requests]:REPLAY_PROXY:_urls' \
|
||||
'--replay-proxy=[Send only unfiltered requests through a Replay Proxy, instead of all requests]:REPLAY_PROXY:_urls' \
|
||||
'*-R+[Status Codes to send through a Replay Proxy when found (default\: --status-codes value)]:REPLAY_CODE: ' \
|
||||
'*--replay-codes=[Status Codes to send through a Replay Proxy when found (default\: --status-codes value)]:REPLAY_CODE: ' \
|
||||
'-a+[Sets the User-Agent (default\: feroxbuster/2.10.4)]:USER_AGENT: ' \
|
||||
'--user-agent=[Sets the User-Agent (default\: feroxbuster/2.10.4)]:USER_AGENT: ' \
|
||||
'-a+[Sets the User-Agent (default\: feroxbuster/2.11.0)]:USER_AGENT: ' \
|
||||
'--user-agent=[Sets the User-Agent (default\: feroxbuster/2.11.0)]:USER_AGENT: ' \
|
||||
'*-x+[File extension(s) to search for (ex\: -x php -x pdf js); reads values (newline-separated) from file if input starts with an @ (ex\: @ext.txt)]:FILE_EXTENSION: ' \
|
||||
'*--extensions=[File extension(s) to search for (ex\: -x php -x pdf js); reads values (newline-separated) from file if input starts with an @ (ex\: @ext.txt)]:FILE_EXTENSION: ' \
|
||||
'*-m+[Which HTTP request method(s) should be sent (default\: GET)]:HTTP_METHODS: ' \
|
||||
@@ -37,6 +38,7 @@ _feroxbuster() {
|
||||
'*--cookies=[Specify HTTP cookies to be used in each request (ex\: -b stuff=things)]:COOKIE: ' \
|
||||
'*-Q+[Request'\''s URL query parameters (ex\: -Q token=stuff -Q secret=key)]:QUERY: ' \
|
||||
'*--query=[Request'\''s URL query parameters (ex\: -Q token=stuff -Q secret=key)]:QUERY: ' \
|
||||
'--protocol=[Specify the protocol to use when targeting via --request-file or --url with domain only (default\: https)]:PROTOCOL: ' \
|
||||
'*--dont-scan=[URL(s) or Regex Pattern(s) to exclude from recursion/scans]:URL: ' \
|
||||
'*-S+[Filter out messages of a particular size (ex\: -S 5120 -S 4927,1970)]:SIZE: ' \
|
||||
'*--filter-size=[Filter out messages of a particular size (ex\: -S 5120 -S 4927,1970)]:SIZE: ' \
|
||||
@@ -74,11 +76,12 @@ _feroxbuster() {
|
||||
'-o+[Output file to write results to (use w/ --json for JSON entries)]:FILE:_files' \
|
||||
'--output=[Output file to write results to (use w/ --json for JSON entries)]:FILE:_files' \
|
||||
'--debug-log=[Output file to write log entries (use w/ --json for JSON entries)]:FILE:_files' \
|
||||
'--limit-bars=[Number of directory scan bars to show at any given time (default\: no limit)]:NUM_BARS_TO_SHOW: ' \
|
||||
'(-u --url)--stdin[Read url(s) from STDIN]' \
|
||||
'(-p --proxy -k --insecure --burp-replay)--burp[Set --proxy to http\://127.0.0.1\:8080 and set --insecure to true]' \
|
||||
'(-P --replay-proxy -k --insecure)--burp-replay[Set --replay-proxy to http\://127.0.0.1\:8080 and set --insecure to true]' \
|
||||
'(--rate-limit --auto-bail)--smart[Set --auto-tune, --collect-words, and --collect-backups to true]' \
|
||||
'(--rate-limit --auto-bail)--thorough[Use the same settings as --smart and set --collect-extensions to true]' \
|
||||
'(--rate-limit --auto-bail)--thorough[Use the same settings as --smart and set --collect-extensions and --scan-dir-listings to true]' \
|
||||
'-A[Use a random User-Agent]' \
|
||||
'--random-agent[Use a random User-Agent]' \
|
||||
'-f[Append / to each request'\''s URL]' \
|
||||
@@ -101,6 +104,7 @@ _feroxbuster() {
|
||||
'--collect-extensions[Automatically discover extensions and add them to --extensions (unless they'\''re in --dont-collect)]' \
|
||||
'-g[Automatically discover important words from within responses and add them to the wordlist]' \
|
||||
'--collect-words[Automatically discover important words from within responses and add them to the wordlist]' \
|
||||
'--scan-dir-listings[Force scans to recurse into directory listings]' \
|
||||
'(--silent)*-v[Increase verbosity level (use -vv or more for greater effect. \[CAUTION\] 4 -v'\''s is probably too much)]' \
|
||||
'(--silent)*--verbosity[Increase verbosity level (use -vv or more for greater effect. \[CAUTION\] 4 -v'\''s is probably too much)]' \
|
||||
'(-q --quiet)--silent[Only print URLs (or JSON w/ --json) + turn off logging (good for piping a list of urls to other commands)]' \
|
||||
|
||||
@@ -21,105 +21,109 @@ Register-ArgumentCompleter -Native -CommandName 'feroxbuster' -ScriptBlock {
|
||||
|
||||
$completions = @(switch ($command) {
|
||||
'feroxbuster' {
|
||||
[CompletionResult]::new('-u', 'u', [CompletionResultType]::ParameterName, 'The target URL (required, unless [--stdin || --resume-from] used)')
|
||||
[CompletionResult]::new('--url', 'url', [CompletionResultType]::ParameterName, 'The target URL (required, unless [--stdin || --resume-from] used)')
|
||||
[CompletionResult]::new('--resume-from', 'resume-from', [CompletionResultType]::ParameterName, 'State file from which to resume a partially complete scan (ex. --resume-from ferox-1606586780.state)')
|
||||
[CompletionResult]::new('-p', 'p', [CompletionResultType]::ParameterName, 'Proxy to use for requests (ex: http(s)://host:port, socks5(h)://host:port)')
|
||||
[CompletionResult]::new('--proxy', 'proxy', [CompletionResultType]::ParameterName, 'Proxy to use for requests (ex: http(s)://host:port, socks5(h)://host:port)')
|
||||
[CompletionResult]::new('-P', 'P ', [CompletionResultType]::ParameterName, 'Send only unfiltered requests through a Replay Proxy, instead of all requests')
|
||||
[CompletionResult]::new('--replay-proxy', 'replay-proxy', [CompletionResultType]::ParameterName, 'Send only unfiltered requests through a Replay Proxy, instead of all requests')
|
||||
[CompletionResult]::new('-R', 'R ', [CompletionResultType]::ParameterName, 'Status Codes to send through a Replay Proxy when found (default: --status-codes value)')
|
||||
[CompletionResult]::new('--replay-codes', 'replay-codes', [CompletionResultType]::ParameterName, 'Status Codes to send through a Replay Proxy when found (default: --status-codes value)')
|
||||
[CompletionResult]::new('-a', 'a', [CompletionResultType]::ParameterName, 'Sets the User-Agent (default: feroxbuster/2.10.4)')
|
||||
[CompletionResult]::new('--user-agent', 'user-agent', [CompletionResultType]::ParameterName, 'Sets the User-Agent (default: feroxbuster/2.10.4)')
|
||||
[CompletionResult]::new('-x', 'x', [CompletionResultType]::ParameterName, 'File extension(s) to search for (ex: -x php -x pdf js); reads values (newline-separated) from file if input starts with an @ (ex: @ext.txt)')
|
||||
[CompletionResult]::new('--extensions', 'extensions', [CompletionResultType]::ParameterName, 'File extension(s) to search for (ex: -x php -x pdf js); reads values (newline-separated) from file if input starts with an @ (ex: @ext.txt)')
|
||||
[CompletionResult]::new('-m', 'm', [CompletionResultType]::ParameterName, 'Which HTTP request method(s) should be sent (default: GET)')
|
||||
[CompletionResult]::new('--methods', 'methods', [CompletionResultType]::ParameterName, 'Which HTTP request method(s) should be sent (default: GET)')
|
||||
[CompletionResult]::new('--data', 'data', [CompletionResultType]::ParameterName, 'Request''s Body; can read data from a file if input starts with an @ (ex: @post.bin)')
|
||||
[CompletionResult]::new('-H', 'H ', [CompletionResultType]::ParameterName, 'Specify HTTP headers to be used in each request (ex: -H Header:val -H ''stuff: things'')')
|
||||
[CompletionResult]::new('--headers', 'headers', [CompletionResultType]::ParameterName, 'Specify HTTP headers to be used in each request (ex: -H Header:val -H ''stuff: things'')')
|
||||
[CompletionResult]::new('-b', 'b', [CompletionResultType]::ParameterName, 'Specify HTTP cookies to be used in each request (ex: -b stuff=things)')
|
||||
[CompletionResult]::new('--cookies', 'cookies', [CompletionResultType]::ParameterName, 'Specify HTTP cookies to be used in each request (ex: -b stuff=things)')
|
||||
[CompletionResult]::new('-Q', 'Q ', [CompletionResultType]::ParameterName, 'Request''s URL query parameters (ex: -Q token=stuff -Q secret=key)')
|
||||
[CompletionResult]::new('--query', 'query', [CompletionResultType]::ParameterName, 'Request''s URL query parameters (ex: -Q token=stuff -Q secret=key)')
|
||||
[CompletionResult]::new('--dont-scan', 'dont-scan', [CompletionResultType]::ParameterName, 'URL(s) or Regex Pattern(s) to exclude from recursion/scans')
|
||||
[CompletionResult]::new('-S', 'S ', [CompletionResultType]::ParameterName, 'Filter out messages of a particular size (ex: -S 5120 -S 4927,1970)')
|
||||
[CompletionResult]::new('--filter-size', 'filter-size', [CompletionResultType]::ParameterName, 'Filter out messages of a particular size (ex: -S 5120 -S 4927,1970)')
|
||||
[CompletionResult]::new('-X', 'X ', [CompletionResultType]::ParameterName, 'Filter out messages via regular expression matching on the response''s body/headers (ex: -X ''^ignore me$'')')
|
||||
[CompletionResult]::new('--filter-regex', 'filter-regex', [CompletionResultType]::ParameterName, 'Filter out messages via regular expression matching on the response''s body/headers (ex: -X ''^ignore me$'')')
|
||||
[CompletionResult]::new('-W', 'W ', [CompletionResultType]::ParameterName, 'Filter out messages of a particular word count (ex: -W 312 -W 91,82)')
|
||||
[CompletionResult]::new('--filter-words', 'filter-words', [CompletionResultType]::ParameterName, 'Filter out messages of a particular word count (ex: -W 312 -W 91,82)')
|
||||
[CompletionResult]::new('-N', 'N ', [CompletionResultType]::ParameterName, 'Filter out messages of a particular line count (ex: -N 20 -N 31,30)')
|
||||
[CompletionResult]::new('--filter-lines', 'filter-lines', [CompletionResultType]::ParameterName, 'Filter out messages of a particular line count (ex: -N 20 -N 31,30)')
|
||||
[CompletionResult]::new('-C', 'C ', [CompletionResultType]::ParameterName, 'Filter out status codes (deny list) (ex: -C 200 -C 401)')
|
||||
[CompletionResult]::new('--filter-status', 'filter-status', [CompletionResultType]::ParameterName, 'Filter out status codes (deny list) (ex: -C 200 -C 401)')
|
||||
[CompletionResult]::new('--filter-similar-to', 'filter-similar-to', [CompletionResultType]::ParameterName, 'Filter out pages that are similar to the given page (ex. --filter-similar-to http://site.xyz/soft404)')
|
||||
[CompletionResult]::new('-s', 's', [CompletionResultType]::ParameterName, 'Status Codes to include (allow list) (default: All Status Codes)')
|
||||
[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-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)')
|
||||
[CompletionResult]::new('--depth', 'depth', [CompletionResultType]::ParameterName, 'Maximum recursion depth, a depth of 0 is infinite recursion (default: 4)')
|
||||
[CompletionResult]::new('-L', 'L ', [CompletionResultType]::ParameterName, 'Limit total number of concurrent scans (default: 0, i.e. no limit)')
|
||||
[CompletionResult]::new('--scan-limit', 'scan-limit', [CompletionResultType]::ParameterName, 'Limit total number of concurrent scans (default: 0, i.e. no limit)')
|
||||
[CompletionResult]::new('--parallel', 'parallel', [CompletionResultType]::ParameterName, 'Run parallel feroxbuster instances (one child process per url passed via stdin)')
|
||||
[CompletionResult]::new('--rate-limit', 'rate-limit', [CompletionResultType]::ParameterName, 'Limit number of requests per second (per directory) (default: 0, i.e. no limit)')
|
||||
[CompletionResult]::new('--time-limit', 'time-limit', [CompletionResultType]::ParameterName, 'Limit total run time of all scans (ex: --time-limit 10m)')
|
||||
[CompletionResult]::new('-w', 'w', [CompletionResultType]::ParameterName, 'Path or URL of the wordlist')
|
||||
[CompletionResult]::new('--wordlist', 'wordlist', [CompletionResultType]::ParameterName, 'Path or URL of the wordlist')
|
||||
[CompletionResult]::new('-B', 'B ', [CompletionResultType]::ParameterName, 'Automatically request likely backup extensions for "found" urls (default: ~, .bak, .bak2, .old, .1)')
|
||||
[CompletionResult]::new('--collect-backups', 'collect-backups', [CompletionResultType]::ParameterName, 'Automatically request likely backup extensions for "found" urls (default: ~, .bak, .bak2, .old, .1)')
|
||||
[CompletionResult]::new('-I', 'I ', [CompletionResultType]::ParameterName, 'File extension(s) to Ignore while collecting extensions (only used with --collect-extensions)')
|
||||
[CompletionResult]::new('--dont-collect', 'dont-collect', [CompletionResultType]::ParameterName, 'File extension(s) to Ignore while collecting extensions (only used with --collect-extensions)')
|
||||
[CompletionResult]::new('-o', 'o', [CompletionResultType]::ParameterName, 'Output file to write results to (use w/ --json for JSON entries)')
|
||||
[CompletionResult]::new('--output', 'output', [CompletionResultType]::ParameterName, 'Output file to write results to (use w/ --json for JSON entries)')
|
||||
[CompletionResult]::new('--debug-log', 'debug-log', [CompletionResultType]::ParameterName, 'Output file to write log entries (use w/ --json for JSON entries)')
|
||||
[CompletionResult]::new('--stdin', 'stdin', [CompletionResultType]::ParameterName, 'Read url(s) from STDIN')
|
||||
[CompletionResult]::new('--burp', 'burp', [CompletionResultType]::ParameterName, 'Set --proxy to http://127.0.0.1:8080 and set --insecure to true')
|
||||
[CompletionResult]::new('--burp-replay', 'burp-replay', [CompletionResultType]::ParameterName, 'Set --replay-proxy to http://127.0.0.1:8080 and set --insecure to true')
|
||||
[CompletionResult]::new('--smart', 'smart', [CompletionResultType]::ParameterName, 'Set --auto-tune, --collect-words, and --collect-backups to true')
|
||||
[CompletionResult]::new('--thorough', 'thorough', [CompletionResultType]::ParameterName, 'Use the same settings as --smart and set --collect-extensions to true')
|
||||
[CompletionResult]::new('-A', 'A ', [CompletionResultType]::ParameterName, 'Use a random User-Agent')
|
||||
[CompletionResult]::new('--random-agent', 'random-agent', [CompletionResultType]::ParameterName, 'Use a random User-Agent')
|
||||
[CompletionResult]::new('-f', 'f', [CompletionResultType]::ParameterName, 'Append / to each request''s URL')
|
||||
[CompletionResult]::new('--add-slash', 'add-slash', [CompletionResultType]::ParameterName, 'Append / to each request''s URL')
|
||||
[CompletionResult]::new('-r', 'r', [CompletionResultType]::ParameterName, 'Allow client to follow redirects')
|
||||
[CompletionResult]::new('--redirects', 'redirects', [CompletionResultType]::ParameterName, 'Allow client to follow redirects')
|
||||
[CompletionResult]::new('-k', 'k', [CompletionResultType]::ParameterName, 'Disables TLS certificate validation in the client')
|
||||
[CompletionResult]::new('--insecure', 'insecure', [CompletionResultType]::ParameterName, 'Disables TLS certificate validation in the client')
|
||||
[CompletionResult]::new('-n', 'n', [CompletionResultType]::ParameterName, 'Do not scan recursively')
|
||||
[CompletionResult]::new('--no-recursion', 'no-recursion', [CompletionResultType]::ParameterName, 'Do not scan recursively')
|
||||
[CompletionResult]::new('--force-recursion', 'force-recursion', [CompletionResultType]::ParameterName, 'Force recursion attempts on all ''found'' endpoints (still respects recursion depth)')
|
||||
[CompletionResult]::new('-e', 'e', [CompletionResultType]::ParameterName, 'Extract links from response body (html, javascript, etc...); make new requests based on findings (default: true)')
|
||||
[CompletionResult]::new('--extract-links', 'extract-links', [CompletionResultType]::ParameterName, 'Extract links from response body (html, javascript, etc...); make new requests based on findings (default: true)')
|
||||
[CompletionResult]::new('--dont-extract-links', 'dont-extract-links', [CompletionResultType]::ParameterName, 'Don''t extract links from response body (html, javascript, etc...)')
|
||||
[CompletionResult]::new('--auto-tune', 'auto-tune', [CompletionResultType]::ParameterName, 'Automatically lower scan rate when an excessive amount of errors are encountered')
|
||||
[CompletionResult]::new('--auto-bail', 'auto-bail', [CompletionResultType]::ParameterName, 'Automatically stop scanning when an excessive amount of errors are encountered')
|
||||
[CompletionResult]::new('-D', 'D ', [CompletionResultType]::ParameterName, 'Don''t auto-filter wildcard responses')
|
||||
[CompletionResult]::new('--dont-filter', 'dont-filter', [CompletionResultType]::ParameterName, 'Don''t auto-filter wildcard responses')
|
||||
[CompletionResult]::new('-E', 'E ', [CompletionResultType]::ParameterName, 'Automatically discover extensions and add them to --extensions (unless they''re in --dont-collect)')
|
||||
[CompletionResult]::new('--collect-extensions', 'collect-extensions', [CompletionResultType]::ParameterName, 'Automatically discover extensions and add them to --extensions (unless they''re in --dont-collect)')
|
||||
[CompletionResult]::new('-g', 'g', [CompletionResultType]::ParameterName, 'Automatically discover important words from within responses and add them to the wordlist')
|
||||
[CompletionResult]::new('--collect-words', 'collect-words', [CompletionResultType]::ParameterName, 'Automatically discover important words from within responses and add them to the wordlist')
|
||||
[CompletionResult]::new('-v', 'v', [CompletionResultType]::ParameterName, 'Increase verbosity level (use -vv or more for greater effect. [CAUTION] 4 -v''s is probably too much)')
|
||||
[CompletionResult]::new('--verbosity', 'verbosity', [CompletionResultType]::ParameterName, 'Increase verbosity level (use -vv or more for greater effect. [CAUTION] 4 -v''s is probably too much)')
|
||||
[CompletionResult]::new('--silent', 'silent', [CompletionResultType]::ParameterName, 'Only print URLs (or JSON w/ --json) + turn off logging (good for piping a list of urls to other commands)')
|
||||
[CompletionResult]::new('-q', 'q', [CompletionResultType]::ParameterName, 'Hide progress bars and banner (good for tmux windows w/ notifications)')
|
||||
[CompletionResult]::new('--quiet', 'quiet', [CompletionResultType]::ParameterName, 'Hide progress bars and banner (good for tmux windows w/ notifications)')
|
||||
[CompletionResult]::new('--json', 'json', [CompletionResultType]::ParameterName, 'Emit JSON logs to --output and --debug-log instead of normal text')
|
||||
[CompletionResult]::new('--no-state', 'no-state', [CompletionResultType]::ParameterName, 'Disable state output file (*.state)')
|
||||
[CompletionResult]::new('-U', 'U ', [CompletionResultType]::ParameterName, 'Update feroxbuster to the latest version')
|
||||
[CompletionResult]::new('--update', 'update', [CompletionResultType]::ParameterName, 'Update feroxbuster to the latest version')
|
||||
[CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help (see more with ''--help'')')
|
||||
[CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help (see more with ''--help'')')
|
||||
[CompletionResult]::new('-V', 'V ', [CompletionResultType]::ParameterName, 'Print version')
|
||||
[CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Print version')
|
||||
[CompletionResult]::new('-u', '-u', [CompletionResultType]::ParameterName, 'The target URL (required, unless [--stdin || --resume-from || --request-file] used)')
|
||||
[CompletionResult]::new('--url', '--url', [CompletionResultType]::ParameterName, 'The target URL (required, unless [--stdin || --resume-from || --request-file] used)')
|
||||
[CompletionResult]::new('--resume-from', '--resume-from', [CompletionResultType]::ParameterName, 'State file from which to resume a partially complete scan (ex. --resume-from ferox-1606586780.state)')
|
||||
[CompletionResult]::new('--request-file', '--request-file', [CompletionResultType]::ParameterName, 'Raw HTTP request file to use as a template for all requests')
|
||||
[CompletionResult]::new('-p', '-p', [CompletionResultType]::ParameterName, 'Proxy to use for requests (ex: http(s)://host:port, socks5(h)://host:port)')
|
||||
[CompletionResult]::new('--proxy', '--proxy', [CompletionResultType]::ParameterName, 'Proxy to use for requests (ex: http(s)://host:port, socks5(h)://host:port)')
|
||||
[CompletionResult]::new('-P', '-P ', [CompletionResultType]::ParameterName, 'Send only unfiltered requests through a Replay Proxy, instead of all requests')
|
||||
[CompletionResult]::new('--replay-proxy', '--replay-proxy', [CompletionResultType]::ParameterName, 'Send only unfiltered requests through a Replay Proxy, instead of all requests')
|
||||
[CompletionResult]::new('-R', '-R ', [CompletionResultType]::ParameterName, 'Status Codes to send through a Replay Proxy when found (default: --status-codes value)')
|
||||
[CompletionResult]::new('--replay-codes', '--replay-codes', [CompletionResultType]::ParameterName, 'Status Codes to send through a Replay Proxy when found (default: --status-codes value)')
|
||||
[CompletionResult]::new('-a', '-a', [CompletionResultType]::ParameterName, 'Sets the User-Agent (default: feroxbuster/2.11.0)')
|
||||
[CompletionResult]::new('--user-agent', '--user-agent', [CompletionResultType]::ParameterName, 'Sets the User-Agent (default: feroxbuster/2.11.0)')
|
||||
[CompletionResult]::new('-x', '-x', [CompletionResultType]::ParameterName, 'File extension(s) to search for (ex: -x php -x pdf js); reads values (newline-separated) from file if input starts with an @ (ex: @ext.txt)')
|
||||
[CompletionResult]::new('--extensions', '--extensions', [CompletionResultType]::ParameterName, 'File extension(s) to search for (ex: -x php -x pdf js); reads values (newline-separated) from file if input starts with an @ (ex: @ext.txt)')
|
||||
[CompletionResult]::new('-m', '-m', [CompletionResultType]::ParameterName, 'Which HTTP request method(s) should be sent (default: GET)')
|
||||
[CompletionResult]::new('--methods', '--methods', [CompletionResultType]::ParameterName, 'Which HTTP request method(s) should be sent (default: GET)')
|
||||
[CompletionResult]::new('--data', '--data', [CompletionResultType]::ParameterName, 'Request''s Body; can read data from a file if input starts with an @ (ex: @post.bin)')
|
||||
[CompletionResult]::new('-H', '-H ', [CompletionResultType]::ParameterName, 'Specify HTTP headers to be used in each request (ex: -H Header:val -H ''stuff: things'')')
|
||||
[CompletionResult]::new('--headers', '--headers', [CompletionResultType]::ParameterName, 'Specify HTTP headers to be used in each request (ex: -H Header:val -H ''stuff: things'')')
|
||||
[CompletionResult]::new('-b', '-b', [CompletionResultType]::ParameterName, 'Specify HTTP cookies to be used in each request (ex: -b stuff=things)')
|
||||
[CompletionResult]::new('--cookies', '--cookies', [CompletionResultType]::ParameterName, 'Specify HTTP cookies to be used in each request (ex: -b stuff=things)')
|
||||
[CompletionResult]::new('-Q', '-Q ', [CompletionResultType]::ParameterName, 'Request''s URL query parameters (ex: -Q token=stuff -Q secret=key)')
|
||||
[CompletionResult]::new('--query', '--query', [CompletionResultType]::ParameterName, 'Request''s URL query parameters (ex: -Q token=stuff -Q secret=key)')
|
||||
[CompletionResult]::new('--protocol', '--protocol', [CompletionResultType]::ParameterName, 'Specify the protocol to use when targeting via --request-file or --url with domain only (default: https)')
|
||||
[CompletionResult]::new('--dont-scan', '--dont-scan', [CompletionResultType]::ParameterName, 'URL(s) or Regex Pattern(s) to exclude from recursion/scans')
|
||||
[CompletionResult]::new('-S', '-S ', [CompletionResultType]::ParameterName, 'Filter out messages of a particular size (ex: -S 5120 -S 4927,1970)')
|
||||
[CompletionResult]::new('--filter-size', '--filter-size', [CompletionResultType]::ParameterName, 'Filter out messages of a particular size (ex: -S 5120 -S 4927,1970)')
|
||||
[CompletionResult]::new('-X', '-X ', [CompletionResultType]::ParameterName, 'Filter out messages via regular expression matching on the response''s body/headers (ex: -X ''^ignore me$'')')
|
||||
[CompletionResult]::new('--filter-regex', '--filter-regex', [CompletionResultType]::ParameterName, 'Filter out messages via regular expression matching on the response''s body/headers (ex: -X ''^ignore me$'')')
|
||||
[CompletionResult]::new('-W', '-W ', [CompletionResultType]::ParameterName, 'Filter out messages of a particular word count (ex: -W 312 -W 91,82)')
|
||||
[CompletionResult]::new('--filter-words', '--filter-words', [CompletionResultType]::ParameterName, 'Filter out messages of a particular word count (ex: -W 312 -W 91,82)')
|
||||
[CompletionResult]::new('-N', '-N ', [CompletionResultType]::ParameterName, 'Filter out messages of a particular line count (ex: -N 20 -N 31,30)')
|
||||
[CompletionResult]::new('--filter-lines', '--filter-lines', [CompletionResultType]::ParameterName, 'Filter out messages of a particular line count (ex: -N 20 -N 31,30)')
|
||||
[CompletionResult]::new('-C', '-C ', [CompletionResultType]::ParameterName, 'Filter out status codes (deny list) (ex: -C 200 -C 401)')
|
||||
[CompletionResult]::new('--filter-status', '--filter-status', [CompletionResultType]::ParameterName, 'Filter out status codes (deny list) (ex: -C 200 -C 401)')
|
||||
[CompletionResult]::new('--filter-similar-to', '--filter-similar-to', [CompletionResultType]::ParameterName, 'Filter out pages that are similar to the given page (ex. --filter-similar-to http://site.xyz/soft404)')
|
||||
[CompletionResult]::new('-s', '-s', [CompletionResultType]::ParameterName, 'Status Codes to include (allow list) (default: All Status Codes)')
|
||||
[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-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)')
|
||||
[CompletionResult]::new('--depth', '--depth', [CompletionResultType]::ParameterName, 'Maximum recursion depth, a depth of 0 is infinite recursion (default: 4)')
|
||||
[CompletionResult]::new('-L', '-L ', [CompletionResultType]::ParameterName, 'Limit total number of concurrent scans (default: 0, i.e. no limit)')
|
||||
[CompletionResult]::new('--scan-limit', '--scan-limit', [CompletionResultType]::ParameterName, 'Limit total number of concurrent scans (default: 0, i.e. no limit)')
|
||||
[CompletionResult]::new('--parallel', '--parallel', [CompletionResultType]::ParameterName, 'Run parallel feroxbuster instances (one child process per url passed via stdin)')
|
||||
[CompletionResult]::new('--rate-limit', '--rate-limit', [CompletionResultType]::ParameterName, 'Limit number of requests per second (per directory) (default: 0, i.e. no limit)')
|
||||
[CompletionResult]::new('--time-limit', '--time-limit', [CompletionResultType]::ParameterName, 'Limit total run time of all scans (ex: --time-limit 10m)')
|
||||
[CompletionResult]::new('-w', '-w', [CompletionResultType]::ParameterName, 'Path or URL of the wordlist')
|
||||
[CompletionResult]::new('--wordlist', '--wordlist', [CompletionResultType]::ParameterName, 'Path or URL of the wordlist')
|
||||
[CompletionResult]::new('-B', '-B ', [CompletionResultType]::ParameterName, 'Automatically request likely backup extensions for "found" urls (default: ~, .bak, .bak2, .old, .1)')
|
||||
[CompletionResult]::new('--collect-backups', '--collect-backups', [CompletionResultType]::ParameterName, 'Automatically request likely backup extensions for "found" urls (default: ~, .bak, .bak2, .old, .1)')
|
||||
[CompletionResult]::new('-I', '-I ', [CompletionResultType]::ParameterName, 'File extension(s) to Ignore while collecting extensions (only used with --collect-extensions)')
|
||||
[CompletionResult]::new('--dont-collect', '--dont-collect', [CompletionResultType]::ParameterName, 'File extension(s) to Ignore while collecting extensions (only used with --collect-extensions)')
|
||||
[CompletionResult]::new('-o', '-o', [CompletionResultType]::ParameterName, 'Output file to write results to (use w/ --json for JSON entries)')
|
||||
[CompletionResult]::new('--output', '--output', [CompletionResultType]::ParameterName, 'Output file to write results to (use w/ --json for JSON entries)')
|
||||
[CompletionResult]::new('--debug-log', '--debug-log', [CompletionResultType]::ParameterName, 'Output file to write log entries (use w/ --json for JSON entries)')
|
||||
[CompletionResult]::new('--limit-bars', '--limit-bars', [CompletionResultType]::ParameterName, 'Number of directory scan bars to show at any given time (default: no limit)')
|
||||
[CompletionResult]::new('--stdin', '--stdin', [CompletionResultType]::ParameterName, 'Read url(s) from STDIN')
|
||||
[CompletionResult]::new('--burp', '--burp', [CompletionResultType]::ParameterName, 'Set --proxy to http://127.0.0.1:8080 and set --insecure to true')
|
||||
[CompletionResult]::new('--burp-replay', '--burp-replay', [CompletionResultType]::ParameterName, 'Set --replay-proxy to http://127.0.0.1:8080 and set --insecure to true')
|
||||
[CompletionResult]::new('--smart', '--smart', [CompletionResultType]::ParameterName, 'Set --auto-tune, --collect-words, and --collect-backups to true')
|
||||
[CompletionResult]::new('--thorough', '--thorough', [CompletionResultType]::ParameterName, 'Use the same settings as --smart and set --collect-extensions and --scan-dir-listings to true')
|
||||
[CompletionResult]::new('-A', '-A ', [CompletionResultType]::ParameterName, 'Use a random User-Agent')
|
||||
[CompletionResult]::new('--random-agent', '--random-agent', [CompletionResultType]::ParameterName, 'Use a random User-Agent')
|
||||
[CompletionResult]::new('-f', '-f', [CompletionResultType]::ParameterName, 'Append / to each request''s URL')
|
||||
[CompletionResult]::new('--add-slash', '--add-slash', [CompletionResultType]::ParameterName, 'Append / to each request''s URL')
|
||||
[CompletionResult]::new('-r', '-r', [CompletionResultType]::ParameterName, 'Allow client to follow redirects')
|
||||
[CompletionResult]::new('--redirects', '--redirects', [CompletionResultType]::ParameterName, 'Allow client to follow redirects')
|
||||
[CompletionResult]::new('-k', '-k', [CompletionResultType]::ParameterName, 'Disables TLS certificate validation in the client')
|
||||
[CompletionResult]::new('--insecure', '--insecure', [CompletionResultType]::ParameterName, 'Disables TLS certificate validation in the client')
|
||||
[CompletionResult]::new('-n', '-n', [CompletionResultType]::ParameterName, 'Do not scan recursively')
|
||||
[CompletionResult]::new('--no-recursion', '--no-recursion', [CompletionResultType]::ParameterName, 'Do not scan recursively')
|
||||
[CompletionResult]::new('--force-recursion', '--force-recursion', [CompletionResultType]::ParameterName, 'Force recursion attempts on all ''found'' endpoints (still respects recursion depth)')
|
||||
[CompletionResult]::new('-e', '-e', [CompletionResultType]::ParameterName, 'Extract links from response body (html, javascript, etc...); make new requests based on findings (default: true)')
|
||||
[CompletionResult]::new('--extract-links', '--extract-links', [CompletionResultType]::ParameterName, 'Extract links from response body (html, javascript, etc...); make new requests based on findings (default: true)')
|
||||
[CompletionResult]::new('--dont-extract-links', '--dont-extract-links', [CompletionResultType]::ParameterName, 'Don''t extract links from response body (html, javascript, etc...)')
|
||||
[CompletionResult]::new('--auto-tune', '--auto-tune', [CompletionResultType]::ParameterName, 'Automatically lower scan rate when an excessive amount of errors are encountered')
|
||||
[CompletionResult]::new('--auto-bail', '--auto-bail', [CompletionResultType]::ParameterName, 'Automatically stop scanning when an excessive amount of errors are encountered')
|
||||
[CompletionResult]::new('-D', '-D ', [CompletionResultType]::ParameterName, 'Don''t auto-filter wildcard responses')
|
||||
[CompletionResult]::new('--dont-filter', '--dont-filter', [CompletionResultType]::ParameterName, 'Don''t auto-filter wildcard responses')
|
||||
[CompletionResult]::new('-E', '-E ', [CompletionResultType]::ParameterName, 'Automatically discover extensions and add them to --extensions (unless they''re in --dont-collect)')
|
||||
[CompletionResult]::new('--collect-extensions', '--collect-extensions', [CompletionResultType]::ParameterName, 'Automatically discover extensions and add them to --extensions (unless they''re in --dont-collect)')
|
||||
[CompletionResult]::new('-g', '-g', [CompletionResultType]::ParameterName, 'Automatically discover important words from within responses and add them to the wordlist')
|
||||
[CompletionResult]::new('--collect-words', '--collect-words', [CompletionResultType]::ParameterName, 'Automatically discover important words from within responses and add them to the wordlist')
|
||||
[CompletionResult]::new('--scan-dir-listings', '--scan-dir-listings', [CompletionResultType]::ParameterName, 'Force scans to recurse into directory listings')
|
||||
[CompletionResult]::new('-v', '-v', [CompletionResultType]::ParameterName, 'Increase verbosity level (use -vv or more for greater effect. [CAUTION] 4 -v''s is probably too much)')
|
||||
[CompletionResult]::new('--verbosity', '--verbosity', [CompletionResultType]::ParameterName, 'Increase verbosity level (use -vv or more for greater effect. [CAUTION] 4 -v''s is probably too much)')
|
||||
[CompletionResult]::new('--silent', '--silent', [CompletionResultType]::ParameterName, 'Only print URLs (or JSON w/ --json) + turn off logging (good for piping a list of urls to other commands)')
|
||||
[CompletionResult]::new('-q', '-q', [CompletionResultType]::ParameterName, 'Hide progress bars and banner (good for tmux windows w/ notifications)')
|
||||
[CompletionResult]::new('--quiet', '--quiet', [CompletionResultType]::ParameterName, 'Hide progress bars and banner (good for tmux windows w/ notifications)')
|
||||
[CompletionResult]::new('--json', '--json', [CompletionResultType]::ParameterName, 'Emit JSON logs to --output and --debug-log instead of normal text')
|
||||
[CompletionResult]::new('--no-state', '--no-state', [CompletionResultType]::ParameterName, 'Disable state output file (*.state)')
|
||||
[CompletionResult]::new('-U', '-U ', [CompletionResultType]::ParameterName, 'Update feroxbuster to the latest version')
|
||||
[CompletionResult]::new('--update', '--update', [CompletionResultType]::ParameterName, 'Update feroxbuster to the latest version')
|
||||
[CompletionResult]::new('-h', '-h', [CompletionResultType]::ParameterName, 'Print help (see more with ''--help'')')
|
||||
[CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, 'Print help (see more with ''--help'')')
|
||||
[CompletionResult]::new('-V', '-V ', [CompletionResultType]::ParameterName, 'Print version')
|
||||
[CompletionResult]::new('--version', '--version', [CompletionResultType]::ParameterName, 'Print version')
|
||||
break
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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-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"
|
||||
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 --request-file --burp --burp-replay --smart --thorough --proxy --replay-proxy --replay-codes --user-agent --random-agent --extensions --methods --data --headers --cookies --query --add-slash --protocol --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 --scan-dir-listings --verbosity --silent --quiet --json --output --debug-log --no-state --limit-bars --update --help --version"
|
||||
if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||
return 0
|
||||
@@ -48,6 +48,21 @@ _feroxbuster() {
|
||||
fi
|
||||
return 0
|
||||
;;
|
||||
--request-file)
|
||||
local oldifs
|
||||
if [ -n "${IFS+x}" ]; then
|
||||
oldifs="$IFS"
|
||||
fi
|
||||
IFS=$'\n'
|
||||
COMPREPLY=($(compgen -f "${cur}"))
|
||||
if [ -n "${oldifs+x}" ]; then
|
||||
IFS="$oldifs"
|
||||
fi
|
||||
if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then
|
||||
compopt -o filenames
|
||||
fi
|
||||
return 0
|
||||
;;
|
||||
--proxy)
|
||||
COMPREPLY=($(compgen -f "${cur}"))
|
||||
return 0
|
||||
@@ -124,6 +139,10 @@ _feroxbuster() {
|
||||
COMPREPLY=($(compgen -f "${cur}"))
|
||||
return 0
|
||||
;;
|
||||
--protocol)
|
||||
COMPREPLY=($(compgen -f "${cur}"))
|
||||
return 0
|
||||
;;
|
||||
--dont-scan)
|
||||
COMPREPLY=($(compgen -f "${cur}"))
|
||||
return 0
|
||||
@@ -360,6 +379,10 @@ _feroxbuster() {
|
||||
fi
|
||||
return 0
|
||||
;;
|
||||
--limit-bars)
|
||||
COMPREPLY=($(compgen -f "${cur}"))
|
||||
return 0
|
||||
;;
|
||||
*)
|
||||
COMPREPLY=()
|
||||
;;
|
||||
|
||||
@@ -18,17 +18,18 @@ set edit:completion:arg-completer[feroxbuster] = {|@words|
|
||||
}
|
||||
var completions = [
|
||||
&'feroxbuster'= {
|
||||
cand -u 'The target URL (required, unless [--stdin || --resume-from] used)'
|
||||
cand --url 'The target URL (required, unless [--stdin || --resume-from] used)'
|
||||
cand -u 'The target URL (required, unless [--stdin || --resume-from || --request-file] used)'
|
||||
cand --url 'The target URL (required, unless [--stdin || --resume-from || --request-file] used)'
|
||||
cand --resume-from 'State file from which to resume a partially complete scan (ex. --resume-from ferox-1606586780.state)'
|
||||
cand --request-file 'Raw HTTP request file to use as a template for all requests'
|
||||
cand -p 'Proxy to use for requests (ex: http(s)://host:port, socks5(h)://host:port)'
|
||||
cand --proxy 'Proxy to use for requests (ex: http(s)://host:port, socks5(h)://host:port)'
|
||||
cand -P 'Send only unfiltered requests through a Replay Proxy, instead of all requests'
|
||||
cand --replay-proxy 'Send only unfiltered requests through a Replay Proxy, instead of all requests'
|
||||
cand -R 'Status Codes to send through a Replay Proxy when found (default: --status-codes value)'
|
||||
cand --replay-codes 'Status Codes to send through a Replay Proxy when found (default: --status-codes value)'
|
||||
cand -a 'Sets the User-Agent (default: feroxbuster/2.10.4)'
|
||||
cand --user-agent 'Sets the User-Agent (default: feroxbuster/2.10.4)'
|
||||
cand -a 'Sets the User-Agent (default: feroxbuster/2.11.0)'
|
||||
cand --user-agent 'Sets the User-Agent (default: feroxbuster/2.11.0)'
|
||||
cand -x 'File extension(s) to search for (ex: -x php -x pdf js); reads values (newline-separated) from file if input starts with an @ (ex: @ext.txt)'
|
||||
cand --extensions 'File extension(s) to search for (ex: -x php -x pdf js); reads values (newline-separated) from file if input starts with an @ (ex: @ext.txt)'
|
||||
cand -m 'Which HTTP request method(s) should be sent (default: GET)'
|
||||
@@ -40,6 +41,7 @@ set edit:completion:arg-completer[feroxbuster] = {|@words|
|
||||
cand --cookies 'Specify HTTP cookies to be used in each request (ex: -b stuff=things)'
|
||||
cand -Q 'Request''s URL query parameters (ex: -Q token=stuff -Q secret=key)'
|
||||
cand --query 'Request''s URL query parameters (ex: -Q token=stuff -Q secret=key)'
|
||||
cand --protocol 'Specify the protocol to use when targeting via --request-file or --url with domain only (default: https)'
|
||||
cand --dont-scan 'URL(s) or Regex Pattern(s) to exclude from recursion/scans'
|
||||
cand -S 'Filter out messages of a particular size (ex: -S 5120 -S 4927,1970)'
|
||||
cand --filter-size 'Filter out messages of a particular size (ex: -S 5120 -S 4927,1970)'
|
||||
@@ -77,11 +79,12 @@ set edit:completion:arg-completer[feroxbuster] = {|@words|
|
||||
cand -o 'Output file to write results to (use w/ --json for JSON entries)'
|
||||
cand --output 'Output file to write results to (use w/ --json for JSON entries)'
|
||||
cand --debug-log 'Output file to write log entries (use w/ --json for JSON entries)'
|
||||
cand --limit-bars 'Number of directory scan bars to show at any given time (default: no limit)'
|
||||
cand --stdin 'Read url(s) from STDIN'
|
||||
cand --burp 'Set --proxy to http://127.0.0.1:8080 and set --insecure to true'
|
||||
cand --burp-replay 'Set --replay-proxy to http://127.0.0.1:8080 and set --insecure to true'
|
||||
cand --smart 'Set --auto-tune, --collect-words, and --collect-backups to true'
|
||||
cand --thorough 'Use the same settings as --smart and set --collect-extensions to true'
|
||||
cand --thorough 'Use the same settings as --smart and set --collect-extensions and --scan-dir-listings to true'
|
||||
cand -A 'Use a random User-Agent'
|
||||
cand --random-agent 'Use a random User-Agent'
|
||||
cand -f 'Append / to each request''s URL'
|
||||
@@ -104,6 +107,7 @@ set edit:completion:arg-completer[feroxbuster] = {|@words|
|
||||
cand --collect-extensions 'Automatically discover extensions and add them to --extensions (unless they''re in --dont-collect)'
|
||||
cand -g 'Automatically discover important words from within responses and add them to the wordlist'
|
||||
cand --collect-words 'Automatically discover important words from within responses and add them to the wordlist'
|
||||
cand --scan-dir-listings 'Force scans to recurse into directory listings'
|
||||
cand -v 'Increase verbosity level (use -vv or more for greater effect. [CAUTION] 4 -v''s is probably too much)'
|
||||
cand --verbosity 'Increase verbosity level (use -vv or more for greater effect. [CAUTION] 4 -v''s is probably too much)'
|
||||
cand --silent 'Only print URLs (or JSON w/ --json) + turn off logging (good for piping a list of urls to other commands)'
|
||||
|
||||
@@ -176,6 +176,15 @@ pub struct Banner {
|
||||
|
||||
/// represents Configuration.collect_words
|
||||
force_recursion: BannerEntry,
|
||||
|
||||
/// represents Configuration.protocol
|
||||
protocol: BannerEntry,
|
||||
|
||||
/// represents Configuration.scan_dir_listings
|
||||
scan_dir_listings: BannerEntry,
|
||||
|
||||
/// represents Configuration.limit_bars
|
||||
limit_bars: BannerEntry,
|
||||
}
|
||||
|
||||
/// implementation of Banner
|
||||
@@ -320,6 +329,12 @@ impl Banner {
|
||||
BannerEntry::new("🚫", "Do Not Recurse", &config.no_recursion.to_string())
|
||||
};
|
||||
|
||||
let protocol = if config.protocol.to_lowercase() == "http" {
|
||||
BannerEntry::new("🔓", "Default Protocol", &config.protocol)
|
||||
} else {
|
||||
BannerEntry::new("🔒", "Default Protocol", &config.protocol)
|
||||
};
|
||||
|
||||
let scan_limit = BannerEntry::new(
|
||||
"🦥",
|
||||
"Concurrent Scan Limit",
|
||||
@@ -331,6 +346,11 @@ impl Banner {
|
||||
let replay_proxy = BannerEntry::new("🎥", "Replay Proxy", &config.replay_proxy);
|
||||
let auto_tune = BannerEntry::new("🎶", "Auto Tune", &config.auto_tune.to_string());
|
||||
let auto_bail = BannerEntry::new("🙅", "Auto Bail", &config.auto_bail.to_string());
|
||||
let scan_dir_listings = BannerEntry::new(
|
||||
"📂",
|
||||
"Scan Dir Listings",
|
||||
&config.scan_dir_listings.to_string(),
|
||||
);
|
||||
let cfg = BannerEntry::new("💉", "Config File", &config.config);
|
||||
let proxy = BannerEntry::new("💎", "Proxy", &config.proxy);
|
||||
let server_certs = BannerEntry::new(
|
||||
@@ -341,6 +361,8 @@ impl Banner {
|
||||
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());
|
||||
let limit_bars =
|
||||
BannerEntry::new("📊", "Limit Dir Scan Bars", &config.limit_bars.to_string());
|
||||
let wordlist = BannerEntry::new("📖", "Wordlist", &config.wordlist);
|
||||
let timeout = BannerEntry::new("💥", "Timeout (secs)", &config.timeout.to_string());
|
||||
let user_agent = BannerEntry::new("🦡", "User-Agent", &config.user_agent);
|
||||
@@ -455,6 +477,9 @@ impl Banner {
|
||||
collect_words,
|
||||
dont_collect,
|
||||
config: cfg,
|
||||
scan_dir_listings,
|
||||
protocol,
|
||||
limit_bars,
|
||||
version: VERSION.to_string(),
|
||||
update_status: UpdateStatus::Unknown,
|
||||
}
|
||||
@@ -595,6 +620,14 @@ by Ben "epi" Risher {} ver: {}"#,
|
||||
}
|
||||
|
||||
// followed by the maybe printed or variably displayed values
|
||||
if !config.request_file.is_empty() || !config.target_url.starts_with("http") {
|
||||
writeln!(&mut writer, "{}", self.protocol)?;
|
||||
}
|
||||
|
||||
if config.limit_bars > 0 {
|
||||
writeln!(&mut writer, "{}", self.limit_bars)?;
|
||||
}
|
||||
|
||||
if !config.config.is_empty() {
|
||||
writeln!(&mut writer, "{}", self.config)?;
|
||||
}
|
||||
@@ -662,6 +695,10 @@ by Ben "epi" Risher {} ver: {}"#,
|
||||
writeln!(&mut writer, "{}", self.output)?;
|
||||
}
|
||||
|
||||
if config.scan_dir_listings {
|
||||
writeln!(&mut writer, "{}", self.scan_dir_listings)?;
|
||||
}
|
||||
|
||||
if !config.debug_log.is_empty() {
|
||||
writeln!(&mut writer, "{}", self.debug_log)?;
|
||||
}
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
use super::utils::{
|
||||
backup_extensions, depth, extract_links, ignored_extensions, methods, report_and_exit,
|
||||
save_state, serialized_type, status_codes, threads, timeout, user_agent, wordlist, OutputLevel,
|
||||
backup_extensions, depth, determine_requester_policy, extract_links, ignored_extensions,
|
||||
methods, parse_request_file, report_and_exit, request_protocol, save_state, serialized_type,
|
||||
split_header, split_query, status_codes, threads, timeout, user_agent, wordlist, OutputLevel,
|
||||
RequesterPolicy,
|
||||
};
|
||||
|
||||
use crate::config::determine_output_level;
|
||||
use crate::config::utils::determine_requester_policy;
|
||||
use crate::{
|
||||
client, parser,
|
||||
scan_manager::resume_scan,
|
||||
traits::FeroxSerialize,
|
||||
utils::{fmt_err, parse_url_with_raw_path},
|
||||
utils::{fmt_err, module_colorizer, parse_url_with_raw_path, status_colorizer},
|
||||
DEFAULT_CONFIG_NAME,
|
||||
};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
@@ -332,6 +333,22 @@ pub struct Configuration {
|
||||
/// Auto update app feature
|
||||
#[serde(skip)]
|
||||
pub update_app: bool,
|
||||
|
||||
/// whether to recurse into directory listings or not
|
||||
#[serde(default)]
|
||||
pub scan_dir_listings: bool,
|
||||
|
||||
/// path to a raw request file generated by burp or similar
|
||||
#[serde(skip)]
|
||||
pub request_file: String,
|
||||
|
||||
/// default request protocol
|
||||
#[serde(default = "request_protocol")]
|
||||
pub protocol: String,
|
||||
|
||||
/// number of directory scan bars to show at any given time, 0 is no limit
|
||||
#[serde(default)]
|
||||
pub limit_bars: usize,
|
||||
}
|
||||
|
||||
impl Default for Configuration {
|
||||
@@ -378,10 +395,12 @@ impl Default for Configuration {
|
||||
resumed: false,
|
||||
stdin: false,
|
||||
json: false,
|
||||
scan_dir_listings: false,
|
||||
verbosity: 0,
|
||||
scan_limit: 0,
|
||||
parallel: 0,
|
||||
rate_limit: 0,
|
||||
limit_bars: 0,
|
||||
add_slash: false,
|
||||
insecure: false,
|
||||
redirects: false,
|
||||
@@ -403,6 +422,8 @@ impl Default for Configuration {
|
||||
time_limit: String::new(),
|
||||
resume_from: String::new(),
|
||||
replay_proxy: String::new(),
|
||||
request_file: String::new(),
|
||||
protocol: request_protocol(),
|
||||
server_certs: Vec::new(),
|
||||
queries: Vec::new(),
|
||||
extensions: Vec::new(),
|
||||
@@ -476,12 +497,16 @@ impl Configuration {
|
||||
/// - **depth**: `4` (maximum recursion depth)
|
||||
/// - **force_recursion**: `false` (still respects recursion depth)
|
||||
/// - **scan_limit**: `0` (no limit on concurrent scans imposed)
|
||||
/// - **limit_bars**: `0` (no limit on number of directory scan bars shown)
|
||||
/// - **parallel**: `0` (no limit on parallel scans imposed)
|
||||
/// - **rate_limit**: `0` (no limit on requests per second imposed)
|
||||
/// - **time_limit**: `None` (no limit on length of scan imposed)
|
||||
/// - **replay_proxy**: `None` (no limit on concurrent scans imposed)
|
||||
/// - **replay_codes**: [`DEFAULT_RESPONSE_CODES`](constant.DEFAULT_RESPONSE_CODES.html)
|
||||
/// - **update_app**: `false`
|
||||
/// - **scan_dir_listings**: `false`
|
||||
/// - **request_file**: `None`
|
||||
/// - **protocol**: `https`
|
||||
///
|
||||
/// After which, any values defined in a
|
||||
/// [ferox-config.toml](constant.DEFAULT_CONFIG_NAME.html) config file will override the
|
||||
@@ -555,6 +580,18 @@ impl Configuration {
|
||||
// merge the cli options into the config file options and return the result
|
||||
Self::merge_config(&mut config, cli_config);
|
||||
|
||||
// if the user provided a raw request file as the target, we'll need to parse out
|
||||
// the provided info and update the config with those values. This call needs to
|
||||
// come after the cli/config merge so we can allow the cli options to override
|
||||
// the raw request values (i.e. --headers "stuff: things" should override a "stuff"
|
||||
// header from the raw request).
|
||||
//
|
||||
// Additionally, this call needs to come before client rebuild so that the things
|
||||
// like user-agent can be set at the client level instead of the header level.
|
||||
if !config.request_file.is_empty() {
|
||||
parse_request_file(&mut config)?;
|
||||
}
|
||||
|
||||
// rebuild clients is the last step in either code branch
|
||||
Self::try_rebuild_clients(&mut config);
|
||||
|
||||
@@ -614,10 +651,13 @@ impl Configuration {
|
||||
update_config_with_num_type_if_present!(&mut config.depth, args, "depth", usize);
|
||||
update_config_with_num_type_if_present!(&mut config.scan_limit, args, "scan_limit", usize);
|
||||
update_config_with_num_type_if_present!(&mut config.rate_limit, args, "rate_limit", usize);
|
||||
update_config_with_num_type_if_present!(&mut config.limit_bars, args, "limit_bars", usize);
|
||||
update_config_if_present!(&mut config.wordlist, args, "wordlist", String);
|
||||
update_config_if_present!(&mut config.output, args, "output", String);
|
||||
update_config_if_present!(&mut config.debug_log, args, "debug_log", String);
|
||||
update_config_if_present!(&mut config.resume_from, args, "resume_from", String);
|
||||
update_config_if_present!(&mut config.request_file, args, "request_file", String);
|
||||
update_config_if_present!(&mut config.protocol, args, "protocol", String);
|
||||
|
||||
if let Ok(Some(inner)) = args.try_get_one::<String>("time_limit") {
|
||||
inner.clone_into(&mut config.time_limit);
|
||||
@@ -831,6 +871,10 @@ impl Configuration {
|
||||
config.save_state = false;
|
||||
}
|
||||
|
||||
if came_from_cli!(args, "scan_dir_listings") || came_from_cli!(args, "thorough") {
|
||||
config.scan_dir_listings = true;
|
||||
}
|
||||
|
||||
if came_from_cli!(args, "dont_filter") {
|
||||
config.dont_filter = true;
|
||||
}
|
||||
@@ -871,6 +915,25 @@ impl Configuration {
|
||||
// occurrences_of returns 0 if none are found; this is protected in
|
||||
// an if block for the same reason as the quiet option
|
||||
config.verbosity = args.get_count("verbosity");
|
||||
|
||||
// todo: starting on 2.11.0 (907-dont-skip-dir-listings), trace-level
|
||||
// logging started causing the following error:
|
||||
//
|
||||
// thread 'tokio-runtime-worker' has overflowed its stack
|
||||
// fatal runtime error: stack overflow
|
||||
// Aborted (core dumped)
|
||||
//
|
||||
// as a temporary fix, we'll disable trace logging to prevent the stack
|
||||
// overflow until I get time to investigate the root cause
|
||||
if config.verbosity > 3 {
|
||||
eprintln!(
|
||||
"{} {}: Trace level logging is disabled; setting log level to debug",
|
||||
status_colorizer("WRN"),
|
||||
module_colorizer("Configuration::parse_cli_args"),
|
||||
);
|
||||
|
||||
config.verbosity = 3;
|
||||
}
|
||||
}
|
||||
|
||||
if came_from_cli!(args, "no_recursion") {
|
||||
@@ -932,23 +995,11 @@ impl Configuration {
|
||||
|
||||
if let Some(headers) = args.get_many::<String>("headers") {
|
||||
for val in headers {
|
||||
let mut split_val = val.split(':');
|
||||
|
||||
// explicitly take first split value as header's name
|
||||
let name = split_val.next().unwrap().trim();
|
||||
|
||||
// all other items in the iterator returned by split, when combined with the
|
||||
// original split deliminator (:), make up the header's final value
|
||||
let value = split_val.collect::<Vec<&str>>().join(":");
|
||||
|
||||
if value.starts_with(' ') && !value.starts_with(" ") {
|
||||
// first character is a space and the second character isn't
|
||||
// we can trim the leading space
|
||||
let trimmed = value.trim_start();
|
||||
config.headers.insert(name.to_string(), trimmed.to_string());
|
||||
} else {
|
||||
config.headers.insert(name.to_string(), value.to_string());
|
||||
}
|
||||
let Ok((name, value)) = split_header(val) else {
|
||||
log::warn!("Invalid header: {}", val);
|
||||
continue;
|
||||
};
|
||||
config.headers.insert(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -982,14 +1033,11 @@ impl Configuration {
|
||||
|
||||
if let Some(queries) = args.get_many::<String>("queries") {
|
||||
for val in queries {
|
||||
// same basic logic used as reading in the headers HashMap above
|
||||
let mut split_val = val.split('=');
|
||||
|
||||
let name = split_val.next().unwrap().trim();
|
||||
|
||||
let value = split_val.collect::<Vec<&str>>().join("=");
|
||||
|
||||
config.queries.push((name.to_string(), value.to_string()));
|
||||
let Ok((name, value)) = split_query(val) else {
|
||||
log::warn!("Invalid query string: {}", val);
|
||||
continue;
|
||||
};
|
||||
config.queries.push((name, value));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1111,6 +1159,7 @@ impl Configuration {
|
||||
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);
|
||||
update_if_not_default!(&mut conf.limit_bars, new.limit_bars, 0);
|
||||
update_if_not_default!(&mut conf.silent, new.silent, false);
|
||||
update_if_not_default!(&mut conf.quiet, new.quiet, false);
|
||||
update_if_not_default!(&mut conf.auto_bail, new.auto_bail, false);
|
||||
@@ -1171,12 +1220,15 @@ impl Configuration {
|
||||
Vec::<u16>::new()
|
||||
);
|
||||
update_if_not_default!(&mut conf.dont_filter, new.dont_filter, false);
|
||||
update_if_not_default!(&mut conf.scan_dir_listings, new.scan_dir_listings, false);
|
||||
update_if_not_default!(&mut conf.scan_limit, new.scan_limit, 0);
|
||||
update_if_not_default!(&mut conf.parallel, new.parallel, 0);
|
||||
update_if_not_default!(&mut conf.rate_limit, new.rate_limit, 0);
|
||||
update_if_not_default!(&mut conf.replay_proxy, new.replay_proxy, "");
|
||||
update_if_not_default!(&mut conf.debug_log, new.debug_log, "");
|
||||
update_if_not_default!(&mut conf.resume_from, new.resume_from, "");
|
||||
update_if_not_default!(&mut conf.request_file, new.request_file, "");
|
||||
update_if_not_default!(&mut conf.protocol, new.protocol, request_protocol());
|
||||
|
||||
update_if_not_default!(&mut conf.timeout, new.timeout, timeout());
|
||||
update_if_not_default!(&mut conf.user_agent, new.user_agent, user_agent());
|
||||
|
||||
@@ -49,6 +49,10 @@ fn setup_config_test() -> Configuration {
|
||||
json = true
|
||||
save_state = false
|
||||
depth = 1
|
||||
limit_bars = 3
|
||||
protocol = "http"
|
||||
request_file = "/some/request/file"
|
||||
scan_dir_listings = true
|
||||
force_recursion = true
|
||||
filter_size = [4120]
|
||||
filter_regex = ["^ignore me$"]
|
||||
@@ -87,6 +91,7 @@ fn default_configuration() {
|
||||
assert_eq!(config.timeout, timeout());
|
||||
assert_eq!(config.verbosity, 0);
|
||||
assert_eq!(config.scan_limit, 0);
|
||||
assert_eq!(config.limit_bars, 0);
|
||||
assert!(!config.silent);
|
||||
assert!(!config.quiet);
|
||||
assert_eq!(config.output_level, OutputLevel::Default);
|
||||
@@ -107,6 +112,7 @@ fn default_configuration() {
|
||||
assert!(!config.collect_extensions);
|
||||
assert!(!config.collect_backups);
|
||||
assert!(!config.collect_words);
|
||||
assert!(!config.scan_dir_listings);
|
||||
assert!(config.regex_denylist.is_empty());
|
||||
assert_eq!(config.queries, Vec::new());
|
||||
assert_eq!(config.filter_size, Vec::<u64>::new());
|
||||
@@ -125,6 +131,8 @@ fn default_configuration() {
|
||||
assert_eq!(config.client_cert, String::new());
|
||||
assert_eq!(config.client_key, String::new());
|
||||
assert_eq!(config.backup_extensions, backup_extensions());
|
||||
assert_eq!(config.protocol, request_protocol());
|
||||
assert_eq!(config.request_file, String::new());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -260,6 +268,13 @@ fn config_reads_verbosity() {
|
||||
assert_eq!(config.verbosity, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// parse the test config and see that the value parsed is correct
|
||||
fn config_reads_limit_bars() {
|
||||
let config = setup_config_test();
|
||||
assert_eq!(config.limit_bars, 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// parse the test config and see that the value parsed is correct
|
||||
fn config_reads_output() {
|
||||
@@ -444,6 +459,27 @@ fn config_reads_time_limit() {
|
||||
assert_eq!(config.time_limit, "10m");
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// parse the test config and see that the value parsed is correct
|
||||
fn config_reads_scan_dir_listings() {
|
||||
let config = setup_config_test();
|
||||
assert!(config.scan_dir_listings);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// parse the test config and see that the value parsed is correct
|
||||
fn config_reads_protocol() {
|
||||
let config = setup_config_test();
|
||||
assert_eq!(config.protocol, "http");
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// parse the test config and see that the value parsed is correct
|
||||
fn config_reads_request_file() {
|
||||
let config = setup_config_test();
|
||||
assert_eq!(config.request_file, String::new());
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// parse the test config and see that the value parsed is correct
|
||||
fn config_reads_resume_from() {
|
||||
|
||||
1056
src/config/utils.rs
1056
src/config/utils.rs
File diff suppressed because it is too large
Load Diff
@@ -120,7 +120,10 @@ impl ScanHandler {
|
||||
pub fn initialize(handles: Arc<Handles>) -> (Joiner, ScanHandle) {
|
||||
log::trace!("enter: initialize");
|
||||
|
||||
let data = Arc::new(FeroxScans::new(handles.config.output_level));
|
||||
let data = Arc::new(FeroxScans::new(
|
||||
handles.config.output_level,
|
||||
handles.config.limit_bars,
|
||||
));
|
||||
let (tx, rx): FeroxChannel<Command> = mpsc::unbounded_channel();
|
||||
|
||||
let max_depth = handles.config.depth;
|
||||
@@ -322,7 +325,9 @@ impl ScanHandler {
|
||||
let scan = if let Some(ferox_scan) = self.data.get_scan_by_url(&target) {
|
||||
ferox_scan // scan already known
|
||||
} else {
|
||||
self.data.add_directory_scan(&target, order).1 // add the new target; return FeroxScan
|
||||
self.data
|
||||
.add_directory_scan(&target, order, self.handles.clone())
|
||||
.1 // add the new target; return FeroxScan
|
||||
};
|
||||
|
||||
if should_test_deny
|
||||
|
||||
@@ -228,8 +228,11 @@ impl<'a> Extractor<'a> {
|
||||
if resp.is_file() || !resp.is_directory() {
|
||||
log::debug!("Extracted File: {}", resp);
|
||||
|
||||
c_scanned_urls
|
||||
.add_file_scan(resp.url().as_str(), ScanOrder::Latest);
|
||||
c_scanned_urls.add_file_scan(
|
||||
resp.url().as_str(),
|
||||
ScanOrder::Latest,
|
||||
c_handles.clone(),
|
||||
);
|
||||
|
||||
if c_handles.config.collect_extensions {
|
||||
// no real reason this should fail
|
||||
|
||||
@@ -386,7 +386,11 @@ async fn request_link_bails_on_seen_url() -> Result<()> {
|
||||
});
|
||||
|
||||
let scans = Arc::new(FeroxScans::default());
|
||||
scans.add_file_scan(&served, ScanOrder::Latest);
|
||||
scans.add_file_scan(
|
||||
&served,
|
||||
ScanOrder::Latest,
|
||||
Arc::new(Handles::for_testing(None, None).0),
|
||||
);
|
||||
|
||||
let robots = setup_extractor(ExtractionTarget::RobotsTxt, scans.clone());
|
||||
let body = setup_extractor(ExtractionTarget::ResponseBody, scans);
|
||||
|
||||
@@ -197,9 +197,9 @@ async fn get_targets(handles: Arc<Handles>) -> Result<Vec<String>> {
|
||||
}
|
||||
}
|
||||
|
||||
if !target.starts_with("http") && !target.starts_with("https") {
|
||||
if !target.starts_with("http") {
|
||||
// --url hackerone.com
|
||||
*target = format!("https://{target}");
|
||||
*target = format!("{}://{target}", handles.config.protocol);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -40,12 +40,12 @@ pub fn initialize() -> Command {
|
||||
Arg::new("url")
|
||||
.short('u')
|
||||
.long("url")
|
||||
.required_unless_present_any(["stdin", "resume_from", "update_app"])
|
||||
.required_unless_present_any(["stdin", "resume_from", "update_app", "request_file"])
|
||||
.help_heading("Target selection")
|
||||
.value_name("URL")
|
||||
.use_value_delimiter(true)
|
||||
.value_hint(ValueHint::Url)
|
||||
.help("The target URL (required, unless [--stdin || --resume-from] used)"),
|
||||
.help("The target URL (required, unless [--stdin || --resume-from || --request-file] used)"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("stdin")
|
||||
@@ -64,6 +64,15 @@ pub fn initialize() -> Command {
|
||||
.help("State file from which to resume a partially complete scan (ex. --resume-from ferox-1606586780.state)")
|
||||
.conflicts_with("url")
|
||||
.num_args(1),
|
||||
).arg(
|
||||
Arg::new("request_file")
|
||||
.long("request-file")
|
||||
.help_heading("Target selection")
|
||||
.value_hint(ValueHint::FilePath)
|
||||
.conflicts_with("url")
|
||||
.num_args(1)
|
||||
.value_name("REQUEST_FILE")
|
||||
.help("Raw HTTP request file to use as a template for all requests"),
|
||||
);
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
@@ -100,7 +109,7 @@ pub fn initialize() -> Command {
|
||||
.num_args(0)
|
||||
.help_heading("Composite settings")
|
||||
.conflicts_with_all(["rate_limit", "auto_bail"])
|
||||
.help("Use the same settings as --smart and set --collect-extensions to true"),
|
||||
.help("Use the same settings as --smart and set --collect-extensions and --scan-dir-listings to true"),
|
||||
);
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
@@ -248,6 +257,13 @@ pub fn initialize() -> Command {
|
||||
.help_heading("Request settings")
|
||||
.num_args(0)
|
||||
.help("Append / to each request's URL")
|
||||
).arg(
|
||||
Arg::new("protocol")
|
||||
.long("protocol")
|
||||
.value_name("PROTOCOL")
|
||||
.num_args(1)
|
||||
.help_heading("Request settings")
|
||||
.help("Specify the protocol to use when targeting via --request-file or --url with domain only (default: https)"),
|
||||
);
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
@@ -574,6 +590,12 @@ pub fn initialize() -> Command {
|
||||
.help(
|
||||
"File extension(s) to Ignore while collecting extensions (only used with --collect-extensions)",
|
||||
),
|
||||
).arg(
|
||||
Arg::new("scan_dir_listings")
|
||||
.long("scan-dir-listings")
|
||||
.num_args(0)
|
||||
.help_heading("Scan settings")
|
||||
.help("Force scans to recurse into directory listings")
|
||||
);
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
@@ -638,6 +660,13 @@ pub fn initialize() -> Command {
|
||||
.num_args(0)
|
||||
.help_heading("Output settings")
|
||||
.help("Disable state output file (*.state)")
|
||||
).arg(
|
||||
Arg::new("limit_bars")
|
||||
.long("limit-bars")
|
||||
.value_name("NUM_BARS_TO_SHOW")
|
||||
.num_args(1)
|
||||
.help_heading("Output settings")
|
||||
.help("Number of directory scan bars to show at any given time (default: no limit)"),
|
||||
);
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -31,6 +31,15 @@ pub enum BarType {
|
||||
/// Add an [indicatif::ProgressBar](https://docs.rs/indicatif/latest/indicatif/struct.ProgressBar.html)
|
||||
/// to the global [PROGRESS_BAR](../config/struct.PROGRESS_BAR.html)
|
||||
pub fn add_bar(prefix: &str, length: u64, bar_type: BarType) -> ProgressBar {
|
||||
let pb = ProgressBar::new(length).with_prefix(prefix.to_string());
|
||||
|
||||
update_style(&pb, bar_type);
|
||||
|
||||
PROGRESS_BAR.add(pb)
|
||||
}
|
||||
|
||||
/// Update the style of a progress bar based on the `BarType`
|
||||
pub fn update_style(bar: &ProgressBar, bar_type: BarType) {
|
||||
let mut style = ProgressStyle::default_bar().progress_chars("#>-").with_key(
|
||||
"smoothed_per_sec",
|
||||
|state: &indicatif::ProgressState, w: &mut dyn std::fmt::Write| match (
|
||||
@@ -66,11 +75,7 @@ pub fn add_bar(prefix: &str, length: u64, bar_type: BarType) -> ProgressBar {
|
||||
BarType::Quiet => style.template("Scanning: {prefix}").unwrap(),
|
||||
};
|
||||
|
||||
PROGRESS_BAR.add(
|
||||
ProgressBar::new(length)
|
||||
.with_style(style)
|
||||
.with_prefix(prefix.to_string()),
|
||||
)
|
||||
bar.set_style(style);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
use super::*;
|
||||
use crate::{
|
||||
config::OutputLevel,
|
||||
event_handlers::Handles,
|
||||
progress::update_style,
|
||||
progress::{add_bar, BarType},
|
||||
scan_manager::utils::determine_bar_type,
|
||||
scanner::PolicyTrigger,
|
||||
};
|
||||
use anyhow::Result;
|
||||
@@ -16,10 +19,20 @@ use std::{
|
||||
time::Instant,
|
||||
};
|
||||
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
||||
use tokio::{sync, task::JoinHandle};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Debug, Default, Copy, Clone)]
|
||||
pub enum Visibility {
|
||||
/// whether a FeroxScan's progress bar is currently shown
|
||||
#[default]
|
||||
Visible,
|
||||
|
||||
/// whether a FeroxScan's progress bar is currently hidden
|
||||
Hidden,
|
||||
}
|
||||
|
||||
/// Struct to hold scan-related state
|
||||
///
|
||||
/// The purpose of this container is to open up the pathway to aborting currently running tasks and
|
||||
@@ -58,7 +71,7 @@ pub struct FeroxScan {
|
||||
pub(super) task: sync::Mutex<Option<JoinHandle<()>>>,
|
||||
|
||||
/// The progress bar associated with this scan
|
||||
pub(super) progress_bar: Mutex<Option<ProgressBar>>,
|
||||
pub progress_bar: Mutex<Option<ProgressBar>>,
|
||||
|
||||
/// whether or not the user passed --silent|--quiet on the command line
|
||||
pub(super) output_level: OutputLevel,
|
||||
@@ -74,6 +87,12 @@ pub struct FeroxScan {
|
||||
|
||||
/// tracker for the time at which this scan was started
|
||||
pub(super) start_time: Instant,
|
||||
|
||||
/// whether the progress bar is currently visible or hidden
|
||||
pub(super) visible: AtomicBool,
|
||||
|
||||
/// handles object pointer
|
||||
pub(super) handles: Option<Arc<Handles>>,
|
||||
}
|
||||
|
||||
/// Default implementation for FeroxScan
|
||||
@@ -86,6 +105,7 @@ impl Default for FeroxScan {
|
||||
id: new_id,
|
||||
task: sync::Mutex::new(None), // tokio mutex
|
||||
status: Mutex::new(ScanStatus::default()),
|
||||
handles: None,
|
||||
num_requests: 0,
|
||||
requests_made_so_far: 0,
|
||||
scan_order: ScanOrder::Latest,
|
||||
@@ -98,14 +118,54 @@ impl Default for FeroxScan {
|
||||
status_429s: Default::default(),
|
||||
status_403s: Default::default(),
|
||||
start_time: Instant::now(),
|
||||
visible: AtomicBool::new(true),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Implementation of FeroxScan
|
||||
impl FeroxScan {
|
||||
/// return the visibility of the scan as a boolean
|
||||
pub fn visible(&self) -> bool {
|
||||
self.visible.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
pub fn swap_visibility(&self) {
|
||||
// fetch_xor toggles the boolean to its opposite and returns the previous value
|
||||
let visible = self.visible.fetch_xor(true, Ordering::Relaxed);
|
||||
|
||||
let Ok(bar) = self.progress_bar.lock() else {
|
||||
log::warn!("couldn't unlock progress bar for {}", self.url);
|
||||
return;
|
||||
};
|
||||
|
||||
if bar.is_none() {
|
||||
log::warn!("there is no progress bar for {}", self.url);
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(handles) = self.handles.as_ref() else {
|
||||
log::warn!("couldn't access handles pointer for {}", self.url);
|
||||
return;
|
||||
};
|
||||
|
||||
let bar_type = if !visible {
|
||||
// visibility was false before we xor'd the value
|
||||
match handles.config.output_level {
|
||||
OutputLevel::Default => BarType::Default,
|
||||
OutputLevel::Quiet => BarType::Quiet,
|
||||
OutputLevel::Silent | OutputLevel::SilentJSON => BarType::Hidden,
|
||||
}
|
||||
} else {
|
||||
// visibility was true before we xor'd the value
|
||||
BarType::Hidden
|
||||
};
|
||||
|
||||
update_style(bar.as_ref().unwrap(), bar_type);
|
||||
}
|
||||
|
||||
/// Stop a currently running scan
|
||||
pub async fn abort(&self) -> Result<()> {
|
||||
pub async fn abort(&self, active_bars: usize) -> Result<()> {
|
||||
log::trace!("enter: abort");
|
||||
|
||||
match self.task.try_lock() {
|
||||
@@ -114,7 +174,7 @@ impl FeroxScan {
|
||||
log::trace!("aborting {:?}", self);
|
||||
task.abort();
|
||||
self.set_status(ScanStatus::Cancelled)?;
|
||||
self.stop_progress_bar();
|
||||
self.stop_progress_bar(active_bars);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
@@ -151,15 +211,26 @@ impl FeroxScan {
|
||||
}
|
||||
|
||||
/// Simple helper to call .finish on the scan's progress bar
|
||||
pub(super) fn stop_progress_bar(&self) {
|
||||
pub(super) fn stop_progress_bar(&self, active_bars: usize) {
|
||||
if let Ok(guard) = self.progress_bar.lock() {
|
||||
if guard.is_some() {
|
||||
let pb = (*guard).as_ref().unwrap();
|
||||
|
||||
if pb.position() > self.num_requests {
|
||||
pb.finish()
|
||||
let bar_limit = if let Some(handles) = self.handles.as_ref() {
|
||||
handles.config.limit_bars
|
||||
} else {
|
||||
pb.abandon()
|
||||
0
|
||||
};
|
||||
|
||||
if bar_limit > 0 && bar_limit < active_bars {
|
||||
pb.finish_and_clear();
|
||||
return;
|
||||
}
|
||||
|
||||
if pb.position() > self.num_requests {
|
||||
pb.finish();
|
||||
} else {
|
||||
pb.abandon();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -172,12 +243,18 @@ impl FeroxScan {
|
||||
if guard.is_some() {
|
||||
(*guard).as_ref().unwrap().clone()
|
||||
} else {
|
||||
let bar_type = match self.output_level {
|
||||
OutputLevel::Default => BarType::Default,
|
||||
OutputLevel::Quiet => BarType::Quiet,
|
||||
OutputLevel::Silent | OutputLevel::SilentJSON => BarType::Hidden,
|
||||
let (active_bars, bar_limit) = if let Some(handles) = self.handles.as_ref() {
|
||||
if let Ok(scans) = handles.ferox_scans() {
|
||||
(scans.number_of_bars(), handles.config.limit_bars)
|
||||
} else {
|
||||
(0, handles.config.limit_bars)
|
||||
}
|
||||
} else {
|
||||
(0, 0)
|
||||
};
|
||||
|
||||
let bar_type = determine_bar_type(bar_limit, active_bars, self.output_level);
|
||||
|
||||
let pb = add_bar(&self.url, self.num_requests, bar_type);
|
||||
pb.reset_elapsed();
|
||||
|
||||
@@ -191,12 +268,18 @@ impl FeroxScan {
|
||||
Err(_) => {
|
||||
log::warn!("Could not unlock progress bar on {:?}", self);
|
||||
|
||||
let bar_type = match self.output_level {
|
||||
OutputLevel::Default => BarType::Default,
|
||||
OutputLevel::Quiet => BarType::Quiet,
|
||||
OutputLevel::Silent | OutputLevel::SilentJSON => BarType::Hidden,
|
||||
let (active_bars, bar_limit) = if let Some(handles) = self.handles.as_ref() {
|
||||
if let Ok(scans) = handles.ferox_scans() {
|
||||
(scans.number_of_bars(), handles.config.limit_bars)
|
||||
} else {
|
||||
(0, handles.config.limit_bars)
|
||||
}
|
||||
} else {
|
||||
(0, 0)
|
||||
};
|
||||
|
||||
let bar_type = determine_bar_type(bar_limit, active_bars, self.output_level);
|
||||
|
||||
let pb = add_bar(&self.url, self.num_requests, bar_type);
|
||||
pb.reset_elapsed();
|
||||
|
||||
@@ -206,6 +289,7 @@ impl FeroxScan {
|
||||
}
|
||||
|
||||
/// Given a URL and ProgressBar, create a new FeroxScan, wrap it in an Arc and return it
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
url: &str,
|
||||
scan_type: ScanType,
|
||||
@@ -213,6 +297,8 @@ impl FeroxScan {
|
||||
num_requests: u64,
|
||||
output_level: OutputLevel,
|
||||
pb: Option<ProgressBar>,
|
||||
visibility: bool,
|
||||
handles: Arc<Handles>,
|
||||
) -> Arc<Self> {
|
||||
Arc::new(Self {
|
||||
url: url.to_string(),
|
||||
@@ -222,14 +308,16 @@ impl FeroxScan {
|
||||
num_requests,
|
||||
output_level,
|
||||
progress_bar: Mutex::new(pb),
|
||||
visible: AtomicBool::new(visibility),
|
||||
handles: Some(handles),
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
/// Mark the scan as complete and stop the scan's progress bar
|
||||
pub fn finish(&self) -> Result<()> {
|
||||
pub fn finish(&self, active_bars: usize) -> Result<()> {
|
||||
self.set_status(ScanStatus::Complete)?;
|
||||
self.stop_progress_bar();
|
||||
self.stop_progress_bar(active_bars);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -262,6 +350,22 @@ impl FeroxScan {
|
||||
false
|
||||
}
|
||||
|
||||
/// small wrapper to inspect ScanStatus and see if it's Running
|
||||
pub fn is_running(&self) -> bool {
|
||||
if let Ok(guard) = self.status.lock() {
|
||||
return matches!(*guard, ScanStatus::Running);
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// small wrapper to inspect ScanStatus and see if it's NotStarted
|
||||
pub fn is_not_started(&self) -> bool {
|
||||
if let Ok(guard) = self.status.lock() {
|
||||
return matches!(*guard, ScanStatus::NotStarted);
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// await a task's completion, similar to a thread's join; perform necessary bookkeeping
|
||||
pub async fn join(&self) {
|
||||
log::trace!("enter join({:?})", self);
|
||||
@@ -507,6 +611,8 @@ mod tests {
|
||||
1000,
|
||||
OutputLevel::Default,
|
||||
None,
|
||||
true,
|
||||
Arc::new(Handles::for_testing(None, None).0),
|
||||
);
|
||||
|
||||
scan.add_error();
|
||||
@@ -532,6 +638,7 @@ mod tests {
|
||||
scan_order: ScanOrder::Initial,
|
||||
num_requests: 0,
|
||||
requests_made_so_far: 0,
|
||||
visible: AtomicBool::new(true),
|
||||
status: Mutex::new(ScanStatus::Running),
|
||||
task: Default::default(),
|
||||
progress_bar: Mutex::new(None),
|
||||
@@ -540,6 +647,7 @@ mod tests {
|
||||
status_429s: Default::default(),
|
||||
errors: Default::default(),
|
||||
start_time: Instant::now(),
|
||||
handles: None,
|
||||
};
|
||||
|
||||
let pb = scan.progress_bar();
|
||||
@@ -551,7 +659,62 @@ mod tests {
|
||||
|
||||
assert_eq!(req_sec, 100);
|
||||
|
||||
scan.finish().unwrap();
|
||||
scan.finish(0).unwrap();
|
||||
assert_eq!(scan.requests_per_second(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_swap_visibility() {
|
||||
let scan = FeroxScan::new(
|
||||
"http://localhost",
|
||||
ScanType::Directory,
|
||||
ScanOrder::Latest,
|
||||
1000,
|
||||
OutputLevel::Default,
|
||||
None,
|
||||
true,
|
||||
Arc::new(Handles::for_testing(None, None).0),
|
||||
);
|
||||
|
||||
assert!(scan.visible());
|
||||
|
||||
scan.swap_visibility();
|
||||
assert!(!scan.visible());
|
||||
|
||||
scan.swap_visibility();
|
||||
assert!(scan.visible());
|
||||
|
||||
scan.swap_visibility();
|
||||
assert!(!scan.visible());
|
||||
|
||||
scan.swap_visibility();
|
||||
assert!(scan.visible());
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// test for is_running method
|
||||
fn test_is_running() {
|
||||
let scan = FeroxScan::new(
|
||||
"http://localhost",
|
||||
ScanType::Directory,
|
||||
ScanOrder::Latest,
|
||||
1000,
|
||||
OutputLevel::Default,
|
||||
None,
|
||||
true,
|
||||
Arc::new(Handles::for_testing(None, None).0),
|
||||
);
|
||||
|
||||
assert!(scan.is_not_started());
|
||||
assert!(!scan.is_running());
|
||||
assert!(!scan.is_complete());
|
||||
assert!(!scan.is_cancelled());
|
||||
|
||||
*scan.status.lock().unwrap() = ScanStatus::Running;
|
||||
|
||||
assert!(!scan.is_not_started());
|
||||
assert!(scan.is_running());
|
||||
assert!(!scan.is_complete());
|
||||
assert!(!scan.is_cancelled());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ use crate::{
|
||||
config::OutputLevel,
|
||||
progress::PROGRESS_PRINTER,
|
||||
progress::{add_bar, BarType},
|
||||
scan_manager::utils::determine_bar_type,
|
||||
scan_manager::{MenuCmd, MenuCmdResult},
|
||||
scanner::RESPONSES,
|
||||
traits::FeroxSerialize,
|
||||
@@ -61,6 +62,9 @@ pub struct FeroxScans {
|
||||
|
||||
/// vector of extensions discovered and collected during scans
|
||||
pub(crate) collected_extensions: RwLock<HashSet<String>>,
|
||||
|
||||
/// stored value for Configuration.limit_bars
|
||||
bar_limit: usize,
|
||||
}
|
||||
|
||||
/// Serialize implementation for FeroxScans
|
||||
@@ -93,9 +97,10 @@ impl Serialize for FeroxScans {
|
||||
/// Implementation of `FeroxScans`
|
||||
impl FeroxScans {
|
||||
/// given an OutputLevel, create a new FeroxScans object
|
||||
pub fn new(output_level: OutputLevel) -> Self {
|
||||
pub fn new(output_level: OutputLevel, bar_limit: usize) -> Self {
|
||||
Self {
|
||||
output_level,
|
||||
bar_limit,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
@@ -388,8 +393,9 @@ impl FeroxScans {
|
||||
|
||||
if input == 'y' || input == '\n' {
|
||||
self.menu.println(&format!("Stopping {}...", selected.url));
|
||||
let active_bars = self.number_of_bars();
|
||||
selected
|
||||
.abort()
|
||||
.abort(active_bars)
|
||||
.await
|
||||
.unwrap_or_else(|e| log::warn!("Could not cancel task: {}", e));
|
||||
|
||||
@@ -521,14 +527,22 @@ impl FeroxScans {
|
||||
|
||||
/// if a resumed scan is already complete, display a completed progress bar to the user
|
||||
pub fn print_completed_bars(&self, bar_length: usize) -> Result<()> {
|
||||
let bar_type = match self.output_level {
|
||||
OutputLevel::Default => BarType::Message,
|
||||
OutputLevel::Quiet => BarType::Quiet,
|
||||
OutputLevel::Silent | OutputLevel::SilentJSON => return Ok(()), // fast exit when --silent was used
|
||||
};
|
||||
if self.output_level == OutputLevel::SilentJSON || self.output_level == OutputLevel::Silent
|
||||
{
|
||||
// fast exit when --silent was used
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let bar_type: BarType =
|
||||
determine_bar_type(self.bar_limit, self.number_of_bars(), self.output_level);
|
||||
|
||||
if let Ok(scans) = self.scans.read() {
|
||||
for scan in scans.iter() {
|
||||
if matches!(bar_type, BarType::Hidden) {
|
||||
// no need to show hidden bars
|
||||
continue;
|
||||
}
|
||||
|
||||
if scan.is_complete() {
|
||||
// these scans are complete, and just need to be shown to the user
|
||||
let pb = add_bar(
|
||||
@@ -605,6 +619,7 @@ impl FeroxScans {
|
||||
url: &str,
|
||||
scan_type: ScanType,
|
||||
scan_order: ScanOrder,
|
||||
handles: Arc<Handles>,
|
||||
) -> (bool, Arc<FeroxScan>) {
|
||||
let bar_length = if let Ok(guard) = self.bar_length.lock() {
|
||||
*guard
|
||||
@@ -612,14 +627,11 @@ impl FeroxScans {
|
||||
0
|
||||
};
|
||||
|
||||
let active_bars = self.number_of_bars();
|
||||
let bar_type = determine_bar_type(self.bar_limit, active_bars, self.output_level);
|
||||
|
||||
let bar = match scan_type {
|
||||
ScanType::Directory => {
|
||||
let bar_type = match self.output_level {
|
||||
OutputLevel::Default => BarType::Default,
|
||||
OutputLevel::Quiet => BarType::Quiet,
|
||||
OutputLevel::Silent | OutputLevel::SilentJSON => BarType::Hidden,
|
||||
};
|
||||
|
||||
let progress_bar = add_bar(url, bar_length, bar_type);
|
||||
|
||||
progress_bar.reset_elapsed();
|
||||
@@ -629,6 +641,8 @@ impl FeroxScans {
|
||||
ScanType::File => None,
|
||||
};
|
||||
|
||||
let is_visible = !matches!(bar_type, BarType::Hidden);
|
||||
|
||||
let ferox_scan = FeroxScan::new(
|
||||
url,
|
||||
scan_type,
|
||||
@@ -636,6 +650,8 @@ impl FeroxScans {
|
||||
bar_length,
|
||||
self.output_level,
|
||||
bar,
|
||||
is_visible,
|
||||
handles,
|
||||
);
|
||||
|
||||
// If the set did not contain the scan, true is returned.
|
||||
@@ -650,9 +666,14 @@ impl FeroxScans {
|
||||
/// If `FeroxScans` did not already contain the scan, return true; otherwise return false
|
||||
///
|
||||
/// Also return a reference to the new `FeroxScan`
|
||||
pub fn add_directory_scan(&self, url: &str, scan_order: ScanOrder) -> (bool, Arc<FeroxScan>) {
|
||||
pub fn add_directory_scan(
|
||||
&self,
|
||||
url: &str,
|
||||
scan_order: ScanOrder,
|
||||
handles: Arc<Handles>,
|
||||
) -> (bool, Arc<FeroxScan>) {
|
||||
let normalized = format!("{}/", url.trim_end_matches('/'));
|
||||
self.add_scan(&normalized, ScanType::Directory, scan_order)
|
||||
self.add_scan(&normalized, ScanType::Directory, scan_order, handles)
|
||||
}
|
||||
|
||||
/// Given a url, create a new `FeroxScan` and add it to `FeroxScans` as a File Scan
|
||||
@@ -660,8 +681,65 @@ impl FeroxScans {
|
||||
/// If `FeroxScans` did not already contain the scan, return true; otherwise return false
|
||||
///
|
||||
/// Also return a reference to the new `FeroxScan`
|
||||
pub fn add_file_scan(&self, url: &str, scan_order: ScanOrder) -> (bool, Arc<FeroxScan>) {
|
||||
self.add_scan(url, ScanType::File, scan_order)
|
||||
pub fn add_file_scan(
|
||||
&self,
|
||||
url: &str,
|
||||
scan_order: ScanOrder,
|
||||
handles: Arc<Handles>,
|
||||
) -> (bool, Arc<FeroxScan>) {
|
||||
self.add_scan(url, ScanType::File, scan_order, handles)
|
||||
}
|
||||
|
||||
/// returns the number of active AND visible scans; supports --limit-bars functionality
|
||||
pub fn number_of_bars(&self) -> usize {
|
||||
let Ok(scans) = self.scans.read() else {
|
||||
return 0;
|
||||
};
|
||||
|
||||
// starting at one ensures we don't have an extra bar
|
||||
// due to counting up from 0 when there's actually 1 bar
|
||||
let mut count = 1;
|
||||
|
||||
for scan in &*scans {
|
||||
if scan.is_active() && scan.visible() {
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
count
|
||||
}
|
||||
|
||||
/// make one hidden bar visible; supports --limit-bars functionality
|
||||
pub fn make_visible(&self) {
|
||||
if let Ok(guard) = self.scans.read() {
|
||||
// when swapping visibility, we'll prefer an actively running scan
|
||||
// if none are found, we'll
|
||||
let mut queued = None;
|
||||
|
||||
for scan in &*guard {
|
||||
if !matches!(scan.scan_type, ScanType::Directory) {
|
||||
// visibility only makes sense for directory scans
|
||||
continue;
|
||||
}
|
||||
|
||||
if scan.visible() {
|
||||
continue;
|
||||
}
|
||||
|
||||
if scan.is_running() {
|
||||
scan.swap_visibility();
|
||||
return;
|
||||
}
|
||||
|
||||
if queued.is_none() && scan.is_not_started() {
|
||||
queued = Some(scan.clone());
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(scan) = queued {
|
||||
scan.swap_visibility();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// small helper to determine whether any scans are active or not
|
||||
@@ -726,7 +804,7 @@ mod tests {
|
||||
#[test]
|
||||
/// unknown extension should be added to collected_extensions
|
||||
fn unknown_extension_is_added_to_collected_extensions() {
|
||||
let scans = FeroxScans::new(OutputLevel::Default);
|
||||
let scans = FeroxScans::new(OutputLevel::Default, 0);
|
||||
|
||||
assert_eq!(0, scans.collected_extensions.read().unwrap().len());
|
||||
|
||||
@@ -739,7 +817,7 @@ mod tests {
|
||||
#[test]
|
||||
/// known extension should not be added to collected_extensions
|
||||
fn known_extension_is_added_to_collected_extensions() {
|
||||
let scans = FeroxScans::new(OutputLevel::Default);
|
||||
let scans = FeroxScans::new(OutputLevel::Default, 0);
|
||||
scans
|
||||
.collected_extensions
|
||||
.write()
|
||||
|
||||
@@ -15,6 +15,7 @@ use crate::{
|
||||
use indicatif::ProgressBar;
|
||||
use predicates::prelude::*;
|
||||
use regex::Regex;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::{atomic::Ordering, Arc};
|
||||
use std::thread::sleep;
|
||||
use std::time::Instant;
|
||||
@@ -57,7 +58,12 @@ async fn scanner_pause_scan_with_finished_spinner() {
|
||||
fn add_url_to_list_of_scanned_urls_with_unknown_url() {
|
||||
let urls = FeroxScans::default();
|
||||
let url = "http://unknown_url";
|
||||
let (result, _scan) = urls.add_scan(url, ScanType::Directory, ScanOrder::Latest);
|
||||
let (result, _scan) = urls.add_scan(
|
||||
url,
|
||||
ScanType::Directory,
|
||||
ScanOrder::Latest,
|
||||
Arc::new(Handles::for_testing(None, None).0),
|
||||
);
|
||||
assert!(result);
|
||||
}
|
||||
|
||||
@@ -75,11 +81,18 @@ fn add_url_to_list_of_scanned_urls_with_known_url() {
|
||||
pb.length().unwrap(),
|
||||
OutputLevel::Default,
|
||||
Some(pb),
|
||||
true,
|
||||
Arc::new(Handles::for_testing(None, None).0),
|
||||
);
|
||||
|
||||
assert!(urls.insert(scan));
|
||||
|
||||
let (result, _scan) = urls.add_scan(url, ScanType::Directory, ScanOrder::Latest);
|
||||
let (result, _scan) = urls.add_scan(
|
||||
url,
|
||||
ScanType::Directory,
|
||||
ScanOrder::Latest,
|
||||
Arc::new(Handles::for_testing(None, None).0),
|
||||
);
|
||||
|
||||
assert!(!result);
|
||||
}
|
||||
@@ -97,6 +110,8 @@ fn stop_progress_bar_stops_bar() {
|
||||
pb.length().unwrap(),
|
||||
OutputLevel::Default,
|
||||
Some(pb),
|
||||
true,
|
||||
Arc::new(Handles::for_testing(None, None).0),
|
||||
);
|
||||
|
||||
assert!(!scan
|
||||
@@ -107,7 +122,7 @@ fn stop_progress_bar_stops_bar() {
|
||||
.unwrap()
|
||||
.is_finished());
|
||||
|
||||
scan.stop_progress_bar();
|
||||
scan.stop_progress_bar(0);
|
||||
|
||||
assert!(scan
|
||||
.progress_bar
|
||||
@@ -124,18 +139,25 @@ fn add_url_to_list_of_scanned_urls_with_known_url_without_slash() {
|
||||
let urls = FeroxScans::default();
|
||||
let url = "http://unknown_url";
|
||||
|
||||
let scan = FeroxScan::new(
|
||||
let scan: Arc<FeroxScan> = FeroxScan::new(
|
||||
url,
|
||||
ScanType::File,
|
||||
ScanOrder::Latest,
|
||||
0,
|
||||
OutputLevel::Default,
|
||||
None,
|
||||
true,
|
||||
Arc::new(Handles::for_testing(None, None).0),
|
||||
);
|
||||
|
||||
assert!(urls.insert(scan));
|
||||
|
||||
let (result, _scan) = urls.add_scan(url, ScanType::File, ScanOrder::Latest);
|
||||
let (result, _scan) = urls.add_scan(
|
||||
url,
|
||||
ScanType::File,
|
||||
ScanOrder::Latest,
|
||||
Arc::new(Handles::for_testing(None, None).0),
|
||||
);
|
||||
|
||||
assert!(!result);
|
||||
}
|
||||
@@ -155,6 +177,8 @@ async fn call_display_scans() {
|
||||
pb.length().unwrap(),
|
||||
OutputLevel::Default,
|
||||
Some(pb),
|
||||
true,
|
||||
Arc::new(Handles::for_testing(None, None).0),
|
||||
);
|
||||
let scan_two = FeroxScan::new(
|
||||
url_two,
|
||||
@@ -163,9 +187,11 @@ async fn call_display_scans() {
|
||||
pb_two.length().unwrap(),
|
||||
OutputLevel::Default,
|
||||
Some(pb_two),
|
||||
true,
|
||||
Arc::new(Handles::for_testing(None, None).0),
|
||||
);
|
||||
|
||||
scan_two.finish().unwrap(); // one complete, one incomplete
|
||||
scan_two.finish(0).unwrap(); // one complete, one incomplete
|
||||
scan_two
|
||||
.set_task(tokio::spawn(async move {
|
||||
sleep(Duration::from_millis(SLEEP_DURATION));
|
||||
@@ -190,6 +216,8 @@ fn partial_eq_compares_the_id_field() {
|
||||
0,
|
||||
OutputLevel::Default,
|
||||
None,
|
||||
true,
|
||||
Arc::new(Handles::for_testing(None, None).0),
|
||||
);
|
||||
let scan_two = FeroxScan::new(
|
||||
url,
|
||||
@@ -198,6 +226,8 @@ fn partial_eq_compares_the_id_field() {
|
||||
0,
|
||||
OutputLevel::Default,
|
||||
None,
|
||||
true,
|
||||
Arc::new(Handles::for_testing(None, None).0),
|
||||
);
|
||||
|
||||
assert!(!scan.eq(&scan_two));
|
||||
@@ -280,6 +310,8 @@ fn ferox_scan_serialize() {
|
||||
0,
|
||||
OutputLevel::Default,
|
||||
None,
|
||||
true,
|
||||
Arc::new(Handles::for_testing(None, None).0),
|
||||
);
|
||||
let fs_json = format!(
|
||||
r#"{{"id":"{}","url":"https://spiritanimal.com","normalized_url":"https://spiritanimal.com/","scan_type":"Directory","status":"NotStarted","num_requests":0,"requests_made_so_far":0}}"#,
|
||||
@@ -298,6 +330,8 @@ fn ferox_scans_serialize() {
|
||||
0,
|
||||
OutputLevel::Default,
|
||||
None,
|
||||
true,
|
||||
Arc::new(Handles::for_testing(None, None).0),
|
||||
);
|
||||
let ferox_scans = FeroxScans::default();
|
||||
let ferox_scans_json = format!(
|
||||
@@ -360,6 +394,8 @@ fn feroxstates_feroxserialize_implementation() {
|
||||
0,
|
||||
OutputLevel::Default,
|
||||
None,
|
||||
true,
|
||||
Arc::new(Handles::for_testing(None, None).0),
|
||||
);
|
||||
let ferox_scans = FeroxScans::default();
|
||||
let saved_id = ferox_scan.id.clone();
|
||||
@@ -501,12 +537,15 @@ fn feroxstates_feroxserialize_implementation() {
|
||||
r#""method":"GET""#,
|
||||
r#""content_length":173"#,
|
||||
r#""line_count":10"#,
|
||||
r#""limit_bars":0"#,
|
||||
r#""word_count":16"#,
|
||||
r#""headers""#,
|
||||
r#""server":"nginx/1.16.1"#,
|
||||
r#""collect_extensions":true"#,
|
||||
r#""collect_backups":false"#,
|
||||
r#""collect_words":false"#,
|
||||
r#""scan_dir_listings":false"#,
|
||||
r#""protocol":"https""#,
|
||||
r#""filters":[{"filter_code":100},{"word_count":200},{"content_length":300},{"line_count":400},{"compiled":".*","raw_string":".*"},{"hash":1,"original_url":"http://localhost:12345/"}]"#,
|
||||
r#""collected_extensions":["php"]"#,
|
||||
r#""dont_collect":["tif","tiff","ico","cur","bmp","webp","svg","png","jpg","jpeg","jfif","gif","avif","apng","pjpeg","pjp","mov","wav","mpg","mpeg","mp3","mp4","m4a","m4p","m4v","ogg","webm","ogv","oga","flac","aac","3gp","css","zip","xls","xml","gz","tgz"]"#,
|
||||
@@ -567,8 +606,10 @@ fn feroxscan_display() {
|
||||
normalized_url: String::from("http://localhost/"),
|
||||
scan_order: ScanOrder::Latest,
|
||||
scan_type: Default::default(),
|
||||
handles: Some(Arc::new(Handles::for_testing(None, None).0)),
|
||||
num_requests: 0,
|
||||
requests_made_so_far: 0,
|
||||
visible: AtomicBool::new(true),
|
||||
start_time: Instant::now(),
|
||||
output_level: OutputLevel::Default,
|
||||
status_403s: Default::default(),
|
||||
@@ -617,6 +658,7 @@ async fn ferox_scan_abort() {
|
||||
requests_made_so_far: 0,
|
||||
start_time: Instant::now(),
|
||||
output_level: OutputLevel::Default,
|
||||
visible: AtomicBool::new(true),
|
||||
status_403s: Default::default(),
|
||||
status_429s: Default::default(),
|
||||
status: std::sync::Mutex::new(ScanStatus::Running),
|
||||
@@ -625,9 +667,10 @@ async fn ferox_scan_abort() {
|
||||
}))),
|
||||
progress_bar: std::sync::Mutex::new(None),
|
||||
errors: Default::default(),
|
||||
handles: Some(Arc::new(Handles::for_testing(None, None).0)),
|
||||
};
|
||||
|
||||
scan.abort().await.unwrap();
|
||||
scan.abort(0).await.unwrap();
|
||||
|
||||
assert!(matches!(
|
||||
*scan.status.lock().unwrap(),
|
||||
@@ -718,15 +761,26 @@ fn split_to_nums_is_correct() {
|
||||
#[test]
|
||||
/// given a deep url, find the correct scan
|
||||
fn get_base_scan_by_url_finds_correct_scan() {
|
||||
let handles = Arc::new(Handles::for_testing(None, None).0);
|
||||
let urls = FeroxScans::default();
|
||||
let url = "http://localhost";
|
||||
let url1 = "http://localhost/stuff";
|
||||
let url2 = "http://shlocalhost/stuff/things";
|
||||
let url3 = "http://shlocalhost/stuff/things/mostuff";
|
||||
let (_, scan) = urls.add_scan(url, ScanType::Directory, ScanOrder::Latest);
|
||||
let (_, scan1) = urls.add_scan(url1, ScanType::Directory, ScanOrder::Latest);
|
||||
let (_, scan2) = urls.add_scan(url2, ScanType::Directory, ScanOrder::Latest);
|
||||
let (_, scan3) = urls.add_scan(url3, ScanType::Directory, ScanOrder::Latest);
|
||||
let (_, scan) = urls.add_scan(url, ScanType::Directory, ScanOrder::Latest, handles.clone());
|
||||
let (_, scan1) = urls.add_scan(
|
||||
url1,
|
||||
ScanType::Directory,
|
||||
ScanOrder::Latest,
|
||||
handles.clone(),
|
||||
);
|
||||
let (_, scan2) = urls.add_scan(
|
||||
url2,
|
||||
ScanType::Directory,
|
||||
ScanOrder::Latest,
|
||||
handles.clone(),
|
||||
);
|
||||
let (_, scan3) = urls.add_scan(url3, ScanType::Directory, ScanOrder::Latest, handles);
|
||||
|
||||
assert_eq!(
|
||||
urls.get_base_scan_by_url("http://localhost/things.php")
|
||||
@@ -759,7 +813,12 @@ fn get_base_scan_by_url_finds_correct_scan() {
|
||||
fn get_base_scan_by_url_finds_correct_scan_without_trailing_slash() {
|
||||
let urls = FeroxScans::default();
|
||||
let url = "http://localhost";
|
||||
let (_, scan) = urls.add_scan(url, ScanType::Directory, ScanOrder::Latest);
|
||||
let (_, scan) = urls.add_scan(
|
||||
url,
|
||||
ScanType::Directory,
|
||||
ScanOrder::Latest,
|
||||
Arc::new(Handles::for_testing(None, None).0),
|
||||
);
|
||||
assert_eq!(
|
||||
urls.get_base_scan_by_url("http://localhost/BKPMiherrortBPKcw")
|
||||
.unwrap()
|
||||
@@ -773,7 +832,12 @@ fn get_base_scan_by_url_finds_correct_scan_without_trailing_slash() {
|
||||
fn get_base_scan_by_url_finds_correct_scan_with_trailing_slash() {
|
||||
let urls = FeroxScans::default();
|
||||
let url = "http://127.0.0.1:41971/";
|
||||
let (_, scan) = urls.add_scan(url, ScanType::Directory, ScanOrder::Latest);
|
||||
let (_, scan) = urls.add_scan(
|
||||
url,
|
||||
ScanType::Directory,
|
||||
ScanOrder::Latest,
|
||||
Arc::new(Handles::for_testing(None, None).0),
|
||||
);
|
||||
assert_eq!(
|
||||
urls.get_base_scan_by_url("http://127.0.0.1:41971/BKPMiherrortBPKcw")
|
||||
.unwrap()
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
#[cfg(not(test))]
|
||||
use crate::event_handlers::TermInputHandler;
|
||||
use crate::{
|
||||
config::Configuration, event_handlers::Handles, parser::TIMESPEC_REGEX, scanner::RESPONSES,
|
||||
config::{Configuration, OutputLevel},
|
||||
event_handlers::Handles,
|
||||
parser::TIMESPEC_REGEX,
|
||||
progress::BarType,
|
||||
scan_manager::scan::Visibility,
|
||||
scanner::RESPONSES,
|
||||
};
|
||||
|
||||
use std::{fs::File, io::BufReader, sync::Arc};
|
||||
@@ -90,3 +95,79 @@ pub fn resume_scan(filename: &str) -> Configuration {
|
||||
log::trace!("exit: resume_scan -> {:?}", config);
|
||||
config
|
||||
}
|
||||
|
||||
/// determine the type of progress bar to display
|
||||
/// takes both --limit-bars and output-level (--quiet|--silent|etc)
|
||||
/// into account to arrive at a `BarType`
|
||||
pub fn determine_bar_type(
|
||||
bar_limit: usize,
|
||||
number_of_bars: usize,
|
||||
output_level: OutputLevel,
|
||||
) -> BarType {
|
||||
let visibility = if bar_limit == 0 {
|
||||
// no limit from cli, just set the value to visible
|
||||
// this protects us from a mutex unlock in number_of_bars
|
||||
// in the normal case
|
||||
Visibility::Visible
|
||||
} else if bar_limit < number_of_bars {
|
||||
// active bars exceed limit; hidden
|
||||
Visibility::Hidden
|
||||
} else {
|
||||
Visibility::Visible
|
||||
};
|
||||
|
||||
match (output_level, visibility) {
|
||||
(OutputLevel::Default, Visibility::Visible) => BarType::Default,
|
||||
(OutputLevel::Quiet, Visibility::Visible) => BarType::Quiet,
|
||||
(OutputLevel::Default, Visibility::Hidden) => BarType::Hidden,
|
||||
(OutputLevel::Quiet, Visibility::Hidden) => BarType::Hidden,
|
||||
(OutputLevel::Silent | OutputLevel::SilentJSON, _) => BarType::Hidden,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_no_limit_visible() {
|
||||
let bar_type = determine_bar_type(0, 1, OutputLevel::Default);
|
||||
assert!(matches!(bar_type, BarType::Default));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_limit_exceeded_hidden() {
|
||||
let bar_type = determine_bar_type(1, 2, OutputLevel::Default);
|
||||
assert!(matches!(bar_type, BarType::Hidden));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_limit_not_exceeded_visible() {
|
||||
let bar_type = determine_bar_type(2, 1, OutputLevel::Default);
|
||||
assert!(matches!(bar_type, BarType::Default));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_quiet_visible() {
|
||||
let bar_type = determine_bar_type(0, 1, OutputLevel::Quiet);
|
||||
assert!(matches!(bar_type, BarType::Quiet));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_quiet_hidden() {
|
||||
let bar_type = determine_bar_type(1, 2, OutputLevel::Quiet);
|
||||
assert!(matches!(bar_type, BarType::Hidden));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_silent_hidden() {
|
||||
let bar_type = determine_bar_type(0, 1, OutputLevel::Silent);
|
||||
assert!(matches!(bar_type, BarType::Hidden));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_silent_json_hidden() {
|
||||
let bar_type = determine_bar_type(0, 1, OutputLevel::SilentJSON);
|
||||
assert!(matches!(bar_type, BarType::Hidden));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -283,6 +283,14 @@ impl FeroxScanner {
|
||||
|
||||
let mut message = format!("=> {}", style("Directory listing").blue().bright());
|
||||
|
||||
if !self.handles.config.scan_dir_listings {
|
||||
write!(
|
||||
message,
|
||||
" (add {} to scan)",
|
||||
style("--scan-dir-listings").bright().yellow()
|
||||
)?;
|
||||
}
|
||||
|
||||
if !self.handles.config.extract_links {
|
||||
write!(
|
||||
message,
|
||||
@@ -291,7 +299,7 @@ impl FeroxScanner {
|
||||
)?;
|
||||
}
|
||||
|
||||
if !self.handles.config.force_recursion {
|
||||
if !self.handles.config.force_recursion && !self.handles.config.scan_dir_listings {
|
||||
for handle in extraction_tasks.into_iter().flatten() {
|
||||
_ = handle.await;
|
||||
}
|
||||
@@ -299,7 +307,14 @@ impl FeroxScanner {
|
||||
progress_bar.reset_eta();
|
||||
progress_bar.finish_with_message(message);
|
||||
|
||||
ferox_scan.finish()?;
|
||||
if self.handles.config.limit_bars > 0 {
|
||||
let scans = self.handles.ferox_scans()?;
|
||||
let num_bars = scans.number_of_bars();
|
||||
ferox_scan.finish(num_bars)?;
|
||||
scans.make_visible();
|
||||
} else {
|
||||
ferox_scan.finish(0)?;
|
||||
}
|
||||
|
||||
return Ok(()); // nothing left to do if we found a dir listing
|
||||
}
|
||||
@@ -382,7 +397,14 @@ impl FeroxScanner {
|
||||
_ = handle.await;
|
||||
}
|
||||
|
||||
ferox_scan.finish()?;
|
||||
if self.handles.config.limit_bars > 0 {
|
||||
let scans = self.handles.ferox_scans()?;
|
||||
let num_bars = scans.number_of_bars();
|
||||
ferox_scan.finish(num_bars)?;
|
||||
scans.make_visible();
|
||||
} else {
|
||||
ferox_scan.finish(0)?;
|
||||
}
|
||||
|
||||
log::trace!("exit: scan_url");
|
||||
|
||||
|
||||
@@ -313,9 +313,12 @@ impl Requester {
|
||||
.set_status(ScanStatus::Cancelled)
|
||||
.unwrap_or_else(|e| log::warn!("Could not set scan status: {}", e));
|
||||
|
||||
let scans = self.handles.ferox_scans()?;
|
||||
let active_bars = scans.number_of_bars();
|
||||
|
||||
// kill the scan
|
||||
self.ferox_scan
|
||||
.abort()
|
||||
.abort(active_bars)
|
||||
.await
|
||||
.unwrap_or_else(|e| log::warn!("Could not bail on scan: {}", e));
|
||||
|
||||
@@ -646,6 +649,8 @@ mod tests {
|
||||
1000,
|
||||
OutputLevel::Default,
|
||||
None,
|
||||
true,
|
||||
handles.clone(),
|
||||
);
|
||||
|
||||
scan.set_status(ScanStatus::Running).unwrap();
|
||||
@@ -1144,6 +1149,8 @@ mod tests {
|
||||
1000,
|
||||
OutputLevel::Default,
|
||||
None,
|
||||
true,
|
||||
Arc::new(Handles::for_testing(None, None).0),
|
||||
);
|
||||
scan.set_status(ScanStatus::Running).unwrap();
|
||||
scan.add_429();
|
||||
@@ -1177,7 +1184,7 @@ mod tests {
|
||||
200
|
||||
);
|
||||
|
||||
scan.finish().unwrap();
|
||||
scan.finish(0).unwrap();
|
||||
assert!(start.elapsed().as_millis() >= 2000);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ use super::*;
|
||||
/// try to hit struct field coverage of FileOutHandler
|
||||
async fn get_scan_by_url_bails_on_unfound_url() {
|
||||
let sem = Semaphore::new(10);
|
||||
let urls = FeroxScans::new(OutputLevel::Default);
|
||||
let urls = FeroxScans::new(OutputLevel::Default, 0);
|
||||
|
||||
let scanner = FeroxScanner::new(
|
||||
"http://localhost",
|
||||
|
||||
66
src/utils.rs
66
src/utils.rs
@@ -975,7 +975,11 @@ mod tests {
|
||||
let tested_url = Url::parse("https://testdomain.com/denied/").unwrap();
|
||||
|
||||
let scans = Arc::new(FeroxScans::default());
|
||||
scans.add_directory_scan(scan_url, ScanOrder::Initial);
|
||||
scans.add_directory_scan(
|
||||
scan_url,
|
||||
ScanOrder::Initial,
|
||||
Arc::new(Handles::for_testing(None, None).0),
|
||||
);
|
||||
|
||||
let mut config = Configuration::new().unwrap();
|
||||
config.url_denylist = vec![Url::parse(deny_url).unwrap()];
|
||||
@@ -994,7 +998,11 @@ mod tests {
|
||||
let tested_url = Url::parse("https://testdomain.com/denied/").unwrap();
|
||||
|
||||
let scans = Arc::new(FeroxScans::default());
|
||||
scans.add_directory_scan(scan_url, ScanOrder::Initial);
|
||||
scans.add_directory_scan(
|
||||
scan_url,
|
||||
ScanOrder::Initial,
|
||||
Arc::new(Handles::for_testing(None, None).0),
|
||||
);
|
||||
|
||||
let mut config = Configuration::new().unwrap();
|
||||
config.url_denylist = vec![Url::parse(deny_url).unwrap()];
|
||||
@@ -1013,7 +1021,11 @@ mod tests {
|
||||
let tested_url = Url::parse("https://testdomain.com/denied/").unwrap();
|
||||
|
||||
let scans = Arc::new(FeroxScans::default());
|
||||
scans.add_directory_scan(scan_url, ScanOrder::Initial);
|
||||
scans.add_directory_scan(
|
||||
scan_url,
|
||||
ScanOrder::Initial,
|
||||
Arc::new(Handles::for_testing(None, None).0),
|
||||
);
|
||||
|
||||
let mut config = Configuration::new().unwrap();
|
||||
config.url_denylist = vec![Url::parse(deny_url).unwrap()];
|
||||
@@ -1034,7 +1046,11 @@ mod tests {
|
||||
let tested_url = Url::parse("https://testdomain.com/denied/").unwrap();
|
||||
|
||||
let scans = Arc::new(FeroxScans::default());
|
||||
scans.add_directory_scan(scan_url, ScanOrder::Initial);
|
||||
scans.add_directory_scan(
|
||||
scan_url,
|
||||
ScanOrder::Initial,
|
||||
Arc::new(Handles::for_testing(None, None).0),
|
||||
);
|
||||
|
||||
let mut config = Configuration::new().unwrap();
|
||||
config.url_denylist = vec![Url::parse(deny_url).unwrap()];
|
||||
@@ -1062,7 +1078,11 @@ mod tests {
|
||||
let tested_url = Url::parse("https://testdomain.com/denied/").unwrap();
|
||||
|
||||
let scans = Arc::new(FeroxScans::default());
|
||||
scans.add_directory_scan(scan_url, ScanOrder::Initial);
|
||||
scans.add_directory_scan(
|
||||
scan_url,
|
||||
ScanOrder::Initial,
|
||||
Arc::new(Handles::for_testing(None, None).0),
|
||||
);
|
||||
|
||||
let mut config = Configuration::new().unwrap();
|
||||
config.url_denylist = vec![Url::parse(deny_url).unwrap()];
|
||||
@@ -1080,7 +1100,11 @@ mod tests {
|
||||
let tested_url = Url::parse("https://testdomain.com/api/denied/").unwrap();
|
||||
|
||||
let scans = Arc::new(FeroxScans::default());
|
||||
scans.add_directory_scan(scan_url, ScanOrder::Initial);
|
||||
scans.add_directory_scan(
|
||||
scan_url,
|
||||
ScanOrder::Initial,
|
||||
Arc::new(Handles::for_testing(None, None).0),
|
||||
);
|
||||
|
||||
let mut config = Configuration::new().unwrap();
|
||||
config.url_denylist = vec![Url::parse(deny_url).unwrap()];
|
||||
@@ -1099,7 +1123,11 @@ mod tests {
|
||||
let tested_url = Url::parse("https://testdomain.com/not-denied/").unwrap();
|
||||
|
||||
let scans = Arc::new(FeroxScans::default());
|
||||
scans.add_directory_scan(scan_url, ScanOrder::Initial);
|
||||
scans.add_directory_scan(
|
||||
scan_url,
|
||||
ScanOrder::Initial,
|
||||
Arc::new(Handles::for_testing(None, None).0),
|
||||
);
|
||||
|
||||
let mut config = Configuration::new().unwrap();
|
||||
config.url_denylist = vec![Url::parse(deny_url).unwrap()];
|
||||
@@ -1118,7 +1146,11 @@ mod tests {
|
||||
let tested_url = Url::parse("https://testdomain.com/stuff/").unwrap();
|
||||
|
||||
let scans = Arc::new(FeroxScans::default());
|
||||
scans.add_directory_scan(scan_url, ScanOrder::Initial);
|
||||
scans.add_directory_scan(
|
||||
scan_url,
|
||||
ScanOrder::Initial,
|
||||
Arc::new(Handles::for_testing(None, None).0),
|
||||
);
|
||||
|
||||
let mut config = Configuration::new().unwrap();
|
||||
config.url_denylist = vec![Url::parse(deny_url).unwrap()];
|
||||
@@ -1137,7 +1169,11 @@ mod tests {
|
||||
let tested_url = Url::parse("https://testdomain.com/api/not-denied/").unwrap();
|
||||
|
||||
let scans = Arc::new(FeroxScans::default());
|
||||
scans.add_directory_scan(scan_url, ScanOrder::Initial);
|
||||
scans.add_directory_scan(
|
||||
scan_url,
|
||||
ScanOrder::Initial,
|
||||
Arc::new(Handles::for_testing(None, None).0),
|
||||
);
|
||||
|
||||
let mut config = Configuration::new().unwrap();
|
||||
config.url_denylist = vec![Url::parse(deny_url).unwrap()];
|
||||
@@ -1157,7 +1193,11 @@ mod tests {
|
||||
let tested_url = Url::parse("https://testdomain.com/denied/").unwrap();
|
||||
|
||||
let scans = Arc::new(FeroxScans::default());
|
||||
scans.add_directory_scan(scan_url, ScanOrder::Initial);
|
||||
scans.add_directory_scan(
|
||||
scan_url,
|
||||
ScanOrder::Initial,
|
||||
Arc::new(Handles::for_testing(None, None).0),
|
||||
);
|
||||
|
||||
let mut config = Configuration::new().unwrap();
|
||||
config.regex_denylist = vec![Regex::new(deny_pattern).unwrap()];
|
||||
@@ -1178,7 +1218,11 @@ mod tests {
|
||||
let tested_https_url = Url::parse("https://testdomain.com/denied/").unwrap();
|
||||
|
||||
let scans = Arc::new(FeroxScans::default());
|
||||
scans.add_directory_scan(scan_url, ScanOrder::Initial);
|
||||
scans.add_directory_scan(
|
||||
scan_url,
|
||||
ScanOrder::Initial,
|
||||
Arc::new(Handles::for_testing(None, None).0),
|
||||
);
|
||||
|
||||
let mut config = Configuration::new().unwrap();
|
||||
config.regex_denylist = vec![Regex::new(deny_pattern).unwrap()];
|
||||
|
||||
@@ -1487,6 +1487,89 @@ fn banner_prints_force_recursion() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// test allows non-existent wordlist to trigger the banner printing to stderr
|
||||
/// expect to see all mandatory prints + scan-dir-listings
|
||||
fn banner_prints_scan_dir_listings() {
|
||||
Command::cargo_bin("feroxbuster")
|
||||
.unwrap()
|
||||
.arg("--url")
|
||||
.arg("http://localhost")
|
||||
.arg("--scan-dir-listings")
|
||||
.arg("--wordlist")
|
||||
.arg("/definitely/doesnt/exist/0cd7fed0-47f4-4b18-a1b0-ac39708c1676")
|
||||
.assert()
|
||||
.success()
|
||||
.stderr(
|
||||
predicate::str::contains("─┬─")
|
||||
.and(predicate::str::contains("Target Url"))
|
||||
.and(predicate::str::contains("http://localhost"))
|
||||
.and(predicate::str::contains("Threads"))
|
||||
.and(predicate::str::contains("Wordlist"))
|
||||
.and(predicate::str::contains("Status Codes"))
|
||||
.and(predicate::str::contains("Timeout (secs)"))
|
||||
.and(predicate::str::contains("User-Agent"))
|
||||
.and(predicate::str::contains("Scan Dir Listings"))
|
||||
.and(predicate::str::contains("─┴─")),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// test allows non-existent wordlist to trigger the banner printing to stderr
|
||||
/// expect to see all mandatory prints + protocol
|
||||
fn banner_prints_protocol() {
|
||||
Command::cargo_bin("feroxbuster")
|
||||
.unwrap()
|
||||
.arg("--url")
|
||||
.arg("localhost")
|
||||
.arg("--protocol")
|
||||
.arg("http")
|
||||
.arg("--wordlist")
|
||||
.arg("/definitely/doesnt/exist/0cd7fed0-47f4-4b18-a1b0-ac39708c1676")
|
||||
.assert()
|
||||
.success()
|
||||
.stderr(
|
||||
predicate::str::contains("─┬─")
|
||||
.and(predicate::str::contains("Target Url"))
|
||||
.and(predicate::str::contains("http://localhost"))
|
||||
.and(predicate::str::contains("Threads"))
|
||||
.and(predicate::str::contains("Wordlist"))
|
||||
.and(predicate::str::contains("Status Codes"))
|
||||
.and(predicate::str::contains("Timeout (secs)"))
|
||||
.and(predicate::str::contains("User-Agent"))
|
||||
.and(predicate::str::contains("Default Protocol"))
|
||||
.and(predicate::str::contains("─┴─")),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// test allows non-existent wordlist to trigger the banner printing to stderr
|
||||
/// expect to see all mandatory prints + protocol
|
||||
fn banner_prints_limit_dirs() {
|
||||
Command::cargo_bin("feroxbuster")
|
||||
.unwrap()
|
||||
.arg("--url")
|
||||
.arg("localhost")
|
||||
.arg("--limit-bars")
|
||||
.arg("3")
|
||||
.arg("--wordlist")
|
||||
.arg("/definitely/doesnt/exist/0cd7fed0-47f4-4b18-a1b0-ac39708c1676")
|
||||
.assert()
|
||||
.success()
|
||||
.stderr(
|
||||
predicate::str::contains("─┬─")
|
||||
.and(predicate::str::contains("Target Url"))
|
||||
.and(predicate::str::contains("http://localhost"))
|
||||
.and(predicate::str::contains("Threads"))
|
||||
.and(predicate::str::contains("Wordlist"))
|
||||
.and(predicate::str::contains("Status Codes"))
|
||||
.and(predicate::str::contains("Timeout (secs)"))
|
||||
.and(predicate::str::contains("User-Agent"))
|
||||
.and(predicate::str::contains("Limit Dir Scan Bars"))
|
||||
.and(predicate::str::contains("─┴─")),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// test allows non-existent wordlist to trigger the banner printing to stderr
|
||||
/// expect to see all mandatory prints + force recursion
|
||||
|
||||
@@ -58,7 +58,7 @@ fn auto_bail_cancels_scan_with_timeouts() {
|
||||
Command::cargo_bin("feroxbuster")
|
||||
.unwrap()
|
||||
.arg("--url")
|
||||
.arg(&srv.url("/"))
|
||||
.arg(srv.url("/"))
|
||||
.arg("--wordlist")
|
||||
.arg(file.as_os_str())
|
||||
.arg("--auto-bail")
|
||||
|
||||
@@ -430,6 +430,7 @@ fn scanner_single_request_scan_with_filtered_result() -> Result<(), Box<dyn std:
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic] // added in 2.11.0 for panicking trace-level logging
|
||||
/// send a single valid request, get a response, and write the logging messages to disk
|
||||
fn scanner_single_request_scan_with_debug_logging() {
|
||||
let srv = MockServer::start();
|
||||
@@ -467,6 +468,7 @@ fn scanner_single_request_scan_with_debug_logging() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic] // added in 2.11.0 for panicking trace-level logging
|
||||
/// send a single valid request, get a response, and write the logging messages to disk as NDJSON
|
||||
fn scanner_single_request_scan_with_debug_logging_as_json() {
|
||||
let srv = MockServer::start();
|
||||
|
||||
Reference in New Issue
Block a user