mod utils; use assert_cmd::prelude::*; use assert_cmd::Command; use httpmock::Method::GET; use httpmock::MockServer; use predicates::prelude::*; use utils::{setup_tmp_directory, teardown_tmp_directory}; #[test] /// test that the deny list prevents a request if the requested url is a match fn deny_list_works_during_with_a_normal_scan() { let srv = MockServer::start(); let (tmp_dir, file) = setup_tmp_directory(&["LICENSE".to_string()], "wordlist").unwrap(); let mock = srv.mock(|when, then| { when.method(GET).path("/LICENSE"); then.status(200).body("this is a test"); }); let cmd = Command::cargo_bin("feroxbuster") .unwrap() .arg("--url") .arg(srv.url("/")) .arg("--wordlist") .arg(file.as_os_str()) .arg("--dont-scan") .arg(srv.url("/LICENSE")) .unwrap(); teardown_tmp_directory(tmp_dir); cmd.assert() .success() .stdout(predicate::str::contains(srv.url("/LICENSE")).not()); assert_eq!(mock.hits(), 0); } #[test] /// test that the deny list prevents requests of urls found during extraction fn deny_list_works_during_extraction() { let srv = MockServer::start(); let (tmp_dir, file) = setup_tmp_directory(&["LICENSE".to_string()], "wordlist").unwrap(); let mock = srv.mock(|when, then| { when.method(GET).path("/LICENSE"); then.status(200) .body(srv.url("'/homepage/assets/img/icons/handshake.svg'")); }); let mock_two = srv.mock(|when, then| { when.method(GET) .path("/homepage/assets/img/icons/handshake.svg"); then.status(200); }); let cmd = Command::cargo_bin("feroxbuster") .unwrap() .arg("--url") .arg(srv.url("/")) .arg("--wordlist") .arg(file.as_os_str()) .arg("--extract-links") .arg("--dont-scan") .arg(srv.url("/homepage/")) .unwrap(); cmd.assert().success().stdout( predicate::str::contains("/LICENSE") .and(predicate::str::contains("200")) .and(predicate::str::contains("/homepage/assets/img/icons/handshake.svg").not()), ); assert_eq!(mock.hits(), 1); assert_eq!(mock_two.hits(), 0); teardown_tmp_directory(tmp_dir); } #[test] /// test that the deny list prevents requests of urls found during recursion fn deny_list_works_during_recursion() { let srv = MockServer::start(); let urls = [ "js".to_string(), "prod".to_string(), "dev".to_string(), "file.js".to_string(), ]; let (tmp_dir, file) = setup_tmp_directory(&urls, "wordlist").unwrap(); let js_mock = srv.mock(|when, then| { when.method(GET).path("/js"); then.status(301).header("Location", srv.url("/js/")); }); let js_prod_mock = srv.mock(|when, then| { when.method(GET).path("/js/prod"); then.status(301).header("Location", srv.url("/js/prod/")); }); let js_dev_mock = srv.mock(|when, then| { when.method(GET).path("/js/dev"); then.status(301).header("Location", srv.url("/js/dev/")); }); let js_dev_file_mock = srv.mock(|when, then| { when.method(GET).path("/js/dev/file.js"); then.status(200) .body("this is a test and is more bytes than other ones"); }); let cmd = Command::cargo_bin("feroxbuster") .unwrap() .arg("--url") .arg(srv.url("/")) .arg("--wordlist") .arg(file.as_os_str()) .arg("-t") .arg("1") .arg("--dont-scan") .arg(srv.url("/js/dev")) .unwrap(); cmd.assert().success().stdout( predicate::str::is_match("301.*js") .unwrap() .and(predicate::str::is_match("301.*js/prod").unwrap()) .and(predicate::str::is_match("301.*js/dev").unwrap()) .not() .and(predicate::str::is_match("200.*js/dev/file.js").unwrap()) .not(), ); assert_eq!(js_mock.hits(), 2); assert_eq!(js_prod_mock.hits(), 2); assert_eq!(js_dev_mock.hits(), 0); assert_eq!(js_dev_file_mock.hits(), 0); teardown_tmp_directory(tmp_dir); } #[test] /// test that the deny list prevents requests of urls found during recursion when the denier is a /// parent of a user-specified scan fn deny_list_works_during_recursion_with_inverted_parents() { let srv = MockServer::start(); let urls = [ "js".to_string(), "prod".to_string(), "dev".to_string(), "api".to_string(), "file.js".to_string(), ]; let (tmp_dir, file) = setup_tmp_directory(&urls, "wordlist").unwrap(); let js_mock = srv.mock(|when, then| { when.method(GET).path("/js"); then.status(301).header("Location", srv.url("/js/")); }); let api_mock = srv.mock(|when, then| { when.method(GET).path("/api"); then.status(200); }); let js_prod_mock = srv.mock(|when, then| { when.method(GET).path("/js/prod"); then.status(301).header("Location", srv.url("/js/prod/")); }); let js_dev_mock = srv.mock(|when, then| { when.method(GET).path("/js/dev"); then.status(301).header("Location", srv.url("/js/dev/")); }); let js_dev_file_mock = srv.mock(|when, then| { when.method(GET).path("/js/dev/file.js"); then.status(200) .body("this is a test and is more bytes than other ones"); }); let cmd = Command::cargo_bin("feroxbuster") .unwrap() .arg("--url") .arg(srv.url("/js")) .arg("--wordlist") .arg(file.as_os_str()) .arg("-t") .arg("1") .arg("-vvvv") .arg("--dont-scan") .arg(srv.url("/")) .unwrap(); cmd.assert().success().stdout( predicate::str::is_match("301.*js") .unwrap() .and(predicate::str::is_match("301.*js/prod").unwrap()) .and(predicate::str::is_match("301.*js/dev").unwrap()) .and(predicate::str::is_match("200.*js/dev/file.js").unwrap()) .and(predicate::str::is_match("200.*api").unwrap()) .not(), ); assert_eq!(js_mock.hits(), 2); assert_eq!(js_prod_mock.hits(), 2); assert_eq!(js_dev_mock.hits(), 2); assert_eq!(js_dev_file_mock.hits(), 1); assert_eq!(api_mock.hits(), 0); teardown_tmp_directory(tmp_dir); } #[test] /// test that a regex that prevents the base url from being scanned results in an early exit fn deny_list_prevents_regex_that_denies_base_url() { let srv = MockServer::start(); let (tmp_dir, file) = setup_tmp_directory(&["LICENSE".to_string()], "wordlist").unwrap(); let mock = srv.mock(|when, then| { when.method(GET).path("/LICENSE"); then.status(200).body("this is a test"); }); let cmd = Command::cargo_bin("feroxbuster") .unwrap() .arg("--url") .arg(srv.url("/")) .arg("--wordlist") .arg(file.as_os_str()) .arg("--dont-scan") .arg("/") .unwrap(); teardown_tmp_directory(tmp_dir); let err_msg = format!( "Could not determine initial targets: The regex '/' matches {}/; the scan will never start", srv.base_url() ); cmd.assert() .success() .stderr(predicate::str::contains(err_msg)); assert_eq!(mock.hits(), 0); } #[test] /// test that a url that prevents the base url from being scanned results in an early exit fn deny_list_prevents_url_that_denies_base_url() { let srv = MockServer::start(); let (tmp_dir, file) = setup_tmp_directory(&["LICENSE".to_string()], "wordlist").unwrap(); let mock = srv.mock(|when, then| { when.method(GET).path("/LICENSE"); then.status(200).body("this is a test"); }); let cmd = Command::cargo_bin("feroxbuster") .unwrap() .arg("--url") .arg(srv.url("/")) .arg("--wordlist") .arg(file.as_os_str()) .arg("--dont-scan") .arg(srv.base_url()) .unwrap(); teardown_tmp_directory(tmp_dir); let err_msg = format!( "Could not determine initial targets: The url '{}/' matches {}/; the scan will never start", srv.base_url(), srv.base_url() ); cmd.assert() .success() .stderr(predicate::str::contains(err_msg)); assert_eq!(mock.hits(), 0); }