mirror of
https://github.com/epi052/feroxbuster.git
synced 2026-05-27 00:21:13 -03:00
Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e77c1314b1 | ||
|
|
1ced3b5d77 | ||
|
|
b5472f5341 | ||
|
|
ea81600850 | ||
|
|
4f679592b8 | ||
|
|
b375893461 | ||
|
|
e110f86f39 | ||
|
|
c7498a7695 | ||
|
|
f973baaba8 | ||
|
|
148982cdc4 | ||
|
|
5d96658c79 | ||
|
|
46d00507b0 | ||
|
|
d561e59ec9 | ||
|
|
b786578c03 | ||
|
|
bd54ad0087 | ||
|
|
d98c6a7457 | ||
|
|
c493d001b5 | ||
|
|
bd4566fa7b | ||
|
|
8fbf9d0274 | ||
|
|
d6b10c6476 | ||
|
|
a5e845864c | ||
|
|
b02358678b | ||
|
|
1b8fdcec17 | ||
|
|
92cc2ab448 | ||
|
|
0b0e08ae4f | ||
|
|
25762395b1 | ||
|
|
55b4034bd0 | ||
|
|
ffa409ca3d | ||
|
|
bb4a335299 | ||
|
|
1e0ec5c833 | ||
|
|
b5fa6b149e |
@@ -553,6 +553,24 @@
|
||||
"infra",
|
||||
"ideas"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "imBigo",
|
||||
"name": "Simon",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/54672433?v=4",
|
||||
"profile": "https://github.com/imBigo",
|
||||
"contributions": [
|
||||
"bug"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "acut3",
|
||||
"name": "Nicolas Christin",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/17295243?v=4",
|
||||
"profile": "https://acut3.github.io/",
|
||||
"contributions": [
|
||||
"bug"
|
||||
]
|
||||
}
|
||||
],
|
||||
"contributorsPerLine": 7,
|
||||
|
||||
223
Cargo.lock
generated
223
Cargo.lock
generated
@@ -19,6 +19,18 @@ dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"getrandom 0.2.8",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "0.7.20"
|
||||
@@ -29,10 +41,16 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.69"
|
||||
name = "anstyle"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800"
|
||||
checksum = "23ea9e81bd02e310c216d080f6223c179012256e5151c41db88d12c88a1684d2"
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.70"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4"
|
||||
|
||||
[[package]]
|
||||
name = "ascii-canvas"
|
||||
@@ -55,10 +73,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "assert_cmd"
|
||||
version = "2.0.8"
|
||||
version = "2.0.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9834fcc22e0874394a010230586367d4a3e9f11b560f469262678547e1d2575e"
|
||||
checksum = "ec0b2340f55d9661d76793b2bfc2eb0e62689bd79d067a95707ea762afd5e9dd"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"bstr",
|
||||
"doc-comment",
|
||||
"predicates",
|
||||
@@ -198,13 +217,13 @@ checksum = "7a40729d2133846d9ed0ea60a8b9541bccddab49cd30f0715a1da672fe9a2524"
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.66"
|
||||
version = "0.1.67"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b84f9ebcc6c1f5b8cb160f6990096a5c127f423fcb6e1ccc46c370cbdfb75dfc"
|
||||
checksum = "86ea188f25f0255d8f92797797c97ebf5631fa88178beb1a46fdf5622c9a00e4"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -299,9 +318,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
version = "1.3.0"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ffdb39cb703212f3c11973452c2861b972f757b021158f3516ba10f2fa8b2c1"
|
||||
checksum = "c3d4260bcc2e8fc9df1eac4919a720effeb63a3f0952f5bf4944adfa18897f09"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"once_cell",
|
||||
@@ -347,9 +366,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.1.8"
|
||||
version = "4.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3d7ae14b20b94cb02149ed21a86c423859cbe18dc7ed69845cace50e52b40a5"
|
||||
checksum = "ce38afc168d8665cfc75c7b1dd9672e50716a137f433f070991619744a67342a"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"clap_lex",
|
||||
@@ -362,18 +381,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_complete"
|
||||
version = "4.1.4"
|
||||
version = "4.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "501ff0a401473ea1d4c3b125ff95506b62c5bc5768d818634195fbb7c4ad5ff4"
|
||||
checksum = "37686beaba5ac9f3ab01ee3172f792fc6ffdd685bfb9e63cfef02c0571a4e8e1"
|
||||
dependencies = [
|
||||
"clap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.3.2"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "350b9cf31731f9957399229e9b2adc51eeabdfbe9d71d9a0552275fd12710d09"
|
||||
checksum = "033f6b7a4acb1f358c742aaca805c939ee73b4c6209ae4318ec7aca81c42e646"
|
||||
dependencies = [
|
||||
"os_str_bytes",
|
||||
]
|
||||
@@ -528,7 +547,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"smallvec",
|
||||
"syn",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -538,7 +557,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dfae75de57f2b2e85e8768c3ea840fd159c8f33e2b6522c7835b7abac81be16e"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -548,7 +567,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -602,7 +621,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustc_version",
|
||||
"syn",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -628,9 +647,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "dirs"
|
||||
version = "4.0.0"
|
||||
version = "5.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059"
|
||||
checksum = "dece029acd3353e3a58ac2e3eb3c8d6c35827a892edc6cc4138ef9c33df46ecd"
|
||||
dependencies = [
|
||||
"dirs-sys",
|
||||
]
|
||||
@@ -647,13 +666,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "dirs-sys"
|
||||
version = "0.3.7"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6"
|
||||
checksum = "04414300db88f70d74c5ff54e50f9e1d1737d9a5b90f53fcf2e95ca2a9ab554b"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"redox_users",
|
||||
"winapi",
|
||||
"windows-sys 0.45.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -702,9 +721,9 @@ checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
|
||||
|
||||
[[package]]
|
||||
name = "ena"
|
||||
version = "0.14.1"
|
||||
version = "0.14.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2e5d13ca2353ab7d0230988629def93914a8c4015f621f9b13ed2955614731d"
|
||||
checksum = "c533630cf40e9caa44bd91aadc88a75d75a4c3a12b4cfde353cbed41daa1e1f1"
|
||||
dependencies = [
|
||||
"log",
|
||||
]
|
||||
@@ -775,7 +794,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "feroxbuster"
|
||||
version = "2.9.1"
|
||||
version = "2.9.3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"assert_cmd",
|
||||
@@ -965,7 +984,7 @@ checksum = "3eb14ed937631bd8b8b8977f2c198443447a8355b6e3ca599f38c975e5a963b6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1013,7 +1032,7 @@ version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f5a92848d5984b9e3cea74c8df667ffb79ee6f181e2cf9b0df2e50c2f96cabb"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"ahash 0.7.6",
|
||||
"crossbeam-utils",
|
||||
"fnv",
|
||||
"itertools",
|
||||
@@ -1142,7 +1161,7 @@ dependencies = [
|
||||
"markup5ever",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1305,10 +1324,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "io-lifetimes"
|
||||
version = "1.0.6"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cfa919a82ea574332e2de6e74b4c36e74d41982b335080fa59d4ef31be20fdf3"
|
||||
checksum = "76e86b86ae312accbf05ade23ce76b625e0e47a255712b7414037385a1c05380"
|
||||
dependencies = [
|
||||
"hermit-abi 0.3.1",
|
||||
"libc",
|
||||
"windows-sys 0.45.0",
|
||||
]
|
||||
@@ -1661,9 +1681,9 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
|
||||
|
||||
[[package]]
|
||||
name = "openssl"
|
||||
version = "0.10.45"
|
||||
version = "0.10.46"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b102428fd03bc5edf97f62620f7298614c45cedf287c271e7ed450bbaf83f2e1"
|
||||
checksum = "fd2523381e46256e40930512c7fd25562b9eae4812cb52078f155e87217c9d1e"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
@@ -1682,7 +1702,7 @@ checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1702,9 +1722,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openssl-sys"
|
||||
version = "0.9.80"
|
||||
version = "0.9.81"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23bbbf7854cd45b83958ebe919f0e8e516793727652e27fda10a8384cfc790b7"
|
||||
checksum = "176be2629957c157240f68f61f2d0053ad3a4ecfdd9ebf1e6521d18d9635cf67"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"cc",
|
||||
@@ -1836,7 +1856,7 @@ dependencies = [
|
||||
"proc-macro-hack",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1880,7 +1900,7 @@ checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1937,10 +1957,11 @@ checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
|
||||
|
||||
[[package]]
|
||||
name = "predicates"
|
||||
version = "2.1.5"
|
||||
version = "3.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59230a63c37f3e18569bdb90e4a89cbf5bf8b06fea0b84e65ea10cc4df47addd"
|
||||
checksum = "1ba7d6ead3e3966038f68caa9fc1f860185d95a793180bbcfe0d0da47b3961ed"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"difflib",
|
||||
"float-cmp",
|
||||
"itertools",
|
||||
@@ -1951,15 +1972,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "predicates-core"
|
||||
version = "1.0.5"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72f883590242d3c6fc5bf50299011695fa6590c2c70eac95ee1bdb9a733ad1a2"
|
||||
checksum = "b794032607612e7abeb4db69adb4e33590fa6cf1149e95fd7cb00e634b92f174"
|
||||
|
||||
[[package]]
|
||||
name = "predicates-tree"
|
||||
version = "1.0.7"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "54ff541861505aabf6ea722d2131ee980b8276e10a1297b94e896dd8b621850d"
|
||||
checksum = "368ba315fb8c5052ab692e68a0eefec6ec57b23a36959c14496f0b0df2c0cecf"
|
||||
dependencies = [
|
||||
"predicates-core",
|
||||
"termtree",
|
||||
@@ -1973,9 +1994,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.51"
|
||||
version = "1.0.52"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6"
|
||||
checksum = "1d0e1ae9e836cc3beddd63db0df682593d7e2d3d891ae8c9083d2113e1744224"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@@ -1991,9 +2012,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.23"
|
||||
version = "1.0.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
|
||||
checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
@@ -2308,15 +2329,16 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
||||
|
||||
[[package]]
|
||||
name = "scraper"
|
||||
version = "0.15.0"
|
||||
version = "0.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c557a9a03db98b0b298b497f0e16cd35a04a1fa9ee1130a6889c0714e0b73df"
|
||||
checksum = "59e25654b5e9fd557a67dbaab5a5d36b8c448d0561beb4c041b6dbb902eddfa6"
|
||||
dependencies = [
|
||||
"ahash 0.8.3",
|
||||
"cssparser",
|
||||
"ego-tree",
|
||||
"getopts",
|
||||
"html5ever",
|
||||
"matches",
|
||||
"once_cell",
|
||||
"selectors",
|
||||
"smallvec",
|
||||
"tendril",
|
||||
@@ -2393,28 +2415,28 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.16"
|
||||
version = "1.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a"
|
||||
checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.155"
|
||||
version = "1.0.157"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71f2b4817415c6d4210bfe1c7bfcf4801b2d904cb4d0e1a8fdb651013c9e86b8"
|
||||
checksum = "707de5fcf5df2b5788fca98dd7eab490bc2fd9b7ef1404defc462833b83f25ca"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.155"
|
||||
version = "1.0.157"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d071a94a3fac4aff69d023a7f411e33f40f3483f8c5190b1953822b6b76d7630"
|
||||
checksum = "78997f4555c22a7971214540c4a661291970619afd56de19f77e0de86296e1e5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2621,6 +2643,17 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4cff13bb1732bccfe3b246f3fdb09edfd51c01d6f5299b7ccd9457c2e4e37774"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tar"
|
||||
version = "0.4.38"
|
||||
@@ -2688,28 +2721,28 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "termtree"
|
||||
version = "0.4.0"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95059e91184749cb66be6dc994f67f182b6d897cb3df74a5bf66b5e709295fd8"
|
||||
checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76"
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.39"
|
||||
version = "1.0.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a5ab016db510546d856297882807df8da66a16fb8c4101cb8b30054b0d5b2d9c"
|
||||
checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.39"
|
||||
version = "1.0.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5420d42e90af0c38c3290abcca25b9b3bdf379fc9f55c528f53a269d9c9a267e"
|
||||
checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2780,7 +2813,7 @@ checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2821,9 +2854,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.7.2"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7afcae9e3f0fe2c370fd4657108972cbb2fa9db1b9f84849cefd80741b01cb6"
|
||||
checksum = "b403acf6f2bb0859c93c7f0d967cb4a75a7ac552100f9322faf64dc047669b21"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
@@ -2842,9 +2875,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.19.5"
|
||||
version = "0.19.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7082a95d48029677a28f181e5f6422d0c8339ad8396a39d3f33d62a90c1f6c30"
|
||||
checksum = "dc18466501acd8ac6a3f615dd29a3438f8ca6bb3b19537138b3106e575621274"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
@@ -2880,7 +2913,7 @@ checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2926,9 +2959,9 @@ checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi"
|
||||
version = "0.3.11"
|
||||
version = "0.3.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "524b68aca1d05e03fdf03fcdce2c6c94b6daf6d16861ddaa7e4f2b6638a9052c"
|
||||
checksum = "7d502c968c6a838ead8e69b2ee18ec708802f99db92a0d156705ec9ef801993b"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
@@ -3070,7 +3103,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 1.0.109",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
@@ -3104,7 +3137,7 @@ checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 1.0.109",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
@@ -3182,9 +3215,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.42.1"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7"
|
||||
checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
@@ -3197,51 +3230,51 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.42.1"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608"
|
||||
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.42.1"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7"
|
||||
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.42.1"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640"
|
||||
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.42.1"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605"
|
||||
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.42.1"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45"
|
||||
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.42.1"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463"
|
||||
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.42.1"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"
|
||||
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.3.5"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee7b2c67f962bf5042bfd8b6a916178df33a26eec343ae064cb8e069f638fa6f"
|
||||
checksum = "23d020b441f92996c80d94ae9166e8501e59c7bb56121189dc9eab3bd8216966"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
17
Cargo.toml
17
Cargo.toml
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "feroxbuster"
|
||||
version = "2.9.1"
|
||||
version = "2.9.3"
|
||||
authors = ["Ben 'epi' Risher (@epi052)"]
|
||||
license = "MIT"
|
||||
edition = "2021"
|
||||
@@ -26,10 +26,10 @@ clap = { version = "4.1.8", features = ["wrap_help", "cargo"] }
|
||||
clap_complete = "4.1.4"
|
||||
regex = "1.5.5"
|
||||
lazy_static = "1.4.0"
|
||||
dirs = "4.0.0"
|
||||
dirs = "5.0.0"
|
||||
|
||||
[dependencies]
|
||||
scraper = "0.15.0"
|
||||
scraper = "0.16.0"
|
||||
futures = "0.3.26"
|
||||
tokio = { version = "1.26.0", features = ["full"] }
|
||||
tokio-util = { version = "0.7.7", features = ["codec"] }
|
||||
@@ -48,7 +48,7 @@ uuid = { version = "1.3.0", features = ["v4"] }
|
||||
indicatif = "0.15"
|
||||
console = "0.15.2"
|
||||
openssl = { version = "0.10", features = ["vendored"] }
|
||||
dirs = "4.0.0"
|
||||
dirs = "5.0.0"
|
||||
regex = "1.5.5"
|
||||
crossterm = "0.26.0"
|
||||
rlimit = "0.9.1"
|
||||
@@ -56,13 +56,18 @@ ctrlc = "3.2.2"
|
||||
anyhow = "1.0.69"
|
||||
leaky-bucket = "0.12.1"
|
||||
gaoya = "0.1.2"
|
||||
self_update = {version = "0.36.0", features = ["archive-tar", "compression-flate2", "archive-zip", "compression-zip-deflate"]}
|
||||
self_update = { version = "0.36.0", features = [
|
||||
"archive-tar",
|
||||
"compression-flate2",
|
||||
"archive-zip",
|
||||
"compression-zip-deflate",
|
||||
] }
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.3.0"
|
||||
httpmock = "0.6.6"
|
||||
assert_cmd = "2.0.4"
|
||||
predicates = "2.1.1"
|
||||
predicates = "3.0.1"
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
|
||||
14
README.md
14
README.md
@@ -101,6 +101,12 @@ sudo apt update && sudo apt install -y feroxbuster
|
||||
curl -sL https://raw.githubusercontent.com/epi052/feroxbuster/master/install-nix.sh | bash
|
||||
```
|
||||
|
||||
#### MacOS via Homebrew
|
||||
|
||||
```
|
||||
brew install feroxbuster
|
||||
```
|
||||
|
||||
#### Windows x86_64
|
||||
|
||||
```
|
||||
@@ -109,6 +115,12 @@ Expand-Archive .\feroxbuster.zip
|
||||
.\feroxbuster\feroxbuster.exe -V
|
||||
```
|
||||
|
||||
#### Windows via Chocolatey
|
||||
|
||||
```
|
||||
choco install feroxbuster
|
||||
```
|
||||
|
||||
#### All others
|
||||
|
||||
Please refer the the [documentation](https://epi052.github.io/feroxbuster-docs/docs/).
|
||||
@@ -265,6 +277,8 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/xaeroborg"><img src="https://avatars.githubusercontent.com/u/33274680?v=4?s=100" width="100px;" alt="xaeroborg"/><br /><sub><b>xaeroborg</b></sub></a><br /><a href="#ideas-xaeroborg" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Luoooio"><img src="https://avatars.githubusercontent.com/u/26653157?v=4?s=100" width="100px;" alt="Luoooio"/><br /><sub><b>Luoooio</b></sub></a><br /><a href="#ideas-Luoooio" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://petruknisme.com"><img src="https://avatars.githubusercontent.com/u/6284204?v=4?s=100" width="100px;" alt="Aan"/><br /><sub><b>Aan</b></sub></a><br /><a href="https://github.com/epi052/feroxbuster/commits?author=aancw" title="Code">💻</a> <a href="#infra-aancw" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="#ideas-aancw" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/imBigo"><img src="https://avatars.githubusercontent.com/u/54672433?v=4?s=100" width="100px;" alt="Simon"/><br /><sub><b>Simon</b></sub></a><br /><a href="https://github.com/epi052/feroxbuster/issues?q=author%3AimBigo" title="Bug reports">🐛</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://acut3.github.io/"><img src="https://avatars.githubusercontent.com/u/17295243?v=4?s=100" width="100px;" alt="Nicolas Christin"/><br /><sub><b>Nicolas Christin</b></sub></a><br /><a href="https://github.com/epi052/feroxbuster/issues?q=author%3Aacut3" title="Bug reports">🐛</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -24,8 +24,8 @@ _feroxbuster() {
|
||||
'--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.9.1)]:USER_AGENT: ' \
|
||||
'--user-agent=[Sets the User-Agent (default: feroxbuster/2.9.1)]:USER_AGENT: ' \
|
||||
'-a+[Sets the User-Agent (default: feroxbuster/2.9.3)]:USER_AGENT: ' \
|
||||
'--user-agent=[Sets the User-Agent (default: feroxbuster/2.9.3)]:USER_AGENT: ' \
|
||||
'*-x+[File extension(s) to search for (ex: -x php -x pdf js)]:FILE_EXTENSION: ' \
|
||||
'*--extensions=[File extension(s) to search for (ex: -x php -x pdf js)]:FILE_EXTENSION: ' \
|
||||
'*-m+[Which HTTP request method(s) should be sent (default: GET)]:HTTP_METHODS: ' \
|
||||
@@ -62,8 +62,8 @@ _feroxbuster() {
|
||||
'--parallel=[Run parallel feroxbuster instances (one child process per url passed via stdin)]:PARALLEL_SCANS: ' \
|
||||
'(--auto-tune)--rate-limit=[Limit number of requests per second (per directory) (default: 0, i.e. no limit)]:RATE_LIMIT: ' \
|
||||
'--time-limit=[Limit total run time of all scans (ex: --time-limit 10m)]:TIME_SPEC: ' \
|
||||
'-w+[Path to the wordlist]:FILE:_files' \
|
||||
'--wordlist=[Path to the wordlist]:FILE:_files' \
|
||||
'-w+[Path or URL of the wordlist]:FILE:_files' \
|
||||
'--wordlist=[Path or URL of the wordlist]:FILE:_files' \
|
||||
'*-I+[File extension(s) to Ignore while collecting extensions (only used with --collect-extensions)]:FILE_EXTENSION: ' \
|
||||
'*--dont-collect=[File extension(s) to Ignore while collecting extensions (only used with --collect-extensions)]:FILE_EXTENSION: ' \
|
||||
'-o+[Output file to write results to (use w/ --json for JSON entries)]:FILE:_files' \
|
||||
@@ -72,7 +72,7 @@ _feroxbuster() {
|
||||
'(-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 --extract-links, --auto-tune, --collect-words, and --collect-backups 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]' \
|
||||
'-A[Use a random User-Agent]' \
|
||||
'--random-agent[Use a random User-Agent]' \
|
||||
@@ -85,8 +85,9 @@ _feroxbuster() {
|
||||
'-n[Do not scan recursively]' \
|
||||
'--no-recursion[Do not scan recursively]' \
|
||||
'(-n --no-recursion)--force-recursion[Force recursion attempts on all '\''found'\'' endpoints (still respects recursion depth)]' \
|
||||
'-e[Extract links from response body (html, javascript, etc...); make new requests based on findings]' \
|
||||
'--extract-links[Extract links from response body (html, javascript, etc...); make new requests based on findings]' \
|
||||
'-e[Extract links from response body (html, javascript, etc...); make new requests based on findings (default: true)]' \
|
||||
'--extract-links[Extract links from response body (html, javascript, etc...); make new requests based on findings (default: true)]' \
|
||||
'--dont-extract-links[Don'\''t extract links from response body (html, javascript, etc...)]' \
|
||||
'(--auto-bail)--auto-tune[Automatically lower scan rate when an excessive amount of errors are encountered]' \
|
||||
'--auto-bail[Automatically stop scanning when an excessive amount of errors are encountered]' \
|
||||
'-D[Don'\''t auto-filter wildcard responses]' \
|
||||
|
||||
@@ -30,8 +30,8 @@ Register-ArgumentCompleter -Native -CommandName 'feroxbuster' -ScriptBlock {
|
||||
[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.9.1)')
|
||||
[CompletionResult]::new('--user-agent', 'user-agent', [CompletionResultType]::ParameterName, 'Sets the User-Agent (default: feroxbuster/2.9.1)')
|
||||
[CompletionResult]::new('-a', 'a', [CompletionResultType]::ParameterName, 'Sets the User-Agent (default: feroxbuster/2.9.3)')
|
||||
[CompletionResult]::new('--user-agent', 'user-agent', [CompletionResultType]::ParameterName, 'Sets the User-Agent (default: feroxbuster/2.9.3)')
|
||||
[CompletionResult]::new('-x', 'x', [CompletionResultType]::ParameterName, 'File extension(s) to search for (ex: -x php -x pdf js)')
|
||||
[CompletionResult]::new('--extensions', 'extensions', [CompletionResultType]::ParameterName, 'File extension(s) to search for (ex: -x php -x pdf js)')
|
||||
[CompletionResult]::new('-m', 'm', [CompletionResultType]::ParameterName, 'Which HTTP request method(s) should be sent (default: GET)')
|
||||
@@ -68,8 +68,8 @@ Register-ArgumentCompleter -Native -CommandName 'feroxbuster' -ScriptBlock {
|
||||
[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 to the wordlist')
|
||||
[CompletionResult]::new('--wordlist', 'wordlist', [CompletionResultType]::ParameterName, 'Path to the wordlist')
|
||||
[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('-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)')
|
||||
@@ -78,7 +78,7 @@ Register-ArgumentCompleter -Native -CommandName 'feroxbuster' -ScriptBlock {
|
||||
[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 --extract-links, --auto-tune, --collect-words, and --collect-backups 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')
|
||||
@@ -91,8 +91,9 @@ Register-ArgumentCompleter -Native -CommandName 'feroxbuster' -ScriptBlock {
|
||||
[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')
|
||||
[CompletionResult]::new('--extract-links', 'extract-links', [CompletionResultType]::ParameterName, 'Extract links from response body (html, javascript, etc...); make new requests based on findings')
|
||||
[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')
|
||||
|
||||
@@ -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 --threads --no-recursion --depth --force-recursion --extract-links --scan-limit --parallel --rate-limit --time-limit --wordlist --auto-tune --auto-bail --dont-filter --collect-extensions --collect-backups --collect-words --dont-collect --verbosity --silent --quiet --json --output --debug-log --no-state --update --help --version"
|
||||
opts="-u -p -P -R -a -A -x -m -H -b -Q -f -S -X -W -N -C -s -T -r -k -t -n -d -e -L -w -D -E -B -g -I -v -q -o -U -h -V --url --stdin --resume-from --burp --burp-replay --smart --thorough --proxy --replay-proxy --replay-codes --user-agent --random-agent --extensions --methods --data --headers --cookies --query --add-slash --dont-scan --filter-size --filter-regex --filter-words --filter-lines --filter-status --filter-similar-to --status-codes --timeout --redirects --insecure --threads --no-recursion --depth --force-recursion --extract-links --dont-extract-links --scan-limit --parallel --rate-limit --time-limit --wordlist --auto-tune --auto-bail --dont-filter --collect-extensions --collect-backups --collect-words --dont-collect --verbosity --silent --quiet --json --output --debug-log --no-state --update --help --version"
|
||||
if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||
return 0
|
||||
|
||||
@@ -27,8 +27,8 @@ set edit:completion:arg-completer[feroxbuster] = {|@words|
|
||||
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.9.1)'
|
||||
cand --user-agent 'Sets the User-Agent (default: feroxbuster/2.9.1)'
|
||||
cand -a 'Sets the User-Agent (default: feroxbuster/2.9.3)'
|
||||
cand --user-agent 'Sets the User-Agent (default: feroxbuster/2.9.3)'
|
||||
cand -x 'File extension(s) to search for (ex: -x php -x pdf js)'
|
||||
cand --extensions 'File extension(s) to search for (ex: -x php -x pdf js)'
|
||||
cand -m 'Which HTTP request method(s) should be sent (default: GET)'
|
||||
@@ -65,8 +65,8 @@ set edit:completion:arg-completer[feroxbuster] = {|@words|
|
||||
cand --parallel 'Run parallel feroxbuster instances (one child process per url passed via stdin)'
|
||||
cand --rate-limit 'Limit number of requests per second (per directory) (default: 0, i.e. no limit)'
|
||||
cand --time-limit 'Limit total run time of all scans (ex: --time-limit 10m)'
|
||||
cand -w 'Path to the wordlist'
|
||||
cand --wordlist 'Path to the wordlist'
|
||||
cand -w 'Path or URL of the wordlist'
|
||||
cand --wordlist 'Path or URL of the wordlist'
|
||||
cand -I 'File extension(s) to Ignore while collecting extensions (only used with --collect-extensions)'
|
||||
cand --dont-collect 'File extension(s) to Ignore while collecting extensions (only used with --collect-extensions)'
|
||||
cand -o 'Output file to write results to (use w/ --json for JSON entries)'
|
||||
@@ -75,7 +75,7 @@ set edit:completion:arg-completer[feroxbuster] = {|@words|
|
||||
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 --extract-links, --auto-tune, --collect-words, and --collect-backups 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 -A 'Use a random User-Agent'
|
||||
cand --random-agent 'Use a random User-Agent'
|
||||
@@ -88,8 +88,9 @@ set edit:completion:arg-completer[feroxbuster] = {|@words|
|
||||
cand -n 'Do not scan recursively'
|
||||
cand --no-recursion 'Do not scan recursively'
|
||||
cand --force-recursion 'Force recursion attempts on all ''found'' endpoints (still respects recursion depth)'
|
||||
cand -e 'Extract links from response body (html, javascript, etc...); make new requests based on findings'
|
||||
cand --extract-links 'Extract links from response body (html, javascript, etc...); make new requests based on findings'
|
||||
cand -e 'Extract links from response body (html, javascript, etc...); make new requests based on findings (default: true)'
|
||||
cand --extract-links 'Extract links from response body (html, javascript, etc...); make new requests based on findings (default: true)'
|
||||
cand --dont-extract-links 'Don''t extract links from response body (html, javascript, etc...)'
|
||||
cand --auto-tune 'Automatically lower scan rate when an excessive amount of errors are encountered'
|
||||
cand --auto-bail 'Automatically stop scanning when an excessive amount of errors are encountered'
|
||||
cand -D 'Don''t auto-filter wildcard responses'
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use super::utils::{
|
||||
depth, ignored_extensions, methods, report_and_exit, save_state, serialized_type, status_codes,
|
||||
threads, timeout, user_agent, wordlist, OutputLevel, RequesterPolicy,
|
||||
depth, extract_links, ignored_extensions, methods, report_and_exit, save_state,
|
||||
serialized_type, status_codes, threads, timeout, user_agent, wordlist, OutputLevel,
|
||||
RequesterPolicy,
|
||||
};
|
||||
use crate::config::determine_output_level;
|
||||
use crate::config::utils::determine_requester_policy;
|
||||
@@ -214,7 +215,7 @@ pub struct Configuration {
|
||||
pub no_recursion: bool,
|
||||
|
||||
/// Extract links from html/javscript
|
||||
#[serde(default)]
|
||||
#[serde(default = "extract_links")]
|
||||
pub extract_links: bool,
|
||||
|
||||
/// Append / to each request
|
||||
@@ -328,6 +329,7 @@ impl Default for Configuration {
|
||||
let kind = serialized_type();
|
||||
let output_level = OutputLevel::Default;
|
||||
let requester_policy = RequesterPolicy::Default;
|
||||
let extract_links = extract_links();
|
||||
|
||||
Configuration {
|
||||
kind,
|
||||
@@ -336,6 +338,7 @@ impl Default for Configuration {
|
||||
user_agent,
|
||||
replay_codes,
|
||||
status_codes,
|
||||
extract_links,
|
||||
replay_client,
|
||||
requester_policy,
|
||||
dont_filter: false,
|
||||
@@ -355,7 +358,6 @@ impl Default for Configuration {
|
||||
insecure: false,
|
||||
redirects: false,
|
||||
no_recursion: false,
|
||||
extract_links: false,
|
||||
random_agent: false,
|
||||
collect_extensions: false,
|
||||
collect_backups: false,
|
||||
@@ -398,7 +400,7 @@ impl Configuration {
|
||||
///
|
||||
/// - **timeout**: `5` seconds
|
||||
/// - **redirects**: `false`
|
||||
/// - **extract-links**: `false`
|
||||
/// - **extract_links**: `true`
|
||||
/// - **wordlist**: [`DEFAULT_WORDLIST`](constant.DEFAULT_WORDLIST.html)
|
||||
/// - **config**: `None`
|
||||
/// - **threads**: `50`
|
||||
@@ -807,11 +809,8 @@ impl Configuration {
|
||||
config.add_slash = true;
|
||||
}
|
||||
|
||||
if came_from_cli!(args, "extract_links")
|
||||
|| came_from_cli!(args, "smart")
|
||||
|| came_from_cli!(args, "thorough")
|
||||
{
|
||||
config.extract_links = true;
|
||||
if came_from_cli!(args, "dont_extract_links") {
|
||||
config.extract_links = false;
|
||||
}
|
||||
|
||||
if came_from_cli!(args, "json") {
|
||||
@@ -997,7 +996,7 @@ impl Configuration {
|
||||
update_if_not_default!(&mut conf.redirects, new.redirects, false);
|
||||
update_if_not_default!(&mut conf.insecure, new.insecure, false);
|
||||
update_if_not_default!(&mut conf.force_recursion, new.force_recursion, false);
|
||||
update_if_not_default!(&mut conf.extract_links, new.extract_links, false);
|
||||
update_if_not_default!(&mut conf.extract_links, new.extract_links, extract_links());
|
||||
update_if_not_default!(&mut conf.extensions, new.extensions, Vec::<String>::new());
|
||||
update_if_not_default!(&mut conf.methods, new.methods, methods());
|
||||
update_if_not_default!(&mut conf.data, new.data, Vec::<u8>::new());
|
||||
|
||||
@@ -45,7 +45,7 @@ fn setup_config_test() -> Configuration {
|
||||
add_slash = true
|
||||
stdin = true
|
||||
dont_filter = true
|
||||
extract_links = true
|
||||
extract_links = false
|
||||
json = true
|
||||
save_state = false
|
||||
depth = 1
|
||||
@@ -98,7 +98,7 @@ fn default_configuration() {
|
||||
assert!(!config.add_slash);
|
||||
assert!(!config.force_recursion);
|
||||
assert!(!config.redirects);
|
||||
assert!(!config.extract_links);
|
||||
assert!(config.extract_links);
|
||||
assert!(!config.insecure);
|
||||
assert!(!config.collect_extensions);
|
||||
assert!(!config.collect_backups);
|
||||
@@ -305,7 +305,7 @@ fn config_reads_add_slash() {
|
||||
/// parse the test config and see that the value parsed is correct
|
||||
fn config_reads_extract_links() {
|
||||
let config = setup_config_test();
|
||||
assert!(config.extract_links);
|
||||
assert!(!config.extract_links);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -84,6 +84,11 @@ pub(super) fn depth() -> usize {
|
||||
4
|
||||
}
|
||||
|
||||
/// default extract links
|
||||
pub(super) fn extract_links() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
/// enum representing the three possible states for informational output (not logging verbosity)
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum OutputLevel {
|
||||
|
||||
@@ -222,7 +222,7 @@ impl ScanHandler {
|
||||
let current_expectation = self.handles.expected_num_requests_per_dir() as u64;
|
||||
|
||||
// used in the calculation of bar width down below, see explanation there
|
||||
let divisor = self.handles.expected_num_requests_multiplier() as u64 - 1;
|
||||
let divisor = (self.handles.expected_num_requests_multiplier() as u64 - 1).max(1);
|
||||
|
||||
// add another `wordlist.len` to the expected per scan tracker in the statistics handler
|
||||
self.handles
|
||||
@@ -339,7 +339,7 @@ impl ScanHandler {
|
||||
// number of requests have already been sent, we need to adjust the offset into the
|
||||
// wordlist to ensure we don't index out of bounds
|
||||
|
||||
let adjusted = scan.requests_made_so_far() as f64 / divisor as f64 - 1.0;
|
||||
let adjusted = scan.requests_made_so_far() as f64 / (divisor as f64 - 1.0).max(1.0);
|
||||
self.get_wordlist(adjusted as usize)?
|
||||
} else {
|
||||
self.get_wordlist(scan.requests_made_so_far() as usize)?
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
@@ -276,133 +277,178 @@ impl HeuristicTests {
|
||||
None
|
||||
};
|
||||
|
||||
// 4 is due to the array in the nested for loop below
|
||||
let mut responses = Vec::with_capacity(4);
|
||||
// 6 is due to the array in the nested for loop below
|
||||
let mut responses = Vec::with_capacity(6);
|
||||
|
||||
// no matter what, we want an empty extension for the base case
|
||||
let mut extensions = vec!["".to_string()];
|
||||
|
||||
// and then we want to add any extensions that was specified
|
||||
// or has since been added to the running config
|
||||
for ext in &self.handles.config.extensions {
|
||||
extensions.push(format!(".{}", ext));
|
||||
}
|
||||
|
||||
// for every method, attempt to id its 404 response
|
||||
//
|
||||
// a good example of one where the GET/POST differ is on hackthebox:
|
||||
// - http://prd.m.rendering-api.interface.htb/api
|
||||
//
|
||||
// a good example of one where the heuristics return a 403 and a 404 (apache)
|
||||
// as well as return two different types of 404s based on the file extension
|
||||
// - http://10.10.11.198 (Encoding box in normal labs)
|
||||
//
|
||||
// both methods and extensions can elicit different responses from a given
|
||||
// server, so both are considered when building auto-filter rules
|
||||
for method in self.handles.config.methods.iter() {
|
||||
for (prefix, length) in [("", 1), ("", 3), (".htaccess", 1), ("admin", 1)] {
|
||||
let path = format!("{prefix}{}", self.unique_string(length));
|
||||
for extension in extensions.iter() {
|
||||
for (prefix, length) in [
|
||||
("", 1),
|
||||
("", 3),
|
||||
(".htaccess", 1),
|
||||
(".htaccess", 3),
|
||||
("admin", 1),
|
||||
("admin", 3),
|
||||
] {
|
||||
let path = format!("{prefix}{}{extension}", self.unique_string(length));
|
||||
|
||||
let ferox_url = FeroxUrl::from_string(target_url, self.handles.clone());
|
||||
let ferox_url = FeroxUrl::from_string(target_url, self.handles.clone());
|
||||
|
||||
let nonexistent_url = ferox_url.format(&path, slash)?;
|
||||
let nonexistent_url = ferox_url.format(&path, slash)?;
|
||||
|
||||
// example requests:
|
||||
// - http://localhost/2fc1077836ad43ab98b7a31c2ca28fea
|
||||
// - http://localhost/92969beae6bf4beb855d1622406d87e395c87387a9ad432e8a11245002b709b03cf609d471004154b83bcc1c6ec49f6f
|
||||
// - http://localhost/.htaccessa005a2131e68449aa26e99029c914c09
|
||||
// - http://localhost/adminf1d2541e73c44dcb9d1fb7d93334b280
|
||||
let response =
|
||||
logged_request(&nonexistent_url, method, data, self.handles.clone()).await;
|
||||
// example requests:
|
||||
// - http://localhost/2fc1077836ad43ab98b7a31c2ca28fea
|
||||
// - http://localhost/92969beae6bf4beb855d1622406d87e395c87387a9ad432e8a11245002b709b03cf609d471004154b83bcc1c6ec49f6f
|
||||
// - http://localhost/.htaccessa005a2131e68449aa26e99029c914c09
|
||||
// - http://localhost/.htaccess92969beae6bf4beb855d1622406d87e395c87387a9ad432e8a11245002b709b03cf609d471004154b83bcc1c6ec49f6f
|
||||
// - http://localhost/adminf1d2541e73c44dcb9d1fb7d93334b280
|
||||
// - http://localhost/admin92969beae6bf4beb855d1622406d87e395c87387a9ad432e8a11245002b709b03cf609d471004154b83bcc1c6ec49f6f
|
||||
let response =
|
||||
logged_request(&nonexistent_url, method, data, self.handles.clone()).await;
|
||||
|
||||
req_counter += 1;
|
||||
req_counter += 1;
|
||||
|
||||
// continue to next on error
|
||||
let response = skip_fail!(response);
|
||||
// continue to next on error
|
||||
let response = skip_fail!(response);
|
||||
|
||||
if !self
|
||||
.handles
|
||||
.config
|
||||
.status_codes
|
||||
.contains(&response.status().as_u16())
|
||||
{
|
||||
// if the response code isn't one that's accepted via -s values, then skip to the next
|
||||
//
|
||||
// the default value for -s is all status codes, so unless the user says otherwise
|
||||
// this won't fire
|
||||
if !self
|
||||
.handles
|
||||
.config
|
||||
.status_codes
|
||||
.contains(&response.status().as_u16())
|
||||
{
|
||||
// if the response code isn't one that's accepted via -s values, then skip to the next
|
||||
//
|
||||
// the default value for -s is all status codes, so unless the user says otherwise
|
||||
// this won't fire
|
||||
continue;
|
||||
}
|
||||
|
||||
let ferox_response = FeroxResponse::from(
|
||||
response,
|
||||
&ferox_url.target,
|
||||
method,
|
||||
self.handles.config.output_level,
|
||||
)
|
||||
.await;
|
||||
|
||||
responses.push(ferox_response);
|
||||
}
|
||||
|
||||
if responses.len() < 2 {
|
||||
// don't have enough responses to make a determination, continue to next method
|
||||
responses.clear();
|
||||
continue;
|
||||
}
|
||||
|
||||
let ferox_response = FeroxResponse::from(
|
||||
response,
|
||||
&ferox_url.target,
|
||||
method,
|
||||
// check the responses for similarities on which we can filter, multiple may be returned
|
||||
let Some((wildcard_filters, wildcard_responses)) = self.examine_404_like_responses(&responses) else {
|
||||
// no match was found during analysis of responses
|
||||
responses.clear();
|
||||
log::warn!("no match found for 404 responses");
|
||||
continue;
|
||||
};
|
||||
|
||||
// report to the user, if appropriate
|
||||
if matches!(
|
||||
self.handles.config.output_level,
|
||||
)
|
||||
.await;
|
||||
OutputLevel::Default | OutputLevel::Quiet
|
||||
) {
|
||||
// sentry value to control whether or not to print the filter
|
||||
// used because we only want to print the same filter once
|
||||
let mut print_sentry;
|
||||
|
||||
responses.push(ferox_response);
|
||||
}
|
||||
if let Ok(filters) = self.handles.filters.data.filters.read() {
|
||||
for new_wildcard in &wildcard_filters {
|
||||
// reset the sentry for every new wildcard produced by examine_404_like_responses
|
||||
print_sentry = true;
|
||||
|
||||
if responses.len() < 2 {
|
||||
// don't have enough responses to make a determination, continue to next method
|
||||
responses.clear();
|
||||
continue;
|
||||
}
|
||||
for other in filters.iter() {
|
||||
if let Some(other_wildcard) =
|
||||
other.as_any().downcast_ref::<WildcardFilter>()
|
||||
{
|
||||
// check the new wildcard against all existing wildcards, if it was added
|
||||
// on the cli or by a previous directory, don't print it
|
||||
if new_wildcard.as_ref() == other_wildcard {
|
||||
print_sentry = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Command::AddFilter, &str (bytes/words/lines), usize (i.e. length associated with the type)
|
||||
let Some(filter) = self.examine_404_like_responses(&responses) else {
|
||||
// no match was found during analysis of responses
|
||||
responses.clear();
|
||||
continue;
|
||||
};
|
||||
|
||||
// report to the user, if appropriate
|
||||
if matches!(
|
||||
self.handles.config.output_level,
|
||||
OutputLevel::Default | OutputLevel::Quiet
|
||||
) {
|
||||
// sentry value to control whether or not to print the filter
|
||||
// used because we only want to print the same filter once
|
||||
let mut print_sentry = true;
|
||||
|
||||
if let Ok(filters) = self.handles.filters.data.filters.read() {
|
||||
for other in filters.iter() {
|
||||
if let Some(other_wildcard) =
|
||||
other.as_any().downcast_ref::<WildcardFilter>()
|
||||
{
|
||||
if &*filter == other_wildcard {
|
||||
print_sentry = false;
|
||||
break;
|
||||
// if we're here, we've found a new wildcard that we didn't previously display, print it
|
||||
if print_sentry {
|
||||
ferox_print(&format!("{}", new_wildcard), &PROGRESS_PRINTER);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if print_sentry {
|
||||
ferox_print(&format!("{}", filter), &PROGRESS_PRINTER);
|
||||
// create the new filter
|
||||
for wildcard in wildcard_filters {
|
||||
self.handles.filters.send(Command::AddFilter(wildcard))?;
|
||||
}
|
||||
|
||||
// if we're here, we've detected a 404-like response pattern, and we're already filtering for size/word/line
|
||||
//
|
||||
// in addition, we'll create a similarity filter as a fallback
|
||||
for resp in wildcard_responses {
|
||||
let hash = SIM_HASHER.create_signature(preprocess(resp.text()).iter());
|
||||
|
||||
let sim_filter = SimilarityFilter {
|
||||
hash,
|
||||
original_url: resp.url().to_string(),
|
||||
};
|
||||
|
||||
self.handles
|
||||
.filters
|
||||
.send(Command::AddFilter(Box::new(sim_filter)))?;
|
||||
|
||||
if resp.is_directory() {
|
||||
// response is either a 3XX with a Location header that matches url + '/'
|
||||
// or it's a 2XX that ends with a '/'
|
||||
// or it's a 403 that ends with a '/'
|
||||
|
||||
// set the wildcard flag to true, so we can check it when preventing
|
||||
// recursion in event_handlers/scans.rs
|
||||
|
||||
// we'd need to clone the response to give ownership to the global list anyway
|
||||
// so we'll also use that clone to set the wildcard flag
|
||||
let mut cloned_resp = resp.clone();
|
||||
|
||||
cloned_resp.set_wildcard(true);
|
||||
|
||||
// add the response to the global list of responses
|
||||
RESPONSES.insert(cloned_resp);
|
||||
|
||||
// function-internal magic number, indicates that we've detected a wildcard directory
|
||||
req_counter += 100;
|
||||
}
|
||||
}
|
||||
|
||||
// reset the responses for the next method, if it exists
|
||||
responses.clear();
|
||||
}
|
||||
|
||||
// create the new filter
|
||||
self.handles.filters.send(Command::AddFilter(filter))?;
|
||||
|
||||
// if we're here, we've detected a 404-like response pattern, and we're already filtering for size/word/line
|
||||
//
|
||||
// in addition, we'll create a similarity filter as a fallback
|
||||
let hash = SIM_HASHER.create_signature(preprocess(responses[0].text()).iter());
|
||||
|
||||
let sim_filter = SimilarityFilter {
|
||||
hash,
|
||||
original_url: responses[0].url().to_string(),
|
||||
};
|
||||
|
||||
self.handles
|
||||
.filters
|
||||
.send(Command::AddFilter(Box::new(sim_filter)))?;
|
||||
|
||||
if responses[0].is_directory() {
|
||||
// response is either a 3XX with a Location header that matches url + '/'
|
||||
// or it's a 2XX that ends with a '/'
|
||||
// or it's a 403 that ends with a '/'
|
||||
|
||||
// set the wildcard flag to true, so we can check it when preventing
|
||||
// recursion in event_handlers/scans.rs
|
||||
responses[0].set_wildcard(true);
|
||||
|
||||
// add the response to the global list of responses
|
||||
RESPONSES.insert(responses[0].clone());
|
||||
|
||||
// function-internal magic number, indicates that we've detected a wildcard directory
|
||||
req_counter += 100;
|
||||
}
|
||||
|
||||
// reset the responses for the next method, if it exists
|
||||
responses.clear();
|
||||
}
|
||||
|
||||
log::trace!("exit: detect_404_like_responses");
|
||||
@@ -416,96 +462,138 @@ impl HeuristicTests {
|
||||
Ok(Some(retval))
|
||||
}
|
||||
|
||||
/// for all responses, examine chars/words/lines
|
||||
/// if all responses respective lengths match each other, we can assume
|
||||
/// that will remain true for subsequent non-existent urls
|
||||
/// for all responses, group them by status code, then examine chars/words/lines.
|
||||
/// if all responses' respective lengths within a status code grouping match
|
||||
/// each other, we can assume that will remain true for subsequent non-existent urls
|
||||
///
|
||||
/// values are examined from most to least specific (content length, word count, line count)
|
||||
fn examine_404_like_responses(
|
||||
/// within a status code grouping, values are examined from most to
|
||||
/// least specific (content length, word count, line count)
|
||||
#[allow(clippy::vec_box)] // the box is needed in the caller and i dont feel like changing it
|
||||
fn examine_404_like_responses<'a>(
|
||||
&self,
|
||||
responses: &[FeroxResponse],
|
||||
) -> Option<Box<WildcardFilter>> {
|
||||
responses: &'a [FeroxResponse],
|
||||
) -> Option<(Vec<Box<WildcardFilter>>, Vec<&'a FeroxResponse>)> {
|
||||
// aside from word/line/byte counts, additional discriminators are status code
|
||||
// extension, and request method. The request method and extension are handled by
|
||||
// the caller, since they're part of the request and make up the nested for loops
|
||||
// in detect_404_like_responses.
|
||||
//
|
||||
// The status code is handled here, since it's part of the response to catch cases
|
||||
// where we have something like a 403 and a 404
|
||||
|
||||
let mut size_sentry = true;
|
||||
let mut word_sentry = true;
|
||||
let mut line_sentry = true;
|
||||
|
||||
let method = responses[0].method();
|
||||
let status_code = responses[0].status();
|
||||
let content_length = responses[0].content_length();
|
||||
let word_count = responses[0].word_count();
|
||||
let line_count = responses[0].line_count();
|
||||
// returned vec of boxed wildcard filters
|
||||
let mut wildcards = Vec::new();
|
||||
|
||||
for response in &responses[1..] {
|
||||
// if any of the responses differ in length, that particular
|
||||
// response length type is no longer a candidate for filtering
|
||||
if response.content_length() != content_length {
|
||||
size_sentry = false;
|
||||
}
|
||||
// returned vec of ferox responses that are needed for additional
|
||||
// analysis
|
||||
let mut wild_responses = Vec::new();
|
||||
|
||||
if response.word_count() != word_count {
|
||||
word_sentry = false;
|
||||
}
|
||||
// mapping of grouped responses to status code
|
||||
let mut grouped_responses = HashMap::new();
|
||||
|
||||
if response.line_count() != line_count {
|
||||
line_sentry = false;
|
||||
}
|
||||
// iterate over all responses and add each response to its
|
||||
// corresponding status code group
|
||||
for response in responses {
|
||||
grouped_responses
|
||||
.entry(response.status())
|
||||
.or_insert_with(Vec::new)
|
||||
.push(response);
|
||||
}
|
||||
|
||||
if !size_sentry && !word_sentry && !line_sentry {
|
||||
// none of the response lengths match, so we can't filter on any of them
|
||||
return None;
|
||||
// iterate over each grouped response and determine the most specific
|
||||
// filter that can be applied to all responses in the group, i.e.
|
||||
// start from byte count and work 'out' to line count
|
||||
for response_group in grouped_responses.values() {
|
||||
if response_group.len() < 2 {
|
||||
// not enough responses to make a determination
|
||||
continue;
|
||||
}
|
||||
|
||||
let method = response_group[0].method();
|
||||
let status_code = response_group[0].status();
|
||||
let content_length = response_group[0].content_length();
|
||||
let word_count = response_group[0].word_count();
|
||||
let line_count = response_group[0].line_count();
|
||||
|
||||
for response in &response_group[1..] {
|
||||
// if any of the responses differ in length, that particular
|
||||
// response length type is no longer a candidate for filtering
|
||||
if response.content_length() != content_length {
|
||||
size_sentry = false;
|
||||
}
|
||||
|
||||
if response.word_count() != word_count {
|
||||
word_sentry = false;
|
||||
}
|
||||
|
||||
if response.line_count() != line_count {
|
||||
line_sentry = false;
|
||||
}
|
||||
}
|
||||
|
||||
if !size_sentry && !word_sentry && !line_sentry {
|
||||
// none of the response lengths match, so we can't filter on any of them
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut wildcard = WildcardFilter {
|
||||
content_length: None,
|
||||
line_count: None,
|
||||
word_count: None,
|
||||
method: method.to_string(),
|
||||
status_code: status_code.as_u16(),
|
||||
dont_filter: self.handles.config.dont_filter,
|
||||
};
|
||||
|
||||
match (size_sentry, word_sentry, line_sentry) {
|
||||
(true, true, true) => {
|
||||
// all three types of length match, so we can't filter on any of them
|
||||
wildcard.content_length = Some(content_length);
|
||||
wildcard.word_count = Some(word_count);
|
||||
wildcard.line_count = Some(line_count);
|
||||
}
|
||||
(true, true, false) => {
|
||||
// content length and word count match, so we can filter on either
|
||||
wildcard.content_length = Some(content_length);
|
||||
wildcard.word_count = Some(word_count);
|
||||
}
|
||||
(true, false, true) => {
|
||||
// content length and line count match, so we can filter on either
|
||||
wildcard.content_length = Some(content_length);
|
||||
wildcard.line_count = Some(line_count);
|
||||
}
|
||||
(false, true, true) => {
|
||||
// word count and line count match, so we can filter on either
|
||||
wildcard.word_count = Some(word_count);
|
||||
wildcard.line_count = Some(line_count);
|
||||
}
|
||||
(true, false, false) => {
|
||||
// content length matches, so we can filter on that
|
||||
wildcard.content_length = Some(content_length);
|
||||
}
|
||||
(false, true, false) => {
|
||||
// word count matches, so we can filter on that
|
||||
wildcard.word_count = Some(word_count);
|
||||
}
|
||||
(false, false, true) => {
|
||||
// line count matches, so we can filter on that
|
||||
wildcard.line_count = Some(line_count);
|
||||
}
|
||||
(false, false, false) => {
|
||||
// none of the length types match, so we can't filter on any of them
|
||||
unreachable!("no wildcard size matches; handled by the if statement above");
|
||||
}
|
||||
};
|
||||
|
||||
wild_responses.push(response_group[0]);
|
||||
wildcards.push(Box::new(wildcard));
|
||||
}
|
||||
|
||||
let mut wildcard = WildcardFilter {
|
||||
content_length: None,
|
||||
line_count: None,
|
||||
word_count: None,
|
||||
method: method.to_string(),
|
||||
status_code: status_code.as_u16(),
|
||||
dont_filter: self.handles.config.dont_filter,
|
||||
};
|
||||
|
||||
match (size_sentry, word_sentry, line_sentry) {
|
||||
(true, true, true) => {
|
||||
// all three types of length match, so we can't filter on any of them
|
||||
wildcard.content_length = Some(content_length);
|
||||
wildcard.word_count = Some(word_count);
|
||||
wildcard.line_count = Some(line_count);
|
||||
}
|
||||
(true, true, false) => {
|
||||
// content length and word count match, so we can filter on either
|
||||
wildcard.content_length = Some(content_length);
|
||||
wildcard.word_count = Some(word_count);
|
||||
}
|
||||
(true, false, true) => {
|
||||
// content length and line count match, so we can filter on either
|
||||
wildcard.content_length = Some(content_length);
|
||||
wildcard.line_count = Some(line_count);
|
||||
}
|
||||
(false, true, true) => {
|
||||
// word count and line count match, so we can filter on either
|
||||
wildcard.word_count = Some(word_count);
|
||||
wildcard.line_count = Some(line_count);
|
||||
}
|
||||
(true, false, false) => {
|
||||
// content length matches, so we can filter on that
|
||||
wildcard.content_length = Some(content_length);
|
||||
}
|
||||
(false, true, false) => {
|
||||
// word count matches, so we can filter on that
|
||||
wildcard.word_count = Some(word_count);
|
||||
}
|
||||
(false, false, true) => {
|
||||
// line count matches, so we can filter on that
|
||||
wildcard.line_count = Some(line_count);
|
||||
}
|
||||
(false, false, false) => {
|
||||
// none of the length types match, so we can't filter on any of them
|
||||
unreachable!("no wildcard size matches; handled by the if statement above");
|
||||
}
|
||||
};
|
||||
|
||||
Some(Box::new(wildcard))
|
||||
Some((wildcards, wild_responses))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
55
src/main.rs
55
src/main.rs
@@ -237,19 +237,50 @@ async fn wrapped_main(config: Arc<Configuration>) -> Result<()> {
|
||||
exit(0);
|
||||
}
|
||||
|
||||
// cloning an Arc is cheap (it's basically a pointer into the heap)
|
||||
// so that will allow for cheap/safe sharing of a single wordlist across multi-target scans
|
||||
// as well as additional directories found as part of recursion
|
||||
let words = match get_unique_words_from_wordlist(&config.wordlist) {
|
||||
Ok(w) => w,
|
||||
Err(err) => {
|
||||
let secondary = Path::new(SECONDARY_WORDLIST);
|
||||
let words = if config.wordlist.starts_with("http") {
|
||||
// found a url scheme, attempt to download the wordlist
|
||||
let response = config.client.get(&config.wordlist).send().await?;
|
||||
|
||||
if secondary.exists() {
|
||||
eprintln!("Found wordlist in secondary location");
|
||||
get_unique_words_from_wordlist(SECONDARY_WORDLIST)?
|
||||
} else {
|
||||
return Err(err);
|
||||
if !response.status().is_success() {
|
||||
// status code isn't a 200, bail
|
||||
bail!(
|
||||
"[{}] Unable to download wordlist from url: {}",
|
||||
response.status().as_str(),
|
||||
config.wordlist
|
||||
);
|
||||
}
|
||||
|
||||
// attempt to get the filename from the url's path
|
||||
let Some(path_segments) = response
|
||||
.url()
|
||||
.path_segments() else {
|
||||
bail!("Unable to parse path from url: {}", response.url());
|
||||
};
|
||||
|
||||
let Some(filename) = path_segments.last() else {
|
||||
bail!("Unable to parse filename from url's path: {}", response.url().path());
|
||||
};
|
||||
|
||||
let filename = filename.to_string();
|
||||
|
||||
// read the body and write it to disk, then use existing code to read the wordlist
|
||||
let body = response.text().await?;
|
||||
|
||||
std::fs::write(&filename, body)?;
|
||||
|
||||
get_unique_words_from_wordlist(&filename)?
|
||||
} else {
|
||||
match get_unique_words_from_wordlist(&config.wordlist) {
|
||||
Ok(w) => w,
|
||||
Err(err) => {
|
||||
let secondary = Path::new(SECONDARY_WORDLIST);
|
||||
|
||||
if secondary.exists() {
|
||||
eprintln!("Found wordlist in secondary location");
|
||||
get_unique_words_from_wordlist(SECONDARY_WORDLIST)?
|
||||
} else {
|
||||
return Err(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -92,8 +92,9 @@ pub fn initialize() -> Command {
|
||||
.num_args(0)
|
||||
.help_heading("Composite settings")
|
||||
.conflicts_with_all(["rate_limit", "auto_bail"])
|
||||
.help("Set --extract-links, --auto-tune, --collect-words, and --collect-backups to true"),
|
||||
).arg(
|
||||
.help("Set --auto-tune, --collect-words, and --collect-backups to true"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("thorough")
|
||||
.long("thorough")
|
||||
.num_args(0)
|
||||
@@ -433,7 +434,15 @@ pub fn initialize() -> Command {
|
||||
.long("extract-links")
|
||||
.num_args(0)
|
||||
.help_heading("Scan settings")
|
||||
.help("Extract links from response body (html, javascript, etc...); make new requests based on findings")
|
||||
.hide(true)
|
||||
.help("Extract links from response body (html, javascript, etc...); make new requests based on findings (default: true)")
|
||||
)
|
||||
.arg(
|
||||
Arg::new("dont_extract_links")
|
||||
.long("dont-extract-links")
|
||||
.num_args(0)
|
||||
.help_heading("Scan settings")
|
||||
.help("Don't extract links from response body (html, javascript, etc...)")
|
||||
)
|
||||
.arg(
|
||||
Arg::new("scan_limit")
|
||||
@@ -477,7 +486,7 @@ pub fn initialize() -> Command {
|
||||
.long("wordlist")
|
||||
.value_hint(ValueHint::FilePath)
|
||||
.value_name("FILE")
|
||||
.help("Path to the wordlist")
|
||||
.help("Path or URL of the wordlist")
|
||||
.help_heading("Scan settings")
|
||||
.num_args(1),
|
||||
).arg(
|
||||
@@ -515,7 +524,8 @@ pub fn initialize() -> Command {
|
||||
.num_args(0)
|
||||
.help_heading("Dynamic collection settings")
|
||||
.help("Automatically request likely backup extensions for \"found\" urls")
|
||||
).arg(
|
||||
)
|
||||
.arg(
|
||||
Arg::new("collect_words")
|
||||
.short('g')
|
||||
.long("collect-words")
|
||||
@@ -684,9 +694,6 @@ EXAMPLES:
|
||||
Pass auth token via query parameter
|
||||
./feroxbuster -u http://127.1 --query token=0123456789ABCDEF
|
||||
|
||||
Find links in javascript/html and make additional requests based on results
|
||||
./feroxbuster -u http://127.1 --extract-links
|
||||
|
||||
Ludicrous speed... go!
|
||||
./feroxbuster -u http://127.1 --threads 200
|
||||
|
||||
|
||||
@@ -469,7 +469,7 @@ fn feroxstates_feroxserialize_implementation() {
|
||||
r#""headers""#,
|
||||
r#""queries":[]"#,
|
||||
r#""no_recursion":false"#,
|
||||
r#""extract_links":false"#,
|
||||
r#""extract_links":true"#,
|
||||
r#""add_slash":false"#,
|
||||
r#""stdin":false"#,
|
||||
r#""depth":4"#,
|
||||
|
||||
@@ -283,15 +283,21 @@ impl FeroxScanner {
|
||||
let mut message = format!("=> {}", style("Directory listing").blue().bright());
|
||||
|
||||
if !self.handles.config.extract_links {
|
||||
write!(message, " (add {} to scan)", style("-e").bright().yellow())?;
|
||||
write!(
|
||||
message,
|
||||
" (remove {} to scan)",
|
||||
style("--dont-extract-links").bright().yellow()
|
||||
)?;
|
||||
}
|
||||
|
||||
progress_bar.reset_eta();
|
||||
progress_bar.finish_with_message(&message);
|
||||
if !self.handles.config.force_recursion {
|
||||
progress_bar.reset_eta();
|
||||
progress_bar.finish_with_message(&message);
|
||||
|
||||
ferox_scan.finish()?;
|
||||
ferox_scan.finish()?;
|
||||
|
||||
return Ok(()); // nothing left to do if we found a dir listing
|
||||
return Ok(()); // nothing left to do if we found a dir listing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -164,7 +164,7 @@ fn test_static_wildcard_request_found() -> Result<(), Box<dyn std::error::Error>
|
||||
|
||||
let mock = srv.mock(|when, then| {
|
||||
when.method(GET)
|
||||
.path_matches(Regex::new("/[a-zA-Z0-9]{32}/").unwrap());
|
||||
.path_matches(Regex::new("/[.a-zA-Z0-9]{32,}/").unwrap());
|
||||
then.status(200).body("this is a test");
|
||||
});
|
||||
|
||||
@@ -188,7 +188,8 @@ fn test_static_wildcard_request_found() -> Result<(), Box<dyn std::error::Error>
|
||||
.and(predicate::str::contains("1l")),
|
||||
);
|
||||
|
||||
assert_eq!(mock.hits(), 1);
|
||||
assert_eq!(mock.hits(), 6);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -305,11 +306,67 @@ fn heuristics_wildcard_test_with_two_static_wildcards_with_silent_enabled(
|
||||
.success()
|
||||
.stdout(predicate::str::contains(srv.url("/")));
|
||||
|
||||
assert_eq!(mock.hits(), 4);
|
||||
assert_eq!(mock.hits(), 6);
|
||||
assert_eq!(mock2.hits(), 1);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// test finds a 404-like response that returns a 403 and a 403 directory should still be allowed
|
||||
/// to be tested for recrusion
|
||||
fn heuristics_wildcard_test_that_auto_filtering_403s_still_allows_for_recursion_into_403_directories(
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let srv = MockServer::start();
|
||||
|
||||
let super_long = String::from("92969beae6bf4beb855d1622406d87e395c87387a9ad432e8a11245002b709b03cf609d471004154b83bcc1c6ec49f6f09d471004154b83bcc1c6ec49f6f");
|
||||
|
||||
let (tmp_dir, file) =
|
||||
setup_tmp_directory(&["LICENSE".to_string(), super_long.clone()], "wordlist")?;
|
||||
|
||||
srv.mock(|when, then| {
|
||||
when.method(GET)
|
||||
.path_matches(Regex::new("/.?[a-zA-Z0-9]{32,103}").unwrap());
|
||||
then.status(403)
|
||||
.body("this is a testAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
|
||||
});
|
||||
|
||||
srv.mock(|when, then| {
|
||||
when.method(GET).path("/LICENSE/");
|
||||
then.status(403)
|
||||
.body("this is a testAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
|
||||
});
|
||||
|
||||
srv.mock(|when, then| {
|
||||
when.method(GET).path(format!("/LICENSE/{}", super_long));
|
||||
then.status(200);
|
||||
});
|
||||
|
||||
let cmd = Command::cargo_bin("feroxbuster")
|
||||
.unwrap()
|
||||
.arg("--url")
|
||||
.arg(srv.url("/"))
|
||||
.arg("--wordlist")
|
||||
.arg(file.as_os_str())
|
||||
.arg("--add-slash")
|
||||
.unwrap();
|
||||
|
||||
teardown_tmp_directory(tmp_dir);
|
||||
|
||||
cmd.assert().success().stdout(
|
||||
predicate::str::contains("GET")
|
||||
.and(predicate::str::contains(
|
||||
"Auto-filtering found 404-like response and created new filter",
|
||||
))
|
||||
.and(predicate::str::contains("403"))
|
||||
.and(predicate::str::contains("1l"))
|
||||
.and(predicate::str::contains("4w"))
|
||||
.and(predicate::str::contains("46c"))
|
||||
.and(predicate::str::contains(srv.url("/LICENSE/LICENSE/"))),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// /// test finds a static wildcard and reports as much to stdout and a file
|
||||
// fn heuristics_wildcard_test_with_two_static_wildcards_and_output_to_file() {
|
||||
|
||||
@@ -218,3 +218,46 @@ fn main_parallel_creates_output_directory() -> Result<(), Box<dyn std::error::Er
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// download a wordlist from a url
|
||||
fn main_download_wordlist_from_url() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let srv = MockServer::start();
|
||||
|
||||
let (tmp_dir, _) = setup_tmp_directory(&["a".to_string()], "wordlist")?;
|
||||
|
||||
let mock1 = srv.mock(|when, then| {
|
||||
when.method(GET).path("/derp");
|
||||
then.status(200).body("stuff\nthings");
|
||||
});
|
||||
|
||||
// serve endpoints stuff and things
|
||||
let mock2 = srv.mock(|when, then| {
|
||||
when.method(GET).path("/stuff");
|
||||
then.status(200);
|
||||
});
|
||||
|
||||
let mock3 = srv.mock(|when, then| {
|
||||
when.method(GET).path("/things");
|
||||
then.status(200);
|
||||
});
|
||||
|
||||
Command::cargo_bin("feroxbuster")
|
||||
.unwrap()
|
||||
.current_dir(&tmp_dir)
|
||||
.arg("--url")
|
||||
.arg(srv.url("/"))
|
||||
.arg("--wordlist")
|
||||
.arg(srv.url("/derp"))
|
||||
.assert()
|
||||
.success()
|
||||
.stderr(predicate::str::contains(srv.url("/derp")));
|
||||
|
||||
teardown_tmp_directory(tmp_dir);
|
||||
|
||||
assert_eq!(mock1.hits(), 1); // downloaded wordlist
|
||||
assert_eq!(mock2.hits(), 1); // found stuff from wordlist
|
||||
assert_eq!(mock3.hits(), 1); // found things from wordlist
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user