mirror of
https://github.com/epi052/feroxbuster.git
synced 2026-06-08 02:31:16 -03:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
030b588448 | ||
|
|
4ee143968e | ||
|
|
834d681bb9 | ||
|
|
fc35bb6764 | ||
|
|
8e2b08ce90 | ||
|
|
24a44ff253 | ||
|
|
0345e03e6a | ||
|
|
873539ac92 | ||
|
|
9c85f90faf | ||
|
|
1643643e77 | ||
|
|
a7e4cc914b | ||
|
|
6daa2a230a | ||
|
|
5486e3c95f | ||
|
|
204aa5e226 |
2
.github/pull_request_template.md
vendored
2
.github/pull_request_template.md
vendored
@@ -7,7 +7,7 @@ Long form explanations of most of the items below can be found in the [CONTRIBUT
|
|||||||
- [ ] Your PR description references the associated issue (i.e. fixes #123456)
|
- [ ] Your PR description references the associated issue (i.e. fixes #123456)
|
||||||
- [ ] Code is in its own branch
|
- [ ] Code is in its own branch
|
||||||
- [ ] Branch name is related to the PR contents
|
- [ ] Branch name is related to the PR contents
|
||||||
- [ ] PR targets master
|
- [ ] PR targets main
|
||||||
|
|
||||||
## Static analysis checks
|
## Static analysis checks
|
||||||
- [ ] All rust files are formatted using `cargo fmt`
|
- [ ] All rust files are formatted using `cargo fmt`
|
||||||
|
|||||||
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -633,7 +633,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "feroxbuster"
|
name = "feroxbuster"
|
||||||
version = "2.1.0"
|
version = "2.2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"assert_cmd",
|
"assert_cmd",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "feroxbuster"
|
name = "feroxbuster"
|
||||||
version = "2.1.0"
|
version = "2.2.0"
|
||||||
authors = ["Ben 'epi' Risher <epibar052@gmail.com>"]
|
authors = ["Ben 'epi' Risher <epibar052@gmail.com>"]
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
@@ -22,7 +22,7 @@ lazy_static = "1.4"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
futures = { version = "0.3"}
|
futures = { version = "0.3"}
|
||||||
tokio = { version = "1.0", features = ["full"] }
|
tokio = { version = "1.2.0", features = ["full"] }
|
||||||
tokio-util = {version = "0.6.3", features = ["codec"]}
|
tokio-util = {version = "0.6.3", features = ["codec"]}
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
env_logger = "0.8.3"
|
env_logger = "0.8.3"
|
||||||
@@ -31,7 +31,7 @@ clap = "2.33"
|
|||||||
lazy_static = "1.4"
|
lazy_static = "1.4"
|
||||||
toml = "0.5"
|
toml = "0.5"
|
||||||
serde = { version = "1.0", features = ["derive", "rc"] }
|
serde = { version = "1.0", features = ["derive", "rc"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0.62"
|
||||||
uuid = { version = "0.8", features = ["v4"] }
|
uuid = { version = "0.8", features = ["v4"] }
|
||||||
indicatif = "0.15"
|
indicatif = "0.15"
|
||||||
console = "0.14"
|
console = "0.14"
|
||||||
|
|||||||
29
README.md
29
README.md
@@ -105,6 +105,7 @@ Enumeration.
|
|||||||
- [Limit Number of Requests per Second (Rate Limiting) (new in `v2.0.0`)](#limit-number-of-requests-per-second-rate-limiting-new-in-v200)
|
- [Limit Number of Requests per Second (Rate Limiting) (new in `v2.0.0`)](#limit-number-of-requests-per-second-rate-limiting-new-in-v200)
|
||||||
- [Silence all Output or Be Kinda Quiet (new in `v2.0.0`)](#silence-all-output-or-be-kinda-quiet-new-in-v200)
|
- [Silence all Output or Be Kinda Quiet (new in `v2.0.0`)](#silence-all-output-or-be-kinda-quiet-new-in-v200)
|
||||||
- [Auto-tune or Auto-bail from Scans (new in `v2.1.0`)](#auto-tune-or-auto-bail-from-scans-new-in-v210)
|
- [Auto-tune or Auto-bail from Scans (new in `v2.1.0`)](#auto-tune-or-auto-bail-from-scans-new-in-v210)
|
||||||
|
- [Run Scans in Parallel (new in `v2.2.0`)](#run-scans-in-parallel-new-in-v220)
|
||||||
- [Comparison w/ Similar Tools](#-comparison-w-similar-tools)
|
- [Comparison w/ Similar Tools](#-comparison-w-similar-tools)
|
||||||
- [Common Problems/Issues (FAQ)](#-common-problemsissues-faq)
|
- [Common Problems/Issues (FAQ)](#-common-problemsissues-faq)
|
||||||
- [No file descriptors available](#no-file-descriptors-available)
|
- [No file descriptors available](#no-file-descriptors-available)
|
||||||
@@ -370,6 +371,7 @@ A pre-made configuration file with examples of all available settings can be fou
|
|||||||
# status_codes = [200, 500]
|
# status_codes = [200, 500]
|
||||||
# filter_status = [301]
|
# filter_status = [301]
|
||||||
# threads = 1
|
# threads = 1
|
||||||
|
# parallel = 2
|
||||||
# timeout = 5
|
# timeout = 5
|
||||||
# auto_tune = true
|
# auto_tune = true
|
||||||
# auto_bail = true
|
# auto_bail = true
|
||||||
@@ -463,6 +465,9 @@ OPTIONS:
|
|||||||
-W, --filter-words <WORDS>... Filter out messages of a particular word count (ex: -W 312 -W 91,82)
|
-W, --filter-words <WORDS>... Filter out messages of a particular word count (ex: -W 312 -W 91,82)
|
||||||
-H, --headers <HEADER>... Specify HTTP headers (ex: -H Header:val 'stuff: things')
|
-H, --headers <HEADER>... Specify HTTP headers (ex: -H Header:val 'stuff: things')
|
||||||
-o, --output <FILE> Output file to write results to (use w/ --json for JSON entries)
|
-o, --output <FILE> Output file to write results to (use w/ --json for JSON entries)
|
||||||
|
--parallel <PARALLEL_SCANS>
|
||||||
|
Run parallel feroxbuster instances (one child process per url passed via stdin)
|
||||||
|
|
||||||
-p, --proxy <PROXY>
|
-p, --proxy <PROXY>
|
||||||
Proxy to use for requests (ex: http(s)://host:port, socks5(h)://host:port)
|
Proxy to use for requests (ex: http(s)://host:port, socks5(h)://host:port)
|
||||||
|
|
||||||
@@ -898,6 +903,29 @@ The AutoBail policy aborts individual directory scans when one of the criteria a
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
### Run Scans in Parallel (new in `v2.2.0`)
|
||||||
|
|
||||||
|
Version 2.2.0 introduces the `--parallel` option. If you're one of those people who use `feroxbuster` to scan 100s of hosts at a time, this is the option for you! `--parallel` spawns a child process per target passed in over stdin (recursive directories are still async within each child).
|
||||||
|
|
||||||
|
The number of parallel scans is limited to whatever you pass to `--parallel`. When one child finishes its scan, the next child will be spawned.
|
||||||
|
|
||||||
|
Unfortunately, using `--parallel` limits terminal output such that only discovered URLs are shown. No amount of `-v`'s will help you here. I imagine this isn't too big of a deal, as folks that need `--parallel` probably aren't sitting there watching the output... 🙃
|
||||||
|
|
||||||
|
Example Command:
|
||||||
|
```
|
||||||
|
cat large-target-list | ./feroxbuster --stdin --parallel 10 --extract-links --auto-bail
|
||||||
|
```
|
||||||
|
|
||||||
|
Resuling Process List (illustrative):
|
||||||
|
```
|
||||||
|
\_ target/debug/feroxbuster --stdin --parallel 10
|
||||||
|
\_ target/debug/feroxbuster --silent --extract-links --auto-bail -u https://target-one
|
||||||
|
\_ target/debug/feroxbuster --silent --extract-links --auto-bail -u https://target-two
|
||||||
|
\_ target/debug/feroxbuster --silent --extract-links --auto-bail -u https://target-three
|
||||||
|
\_ ...
|
||||||
|
\_ target/debug/feroxbuster --silent --extract-links --auto-bail -u https://target-ten
|
||||||
|
```
|
||||||
|
|
||||||
## 🧐 Comparison w/ Similar Tools
|
## 🧐 Comparison w/ Similar Tools
|
||||||
|
|
||||||
There are quite a few similar tools for forced browsing/content discovery. Burp Suite Pro, Dirb, Dirbuster, etc...
|
There are quite a few similar tools for forced browsing/content discovery. Burp Suite Pro, Dirb, Dirbuster, etc...
|
||||||
@@ -947,6 +975,7 @@ few of the use-cases in which feroxbuster may be a better fit:
|
|||||||
| hide progress bars or be silent (or some variation) (`v2.0.0`) | ✔ | ✔ | ✔ |
|
| hide progress bars or be silent (or some variation) (`v2.0.0`) | ✔ | ✔ | ✔ |
|
||||||
| automatically tune scans based on errors/403s/429s (`v2.1.0`) | ✔ | | |
|
| automatically tune scans based on errors/403s/429s (`v2.1.0`) | ✔ | | |
|
||||||
| automatically stop scans based on errors/403s/429s (`v2.1.0`) | ✔ | | ✔ |
|
| automatically stop scans based on errors/403s/429s (`v2.1.0`) | ✔ | | ✔ |
|
||||||
|
| run scans in parallel (1 process per target) (`v2.2.0`) | ✔ | | |
|
||||||
| **huge** number of other options | | | ✔ |
|
| **huge** number of other options | | | ✔ |
|
||||||
|
|
||||||
Of note, there's another written-in-rust content discovery tool, [rustbuster](https://github.com/phra/rustbuster). I
|
Of note, there's another written-in-rust content discovery tool, [rustbuster](https://github.com/phra/rustbuster). I
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
# replay_proxy = "http://127.0.0.1:8081"
|
# replay_proxy = "http://127.0.0.1:8081"
|
||||||
# replay_codes = [200, 302]
|
# replay_codes = [200, 302]
|
||||||
# verbosity = 1
|
# verbosity = 1
|
||||||
|
# parallel = 8
|
||||||
# scan_limit = 6
|
# scan_limit = 6
|
||||||
# rate_limit = 250
|
# rate_limit = 250
|
||||||
# quiet = true
|
# quiet = true
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ _feroxbuster() {
|
|||||||
'*--filter-similar-to=[Filter out pages that are similar to the given page (ex. --filter-similar-to http://site.xyz/soft404)]' \
|
'*--filter-similar-to=[Filter out pages that are similar to the given page (ex. --filter-similar-to http://site.xyz/soft404)]' \
|
||||||
'-L+[Limit total number of concurrent scans (default: 0, i.e. no limit)]' \
|
'-L+[Limit total number of concurrent scans (default: 0, i.e. no limit)]' \
|
||||||
'--scan-limit=[Limit total number of concurrent scans (default: 0, i.e. no limit)]' \
|
'--scan-limit=[Limit total number of concurrent scans (default: 0, i.e. no limit)]' \
|
||||||
|
'--parallel=[Run parallel feroxbuster instances (one child process per url passed via stdin)]' \
|
||||||
'(--auto-tune)--rate-limit=[Limit number of requests per second (per directory) (default: 0, i.e. no limit)]' \
|
'(--auto-tune)--rate-limit=[Limit number of requests per second (per directory) (default: 0, i.e. no limit)]' \
|
||||||
'--time-limit=[Limit total run time of all scans (ex: --time-limit 10m)]' \
|
'--time-limit=[Limit total run time of all scans (ex: --time-limit 10m)]' \
|
||||||
'(--silent)*-v[Increase verbosity level (use -vv or more for greater effect. \[CAUTION\] 4 -v'\''s is probably too much)]' \
|
'(--silent)*-v[Increase verbosity level (use -vv or more for greater effect. \[CAUTION\] 4 -v'\''s is probably too much)]' \
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ Register-ArgumentCompleter -Native -CommandName 'feroxbuster' -ScriptBlock {
|
|||||||
[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('--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('-L', 'L', [CompletionResultType]::ParameterName, 'Limit total number of concurrent scans (default: 0, i.e. no limit)')
|
[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('--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('--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('--time-limit', 'time-limit', [CompletionResultType]::ParameterName, 'Limit total run time of all scans (ex: --time-limit 10m)')
|
||||||
[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('-v', 'v', [CompletionResultType]::ParameterName, 'Increase verbosity level (use -vv or more for greater effect. [CAUTION] 4 -v''s is probably too much)')
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ _feroxbuster() {
|
|||||||
|
|
||||||
case "${cmd}" in
|
case "${cmd}" in
|
||||||
feroxbuster)
|
feroxbuster)
|
||||||
opts=" -v -q -D -r -k -n -f -e -h -V -w -u -t -d -T -p -P -R -s -o -a -x -H -Q -S -X -W -N -C -L --verbosity --silent --quiet --auto-tune --auto-bail --json --dont-filter --redirects --insecure --no-recursion --add-slash --stdin --extract-links --help --version --wordlist --url --threads --depth --timeout --proxy --replay-proxy --replay-codes --status-codes --output --resume-from --debug-log --user-agent --extensions --headers --query --filter-size --filter-regex --filter-words --filter-lines --filter-status --filter-similar-to --scan-limit --rate-limit --time-limit "
|
opts=" -v -q -D -r -k -n -f -e -h -V -w -u -t -d -T -p -P -R -s -o -a -x -H -Q -S -X -W -N -C -L --verbosity --silent --quiet --auto-tune --auto-bail --json --dont-filter --redirects --insecure --no-recursion --add-slash --stdin --extract-links --help --version --wordlist --url --threads --depth --timeout --proxy --replay-proxy --replay-codes --status-codes --output --resume-from --debug-log --user-agent --extensions --headers --query --filter-size --filter-regex --filter-words --filter-lines --filter-status --filter-similar-to --scan-limit --parallel --rate-limit --time-limit "
|
||||||
if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then
|
if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then
|
||||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||||
return 0
|
return 0
|
||||||
@@ -199,6 +199,10 @@ _feroxbuster() {
|
|||||||
COMPREPLY=($(compgen -f "${cur}"))
|
COMPREPLY=($(compgen -f "${cur}"))
|
||||||
return 0
|
return 0
|
||||||
;;
|
;;
|
||||||
|
--parallel)
|
||||||
|
COMPREPLY=($(compgen -f "${cur}"))
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
--rate-limit)
|
--rate-limit)
|
||||||
COMPREPLY=($(compgen -f "${cur}"))
|
COMPREPLY=($(compgen -f "${cur}"))
|
||||||
return 0
|
return 0
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ complete -c feroxbuster -n "__fish_use_subcommand" -s N -l filter-lines -d 'Filt
|
|||||||
complete -c feroxbuster -n "__fish_use_subcommand" -s C -l filter-status -d 'Filter out status codes (deny list) (ex: -C 200 -C 401)'
|
complete -c feroxbuster -n "__fish_use_subcommand" -s C -l filter-status -d 'Filter out status codes (deny list) (ex: -C 200 -C 401)'
|
||||||
complete -c feroxbuster -n "__fish_use_subcommand" -l filter-similar-to -d 'Filter out pages that are similar to the given page (ex. --filter-similar-to http://site.xyz/soft404)'
|
complete -c feroxbuster -n "__fish_use_subcommand" -l filter-similar-to -d 'Filter out pages that are similar to the given page (ex. --filter-similar-to http://site.xyz/soft404)'
|
||||||
complete -c feroxbuster -n "__fish_use_subcommand" -s L -l scan-limit -d 'Limit total number of concurrent scans (default: 0, i.e. no limit)'
|
complete -c feroxbuster -n "__fish_use_subcommand" -s L -l scan-limit -d 'Limit total number of concurrent scans (default: 0, i.e. no limit)'
|
||||||
|
complete -c feroxbuster -n "__fish_use_subcommand" -l parallel -d 'Run parallel feroxbuster instances (one child process per url passed via stdin)'
|
||||||
complete -c feroxbuster -n "__fish_use_subcommand" -l rate-limit -d 'Limit number of requests per second (per directory) (default: 0, i.e. no limit)'
|
complete -c feroxbuster -n "__fish_use_subcommand" -l rate-limit -d 'Limit number of requests per second (per directory) (default: 0, i.e. no limit)'
|
||||||
complete -c feroxbuster -n "__fish_use_subcommand" -l time-limit -d 'Limit total run time of all scans (ex: --time-limit 10m)'
|
complete -c feroxbuster -n "__fish_use_subcommand" -l time-limit -d 'Limit total run time of all scans (ex: --time-limit 10m)'
|
||||||
complete -c feroxbuster -n "__fish_use_subcommand" -s v -l verbosity -d 'Increase verbosity level (use -vv or more for greater effect. [CAUTION] 4 -v\'s is probably too much)'
|
complete -c feroxbuster -n "__fish_use_subcommand" -s v -l verbosity -d 'Increase verbosity level (use -vv or more for greater effect. [CAUTION] 4 -v\'s is probably too much)'
|
||||||
|
|||||||
@@ -125,6 +125,9 @@ pub struct Banner {
|
|||||||
/// represents Configuration.rate_limit
|
/// represents Configuration.rate_limit
|
||||||
rate_limit: BannerEntry,
|
rate_limit: BannerEntry,
|
||||||
|
|
||||||
|
/// represents Configuration.parallel
|
||||||
|
parallel: BannerEntry,
|
||||||
|
|
||||||
/// represents Configuration.auto_tune
|
/// represents Configuration.auto_tune
|
||||||
auto_tune: BannerEntry,
|
auto_tune: BannerEntry,
|
||||||
|
|
||||||
@@ -281,6 +284,7 @@ impl Banner {
|
|||||||
BannerEntry::new("🤪", "Filter Wildcards", &(!config.dont_filter).to_string());
|
BannerEntry::new("🤪", "Filter Wildcards", &(!config.dont_filter).to_string());
|
||||||
let add_slash = BannerEntry::new("🪓", "Add Slash", &config.add_slash.to_string());
|
let add_slash = BannerEntry::new("🪓", "Add Slash", &config.add_slash.to_string());
|
||||||
let time_limit = BannerEntry::new("🕖", "Time Limit", &config.time_limit);
|
let time_limit = BannerEntry::new("🕖", "Time Limit", &config.time_limit);
|
||||||
|
let parallel = BannerEntry::new("🛤", "Parallel Scans", &config.parallel.to_string());
|
||||||
let rate_limit =
|
let rate_limit =
|
||||||
BannerEntry::new("🚧", "Requests per Second", &config.rate_limit.to_string());
|
BannerEntry::new("🚧", "Requests per Second", &config.rate_limit.to_string());
|
||||||
|
|
||||||
@@ -304,6 +308,7 @@ impl Banner {
|
|||||||
filter_line_count,
|
filter_line_count,
|
||||||
filter_regex,
|
filter_regex,
|
||||||
extract_links,
|
extract_links,
|
||||||
|
parallel,
|
||||||
json,
|
json,
|
||||||
queries,
|
queries,
|
||||||
output,
|
output,
|
||||||
@@ -518,6 +523,10 @@ by Ben "epi" Risher {} ver: {}"#,
|
|||||||
writeln!(&mut writer, "{}", self.scan_limit)?;
|
writeln!(&mut writer, "{}", self.scan_limit)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if config.parallel > 0 {
|
||||||
|
writeln!(&mut writer, "{}", self.parallel)?;
|
||||||
|
}
|
||||||
|
|
||||||
if config.rate_limit > 0 {
|
if config.rate_limit > 0 {
|
||||||
writeln!(&mut writer, "{}", self.rate_limit)?;
|
writeln!(&mut writer, "{}", self.rate_limit)?;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -198,6 +198,10 @@ pub struct Configuration {
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub scan_limit: usize,
|
pub scan_limit: usize,
|
||||||
|
|
||||||
|
/// Number of parallel scans permitted; a limit of 0 means no limit is imposed
|
||||||
|
#[serde(default)]
|
||||||
|
pub parallel: usize,
|
||||||
|
|
||||||
/// Number of requests per second permitted (per directory); a limit of 0 means no limit is imposed
|
/// Number of requests per second permitted (per directory); a limit of 0 means no limit is imposed
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub rate_limit: usize,
|
pub rate_limit: usize,
|
||||||
@@ -280,6 +284,7 @@ impl Default for Configuration {
|
|||||||
json: false,
|
json: false,
|
||||||
verbosity: 0,
|
verbosity: 0,
|
||||||
scan_limit: 0,
|
scan_limit: 0,
|
||||||
|
parallel: 0,
|
||||||
rate_limit: 0,
|
rate_limit: 0,
|
||||||
add_slash: false,
|
add_slash: false,
|
||||||
insecure: false,
|
insecure: false,
|
||||||
@@ -350,7 +355,8 @@ impl Configuration {
|
|||||||
/// - **dont_filter**: `false` (auto filter wildcard responses)
|
/// - **dont_filter**: `false` (auto filter wildcard responses)
|
||||||
/// - **depth**: `4` (maximum recursion depth)
|
/// - **depth**: `4` (maximum recursion depth)
|
||||||
/// - **scan_limit**: `0` (no limit on concurrent scans imposed)
|
/// - **scan_limit**: `0` (no limit on concurrent scans imposed)
|
||||||
/// - **rate_limit**: `0` (no limit on concurrent scans imposed)
|
/// - **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)
|
/// - **time_limit**: `None` (no limit on length of scan imposed)
|
||||||
/// - **replay_proxy**: `None` (no limit on concurrent scans imposed)
|
/// - **replay_proxy**: `None` (no limit on concurrent scans imposed)
|
||||||
/// - **replay_codes**: [`DEFAULT_RESPONSE_CODES`](constant.DEFAULT_RESPONSE_CODES.html)
|
/// - **replay_codes**: [`DEFAULT_RESPONSE_CODES`](constant.DEFAULT_RESPONSE_CODES.html)
|
||||||
@@ -486,6 +492,7 @@ impl Configuration {
|
|||||||
update_config_if_present!(&mut config.threads, args, "threads", usize);
|
update_config_if_present!(&mut config.threads, args, "threads", usize);
|
||||||
update_config_if_present!(&mut config.depth, args, "depth", usize);
|
update_config_if_present!(&mut config.depth, args, "depth", usize);
|
||||||
update_config_if_present!(&mut config.scan_limit, args, "scan_limit", usize);
|
update_config_if_present!(&mut config.scan_limit, args, "scan_limit", usize);
|
||||||
|
update_config_if_present!(&mut config.parallel, args, "parallel", usize);
|
||||||
update_config_if_present!(&mut config.rate_limit, args, "rate_limit", usize);
|
update_config_if_present!(&mut config.rate_limit, args, "rate_limit", usize);
|
||||||
update_config_if_present!(&mut config.wordlist, args, "wordlist", String);
|
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.output, args, "output", String);
|
||||||
@@ -793,6 +800,7 @@ impl Configuration {
|
|||||||
);
|
);
|
||||||
update_if_not_default!(&mut conf.dont_filter, new.dont_filter, false);
|
update_if_not_default!(&mut conf.dont_filter, new.dont_filter, false);
|
||||||
update_if_not_default!(&mut conf.scan_limit, new.scan_limit, 0);
|
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.rate_limit, new.rate_limit, 0);
|
||||||
update_if_not_default!(&mut conf.replay_proxy, new.replay_proxy, "");
|
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.debug_log, new.debug_log, "");
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ fn setup_config_test() -> Configuration {
|
|||||||
auto_bail = true
|
auto_bail = true
|
||||||
verbosity = 1
|
verbosity = 1
|
||||||
scan_limit = 6
|
scan_limit = 6
|
||||||
|
parallel = 14
|
||||||
rate_limit = 250
|
rate_limit = 250
|
||||||
time_limit = "10m"
|
time_limit = "10m"
|
||||||
output = "/some/otherpath"
|
output = "/some/otherpath"
|
||||||
@@ -146,6 +147,13 @@ fn config_reads_scan_limit() {
|
|||||||
assert_eq!(config.scan_limit, 6);
|
assert_eq!(config.scan_limit, 6);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
/// parse the test config and see that the value parsed is correct
|
||||||
|
fn config_reads_parallel() {
|
||||||
|
let config = setup_config_test();
|
||||||
|
assert_eq!(config.parallel, 14);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
/// parse the test config and see that the value parsed is correct
|
/// parse the test config and see that the value parsed is correct
|
||||||
fn config_reads_rate_limit() {
|
fn config_reads_rate_limit() {
|
||||||
|
|||||||
81
src/main.rs
81
src/main.rs
@@ -1,13 +1,19 @@
|
|||||||
use std::{
|
use std::{
|
||||||
collections::HashSet,
|
collections::HashSet,
|
||||||
|
env::args,
|
||||||
fs::File,
|
fs::File,
|
||||||
io::{stderr, BufRead, BufReader},
|
io::{stderr, BufRead, BufReader},
|
||||||
|
ops::Index,
|
||||||
|
process::Command,
|
||||||
sync::{atomic::Ordering, Arc},
|
sync::{atomic::Ordering, Arc},
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::{bail, Context, Result};
|
use anyhow::{bail, Context, Result};
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use tokio::{io, sync::oneshot};
|
use tokio::{
|
||||||
|
io,
|
||||||
|
sync::{oneshot, Semaphore},
|
||||||
|
};
|
||||||
use tokio_util::codec::{FramedRead, LinesCodec};
|
use tokio_util::codec::{FramedRead, LinesCodec};
|
||||||
|
|
||||||
use feroxbuster::{
|
use feroxbuster::{
|
||||||
@@ -26,6 +32,13 @@ use feroxbuster::{
|
|||||||
};
|
};
|
||||||
#[cfg(not(target_os = "windows"))]
|
#[cfg(not(target_os = "windows"))]
|
||||||
use feroxbuster::{utils::set_open_file_limit, DEFAULT_OPEN_FILE_LIMIT};
|
use feroxbuster::{utils::set_open_file_limit, DEFAULT_OPEN_FILE_LIMIT};
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use regex::Regex;
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
/// Limits the number of parallel scans active at any given time when using --parallel
|
||||||
|
static ref PARALLEL_LIMITER: Semaphore = Semaphore::new(0);
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a HashSet of Strings from the given wordlist then stores it inside an Arc
|
/// Create a HashSet of Strings from the given wordlist then stores it inside an Arc
|
||||||
fn get_unique_words_from_wordlist(path: &str) -> Result<Arc<HashSet<String>>> {
|
fn get_unique_words_from_wordlist(path: &str) -> Result<Arc<HashSet<String>>> {
|
||||||
@@ -226,6 +239,72 @@ async fn wrapped_main(config: Arc<Configuration>) -> Result<()> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// --parallel branch
|
||||||
|
if config.parallel > 0 {
|
||||||
|
log::trace!("enter: parallel branch");
|
||||||
|
|
||||||
|
PARALLEL_LIMITER.add_permits(config.parallel);
|
||||||
|
|
||||||
|
let invocation = args();
|
||||||
|
|
||||||
|
let para_regex =
|
||||||
|
Regex::new("--stdin|-q|--quiet|--silent|--verbosity|-v|-vv|-vvv|-vvvv").unwrap();
|
||||||
|
|
||||||
|
// remove stdin since only the original process will process targets
|
||||||
|
// remove quiet and silent so we can force silent later to normalize output
|
||||||
|
let mut original = invocation
|
||||||
|
.filter(|s| !para_regex.is_match(s))
|
||||||
|
.collect::<Vec<String>>();
|
||||||
|
|
||||||
|
original.push("--silent".to_string()); // only output modifier allowed
|
||||||
|
|
||||||
|
// we need remove --parallel from command line so we don't hit this branch over and over
|
||||||
|
// but we must remove --parallel N manually; the filter above never sees --parallel and the
|
||||||
|
// value passed to it at the same time, so can't filter them out in one pass
|
||||||
|
|
||||||
|
// unwrap is fine, as it has to be in the args for us to be in this code branch
|
||||||
|
let parallel_index = original.iter().position(|s| *s == "--parallel").unwrap();
|
||||||
|
|
||||||
|
// remove --parallel
|
||||||
|
original.remove(parallel_index);
|
||||||
|
|
||||||
|
// remove N passed to --parallel (it's the same index again since everything shifts
|
||||||
|
// from removing --parallel)
|
||||||
|
original.remove(parallel_index);
|
||||||
|
|
||||||
|
// unvalidated targets fresh from stdin, just spawn children and let them do all checks
|
||||||
|
for target in targets {
|
||||||
|
// add the current target to the provided command
|
||||||
|
let mut cloned = original.clone();
|
||||||
|
cloned.push("-u".to_string());
|
||||||
|
cloned.push(target);
|
||||||
|
|
||||||
|
let bin = cloned.index(0).to_owned(); // user's path to feroxbuster
|
||||||
|
let args = cloned.index(1..).to_vec(); // and args
|
||||||
|
|
||||||
|
let permit = PARALLEL_LIMITER.acquire().await?;
|
||||||
|
|
||||||
|
log::debug!("parallel exec: {} {}", bin, args.join(" "));
|
||||||
|
|
||||||
|
tokio::task::spawn_blocking(move || {
|
||||||
|
let result = Command::new(bin)
|
||||||
|
.args(&args)
|
||||||
|
.spawn()
|
||||||
|
.expect("failed to spawn a child process")
|
||||||
|
.wait()
|
||||||
|
.expect("child process errored during execution");
|
||||||
|
|
||||||
|
drop(permit);
|
||||||
|
result
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
clean_up(handles, tasks).await?;
|
||||||
|
|
||||||
|
log::trace!("exit: parallel branch && wrapped main");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
if matches!(config.output_level, OutputLevel::Default) {
|
if matches!(config.output_level, OutputLevel::Default) {
|
||||||
// only print banner if output level is default (no banner on --quiet|--silent)
|
// only print banner if output level is default (no banner on --quiet|--silent)
|
||||||
let std_stderr = stderr(); // std::io::stderr
|
let std_stderr = stderr(); // std::io::stderr
|
||||||
|
|||||||
@@ -348,6 +348,14 @@ pub fn initialize() -> App<'static, 'static> {
|
|||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
.help("Limit total number of concurrent scans (default: 0, i.e. no limit)")
|
.help("Limit total number of concurrent scans (default: 0, i.e. no limit)")
|
||||||
)
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("parallel")
|
||||||
|
.long("parallel")
|
||||||
|
.value_name("PARALLEL_SCANS")
|
||||||
|
.takes_value(true)
|
||||||
|
.requires("stdin")
|
||||||
|
.help("Run parallel feroxbuster instances (one child process per url passed via stdin)")
|
||||||
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("rate_limit")
|
Arg::with_name("rate_limit")
|
||||||
.long("rate-limit")
|
.long("rate-limit")
|
||||||
|
|||||||
@@ -383,7 +383,7 @@ fn feroxstates_feroxserialize_implementation() {
|
|||||||
|
|
||||||
let json_state = ferox_state.as_json().unwrap();
|
let json_state = ferox_state.as_json().unwrap();
|
||||||
let expected = format!(
|
let expected = format!(
|
||||||
r#"{{"scans":[{{"id":"{}","url":"https://spiritanimal.com","scan_type":"Directory","status":"NotStarted","num_requests":0}}],"config":{{"type":"configuration","wordlist":"/usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt","config":"","proxy":"","replay_proxy":"","target_url":"","status_codes":[200,204,301,302,307,308,401,403,405],"replay_codes":[200,204,301,302,307,308,401,403,405],"filter_status":[],"threads":50,"timeout":7,"verbosity":0,"silent":false,"quiet":false,"auto_bail":false,"auto_tune":false,"json":false,"output":"","debug_log":"","user_agent":"feroxbuster/{}","redirects":false,"insecure":false,"extensions":[],"headers":{{}},"queries":[],"no_recursion":false,"extract_links":false,"add_slash":false,"stdin":false,"depth":4,"scan_limit":0,"rate_limit":0,"filter_size":[],"filter_line_count":[],"filter_word_count":[],"filter_regex":[],"dont_filter":false,"resumed":false,"resume_from":"","save_state":false,"time_limit":"","filter_similar":[]}},"responses":[{{"type":"response","url":"https://nerdcore.com/css","path":"/css","wildcard":true,"status":301,"content_length":173,"line_count":10,"word_count":16,"headers":{{"server":"nginx/1.16.1"}}}}]"#,
|
r#"{{"scans":[{{"id":"{}","url":"https://spiritanimal.com","scan_type":"Directory","status":"NotStarted","num_requests":0}}],"config":{{"type":"configuration","wordlist":"/usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt","config":"","proxy":"","replay_proxy":"","target_url":"","status_codes":[200,204,301,302,307,308,401,403,405],"replay_codes":[200,204,301,302,307,308,401,403,405],"filter_status":[],"threads":50,"timeout":7,"verbosity":0,"silent":false,"quiet":false,"auto_bail":false,"auto_tune":false,"json":false,"output":"","debug_log":"","user_agent":"feroxbuster/{}","redirects":false,"insecure":false,"extensions":[],"headers":{{}},"queries":[],"no_recursion":false,"extract_links":false,"add_slash":false,"stdin":false,"depth":4,"scan_limit":0,"parallel":0,"rate_limit":0,"filter_size":[],"filter_line_count":[],"filter_word_count":[],"filter_regex":[],"dont_filter":false,"resumed":false,"resume_from":"","save_state":false,"time_limit":"","filter_similar":[]}},"responses":[{{"type":"response","url":"https://nerdcore.com/css","path":"/css","wildcard":true,"status":301,"content_length":173,"line_count":10,"word_count":16,"headers":{{"server":"nginx/1.16.1"}}}}]"#,
|
||||||
saved_id, VERSION
|
saved_id, VERSION
|
||||||
);
|
);
|
||||||
println!("{}\n{}", expected, json_state);
|
println!("{}\n{}", expected, json_state);
|
||||||
|
|||||||
@@ -948,3 +948,27 @@ fn banner_doesnt_print_when_quiet() {
|
|||||||
.and(predicate::str::contains("User-Agent").not()),
|
.and(predicate::str::contains("User-Agent").not()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
/// test allows non-existent wordlist to trigger the banner printing to stderr
|
||||||
|
/// expect to see nothing as --parallel forces --silent to be true
|
||||||
|
fn banner_prints_parallel() {
|
||||||
|
Command::cargo_bin("feroxbuster")
|
||||||
|
.unwrap()
|
||||||
|
.arg("--stdin")
|
||||||
|
.arg("--parallel")
|
||||||
|
.arg("4316")
|
||||||
|
.assert()
|
||||||
|
.success()
|
||||||
|
.stderr(
|
||||||
|
predicate::str::contains("─┬─")
|
||||||
|
.not()
|
||||||
|
.and(predicate::str::contains("Target Url").not())
|
||||||
|
.and(predicate::str::contains("Parallel Scans").not())
|
||||||
|
.and(predicate::str::contains("Threads").not())
|
||||||
|
.and(predicate::str::contains("Wordlist").not())
|
||||||
|
.and(predicate::str::contains("Status Codes").not())
|
||||||
|
.and(predicate::str::contains("Timeout (secs)").not())
|
||||||
|
.and(predicate::str::contains("User-Agent").not()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
mod utils;
|
mod utils;
|
||||||
use assert_cmd::Command;
|
use assert_cmd::Command;
|
||||||
use httpmock::Method::GET;
|
use httpmock::Method::GET;
|
||||||
use httpmock::MockServer;
|
use httpmock::{MockServer, Regex};
|
||||||
use predicates::prelude::*;
|
use predicates::prelude::*;
|
||||||
|
use std::fs::read_to_string;
|
||||||
use utils::{setup_tmp_directory, teardown_tmp_directory};
|
use utils::{setup_tmp_directory, teardown_tmp_directory};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -89,3 +90,66 @@ fn main_use_empty_stdin_targets() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
/// send three targets over stdin, expect parallel to spawn children and each child config to show
|
||||||
|
/// up in the output file
|
||||||
|
fn main_parallel_spawns_children() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let t1 = MockServer::start();
|
||||||
|
let t2 = MockServer::start();
|
||||||
|
let t3 = MockServer::start();
|
||||||
|
|
||||||
|
let words = [
|
||||||
|
String::from("LICENSE"),
|
||||||
|
String::from("stuff"),
|
||||||
|
String::from("things"),
|
||||||
|
String::from("mostuff"),
|
||||||
|
String::from("mothings"),
|
||||||
|
];
|
||||||
|
let (word_tmp_dir, wordlist) = setup_tmp_directory(&words, "wordlist")?;
|
||||||
|
let (output_dir, outfile) = setup_tmp_directory(&[], "output-file")?;
|
||||||
|
let (tgt_tmp_dir, targets) =
|
||||||
|
setup_tmp_directory(&[t1.url("/"), t2.url("/"), t3.url("/")], "targets")?;
|
||||||
|
|
||||||
|
Command::cargo_bin("feroxbuster")
|
||||||
|
.unwrap()
|
||||||
|
.arg("--stdin")
|
||||||
|
.arg("--parallel")
|
||||||
|
.arg("2")
|
||||||
|
.arg("-vvvv")
|
||||||
|
.arg("--debug-log")
|
||||||
|
.arg(outfile.as_os_str())
|
||||||
|
.arg("--wordlist")
|
||||||
|
.arg(wordlist.as_os_str())
|
||||||
|
.pipe_stdin(targets)
|
||||||
|
.unwrap()
|
||||||
|
.assert()
|
||||||
|
.success()
|
||||||
|
.stderr(
|
||||||
|
predicate::str::contains("Could not connect to any target provided")
|
||||||
|
.and(predicate::str::contains("Target Url"))
|
||||||
|
.not(), // no target url found
|
||||||
|
);
|
||||||
|
|
||||||
|
let contents = read_to_string(outfile).unwrap();
|
||||||
|
println!("contents: {}", contents);
|
||||||
|
|
||||||
|
assert!(contents.contains("parallel branch && wrapped main")); // exits parallel branch
|
||||||
|
|
||||||
|
// DBG 0.007 feroxbuster parallel exec: target/debug/feroxbuster
|
||||||
|
// --debug-log /tmp/.tmpAjRts6/output-file --wordlist /tmp/.tmpS4CKKq/wordlist
|
||||||
|
// --silent -u http://127.0.0.1:41979/
|
||||||
|
let r1 = Regex::new(&format!("parallel exec:.*-u {}", t1.url("/"))).unwrap();
|
||||||
|
let r2 = Regex::new(&format!("parallel exec:.*-u {}", t2.url("/"))).unwrap();
|
||||||
|
let r3 = Regex::new(&format!("parallel exec:.*-u {}", t3.url("/"))).unwrap();
|
||||||
|
|
||||||
|
assert!(r1.is_match(&contents)); // all 3 were spawned
|
||||||
|
assert!(r2.is_match(&contents));
|
||||||
|
assert!(r3.is_match(&contents));
|
||||||
|
|
||||||
|
teardown_tmp_directory(word_tmp_dir);
|
||||||
|
teardown_tmp_directory(tgt_tmp_dir);
|
||||||
|
teardown_tmp_directory(output_dir);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user