From 1c364b0a215c0f17ec9061a9dc88d22a92a219ba Mon Sep 17 00:00:00 2001 From: epi Date: Sat, 24 Oct 2020 15:59:53 -0500 Subject: [PATCH] added --scan-limit option --- Cargo.toml | 2 +- README.md | 8 +++++--- ferox-config.toml.example | 1 + src/banner.rs | 9 +++++++++ src/config.rs | 22 ++++++++++++++++++++++ src/parser.rs | 9 ++++++++- tests/test_banner.rs | 28 ++++++++++++++++++++++++++++ 7 files changed, 74 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 889e618..3f92644 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "feroxbuster" -version = "1.1.2" +version = "1.2.0" authors = ["Ben 'epi' Risher "] license = "MIT" edition = "2018" diff --git a/README.md b/README.md index 932efc2..552fa8d 100644 --- a/README.md +++ b/README.md @@ -237,6 +237,7 @@ Configuration begins with with the following built-in default values baked into - wordlist: `/usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt` - threads: `50` - verbosity: `0` (no logging enabled) +- scan_limit: `0` (no limit imposed on concurrent scans) - statuscodes: `200 204 301 302 307 308 401 403 405` - useragent: `feroxbuster/VERSION` - recursion depth: `4` @@ -293,6 +294,7 @@ A pre-made configuration file with examples of all available settings can be fou # timeout = 5 # proxy = "http://127.0.0.1:8080" # verbosity = 1 +# scan_limit = 6 # quiet = true # output = "/targets/ellingson_mineral_company/gibson.txt" # useragent = "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0" @@ -350,6 +352,7 @@ OPTIONS: -o, --output Output file to write results to (default: stdout) -p, --proxy Proxy to use for requests (ex: http(s)://host:port, socks5://host:port) -Q, --query ... Specify URL query parameters (ex: -Q token=stuff -Q secret=key) + -L, --scan-limit Limit total number of concurrent scans (default: 7) -S, --sizefilter ... Filter out messages of a particular size (ex: -S 5120 -S 4927,1970) -s, --statuscodes ... Status Codes of interest (default: 200 204 301 302 307 308 401 403 405) -t, --threads Number of concurrent threads (default: 50) @@ -423,13 +426,12 @@ cat targets | ./feroxbuster --stdin --quiet -s 200 301 302 --redirects -x js | f ./feroxbuster -u http://127.1 --proxy socks5://127.0.0.1:9050 ``` -### Pass auth token via query parameter +### Pass auth token via query parameter and limit number of concurrent scans ``` -./feroxbuster -u http://127.1 --query token=0123456789ABCDEF +./feroxbuster -u http://127.1 --query token=0123456789ABCDEF --scan-limit 6 ``` - ## 🧐 Comparison w/ Similar Tools There are quite a few similar tools for forced browsing/content discovery. Burp Suite Pro, Dirb, Dirbuster, etc... diff --git a/ferox-config.toml.example b/ferox-config.toml.example index 6f0e8fc..10a5235 100644 --- a/ferox-config.toml.example +++ b/ferox-config.toml.example @@ -13,6 +13,7 @@ # timeout = 5 # proxy = "http://127.0.0.1:8080" # verbosity = 1 +# scan_limit = 6 # quiet = true # output = "/targets/ellingson_mineral_company/gibson.txt" # useragent = "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0" diff --git a/src/banner.rs b/src/banner.rs index ffdaec8..fe94b54 100644 --- a/src/banner.rs +++ b/src/banner.rs @@ -389,6 +389,15 @@ by Ben "epi" Risher {} ver: {}"#, .unwrap_or_default(); // 🚫 } + if CONFIGURATION.scan_limit > 0 { + writeln!( + &mut writer, + "{}", + format_banner_entry!("\u{1f9a5}", "Concurrent Scan Limit", config.scan_limit) + ) + .unwrap_or_default(); // 🦥 + } + if matches!(status, UpdateStatus::OutOfDate) { writeln!( &mut writer, diff --git a/src/config.rs b/src/config.rs index 4c0c714..2102512 100644 --- a/src/config.rs +++ b/src/config.rs @@ -123,6 +123,10 @@ pub struct Configuration { #[serde(default = "depth")] pub depth: usize, + /// Number of concurrent scans permitted; a limit of 0 means no limit is imposed + #[serde(default)] + pub scan_limit: usize, + /// Filter out messages of a particular size #[serde(default)] pub sizefilters: Vec, @@ -184,6 +188,7 @@ impl Default for Configuration { quiet: false, stdin: false, verbosity: 0, + scan_limit: 0, addslash: false, insecure: false, redirects: false, @@ -232,6 +237,7 @@ impl Configuration { /// - **stdin**: `false` /// - **dontfilter**: `false` (auto filter wildcard responses) /// - **depth**: `4` (maximum recursion depth) + /// - **scan_limit**: `0` (no limit on concurrent scans imposed) /// /// After which, any values defined in a /// [ferox-config.toml](constant.DEFAULT_CONFIG_NAME.html) config file will override the @@ -316,6 +322,12 @@ impl Configuration { config.depth = depth; } + if args.value_of("scan_limit").is_some() { + let scan_limit = + value_t!(args.value_of("scan_limit"), usize).unwrap_or_else(|e| e.exit()); + config.scan_limit = scan_limit; + } + if args.value_of("wordlist").is_some() { config.wordlist = String::from(args.value_of("wordlist").unwrap()); } @@ -534,6 +546,7 @@ impl Configuration { settings.depth = settings_to_merge.depth; settings.sizefilters = settings_to_merge.sizefilters; settings.dontfilter = settings_to_merge.dontfilter; + settings.scan_limit = settings_to_merge.scan_limit; } /// If present, read in `DEFAULT_CONFIG_NAME` and deserialize the specified values @@ -575,6 +588,7 @@ mod tests { proxy = "http://127.0.0.1:8080" quiet = true verbosity = 1 + scan_limit = 6 output = "/some/otherpath" redirects = true insecure = true @@ -608,6 +622,7 @@ mod tests { assert_eq!(config.depth, depth()); assert_eq!(config.timeout, timeout()); assert_eq!(config.verbosity, 0); + assert_eq!(config.scan_limit, 0); assert_eq!(config.quiet, false); assert_eq!(config.dontfilter, false); assert_eq!(config.norecursion, false); @@ -650,6 +665,13 @@ mod tests { assert_eq!(config.depth, 1); } + #[test] + /// parse the test config and see that the value parsed is correct + fn config_reads_scan_limit() { + let config = setup_config_test(); + assert_eq!(config.scan_limit, 6); + } + #[test] /// parse the test config and see that the value parsed is correct fn config_reads_timeout() { diff --git a/src/parser.rs b/src/parser.rs index 83e1e10..0c5ce68 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -202,7 +202,14 @@ pub fn initialize() -> App<'static, 'static> { .takes_value(false) .help("Extract links from response body (html, javascript, etc...); make new requests based on findings (default: false)") ) - + .arg( + Arg::with_name("scan_limit") + .short("L") + .long("scan-limit") + .value_name("SCAN_LIMIT") + .takes_value(true) + .help("Limit total number of concurrent scans (default: 0, i.e. no limit)") + ) .after_help(r#"NOTE: Options that take multiple values are very flexible. Consider the following ways of specifying extensions: diff --git a/tests/test_banner.rs b/tests/test_banner.rs index ad7c811..459def5 100644 --- a/tests/test_banner.rs +++ b/tests/test_banner.rs @@ -563,3 +563,31 @@ fn banner_prints_extract_links() -> Result<(), Box> { ); Ok(()) } + +#[test] +/// test allows non-existent wordlist to trigger the banner printing to stderr +/// expect to see all mandatory prints + scan-limit +fn banner_prints_scan_limit() -> Result<(), Box> { + Command::cargo_bin("feroxbuster") + .unwrap() + .arg("--url") + .arg("http://localhost") + .arg("-L") + .arg("4") + .assert() + .failure() + .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("Concurrent Scan Limit")) + .and(predicate::str::contains("│ 4")) + .and(predicate::str::contains("─┴─")), + ); + Ok(()) +}