mirror of
https://github.com/epi052/feroxbuster.git
synced 2026-05-23 04:51:13 -03:00
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
70ae679b50 | ||
|
|
01da38fa6d | ||
|
|
22586f3835 | ||
|
|
0510cb91aa | ||
|
|
4663ec4cea | ||
|
|
e8a98a54d8 | ||
|
|
fa42c72ac5 | ||
|
|
4ce77b5012 | ||
|
|
72c09854fc | ||
|
|
17a3d8af9f | ||
|
|
b67f1399b3 | ||
|
|
57db4adb69 | ||
|
|
87b6589f51 | ||
|
|
f36897431e | ||
|
|
3c89721f54 | ||
|
|
9193614f3c | ||
|
|
8eb41f40a0 |
@@ -764,6 +764,91 @@
|
||||
"contributions": [
|
||||
"ideas"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "deadloot",
|
||||
"name": "deadloot",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/92878901?v=4",
|
||||
"profile": "https://github.com/deadloot",
|
||||
"contributions": [
|
||||
"ideas"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Spidle",
|
||||
"name": "Spidle",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/90011249?v=4",
|
||||
"profile": "https://github.com/Spidle",
|
||||
"contributions": [
|
||||
"ideas"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "JulianGR",
|
||||
"name": "Julián Gómez",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/53094530?v=4",
|
||||
"profile": "https://github.com/JulianGR",
|
||||
"contributions": [
|
||||
"ideas",
|
||||
"infra",
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "soutzis",
|
||||
"name": "Petros",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/25797286?v=4",
|
||||
"profile": "https://github.com/soutzis",
|
||||
"contributions": [
|
||||
"bug"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "sitiom",
|
||||
"name": "Ryan",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/56180050?v=4",
|
||||
"profile": "https://github.com/sitiom",
|
||||
"contributions": [
|
||||
"infra",
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "wikamp-collaborator",
|
||||
"name": "wikamp-collaborator",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/147445097?v=4",
|
||||
"profile": "https://github.com/wikamp-collaborator",
|
||||
"contributions": [
|
||||
"ideas",
|
||||
"infra"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "L1-0",
|
||||
"name": "Lino",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/123986259?v=4",
|
||||
"profile": "http://lino.codes",
|
||||
"contributions": [
|
||||
"bug"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "sa7mon",
|
||||
"name": "Dan Salmon",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/3712226?v=4",
|
||||
"profile": "https://danthesalmon.com",
|
||||
"contributions": [
|
||||
"ideas"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "swordfish0x0",
|
||||
"name": "swordfish0x0",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/21209130?v=4",
|
||||
"profile": "https://github.com/swordfish0x0",
|
||||
"contributions": [
|
||||
"ideas"
|
||||
]
|
||||
}
|
||||
],
|
||||
"contributorsPerLine": 7,
|
||||
|
||||
32
.github/workflows/build.yml
vendored
32
.github/workflows/build.yml
vendored
@@ -156,6 +156,38 @@ jobs:
|
||||
with:
|
||||
name: x86_64-macos-feroxbuster.tar.gz
|
||||
path: x86_64-macos-feroxbuster.tar.gz
|
||||
|
||||
build-macos-aarch64:
|
||||
env:
|
||||
IN_PIPELINE: true
|
||||
runs-on: macos-latest
|
||||
# if: github.ref == 'refs/heads/main'
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
target: aarch64-apple-darwin
|
||||
override: true
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
use-cross: true
|
||||
command: build
|
||||
args: --release --target=aarch64-apple-darwin
|
||||
- name: Strip symbols from binary
|
||||
run: |
|
||||
strip -u -r target/aarch64-apple-darwin/release/feroxbuster
|
||||
- name: Build tar.gz for homebrew installs
|
||||
run: |
|
||||
tar czf aarch64-macos-feroxbuster.tar.gz -C target/aarch64-apple-darwin/release feroxbuster
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: aarch64-macos-feroxbuster
|
||||
path: target/aarch64-apple-darwin/release/feroxbuster
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: aarch64-macos-feroxbuster.tar.gz
|
||||
path: aarch64-macos-feroxbuster.tar.gz
|
||||
|
||||
build-windows:
|
||||
env:
|
||||
|
||||
14
.github/workflows/winget.yml
vendored
Normal file
14
.github/workflows/winget.yml
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
name: Publish to Winget
|
||||
on:
|
||||
release:
|
||||
types: [released]
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: vedantmgoyal2009/winget-releaser@v2
|
||||
with:
|
||||
identifier: epi052.feroxbuster
|
||||
installers-regex: '-windows-feroxbuster\.exe\.zip$'
|
||||
token: ${{ secrets.WINGET_TOKEN }}
|
||||
1617
Cargo.lock
generated
1617
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
36
Cargo.toml
36
Cargo.toml
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "feroxbuster"
|
||||
version = "2.10.2"
|
||||
version = "2.10.4"
|
||||
authors = ["Ben 'epi' Risher (@epi052)"]
|
||||
license = "MIT"
|
||||
edition = "2021"
|
||||
@@ -22,46 +22,46 @@ build = "build.rs"
|
||||
maintenance = { status = "actively-developed" }
|
||||
|
||||
[build-dependencies]
|
||||
clap = { version = "4.3", features = ["wrap_help", "cargo"] }
|
||||
clap_complete = "4.3"
|
||||
regex = "1.9"
|
||||
clap = { version = "4.5", features = ["wrap_help", "cargo"] }
|
||||
clap_complete = "4.5"
|
||||
regex = "1.10"
|
||||
lazy_static = "1.4"
|
||||
dirs = "5.0"
|
||||
|
||||
[dependencies]
|
||||
scraper = "0.18"
|
||||
scraper = "0.19"
|
||||
futures = "0.3"
|
||||
tokio = { version = "1.29", features = ["full"] }
|
||||
tokio = { version = "1.38", features = ["full"] }
|
||||
tokio-util = { version = "0.7", features = ["codec"] }
|
||||
log = "0.4"
|
||||
env_logger = "0.10"
|
||||
reqwest = { version = "0.11", features = ["socks", "native-tls-alpn"] }
|
||||
env_logger = "0.11"
|
||||
reqwest = { version = "0.12", features = ["socks", "native-tls-alpn"] }
|
||||
# uses feature unification to add 'serde' to reqwest::Url
|
||||
url = { version = "2.4", features = ["serde"] }
|
||||
url = { version = "2.5", features = ["serde"] }
|
||||
serde_regex = "1.1"
|
||||
clap = { version = "4.3", features = ["wrap_help", "cargo"] }
|
||||
clap = { version = "4.5", features = ["wrap_help", "cargo"] }
|
||||
lazy_static = "1.4"
|
||||
toml = "0.8"
|
||||
serde = { version = "1.0", features = ["derive", "rc"] }
|
||||
serde_json = "1.0"
|
||||
uuid = { version = "1.4", features = ["v4"] }
|
||||
uuid = { version = "1.8", features = ["v4"] }
|
||||
# last known working version of indicatif; 0.17.5 has a bug that causes the
|
||||
# scan menu to fail spectacularly
|
||||
indicatif = { version = "0.17.3" }
|
||||
indicatif = { version = "0.17.8" }
|
||||
console = "0.15"
|
||||
openssl = { version = "0.10", features = ["vendored"] }
|
||||
dirs = "5.0"
|
||||
regex = "1.9"
|
||||
regex = "1.10"
|
||||
crossterm = "0.27"
|
||||
rlimit = "0.10"
|
||||
ctrlc = "3.4"
|
||||
anyhow = "1.0"
|
||||
leaky-bucket = "1.0"
|
||||
leaky-bucket = "1.1"
|
||||
gaoya = "0.2"
|
||||
# 0.37+ relies on the broken version of indicatif and forces
|
||||
# the broken version to be used regardless of the version
|
||||
# specified above
|
||||
self_update = { version = "0.36", features = [
|
||||
self_update = { version = "0.40", features = [
|
||||
"archive-tar",
|
||||
"compression-flate2",
|
||||
"archive-zip",
|
||||
@@ -69,10 +69,10 @@ self_update = { version = "0.36", features = [
|
||||
] }
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.6"
|
||||
httpmock = "0.6"
|
||||
tempfile = "3.10"
|
||||
httpmock = "0.7"
|
||||
assert_cmd = "2.0"
|
||||
predicates = "3.0"
|
||||
predicates = "3.1"
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
[tasks.upgrade]
|
||||
dependencies = ["upgrade-deps", "update"]
|
||||
|
||||
[tasks.check]
|
||||
dependencies = ["fmt", "clippy", "test"]
|
||||
|
||||
# cleaning
|
||||
[tasks.clean-state]
|
||||
script = """
|
||||
@@ -24,6 +27,12 @@ script = """
|
||||
cargo clippy --all-targets --all-features -- -D warnings
|
||||
"""
|
||||
|
||||
[tasks.fmt]
|
||||
clear = true
|
||||
script = """
|
||||
cargo fmt --all
|
||||
"""
|
||||
|
||||
# tests
|
||||
[tasks.test]
|
||||
clear = true
|
||||
|
||||
17
README.md
17
README.md
@@ -121,6 +121,12 @@ Expand-Archive .\feroxbuster.zip
|
||||
.\feroxbuster\feroxbuster.exe -V
|
||||
```
|
||||
|
||||
#### Windows via Winget
|
||||
|
||||
```
|
||||
winget install epi052.feroxbuster
|
||||
```
|
||||
|
||||
#### Windows via Chocolatey
|
||||
|
||||
```
|
||||
@@ -312,6 +318,17 @@ 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/NotoriousRebel"><img src="https://avatars.githubusercontent.com/u/36310667?v=4?s=100" width="100px;" alt="Matt"/><br /><sub><b>Matt</b></sub></a><br /><a href="#ideas-NotoriousRebel" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/tritoke"><img src="https://avatars.githubusercontent.com/u/34941249?v=4?s=100" width="100px;" alt="Sam Leonard"/><br /><sub><b>Sam Leonard</b></sub></a><br /><a href="https://github.com/epi052/feroxbuster/commits?author=tritoke" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/rew1nter"><img src="https://avatars.githubusercontent.com/u/64508791?v=4?s=100" width="100px;" alt="Rewinter"/><br /><sub><b>Rewinter</b></sub></a><br /><a href="#ideas-rew1nter" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/deadloot"><img src="https://avatars.githubusercontent.com/u/92878901?v=4?s=100" width="100px;" alt="deadloot"/><br /><sub><b>deadloot</b></sub></a><br /><a href="#ideas-deadloot" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Spidle"><img src="https://avatars.githubusercontent.com/u/90011249?v=4?s=100" width="100px;" alt="Spidle"/><br /><sub><b>Spidle</b></sub></a><br /><a href="#ideas-Spidle" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/JulianGR"><img src="https://avatars.githubusercontent.com/u/53094530?v=4?s=100" width="100px;" alt="Julián Gómez"/><br /><sub><b>Julián Gómez</b></sub></a><br /><a href="#ideas-JulianGR" title="Ideas, Planning, & Feedback">🤔</a> <a href="#infra-JulianGR" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="https://github.com/epi052/feroxbuster/commits?author=JulianGR" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/soutzis"><img src="https://avatars.githubusercontent.com/u/25797286?v=4?s=100" width="100px;" alt="Petros"/><br /><sub><b>Petros</b></sub></a><br /><a href="https://github.com/epi052/feroxbuster/issues?q=author%3Asoutzis" title="Bug reports">🐛</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/sitiom"><img src="https://avatars.githubusercontent.com/u/56180050?v=4?s=100" width="100px;" alt="Ryan"/><br /><sub><b>Ryan</b></sub></a><br /><a href="#infra-sitiom" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="https://github.com/epi052/feroxbuster/commits?author=sitiom" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/wikamp-collaborator"><img src="https://avatars.githubusercontent.com/u/147445097?v=4?s=100" width="100px;" alt="wikamp-collaborator"/><br /><sub><b>wikamp-collaborator</b></sub></a><br /><a href="#ideas-wikamp-collaborator" title="Ideas, Planning, & Feedback">🤔</a> <a href="#infra-wikamp-collaborator" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://lino.codes"><img src="https://avatars.githubusercontent.com/u/123986259?v=4?s=100" width="100px;" alt="Lino"/><br /><sub><b>Lino</b></sub></a><br /><a href="https://github.com/epi052/feroxbuster/issues?q=author%3AL1-0" title="Bug reports">🐛</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://danthesalmon.com"><img src="https://avatars.githubusercontent.com/u/3712226?v=4?s=100" width="100px;" alt="Dan Salmon"/><br /><sub><b>Dan Salmon</b></sub></a><br /><a href="#ideas-sa7mon" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/swordfish0x0"><img src="https://avatars.githubusercontent.com/u/21209130?v=4?s=100" width="100px;" alt="swordfish0x0"/><br /><sub><b>swordfish0x0</b></sub></a><br /><a href="#ideas-swordfish0x0" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -67,6 +67,7 @@
|
||||
# note: if multi-line is used, all key/value pairs under it belong to the headers table until the next table
|
||||
# is found or the end of the file is reached
|
||||
#
|
||||
# If you want to use [headers], UNCOMMENT the line below
|
||||
# [headers]
|
||||
# stuff = "things"
|
||||
# more = "headers"
|
||||
|
||||
@@ -14,7 +14,7 @@ _feroxbuster() {
|
||||
fi
|
||||
|
||||
local context curcontext="$curcontext" state line
|
||||
_arguments "${_arguments_options[@]}" \
|
||||
_arguments "${_arguments_options[@]}" : \
|
||||
'-u+[The target URL (required, unless \[--stdin || --resume-from\] used)]:URL:_urls' \
|
||||
'--url=[The target URL (required, unless \[--stdin || --resume-from\] used)]:URL:_urls' \
|
||||
'(-u --url)--resume-from=[State file from which to resume a partially complete scan (ex. --resume-from ferox-1606586780.state)]:STATE_FILE:_files' \
|
||||
@@ -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.10.2)]:USER_AGENT: ' \
|
||||
'--user-agent=[Sets the User-Agent (default\: feroxbuster/2.10.2)]:USER_AGENT: ' \
|
||||
'-a+[Sets the User-Agent (default\: feroxbuster/2.10.4)]:USER_AGENT: ' \
|
||||
'--user-agent=[Sets the User-Agent (default\: feroxbuster/2.10.4)]:USER_AGENT: ' \
|
||||
'*-x+[File extension(s) to search for (ex\: -x php -x pdf js); reads values (newline-separated) from file if input starts with an @ (ex\: @ext.txt)]:FILE_EXTENSION: ' \
|
||||
'*--extensions=[File extension(s) to search for (ex\: -x php -x pdf js); reads values (newline-separated) from file if input starts with an @ (ex\: @ext.txt)]:FILE_EXTENSION: ' \
|
||||
'*-m+[Which HTTP request method(s) should be sent (default\: GET)]:HTTP_METHODS: ' \
|
||||
@@ -40,8 +40,8 @@ _feroxbuster() {
|
||||
'*--dont-scan=[URL(s) or Regex Pattern(s) to exclude from recursion/scans]:URL: ' \
|
||||
'*-S+[Filter out messages of a particular size (ex\: -S 5120 -S 4927,1970)]:SIZE: ' \
|
||||
'*--filter-size=[Filter out messages of a particular size (ex\: -S 5120 -S 4927,1970)]:SIZE: ' \
|
||||
'*-X+[Filter out messages via regular expression matching on the response'\''s body (ex\: -X '\''^ignore me\$'\'')]:REGEX: ' \
|
||||
'*--filter-regex=[Filter out messages via regular expression matching on the response'\''s body (ex\: -X '\''^ignore me\$'\'')]:REGEX: ' \
|
||||
'*-X+[Filter out messages via regular expression matching on the response'\''s body/headers (ex\: -X '\''^ignore me\$'\'')]:REGEX: ' \
|
||||
'*--filter-regex=[Filter out messages via regular expression matching on the response'\''s body/headers (ex\: -X '\''^ignore me\$'\'')]:REGEX: ' \
|
||||
'*-W+[Filter out messages of a particular word count (ex\: -W 312 -W 91,82)]:WORDS: ' \
|
||||
'*--filter-words=[Filter out messages of a particular word count (ex\: -W 312 -W 91,82)]:WORDS: ' \
|
||||
'*-N+[Filter out messages of a particular line count (ex\: -N 20 -N 31,30)]:LINES: ' \
|
||||
|
||||
@@ -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.10.2)')
|
||||
[CompletionResult]::new('--user-agent', 'user-agent', [CompletionResultType]::ParameterName, 'Sets the User-Agent (default: feroxbuster/2.10.2)')
|
||||
[CompletionResult]::new('-a', 'a', [CompletionResultType]::ParameterName, 'Sets the User-Agent (default: feroxbuster/2.10.4)')
|
||||
[CompletionResult]::new('--user-agent', 'user-agent', [CompletionResultType]::ParameterName, 'Sets the User-Agent (default: feroxbuster/2.10.4)')
|
||||
[CompletionResult]::new('-x', 'x', [CompletionResultType]::ParameterName, 'File extension(s) to search for (ex: -x php -x pdf js); reads values (newline-separated) from file if input starts with an @ (ex: @ext.txt)')
|
||||
[CompletionResult]::new('--extensions', 'extensions', [CompletionResultType]::ParameterName, 'File extension(s) to search for (ex: -x php -x pdf js); reads values (newline-separated) from file if input starts with an @ (ex: @ext.txt)')
|
||||
[CompletionResult]::new('-m', 'm', [CompletionResultType]::ParameterName, 'Which HTTP request method(s) should be sent (default: GET)')
|
||||
@@ -46,8 +46,8 @@ Register-ArgumentCompleter -Native -CommandName 'feroxbuster' -ScriptBlock {
|
||||
[CompletionResult]::new('--dont-scan', 'dont-scan', [CompletionResultType]::ParameterName, 'URL(s) or Regex Pattern(s) to exclude from recursion/scans')
|
||||
[CompletionResult]::new('-S', 'S ', [CompletionResultType]::ParameterName, 'Filter out messages of a particular size (ex: -S 5120 -S 4927,1970)')
|
||||
[CompletionResult]::new('--filter-size', 'filter-size', [CompletionResultType]::ParameterName, 'Filter out messages of a particular size (ex: -S 5120 -S 4927,1970)')
|
||||
[CompletionResult]::new('-X', 'X ', [CompletionResultType]::ParameterName, 'Filter out messages via regular expression matching on the response''s body (ex: -X ''^ignore me$'')')
|
||||
[CompletionResult]::new('--filter-regex', 'filter-regex', [CompletionResultType]::ParameterName, 'Filter out messages via regular expression matching on the response''s body (ex: -X ''^ignore me$'')')
|
||||
[CompletionResult]::new('-X', 'X ', [CompletionResultType]::ParameterName, 'Filter out messages via regular expression matching on the response''s body/headers (ex: -X ''^ignore me$'')')
|
||||
[CompletionResult]::new('--filter-regex', 'filter-regex', [CompletionResultType]::ParameterName, 'Filter out messages via regular expression matching on the response''s body/headers (ex: -X ''^ignore me$'')')
|
||||
[CompletionResult]::new('-W', 'W ', [CompletionResultType]::ParameterName, 'Filter out messages of a particular word count (ex: -W 312 -W 91,82)')
|
||||
[CompletionResult]::new('--filter-words', 'filter-words', [CompletionResultType]::ParameterName, 'Filter out messages of a particular word count (ex: -W 312 -W 91,82)')
|
||||
[CompletionResult]::new('-N', 'N ', [CompletionResultType]::ParameterName, 'Filter out messages of a particular line count (ex: -N 20 -N 31,30)')
|
||||
|
||||
@@ -35,12 +35,12 @@ _feroxbuster() {
|
||||
;;
|
||||
--resume-from)
|
||||
local oldifs
|
||||
if [[ -v IFS ]]; then
|
||||
if [ -n "${IFS+x}" ]; then
|
||||
oldifs="$IFS"
|
||||
fi
|
||||
IFS=$'\n'
|
||||
COMPREPLY=($(compgen -f "${cur}"))
|
||||
if [[ -v oldifs ]]; then
|
||||
if [ -n "${oldifs+x}" ]; then
|
||||
IFS="$oldifs"
|
||||
fi
|
||||
if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then
|
||||
@@ -190,12 +190,12 @@ _feroxbuster() {
|
||||
;;
|
||||
--server-certs)
|
||||
local oldifs
|
||||
if [[ -v IFS ]]; then
|
||||
if [ -n "${IFS+x}" ]; then
|
||||
oldifs="$IFS"
|
||||
fi
|
||||
IFS=$'\n'
|
||||
COMPREPLY=($(compgen -f "${cur}"))
|
||||
if [[ -v oldifs ]]; then
|
||||
if [ -n "${oldifs+x}" ]; then
|
||||
IFS="$oldifs"
|
||||
fi
|
||||
if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then
|
||||
@@ -205,12 +205,12 @@ _feroxbuster() {
|
||||
;;
|
||||
--client-cert)
|
||||
local oldifs
|
||||
if [[ -v IFS ]]; then
|
||||
if [ -n "${IFS+x}" ]; then
|
||||
oldifs="$IFS"
|
||||
fi
|
||||
IFS=$'\n'
|
||||
COMPREPLY=($(compgen -f "${cur}"))
|
||||
if [[ -v oldifs ]]; then
|
||||
if [ -n "${oldifs+x}" ]; then
|
||||
IFS="$oldifs"
|
||||
fi
|
||||
if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then
|
||||
@@ -220,12 +220,12 @@ _feroxbuster() {
|
||||
;;
|
||||
--client-key)
|
||||
local oldifs
|
||||
if [[ -v IFS ]]; then
|
||||
if [ -n "${IFS+x}" ]; then
|
||||
oldifs="$IFS"
|
||||
fi
|
||||
IFS=$'\n'
|
||||
COMPREPLY=($(compgen -f "${cur}"))
|
||||
if [[ -v oldifs ]]; then
|
||||
if [ -n "${oldifs+x}" ]; then
|
||||
IFS="$oldifs"
|
||||
fi
|
||||
if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then
|
||||
@@ -271,12 +271,12 @@ _feroxbuster() {
|
||||
;;
|
||||
--wordlist)
|
||||
local oldifs
|
||||
if [[ -v IFS ]]; then
|
||||
if [ -n "${IFS+x}" ]; then
|
||||
oldifs="$IFS"
|
||||
fi
|
||||
IFS=$'\n'
|
||||
COMPREPLY=($(compgen -f "${cur}"))
|
||||
if [[ -v oldifs ]]; then
|
||||
if [ -n "${oldifs+x}" ]; then
|
||||
IFS="$oldifs"
|
||||
fi
|
||||
if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then
|
||||
@@ -286,12 +286,12 @@ _feroxbuster() {
|
||||
;;
|
||||
-w)
|
||||
local oldifs
|
||||
if [[ -v IFS ]]; then
|
||||
if [ -n "${IFS+x}" ]; then
|
||||
oldifs="$IFS"
|
||||
fi
|
||||
IFS=$'\n'
|
||||
COMPREPLY=($(compgen -f "${cur}"))
|
||||
if [[ -v oldifs ]]; then
|
||||
if [ -n "${oldifs+x}" ]; then
|
||||
IFS="$oldifs"
|
||||
fi
|
||||
if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then
|
||||
@@ -317,12 +317,12 @@ _feroxbuster() {
|
||||
;;
|
||||
--output)
|
||||
local oldifs
|
||||
if [[ -v IFS ]]; then
|
||||
if [ -n "${IFS+x}" ]; then
|
||||
oldifs="$IFS"
|
||||
fi
|
||||
IFS=$'\n'
|
||||
COMPREPLY=($(compgen -f "${cur}"))
|
||||
if [[ -v oldifs ]]; then
|
||||
if [ -n "${oldifs+x}" ]; then
|
||||
IFS="$oldifs"
|
||||
fi
|
||||
if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then
|
||||
@@ -332,12 +332,12 @@ _feroxbuster() {
|
||||
;;
|
||||
-o)
|
||||
local oldifs
|
||||
if [[ -v IFS ]]; then
|
||||
if [ -n "${IFS+x}" ]; then
|
||||
oldifs="$IFS"
|
||||
fi
|
||||
IFS=$'\n'
|
||||
COMPREPLY=($(compgen -f "${cur}"))
|
||||
if [[ -v oldifs ]]; then
|
||||
if [ -n "${oldifs+x}" ]; then
|
||||
IFS="$oldifs"
|
||||
fi
|
||||
if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then
|
||||
@@ -347,12 +347,12 @@ _feroxbuster() {
|
||||
;;
|
||||
--debug-log)
|
||||
local oldifs
|
||||
if [[ -v IFS ]]; then
|
||||
if [ -n "${IFS+x}" ]; then
|
||||
oldifs="$IFS"
|
||||
fi
|
||||
IFS=$'\n'
|
||||
COMPREPLY=($(compgen -f "${cur}"))
|
||||
if [[ -v oldifs ]]; then
|
||||
if [ -n "${oldifs+x}" ]; then
|
||||
IFS="$oldifs"
|
||||
fi
|
||||
if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then
|
||||
|
||||
@@ -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.10.2)'
|
||||
cand --user-agent 'Sets the User-Agent (default: feroxbuster/2.10.2)'
|
||||
cand -a 'Sets the User-Agent (default: feroxbuster/2.10.4)'
|
||||
cand --user-agent 'Sets the User-Agent (default: feroxbuster/2.10.4)'
|
||||
cand -x 'File extension(s) to search for (ex: -x php -x pdf js); reads values (newline-separated) from file if input starts with an @ (ex: @ext.txt)'
|
||||
cand --extensions 'File extension(s) to search for (ex: -x php -x pdf js); reads values (newline-separated) from file if input starts with an @ (ex: @ext.txt)'
|
||||
cand -m 'Which HTTP request method(s) should be sent (default: GET)'
|
||||
@@ -43,8 +43,8 @@ set edit:completion:arg-completer[feroxbuster] = {|@words|
|
||||
cand --dont-scan 'URL(s) or Regex Pattern(s) to exclude from recursion/scans'
|
||||
cand -S 'Filter out messages of a particular size (ex: -S 5120 -S 4927,1970)'
|
||||
cand --filter-size 'Filter out messages of a particular size (ex: -S 5120 -S 4927,1970)'
|
||||
cand -X 'Filter out messages via regular expression matching on the response''s body (ex: -X ''^ignore me$'')'
|
||||
cand --filter-regex 'Filter out messages via regular expression matching on the response''s body (ex: -X ''^ignore me$'')'
|
||||
cand -X 'Filter out messages via regular expression matching on the response''s body/headers (ex: -X ''^ignore me$'')'
|
||||
cand --filter-regex 'Filter out messages via regular expression matching on the response''s body/headers (ex: -X ''^ignore me$'')'
|
||||
cand -W 'Filter out messages of a particular word count (ex: -W 312 -W 91,82)'
|
||||
cand --filter-words 'Filter out messages of a particular word count (ex: -W 312 -W 91,82)'
|
||||
cand -N 'Filter out messages of a particular line count (ex: -N 20 -N 31,30)'
|
||||
|
||||
@@ -620,7 +620,7 @@ impl Configuration {
|
||||
update_config_if_present!(&mut config.resume_from, args, "resume_from", String);
|
||||
|
||||
if let Ok(Some(inner)) = args.try_get_one::<String>("time_limit") {
|
||||
config.time_limit = inner.to_owned();
|
||||
inner.clone_into(&mut config.time_limit);
|
||||
}
|
||||
|
||||
if let Some(arg) = args.get_many::<String>("status_codes") {
|
||||
@@ -644,7 +644,7 @@ impl Configuration {
|
||||
.collect();
|
||||
} else {
|
||||
// not passed in by the user, use whatever value is held in status_codes
|
||||
config.replay_codes = config.status_codes.clone();
|
||||
config.replay_codes.clone_from(&config.status_codes);
|
||||
}
|
||||
|
||||
if let Some(arg) = args.get_many::<String>("filter_status") {
|
||||
@@ -704,6 +704,11 @@ impl Configuration {
|
||||
} else {
|
||||
config.data = arg.as_bytes().to_vec();
|
||||
}
|
||||
|
||||
if config.methods == methods() {
|
||||
// if the user didn't specify a method, we're going to assume they meant to use POST
|
||||
config.methods = vec![Method::POST.as_str().to_string()];
|
||||
}
|
||||
}
|
||||
|
||||
if came_from_cli!(args, "stdin") {
|
||||
@@ -935,7 +940,15 @@ impl Configuration {
|
||||
// all other items in the iterator returned by split, when combined with the
|
||||
// original split deliminator (:), make up the header's final value
|
||||
let value = split_val.collect::<Vec<&str>>().join(":");
|
||||
config.headers.insert(name.to_string(), value.to_string());
|
||||
|
||||
if value.starts_with(' ') && !value.starts_with(" ") {
|
||||
// first character is a space and the second character isn't
|
||||
// we can trim the leading space
|
||||
let trimmed = value.trim_start();
|
||||
config.headers.insert(name.to_string(), trimmed.to_string());
|
||||
} else {
|
||||
config.headers.insert(name.to_string(), value.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -943,15 +956,27 @@ impl Configuration {
|
||||
config.headers.insert(
|
||||
// we know the header name is always "cookie"
|
||||
"Cookie".to_string(),
|
||||
// on splitting, there should be only two elements,
|
||||
// a key and a value
|
||||
cookies
|
||||
.map(|cookie| cookie.split('=').collect::<Vec<&str>>()[..].to_owned())
|
||||
.filter(|parts| parts.len() == 2)
|
||||
.map(|parts| format!("{}={}", parts[0].trim(), parts[1].trim()))
|
||||
// trim the spaces, join with an equals sign
|
||||
.flat_map(|cookie| {
|
||||
cookie.split(';').filter_map(|part| {
|
||||
// trim the spaces
|
||||
let trimmed = part.trim();
|
||||
if trimmed.is_empty() {
|
||||
None
|
||||
} else {
|
||||
// join with an equals sign
|
||||
let parts = trimmed.split('=').collect::<Vec<&str>>();
|
||||
Some(format!(
|
||||
"{}={}",
|
||||
parts[0].trim(),
|
||||
parts[1..].join("").trim()
|
||||
))
|
||||
}
|
||||
})
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
.join("; "), // join all the cookies with semicolons for the final header
|
||||
// join all the cookies with semicolons for the final header
|
||||
.join("; "),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use reqwest::StatusCode;
|
||||
use tokio::sync::oneshot::Sender;
|
||||
@@ -85,4 +86,10 @@ pub enum Command {
|
||||
/// Give a handler access to an Arc<Handles> instance after the handler has
|
||||
/// already been initialized
|
||||
AddHandles(Arc<Handles>),
|
||||
|
||||
/// inform the Stats object about which targets are being scanned
|
||||
UpdateTargets(Vec<String>),
|
||||
|
||||
/// query the Stats handler about the position of the overall progress bar
|
||||
QueryOverallBarEta(Sender<Duration>),
|
||||
}
|
||||
|
||||
@@ -98,6 +98,8 @@ impl FileOutHandler {
|
||||
|
||||
log::info!("Writing scan results to {}", self.config.output);
|
||||
|
||||
write_to(&*self.config, &mut file, self.config.json)?;
|
||||
|
||||
while let Some(command) = self.receiver.recv().await {
|
||||
match command {
|
||||
Command::Report(response) => {
|
||||
|
||||
@@ -125,6 +125,12 @@ impl StatsHandler {
|
||||
Command::Sync(sender) => {
|
||||
sender.send(true).unwrap_or_default();
|
||||
}
|
||||
Command::QueryOverallBarEta(sender) => {
|
||||
sender.send(self.bar.eta()).unwrap_or_default();
|
||||
}
|
||||
Command::UpdateTargets(targets) => {
|
||||
self.stats.update_targets(targets);
|
||||
}
|
||||
Command::Exit => break,
|
||||
_ => {} // no more commands needed
|
||||
}
|
||||
|
||||
@@ -30,10 +30,13 @@ impl FeroxFilter for RegexFilter {
|
||||
log::trace!("enter: should_filter_response({:?} {})", self, response);
|
||||
|
||||
let result = self.compiled.is_match(response.text());
|
||||
let other = response.headers().iter().any(|(k, v)| {
|
||||
self.compiled.is_match(k.as_str()) || self.compiled.is_match(v.to_str().unwrap_or(""))
|
||||
});
|
||||
|
||||
log::trace!("exit: should_filter_response -> {}", result);
|
||||
log::trace!("exit: should_filter_response -> {}", result || other);
|
||||
|
||||
result
|
||||
result || other
|
||||
}
|
||||
|
||||
/// Compare one SizeFilter to another
|
||||
|
||||
@@ -271,7 +271,7 @@ fn remove_function_works_as_expected() {
|
||||
|
||||
assert_eq!(data.filters.read().unwrap().len(), 5);
|
||||
|
||||
let expected = vec
|
||||
/// to the global [PROGRESS_BAR](../config/struct.PROGRESS_BAR.html)
|
||||
pub fn add_bar(prefix: &str, length: u64, bar_type: BarType) -> ProgressBar {
|
||||
let mut style = ProgressStyle::default_bar()
|
||||
.progress_chars("#>-")
|
||||
.with_key(
|
||||
"smoothed_per_sec",
|
||||
|state: &indicatif::ProgressState, w: &mut dyn std::fmt::Write| match (
|
||||
state.pos(),
|
||||
state.elapsed().as_millis(),
|
||||
) {
|
||||
// https://github.com/console-rs/indicatif/issues/394#issuecomment-1309971049
|
||||
//
|
||||
// indicatif released a change to how they reported eta/per_sec
|
||||
// and the results looked really weird based on how we use the progress
|
||||
// bars. this fixes that
|
||||
(pos, elapsed_ms) if elapsed_ms > 0 => {
|
||||
write!(w, "{:.0}/s", pos as f64 * 1000_f64 / elapsed_ms as f64).unwrap()
|
||||
}
|
||||
_ => write!(w, "-").unwrap(),
|
||||
},
|
||||
)
|
||||
.with_key(
|
||||
"smoothed_eta",
|
||||
|state: &indicatif::ProgressState, w: &mut dyn std::fmt::Write| match (
|
||||
state.pos(),
|
||||
state.len(),
|
||||
) {
|
||||
(pos, Some(len)) => write!(
|
||||
w,
|
||||
"{:#}",
|
||||
HumanDuration(Duration::from_millis(
|
||||
(state.elapsed().as_millis()
|
||||
* ((len as u128).checked_sub(pos as u128).unwrap_or(1))
|
||||
.checked_div(pos as u128)
|
||||
.unwrap_or(1)) as u64
|
||||
))
|
||||
)
|
||||
.unwrap(),
|
||||
_ => write!(w, "-").unwrap(),
|
||||
},
|
||||
);
|
||||
let mut style = ProgressStyle::default_bar().progress_chars("#>-").with_key(
|
||||
"smoothed_per_sec",
|
||||
|state: &indicatif::ProgressState, w: &mut dyn std::fmt::Write| match (
|
||||
state.pos(),
|
||||
state.elapsed().as_millis(),
|
||||
) {
|
||||
// https://github.com/console-rs/indicatif/issues/394#issuecomment-1309971049
|
||||
//
|
||||
// indicatif released a change to how they reported eta/per_sec
|
||||
// and the results looked really weird based on how we use the progress
|
||||
// bars. this fixes that
|
||||
(pos, elapsed_ms) if elapsed_ms > 0 => {
|
||||
write!(w, "{:.0}/s", pos as f64 * 1000_f64 / elapsed_ms as f64).unwrap()
|
||||
}
|
||||
_ => write!(w, "-").unwrap(),
|
||||
},
|
||||
);
|
||||
|
||||
style = match bar_type {
|
||||
BarType::Hidden => style.template("").unwrap(),
|
||||
@@ -85,7 +61,7 @@ pub fn add_bar(prefix: &str, length: u64, bar_type: BarType) -> ProgressBar {
|
||||
))
|
||||
.unwrap(),
|
||||
BarType::Total => style
|
||||
.template("[{bar:.yellow/blue}] - {elapsed:<4} {pos:>7}/{len:7} {smoothed_eta:7} {msg}")
|
||||
.template("[{bar:.yellow/blue}] - {elapsed:<4} {pos:>7}/{len:7} {eta:7} {msg}")
|
||||
.unwrap(),
|
||||
BarType::Quiet => style.template("Scanning: {prefix}").unwrap(),
|
||||
};
|
||||
|
||||
@@ -21,7 +21,7 @@ use crate::{
|
||||
event_handlers::{Command, Handles},
|
||||
traits::FeroxSerialize,
|
||||
url::FeroxUrl,
|
||||
utils::{self, fmt_err, parse_url_with_raw_path, status_colorizer},
|
||||
utils::{self, fmt_err, parse_url_with_raw_path, status_colorizer, timestamp},
|
||||
CommandSender,
|
||||
};
|
||||
|
||||
@@ -63,6 +63,9 @@ pub struct FeroxResponse {
|
||||
|
||||
/// Url's file extension, if one exists
|
||||
pub(crate) extension: Option<String>,
|
||||
|
||||
/// Timestamp of when this response was received
|
||||
timestamp: f64,
|
||||
}
|
||||
|
||||
/// implement Default trait for FeroxResponse
|
||||
@@ -82,6 +85,7 @@ impl Default for FeroxResponse {
|
||||
wildcard: false,
|
||||
output_level: Default::default(),
|
||||
extension: None,
|
||||
timestamp: timestamp(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -138,6 +142,11 @@ impl FeroxResponse {
|
||||
self.content_length
|
||||
}
|
||||
|
||||
/// Get the timestamp of this response
|
||||
pub fn timestamp(&self) -> f64 {
|
||||
self.timestamp
|
||||
}
|
||||
|
||||
/// Set `FeroxResponse`'s `url` attribute, has no affect if an error occurs
|
||||
pub fn set_url(&mut self, url: &str) {
|
||||
match parse_url_with_raw_path(url) {
|
||||
@@ -216,6 +225,7 @@ impl FeroxResponse {
|
||||
let status = response.status();
|
||||
let headers = response.headers().clone();
|
||||
let content_length = response.content_length().unwrap_or(0);
|
||||
let timestamp = timestamp();
|
||||
|
||||
// .text() consumes the response, must be called last
|
||||
let text = response
|
||||
@@ -248,6 +258,7 @@ impl FeroxResponse {
|
||||
output_level,
|
||||
wildcard: false,
|
||||
extension: None,
|
||||
timestamp,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -574,6 +585,7 @@ impl Serialize for FeroxResponse {
|
||||
"extension",
|
||||
self.extension.as_ref().unwrap_or(&String::new()),
|
||||
)?;
|
||||
state.serialize_field("timestamp", &self.timestamp)?;
|
||||
|
||||
state.end()
|
||||
}
|
||||
@@ -599,6 +611,7 @@ impl<'de> Deserialize<'de> for FeroxResponse {
|
||||
line_count: 0,
|
||||
word_count: 0,
|
||||
extension: None,
|
||||
timestamp: timestamp(),
|
||||
};
|
||||
|
||||
let map: HashMap<String, Value> = HashMap::deserialize(deserializer)?;
|
||||
@@ -672,6 +685,11 @@ impl<'de> Deserialize<'de> for FeroxResponse {
|
||||
response.extension = Some(result.to_string());
|
||||
}
|
||||
}
|
||||
"timestamp" => {
|
||||
if let Some(result) = value.as_f64() {
|
||||
response.timestamp = result;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::filters::filter_lookup;
|
||||
use crate::progress::PROGRESS_BAR;
|
||||
use crate::traits::FeroxFilter;
|
||||
use console::{measure_text_width, pad_str, style, Alignment, Term};
|
||||
use indicatif::ProgressDrawTarget;
|
||||
use indicatif::{HumanDuration, ProgressDrawTarget};
|
||||
use regex::Regex;
|
||||
|
||||
/// Data container for a command entered by the user interactively
|
||||
@@ -43,6 +45,9 @@ pub(super) struct Menu {
|
||||
/// footer: instructions surrounded by separators
|
||||
footer: String,
|
||||
|
||||
/// length of longest displayed line (suitable for ascii/unicode)
|
||||
longest: usize,
|
||||
|
||||
/// unicode line border, matched to longest displayed line
|
||||
border: String,
|
||||
|
||||
@@ -110,7 +115,7 @@ impl Menu {
|
||||
commands.push_str(&valid_filters);
|
||||
commands.push_str(&rm_filter_cmd);
|
||||
|
||||
let longest = measure_text_width(&canx_cmd).max(measure_text_width(&name));
|
||||
let longest = measure_text_width(&canx_cmd).max(measure_text_width(&name)) + 1;
|
||||
|
||||
let border = separator.repeat(longest);
|
||||
|
||||
@@ -123,6 +128,7 @@ impl Menu {
|
||||
header,
|
||||
footer,
|
||||
border,
|
||||
longest,
|
||||
term: Term::stderr(),
|
||||
}
|
||||
}
|
||||
@@ -142,6 +148,13 @@ impl Menu {
|
||||
self.println(&self.footer);
|
||||
}
|
||||
|
||||
/// print menu footer
|
||||
pub(super) fn print_eta(&self, eta: Duration) {
|
||||
let inner = format!("⏳ {} remaining ⏳", HumanDuration(eta));
|
||||
let padded_eta = pad_str(&inner, self.longest, Alignment::Center, None);
|
||||
self.println(&format!("{padded_eta}\n{}", self.border));
|
||||
}
|
||||
|
||||
/// set PROGRESS_BAR bar target to hidden
|
||||
pub(super) fn hide_progress_bars(&self) {
|
||||
PROGRESS_BAR.set_draw_target(ProgressDrawTarget::hidden());
|
||||
|
||||
@@ -33,6 +33,7 @@ use std::{
|
||||
},
|
||||
thread::sleep,
|
||||
};
|
||||
use tokio::sync::oneshot;
|
||||
use tokio::time::{self, Duration};
|
||||
|
||||
/// Single atomic number that gets incremented once, used to track first thread to interact with
|
||||
@@ -430,6 +431,13 @@ impl FeroxScans {
|
||||
self.menu.hide_progress_bars();
|
||||
self.menu.clear_screen();
|
||||
self.menu.print_header();
|
||||
let (tx, rx) = oneshot::channel::<Duration>();
|
||||
if handles.stats.send(Command::QueryOverallBarEta(tx)).is_ok() {
|
||||
if let Ok(y) = rx.await {
|
||||
self.menu.print_eta(y);
|
||||
}
|
||||
}
|
||||
|
||||
self.display_scans().await;
|
||||
self.display_filters(handles.clone());
|
||||
self.menu.print_footer();
|
||||
|
||||
@@ -314,7 +314,7 @@ fn ferox_scans_serialize() {
|
||||
#[test]
|
||||
/// given a FeroxResponses, test that it serializes into the proper JSON entry
|
||||
fn ferox_responses_serialize() {
|
||||
let json_response = r#"{"type":"response","url":"https://nerdcore.com/css","original_url":"https://nerdcore.com","path":"/css","wildcard":true,"status":301,"method":"GET","content_length":173,"line_count":10,"word_count":16,"headers":{"server":"nginx/1.16.1"},"extension":""}"#;
|
||||
let json_response = r#"{"type":"response","url":"https://nerdcore.com/css","original_url":"https://nerdcore.com","path":"/css","wildcard":true,"status":301,"method":"GET","content_length":173,"line_count":10,"word_count":16,"headers":{"server":"nginx/1.16.1"},"extension":"","timestamp":1711796681.3455093}"#;
|
||||
let response: FeroxResponse = serde_json::from_str(json_response).unwrap();
|
||||
|
||||
let responses = FeroxResponses::default();
|
||||
@@ -332,7 +332,7 @@ fn ferox_responses_serialize() {
|
||||
/// given a FeroxResponse, test that it serializes into the proper JSON entry
|
||||
fn ferox_response_serialize_and_deserialize() {
|
||||
// deserialize
|
||||
let json_response = r#"{"type":"response","url":"https://nerdcore.com/css","original_url":"https://nerdcore.com","path":"/css","wildcard":true,"status":301,"method":"GET","content_length":173,"line_count":10,"word_count":16,"headers":{"server":"nginx/1.16.1"},"extension":""}"#;
|
||||
let json_response = r#"{"type":"response","url":"https://nerdcore.com/css","original_url":"https://nerdcore.com","path":"/css","wildcard":true,"status":301,"method":"GET","content_length":173,"line_count":10,"word_count":16,"headers":{"server":"nginx/1.16.1"},"extension":"","timestamp":1711796681.3455093}"#;
|
||||
let response: FeroxResponse = serde_json::from_str(json_response).unwrap();
|
||||
|
||||
assert_eq!(response.url().as_str(), "https://nerdcore.com/css");
|
||||
@@ -343,6 +343,7 @@ fn ferox_response_serialize_and_deserialize() {
|
||||
assert_eq!(response.line_count(), 10);
|
||||
assert_eq!(response.word_count(), 16);
|
||||
assert_eq!(response.headers().get("server").unwrap(), "nginx/1.16.1");
|
||||
assert_eq!(response.timestamp(), 1711796681.3455093);
|
||||
|
||||
// serialize, however, this can fail when headers are out of order
|
||||
let new_json = serde_json::to_string(&response).unwrap();
|
||||
|
||||
@@ -132,6 +132,9 @@ pub struct Stats {
|
||||
|
||||
/// tracker for whether to use json during serialization or not
|
||||
json: bool,
|
||||
|
||||
/// tracker for the initial targets that were passed in to the scan
|
||||
targets: Mutex<Vec<String>>,
|
||||
}
|
||||
|
||||
/// FeroxSerialize implementation for Stats
|
||||
@@ -196,6 +199,7 @@ impl Serialize for Stats {
|
||||
state.serialize_field("request_errors", &atomic_load!(self.request_errors))?;
|
||||
state.serialize_field("directory_scan_times", &self.directory_scan_times)?;
|
||||
state.serialize_field("total_runtime", &self.total_runtime)?;
|
||||
state.serialize_field("targets", &self.targets)?;
|
||||
|
||||
state.end()
|
||||
}
|
||||
@@ -446,6 +450,17 @@ impl<'a> Deserialize<'a> for Stats {
|
||||
}
|
||||
}
|
||||
}
|
||||
"targets" => {
|
||||
if let Some(arr) = value.as_array() {
|
||||
for val in arr {
|
||||
if let Some(parsed) = val.as_str() {
|
||||
if let Ok(mut guard) = stats.targets.lock() {
|
||||
guard.push(parsed.to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@@ -514,6 +529,13 @@ impl Stats {
|
||||
}
|
||||
}
|
||||
|
||||
/// update targets with the given vector of strings
|
||||
pub fn update_targets(&self, targets: Vec<String>) {
|
||||
if let Ok(mut locked_targets) = self.targets.lock() {
|
||||
*locked_targets = targets;
|
||||
}
|
||||
}
|
||||
|
||||
/// save an instance of `Stats` to disk after updating the total runtime for the scan
|
||||
pub fn save(&self, seconds: f64, location: &str) -> Result<()> {
|
||||
let mut file = open_file(location)?;
|
||||
|
||||
@@ -270,7 +270,7 @@ mod tests {
|
||||
let pdf = Url::parse("http://localhost/turbo.pdf").unwrap();
|
||||
let tar = Url::parse("http://localhost/turbo.tar.gz").unwrap();
|
||||
|
||||
let expected = vec![
|
||||
let expected = [
|
||||
vec![base.clone(), js.clone()],
|
||||
vec![base.clone(), js.clone(), php.clone()],
|
||||
vec![base.clone(), js.clone(), php.clone(), pdf.clone()],
|
||||
|
||||
14
src/utils.rs
14
src/utils.rs
@@ -68,6 +68,20 @@ pub fn fmt_err(msg: &str) -> String {
|
||||
format!("{}: {}", status_colorizer("ERROR"), msg)
|
||||
}
|
||||
|
||||
/// simple wrapper to get the current system time as
|
||||
/// time elapsed from unix epoch
|
||||
pub fn timestamp() -> f64 {
|
||||
let since_the_epoch = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap_or_else(|_| Duration::from_secs(0));
|
||||
|
||||
let secs = since_the_epoch.as_secs() as f64;
|
||||
let nanos = since_the_epoch.subsec_nanos() as f64;
|
||||
|
||||
// Convert nanoseconds to fractional seconds and add to secs
|
||||
secs + (nanos / 1_000_000_000.0)
|
||||
}
|
||||
|
||||
/// given a FeroxResponse, send a TryRecursion command
|
||||
///
|
||||
/// moved to utils to allow for calls from extractor and scanner
|
||||
|
||||
@@ -290,3 +290,52 @@ fn collect_backups_should_be_filtered() {
|
||||
assert_eq!(mock_two.hits(), 1);
|
||||
teardown_tmp_directory(tmp_dir);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// create a FeroxResponse that should elicit a true from
|
||||
/// RegexFilter::should_filter_response
|
||||
fn filters_regex_should_filter_response_based_on_headers() {
|
||||
let srv = MockServer::start();
|
||||
let (tmp_dir, file) = setup_tmp_directory(
|
||||
&["not-matching".to_string(), "matching".to_string()],
|
||||
"wordlist",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mock = srv.mock(|when, then| {
|
||||
when.method(GET).path("/not-matching");
|
||||
then.status(200)
|
||||
.header("content-type", "text/html")
|
||||
.body("this is a test");
|
||||
});
|
||||
|
||||
let mock_two = srv.mock(|when, then| {
|
||||
when.method(GET).path("/matching");
|
||||
then.status(200)
|
||||
.header("content-type", "application/json")
|
||||
.body("this is also a test");
|
||||
});
|
||||
|
||||
let cmd = Command::cargo_bin("feroxbuster")
|
||||
.unwrap()
|
||||
.arg("--url")
|
||||
.arg(srv.url("/"))
|
||||
.arg("--wordlist")
|
||||
.arg(file.as_os_str())
|
||||
.arg("--filter-regex")
|
||||
.arg("content-type:application/json")
|
||||
.unwrap();
|
||||
|
||||
cmd.assert().success().stdout(
|
||||
predicate::str::contains("/not-matching")
|
||||
.and(predicate::str::contains("200"))
|
||||
.and(predicate::str::contains("/matching"))
|
||||
.not()
|
||||
.and(predicate::str::contains("200"))
|
||||
.not(),
|
||||
);
|
||||
|
||||
assert_eq!(mock.hits(), 1);
|
||||
assert_eq!(mock_two.hits(), 1);
|
||||
teardown_tmp_directory(tmp_dir);
|
||||
}
|
||||
|
||||
@@ -104,9 +104,9 @@ fn test_single_target_cannot_connect_due_to_ssl_errors() -> Result<(), Box<dyn s
|
||||
.arg(file.as_os_str())
|
||||
.assert()
|
||||
.success()
|
||||
.stdout(
|
||||
predicate::str::contains("Could not connect to https://expired.badssl.com due to SSL errors (run with -k to ignore), skipping...", )
|
||||
);
|
||||
.stdout(predicate::str::contains(
|
||||
"Could not connect to https://expired.badssl.com",
|
||||
));
|
||||
|
||||
teardown_tmp_directory(tmp_dir);
|
||||
Ok(())
|
||||
|
||||
@@ -22,6 +22,7 @@ use utils::{setup_tmp_directory, teardown_tmp_directory};
|
||||
// these words will be used along with pattern matching to trigger different policies
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
/// --auto-bail should cancel a scan with spurious errors
|
||||
fn auto_bail_cancels_scan_with_timeouts() {
|
||||
let srv = MockServer::start();
|
||||
|
||||
Reference in New Issue
Block a user