mirror of
https://github.com/epi052/feroxbuster.git
synced 2026-05-23 21:31:12 -03:00
Compare commits
112 Commits
v2.10.0
...
dependabot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1182d6b70a | ||
|
|
af3dcdf6a2 | ||
|
|
d4fd06418b | ||
|
|
3b0e530fb4 | ||
|
|
64113b8da4 | ||
|
|
e827fd02ad | ||
|
|
988fa744b5 | ||
|
|
56d0ebaa59 | ||
|
|
0cee8d4a7d | ||
|
|
34a9eb236b | ||
|
|
5acaf47db4 | ||
|
|
c0243475e4 | ||
|
|
08b3534c33 | ||
|
|
30877cadb8 | ||
|
|
05d550c7f8 | ||
|
|
a0a836695f | ||
|
|
4f959f926d | ||
|
|
4b613b716c | ||
|
|
24617a63ac | ||
|
|
5bbbcc87b0 | ||
|
|
fd58223d24 | ||
|
|
1206ca835e | ||
|
|
8daada6690 | ||
|
|
8599c87174 | ||
|
|
4f83b30424 | ||
|
|
f96466d5f0 | ||
|
|
36081fd6eb | ||
|
|
a8d8b655a5 | ||
|
|
ae0bcfab14 | ||
|
|
a8dac70ba1 | ||
|
|
53162bae85 | ||
|
|
98b2268aa9 | ||
|
|
762bfc4e78 | ||
|
|
b44c52f0ea | ||
|
|
27061eb1b5 | ||
|
|
49e54f5722 | ||
|
|
bb01fadd5a | ||
|
|
70ae679b50 | ||
|
|
01da38fa6d | ||
|
|
22586f3835 | ||
|
|
0510cb91aa | ||
|
|
4663ec4cea | ||
|
|
e8a98a54d8 | ||
|
|
fa42c72ac5 | ||
|
|
4ce77b5012 | ||
|
|
72c09854fc | ||
|
|
17a3d8af9f | ||
|
|
b67f1399b3 | ||
|
|
57db4adb69 | ||
|
|
87b6589f51 | ||
|
|
f36897431e | ||
|
|
3c89721f54 | ||
|
|
9193614f3c | ||
|
|
8eb41f40a0 | ||
|
|
f3d6d185cd | ||
|
|
df7b6ab6f9 | ||
|
|
22bed3c9e7 | ||
|
|
fe0f7d6f3c | ||
|
|
3b0d787ca7 | ||
|
|
eba35b205e | ||
|
|
ecdd1bce81 | ||
|
|
0771407939 | ||
|
|
2ea6b97c86 | ||
|
|
9ff0253deb | ||
|
|
423889b142 | ||
|
|
595665cc04 | ||
|
|
a583e2ff38 | ||
|
|
539851e3e8 | ||
|
|
c1e7c5ff59 | ||
|
|
38a1ed3f63 | ||
|
|
0d55fe2502 | ||
|
|
a714825d09 | ||
|
|
d805e46474 | ||
|
|
fe71f288e3 | ||
|
|
a38a0444fe | ||
|
|
2938094c73 | ||
|
|
55c67358d6 | ||
|
|
c3c6fc6753 | ||
|
|
a28ff857ca | ||
|
|
6c0fe90909 | ||
|
|
bc486ac8d3 | ||
|
|
fa9d42554f | ||
|
|
b78dbe6cc4 | ||
|
|
29f616f51a | ||
|
|
c1ba5cf942 | ||
|
|
e3ec3aee3a | ||
|
|
52db396aa9 | ||
|
|
e1066cd5c7 | ||
|
|
d90ee38aad | ||
|
|
a3501ac494 | ||
|
|
23827a1d45 | ||
|
|
a2b04b2b5e | ||
|
|
362633bc63 | ||
|
|
08c5b2bf67 | ||
|
|
ccef4fd713 | ||
|
|
4afe0cf95c | ||
|
|
564686bc5a | ||
|
|
83f90529e9 | ||
|
|
ad49320968 | ||
|
|
70946ad916 | ||
|
|
fd5c5af5fa | ||
|
|
ff32aba1db | ||
|
|
cbf028a8ac | ||
|
|
8bf80f4eda | ||
|
|
7c2d09cc22 | ||
|
|
0fb682c121 | ||
|
|
bcfd8b6eef | ||
|
|
1c9235a56b | ||
|
|
4d787f08d0 | ||
|
|
0c7520f5ee | ||
|
|
83b55aaf10 | ||
|
|
aea64324f7 |
@@ -144,7 +144,8 @@
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/48113936?v=4",
|
||||
"profile": "https://tib3rius.com",
|
||||
"contributions": [
|
||||
"bug"
|
||||
"bug",
|
||||
"ideas"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -198,7 +199,8 @@
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/24260009?v=4",
|
||||
"profile": "https://github.com/N0ur5",
|
||||
"contributions": [
|
||||
"ideas"
|
||||
"ideas",
|
||||
"bug"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -299,7 +301,8 @@
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/16690056?v=4",
|
||||
"profile": "https://github.com/n0kovo",
|
||||
"contributions": [
|
||||
"ideas"
|
||||
"ideas",
|
||||
"bug"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -608,6 +611,273 @@
|
||||
"contributions": [
|
||||
"ideas"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "sectroyer",
|
||||
"name": "sectroyer",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/6706818?v=4",
|
||||
"profile": "https://github.com/sectroyer",
|
||||
"contributions": [
|
||||
"bug",
|
||||
"ideas"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "ktecv2000",
|
||||
"name": "ktecv2000",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/19836003?v=4",
|
||||
"profile": "https://medium.com/@b3rm1nG",
|
||||
"contributions": [
|
||||
"bug"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "andreademurtas",
|
||||
"name": "Andrea De Murtas",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/56048157?v=4",
|
||||
"profile": "http://untrue.me",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "sawmj",
|
||||
"name": "sawmj",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/30024085?v=4",
|
||||
"profile": "https://github.com/sawmj",
|
||||
"contributions": [
|
||||
"bug"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "devx00",
|
||||
"name": "Zach Hanson",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/6897405?v=4",
|
||||
"profile": "https://github.com/devx00",
|
||||
"contributions": [
|
||||
"bug"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "ocervell",
|
||||
"name": "Olivier Cervello",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/9629314?v=4",
|
||||
"profile": "https://github.com/ocervell",
|
||||
"contributions": [
|
||||
"ideas"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "RavySena",
|
||||
"name": "RavySena",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/67729597?v=4",
|
||||
"profile": "https://github.com/RavySena",
|
||||
"contributions": [
|
||||
"ideas"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "stuhlmann",
|
||||
"name": "Florian Stuhlmann",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/11061864?v=4",
|
||||
"profile": "https://github.com/stuhlmann",
|
||||
"contributions": [
|
||||
"bug"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Mister7F",
|
||||
"name": "Mister7F",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/35213773?v=4",
|
||||
"profile": "https://github.com/Mister7F",
|
||||
"contributions": [
|
||||
"ideas"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "manugramm",
|
||||
"name": "manugramm",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/145961515?v=4",
|
||||
"profile": "https://github.com/manugramm",
|
||||
"contributions": [
|
||||
"bug"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "ArthurMuraro",
|
||||
"name": "ArthurMuraro",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/73059809?v=4",
|
||||
"profile": "https://github.com/ArthurMuraro",
|
||||
"contributions": [
|
||||
"bug"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "amiremami",
|
||||
"name": "Shadow",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/15929497?v=4",
|
||||
"profile": "https://github.com/amiremami",
|
||||
"contributions": [
|
||||
"bug"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "dirhamgithub",
|
||||
"name": "dirhamgithub",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/115349974?v=4",
|
||||
"profile": "https://github.com/dirhamgithub",
|
||||
"contributions": [
|
||||
"bug"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "FieldOfRice",
|
||||
"name": "FieldOfRice",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/85353?v=4",
|
||||
"profile": "https://github.com/FieldOfRice",
|
||||
"contributions": [
|
||||
"infra"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "NotoriousRebel",
|
||||
"name": "Matt",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/36310667?v=4",
|
||||
"profile": "https://github.com/NotoriousRebel",
|
||||
"contributions": [
|
||||
"ideas"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "tritoke",
|
||||
"name": "Sam Leonard",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/34941249?v=4",
|
||||
"profile": "https://github.com/tritoke",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "rew1nter",
|
||||
"name": "Rewinter",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/64508791?v=4",
|
||||
"profile": "https://github.com/rew1nter",
|
||||
"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",
|
||||
"ideas"
|
||||
]
|
||||
},
|
||||
{
|
||||
"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"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "libklein",
|
||||
"name": "Patrick Klein",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/42714034?v=4",
|
||||
"profile": "https://github.com/libklein",
|
||||
"contributions": [
|
||||
"ideas"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Raymond-JV",
|
||||
"name": "Raymond",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/23642921?v=4",
|
||||
"profile": "https://github.com/Raymond-JV",
|
||||
"contributions": [
|
||||
"ideas"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "zer0x64",
|
||||
"name": "zer0x64",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/17575242?v=4",
|
||||
"profile": "https://github.com/zer0x64",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
}
|
||||
],
|
||||
"contributorsPerLine": 7,
|
||||
@@ -616,5 +886,6 @@
|
||||
"repoType": "github",
|
||||
"repoHost": "https://github.com",
|
||||
"skipCi": true,
|
||||
"commitConvention": "angular"
|
||||
"commitConvention": "angular",
|
||||
"commitType": "docs"
|
||||
}
|
||||
|
||||
1
.github/pull_request_template.md
vendored
1
.github/pull_request_template.md
vendored
@@ -20,7 +20,6 @@ Long form explanations of most of the items below can be found in the [CONTRIBUT
|
||||
- [ ] update [example config file section](https://epi052.github.io/feroxbuster-docs/docs/configuration/ferox-config-toml/)
|
||||
- [ ] update [help output section](https://epi052.github.io/feroxbuster-docs/docs/configuration/command-line/)
|
||||
- [ ] add an [example](https://epi052.github.io/feroxbuster-docs/docs/examples/)
|
||||
- [ ] update [comparison table](https://epi052.github.io/feroxbuster-docs/docs/compare/)
|
||||
|
||||
## Additional Tests
|
||||
- [ ] New code is unit tested
|
||||
|
||||
173
.github/workflows/build.yml
vendored
173
.github/workflows/build.yml
vendored
@@ -37,58 +37,81 @@ jobs:
|
||||
path: target/aarch64-unknown-linux-gnu/release/feroxbuster
|
||||
pkg_config_path: /usr/lib/x86_64-linux-gnu/pkgconfig
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install System Dependencies
|
||||
run: |
|
||||
env
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y --no-install-recommends libssl-dev pkg-config gcc-arm-linux-gnueabihf gcc-aarch64-linux-gnu
|
||||
- uses: actions-rs/toolchain@v1
|
||||
- uses: actions/checkout@v4
|
||||
- name: Cache cargo & target directories
|
||||
uses: Swatinem/rust-cache@v2
|
||||
- name: Build binary
|
||||
uses: houseabsolute/actions-rust-cross@v0
|
||||
with:
|
||||
toolchain: stable
|
||||
target: ${{ matrix.target }}
|
||||
override: true
|
||||
- uses: actions-rs/cargo@v1
|
||||
env:
|
||||
PKG_CONFIG_PATH: ${{ matrix.pkg_config_path }}
|
||||
OPENSSL_DIR: /usr/lib/ssl
|
||||
with:
|
||||
use-cross: true
|
||||
command: build
|
||||
args: --release --target=${{ matrix.target }}
|
||||
- name: Strip symbols from binary
|
||||
run: |
|
||||
strip -s ${{ matrix.path }} || arm-linux-gnueabihf-strip -s ${{ matrix.path }} || aarch64-linux-gnu-strip -s ${{ matrix.path }}
|
||||
target: ${{ matrix.target }}
|
||||
args: "--locked --release"
|
||||
strip: true
|
||||
toolchain: stable
|
||||
- name: Build tar.gz for homebrew installs
|
||||
if: matrix.type == 'ubuntu-x64'
|
||||
run: |
|
||||
tar czf ${{ matrix.name }}.tar.gz -C target/x86_64-unknown-linux-musl/release feroxbuster
|
||||
- uses: actions/upload-artifact@v2
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.name }}
|
||||
path: ${{ matrix.path }}
|
||||
- uses: actions/upload-artifact@v2
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: matrix.type == 'ubuntu-x64'
|
||||
with:
|
||||
name: ${{ matrix.name }}.tar.gz
|
||||
path: ${{ matrix.name }}.tar.gz
|
||||
|
||||
# build-deb:
|
||||
# needs: [build-nix]
|
||||
# runs-on: ubuntu-latest
|
||||
# steps:
|
||||
# - uses: actions/checkout@master
|
||||
# - name: Install cargo-deb
|
||||
# run: cargo install -f cargo-deb
|
||||
# - name: Install musl toolchain
|
||||
# run: rustup target add x86_64-unknown-linux-musl
|
||||
# - name: Deb Build
|
||||
# run: cargo deb --target=x86_64-unknown-linux-musl
|
||||
# - name: Upload Deb Artifact
|
||||
# uses: actions/upload-artifact@v2
|
||||
# with:
|
||||
# name: feroxbuster_amd64.deb
|
||||
# path: ./target/x86_64-unknown-linux-musl/debian/*
|
||||
build-debug:
|
||||
env:
|
||||
IN_PIPELINE: true
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install System Dependencies
|
||||
run: |
|
||||
env
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y --no-install-recommends libssl-dev pkg-config musl-tools
|
||||
- name: Set up Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: stable
|
||||
target: x86_64-unknown-linux-musl
|
||||
|
||||
- name: Build the project
|
||||
env:
|
||||
PKG_CONFIG_PATH: /usr/lib/x86_64-linux-gnu/pkgconfig
|
||||
OPENSSL_DIR: /usr/lib/ssl
|
||||
run: cargo build --target=x86_64-unknown-linux-musl
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: x86_64-linux-debug-feroxbuster
|
||||
path: target/x86_64-unknown-linux-musl/debug/feroxbuster
|
||||
|
||||
build-deb:
|
||||
needs: [build-nix]
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
IN_PIPELINE: true
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install cargo-deb
|
||||
run: cargo install -f cargo-deb
|
||||
- uses: awalsh128/cache-apt-pkgs-action@v1
|
||||
with:
|
||||
packages: musl-tools # provides musl-gcc
|
||||
version: 1.0
|
||||
- name: Install musl toolchain
|
||||
run: rustup target add x86_64-unknown-linux-musl
|
||||
- name: Deb Build
|
||||
run: cargo deb --target=x86_64-unknown-linux-musl
|
||||
- name: Upload Deb Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: feroxbuster_amd64.deb
|
||||
path: ./target/x86_64-unknown-linux-musl/debian/*
|
||||
|
||||
build-macos:
|
||||
env:
|
||||
@@ -96,31 +119,57 @@ jobs:
|
||||
runs-on: macos-latest
|
||||
if: github.ref == 'refs/heads/main'
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
- uses: actions/checkout@v4
|
||||
- name: Cache cargo & target directories
|
||||
uses: Swatinem/rust-cache@v2
|
||||
- name: Build binary
|
||||
uses: houseabsolute/actions-rust-cross@v0
|
||||
with:
|
||||
toolchain: stable
|
||||
target: x86_64-apple-darwin
|
||||
override: true
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
use-cross: true
|
||||
command: build
|
||||
args: --release --target=x86_64-apple-darwin
|
||||
- name: Strip symbols from binary
|
||||
run: |
|
||||
strip -u -r target/x86_64-apple-darwin/release/feroxbuster
|
||||
target: x86_64-apple-darwin
|
||||
args: "--locked --release"
|
||||
strip: true
|
||||
toolchain: stable
|
||||
- name: Build tar.gz for homebrew installs
|
||||
run: |
|
||||
tar czf x86_64-macos-feroxbuster.tar.gz -C target/x86_64-apple-darwin/release feroxbuster
|
||||
- uses: actions/upload-artifact@v2
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: x86_64-macos-feroxbuster
|
||||
path: target/x86_64-apple-darwin/release/feroxbuster
|
||||
- uses: actions/upload-artifact@v2
|
||||
- uses: actions/upload-artifact@v4
|
||||
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@v4
|
||||
- name: Cache cargo & target directories
|
||||
uses: Swatinem/rust-cache@v2
|
||||
- name: Build binary
|
||||
uses: houseabsolute/actions-rust-cross@v0
|
||||
with:
|
||||
command: build
|
||||
target: aarch64-apple-darwin
|
||||
args: "--locked --release"
|
||||
strip: true
|
||||
toolchain: stable
|
||||
- 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@v4
|
||||
with:
|
||||
name: aarch64-macos-feroxbuster
|
||||
path: target/aarch64-apple-darwin/release/feroxbuster
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: aarch64-macos-feroxbuster.tar.gz
|
||||
path: aarch64-macos-feroxbuster.tar.gz
|
||||
|
||||
build-windows:
|
||||
env:
|
||||
@@ -142,18 +191,18 @@ jobs:
|
||||
name: x86-windows-feroxbuster.exe
|
||||
path: target\i686-pc-windows-msvc\release\feroxbuster.exe
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
- uses: actions/checkout@v4
|
||||
- name: Cache cargo & target directories
|
||||
uses: Swatinem/rust-cache@v2
|
||||
- name: Build binary
|
||||
uses: houseabsolute/actions-rust-cross@v0
|
||||
with:
|
||||
toolchain: stable
|
||||
target: ${{ matrix.target }}
|
||||
override: true
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
use-cross: true
|
||||
command: build
|
||||
args: --release --target=${{ matrix.target }}
|
||||
- uses: actions/upload-artifact@v2
|
||||
target: ${{ matrix.target }}
|
||||
args: "--locked --release"
|
||||
strip: true
|
||||
toolchain: stable
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.name }}
|
||||
path: ${{ matrix.path }}
|
||||
|
||||
39
.github/workflows/check.yml
vendored
39
.github/workflows/check.yml
vendored
@@ -7,40 +7,41 @@ jobs:
|
||||
name: Check
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: check
|
||||
- uses: actions/checkout@v4
|
||||
- name: Cache cargo & target directories
|
||||
uses: Swatinem/rust-cache@v2
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- run: cargo check
|
||||
|
||||
test:
|
||||
name: Test Suite
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
- name: Cache cargo & target directories
|
||||
uses: Swatinem/rust-cache@v2
|
||||
- name: Install latest nextest release
|
||||
uses: taiki-e/install-action@nextest
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- name: Test with latest nextest release
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: nextest
|
||||
args: run --all-features --all-targets --retries 10
|
||||
run: cargo nextest run --all-features --all-targets --retries 4 --no-fail-fast
|
||||
|
||||
fmt:
|
||||
name: Rust fmt
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: fmt
|
||||
args: --all -- --check
|
||||
- uses: actions/checkout@v4
|
||||
- name: Cache cargo & target directories
|
||||
uses: Swatinem/rust-cache@v2
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- run: cargo fmt --all -- --check
|
||||
|
||||
clippy:
|
||||
name: Clippy
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: clippy
|
||||
args: --all-targets --all-features -- -D warnings
|
||||
- uses: actions/checkout@v4
|
||||
- name: Cache cargo & target directories
|
||||
uses: Swatinem/rust-cache@v2
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- run: cargo clippy --all-targets --all-features -- -D warnings
|
||||
|
||||
8
.github/workflows/cicd-to-dockerhub.yml
vendored
8
.github/workflows/cicd-to-dockerhub.yml
vendored
@@ -9,21 +9,21 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v1
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Build and push
|
||||
id: docker_build
|
||||
uses: docker/build-push-action@v2
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: ./
|
||||
file: ./Dockerfile
|
||||
|
||||
7
.github/workflows/coverage.yml
vendored
7
.github/workflows/coverage.yml
vendored
@@ -7,7 +7,7 @@ jobs:
|
||||
name: LLVM Coverage
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
components: llvm-tools-preview
|
||||
@@ -16,9 +16,10 @@ jobs:
|
||||
with:
|
||||
tool: cargo-nextest,cargo-llvm-cov
|
||||
- name: Generate code coverage
|
||||
run: cargo llvm-cov nextest --all-features --no-fail-fast --lcov --output-path lcov.info
|
||||
run: cargo llvm-cov nextest --all-features --no-fail-fast --lcov --retries 4 --output-path lcov.info
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v3
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
files: lcov.info
|
||||
fail_ci_if_error: true
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
21
.github/workflows/winget.yml
vendored
Normal file
21
.github/workflows/winget.yml
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
name: Publish to Winget
|
||||
on:
|
||||
release:
|
||||
types: [released]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag_name:
|
||||
description: 'Tag name of release'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: vedantmgoyal2009/winget-releaser@main
|
||||
with:
|
||||
identifier: epi052.feroxbuster
|
||||
installers-regex: '-windows-feroxbuster\.exe\.zip$'
|
||||
token: ${{ secrets.WINGET_TOKEN }}
|
||||
release-tag: ${{ inputs.tag_name || github.event.release.tag_name || github.ref_name }}
|
||||
2482
Cargo.lock
generated
2482
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
53
Cargo.toml
53
Cargo.toml
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "feroxbuster"
|
||||
version = "2.10.0"
|
||||
version = "2.11.0"
|
||||
authors = ["Ben 'epi' Risher (@epi052)"]
|
||||
license = "MIT"
|
||||
edition = "2021"
|
||||
@@ -22,41 +22,44 @@ build = "build.rs"
|
||||
maintenance = { status = "actively-developed" }
|
||||
|
||||
[build-dependencies]
|
||||
clap = { version = "4.2", features = ["wrap_help", "cargo"] }
|
||||
clap_complete = "4.2"
|
||||
regex = "1.8"
|
||||
lazy_static = "1.4"
|
||||
clap = { version = "4.5", features = ["wrap_help", "cargo"] }
|
||||
clap_complete = "4.5"
|
||||
regex = "1.10"
|
||||
lazy_static = "1.5"
|
||||
dirs = "5.0"
|
||||
|
||||
[dependencies]
|
||||
scraper = "0.16"
|
||||
scraper = "0.19"
|
||||
futures = "0.3"
|
||||
tokio = { version = "1.28", features = ["full"] }
|
||||
tokio = { version = "1.39", features = ["full"] }
|
||||
tokio-util = { version = "0.7", features = ["codec"] }
|
||||
log = "0.4"
|
||||
env_logger = "0.10"
|
||||
reqwest = { version = "0.11", features = ["socks", "native-tls"] }
|
||||
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.3", features = ["serde"] }
|
||||
url = { version = "2.5", features = ["serde"] }
|
||||
serde_regex = "1.1"
|
||||
clap = { version = "4.2", features = ["wrap_help", "cargo"] }
|
||||
lazy_static = "1.4"
|
||||
toml = "0.7"
|
||||
clap = { version = "4.5", features = ["wrap_help", "cargo"] }
|
||||
lazy_static = "1.5"
|
||||
toml = "0.8"
|
||||
serde = { version = "1.0", features = ["derive", "rc"] }
|
||||
serde_json = "1.0"
|
||||
uuid = { version = "1.3", features = ["v4"] }
|
||||
indicatif = "0.17"
|
||||
uuid = { version = "1.16", features = ["v4"] }
|
||||
indicatif = { version = "0.17.8" }
|
||||
console = "0.15"
|
||||
openssl = { version = "0.10", features = ["vendored"] }
|
||||
dirs = "5.0"
|
||||
regex = "1.8"
|
||||
crossterm = "0.26"
|
||||
rlimit = "0.9"
|
||||
ctrlc = "3.2"
|
||||
regex = "1.10"
|
||||
crossterm = "0.27"
|
||||
rlimit = "0.10"
|
||||
ctrlc = "3.4"
|
||||
anyhow = "1.0"
|
||||
leaky-bucket = "0.12"
|
||||
gaoya = "0.1"
|
||||
self_update = { version = "0.36", features = [
|
||||
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.40", features = [
|
||||
"archive-tar",
|
||||
"compression-flate2",
|
||||
"archive-zip",
|
||||
@@ -64,10 +67,10 @@ self_update = { version = "0.36", features = [
|
||||
] }
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.5"
|
||||
httpmock = "0.6"
|
||||
tempfile = "3.12"
|
||||
httpmock = "0.7"
|
||||
assert_cmd = "2.0"
|
||||
predicates = "3.0"
|
||||
predicates = "3.1"
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM alpine:3.17.1 as build
|
||||
FROM alpine:3.17.1 AS build
|
||||
LABEL maintainer="wfnintr@null.net"
|
||||
|
||||
RUN apk upgrade --update-cache --available && apk add --update openssl
|
||||
@@ -9,7 +9,7 @@ RUN wget https://github.com/epi052/feroxbuster/releases/latest/download/x86_64-l
|
||||
&& chmod +x /tmp/feroxbuster \
|
||||
&& wget https://raw.githubusercontent.com/danielmiessler/SecLists/master/Discovery/Web-Content/raft-medium-directories.txt -O /tmp/raft-medium-directories.txt
|
||||
|
||||
from alpine:3.17.1 as release
|
||||
FROM alpine:3.17.1 AS release
|
||||
COPY --from=build /tmp/raft-medium-directories.txt /usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt
|
||||
COPY --from=build /tmp/feroxbuster /usr/local/bin/feroxbuster
|
||||
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
[tasks.upgrade]
|
||||
dependencies = ["upgrade-deps", "update"]
|
||||
|
||||
[tasks.check]
|
||||
dependencies = ["fmt", "clippy", "test"]
|
||||
|
||||
# cleaning
|
||||
[tasks.clean-state]
|
||||
script = """
|
||||
@@ -11,7 +14,7 @@ rm ferox-*.state
|
||||
# dependency management
|
||||
[tasks.upgrade-deps]
|
||||
command = "cargo"
|
||||
args = ["upgrade", "--to-lockfile"]
|
||||
args = ["upgrade", "--exclude", "self_update"]
|
||||
|
||||
[tasks.update]
|
||||
command = "cargo"
|
||||
@@ -24,9 +27,15 @@ script = """
|
||||
cargo clippy --all-targets --all-features -- -D warnings
|
||||
"""
|
||||
|
||||
[tasks.fmt]
|
||||
clear = true
|
||||
script = """
|
||||
cargo fmt --all
|
||||
"""
|
||||
|
||||
# tests
|
||||
[tasks.test]
|
||||
clear = true
|
||||
script = """
|
||||
cargo nextest run --all-features --all-targets --retries 10
|
||||
cargo nextest run --all-features --all-targets --retries 4 --no-fail-fast
|
||||
"""
|
||||
|
||||
49
README.md
49
README.md
@@ -121,6 +121,12 @@ Expand-Archive .\feroxbuster.zip
|
||||
.\feroxbuster\feroxbuster.exe -V
|
||||
```
|
||||
|
||||
#### Windows via Winget
|
||||
|
||||
```
|
||||
winget install epi052.feroxbuster
|
||||
```
|
||||
|
||||
#### Windows via Chocolatey
|
||||
|
||||
```
|
||||
@@ -226,13 +232,13 @@ 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/wtwver"><img src="https://avatars.githubusercontent.com/u/53866088?v=4?s=100" width="100px;" alt="wtwver"/><br /><sub><b>wtwver</b></sub></a><br /><a href="#infra-wtwver" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://tib3rius.com"><img src="https://avatars.githubusercontent.com/u/48113936?v=4?s=100" width="100px;" alt="Tib3rius"/><br /><sub><b>Tib3rius</b></sub></a><br /><a href="https://github.com/epi052/feroxbuster/issues?q=author%3ATib3rius" title="Bug reports">🐛</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://tib3rius.com"><img src="https://avatars.githubusercontent.com/u/48113936?v=4?s=100" width="100px;" alt="Tib3rius"/><br /><sub><b>Tib3rius</b></sub></a><br /><a href="https://github.com/epi052/feroxbuster/issues?q=author%3ATib3rius" title="Bug reports">🐛</a> <a href="#ideas-Tib3rius" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/0xdf"><img src="https://avatars.githubusercontent.com/u/1489045?v=4?s=100" width="100px;" alt="0xdf"/><br /><sub><b>0xdf</b></sub></a><br /><a href="https://github.com/epi052/feroxbuster/issues?q=author%3A0xdf" title="Bug reports">🐛</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://secure77.de"><img src="https://avatars.githubusercontent.com/u/31564517?v=4?s=100" width="100px;" alt="secure-77"/><br /><sub><b>secure-77</b></sub></a><br /><a href="https://github.com/epi052/feroxbuster/issues?q=author%3Asecure-77" title="Bug reports">🐛</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/sbrun"><img src="https://avatars.githubusercontent.com/u/7712154?v=4?s=100" width="100px;" alt="Sophie Brun"/><br /><sub><b>Sophie Brun</b></sub></a><br /><a href="#infra-sbrun" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/black-A"><img src="https://avatars.githubusercontent.com/u/30686803?v=4?s=100" width="100px;" alt="black-A"/><br /><sub><b>black-A</b></sub></a><br /><a href="#ideas-black-A" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/dinosn"><img src="https://avatars.githubusercontent.com/u/3851678?v=4?s=100" width="100px;" alt="Nicolas Krassas"/><br /><sub><b>Nicolas Krassas</b></sub></a><br /><a href="#ideas-dinosn" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/N0ur5"><img src="https://avatars.githubusercontent.com/u/24260009?v=4?s=100" width="100px;" alt="N0ur5"/><br /><sub><b>N0ur5</b></sub></a><br /><a href="#ideas-N0ur5" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/N0ur5"><img src="https://avatars.githubusercontent.com/u/24260009?v=4?s=100" width="100px;" alt="N0ur5"/><br /><sub><b>N0ur5</b></sub></a><br /><a href="#ideas-N0ur5" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/epi052/feroxbuster/issues?q=author%3AN0ur5" title="Bug reports">🐛</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/moscowchill"><img src="https://avatars.githubusercontent.com/u/72578879?v=4?s=100" width="100px;" alt="mchill"/><br /><sub><b>mchill</b></sub></a><br /><a href="https://github.com/epi052/feroxbuster/issues?q=author%3Amoscowchill" title="Bug reports">🐛</a></td>
|
||||
@@ -247,7 +253,7 @@ 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/hunter0x8"><img src="https://avatars.githubusercontent.com/u/46222314?v=4?s=100" width="100px;" alt="Muhammad Ahsan"/><br /><sub><b>Muhammad Ahsan</b></sub></a><br /><a href="https://github.com/epi052/feroxbuster/issues?q=author%3Ahunter0x8" title="Bug reports">🐛</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/cortantief"><img src="https://avatars.githubusercontent.com/u/34527333?v=4?s=100" width="100px;" alt="cortantief"/><br /><sub><b>cortantief</b></sub></a><br /><a href="https://github.com/epi052/feroxbuster/issues?q=author%3Acortantief" title="Bug reports">🐛</a> <a href="https://github.com/epi052/feroxbuster/commits?author=cortantief" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/dsaxton"><img src="https://avatars.githubusercontent.com/u/2658661?v=4?s=100" width="100px;" alt="Daniel Saxton"/><br /><sub><b>Daniel Saxton</b></sub></a><br /><a href="#ideas-dsaxton" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/epi052/feroxbuster/commits?author=dsaxton" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/n0kovo"><img src="https://avatars.githubusercontent.com/u/16690056?v=4?s=100" width="100px;" alt="n0kovo"/><br /><sub><b>n0kovo</b></sub></a><br /><a href="#ideas-n0kovo" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/n0kovo"><img src="https://avatars.githubusercontent.com/u/16690056?v=4?s=100" width="100px;" alt="n0kovo"/><br /><sub><b>n0kovo</b></sub></a><br /><a href="#ideas-n0kovo" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/epi052/feroxbuster/issues?q=author%3An0kovo" title="Bug reports">🐛</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://ring0.lol"><img src="https://avatars.githubusercontent.com/u/1893909?v=4?s=100" width="100px;" alt="Justin Steven"/><br /><sub><b>Justin Steven</b></sub></a><br /><a href="#ideas-justinsteven" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/7047payloads"><img src="https://avatars.githubusercontent.com/u/95562424?v=4?s=100" width="100px;" alt="7047payloads"/><br /><sub><b>7047payloads</b></sub></a><br /><a href="https://github.com/epi052/feroxbuster/commits?author=7047payloads" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/unkn0wnsyst3m"><img src="https://avatars.githubusercontent.com/u/21272239?v=4?s=100" width="100px;" alt="unkn0wnsyst3m"/><br /><sub><b>unkn0wnsyst3m</b></sub></a><br /><a href="#ideas-unkn0wnsyst3m" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
@@ -291,6 +297,43 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://lavafroth.is-a.dev"><img src="https://avatars.githubusercontent.com/u/107522312?v=4?s=100" width="100px;" alt="Himadri Bhattacharjee"/><br /><sub><b>Himadri Bhattacharjee</b></sub></a><br /><a href="https://github.com/epi052/feroxbuster/commits?author=lavafroth" title="Code">💻</a> <a href="#ideas-lavafroth" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/AkechiShiro"><img src="https://avatars.githubusercontent.com/u/14914796?v=4?s=100" width="100px;" alt="Samy Lahfa"/><br /><sub><b>Samy Lahfa</b></sub></a><br /><a href="#ideas-AkechiShiro" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/sectroyer"><img src="https://avatars.githubusercontent.com/u/6706818?v=4?s=100" width="100px;" alt="sectroyer"/><br /><sub><b>sectroyer</b></sub></a><br /><a href="https://github.com/epi052/feroxbuster/issues?q=author%3Asectroyer" title="Bug reports">🐛</a> <a href="#ideas-sectroyer" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://medium.com/@b3rm1nG"><img src="https://avatars.githubusercontent.com/u/19836003?v=4?s=100" width="100px;" alt="ktecv2000"/><br /><sub><b>ktecv2000</b></sub></a><br /><a href="https://github.com/epi052/feroxbuster/issues?q=author%3Aktecv2000" title="Bug reports">🐛</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://untrue.me"><img src="https://avatars.githubusercontent.com/u/56048157?v=4?s=100" width="100px;" alt="Andrea De Murtas"/><br /><sub><b>Andrea De Murtas</b></sub></a><br /><a href="https://github.com/epi052/feroxbuster/commits?author=andreademurtas" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/sawmj"><img src="https://avatars.githubusercontent.com/u/30024085?v=4?s=100" width="100px;" alt="sawmj"/><br /><sub><b>sawmj</b></sub></a><br /><a href="https://github.com/epi052/feroxbuster/issues?q=author%3Asawmj" title="Bug reports">🐛</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/devx00"><img src="https://avatars.githubusercontent.com/u/6897405?v=4?s=100" width="100px;" alt="Zach Hanson"/><br /><sub><b>Zach Hanson</b></sub></a><br /><a href="https://github.com/epi052/feroxbuster/issues?q=author%3Adevx00" title="Bug reports">🐛</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ocervell"><img src="https://avatars.githubusercontent.com/u/9629314?v=4?s=100" width="100px;" alt="Olivier Cervello"/><br /><sub><b>Olivier Cervello</b></sub></a><br /><a href="#ideas-ocervell" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/RavySena"><img src="https://avatars.githubusercontent.com/u/67729597?v=4?s=100" width="100px;" alt="RavySena"/><br /><sub><b>RavySena</b></sub></a><br /><a href="#ideas-RavySena" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/stuhlmann"><img src="https://avatars.githubusercontent.com/u/11061864?v=4?s=100" width="100px;" alt="Florian Stuhlmann"/><br /><sub><b>Florian Stuhlmann</b></sub></a><br /><a href="https://github.com/epi052/feroxbuster/issues?q=author%3Astuhlmann" title="Bug reports">🐛</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Mister7F"><img src="https://avatars.githubusercontent.com/u/35213773?v=4?s=100" width="100px;" alt="Mister7F"/><br /><sub><b>Mister7F</b></sub></a><br /><a href="#ideas-Mister7F" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/manugramm"><img src="https://avatars.githubusercontent.com/u/145961515?v=4?s=100" width="100px;" alt="manugramm"/><br /><sub><b>manugramm</b></sub></a><br /><a href="https://github.com/epi052/feroxbuster/issues?q=author%3Amanugramm" title="Bug reports">🐛</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ArthurMuraro"><img src="https://avatars.githubusercontent.com/u/73059809?v=4?s=100" width="100px;" alt="ArthurMuraro"/><br /><sub><b>ArthurMuraro</b></sub></a><br /><a href="https://github.com/epi052/feroxbuster/issues?q=author%3AArthurMuraro" title="Bug reports">🐛</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/amiremami"><img src="https://avatars.githubusercontent.com/u/15929497?v=4?s=100" width="100px;" alt="Shadow"/><br /><sub><b>Shadow</b></sub></a><br /><a href="https://github.com/epi052/feroxbuster/issues?q=author%3Aamiremami" title="Bug reports">🐛</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/dirhamgithub"><img src="https://avatars.githubusercontent.com/u/115349974?v=4?s=100" width="100px;" alt="dirhamgithub"/><br /><sub><b>dirhamgithub</b></sub></a><br /><a href="https://github.com/epi052/feroxbuster/issues?q=author%3Adirhamgithub" title="Bug reports">🐛</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/FieldOfRice"><img src="https://avatars.githubusercontent.com/u/85353?v=4?s=100" width="100px;" alt="FieldOfRice"/><br /><sub><b>FieldOfRice</b></sub></a><br /><a href="#infra-FieldOfRice" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td>
|
||||
<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> <a href="#ideas-L1-0" title="Ideas, Planning, & Feedback">🤔</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>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/libklein"><img src="https://avatars.githubusercontent.com/u/42714034?v=4?s=100" width="100px;" alt="Patrick Klein"/><br /><sub><b>Patrick Klein</b></sub></a><br /><a href="#ideas-libklein" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Raymond-JV"><img src="https://avatars.githubusercontent.com/u/23642921?v=4?s=100" width="100px;" alt="Raymond"/><br /><sub><b>Raymond</b></sub></a><br /><a href="#ideas-Raymond-JV" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/zer0x64"><img src="https://avatars.githubusercontent.com/u/17575242?v=4?s=100" width="100px;" alt="zer0x64"/><br /><sub><b>zer0x64</b></sub></a><br /><a href="https://github.com/epi052/feroxbuster/commits?author=zer0x64" title="Code">💻</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
2
build.rs
2
build.rs
@@ -18,7 +18,7 @@ fn main() {
|
||||
|
||||
generate_to(shells::Bash, &mut app, "feroxbuster", outdir).unwrap();
|
||||
generate_to(shells::Zsh, &mut app, "feroxbuster", outdir).unwrap();
|
||||
generate_to(shells::Zsh, &mut app, "feroxbuster", outdir).unwrap();
|
||||
generate_to(shells::Fish, &mut app, "feroxbuster", outdir).unwrap();
|
||||
generate_to(shells::PowerShell, &mut app, "feroxbuster", outdir).unwrap();
|
||||
generate_to(shells::Elvish, &mut app, "feroxbuster", outdir).unwrap();
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
# dont_filter = true
|
||||
# extract_links = true
|
||||
# depth = 1
|
||||
# limit_bars = 3
|
||||
# force_recursion = true
|
||||
# filter_size = [5174]
|
||||
# filter_regex = ["^ignore me$"]
|
||||
@@ -57,6 +58,9 @@
|
||||
# server_certs = ["/some/cert.pem", "/some/other/cert.pem"]
|
||||
# client_cert = "/some/client/cert.pem"
|
||||
# client_key = "/some/client/key.pem"
|
||||
# request_file = "/some/raw/request/file"
|
||||
# protocol = "http"
|
||||
# scan_dir_listings = true
|
||||
|
||||
# headers can be specified on multiple lines or as an inline table
|
||||
#
|
||||
@@ -67,6 +71,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"
|
||||
|
||||
@@ -44,7 +44,7 @@ elif [[ "$(expr substr $(uname -s) 1 5)" == "Linux" ]]; then
|
||||
rm "$LIN64_ZIP"
|
||||
fi
|
||||
|
||||
if [[ -e ~/.fonts/NotoColorEmoji.ttf ]]; then
|
||||
if [[ "$(fc-list NotoColorEmoji | wc -l)" -gt 0 ]]; then
|
||||
echo "[=] Found Noto Emoji Font, skipping install"
|
||||
else
|
||||
echo "[=] Installing Noto Emoji Font"
|
||||
|
||||
@@ -14,20 +14,21 @@ _feroxbuster() {
|
||||
fi
|
||||
|
||||
local context curcontext="$curcontext" state line
|
||||
_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' \
|
||||
_arguments "${_arguments_options[@]}" : \
|
||||
'-u+[The target URL (required, unless \[--stdin || --resume-from || --request-file\] used)]:URL:_urls' \
|
||||
'--url=[The target URL (required, unless \[--stdin || --resume-from || --request-file\] 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' \
|
||||
'(-u --url)--request-file=[Raw HTTP request file to use as a template for all requests]:REQUEST_FILE:_files' \
|
||||
'-p+[Proxy to use for requests (ex\: http(s)\://host\:port, socks5(h)\://host\:port)]:PROXY:_urls' \
|
||||
'--proxy=[Proxy to use for requests (ex\: http(s)\://host\:port, socks5(h)\://host\:port)]:PROXY:_urls' \
|
||||
'-P+[Send only unfiltered requests through a Replay Proxy, instead of all requests]:REPLAY_PROXY:_urls' \
|
||||
'--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.0)]:USER_AGENT: ' \
|
||||
'--user-agent=[Sets the User-Agent (default\: feroxbuster/2.10.0)]: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: ' \
|
||||
'-a+[Sets the User-Agent (default\: feroxbuster/2.11.0)]:USER_AGENT: ' \
|
||||
'--user-agent=[Sets the User-Agent (default\: feroxbuster/2.11.0)]: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: ' \
|
||||
'*--methods=[Which HTTP request method(s) should be sent (default\: GET)]:HTTP_METHODS: ' \
|
||||
'--data=[Request'\''s Body; can read data from a file if input starts with an @ (ex\: @post.bin)]:DATA: ' \
|
||||
@@ -37,11 +38,12 @@ _feroxbuster() {
|
||||
'*--cookies=[Specify HTTP cookies to be used in each request (ex\: -b stuff=things)]:COOKIE: ' \
|
||||
'*-Q+[Request'\''s URL query parameters (ex\: -Q token=stuff -Q secret=key)]:QUERY: ' \
|
||||
'*--query=[Request'\''s URL query parameters (ex\: -Q token=stuff -Q secret=key)]:QUERY: ' \
|
||||
'--protocol=[Specify the protocol to use when targeting via --request-file or --url with domain only (default\: https)]:PROTOCOL: ' \
|
||||
'*--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: ' \
|
||||
@@ -62,21 +64,24 @@ _feroxbuster() {
|
||||
'--depth=[Maximum recursion depth, a depth of 0 is infinite recursion (default\: 4)]:RECURSION_DEPTH: ' \
|
||||
'-L+[Limit total number of concurrent scans (default\: 0, i.e. no limit)]:SCAN_LIMIT: ' \
|
||||
'--scan-limit=[Limit total number of concurrent scans (default\: 0, i.e. no limit)]:SCAN_LIMIT: ' \
|
||||
'--parallel=[Run parallel feroxbuster instances (one child process per url passed via stdin)]:PARALLEL_SCANS: ' \
|
||||
'(-v --verbosity -u --url)--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 or URL of the wordlist]:FILE:_files' \
|
||||
'--wordlist=[Path or URL of the wordlist]:FILE:_files' \
|
||||
'-B+[Automatically request likely backup extensions for "found" urls (default\: ~, .bak, .bak2, .old, .1)]' \
|
||||
'--collect-backups=[Automatically request likely backup extensions for "found" urls (default\: ~, .bak, .bak2, .old, .1)]' \
|
||||
'*-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' \
|
||||
'--output=[Output file to write results to (use w/ --json for JSON entries)]:FILE:_files' \
|
||||
'--debug-log=[Output file to write log entries (use w/ --json for JSON entries)]:FILE:_files' \
|
||||
'--limit-bars=[Number of directory scan bars to show at any given time (default\: no limit)]:NUM_BARS_TO_SHOW: ' \
|
||||
'(-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 --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]' \
|
||||
'(--rate-limit --auto-bail)--thorough[Use the same settings as --smart and set --collect-extensions and --scan-dir-listings to true]' \
|
||||
'-A[Use a random User-Agent]' \
|
||||
'--random-agent[Use a random User-Agent]' \
|
||||
'-f[Append / to each request'\''s URL]' \
|
||||
@@ -97,13 +102,12 @@ _feroxbuster() {
|
||||
'--dont-filter[Don'\''t auto-filter wildcard responses]' \
|
||||
'-E[Automatically discover extensions and add them to --extensions (unless they'\''re in --dont-collect)]' \
|
||||
'--collect-extensions[Automatically discover extensions and add them to --extensions (unless they'\''re in --dont-collect)]' \
|
||||
'-B[Automatically request likely backup extensions for "found" urls]' \
|
||||
'--collect-backups[Automatically request likely backup extensions for "found" urls]' \
|
||||
'-g[Automatically discover important words from within responses and add them to the wordlist]' \
|
||||
'--collect-words[Automatically discover important words from within responses and add them to the wordlist]' \
|
||||
'--scan-dir-listings[Force scans to recurse into directory listings]' \
|
||||
'(--silent)*-v[Increase verbosity level (use -vv or more for greater effect. \[CAUTION\] 4 -v'\''s is probably too much)]' \
|
||||
'(--silent)*--verbosity[Increase verbosity level (use -vv or more for greater effect. \[CAUTION\] 4 -v'\''s is probably too much)]' \
|
||||
'(-q --quiet)--silent[Only print URLs + turn off logging (good for piping a list of urls to other commands)]' \
|
||||
'(-q --quiet)--silent[Only print URLs (or JSON w/ --json) + turn off logging (good for piping a list of urls to other commands)]' \
|
||||
'-q[Hide progress bars and banner (good for tmux windows w/ notifications)]' \
|
||||
'--quiet[Hide progress bars and banner (good for tmux windows w/ notifications)]' \
|
||||
'--json[Emit JSON logs to --output and --debug-log instead of normal text]' \
|
||||
|
||||
@@ -21,105 +21,109 @@ Register-ArgumentCompleter -Native -CommandName 'feroxbuster' -ScriptBlock {
|
||||
|
||||
$completions = @(switch ($command) {
|
||||
'feroxbuster' {
|
||||
[CompletionResult]::new('-u', 'u', [CompletionResultType]::ParameterName, 'The target URL (required, unless [--stdin || --resume-from] used)')
|
||||
[CompletionResult]::new('--url', 'url', [CompletionResultType]::ParameterName, 'The target URL (required, unless [--stdin || --resume-from] used)')
|
||||
[CompletionResult]::new('--resume-from', 'resume-from', [CompletionResultType]::ParameterName, 'State file from which to resume a partially complete scan (ex. --resume-from ferox-1606586780.state)')
|
||||
[CompletionResult]::new('-p', 'p', [CompletionResultType]::ParameterName, 'Proxy to use for requests (ex: http(s)://host:port, socks5(h)://host:port)')
|
||||
[CompletionResult]::new('--proxy', 'proxy', [CompletionResultType]::ParameterName, 'Proxy to use for requests (ex: http(s)://host:port, socks5(h)://host:port)')
|
||||
[CompletionResult]::new('-P', 'P', [CompletionResultType]::ParameterName, 'Send only unfiltered requests through a Replay Proxy, instead of all requests')
|
||||
[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.0)')
|
||||
[CompletionResult]::new('--user-agent', 'user-agent', [CompletionResultType]::ParameterName, 'Sets the User-Agent (default: feroxbuster/2.10.0)')
|
||||
[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)')
|
||||
[CompletionResult]::new('--methods', 'methods', [CompletionResultType]::ParameterName, 'Which HTTP request method(s) should be sent (default: GET)')
|
||||
[CompletionResult]::new('--data', 'data', [CompletionResultType]::ParameterName, 'Request''s Body; can read data from a file if input starts with an @ (ex: @post.bin)')
|
||||
[CompletionResult]::new('-H', 'H', [CompletionResultType]::ParameterName, 'Specify HTTP headers to be used in each request (ex: -H Header:val -H ''stuff: things'')')
|
||||
[CompletionResult]::new('--headers', 'headers', [CompletionResultType]::ParameterName, 'Specify HTTP headers to be used in each request (ex: -H Header:val -H ''stuff: things'')')
|
||||
[CompletionResult]::new('-b', 'b', [CompletionResultType]::ParameterName, 'Specify HTTP cookies to be used in each request (ex: -b stuff=things)')
|
||||
[CompletionResult]::new('--cookies', 'cookies', [CompletionResultType]::ParameterName, 'Specify HTTP cookies to be used in each request (ex: -b stuff=things)')
|
||||
[CompletionResult]::new('-Q', 'Q', [CompletionResultType]::ParameterName, 'Request''s URL query parameters (ex: -Q token=stuff -Q secret=key)')
|
||||
[CompletionResult]::new('--query', 'query', [CompletionResultType]::ParameterName, 'Request''s URL query parameters (ex: -Q token=stuff -Q secret=key)')
|
||||
[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('-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)')
|
||||
[CompletionResult]::new('--filter-lines', 'filter-lines', [CompletionResultType]::ParameterName, 'Filter out messages of a particular line count (ex: -N 20 -N 31,30)')
|
||||
[CompletionResult]::new('-C', 'C', [CompletionResultType]::ParameterName, 'Filter out status codes (deny list) (ex: -C 200 -C 401)')
|
||||
[CompletionResult]::new('--filter-status', 'filter-status', [CompletionResultType]::ParameterName, 'Filter out status codes (deny list) (ex: -C 200 -C 401)')
|
||||
[CompletionResult]::new('--filter-similar-to', 'filter-similar-to', [CompletionResultType]::ParameterName, 'Filter out pages that are similar to the given page (ex. --filter-similar-to http://site.xyz/soft404)')
|
||||
[CompletionResult]::new('-s', 's', [CompletionResultType]::ParameterName, 'Status Codes to include (allow list) (default: All Status Codes)')
|
||||
[CompletionResult]::new('--status-codes', 'status-codes', [CompletionResultType]::ParameterName, 'Status Codes to include (allow list) (default: All Status Codes)')
|
||||
[CompletionResult]::new('-T', 'T', [CompletionResultType]::ParameterName, 'Number of seconds before a client''s request times out (default: 7)')
|
||||
[CompletionResult]::new('--timeout', 'timeout', [CompletionResultType]::ParameterName, 'Number of seconds before a client''s request times out (default: 7)')
|
||||
[CompletionResult]::new('--server-certs', 'server-certs', [CompletionResultType]::ParameterName, 'Add custom root certificate(s) for servers with unknown certificates')
|
||||
[CompletionResult]::new('--client-cert', 'client-cert', [CompletionResultType]::ParameterName, 'Add a PEM encoded certificate for mutual authentication (mTLS)')
|
||||
[CompletionResult]::new('--client-key', 'client-key', [CompletionResultType]::ParameterName, 'Add a PEM encoded private key for mutual authentication (mTLS)')
|
||||
[CompletionResult]::new('-t', 't', [CompletionResultType]::ParameterName, 'Number of concurrent threads (default: 50)')
|
||||
[CompletionResult]::new('--threads', 'threads', [CompletionResultType]::ParameterName, 'Number of concurrent threads (default: 50)')
|
||||
[CompletionResult]::new('-d', 'd', [CompletionResultType]::ParameterName, 'Maximum recursion depth, a depth of 0 is infinite recursion (default: 4)')
|
||||
[CompletionResult]::new('--depth', 'depth', [CompletionResultType]::ParameterName, 'Maximum recursion depth, a depth of 0 is infinite recursion (default: 4)')
|
||||
[CompletionResult]::new('-L', 'L', [CompletionResultType]::ParameterName, 'Limit total number of concurrent scans (default: 0, i.e. no limit)')
|
||||
[CompletionResult]::new('--scan-limit', 'scan-limit', [CompletionResultType]::ParameterName, 'Limit total number of concurrent scans (default: 0, i.e. no limit)')
|
||||
[CompletionResult]::new('--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 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)')
|
||||
[CompletionResult]::new('--output', 'output', [CompletionResultType]::ParameterName, 'Output file to write results to (use w/ --json for JSON entries)')
|
||||
[CompletionResult]::new('--debug-log', 'debug-log', [CompletionResultType]::ParameterName, 'Output file to write log entries (use w/ --json for JSON entries)')
|
||||
[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 --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')
|
||||
[CompletionResult]::new('-f', 'f', [CompletionResultType]::ParameterName, 'Append / to each request''s URL')
|
||||
[CompletionResult]::new('--add-slash', 'add-slash', [CompletionResultType]::ParameterName, 'Append / to each request''s URL')
|
||||
[CompletionResult]::new('-r', 'r', [CompletionResultType]::ParameterName, 'Allow client to follow redirects')
|
||||
[CompletionResult]::new('--redirects', 'redirects', [CompletionResultType]::ParameterName, 'Allow client to follow redirects')
|
||||
[CompletionResult]::new('-k', 'k', [CompletionResultType]::ParameterName, 'Disables TLS certificate validation in the client')
|
||||
[CompletionResult]::new('--insecure', 'insecure', [CompletionResultType]::ParameterName, 'Disables TLS certificate validation in the client')
|
||||
[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 (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')
|
||||
[CompletionResult]::new('--dont-filter', 'dont-filter', [CompletionResultType]::ParameterName, 'Don''t auto-filter wildcard responses')
|
||||
[CompletionResult]::new('-E', 'E', [CompletionResultType]::ParameterName, 'Automatically discover extensions and add them to --extensions (unless they''re in --dont-collect)')
|
||||
[CompletionResult]::new('--collect-extensions', 'collect-extensions', [CompletionResultType]::ParameterName, 'Automatically discover extensions and add them to --extensions (unless they''re in --dont-collect)')
|
||||
[CompletionResult]::new('-B', 'B', [CompletionResultType]::ParameterName, 'Automatically request likely backup extensions for "found" urls')
|
||||
[CompletionResult]::new('--collect-backups', 'collect-backups', [CompletionResultType]::ParameterName, 'Automatically request likely backup extensions for "found" urls')
|
||||
[CompletionResult]::new('-g', 'g', [CompletionResultType]::ParameterName, 'Automatically discover important words from within responses and add them to the wordlist')
|
||||
[CompletionResult]::new('--collect-words', 'collect-words', [CompletionResultType]::ParameterName, 'Automatically discover important words from within responses and add them to the wordlist')
|
||||
[CompletionResult]::new('-v', 'v', [CompletionResultType]::ParameterName, 'Increase verbosity level (use -vv or more for greater effect. [CAUTION] 4 -v''s is probably too much)')
|
||||
[CompletionResult]::new('--verbosity', 'verbosity', [CompletionResultType]::ParameterName, 'Increase verbosity level (use -vv or more for greater effect. [CAUTION] 4 -v''s is probably too much)')
|
||||
[CompletionResult]::new('--silent', 'silent', [CompletionResultType]::ParameterName, 'Only print URLs + turn off logging (good for piping a list of urls to other commands)')
|
||||
[CompletionResult]::new('-q', 'q', [CompletionResultType]::ParameterName, 'Hide progress bars and banner (good for tmux windows w/ notifications)')
|
||||
[CompletionResult]::new('--quiet', 'quiet', [CompletionResultType]::ParameterName, 'Hide progress bars and banner (good for tmux windows w/ notifications)')
|
||||
[CompletionResult]::new('--json', 'json', [CompletionResultType]::ParameterName, 'Emit JSON logs to --output and --debug-log instead of normal text')
|
||||
[CompletionResult]::new('--no-state', 'no-state', [CompletionResultType]::ParameterName, 'Disable state output file (*.state)')
|
||||
[CompletionResult]::new('-U', 'U', [CompletionResultType]::ParameterName, 'Update feroxbuster to the latest version')
|
||||
[CompletionResult]::new('--update', 'update', [CompletionResultType]::ParameterName, 'Update feroxbuster to the latest version')
|
||||
[CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help (see more with ''--help'')')
|
||||
[CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help (see more with ''--help'')')
|
||||
[CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Print version')
|
||||
[CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Print version')
|
||||
[CompletionResult]::new('-u', '-u', [CompletionResultType]::ParameterName, 'The target URL (required, unless [--stdin || --resume-from || --request-file] used)')
|
||||
[CompletionResult]::new('--url', '--url', [CompletionResultType]::ParameterName, 'The target URL (required, unless [--stdin || --resume-from || --request-file] used)')
|
||||
[CompletionResult]::new('--resume-from', '--resume-from', [CompletionResultType]::ParameterName, 'State file from which to resume a partially complete scan (ex. --resume-from ferox-1606586780.state)')
|
||||
[CompletionResult]::new('--request-file', '--request-file', [CompletionResultType]::ParameterName, 'Raw HTTP request file to use as a template for all requests')
|
||||
[CompletionResult]::new('-p', '-p', [CompletionResultType]::ParameterName, 'Proxy to use for requests (ex: http(s)://host:port, socks5(h)://host:port)')
|
||||
[CompletionResult]::new('--proxy', '--proxy', [CompletionResultType]::ParameterName, 'Proxy to use for requests (ex: http(s)://host:port, socks5(h)://host:port)')
|
||||
[CompletionResult]::new('-P', '-P ', [CompletionResultType]::ParameterName, 'Send only unfiltered requests through a Replay Proxy, instead of all requests')
|
||||
[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.11.0)')
|
||||
[CompletionResult]::new('--user-agent', '--user-agent', [CompletionResultType]::ParameterName, 'Sets the User-Agent (default: feroxbuster/2.11.0)')
|
||||
[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)')
|
||||
[CompletionResult]::new('--methods', '--methods', [CompletionResultType]::ParameterName, 'Which HTTP request method(s) should be sent (default: GET)')
|
||||
[CompletionResult]::new('--data', '--data', [CompletionResultType]::ParameterName, 'Request''s Body; can read data from a file if input starts with an @ (ex: @post.bin)')
|
||||
[CompletionResult]::new('-H', '-H ', [CompletionResultType]::ParameterName, 'Specify HTTP headers to be used in each request (ex: -H Header:val -H ''stuff: things'')')
|
||||
[CompletionResult]::new('--headers', '--headers', [CompletionResultType]::ParameterName, 'Specify HTTP headers to be used in each request (ex: -H Header:val -H ''stuff: things'')')
|
||||
[CompletionResult]::new('-b', '-b', [CompletionResultType]::ParameterName, 'Specify HTTP cookies to be used in each request (ex: -b stuff=things)')
|
||||
[CompletionResult]::new('--cookies', '--cookies', [CompletionResultType]::ParameterName, 'Specify HTTP cookies to be used in each request (ex: -b stuff=things)')
|
||||
[CompletionResult]::new('-Q', '-Q ', [CompletionResultType]::ParameterName, 'Request''s URL query parameters (ex: -Q token=stuff -Q secret=key)')
|
||||
[CompletionResult]::new('--query', '--query', [CompletionResultType]::ParameterName, 'Request''s URL query parameters (ex: -Q token=stuff -Q secret=key)')
|
||||
[CompletionResult]::new('--protocol', '--protocol', [CompletionResultType]::ParameterName, 'Specify the protocol to use when targeting via --request-file or --url with domain only (default: https)')
|
||||
[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/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)')
|
||||
[CompletionResult]::new('--filter-lines', '--filter-lines', [CompletionResultType]::ParameterName, 'Filter out messages of a particular line count (ex: -N 20 -N 31,30)')
|
||||
[CompletionResult]::new('-C', '-C ', [CompletionResultType]::ParameterName, 'Filter out status codes (deny list) (ex: -C 200 -C 401)')
|
||||
[CompletionResult]::new('--filter-status', '--filter-status', [CompletionResultType]::ParameterName, 'Filter out status codes (deny list) (ex: -C 200 -C 401)')
|
||||
[CompletionResult]::new('--filter-similar-to', '--filter-similar-to', [CompletionResultType]::ParameterName, 'Filter out pages that are similar to the given page (ex. --filter-similar-to http://site.xyz/soft404)')
|
||||
[CompletionResult]::new('-s', '-s', [CompletionResultType]::ParameterName, 'Status Codes to include (allow list) (default: All Status Codes)')
|
||||
[CompletionResult]::new('--status-codes', '--status-codes', [CompletionResultType]::ParameterName, 'Status Codes to include (allow list) (default: All Status Codes)')
|
||||
[CompletionResult]::new('-T', '-T ', [CompletionResultType]::ParameterName, 'Number of seconds before a client''s request times out (default: 7)')
|
||||
[CompletionResult]::new('--timeout', '--timeout', [CompletionResultType]::ParameterName, 'Number of seconds before a client''s request times out (default: 7)')
|
||||
[CompletionResult]::new('--server-certs', '--server-certs', [CompletionResultType]::ParameterName, 'Add custom root certificate(s) for servers with unknown certificates')
|
||||
[CompletionResult]::new('--client-cert', '--client-cert', [CompletionResultType]::ParameterName, 'Add a PEM encoded certificate for mutual authentication (mTLS)')
|
||||
[CompletionResult]::new('--client-key', '--client-key', [CompletionResultType]::ParameterName, 'Add a PEM encoded private key for mutual authentication (mTLS)')
|
||||
[CompletionResult]::new('-t', '-t', [CompletionResultType]::ParameterName, 'Number of concurrent threads (default: 50)')
|
||||
[CompletionResult]::new('--threads', '--threads', [CompletionResultType]::ParameterName, 'Number of concurrent threads (default: 50)')
|
||||
[CompletionResult]::new('-d', '-d', [CompletionResultType]::ParameterName, 'Maximum recursion depth, a depth of 0 is infinite recursion (default: 4)')
|
||||
[CompletionResult]::new('--depth', '--depth', [CompletionResultType]::ParameterName, 'Maximum recursion depth, a depth of 0 is infinite recursion (default: 4)')
|
||||
[CompletionResult]::new('-L', '-L ', [CompletionResultType]::ParameterName, 'Limit total number of concurrent scans (default: 0, i.e. no limit)')
|
||||
[CompletionResult]::new('--scan-limit', '--scan-limit', [CompletionResultType]::ParameterName, 'Limit total number of concurrent scans (default: 0, i.e. no limit)')
|
||||
[CompletionResult]::new('--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 or URL of the wordlist')
|
||||
[CompletionResult]::new('--wordlist', '--wordlist', [CompletionResultType]::ParameterName, 'Path or URL of the wordlist')
|
||||
[CompletionResult]::new('-B', '-B ', [CompletionResultType]::ParameterName, 'Automatically request likely backup extensions for "found" urls (default: ~, .bak, .bak2, .old, .1)')
|
||||
[CompletionResult]::new('--collect-backups', '--collect-backups', [CompletionResultType]::ParameterName, 'Automatically request likely backup extensions for "found" urls (default: ~, .bak, .bak2, .old, .1)')
|
||||
[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)')
|
||||
[CompletionResult]::new('--output', '--output', [CompletionResultType]::ParameterName, 'Output file to write results to (use w/ --json for JSON entries)')
|
||||
[CompletionResult]::new('--debug-log', '--debug-log', [CompletionResultType]::ParameterName, 'Output file to write log entries (use w/ --json for JSON entries)')
|
||||
[CompletionResult]::new('--limit-bars', '--limit-bars', [CompletionResultType]::ParameterName, 'Number of directory scan bars to show at any given time (default: no limit)')
|
||||
[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 --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 and --scan-dir-listings 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')
|
||||
[CompletionResult]::new('-f', '-f', [CompletionResultType]::ParameterName, 'Append / to each request''s URL')
|
||||
[CompletionResult]::new('--add-slash', '--add-slash', [CompletionResultType]::ParameterName, 'Append / to each request''s URL')
|
||||
[CompletionResult]::new('-r', '-r', [CompletionResultType]::ParameterName, 'Allow client to follow redirects')
|
||||
[CompletionResult]::new('--redirects', '--redirects', [CompletionResultType]::ParameterName, 'Allow client to follow redirects')
|
||||
[CompletionResult]::new('-k', '-k', [CompletionResultType]::ParameterName, 'Disables TLS certificate validation in the client')
|
||||
[CompletionResult]::new('--insecure', '--insecure', [CompletionResultType]::ParameterName, 'Disables TLS certificate validation in the client')
|
||||
[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 (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')
|
||||
[CompletionResult]::new('--dont-filter', '--dont-filter', [CompletionResultType]::ParameterName, 'Don''t auto-filter wildcard responses')
|
||||
[CompletionResult]::new('-E', '-E ', [CompletionResultType]::ParameterName, 'Automatically discover extensions and add them to --extensions (unless they''re in --dont-collect)')
|
||||
[CompletionResult]::new('--collect-extensions', '--collect-extensions', [CompletionResultType]::ParameterName, 'Automatically discover extensions and add them to --extensions (unless they''re in --dont-collect)')
|
||||
[CompletionResult]::new('-g', '-g', [CompletionResultType]::ParameterName, 'Automatically discover important words from within responses and add them to the wordlist')
|
||||
[CompletionResult]::new('--collect-words', '--collect-words', [CompletionResultType]::ParameterName, 'Automatically discover important words from within responses and add them to the wordlist')
|
||||
[CompletionResult]::new('--scan-dir-listings', '--scan-dir-listings', [CompletionResultType]::ParameterName, 'Force scans to recurse into directory listings')
|
||||
[CompletionResult]::new('-v', '-v', [CompletionResultType]::ParameterName, 'Increase verbosity level (use -vv or more for greater effect. [CAUTION] 4 -v''s is probably too much)')
|
||||
[CompletionResult]::new('--verbosity', '--verbosity', [CompletionResultType]::ParameterName, 'Increase verbosity level (use -vv or more for greater effect. [CAUTION] 4 -v''s is probably too much)')
|
||||
[CompletionResult]::new('--silent', '--silent', [CompletionResultType]::ParameterName, 'Only print URLs (or JSON w/ --json) + turn off logging (good for piping a list of urls to other commands)')
|
||||
[CompletionResult]::new('-q', '-q', [CompletionResultType]::ParameterName, 'Hide progress bars and banner (good for tmux windows w/ notifications)')
|
||||
[CompletionResult]::new('--quiet', '--quiet', [CompletionResultType]::ParameterName, 'Hide progress bars and banner (good for tmux windows w/ notifications)')
|
||||
[CompletionResult]::new('--json', '--json', [CompletionResultType]::ParameterName, 'Emit JSON logs to --output and --debug-log instead of normal text')
|
||||
[CompletionResult]::new('--no-state', '--no-state', [CompletionResultType]::ParameterName, 'Disable state output file (*.state)')
|
||||
[CompletionResult]::new('-U', '-U ', [CompletionResultType]::ParameterName, 'Update feroxbuster to the latest version')
|
||||
[CompletionResult]::new('--update', '--update', [CompletionResultType]::ParameterName, 'Update feroxbuster to the latest version')
|
||||
[CompletionResult]::new('-h', '-h', [CompletionResultType]::ParameterName, 'Print help (see more with ''--help'')')
|
||||
[CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, 'Print help (see more with ''--help'')')
|
||||
[CompletionResult]::new('-V', '-V ', [CompletionResultType]::ParameterName, 'Print version')
|
||||
[CompletionResult]::new('--version', '--version', [CompletionResultType]::ParameterName, 'Print version')
|
||||
break
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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 --server-certs --client-cert --client-key --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"
|
||||
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 --request-file --burp --burp-replay --smart --thorough --proxy --replay-proxy --replay-codes --user-agent --random-agent --extensions --methods --data --headers --cookies --query --add-slash --protocol --dont-scan --filter-size --filter-regex --filter-words --filter-lines --filter-status --filter-similar-to --status-codes --timeout --redirects --insecure --server-certs --client-cert --client-key --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 --scan-dir-listings --verbosity --silent --quiet --json --output --debug-log --no-state --limit-bars --update --help --version"
|
||||
if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||
return 0
|
||||
@@ -34,7 +34,33 @@ _feroxbuster() {
|
||||
return 0
|
||||
;;
|
||||
--resume-from)
|
||||
local oldifs
|
||||
if [ -n "${IFS+x}" ]; then
|
||||
oldifs="$IFS"
|
||||
fi
|
||||
IFS=$'\n'
|
||||
COMPREPLY=($(compgen -f "${cur}"))
|
||||
if [ -n "${oldifs+x}" ]; then
|
||||
IFS="$oldifs"
|
||||
fi
|
||||
if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then
|
||||
compopt -o filenames
|
||||
fi
|
||||
return 0
|
||||
;;
|
||||
--request-file)
|
||||
local oldifs
|
||||
if [ -n "${IFS+x}" ]; then
|
||||
oldifs="$IFS"
|
||||
fi
|
||||
IFS=$'\n'
|
||||
COMPREPLY=($(compgen -f "${cur}"))
|
||||
if [ -n "${oldifs+x}" ]; then
|
||||
IFS="$oldifs"
|
||||
fi
|
||||
if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then
|
||||
compopt -o filenames
|
||||
fi
|
||||
return 0
|
||||
;;
|
||||
--proxy)
|
||||
@@ -113,6 +139,10 @@ _feroxbuster() {
|
||||
COMPREPLY=($(compgen -f "${cur}"))
|
||||
return 0
|
||||
;;
|
||||
--protocol)
|
||||
COMPREPLY=($(compgen -f "${cur}"))
|
||||
return 0
|
||||
;;
|
||||
--dont-scan)
|
||||
COMPREPLY=($(compgen -f "${cur}"))
|
||||
return 0
|
||||
@@ -178,15 +208,48 @@ _feroxbuster() {
|
||||
return 0
|
||||
;;
|
||||
--server-certs)
|
||||
local oldifs
|
||||
if [ -n "${IFS+x}" ]; then
|
||||
oldifs="$IFS"
|
||||
fi
|
||||
IFS=$'\n'
|
||||
COMPREPLY=($(compgen -f "${cur}"))
|
||||
if [ -n "${oldifs+x}" ]; then
|
||||
IFS="$oldifs"
|
||||
fi
|
||||
if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then
|
||||
compopt -o filenames
|
||||
fi
|
||||
return 0
|
||||
;;
|
||||
--client-cert)
|
||||
local oldifs
|
||||
if [ -n "${IFS+x}" ]; then
|
||||
oldifs="$IFS"
|
||||
fi
|
||||
IFS=$'\n'
|
||||
COMPREPLY=($(compgen -f "${cur}"))
|
||||
if [ -n "${oldifs+x}" ]; then
|
||||
IFS="$oldifs"
|
||||
fi
|
||||
if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then
|
||||
compopt -o filenames
|
||||
fi
|
||||
return 0
|
||||
;;
|
||||
--client-key)
|
||||
local oldifs
|
||||
if [ -n "${IFS+x}" ]; then
|
||||
oldifs="$IFS"
|
||||
fi
|
||||
IFS=$'\n'
|
||||
COMPREPLY=($(compgen -f "${cur}"))
|
||||
if [ -n "${oldifs+x}" ]; then
|
||||
IFS="$oldifs"
|
||||
fi
|
||||
if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then
|
||||
compopt -o filenames
|
||||
fi
|
||||
return 0
|
||||
;;
|
||||
--threads)
|
||||
@@ -226,10 +289,40 @@ _feroxbuster() {
|
||||
return 0
|
||||
;;
|
||||
--wordlist)
|
||||
local oldifs
|
||||
if [ -n "${IFS+x}" ]; then
|
||||
oldifs="$IFS"
|
||||
fi
|
||||
IFS=$'\n'
|
||||
COMPREPLY=($(compgen -f "${cur}"))
|
||||
if [ -n "${oldifs+x}" ]; then
|
||||
IFS="$oldifs"
|
||||
fi
|
||||
if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then
|
||||
compopt -o filenames
|
||||
fi
|
||||
return 0
|
||||
;;
|
||||
-w)
|
||||
local oldifs
|
||||
if [ -n "${IFS+x}" ]; then
|
||||
oldifs="$IFS"
|
||||
fi
|
||||
IFS=$'\n'
|
||||
COMPREPLY=($(compgen -f "${cur}"))
|
||||
if [ -n "${oldifs+x}" ]; then
|
||||
IFS="$oldifs"
|
||||
fi
|
||||
if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then
|
||||
compopt -o filenames
|
||||
fi
|
||||
return 0
|
||||
;;
|
||||
--collect-backups)
|
||||
COMPREPLY=($(compgen -f "${cur}"))
|
||||
return 0
|
||||
;;
|
||||
-B)
|
||||
COMPREPLY=($(compgen -f "${cur}"))
|
||||
return 0
|
||||
;;
|
||||
@@ -242,14 +335,51 @@ _feroxbuster() {
|
||||
return 0
|
||||
;;
|
||||
--output)
|
||||
local oldifs
|
||||
if [ -n "${IFS+x}" ]; then
|
||||
oldifs="$IFS"
|
||||
fi
|
||||
IFS=$'\n'
|
||||
COMPREPLY=($(compgen -f "${cur}"))
|
||||
if [ -n "${oldifs+x}" ]; then
|
||||
IFS="$oldifs"
|
||||
fi
|
||||
if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then
|
||||
compopt -o filenames
|
||||
fi
|
||||
return 0
|
||||
;;
|
||||
-o)
|
||||
local oldifs
|
||||
if [ -n "${IFS+x}" ]; then
|
||||
oldifs="$IFS"
|
||||
fi
|
||||
IFS=$'\n'
|
||||
COMPREPLY=($(compgen -f "${cur}"))
|
||||
if [ -n "${oldifs+x}" ]; then
|
||||
IFS="$oldifs"
|
||||
fi
|
||||
if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then
|
||||
compopt -o filenames
|
||||
fi
|
||||
return 0
|
||||
;;
|
||||
--debug-log)
|
||||
local oldifs
|
||||
if [ -n "${IFS+x}" ]; then
|
||||
oldifs="$IFS"
|
||||
fi
|
||||
IFS=$'\n'
|
||||
COMPREPLY=($(compgen -f "${cur}"))
|
||||
if [ -n "${oldifs+x}" ]; then
|
||||
IFS="$oldifs"
|
||||
fi
|
||||
if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then
|
||||
compopt -o filenames
|
||||
fi
|
||||
return 0
|
||||
;;
|
||||
--limit-bars)
|
||||
COMPREPLY=($(compgen -f "${cur}"))
|
||||
return 0
|
||||
;;
|
||||
@@ -263,4 +393,8 @@ _feroxbuster() {
|
||||
esac
|
||||
}
|
||||
|
||||
complete -F _feroxbuster -o bashdefault -o default -o plusdirs feroxbuster
|
||||
if [[ "${BASH_VERSINFO[0]}" -eq 4 && "${BASH_VERSINFO[1]}" -ge 4 || "${BASH_VERSINFO[0]}" -gt 4 ]]; then
|
||||
complete -F _feroxbuster -o nosort -o bashdefault -o default -o plusdirs feroxbuster
|
||||
else
|
||||
complete -F _feroxbuster -o bashdefault -o default -o plusdirs feroxbuster
|
||||
fi
|
||||
|
||||
@@ -18,19 +18,20 @@ set edit:completion:arg-completer[feroxbuster] = {|@words|
|
||||
}
|
||||
var completions = [
|
||||
&'feroxbuster'= {
|
||||
cand -u 'The target URL (required, unless [--stdin || --resume-from] used)'
|
||||
cand --url 'The target URL (required, unless [--stdin || --resume-from] used)'
|
||||
cand -u 'The target URL (required, unless [--stdin || --resume-from || --request-file] used)'
|
||||
cand --url 'The target URL (required, unless [--stdin || --resume-from || --request-file] used)'
|
||||
cand --resume-from 'State file from which to resume a partially complete scan (ex. --resume-from ferox-1606586780.state)'
|
||||
cand --request-file 'Raw HTTP request file to use as a template for all requests'
|
||||
cand -p 'Proxy to use for requests (ex: http(s)://host:port, socks5(h)://host:port)'
|
||||
cand --proxy 'Proxy to use for requests (ex: http(s)://host:port, socks5(h)://host:port)'
|
||||
cand -P 'Send only unfiltered requests through a Replay Proxy, instead of all requests'
|
||||
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.0)'
|
||||
cand --user-agent 'Sets the User-Agent (default: feroxbuster/2.10.0)'
|
||||
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 -a 'Sets the User-Agent (default: feroxbuster/2.11.0)'
|
||||
cand --user-agent 'Sets the User-Agent (default: feroxbuster/2.11.0)'
|
||||
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)'
|
||||
cand --methods 'Which HTTP request method(s) should be sent (default: GET)'
|
||||
cand --data 'Request''s Body; can read data from a file if input starts with an @ (ex: @post.bin)'
|
||||
@@ -40,11 +41,12 @@ set edit:completion:arg-completer[feroxbuster] = {|@words|
|
||||
cand --cookies 'Specify HTTP cookies to be used in each request (ex: -b stuff=things)'
|
||||
cand -Q 'Request''s URL query parameters (ex: -Q token=stuff -Q secret=key)'
|
||||
cand --query 'Request''s URL query parameters (ex: -Q token=stuff -Q secret=key)'
|
||||
cand --protocol 'Specify the protocol to use when targeting via --request-file or --url with domain only (default: https)'
|
||||
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)'
|
||||
@@ -70,16 +72,19 @@ set edit:completion:arg-completer[feroxbuster] = {|@words|
|
||||
cand --time-limit 'Limit total run time of all scans (ex: --time-limit 10m)'
|
||||
cand -w 'Path or URL of the wordlist'
|
||||
cand --wordlist 'Path or URL of the wordlist'
|
||||
cand -B 'Automatically request likely backup extensions for "found" urls (default: ~, .bak, .bak2, .old, .1)'
|
||||
cand --collect-backups 'Automatically request likely backup extensions for "found" urls (default: ~, .bak, .bak2, .old, .1)'
|
||||
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)'
|
||||
cand --output 'Output file to write results to (use w/ --json for JSON entries)'
|
||||
cand --debug-log 'Output file to write log entries (use w/ --json for JSON entries)'
|
||||
cand --limit-bars 'Number of directory scan bars to show at any given time (default: no limit)'
|
||||
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 --auto-tune, --collect-words, and --collect-backups to true'
|
||||
cand --thorough 'Use the same settings as --smart and set --collect-extensions to true'
|
||||
cand --thorough 'Use the same settings as --smart and set --collect-extensions and --scan-dir-listings to true'
|
||||
cand -A 'Use a random User-Agent'
|
||||
cand --random-agent 'Use a random User-Agent'
|
||||
cand -f 'Append / to each request''s URL'
|
||||
@@ -100,13 +105,12 @@ set edit:completion:arg-completer[feroxbuster] = {|@words|
|
||||
cand --dont-filter 'Don''t auto-filter wildcard responses'
|
||||
cand -E 'Automatically discover extensions and add them to --extensions (unless they''re in --dont-collect)'
|
||||
cand --collect-extensions 'Automatically discover extensions and add them to --extensions (unless they''re in --dont-collect)'
|
||||
cand -B 'Automatically request likely backup extensions for "found" urls'
|
||||
cand --collect-backups 'Automatically request likely backup extensions for "found" urls'
|
||||
cand -g 'Automatically discover important words from within responses and add them to the wordlist'
|
||||
cand --collect-words 'Automatically discover important words from within responses and add them to the wordlist'
|
||||
cand --scan-dir-listings 'Force scans to recurse into directory listings'
|
||||
cand -v 'Increase verbosity level (use -vv or more for greater effect. [CAUTION] 4 -v''s is probably too much)'
|
||||
cand --verbosity 'Increase verbosity level (use -vv or more for greater effect. [CAUTION] 4 -v''s is probably too much)'
|
||||
cand --silent 'Only print URLs + turn off logging (good for piping a list of urls to other commands)'
|
||||
cand --silent 'Only print URLs (or JSON w/ --json) + turn off logging (good for piping a list of urls to other commands)'
|
||||
cand -q 'Hide progress bars and banner (good for tmux windows w/ notifications)'
|
||||
cand --quiet 'Hide progress bars and banner (good for tmux windows w/ notifications)'
|
||||
cand --json 'Emit JSON logs to --output and --debug-log instead of normal text'
|
||||
|
||||
@@ -1,46 +1,65 @@
|
||||
complete -c feroxbuster -n "__fish_use_subcommand" -s w -l wordlist -d 'Path to the wordlist'
|
||||
complete -c feroxbuster -n "__fish_use_subcommand" -s u -l url -d 'The target URL(s) (required, unless --stdin used)'
|
||||
complete -c feroxbuster -n "__fish_use_subcommand" -s t -l threads -d 'Number of concurrent threads (default: 50)'
|
||||
complete -c feroxbuster -n "__fish_use_subcommand" -s d -l depth -d 'Maximum recursion depth, a depth of 0 is infinite recursion (default: 4)'
|
||||
complete -c feroxbuster -n "__fish_use_subcommand" -s T -l timeout -d 'Number of seconds before a request times out (default: 7)'
|
||||
complete -c feroxbuster -n "__fish_use_subcommand" -s p -l proxy -d 'Proxy to use for requests (ex: http(s)://host:port, socks5(h)://host:port)'
|
||||
complete -c feroxbuster -n "__fish_use_subcommand" -s P -l replay-proxy -d 'Send only unfiltered requests through a Replay Proxy, instead of all requests'
|
||||
complete -c feroxbuster -n "__fish_use_subcommand" -s R -l replay-codes -d 'Status Codes to send through a Replay Proxy when found (default: --status-codes value)'
|
||||
complete -c feroxbuster -n "__fish_use_subcommand" -s s -l status-codes -d 'Status Codes to include (allow list) (default: 200 204 301 302 307 308 401 403 405)'
|
||||
complete -c feroxbuster -n "__fish_use_subcommand" -s o -l output -d 'Output file to write results to (use w/ --json for JSON entries)'
|
||||
complete -c feroxbuster -n "__fish_use_subcommand" -l resume-from -d 'State file from which to resume a partially complete scan (ex. --resume-from ferox-1606586780.state)'
|
||||
complete -c feroxbuster -n "__fish_use_subcommand" -l debug-log -d 'Output file to write log entries (use w/ --json for JSON entries)'
|
||||
complete -c feroxbuster -n "__fish_use_subcommand" -s a -l user-agent -d 'Sets the User-Agent (default: feroxbuster/VERSION)'
|
||||
complete -c feroxbuster -n "__fish_use_subcommand" -s x -l extensions -d 'File extension(s) to search for (ex: -x php -x pdf js)'
|
||||
complete -c feroxbuster -n "__fish_use_subcommand" -s m -l methods -d 'HTTP request method(s) (default: GET)'
|
||||
complete -c feroxbuster -n "__fish_use_subcommand" -l data -d 'HTTP Body data; can read data from a file if input starts with an @ (ex: @post.bin)'
|
||||
complete -c feroxbuster -n "__fish_use_subcommand" -l dont-scan -d 'URL(s) or Regex Pattern(s) to exclude from recursion/scans'
|
||||
complete -c feroxbuster -n "__fish_use_subcommand" -s H -l headers -d 'Specify HTTP headers (ex: -H Header:val \'stuff: things\')'
|
||||
complete -c feroxbuster -n "__fish_use_subcommand" -s b -l cookies -d 'Specify HTTP cookies (ex: -b stuff=things)'
|
||||
complete -c feroxbuster -n "__fish_use_subcommand" -s Q -l query -d 'Specify URL query parameters (ex: -Q token=stuff -Q secret=key)'
|
||||
complete -c feroxbuster -n "__fish_use_subcommand" -s S -l filter-size -d 'Filter out messages of a particular size (ex: -S 5120 -S 4927,1970)'
|
||||
complete -c feroxbuster -n "__fish_use_subcommand" -s X -l filter-regex -d 'Filter out messages via regular expression matching on the response\'s body (ex: -X \'^ignore me$\')'
|
||||
complete -c feroxbuster -n "__fish_use_subcommand" -s W -l filter-words -d 'Filter out messages of a particular word count (ex: -W 312 -W 91,82)'
|
||||
complete -c feroxbuster -n "__fish_use_subcommand" -s N -l filter-lines -d 'Filter out messages of a particular line count (ex: -N 20 -N 31,30)'
|
||||
complete -c feroxbuster -n "__fish_use_subcommand" -s C -l filter-status -d 'Filter out status codes (deny list) (ex: -C 200 -C 401)'
|
||||
complete -c feroxbuster -n "__fish_use_subcommand" -l filter-similar-to -d 'Filter out pages that are similar to the given page (ex. --filter-similar-to http://site.xyz/soft404)'
|
||||
complete -c feroxbuster -n "__fish_use_subcommand" -s L -l scan-limit -d 'Limit total number of concurrent scans (default: 0, i.e. no limit)'
|
||||
complete -c feroxbuster -n "__fish_use_subcommand" -l parallel -d 'Run parallel feroxbuster instances (one child process per url passed via stdin)'
|
||||
complete -c feroxbuster -n "__fish_use_subcommand" -l rate-limit -d 'Limit number of requests per second (per directory) (default: 0, i.e. no limit)'
|
||||
complete -c feroxbuster -n "__fish_use_subcommand" -l time-limit -d 'Limit total run time of all scans (ex: --time-limit 10m)'
|
||||
complete -c feroxbuster -n "__fish_use_subcommand" -s v -l verbosity -d 'Increase verbosity level (use -vv or more for greater effect. [CAUTION] 4 -v\'s is probably too much)'
|
||||
complete -c feroxbuster -n "__fish_use_subcommand" -l silent -d 'Only print URLs + turn off logging (good for piping a list of urls to other commands)'
|
||||
complete -c feroxbuster -n "__fish_use_subcommand" -s q -l quiet -d 'Hide progress bars and banner (good for tmux windows w/ notifications)'
|
||||
complete -c feroxbuster -n "__fish_use_subcommand" -l auto-tune -d 'Automatically lower scan rate when an excessive amount of errors are encountered'
|
||||
complete -c feroxbuster -n "__fish_use_subcommand" -l auto-bail -d 'Automatically stop scanning when an excessive amount of errors are encountered'
|
||||
complete -c feroxbuster -n "__fish_use_subcommand" -l json -d 'Emit JSON logs to --output and --debug-log instead of normal text'
|
||||
complete -c feroxbuster -n "__fish_use_subcommand" -s D -l dont-filter -d 'Don\'t auto-filter wildcard responses'
|
||||
complete -c feroxbuster -n "__fish_use_subcommand" -s A -l random-agent -d 'Use a random User-Agent'
|
||||
complete -c feroxbuster -n "__fish_use_subcommand" -s r -l redirects -d 'Follow redirects'
|
||||
complete -c feroxbuster -n "__fish_use_subcommand" -s k -l insecure -d 'Disables TLS certificate validation'
|
||||
complete -c feroxbuster -n "__fish_use_subcommand" -s n -l no-recursion -d 'Do not scan recursively'
|
||||
complete -c feroxbuster -n "__fish_use_subcommand" -s f -l add-slash -d 'Append / to each request'
|
||||
complete -c feroxbuster -n "__fish_use_subcommand" -l stdin -d 'Read url(s) from STDIN'
|
||||
complete -c feroxbuster -n "__fish_use_subcommand" -s e -l extract-links -d 'Extract links from response body (html, javascript, etc...); make new requests based on findings (default: false)'
|
||||
complete -c feroxbuster -n "__fish_use_subcommand" -s h -l help -d 'Prints help information'
|
||||
complete -c feroxbuster -n "__fish_use_subcommand" -s V -l version -d 'Prints version information'
|
||||
complete -c feroxbuster -s u -l url -d 'The target URL (required, unless [--stdin || --resume-from || --request-file] used)' -r -f
|
||||
complete -c feroxbuster -l resume-from -d 'State file from which to resume a partially complete scan (ex. --resume-from ferox-1606586780.state)' -r -F
|
||||
complete -c feroxbuster -l request-file -d 'Raw HTTP request file to use as a template for all requests' -r -F
|
||||
complete -c feroxbuster -s p -l proxy -d 'Proxy to use for requests (ex: http(s)://host:port, socks5(h)://host:port)' -r -f
|
||||
complete -c feroxbuster -s P -l replay-proxy -d 'Send only unfiltered requests through a Replay Proxy, instead of all requests' -r -f
|
||||
complete -c feroxbuster -s R -l replay-codes -d 'Status Codes to send through a Replay Proxy when found (default: --status-codes value)' -r
|
||||
complete -c feroxbuster -s a -l user-agent -d 'Sets the User-Agent (default: feroxbuster/2.11.0)' -r
|
||||
complete -c feroxbuster -s x -l extensions -d '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)' -r
|
||||
complete -c feroxbuster -s m -l methods -d 'Which HTTP request method(s) should be sent (default: GET)' -r
|
||||
complete -c feroxbuster -l data -d 'Request\'s Body; can read data from a file if input starts with an @ (ex: @post.bin)' -r
|
||||
complete -c feroxbuster -s H -l headers -d 'Specify HTTP headers to be used in each request (ex: -H Header:val -H \'stuff: things\')' -r
|
||||
complete -c feroxbuster -s b -l cookies -d 'Specify HTTP cookies to be used in each request (ex: -b stuff=things)' -r
|
||||
complete -c feroxbuster -s Q -l query -d 'Request\'s URL query parameters (ex: -Q token=stuff -Q secret=key)' -r
|
||||
complete -c feroxbuster -l protocol -d 'Specify the protocol to use when targeting via --request-file or --url with domain only (default: https)' -r
|
||||
complete -c feroxbuster -l dont-scan -d 'URL(s) or Regex Pattern(s) to exclude from recursion/scans' -r
|
||||
complete -c feroxbuster -s S -l filter-size -d 'Filter out messages of a particular size (ex: -S 5120 -S 4927,1970)' -r
|
||||
complete -c feroxbuster -s X -l filter-regex -d 'Filter out messages via regular expression matching on the response\'s body/headers (ex: -X \'^ignore me$\')' -r
|
||||
complete -c feroxbuster -s W -l filter-words -d 'Filter out messages of a particular word count (ex: -W 312 -W 91,82)' -r
|
||||
complete -c feroxbuster -s N -l filter-lines -d 'Filter out messages of a particular line count (ex: -N 20 -N 31,30)' -r
|
||||
complete -c feroxbuster -s C -l filter-status -d 'Filter out status codes (deny list) (ex: -C 200 -C 401)' -r
|
||||
complete -c feroxbuster -l filter-similar-to -d 'Filter out pages that are similar to the given page (ex. --filter-similar-to http://site.xyz/soft404)' -r -f
|
||||
complete -c feroxbuster -s s -l status-codes -d 'Status Codes to include (allow list) (default: All Status Codes)' -r
|
||||
complete -c feroxbuster -s T -l timeout -d 'Number of seconds before a client\'s request times out (default: 7)' -r
|
||||
complete -c feroxbuster -l server-certs -d 'Add custom root certificate(s) for servers with unknown certificates' -r -F
|
||||
complete -c feroxbuster -l client-cert -d 'Add a PEM encoded certificate for mutual authentication (mTLS)' -r -F
|
||||
complete -c feroxbuster -l client-key -d 'Add a PEM encoded private key for mutual authentication (mTLS)' -r -F
|
||||
complete -c feroxbuster -s t -l threads -d 'Number of concurrent threads (default: 50)' -r
|
||||
complete -c feroxbuster -s d -l depth -d 'Maximum recursion depth, a depth of 0 is infinite recursion (default: 4)' -r
|
||||
complete -c feroxbuster -s L -l scan-limit -d 'Limit total number of concurrent scans (default: 0, i.e. no limit)' -r
|
||||
complete -c feroxbuster -l parallel -d 'Run parallel feroxbuster instances (one child process per url passed via stdin)' -r
|
||||
complete -c feroxbuster -l rate-limit -d 'Limit number of requests per second (per directory) (default: 0, i.e. no limit)' -r
|
||||
complete -c feroxbuster -l time-limit -d 'Limit total run time of all scans (ex: --time-limit 10m)' -r
|
||||
complete -c feroxbuster -s w -l wordlist -d 'Path or URL of the wordlist' -r -F
|
||||
complete -c feroxbuster -s B -l collect-backups -d 'Automatically request likely backup extensions for "found" urls (default: ~, .bak, .bak2, .old, .1)' -r
|
||||
complete -c feroxbuster -s I -l dont-collect -d 'File extension(s) to Ignore while collecting extensions (only used with --collect-extensions)' -r
|
||||
complete -c feroxbuster -s o -l output -d 'Output file to write results to (use w/ --json for JSON entries)' -r -F
|
||||
complete -c feroxbuster -l debug-log -d 'Output file to write log entries (use w/ --json for JSON entries)' -r -F
|
||||
complete -c feroxbuster -l limit-bars -d 'Number of directory scan bars to show at any given time (default: no limit)' -r
|
||||
complete -c feroxbuster -l stdin -d 'Read url(s) from STDIN'
|
||||
complete -c feroxbuster -l burp -d 'Set --proxy to http://127.0.0.1:8080 and set --insecure to true'
|
||||
complete -c feroxbuster -l burp-replay -d 'Set --replay-proxy to http://127.0.0.1:8080 and set --insecure to true'
|
||||
complete -c feroxbuster -l smart -d 'Set --auto-tune, --collect-words, and --collect-backups to true'
|
||||
complete -c feroxbuster -l thorough -d 'Use the same settings as --smart and set --collect-extensions and --scan-dir-listings to true'
|
||||
complete -c feroxbuster -s A -l random-agent -d 'Use a random User-Agent'
|
||||
complete -c feroxbuster -s f -l add-slash -d 'Append / to each request\'s URL'
|
||||
complete -c feroxbuster -s r -l redirects -d 'Allow client to follow redirects'
|
||||
complete -c feroxbuster -s k -l insecure -d 'Disables TLS certificate validation in the client'
|
||||
complete -c feroxbuster -s n -l no-recursion -d 'Do not scan recursively'
|
||||
complete -c feroxbuster -l force-recursion -d 'Force recursion attempts on all \'found\' endpoints (still respects recursion depth)'
|
||||
complete -c feroxbuster -s e -l extract-links -d 'Extract links from response body (html, javascript, etc...); make new requests based on findings (default: true)'
|
||||
complete -c feroxbuster -l dont-extract-links -d 'Don\'t extract links from response body (html, javascript, etc...)'
|
||||
complete -c feroxbuster -l auto-tune -d 'Automatically lower scan rate when an excessive amount of errors are encountered'
|
||||
complete -c feroxbuster -l auto-bail -d 'Automatically stop scanning when an excessive amount of errors are encountered'
|
||||
complete -c feroxbuster -s D -l dont-filter -d 'Don\'t auto-filter wildcard responses'
|
||||
complete -c feroxbuster -s E -l collect-extensions -d 'Automatically discover extensions and add them to --extensions (unless they\'re in --dont-collect)'
|
||||
complete -c feroxbuster -s g -l collect-words -d 'Automatically discover important words from within responses and add them to the wordlist'
|
||||
complete -c feroxbuster -l scan-dir-listings -d 'Force scans to recurse into directory listings'
|
||||
complete -c feroxbuster -s v -l verbosity -d 'Increase verbosity level (use -vv or more for greater effect. [CAUTION] 4 -v\'s is probably too much)'
|
||||
complete -c feroxbuster -l silent -d 'Only print URLs (or JSON w/ --json) + turn off logging (good for piping a list of urls to other commands)'
|
||||
complete -c feroxbuster -s q -l quiet -d 'Hide progress bars and banner (good for tmux windows w/ notifications)'
|
||||
complete -c feroxbuster -l json -d 'Emit JSON logs to --output and --debug-log instead of normal text'
|
||||
complete -c feroxbuster -l no-state -d 'Disable state output file (*.state)'
|
||||
complete -c feroxbuster -s U -l update -d 'Update feroxbuster to the latest version'
|
||||
complete -c feroxbuster -s h -l help -d 'Print help (see more with \'--help\')'
|
||||
complete -c feroxbuster -s V -l version -d 'Print version'
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
use super::entry::BannerEntry;
|
||||
use crate::{
|
||||
client,
|
||||
config::Configuration,
|
||||
event_handlers::Handles,
|
||||
utils::{logged_request, parse_url_with_raw_path, status_colorizer},
|
||||
utils::{make_request, parse_url_with_raw_path, status_colorizer},
|
||||
DEFAULT_IGNORED_EXTENSIONS, DEFAULT_METHOD, DEFAULT_STATUS_CODES, VERSION,
|
||||
};
|
||||
use anyhow::{bail, Result};
|
||||
use console::{style, Emoji};
|
||||
use serde_json::Value;
|
||||
use std::collections::HashMap;
|
||||
use std::{io::Write, sync::Arc};
|
||||
|
||||
/// Url used to query github's api; specifically used to look for the latest tagged release name
|
||||
@@ -174,6 +176,15 @@ pub struct Banner {
|
||||
|
||||
/// represents Configuration.collect_words
|
||||
force_recursion: BannerEntry,
|
||||
|
||||
/// represents Configuration.protocol
|
||||
protocol: BannerEntry,
|
||||
|
||||
/// represents Configuration.scan_dir_listings
|
||||
scan_dir_listings: BannerEntry,
|
||||
|
||||
/// represents Configuration.limit_bars
|
||||
limit_bars: BannerEntry,
|
||||
}
|
||||
|
||||
/// implementation of Banner
|
||||
@@ -318,6 +329,12 @@ impl Banner {
|
||||
BannerEntry::new("🚫", "Do Not Recurse", &config.no_recursion.to_string())
|
||||
};
|
||||
|
||||
let protocol = if config.protocol.to_lowercase() == "http" {
|
||||
BannerEntry::new("🔓", "Default Protocol", &config.protocol)
|
||||
} else {
|
||||
BannerEntry::new("🔒", "Default Protocol", &config.protocol)
|
||||
};
|
||||
|
||||
let scan_limit = BannerEntry::new(
|
||||
"🦥",
|
||||
"Concurrent Scan Limit",
|
||||
@@ -329,6 +346,11 @@ impl Banner {
|
||||
let replay_proxy = BannerEntry::new("🎥", "Replay Proxy", &config.replay_proxy);
|
||||
let auto_tune = BannerEntry::new("🎶", "Auto Tune", &config.auto_tune.to_string());
|
||||
let auto_bail = BannerEntry::new("🙅", "Auto Bail", &config.auto_bail.to_string());
|
||||
let scan_dir_listings = BannerEntry::new(
|
||||
"📂",
|
||||
"Scan Dir Listings",
|
||||
&config.scan_dir_listings.to_string(),
|
||||
);
|
||||
let cfg = BannerEntry::new("💉", "Config File", &config.config);
|
||||
let proxy = BannerEntry::new("💎", "Proxy", &config.proxy);
|
||||
let server_certs = BannerEntry::new(
|
||||
@@ -339,6 +361,8 @@ impl Banner {
|
||||
let client_cert = BannerEntry::new("🏅", "Client Certificate", &config.client_cert);
|
||||
let client_key = BannerEntry::new("🔑", "Client Key", &config.client_key);
|
||||
let threads = BannerEntry::new("🚀", "Threads", &config.threads.to_string());
|
||||
let limit_bars =
|
||||
BannerEntry::new("📊", "Limit Dir Scan Bars", &config.limit_bars.to_string());
|
||||
let wordlist = BannerEntry::new("📖", "Wordlist", &config.wordlist);
|
||||
let timeout = BannerEntry::new("💥", "Timeout (secs)", &config.timeout.to_string());
|
||||
let user_agent = BannerEntry::new("🦡", "User-Agent", &config.user_agent);
|
||||
@@ -453,6 +477,9 @@ impl Banner {
|
||||
collect_words,
|
||||
dont_collect,
|
||||
config: cfg,
|
||||
scan_dir_listings,
|
||||
protocol,
|
||||
limit_bars,
|
||||
version: VERSION.to_string(),
|
||||
update_status: UpdateStatus::Unknown,
|
||||
}
|
||||
@@ -498,7 +525,34 @@ by Ben "epi" Risher {} ver: {}"#,
|
||||
|
||||
let api_url = parse_url_with_raw_path(url)?;
|
||||
|
||||
let result = logged_request(&api_url, DEFAULT_METHOD, None, handles.clone()).await?;
|
||||
// we don't want to leak sensitive header info / include auth headers
|
||||
// with the github api request, so we'll build a client specifically
|
||||
// for this task. thanks to @stuhlmann for the suggestion!
|
||||
let client = client::initialize(
|
||||
handles.config.timeout,
|
||||
"feroxbuster-update-check",
|
||||
handles.config.redirects,
|
||||
handles.config.insecure,
|
||||
&HashMap::new(),
|
||||
Some(&handles.config.proxy),
|
||||
&handles.config.server_certs,
|
||||
Some(&handles.config.client_cert),
|
||||
Some(&handles.config.client_key),
|
||||
)?;
|
||||
let level = handles.config.output_level;
|
||||
let tx_stats = handles.stats.tx.clone();
|
||||
|
||||
let result = make_request(
|
||||
&client,
|
||||
&api_url,
|
||||
DEFAULT_METHOD,
|
||||
None,
|
||||
level,
|
||||
&handles.config,
|
||||
tx_stats,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let body = result.text().await?;
|
||||
|
||||
let json_response: Value = serde_json::from_str(&body)?;
|
||||
@@ -566,6 +620,14 @@ by Ben "epi" Risher {} ver: {}"#,
|
||||
}
|
||||
|
||||
// followed by the maybe printed or variably displayed values
|
||||
if !config.request_file.is_empty() || !config.target_url.starts_with("http") {
|
||||
writeln!(&mut writer, "{}", self.protocol)?;
|
||||
}
|
||||
|
||||
if config.limit_bars > 0 {
|
||||
writeln!(&mut writer, "{}", self.limit_bars)?;
|
||||
}
|
||||
|
||||
if !config.config.is_empty() {
|
||||
writeln!(&mut writer, "{}", self.config)?;
|
||||
}
|
||||
@@ -633,6 +695,10 @@ by Ben "epi" Risher {} ver: {}"#,
|
||||
writeln!(&mut writer, "{}", self.output)?;
|
||||
}
|
||||
|
||||
if config.scan_dir_listings {
|
||||
writeln!(&mut writer, "{}", self.scan_dir_listings)?;
|
||||
}
|
||||
|
||||
if !config.debug_log.is_empty() {
|
||||
writeln!(&mut writer, "{}", self.debug_log)?;
|
||||
}
|
||||
|
||||
@@ -67,17 +67,19 @@ where
|
||||
}
|
||||
|
||||
if let (Some(cert_path), Some(key_path)) = (client_cert, client_key) {
|
||||
let cert = std::fs::read(cert_path)?;
|
||||
let key = std::fs::read(key_path)?;
|
||||
if !cert_path.is_empty() && !key_path.is_empty() {
|
||||
let cert = std::fs::read(cert_path)?;
|
||||
let key = std::fs::read(key_path)?;
|
||||
|
||||
let identity = reqwest::Identity::from_pkcs8_pem(&cert, &key).with_context(|| {
|
||||
format!(
|
||||
"either {} or {} are invalid; expecting PEM encoded certificate and key",
|
||||
cert_path, key_path
|
||||
)
|
||||
})?;
|
||||
let identity = reqwest::Identity::from_pkcs8_pem(&cert, &key).with_context(|| {
|
||||
format!(
|
||||
"either {} or {} are invalid; expecting PEM encoded certificate and key",
|
||||
cert_path, key_path
|
||||
)
|
||||
})?;
|
||||
|
||||
client = client.identity(identity);
|
||||
client = client.identity(identity);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(client.build()?)
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
use super::utils::{
|
||||
depth, extract_links, ignored_extensions, methods, report_and_exit, save_state,
|
||||
serialized_type, status_codes, threads, timeout, user_agent, wordlist, OutputLevel,
|
||||
backup_extensions, depth, determine_requester_policy, extract_links, ignored_extensions,
|
||||
methods, parse_request_file, report_and_exit, request_protocol, save_state, serialized_type,
|
||||
split_header, split_query, status_codes, threads, timeout, user_agent, wordlist, OutputLevel,
|
||||
RequesterPolicy,
|
||||
};
|
||||
|
||||
use crate::config::determine_output_level;
|
||||
use crate::config::utils::determine_requester_policy;
|
||||
use crate::{
|
||||
client, parser,
|
||||
scan_manager::resume_scan,
|
||||
traits::FeroxSerialize,
|
||||
utils::{fmt_err, parse_url_with_raw_path},
|
||||
utils::{fmt_err, module_colorizer, parse_url_with_raw_path, status_colorizer},
|
||||
DEFAULT_CONFIG_NAME,
|
||||
};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
@@ -21,7 +22,7 @@ use std::{
|
||||
collections::HashMap,
|
||||
env::{current_dir, current_exe},
|
||||
fs::read_to_string,
|
||||
path::PathBuf,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
/// macro helper to abstract away repetitive configuration updates
|
||||
@@ -318,6 +319,9 @@ pub struct Configuration {
|
||||
#[serde(default)]
|
||||
pub collect_backups: bool,
|
||||
|
||||
#[serde(default = "backup_extensions")]
|
||||
pub backup_extensions: Vec<String>,
|
||||
|
||||
/// Automatically discover important words from within responses and add them to the wordlist
|
||||
#[serde(default)]
|
||||
pub collect_words: bool,
|
||||
@@ -329,6 +333,22 @@ pub struct Configuration {
|
||||
/// Auto update app feature
|
||||
#[serde(skip)]
|
||||
pub update_app: bool,
|
||||
|
||||
/// whether to recurse into directory listings or not
|
||||
#[serde(default)]
|
||||
pub scan_dir_listings: bool,
|
||||
|
||||
/// path to a raw request file generated by burp or similar
|
||||
#[serde(skip)]
|
||||
pub request_file: String,
|
||||
|
||||
/// default request protocol
|
||||
#[serde(default = "request_protocol")]
|
||||
pub protocol: String,
|
||||
|
||||
/// number of directory scan bars to show at any given time, 0 is no limit
|
||||
#[serde(default)]
|
||||
pub limit_bars: usize,
|
||||
}
|
||||
|
||||
impl Default for Configuration {
|
||||
@@ -375,10 +395,12 @@ impl Default for Configuration {
|
||||
resumed: false,
|
||||
stdin: false,
|
||||
json: false,
|
||||
scan_dir_listings: false,
|
||||
verbosity: 0,
|
||||
scan_limit: 0,
|
||||
parallel: 0,
|
||||
rate_limit: 0,
|
||||
limit_bars: 0,
|
||||
add_slash: false,
|
||||
insecure: false,
|
||||
redirects: false,
|
||||
@@ -400,6 +422,8 @@ impl Default for Configuration {
|
||||
time_limit: String::new(),
|
||||
resume_from: String::new(),
|
||||
replay_proxy: String::new(),
|
||||
request_file: String::new(),
|
||||
protocol: request_protocol(),
|
||||
server_certs: Vec::new(),
|
||||
queries: Vec::new(),
|
||||
extensions: Vec::new(),
|
||||
@@ -418,6 +442,7 @@ impl Default for Configuration {
|
||||
threads: threads(),
|
||||
wordlist: wordlist(),
|
||||
dont_collect: ignored_extensions(),
|
||||
backup_extensions: backup_extensions(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -450,6 +475,7 @@ impl Configuration {
|
||||
/// - **extensions**: `None`
|
||||
/// - **collect_extensions**: `false`
|
||||
/// - **collect_backups**: `false`
|
||||
/// - **backup_extensions**: [`DEFAULT_BACKUP_EXTENSIONS`](constant.DEFAULT_BACKUP_EXTENSIONS.html)
|
||||
/// - **collect_words**: `false`
|
||||
/// - **dont_collect**: [`DEFAULT_IGNORED_EXTENSIONS`](constant.DEFAULT_RESPONSE_CODES.html)
|
||||
/// - **methods**: [`DEFAULT_METHOD`](constant.DEFAULT_METHOD.html)
|
||||
@@ -471,12 +497,16 @@ impl Configuration {
|
||||
/// - **depth**: `4` (maximum recursion depth)
|
||||
/// - **force_recursion**: `false` (still respects recursion depth)
|
||||
/// - **scan_limit**: `0` (no limit on concurrent scans imposed)
|
||||
/// - **limit_bars**: `0` (no limit on number of directory scan bars shown)
|
||||
/// - **parallel**: `0` (no limit on parallel scans imposed)
|
||||
/// - **rate_limit**: `0` (no limit on requests per second imposed)
|
||||
/// - **time_limit**: `None` (no limit on length of scan imposed)
|
||||
/// - **replay_proxy**: `None` (no limit on concurrent scans imposed)
|
||||
/// - **replay_codes**: [`DEFAULT_RESPONSE_CODES`](constant.DEFAULT_RESPONSE_CODES.html)
|
||||
/// - **update_app**: `false`
|
||||
/// - **scan_dir_listings**: `false`
|
||||
/// - **request_file**: `None`
|
||||
/// - **protocol**: `https`
|
||||
///
|
||||
/// After which, any values defined in a
|
||||
/// [ferox-config.toml](constant.DEFAULT_CONFIG_NAME.html) config file will override the
|
||||
@@ -550,6 +580,18 @@ impl Configuration {
|
||||
// merge the cli options into the config file options and return the result
|
||||
Self::merge_config(&mut config, cli_config);
|
||||
|
||||
// if the user provided a raw request file as the target, we'll need to parse out
|
||||
// the provided info and update the config with those values. This call needs to
|
||||
// come after the cli/config merge so we can allow the cli options to override
|
||||
// the raw request values (i.e. --headers "stuff: things" should override a "stuff"
|
||||
// header from the raw request).
|
||||
//
|
||||
// Additionally, this call needs to come before client rebuild so that the things
|
||||
// like user-agent can be set at the client level instead of the header level.
|
||||
if !config.request_file.is_empty() {
|
||||
parse_request_file(&mut config)?;
|
||||
}
|
||||
|
||||
// rebuild clients is the last step in either code branch
|
||||
Self::try_rebuild_clients(&mut config);
|
||||
|
||||
@@ -571,9 +613,7 @@ impl Configuration {
|
||||
// - current directory
|
||||
|
||||
// merge a config found at /etc/feroxbuster/ferox-config.toml
|
||||
let config_file = PathBuf::new()
|
||||
.join("/etc/feroxbuster")
|
||||
.join(DEFAULT_CONFIG_NAME);
|
||||
let config_file = Path::new("/etc/feroxbuster").join(DEFAULT_CONFIG_NAME);
|
||||
Self::parse_and_merge_config(config_file, config)?;
|
||||
|
||||
// merge a config found at ~/.config/feroxbuster/ferox-config.toml
|
||||
@@ -611,13 +651,16 @@ impl Configuration {
|
||||
update_config_with_num_type_if_present!(&mut config.depth, args, "depth", usize);
|
||||
update_config_with_num_type_if_present!(&mut config.scan_limit, args, "scan_limit", usize);
|
||||
update_config_with_num_type_if_present!(&mut config.rate_limit, args, "rate_limit", usize);
|
||||
update_config_with_num_type_if_present!(&mut config.limit_bars, args, "limit_bars", usize);
|
||||
update_config_if_present!(&mut config.wordlist, args, "wordlist", String);
|
||||
update_config_if_present!(&mut config.output, args, "output", String);
|
||||
update_config_if_present!(&mut config.debug_log, args, "debug_log", String);
|
||||
update_config_if_present!(&mut config.resume_from, args, "resume_from", String);
|
||||
update_config_if_present!(&mut config.request_file, args, "request_file", String);
|
||||
update_config_if_present!(&mut config.protocol, args, "protocol", 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") {
|
||||
@@ -641,7 +684,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") {
|
||||
@@ -655,9 +698,27 @@ impl Configuration {
|
||||
}
|
||||
|
||||
if let Some(arg) = args.get_many::<String>("extensions") {
|
||||
config.extensions = arg
|
||||
.map(|val| val.trim_start_matches('.').to_string())
|
||||
.collect();
|
||||
let mut extensions = Vec::<String>::new();
|
||||
for ext in arg {
|
||||
if let Some(stripped) = ext.strip_prefix('@') {
|
||||
let contents = read_to_string(stripped)
|
||||
.unwrap_or_else(|e| report_and_exit(&e.to_string()));
|
||||
let exts_from_file = contents.split('\n').filter_map(|s| {
|
||||
let trimmed = s.trim().trim_start_matches('.');
|
||||
|
||||
if trimmed.is_empty() || trimmed.starts_with('#') {
|
||||
None
|
||||
} else {
|
||||
Some(trimmed.to_string())
|
||||
}
|
||||
});
|
||||
|
||||
extensions.extend(exts_from_file);
|
||||
} else {
|
||||
extensions.push(ext.trim().trim_start_matches('.').to_string());
|
||||
}
|
||||
}
|
||||
config.extensions = extensions;
|
||||
}
|
||||
|
||||
if let Some(arg) = args.get_many::<String>("dont_collect") {
|
||||
@@ -683,6 +744,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") {
|
||||
@@ -770,18 +836,22 @@ impl Configuration {
|
||||
.collect();
|
||||
}
|
||||
|
||||
if came_from_cli!(args, "silent") {
|
||||
if came_from_cli!(args, "quiet") {
|
||||
config.quiet = true;
|
||||
config.output_level = OutputLevel::Quiet;
|
||||
}
|
||||
|
||||
if came_from_cli!(args, "silent") || (config.parallel > 0 && !config.quiet) {
|
||||
// the reason this is protected by an if statement:
|
||||
// consider a user specifying silent = true in ferox-config.toml
|
||||
// if the line below is outside of the if, we'd overwrite true with
|
||||
// false if no --silent is used on the command line
|
||||
config.silent = true;
|
||||
config.output_level = OutputLevel::Silent;
|
||||
}
|
||||
|
||||
if came_from_cli!(args, "quiet") {
|
||||
config.quiet = true;
|
||||
config.output_level = OutputLevel::Quiet;
|
||||
config.output_level = if config.json {
|
||||
OutputLevel::SilentJSON
|
||||
} else {
|
||||
OutputLevel::Silent
|
||||
};
|
||||
}
|
||||
|
||||
if came_from_cli!(args, "auto_tune")
|
||||
@@ -801,6 +871,10 @@ impl Configuration {
|
||||
config.save_state = false;
|
||||
}
|
||||
|
||||
if came_from_cli!(args, "scan_dir_listings") || came_from_cli!(args, "thorough") {
|
||||
config.scan_dir_listings = true;
|
||||
}
|
||||
|
||||
if came_from_cli!(args, "dont_filter") {
|
||||
config.dont_filter = true;
|
||||
}
|
||||
@@ -814,6 +888,20 @@ impl Configuration {
|
||||
|| came_from_cli!(args, "thorough")
|
||||
{
|
||||
config.collect_backups = true;
|
||||
config.backup_extensions = backup_extensions();
|
||||
|
||||
if came_from_cli!(args, "collect_backups") {
|
||||
if let Some(arg) = args.get_many::<String>("collect_backups") {
|
||||
let backup_exts = arg
|
||||
.map(|ext| ext.trim().to_string())
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
if !backup_exts.is_empty() {
|
||||
// have at least one cli backup, override the defaults
|
||||
config.backup_extensions = backup_exts;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if came_from_cli!(args, "collect_words")
|
||||
@@ -827,6 +915,25 @@ impl Configuration {
|
||||
// occurrences_of returns 0 if none are found; this is protected in
|
||||
// an if block for the same reason as the quiet option
|
||||
config.verbosity = args.get_count("verbosity");
|
||||
|
||||
// todo: starting on 2.11.0 (907-dont-skip-dir-listings), trace-level
|
||||
// logging started causing the following error:
|
||||
//
|
||||
// thread 'tokio-runtime-worker' has overflowed its stack
|
||||
// fatal runtime error: stack overflow
|
||||
// Aborted (core dumped)
|
||||
//
|
||||
// as a temporary fix, we'll disable trace logging to prevent the stack
|
||||
// overflow until I get time to investigate the root cause
|
||||
if config.verbosity > 3 {
|
||||
eprintln!(
|
||||
"{} {}: Trace level logging is disabled; setting log level to debug",
|
||||
status_colorizer("WRN"),
|
||||
module_colorizer("Configuration::parse_cli_args"),
|
||||
);
|
||||
|
||||
config.verbosity = 3;
|
||||
}
|
||||
}
|
||||
|
||||
if came_from_cli!(args, "no_recursion") {
|
||||
@@ -888,15 +995,11 @@ impl Configuration {
|
||||
|
||||
if let Some(headers) = args.get_many::<String>("headers") {
|
||||
for val in headers {
|
||||
let mut split_val = val.split(':');
|
||||
|
||||
// explicitly take first split value as header's name
|
||||
let name = split_val.next().unwrap().trim();
|
||||
|
||||
// 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());
|
||||
let Ok((name, value)) = split_header(val) else {
|
||||
log::warn!("Invalid header: {}", val);
|
||||
continue;
|
||||
};
|
||||
config.headers.insert(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -904,28 +1007,37 @@ 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("; "),
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(queries) = args.get_many::<String>("queries") {
|
||||
for val in queries {
|
||||
// same basic logic used as reading in the headers HashMap above
|
||||
let mut split_val = val.split('=');
|
||||
|
||||
let name = split_val.next().unwrap().trim();
|
||||
|
||||
let value = split_val.collect::<Vec<&str>>().join("=");
|
||||
|
||||
config.queries.push((name.to_string(), value.to_string()));
|
||||
let Ok((name, value)) = split_query(val) else {
|
||||
log::warn!("Invalid query string: {}", val);
|
||||
continue;
|
||||
};
|
||||
config.queries.push((name, value));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1012,7 +1124,7 @@ impl Configuration {
|
||||
|
||||
/// Given a configuration file's location and an instance of `Configuration`, read in
|
||||
/// the config file if found and update the current settings with the settings found therein
|
||||
fn parse_and_merge_config(config_file: PathBuf, mut config: &mut Self) -> Result<()> {
|
||||
fn parse_and_merge_config(config_file: PathBuf, config: &mut Self) -> Result<()> {
|
||||
if config_file.exists() {
|
||||
// save off a string version of the path before it goes out of scope
|
||||
let conf_str = config_file.to_str().unwrap_or("").to_string();
|
||||
@@ -1043,9 +1155,11 @@ impl Configuration {
|
||||
new.server_certs,
|
||||
Vec::<String>::new()
|
||||
);
|
||||
update_if_not_default!(&mut conf.json, new.json, false);
|
||||
update_if_not_default!(&mut conf.client_cert, new.client_cert, "");
|
||||
update_if_not_default!(&mut conf.client_key, new.client_key, "");
|
||||
update_if_not_default!(&mut conf.verbosity, new.verbosity, 0);
|
||||
update_if_not_default!(&mut conf.limit_bars, new.limit_bars, 0);
|
||||
update_if_not_default!(&mut conf.silent, new.silent, false);
|
||||
update_if_not_default!(&mut conf.quiet, new.quiet, false);
|
||||
update_if_not_default!(&mut conf.auto_bail, new.auto_bail, false);
|
||||
@@ -1054,7 +1168,7 @@ impl Configuration {
|
||||
update_if_not_default!(&mut conf.collect_backups, new.collect_backups, false);
|
||||
update_if_not_default!(&mut conf.collect_words, new.collect_words, false);
|
||||
// use updated quiet/silent values to determine output level; same for requester policy
|
||||
conf.output_level = determine_output_level(conf.quiet, conf.silent);
|
||||
conf.output_level = determine_output_level(conf.quiet, conf.silent, conf.json);
|
||||
conf.requester_policy = determine_requester_policy(conf.auto_tune, conf.auto_bail);
|
||||
update_if_not_default!(&mut conf.output, new.output, "");
|
||||
update_if_not_default!(&mut conf.redirects, new.redirects, false);
|
||||
@@ -1106,16 +1220,23 @@ impl Configuration {
|
||||
Vec::<u16>::new()
|
||||
);
|
||||
update_if_not_default!(&mut conf.dont_filter, new.dont_filter, false);
|
||||
update_if_not_default!(&mut conf.scan_dir_listings, new.scan_dir_listings, false);
|
||||
update_if_not_default!(&mut conf.scan_limit, new.scan_limit, 0);
|
||||
update_if_not_default!(&mut conf.parallel, new.parallel, 0);
|
||||
update_if_not_default!(&mut conf.rate_limit, new.rate_limit, 0);
|
||||
update_if_not_default!(&mut conf.replay_proxy, new.replay_proxy, "");
|
||||
update_if_not_default!(&mut conf.debug_log, new.debug_log, "");
|
||||
update_if_not_default!(&mut conf.resume_from, new.resume_from, "");
|
||||
update_if_not_default!(&mut conf.json, new.json, false);
|
||||
update_if_not_default!(&mut conf.request_file, new.request_file, "");
|
||||
update_if_not_default!(&mut conf.protocol, new.protocol, request_protocol());
|
||||
|
||||
update_if_not_default!(&mut conf.timeout, new.timeout, timeout());
|
||||
update_if_not_default!(&mut conf.user_agent, new.user_agent, user_agent());
|
||||
update_if_not_default!(
|
||||
&mut conf.backup_extensions,
|
||||
new.backup_extensions,
|
||||
backup_extensions()
|
||||
);
|
||||
update_if_not_default!(&mut conf.random_agent, new.random_agent, false);
|
||||
update_if_not_default!(&mut conf.threads, new.threads, threads());
|
||||
update_if_not_default!(&mut conf.depth, new.depth, depth());
|
||||
|
||||
@@ -49,6 +49,10 @@ fn setup_config_test() -> Configuration {
|
||||
json = true
|
||||
save_state = false
|
||||
depth = 1
|
||||
limit_bars = 3
|
||||
protocol = "http"
|
||||
request_file = "/some/request/file"
|
||||
scan_dir_listings = true
|
||||
force_recursion = true
|
||||
filter_size = [4120]
|
||||
filter_regex = ["^ignore me$"]
|
||||
@@ -59,6 +63,7 @@ fn setup_config_test() -> Configuration {
|
||||
server_certs = ["/some/cert.pem", "/some/other/cert.pem"]
|
||||
client_cert = "/some/client/cert.pem"
|
||||
client_key = "/some/client/key.pem"
|
||||
backup_extensions = [".save"]
|
||||
"#;
|
||||
let tmp_dir = TempDir::new().unwrap();
|
||||
let file = tmp_dir.path().join(DEFAULT_CONFIG_NAME);
|
||||
@@ -86,6 +91,7 @@ fn default_configuration() {
|
||||
assert_eq!(config.timeout, timeout());
|
||||
assert_eq!(config.verbosity, 0);
|
||||
assert_eq!(config.scan_limit, 0);
|
||||
assert_eq!(config.limit_bars, 0);
|
||||
assert!(!config.silent);
|
||||
assert!(!config.quiet);
|
||||
assert_eq!(config.output_level, OutputLevel::Default);
|
||||
@@ -106,6 +112,7 @@ fn default_configuration() {
|
||||
assert!(!config.collect_extensions);
|
||||
assert!(!config.collect_backups);
|
||||
assert!(!config.collect_words);
|
||||
assert!(!config.scan_dir_listings);
|
||||
assert!(config.regex_denylist.is_empty());
|
||||
assert_eq!(config.queries, Vec::new());
|
||||
assert_eq!(config.filter_size, Vec::<u64>::new());
|
||||
@@ -123,6 +130,9 @@ fn default_configuration() {
|
||||
assert_eq!(config.server_certs, Vec::<String>::new());
|
||||
assert_eq!(config.client_cert, String::new());
|
||||
assert_eq!(config.client_key, String::new());
|
||||
assert_eq!(config.backup_extensions, backup_extensions());
|
||||
assert_eq!(config.protocol, request_protocol());
|
||||
assert_eq!(config.request_file, String::new());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -258,6 +268,13 @@ fn config_reads_verbosity() {
|
||||
assert_eq!(config.verbosity, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// parse the test config and see that the value parsed is correct
|
||||
fn config_reads_limit_bars() {
|
||||
let config = setup_config_test();
|
||||
assert_eq!(config.limit_bars, 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// parse the test config and see that the value parsed is correct
|
||||
fn config_reads_output() {
|
||||
@@ -442,6 +459,27 @@ fn config_reads_time_limit() {
|
||||
assert_eq!(config.time_limit, "10m");
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// parse the test config and see that the value parsed is correct
|
||||
fn config_reads_scan_dir_listings() {
|
||||
let config = setup_config_test();
|
||||
assert!(config.scan_dir_listings);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// parse the test config and see that the value parsed is correct
|
||||
fn config_reads_protocol() {
|
||||
let config = setup_config_test();
|
||||
assert_eq!(config.protocol, "http");
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// parse the test config and see that the value parsed is correct
|
||||
fn config_reads_request_file() {
|
||||
let config = setup_config_test();
|
||||
assert_eq!(config.request_file, String::new());
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// parse the test config and see that the value parsed is correct
|
||||
fn config_reads_resume_from() {
|
||||
@@ -459,6 +497,13 @@ fn config_reads_server_certs() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// parse the test config and see that the value parsed is correct
|
||||
fn config_reads_backup_extensions() {
|
||||
let config = setup_config_test();
|
||||
assert_eq!(config.backup_extensions, [".save"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// parse the test config and see that the value parsed is correct
|
||||
fn config_reads_client_cert() {
|
||||
|
||||
1104
src/config/utils.rs
1104
src/config/utils.rs
File diff suppressed because it is too large
Load Diff
@@ -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>),
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ use anyhow::Result;
|
||||
use console::style;
|
||||
use crossterm::event::{self, Event, KeyCode};
|
||||
use std::{
|
||||
env::temp_dir,
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc,
|
||||
@@ -103,10 +104,36 @@ impl TermInputHandler {
|
||||
|
||||
// User didn't set the --no-state flag (so saved_state is still the default true)
|
||||
if handles.config.save_state {
|
||||
let state_file = open_file(&filename);
|
||||
let Ok(mut state_file) = open_file(&filename) else {
|
||||
// couldn't open the file, let the user know we're going to try again
|
||||
let error = format!(
|
||||
"❌ Could not save {}, falling back to {}",
|
||||
filename,
|
||||
temp_dir().to_string_lossy()
|
||||
);
|
||||
PROGRESS_PRINTER.println(error);
|
||||
|
||||
let mut buffered_file = state_file?;
|
||||
write_to(&state, &mut buffered_file, true)?;
|
||||
let temp_filename = temp_dir().join(&filename);
|
||||
|
||||
let Ok(mut state_file) = open_file(&temp_filename.to_string_lossy()) else {
|
||||
// couldn't open the fallback file, let the user know
|
||||
let error = format!("❌❌ Could not save {:?}, giving up...", temp_filename);
|
||||
PROGRESS_PRINTER.println(error);
|
||||
|
||||
log::trace!("exit: sigint_handler (failed to write)");
|
||||
std::process::exit(1);
|
||||
};
|
||||
|
||||
write_to(&state, &mut state_file, true)?;
|
||||
|
||||
let msg = format!("✅ Saved scan state to {:?}", temp_filename);
|
||||
PROGRESS_PRINTER.println(msg);
|
||||
|
||||
log::trace!("exit: sigint_handler (saved to temp folder)");
|
||||
std::process::exit(1);
|
||||
};
|
||||
|
||||
write_to(&state, &mut state_file, true)?;
|
||||
}
|
||||
|
||||
log::trace!("exit: sigint_handler (end of program)");
|
||||
|
||||
@@ -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) => {
|
||||
@@ -209,8 +211,12 @@ impl TermOutHandler {
|
||||
while let Some(command) = self.receiver.recv().await {
|
||||
match command {
|
||||
Command::Report(resp) => {
|
||||
self.process_response(tx_stats.clone(), resp, ProcessResponseCall::Recursive)
|
||||
.await?;
|
||||
if let Err(err) = self
|
||||
.process_response(tx_stats.clone(), resp, ProcessResponseCall::Recursive)
|
||||
.await
|
||||
{
|
||||
log::warn!("{}", err);
|
||||
}
|
||||
}
|
||||
Command::Sync(sender) => {
|
||||
sender.send(true).unwrap_or_default();
|
||||
@@ -328,6 +334,21 @@ impl TermOutHandler {
|
||||
)
|
||||
.await;
|
||||
|
||||
let Some(handles) = self.handles.as_ref() else {
|
||||
// shouldn't ever happen, but we'll log and return early if it does
|
||||
log::error!("handles were unexpectedly None, this shouldn't happen");
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
if handles
|
||||
.filters
|
||||
.data
|
||||
.should_filter_response(&ferox_response, tx_stats.clone())
|
||||
{
|
||||
// response was filtered for one reason or another, don't process it
|
||||
continue;
|
||||
}
|
||||
|
||||
self.process_response(
|
||||
tx_stats.clone(),
|
||||
Box::new(ferox_response),
|
||||
@@ -385,7 +406,7 @@ impl TermOutHandler {
|
||||
|
||||
if !filename.is_empty() {
|
||||
// append rules
|
||||
for suffix in ["~", ".bak", ".bak2", ".old", ".1"] {
|
||||
for suffix in &self.config.backup_extensions {
|
||||
self.add_new_url_to_vec(url, &format!("{filename}{suffix}"), &mut urls);
|
||||
}
|
||||
|
||||
|
||||
@@ -120,7 +120,10 @@ impl ScanHandler {
|
||||
pub fn initialize(handles: Arc<Handles>) -> (Joiner, ScanHandle) {
|
||||
log::trace!("enter: initialize");
|
||||
|
||||
let data = Arc::new(FeroxScans::new(handles.config.output_level));
|
||||
let data = Arc::new(FeroxScans::new(
|
||||
handles.config.output_level,
|
||||
handles.config.limit_bars,
|
||||
));
|
||||
let (tx, rx): FeroxChannel<Command> = mpsc::unbounded_channel();
|
||||
|
||||
let max_depth = handles.config.depth;
|
||||
@@ -322,7 +325,9 @@ impl ScanHandler {
|
||||
let scan = if let Some(ferox_scan) = self.data.get_scan_by_url(&target) {
|
||||
ferox_scan // scan already known
|
||||
} else {
|
||||
self.data.add_directory_scan(&target, order).1 // add the new target; return FeroxScan
|
||||
self.data
|
||||
.add_directory_scan(&target, order, self.handles.clone())
|
||||
.1 // add the new target; return FeroxScan
|
||||
};
|
||||
|
||||
if should_test_deny
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -132,7 +138,7 @@ impl StatsHandler {
|
||||
|
||||
self.bar.finish();
|
||||
|
||||
log::debug!("{:#?}", *self.stats);
|
||||
log::info!("{:#?}", *self.stats);
|
||||
log::trace!("exit: start");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -228,8 +228,11 @@ impl<'a> Extractor<'a> {
|
||||
if resp.is_file() || !resp.is_directory() {
|
||||
log::debug!("Extracted File: {}", resp);
|
||||
|
||||
c_scanned_urls
|
||||
.add_file_scan(resp.url().as_str(), ScanOrder::Latest);
|
||||
c_scanned_urls.add_file_scan(
|
||||
resp.url().as_str(),
|
||||
ScanOrder::Latest,
|
||||
c_handles.clone(),
|
||||
);
|
||||
|
||||
if c_handles.config.collect_extensions {
|
||||
// no real reason this should fail
|
||||
|
||||
@@ -386,7 +386,11 @@ async fn request_link_bails_on_seen_url() -> Result<()> {
|
||||
});
|
||||
|
||||
let scans = Arc::new(FeroxScans::default());
|
||||
scans.add_file_scan(&served, ScanOrder::Latest);
|
||||
scans.add_file_scan(
|
||||
&served,
|
||||
ScanOrder::Latest,
|
||||
Arc::new(Handles::for_testing(None, None).0),
|
||||
);
|
||||
|
||||
let robots = setup_extractor(ExtractionTarget::RobotsTxt, scans.clone());
|
||||
let body = setup_extractor(ExtractionTarget::ResponseBody, scans);
|
||||
|
||||
@@ -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![
|
||||
let expected = [
|
||||
WordsFilter { word_count: 1 },
|
||||
WordsFilter { word_count: 3 },
|
||||
WordsFilter { word_count: 5 },
|
||||
|
||||
@@ -332,10 +332,10 @@ impl HeuristicTests {
|
||||
// - http://localhost/adminf1d2541e73c44dcb9d1fb7d93334b280
|
||||
// - http://localhost/admin92969beae6bf4beb855d1622406d87e395c87387a9ad432e8a11245002b709b03cf609d471004154b83bcc1c6ec49f6f
|
||||
let Ok(response) =
|
||||
logged_request(&nonexistent_url, method, data, self.handles.clone())
|
||||
.await else {
|
||||
return None;
|
||||
};
|
||||
logged_request(&nonexistent_url, method, data, self.handles.clone()).await
|
||||
else {
|
||||
return None;
|
||||
};
|
||||
|
||||
if !self
|
||||
.handles
|
||||
@@ -372,7 +372,9 @@ impl HeuristicTests {
|
||||
}
|
||||
|
||||
// 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 {
|
||||
let Some((wildcard_filters, wildcard_responses)) =
|
||||
self.examine_404_like_responses(&responses)
|
||||
else {
|
||||
// no match was found during analysis of responses
|
||||
log::warn!("no match found for 404 responses");
|
||||
continue;
|
||||
|
||||
@@ -59,6 +59,9 @@ pub(crate) const DEFAULT_IGNORED_EXTENSIONS: [&str; 38] = [
|
||||
"webm", "ogv", "oga", "flac", "aac", "3gp", "css", "zip", "xls", "xml", "gz", "tgz",
|
||||
];
|
||||
|
||||
/// Default set of extensions to search for when auto-collecting backups during scans
|
||||
pub(crate) const DEFAULT_BACKUP_EXTENSIONS: [&str; 5] = ["~", ".bak", ".bak2", ".old", ".1"];
|
||||
|
||||
/// Default wordlist to use when `-w|--wordlist` isn't specified and not `wordlist` isn't set
|
||||
/// in a [ferox-config.toml](constant.DEFAULT_CONFIG_NAME.html) config file.
|
||||
///
|
||||
|
||||
83
src/main.rs
83
src/main.rs
@@ -8,7 +8,7 @@ use std::{
|
||||
io::{stderr, BufRead, BufReader},
|
||||
ops::Index,
|
||||
path::Path,
|
||||
process::{exit, Command},
|
||||
process::{exit, Command, Stdio},
|
||||
sync::{atomic::Ordering, Arc},
|
||||
};
|
||||
|
||||
@@ -25,7 +25,8 @@ use feroxbuster::{
|
||||
config::{Configuration, OutputLevel},
|
||||
event_handlers::{
|
||||
Command::{
|
||||
AddHandles, CreateBar, Exit, JoinTasks, LoadStats, ScanInitialUrls, UpdateWordlist,
|
||||
AddHandles, CreateBar, Exit, JoinTasks, LoadStats, ScanInitialUrls, UpdateTargets,
|
||||
UpdateWordlist,
|
||||
},
|
||||
FiltersHandler, Handles, ScanHandler, StatsHandler, Tasks, TermInputHandler,
|
||||
TermOutHandler, SCAN_COMPLETE,
|
||||
@@ -196,9 +197,9 @@ async fn get_targets(handles: Arc<Handles>) -> Result<Vec<String>> {
|
||||
}
|
||||
}
|
||||
|
||||
if !target.starts_with("http") && !target.starts_with("https") {
|
||||
if !target.starts_with("http") {
|
||||
// --url hackerone.com
|
||||
*target = format!("https://{target}");
|
||||
*target = format!("{}://{target}", handles.config.protocol);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,7 +239,15 @@ async fn wrapped_main(config: Arc<Configuration>) -> Result<()> {
|
||||
|
||||
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?;
|
||||
let response = config
|
||||
.client
|
||||
.get(&config.wordlist)
|
||||
.send()
|
||||
.await
|
||||
.context(format!(
|
||||
"Unable to download wordlist from remote url: {}",
|
||||
config.wordlist
|
||||
))?;
|
||||
|
||||
if !response.status().is_success() {
|
||||
// status code isn't a 200, bail
|
||||
@@ -250,14 +259,15 @@ async fn wrapped_main(config: Arc<Configuration>) -> Result<()> {
|
||||
}
|
||||
|
||||
// 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(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());
|
||||
bail!(
|
||||
"Unable to parse filename from url's path: {}",
|
||||
response.url().path()
|
||||
);
|
||||
};
|
||||
|
||||
let filename = filename.to_string();
|
||||
@@ -316,9 +326,15 @@ async fn wrapped_main(config: Arc<Configuration>) -> Result<()> {
|
||||
// create new Tasks object, each of these handles is one that will be joined on later
|
||||
let tasks = Tasks::new(out_task, stats_task, filters_task, scan_task);
|
||||
|
||||
if !config.time_limit.is_empty() {
|
||||
if !config.time_limit.is_empty() && config.parallel == 0 {
|
||||
// --time-limit value not an empty string, need to kick off the thread that enforces
|
||||
// the limit
|
||||
//
|
||||
// if --parallel is used, this branch won't execute in the main process, but will in the
|
||||
// children. This is because --parallel is stripped from the children's command line
|
||||
// arguments, so, when spawned, they won't have --parallel, the parallel value will be set
|
||||
// to the default of 0, and will hit this branch. This makes it so that the time limit
|
||||
// is enforced on each individual child process, instead of the main process
|
||||
let time_handles = handles.clone();
|
||||
tokio::spawn(async move { scan_manager::start_max_time_thread(time_handles).await });
|
||||
}
|
||||
@@ -361,8 +377,7 @@ async fn wrapped_main(config: Arc<Configuration>) -> Result<()> {
|
||||
|
||||
let invocation = args();
|
||||
|
||||
let para_regex =
|
||||
Regex::new("--stdin|-q|--quiet|--silent|--verbosity|-v|-vv|-vvv|-vvvv").unwrap();
|
||||
let para_regex = Regex::new("--stdin").unwrap();
|
||||
|
||||
// remove stdin since only the original process will process targets
|
||||
// remove quiet and silent so we can force silent later to normalize output
|
||||
@@ -370,8 +385,6 @@ async fn wrapped_main(config: Arc<Configuration>) -> Result<()> {
|
||||
.filter(|s| !para_regex.is_match(s))
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
original.push("--silent".to_string()); // only output modifier allowed
|
||||
|
||||
// we need remove --parallel from command line so we don't hit this branch over and over
|
||||
// but we must remove --parallel N manually; the filter above never sees --parallel and the
|
||||
// value passed to it at the same time, so can't filter them out in one pass
|
||||
@@ -446,16 +459,32 @@ async fn wrapped_main(config: Arc<Configuration>) -> Result<()> {
|
||||
|
||||
log::debug!("parallel exec: {} {}", bin, args.join(" "));
|
||||
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let result = Command::new(bin)
|
||||
tokio::task::spawn(async move {
|
||||
let mut output = Command::new(bin)
|
||||
.args(&args)
|
||||
.stdout(Stdio::piped())
|
||||
.spawn()
|
||||
.expect("failed to spawn a child process")
|
||||
.wait()
|
||||
.expect("child process errored during execution");
|
||||
.expect("failed to spawn a child process");
|
||||
|
||||
let stdout = output.stdout.take().unwrap();
|
||||
|
||||
let mut bufread = BufReader::new(stdout);
|
||||
// output for a single line is a minimum of 51 bytes, so we'll start with that
|
||||
// + a little wiggle room, and grow as needed
|
||||
let mut buf: String = String::with_capacity(128);
|
||||
|
||||
while let Ok(n) = bufread.read_line(&mut buf) {
|
||||
if n > 0 {
|
||||
let trimmed = buf.trim();
|
||||
if !trimmed.is_empty() {
|
||||
println!("{}", trimmed);
|
||||
}
|
||||
buf.clear();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
drop(permit);
|
||||
result
|
||||
});
|
||||
}
|
||||
|
||||
@@ -479,6 +508,14 @@ async fn wrapped_main(config: Arc<Configuration>) -> Result<()> {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// in order for the Stats object to know about which targets are being scanned, we need to
|
||||
// wait until the parallel branch has been handled before sending the UpdateTargets command
|
||||
// this ensures that only the targets being scanned are sent to the Stats object
|
||||
//
|
||||
// if sent before the parallel branch is handled, the Stats object will have duplicate
|
||||
// targets
|
||||
handles.stats.send(UpdateTargets(targets.clone()))?;
|
||||
|
||||
if matches!(config.output_level, OutputLevel::Default) {
|
||||
// only print banner if output level is default (no banner on --quiet|--silent)
|
||||
let std_stderr = stderr(); // std::io::stderr
|
||||
@@ -637,7 +674,7 @@ fn main() -> Result<()> {
|
||||
// print the banner to stderr
|
||||
let std_stderr = stderr(); // std::io::stderr
|
||||
let banner = Banner::new(&targets, &config);
|
||||
if !config.quiet && !config.silent {
|
||||
if (!config.quiet && !config.silent) || config.parallel != 0 {
|
||||
banner.print_to(std_stderr, config).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,20 +35,19 @@ impl Document {
|
||||
fn add_term(&mut self, word: &str) {
|
||||
let term = Term::new(word);
|
||||
|
||||
let metadata = self.terms.entry(term).or_insert_with(TermMetaData::new);
|
||||
let metadata = self.terms.entry(term).or_default();
|
||||
*metadata.count_mut() += 1;
|
||||
}
|
||||
|
||||
/// create a new `Document` from the given HTML string
|
||||
pub(crate) fn from_html(raw_html: &str) -> Self {
|
||||
pub(crate) fn from_html(raw_html: &str) -> Option<Self> {
|
||||
let selector = Selector::parse("body").unwrap();
|
||||
|
||||
let html = Html::parse_document(raw_html);
|
||||
|
||||
let text = html
|
||||
.select(&selector)
|
||||
.next()
|
||||
.unwrap()
|
||||
let element = html.select(&selector).next()?;
|
||||
|
||||
let text = element
|
||||
.descendants()
|
||||
.filter_map(|node| {
|
||||
if !node.value().is_text() && !node.value().is_comment() {
|
||||
@@ -95,7 +94,7 @@ impl Document {
|
||||
|
||||
// call `new` to push the parsed html through the pre-processing pipeline and process all
|
||||
// the words
|
||||
Self::new(&text)
|
||||
Some(Self::new(&text))
|
||||
}
|
||||
|
||||
/// Log normalized weighting scheme for term frequency
|
||||
@@ -146,19 +145,20 @@ mod tests {
|
||||
#[test]
|
||||
/// `Document::new` should preprocess html and generate a hashmap of `Term, TermMetadata`
|
||||
fn nlp_document_creation_from_html() {
|
||||
let empty = Document::from_html("<html></html>");
|
||||
let empty = Document::from_html("<html></html>").unwrap();
|
||||
assert_eq!(empty.number_of_terms, 0);
|
||||
|
||||
let other_empty = Document::from_html("<html><body><p></p></body></html>");
|
||||
let other_empty = Document::from_html("<html><body><p></p></body></html>").unwrap();
|
||||
assert_eq!(other_empty.number_of_terms, 0);
|
||||
|
||||
let third_empty = Document::from_html("<!DOCTYPE html><html><!DOCTYPE html><p></p></html>");
|
||||
let third_empty =
|
||||
Document::from_html("<!DOCTYPE html><html><!DOCTYPE html><p></p></html>").unwrap();
|
||||
assert_eq!(third_empty.number_of_terms, 0);
|
||||
|
||||
// p tag for is_text check and comment for is_comment
|
||||
let doc = Document::from_html(
|
||||
"<html><body><p>The air quality in Singapore.</p><!--got worse on Wednesday--></body></html>",
|
||||
);
|
||||
).unwrap();
|
||||
|
||||
let expected_terms = ["air", "quality", "singapore", "worse", "wednesday"];
|
||||
|
||||
@@ -209,7 +209,7 @@ mod tests {
|
||||
/// ensure words in script/style tags aren't processed
|
||||
fn document_creation_skips_script_and_style_tags() {
|
||||
let html = "<body><script>The air quality</script><style>in Singapore</style><p>got worse on Wednesday.</p></body>";
|
||||
let doc = Document::from_html(html);
|
||||
let doc = Document::from_html(html).unwrap();
|
||||
let keys = doc.terms().keys().map(|key| key.raw()).collect::<Vec<_>>();
|
||||
|
||||
let expected = ["worse", "wednesday"];
|
||||
|
||||
@@ -35,11 +35,6 @@ pub(super) struct TermMetaData {
|
||||
}
|
||||
|
||||
impl TermMetaData {
|
||||
/// create a new metadata container
|
||||
pub(super) fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// number of times a `Term` has appeared in any `Document` within the corpus
|
||||
pub(super) fn document_frequency(&self) -> usize {
|
||||
self.term_frequencies().len()
|
||||
@@ -90,7 +85,7 @@ mod tests {
|
||||
#[test]
|
||||
/// test accessors for correctness
|
||||
fn nlp_term_metadata_accessor_test() {
|
||||
let mut metadata = TermMetaData::new();
|
||||
let mut metadata = TermMetaData::default();
|
||||
|
||||
*metadata.count_mut() += 1;
|
||||
assert_eq!(metadata.count(), 1);
|
||||
|
||||
@@ -40,12 +40,12 @@ pub fn initialize() -> Command {
|
||||
Arg::new("url")
|
||||
.short('u')
|
||||
.long("url")
|
||||
.required_unless_present_any(["stdin", "resume_from", "update_app"])
|
||||
.required_unless_present_any(["stdin", "resume_from", "update_app", "request_file"])
|
||||
.help_heading("Target selection")
|
||||
.value_name("URL")
|
||||
.use_value_delimiter(true)
|
||||
.value_hint(ValueHint::Url)
|
||||
.help("The target URL (required, unless [--stdin || --resume-from] used)"),
|
||||
.help("The target URL (required, unless [--stdin || --resume-from || --request-file] used)"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("stdin")
|
||||
@@ -64,6 +64,15 @@ pub fn initialize() -> Command {
|
||||
.help("State file from which to resume a partially complete scan (ex. --resume-from ferox-1606586780.state)")
|
||||
.conflicts_with("url")
|
||||
.num_args(1),
|
||||
).arg(
|
||||
Arg::new("request_file")
|
||||
.long("request-file")
|
||||
.help_heading("Target selection")
|
||||
.value_hint(ValueHint::FilePath)
|
||||
.conflicts_with("url")
|
||||
.num_args(1)
|
||||
.value_name("REQUEST_FILE")
|
||||
.help("Raw HTTP request file to use as a template for all requests"),
|
||||
);
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
@@ -100,7 +109,7 @@ pub fn initialize() -> Command {
|
||||
.num_args(0)
|
||||
.help_heading("Composite settings")
|
||||
.conflicts_with_all(["rate_limit", "auto_bail"])
|
||||
.help("Use the same settings as --smart and set --collect-extensions to true"),
|
||||
.help("Use the same settings as --smart and set --collect-extensions and --scan-dir-listings to true"),
|
||||
);
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
@@ -177,7 +186,7 @@ pub fn initialize() -> Command {
|
||||
.use_value_delimiter(true)
|
||||
.help_heading("Request settings")
|
||||
.help(
|
||||
"File extension(s) to search for (ex: -x php -x pdf js)",
|
||||
"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)",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
@@ -211,7 +220,6 @@ pub fn initialize() -> Command {
|
||||
.num_args(1..)
|
||||
.action(ArgAction::Append)
|
||||
.help_heading("Request settings")
|
||||
.use_value_delimiter(true)
|
||||
.help(
|
||||
"Specify HTTP headers to be used in each request (ex: -H Header:val -H 'stuff: things')",
|
||||
),
|
||||
@@ -249,6 +257,13 @@ pub fn initialize() -> Command {
|
||||
.help_heading("Request settings")
|
||||
.num_args(0)
|
||||
.help("Append / to each request's URL")
|
||||
).arg(
|
||||
Arg::new("protocol")
|
||||
.long("protocol")
|
||||
.value_name("PROTOCOL")
|
||||
.num_args(1)
|
||||
.help_heading("Request settings")
|
||||
.help("Specify the protocol to use when targeting via --request-file or --url with domain only (default: https)"),
|
||||
);
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
@@ -292,7 +307,7 @@ pub fn initialize() -> Command {
|
||||
.use_value_delimiter(true)
|
||||
.help_heading("Response filters")
|
||||
.help(
|
||||
"Filter out messages via regular expression matching on the response's body (ex: -X '^ignore me$')",
|
||||
"Filter out messages via regular expression matching on the response's body/headers (ex: -X '^ignore me$')",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
@@ -486,6 +501,8 @@ pub fn initialize() -> Command {
|
||||
Arg::new("parallel")
|
||||
.long("parallel")
|
||||
.value_name("PARALLEL_SCANS")
|
||||
.conflicts_with("verbosity")
|
||||
.conflicts_with("url")
|
||||
.num_args(1)
|
||||
.requires("stdin")
|
||||
.help_heading("Scan settings")
|
||||
@@ -550,9 +567,9 @@ pub fn initialize() -> Command {
|
||||
Arg::new("collect_backups")
|
||||
.short('B')
|
||||
.long("collect-backups")
|
||||
.num_args(0)
|
||||
.num_args(0..)
|
||||
.help_heading("Dynamic collection settings")
|
||||
.help("Automatically request likely backup extensions for \"found\" urls")
|
||||
.help("Automatically request likely backup extensions for \"found\" urls (default: ~, .bak, .bak2, .old, .1)")
|
||||
)
|
||||
.arg(
|
||||
Arg::new("collect_words")
|
||||
@@ -573,6 +590,12 @@ pub fn initialize() -> Command {
|
||||
.help(
|
||||
"File extension(s) to Ignore while collecting extensions (only used with --collect-extensions)",
|
||||
),
|
||||
).arg(
|
||||
Arg::new("scan_dir_listings")
|
||||
.long("scan-dir-listings")
|
||||
.num_args(0)
|
||||
.help_heading("Scan settings")
|
||||
.help("Force scans to recurse into directory listings")
|
||||
);
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
@@ -594,7 +617,7 @@ pub fn initialize() -> Command {
|
||||
.num_args(0)
|
||||
.conflicts_with("quiet")
|
||||
.help_heading("Output settings")
|
||||
.help("Only print URLs + turn off logging (good for piping a list of urls to other commands)")
|
||||
.help("Only print URLs (or JSON w/ --json) + turn off logging (good for piping a list of urls to other commands)")
|
||||
)
|
||||
.arg(
|
||||
Arg::new("quiet")
|
||||
@@ -637,6 +660,13 @@ pub fn initialize() -> Command {
|
||||
.num_args(0)
|
||||
.help_heading("Output settings")
|
||||
.help("Disable state output file (*.state)")
|
||||
).arg(
|
||||
Arg::new("limit_bars")
|
||||
.long("limit-bars")
|
||||
.value_name("NUM_BARS_TO_SHOW")
|
||||
.num_args(1)
|
||||
.help_heading("Output settings")
|
||||
.help("Number of directory scan bars to show at any given time (default: no limit)"),
|
||||
);
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
@@ -645,9 +675,14 @@ pub fn initialize() -> Command {
|
||||
let mut app = app
|
||||
.group(
|
||||
ArgGroup::new("output_files")
|
||||
.args(["debug_log", "output"])
|
||||
.args(["debug_log", "output", "silent"])
|
||||
.multiple(true),
|
||||
)
|
||||
.group(
|
||||
ArgGroup::new("output_limiters")
|
||||
.args(["quiet", "silent"])
|
||||
.multiple(false),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("update_app")
|
||||
.short('U')
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use indicatif::{HumanDuration, MultiProgress, ProgressBar, ProgressDrawTarget, ProgressStyle};
|
||||
use indicatif::{MultiProgress, ProgressBar, ProgressDrawTarget, ProgressStyle};
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
lazy_static! {
|
||||
@@ -33,45 +31,32 @@ pub enum BarType {
|
||||
/// Add an [indicatif::ProgressBar](https://docs.rs/indicatif/latest/indicatif/struct.ProgressBar.html)
|
||||
/// 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 pb = ProgressBar::new(length).with_prefix(prefix.to_string());
|
||||
|
||||
update_style(&pb, bar_type);
|
||||
|
||||
PROGRESS_BAR.add(pb)
|
||||
}
|
||||
|
||||
/// Update the style of a progress bar based on the `BarType`
|
||||
pub fn update_style(bar: &ProgressBar, bar_type: BarType) {
|
||||
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,16 +70,12 @@ 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(),
|
||||
};
|
||||
|
||||
PROGRESS_BAR.add(
|
||||
ProgressBar::new(length)
|
||||
.with_style(style)
|
||||
.with_prefix(prefix.to_string()),
|
||||
)
|
||||
bar.set_style(style);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -423,8 +434,12 @@ impl FeroxSerialize for FeroxResponse {
|
||||
let mut url_with_redirect = match (
|
||||
self.status().is_redirection(),
|
||||
self.headers().get("Location").is_some(),
|
||||
matches!(
|
||||
self.output_level,
|
||||
OutputLevel::Silent | OutputLevel::SilentJSON
|
||||
),
|
||||
) {
|
||||
(true, true) => {
|
||||
(true, true, false) => {
|
||||
// redirect with Location header, show where it goes if possible
|
||||
let loc = self
|
||||
.headers()
|
||||
@@ -485,15 +500,19 @@ impl FeroxSerialize for FeroxResponse {
|
||||
message
|
||||
} else {
|
||||
// not a wildcard, just create a normal entry
|
||||
utils::create_report_string(
|
||||
self.status.as_str(),
|
||||
method,
|
||||
&lines,
|
||||
&words,
|
||||
&chars,
|
||||
&url_with_redirect,
|
||||
self.output_level,
|
||||
)
|
||||
if matches!(self.output_level, OutputLevel::SilentJSON) {
|
||||
self.as_json().unwrap_or_default()
|
||||
} else {
|
||||
utils::create_report_string(
|
||||
self.status.as_str(),
|
||||
method,
|
||||
&lines,
|
||||
&words,
|
||||
&chars,
|
||||
&url_with_redirect,
|
||||
self.output_level,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -566,6 +585,7 @@ impl Serialize for FeroxResponse {
|
||||
"extension",
|
||||
self.extension.as_ref().unwrap_or(&String::new()),
|
||||
)?;
|
||||
state.serialize_field("timestamp", &self.timestamp)?;
|
||||
|
||||
state.end()
|
||||
}
|
||||
@@ -591,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)?;
|
||||
@@ -664,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());
|
||||
|
||||
@@ -8,7 +8,7 @@ mod state;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
pub(self) use menu::Menu;
|
||||
use menu::Menu;
|
||||
pub use menu::{MenuCmd, MenuCmdResult};
|
||||
pub use order::ScanOrder;
|
||||
pub use response_container::FeroxResponses;
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
use super::*;
|
||||
use crate::{
|
||||
config::OutputLevel,
|
||||
event_handlers::Handles,
|
||||
progress::update_style,
|
||||
progress::{add_bar, BarType},
|
||||
scan_manager::utils::determine_bar_type,
|
||||
scanner::PolicyTrigger,
|
||||
};
|
||||
use anyhow::Result;
|
||||
@@ -16,10 +19,20 @@ use std::{
|
||||
time::Instant,
|
||||
};
|
||||
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
||||
use tokio::{sync, task::JoinHandle};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Debug, Default, Copy, Clone)]
|
||||
pub enum Visibility {
|
||||
/// whether a FeroxScan's progress bar is currently shown
|
||||
#[default]
|
||||
Visible,
|
||||
|
||||
/// whether a FeroxScan's progress bar is currently hidden
|
||||
Hidden,
|
||||
}
|
||||
|
||||
/// Struct to hold scan-related state
|
||||
///
|
||||
/// The purpose of this container is to open up the pathway to aborting currently running tasks and
|
||||
@@ -58,7 +71,7 @@ pub struct FeroxScan {
|
||||
pub(super) task: sync::Mutex<Option<JoinHandle<()>>>,
|
||||
|
||||
/// The progress bar associated with this scan
|
||||
pub(super) progress_bar: Mutex<Option<ProgressBar>>,
|
||||
pub progress_bar: Mutex<Option<ProgressBar>>,
|
||||
|
||||
/// whether or not the user passed --silent|--quiet on the command line
|
||||
pub(super) output_level: OutputLevel,
|
||||
@@ -74,6 +87,12 @@ pub struct FeroxScan {
|
||||
|
||||
/// tracker for the time at which this scan was started
|
||||
pub(super) start_time: Instant,
|
||||
|
||||
/// whether the progress bar is currently visible or hidden
|
||||
pub(super) visible: AtomicBool,
|
||||
|
||||
/// handles object pointer
|
||||
pub(super) handles: Option<Arc<Handles>>,
|
||||
}
|
||||
|
||||
/// Default implementation for FeroxScan
|
||||
@@ -86,6 +105,7 @@ impl Default for FeroxScan {
|
||||
id: new_id,
|
||||
task: sync::Mutex::new(None), // tokio mutex
|
||||
status: Mutex::new(ScanStatus::default()),
|
||||
handles: None,
|
||||
num_requests: 0,
|
||||
requests_made_so_far: 0,
|
||||
scan_order: ScanOrder::Latest,
|
||||
@@ -98,23 +118,63 @@ impl Default for FeroxScan {
|
||||
status_429s: Default::default(),
|
||||
status_403s: Default::default(),
|
||||
start_time: Instant::now(),
|
||||
visible: AtomicBool::new(true),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Implementation of FeroxScan
|
||||
impl FeroxScan {
|
||||
/// return the visibility of the scan as a boolean
|
||||
pub fn visible(&self) -> bool {
|
||||
self.visible.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
pub fn swap_visibility(&self) {
|
||||
// fetch_xor toggles the boolean to its opposite and returns the previous value
|
||||
let visible = self.visible.fetch_xor(true, Ordering::Relaxed);
|
||||
|
||||
let Ok(bar) = self.progress_bar.lock() else {
|
||||
log::warn!("couldn't unlock progress bar for {}", self.url);
|
||||
return;
|
||||
};
|
||||
|
||||
if bar.is_none() {
|
||||
log::warn!("there is no progress bar for {}", self.url);
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(handles) = self.handles.as_ref() else {
|
||||
log::warn!("couldn't access handles pointer for {}", self.url);
|
||||
return;
|
||||
};
|
||||
|
||||
let bar_type = if !visible {
|
||||
// visibility was false before we xor'd the value
|
||||
match handles.config.output_level {
|
||||
OutputLevel::Default => BarType::Default,
|
||||
OutputLevel::Quiet => BarType::Quiet,
|
||||
OutputLevel::Silent | OutputLevel::SilentJSON => BarType::Hidden,
|
||||
}
|
||||
} else {
|
||||
// visibility was true before we xor'd the value
|
||||
BarType::Hidden
|
||||
};
|
||||
|
||||
update_style(bar.as_ref().unwrap(), bar_type);
|
||||
}
|
||||
|
||||
/// Stop a currently running scan
|
||||
pub async fn abort(&self) -> Result<()> {
|
||||
pub async fn abort(&self, active_bars: usize) -> Result<()> {
|
||||
log::trace!("enter: abort");
|
||||
|
||||
match self.task.try_lock() {
|
||||
Ok(mut guard) => {
|
||||
if let Some(task) = std::mem::replace(&mut *guard, None) {
|
||||
if let Some(task) = guard.take() {
|
||||
log::trace!("aborting {:?}", self);
|
||||
task.abort();
|
||||
self.set_status(ScanStatus::Cancelled)?;
|
||||
self.stop_progress_bar();
|
||||
self.stop_progress_bar(active_bars);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
@@ -151,15 +211,26 @@ impl FeroxScan {
|
||||
}
|
||||
|
||||
/// Simple helper to call .finish on the scan's progress bar
|
||||
pub(super) fn stop_progress_bar(&self) {
|
||||
pub(super) fn stop_progress_bar(&self, active_bars: usize) {
|
||||
if let Ok(guard) = self.progress_bar.lock() {
|
||||
if guard.is_some() {
|
||||
let pb = (*guard).as_ref().unwrap();
|
||||
|
||||
if pb.position() > self.num_requests {
|
||||
pb.finish()
|
||||
let bar_limit = if let Some(handles) = self.handles.as_ref() {
|
||||
handles.config.limit_bars
|
||||
} else {
|
||||
pb.abandon()
|
||||
0
|
||||
};
|
||||
|
||||
if bar_limit > 0 && bar_limit < active_bars {
|
||||
pb.finish_and_clear();
|
||||
return;
|
||||
}
|
||||
|
||||
if pb.position() > self.num_requests {
|
||||
pb.finish();
|
||||
} else {
|
||||
pb.abandon();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -172,12 +243,18 @@ impl FeroxScan {
|
||||
if guard.is_some() {
|
||||
(*guard).as_ref().unwrap().clone()
|
||||
} else {
|
||||
let bar_type = match self.output_level {
|
||||
OutputLevel::Default => BarType::Default,
|
||||
OutputLevel::Quiet => BarType::Quiet,
|
||||
OutputLevel::Silent => BarType::Hidden,
|
||||
let (active_bars, bar_limit) = if let Some(handles) = self.handles.as_ref() {
|
||||
if let Ok(scans) = handles.ferox_scans() {
|
||||
(scans.number_of_bars(), handles.config.limit_bars)
|
||||
} else {
|
||||
(0, handles.config.limit_bars)
|
||||
}
|
||||
} else {
|
||||
(0, 0)
|
||||
};
|
||||
|
||||
let bar_type = determine_bar_type(bar_limit, active_bars, self.output_level);
|
||||
|
||||
let pb = add_bar(&self.url, self.num_requests, bar_type);
|
||||
pb.reset_elapsed();
|
||||
|
||||
@@ -191,12 +268,18 @@ impl FeroxScan {
|
||||
Err(_) => {
|
||||
log::warn!("Could not unlock progress bar on {:?}", self);
|
||||
|
||||
let bar_type = match self.output_level {
|
||||
OutputLevel::Default => BarType::Default,
|
||||
OutputLevel::Quiet => BarType::Quiet,
|
||||
OutputLevel::Silent => BarType::Hidden,
|
||||
let (active_bars, bar_limit) = if let Some(handles) = self.handles.as_ref() {
|
||||
if let Ok(scans) = handles.ferox_scans() {
|
||||
(scans.number_of_bars(), handles.config.limit_bars)
|
||||
} else {
|
||||
(0, handles.config.limit_bars)
|
||||
}
|
||||
} else {
|
||||
(0, 0)
|
||||
};
|
||||
|
||||
let bar_type = determine_bar_type(bar_limit, active_bars, self.output_level);
|
||||
|
||||
let pb = add_bar(&self.url, self.num_requests, bar_type);
|
||||
pb.reset_elapsed();
|
||||
|
||||
@@ -206,6 +289,7 @@ impl FeroxScan {
|
||||
}
|
||||
|
||||
/// Given a URL and ProgressBar, create a new FeroxScan, wrap it in an Arc and return it
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
url: &str,
|
||||
scan_type: ScanType,
|
||||
@@ -213,6 +297,8 @@ impl FeroxScan {
|
||||
num_requests: u64,
|
||||
output_level: OutputLevel,
|
||||
pb: Option<ProgressBar>,
|
||||
visibility: bool,
|
||||
handles: Arc<Handles>,
|
||||
) -> Arc<Self> {
|
||||
Arc::new(Self {
|
||||
url: url.to_string(),
|
||||
@@ -222,14 +308,16 @@ impl FeroxScan {
|
||||
num_requests,
|
||||
output_level,
|
||||
progress_bar: Mutex::new(pb),
|
||||
visible: AtomicBool::new(visibility),
|
||||
handles: Some(handles),
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
/// Mark the scan as complete and stop the scan's progress bar
|
||||
pub fn finish(&self) -> Result<()> {
|
||||
pub fn finish(&self, active_bars: usize) -> Result<()> {
|
||||
self.set_status(ScanStatus::Complete)?;
|
||||
self.stop_progress_bar();
|
||||
self.stop_progress_bar(active_bars);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -262,13 +350,29 @@ impl FeroxScan {
|
||||
false
|
||||
}
|
||||
|
||||
/// small wrapper to inspect ScanStatus and see if it's Running
|
||||
pub fn is_running(&self) -> bool {
|
||||
if let Ok(guard) = self.status.lock() {
|
||||
return matches!(*guard, ScanStatus::Running);
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// small wrapper to inspect ScanStatus and see if it's NotStarted
|
||||
pub fn is_not_started(&self) -> bool {
|
||||
if let Ok(guard) = self.status.lock() {
|
||||
return matches!(*guard, ScanStatus::NotStarted);
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// await a task's completion, similar to a thread's join; perform necessary bookkeeping
|
||||
pub async fn join(&self) {
|
||||
log::trace!("enter join({:?})", self);
|
||||
let mut guard = self.task.lock().await;
|
||||
|
||||
if guard.is_some() {
|
||||
if let Some(task) = std::mem::replace(&mut *guard, None) {
|
||||
if let Some(task) = guard.take() {
|
||||
task.await.unwrap();
|
||||
self.set_status(ScanStatus::Complete)
|
||||
.unwrap_or_else(|e| log::warn!("Could not mark scan complete: {}", e))
|
||||
@@ -507,6 +611,8 @@ mod tests {
|
||||
1000,
|
||||
OutputLevel::Default,
|
||||
None,
|
||||
true,
|
||||
Arc::new(Handles::for_testing(None, None).0),
|
||||
);
|
||||
|
||||
scan.add_error();
|
||||
@@ -532,6 +638,7 @@ mod tests {
|
||||
scan_order: ScanOrder::Initial,
|
||||
num_requests: 0,
|
||||
requests_made_so_far: 0,
|
||||
visible: AtomicBool::new(true),
|
||||
status: Mutex::new(ScanStatus::Running),
|
||||
task: Default::default(),
|
||||
progress_bar: Mutex::new(None),
|
||||
@@ -540,6 +647,7 @@ mod tests {
|
||||
status_429s: Default::default(),
|
||||
errors: Default::default(),
|
||||
start_time: Instant::now(),
|
||||
handles: None,
|
||||
};
|
||||
|
||||
let pb = scan.progress_bar();
|
||||
@@ -551,7 +659,62 @@ mod tests {
|
||||
|
||||
assert_eq!(req_sec, 100);
|
||||
|
||||
scan.finish().unwrap();
|
||||
scan.finish(0).unwrap();
|
||||
assert_eq!(scan.requests_per_second(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_swap_visibility() {
|
||||
let scan = FeroxScan::new(
|
||||
"http://localhost",
|
||||
ScanType::Directory,
|
||||
ScanOrder::Latest,
|
||||
1000,
|
||||
OutputLevel::Default,
|
||||
None,
|
||||
true,
|
||||
Arc::new(Handles::for_testing(None, None).0),
|
||||
);
|
||||
|
||||
assert!(scan.visible());
|
||||
|
||||
scan.swap_visibility();
|
||||
assert!(!scan.visible());
|
||||
|
||||
scan.swap_visibility();
|
||||
assert!(scan.visible());
|
||||
|
||||
scan.swap_visibility();
|
||||
assert!(!scan.visible());
|
||||
|
||||
scan.swap_visibility();
|
||||
assert!(scan.visible());
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// test for is_running method
|
||||
fn test_is_running() {
|
||||
let scan = FeroxScan::new(
|
||||
"http://localhost",
|
||||
ScanType::Directory,
|
||||
ScanOrder::Latest,
|
||||
1000,
|
||||
OutputLevel::Default,
|
||||
None,
|
||||
true,
|
||||
Arc::new(Handles::for_testing(None, None).0),
|
||||
);
|
||||
|
||||
assert!(scan.is_not_started());
|
||||
assert!(!scan.is_running());
|
||||
assert!(!scan.is_complete());
|
||||
assert!(!scan.is_cancelled());
|
||||
|
||||
*scan.status.lock().unwrap() = ScanStatus::Running;
|
||||
|
||||
assert!(!scan.is_not_started());
|
||||
assert!(scan.is_running());
|
||||
assert!(!scan.is_complete());
|
||||
assert!(!scan.is_cancelled());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ use crate::{
|
||||
config::OutputLevel,
|
||||
progress::PROGRESS_PRINTER,
|
||||
progress::{add_bar, BarType},
|
||||
scan_manager::utils::determine_bar_type,
|
||||
scan_manager::{MenuCmd, MenuCmdResult},
|
||||
scanner::RESPONSES,
|
||||
traits::FeroxSerialize,
|
||||
@@ -33,6 +34,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
|
||||
@@ -60,6 +62,9 @@ pub struct FeroxScans {
|
||||
|
||||
/// vector of extensions discovered and collected during scans
|
||||
pub(crate) collected_extensions: RwLock<HashSet<String>>,
|
||||
|
||||
/// stored value for Configuration.limit_bars
|
||||
bar_limit: usize,
|
||||
}
|
||||
|
||||
/// Serialize implementation for FeroxScans
|
||||
@@ -92,9 +97,10 @@ impl Serialize for FeroxScans {
|
||||
/// Implementation of `FeroxScans`
|
||||
impl FeroxScans {
|
||||
/// given an OutputLevel, create a new FeroxScans object
|
||||
pub fn new(output_level: OutputLevel) -> Self {
|
||||
pub fn new(output_level: OutputLevel, bar_limit: usize) -> Self {
|
||||
Self {
|
||||
output_level,
|
||||
bar_limit,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
@@ -330,6 +336,13 @@ impl FeroxScans {
|
||||
self.menu
|
||||
.println(&format!("{}:", style("Scans").bright().blue()));
|
||||
}
|
||||
|
||||
if let Ok(guard) = scan.status.lock() {
|
||||
if matches!(*guard, ScanStatus::Cancelled) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// we're only interested in displaying directory scans, as those are
|
||||
// the only ones that make sense to be stopped
|
||||
let scan_msg = format!("{i:3}: {scan}");
|
||||
@@ -360,7 +373,14 @@ impl FeroxScans {
|
||||
sleep(menu_pause_duration);
|
||||
continue;
|
||||
}
|
||||
u_scans.index(num).clone()
|
||||
|
||||
let selected = u_scans.index(num);
|
||||
|
||||
if matches!(selected.scan_type, ScanType::File) {
|
||||
continue;
|
||||
}
|
||||
|
||||
selected.clone()
|
||||
}
|
||||
Err(..) => continue,
|
||||
};
|
||||
@@ -373,8 +393,9 @@ impl FeroxScans {
|
||||
|
||||
if input == 'y' || input == '\n' {
|
||||
self.menu.println(&format!("Stopping {}...", selected.url));
|
||||
let active_bars = self.number_of_bars();
|
||||
selected
|
||||
.abort()
|
||||
.abort(active_bars)
|
||||
.await
|
||||
.unwrap_or_else(|e| log::warn!("Could not cancel task: {}", e));
|
||||
|
||||
@@ -416,6 +437,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();
|
||||
@@ -499,14 +527,22 @@ impl FeroxScans {
|
||||
|
||||
/// if a resumed scan is already complete, display a completed progress bar to the user
|
||||
pub fn print_completed_bars(&self, bar_length: usize) -> Result<()> {
|
||||
let bar_type = match self.output_level {
|
||||
OutputLevel::Default => BarType::Message,
|
||||
OutputLevel::Quiet => BarType::Quiet,
|
||||
OutputLevel::Silent => return Ok(()), // fast exit when --silent was used
|
||||
};
|
||||
if self.output_level == OutputLevel::SilentJSON || self.output_level == OutputLevel::Silent
|
||||
{
|
||||
// fast exit when --silent was used
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let bar_type: BarType =
|
||||
determine_bar_type(self.bar_limit, self.number_of_bars(), self.output_level);
|
||||
|
||||
if let Ok(scans) = self.scans.read() {
|
||||
for scan in scans.iter() {
|
||||
if matches!(bar_type, BarType::Hidden) {
|
||||
// no need to show hidden bars
|
||||
continue;
|
||||
}
|
||||
|
||||
if scan.is_complete() {
|
||||
// these scans are complete, and just need to be shown to the user
|
||||
let pb = add_bar(
|
||||
@@ -583,6 +619,7 @@ impl FeroxScans {
|
||||
url: &str,
|
||||
scan_type: ScanType,
|
||||
scan_order: ScanOrder,
|
||||
handles: Arc<Handles>,
|
||||
) -> (bool, Arc<FeroxScan>) {
|
||||
let bar_length = if let Ok(guard) = self.bar_length.lock() {
|
||||
*guard
|
||||
@@ -590,14 +627,11 @@ impl FeroxScans {
|
||||
0
|
||||
};
|
||||
|
||||
let active_bars = self.number_of_bars();
|
||||
let bar_type = determine_bar_type(self.bar_limit, active_bars, self.output_level);
|
||||
|
||||
let bar = match scan_type {
|
||||
ScanType::Directory => {
|
||||
let bar_type = match self.output_level {
|
||||
OutputLevel::Default => BarType::Default,
|
||||
OutputLevel::Quiet => BarType::Quiet,
|
||||
OutputLevel::Silent => BarType::Hidden,
|
||||
};
|
||||
|
||||
let progress_bar = add_bar(url, bar_length, bar_type);
|
||||
|
||||
progress_bar.reset_elapsed();
|
||||
@@ -607,6 +641,8 @@ impl FeroxScans {
|
||||
ScanType::File => None,
|
||||
};
|
||||
|
||||
let is_visible = !matches!(bar_type, BarType::Hidden);
|
||||
|
||||
let ferox_scan = FeroxScan::new(
|
||||
url,
|
||||
scan_type,
|
||||
@@ -614,6 +650,8 @@ impl FeroxScans {
|
||||
bar_length,
|
||||
self.output_level,
|
||||
bar,
|
||||
is_visible,
|
||||
handles,
|
||||
);
|
||||
|
||||
// If the set did not contain the scan, true is returned.
|
||||
@@ -628,9 +666,14 @@ impl FeroxScans {
|
||||
/// If `FeroxScans` did not already contain the scan, return true; otherwise return false
|
||||
///
|
||||
/// Also return a reference to the new `FeroxScan`
|
||||
pub fn add_directory_scan(&self, url: &str, scan_order: ScanOrder) -> (bool, Arc<FeroxScan>) {
|
||||
pub fn add_directory_scan(
|
||||
&self,
|
||||
url: &str,
|
||||
scan_order: ScanOrder,
|
||||
handles: Arc<Handles>,
|
||||
) -> (bool, Arc<FeroxScan>) {
|
||||
let normalized = format!("{}/", url.trim_end_matches('/'));
|
||||
self.add_scan(&normalized, ScanType::Directory, scan_order)
|
||||
self.add_scan(&normalized, ScanType::Directory, scan_order, handles)
|
||||
}
|
||||
|
||||
/// Given a url, create a new `FeroxScan` and add it to `FeroxScans` as a File Scan
|
||||
@@ -638,8 +681,65 @@ impl FeroxScans {
|
||||
/// If `FeroxScans` did not already contain the scan, return true; otherwise return false
|
||||
///
|
||||
/// Also return a reference to the new `FeroxScan`
|
||||
pub fn add_file_scan(&self, url: &str, scan_order: ScanOrder) -> (bool, Arc<FeroxScan>) {
|
||||
self.add_scan(url, ScanType::File, scan_order)
|
||||
pub fn add_file_scan(
|
||||
&self,
|
||||
url: &str,
|
||||
scan_order: ScanOrder,
|
||||
handles: Arc<Handles>,
|
||||
) -> (bool, Arc<FeroxScan>) {
|
||||
self.add_scan(url, ScanType::File, scan_order, handles)
|
||||
}
|
||||
|
||||
/// returns the number of active AND visible scans; supports --limit-bars functionality
|
||||
pub fn number_of_bars(&self) -> usize {
|
||||
let Ok(scans) = self.scans.read() else {
|
||||
return 0;
|
||||
};
|
||||
|
||||
// starting at one ensures we don't have an extra bar
|
||||
// due to counting up from 0 when there's actually 1 bar
|
||||
let mut count = 1;
|
||||
|
||||
for scan in &*scans {
|
||||
if scan.is_active() && scan.visible() {
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
count
|
||||
}
|
||||
|
||||
/// make one hidden bar visible; supports --limit-bars functionality
|
||||
pub fn make_visible(&self) {
|
||||
if let Ok(guard) = self.scans.read() {
|
||||
// when swapping visibility, we'll prefer an actively running scan
|
||||
// if none are found, we'll
|
||||
let mut queued = None;
|
||||
|
||||
for scan in &*guard {
|
||||
if !matches!(scan.scan_type, ScanType::Directory) {
|
||||
// visibility only makes sense for directory scans
|
||||
continue;
|
||||
}
|
||||
|
||||
if scan.visible() {
|
||||
continue;
|
||||
}
|
||||
|
||||
if scan.is_running() {
|
||||
scan.swap_visibility();
|
||||
return;
|
||||
}
|
||||
|
||||
if queued.is_none() && scan.is_not_started() {
|
||||
queued = Some(scan.clone());
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(scan) = queued {
|
||||
scan.swap_visibility();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// small helper to determine whether any scans are active or not
|
||||
@@ -704,7 +804,7 @@ mod tests {
|
||||
#[test]
|
||||
/// unknown extension should be added to collected_extensions
|
||||
fn unknown_extension_is_added_to_collected_extensions() {
|
||||
let scans = FeroxScans::new(OutputLevel::Default);
|
||||
let scans = FeroxScans::new(OutputLevel::Default, 0);
|
||||
|
||||
assert_eq!(0, scans.collected_extensions.read().unwrap().len());
|
||||
|
||||
@@ -717,7 +817,7 @@ mod tests {
|
||||
#[test]
|
||||
/// known extension should not be added to collected_extensions
|
||||
fn known_extension_is_added_to_collected_extensions() {
|
||||
let scans = FeroxScans::new(OutputLevel::Default);
|
||||
let scans = FeroxScans::new(OutputLevel::Default, 0);
|
||||
scans
|
||||
.collected_extensions
|
||||
.write()
|
||||
|
||||
@@ -15,6 +15,7 @@ use crate::{
|
||||
use indicatif::ProgressBar;
|
||||
use predicates::prelude::*;
|
||||
use regex::Regex;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::{atomic::Ordering, Arc};
|
||||
use std::thread::sleep;
|
||||
use std::time::Instant;
|
||||
@@ -57,7 +58,12 @@ async fn scanner_pause_scan_with_finished_spinner() {
|
||||
fn add_url_to_list_of_scanned_urls_with_unknown_url() {
|
||||
let urls = FeroxScans::default();
|
||||
let url = "http://unknown_url";
|
||||
let (result, _scan) = urls.add_scan(url, ScanType::Directory, ScanOrder::Latest);
|
||||
let (result, _scan) = urls.add_scan(
|
||||
url,
|
||||
ScanType::Directory,
|
||||
ScanOrder::Latest,
|
||||
Arc::new(Handles::for_testing(None, None).0),
|
||||
);
|
||||
assert!(result);
|
||||
}
|
||||
|
||||
@@ -75,11 +81,18 @@ fn add_url_to_list_of_scanned_urls_with_known_url() {
|
||||
pb.length().unwrap(),
|
||||
OutputLevel::Default,
|
||||
Some(pb),
|
||||
true,
|
||||
Arc::new(Handles::for_testing(None, None).0),
|
||||
);
|
||||
|
||||
assert!(urls.insert(scan));
|
||||
|
||||
let (result, _scan) = urls.add_scan(url, ScanType::Directory, ScanOrder::Latest);
|
||||
let (result, _scan) = urls.add_scan(
|
||||
url,
|
||||
ScanType::Directory,
|
||||
ScanOrder::Latest,
|
||||
Arc::new(Handles::for_testing(None, None).0),
|
||||
);
|
||||
|
||||
assert!(!result);
|
||||
}
|
||||
@@ -97,6 +110,8 @@ fn stop_progress_bar_stops_bar() {
|
||||
pb.length().unwrap(),
|
||||
OutputLevel::Default,
|
||||
Some(pb),
|
||||
true,
|
||||
Arc::new(Handles::for_testing(None, None).0),
|
||||
);
|
||||
|
||||
assert!(!scan
|
||||
@@ -107,7 +122,7 @@ fn stop_progress_bar_stops_bar() {
|
||||
.unwrap()
|
||||
.is_finished());
|
||||
|
||||
scan.stop_progress_bar();
|
||||
scan.stop_progress_bar(0);
|
||||
|
||||
assert!(scan
|
||||
.progress_bar
|
||||
@@ -124,18 +139,25 @@ fn add_url_to_list_of_scanned_urls_with_known_url_without_slash() {
|
||||
let urls = FeroxScans::default();
|
||||
let url = "http://unknown_url";
|
||||
|
||||
let scan = FeroxScan::new(
|
||||
let scan: Arc<FeroxScan> = FeroxScan::new(
|
||||
url,
|
||||
ScanType::File,
|
||||
ScanOrder::Latest,
|
||||
0,
|
||||
OutputLevel::Default,
|
||||
None,
|
||||
true,
|
||||
Arc::new(Handles::for_testing(None, None).0),
|
||||
);
|
||||
|
||||
assert!(urls.insert(scan));
|
||||
|
||||
let (result, _scan) = urls.add_scan(url, ScanType::File, ScanOrder::Latest);
|
||||
let (result, _scan) = urls.add_scan(
|
||||
url,
|
||||
ScanType::File,
|
||||
ScanOrder::Latest,
|
||||
Arc::new(Handles::for_testing(None, None).0),
|
||||
);
|
||||
|
||||
assert!(!result);
|
||||
}
|
||||
@@ -155,6 +177,8 @@ async fn call_display_scans() {
|
||||
pb.length().unwrap(),
|
||||
OutputLevel::Default,
|
||||
Some(pb),
|
||||
true,
|
||||
Arc::new(Handles::for_testing(None, None).0),
|
||||
);
|
||||
let scan_two = FeroxScan::new(
|
||||
url_two,
|
||||
@@ -163,9 +187,11 @@ async fn call_display_scans() {
|
||||
pb_two.length().unwrap(),
|
||||
OutputLevel::Default,
|
||||
Some(pb_two),
|
||||
true,
|
||||
Arc::new(Handles::for_testing(None, None).0),
|
||||
);
|
||||
|
||||
scan_two.finish().unwrap(); // one complete, one incomplete
|
||||
scan_two.finish(0).unwrap(); // one complete, one incomplete
|
||||
scan_two
|
||||
.set_task(tokio::spawn(async move {
|
||||
sleep(Duration::from_millis(SLEEP_DURATION));
|
||||
@@ -190,6 +216,8 @@ fn partial_eq_compares_the_id_field() {
|
||||
0,
|
||||
OutputLevel::Default,
|
||||
None,
|
||||
true,
|
||||
Arc::new(Handles::for_testing(None, None).0),
|
||||
);
|
||||
let scan_two = FeroxScan::new(
|
||||
url,
|
||||
@@ -198,10 +226,13 @@ fn partial_eq_compares_the_id_field() {
|
||||
0,
|
||||
OutputLevel::Default,
|
||||
None,
|
||||
true,
|
||||
Arc::new(Handles::for_testing(None, None).0),
|
||||
);
|
||||
|
||||
assert!(!scan.eq(&scan_two));
|
||||
|
||||
#[allow(clippy::redundant_clone)]
|
||||
let scan_two = scan.clone();
|
||||
|
||||
assert!(scan.eq(&scan_two));
|
||||
@@ -279,6 +310,8 @@ fn ferox_scan_serialize() {
|
||||
0,
|
||||
OutputLevel::Default,
|
||||
None,
|
||||
true,
|
||||
Arc::new(Handles::for_testing(None, None).0),
|
||||
);
|
||||
let fs_json = format!(
|
||||
r#"{{"id":"{}","url":"https://spiritanimal.com","normalized_url":"https://spiritanimal.com/","scan_type":"Directory","status":"NotStarted","num_requests":0,"requests_made_so_far":0}}"#,
|
||||
@@ -297,6 +330,8 @@ fn ferox_scans_serialize() {
|
||||
0,
|
||||
OutputLevel::Default,
|
||||
None,
|
||||
true,
|
||||
Arc::new(Handles::for_testing(None, None).0),
|
||||
);
|
||||
let ferox_scans = FeroxScans::default();
|
||||
let ferox_scans_json = format!(
|
||||
@@ -313,7 +348,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();
|
||||
@@ -331,7 +366,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");
|
||||
@@ -342,6 +377,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();
|
||||
@@ -358,6 +394,8 @@ fn feroxstates_feroxserialize_implementation() {
|
||||
0,
|
||||
OutputLevel::Default,
|
||||
None,
|
||||
true,
|
||||
Arc::new(Handles::for_testing(None, None).0),
|
||||
);
|
||||
let ferox_scans = FeroxScans::default();
|
||||
let saved_id = ferox_scan.id.clone();
|
||||
@@ -499,12 +537,15 @@ fn feroxstates_feroxserialize_implementation() {
|
||||
r#""method":"GET""#,
|
||||
r#""content_length":173"#,
|
||||
r#""line_count":10"#,
|
||||
r#""limit_bars":0"#,
|
||||
r#""word_count":16"#,
|
||||
r#""headers""#,
|
||||
r#""server":"nginx/1.16.1"#,
|
||||
r#""collect_extensions":true"#,
|
||||
r#""collect_backups":false"#,
|
||||
r#""collect_words":false"#,
|
||||
r#""scan_dir_listings":false"#,
|
||||
r#""protocol":"https""#,
|
||||
r#""filters":[{"filter_code":100},{"word_count":200},{"content_length":300},{"line_count":400},{"compiled":".*","raw_string":".*"},{"hash":1,"original_url":"http://localhost:12345/"}]"#,
|
||||
r#""collected_extensions":["php"]"#,
|
||||
r#""dont_collect":["tif","tiff","ico","cur","bmp","webp","svg","png","jpg","jpeg","jfif","gif","avif","apng","pjpeg","pjp","mov","wav","mpg","mpeg","mp3","mp4","m4a","m4p","m4v","ogg","webm","ogv","oga","flac","aac","3gp","css","zip","xls","xml","gz","tgz"]"#,
|
||||
@@ -565,8 +606,10 @@ fn feroxscan_display() {
|
||||
normalized_url: String::from("http://localhost/"),
|
||||
scan_order: ScanOrder::Latest,
|
||||
scan_type: Default::default(),
|
||||
handles: Some(Arc::new(Handles::for_testing(None, None).0)),
|
||||
num_requests: 0,
|
||||
requests_made_so_far: 0,
|
||||
visible: AtomicBool::new(true),
|
||||
start_time: Instant::now(),
|
||||
output_level: OutputLevel::Default,
|
||||
status_403s: Default::default(),
|
||||
@@ -615,6 +658,7 @@ async fn ferox_scan_abort() {
|
||||
requests_made_so_far: 0,
|
||||
start_time: Instant::now(),
|
||||
output_level: OutputLevel::Default,
|
||||
visible: AtomicBool::new(true),
|
||||
status_403s: Default::default(),
|
||||
status_429s: Default::default(),
|
||||
status: std::sync::Mutex::new(ScanStatus::Running),
|
||||
@@ -623,9 +667,10 @@ async fn ferox_scan_abort() {
|
||||
}))),
|
||||
progress_bar: std::sync::Mutex::new(None),
|
||||
errors: Default::default(),
|
||||
handles: Some(Arc::new(Handles::for_testing(None, None).0)),
|
||||
};
|
||||
|
||||
scan.abort().await.unwrap();
|
||||
scan.abort(0).await.unwrap();
|
||||
|
||||
assert!(matches!(
|
||||
*scan.status.lock().unwrap(),
|
||||
@@ -716,15 +761,26 @@ fn split_to_nums_is_correct() {
|
||||
#[test]
|
||||
/// given a deep url, find the correct scan
|
||||
fn get_base_scan_by_url_finds_correct_scan() {
|
||||
let handles = Arc::new(Handles::for_testing(None, None).0);
|
||||
let urls = FeroxScans::default();
|
||||
let url = "http://localhost";
|
||||
let url1 = "http://localhost/stuff";
|
||||
let url2 = "http://shlocalhost/stuff/things";
|
||||
let url3 = "http://shlocalhost/stuff/things/mostuff";
|
||||
let (_, scan) = urls.add_scan(url, ScanType::Directory, ScanOrder::Latest);
|
||||
let (_, scan1) = urls.add_scan(url1, ScanType::Directory, ScanOrder::Latest);
|
||||
let (_, scan2) = urls.add_scan(url2, ScanType::Directory, ScanOrder::Latest);
|
||||
let (_, scan3) = urls.add_scan(url3, ScanType::Directory, ScanOrder::Latest);
|
||||
let (_, scan) = urls.add_scan(url, ScanType::Directory, ScanOrder::Latest, handles.clone());
|
||||
let (_, scan1) = urls.add_scan(
|
||||
url1,
|
||||
ScanType::Directory,
|
||||
ScanOrder::Latest,
|
||||
handles.clone(),
|
||||
);
|
||||
let (_, scan2) = urls.add_scan(
|
||||
url2,
|
||||
ScanType::Directory,
|
||||
ScanOrder::Latest,
|
||||
handles.clone(),
|
||||
);
|
||||
let (_, scan3) = urls.add_scan(url3, ScanType::Directory, ScanOrder::Latest, handles);
|
||||
|
||||
assert_eq!(
|
||||
urls.get_base_scan_by_url("http://localhost/things.php")
|
||||
@@ -757,7 +813,12 @@ fn get_base_scan_by_url_finds_correct_scan() {
|
||||
fn get_base_scan_by_url_finds_correct_scan_without_trailing_slash() {
|
||||
let urls = FeroxScans::default();
|
||||
let url = "http://localhost";
|
||||
let (_, scan) = urls.add_scan(url, ScanType::Directory, ScanOrder::Latest);
|
||||
let (_, scan) = urls.add_scan(
|
||||
url,
|
||||
ScanType::Directory,
|
||||
ScanOrder::Latest,
|
||||
Arc::new(Handles::for_testing(None, None).0),
|
||||
);
|
||||
assert_eq!(
|
||||
urls.get_base_scan_by_url("http://localhost/BKPMiherrortBPKcw")
|
||||
.unwrap()
|
||||
@@ -771,7 +832,12 @@ fn get_base_scan_by_url_finds_correct_scan_without_trailing_slash() {
|
||||
fn get_base_scan_by_url_finds_correct_scan_with_trailing_slash() {
|
||||
let urls = FeroxScans::default();
|
||||
let url = "http://127.0.0.1:41971/";
|
||||
let (_, scan) = urls.add_scan(url, ScanType::Directory, ScanOrder::Latest);
|
||||
let (_, scan) = urls.add_scan(
|
||||
url,
|
||||
ScanType::Directory,
|
||||
ScanOrder::Latest,
|
||||
Arc::new(Handles::for_testing(None, None).0),
|
||||
);
|
||||
assert_eq!(
|
||||
urls.get_base_scan_by_url("http://127.0.0.1:41971/BKPMiherrortBPKcw")
|
||||
.unwrap()
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
#[cfg(not(test))]
|
||||
use crate::event_handlers::TermInputHandler;
|
||||
use crate::{
|
||||
config::Configuration, event_handlers::Handles, parser::TIMESPEC_REGEX, scanner::RESPONSES,
|
||||
config::{Configuration, OutputLevel},
|
||||
event_handlers::Handles,
|
||||
parser::TIMESPEC_REGEX,
|
||||
progress::BarType,
|
||||
scan_manager::scan::Visibility,
|
||||
scanner::RESPONSES,
|
||||
};
|
||||
|
||||
use std::{fs::File, io::BufReader, sync::Arc};
|
||||
@@ -90,3 +95,79 @@ pub fn resume_scan(filename: &str) -> Configuration {
|
||||
log::trace!("exit: resume_scan -> {:?}", config);
|
||||
config
|
||||
}
|
||||
|
||||
/// determine the type of progress bar to display
|
||||
/// takes both --limit-bars and output-level (--quiet|--silent|etc)
|
||||
/// into account to arrive at a `BarType`
|
||||
pub fn determine_bar_type(
|
||||
bar_limit: usize,
|
||||
number_of_bars: usize,
|
||||
output_level: OutputLevel,
|
||||
) -> BarType {
|
||||
let visibility = if bar_limit == 0 {
|
||||
// no limit from cli, just set the value to visible
|
||||
// this protects us from a mutex unlock in number_of_bars
|
||||
// in the normal case
|
||||
Visibility::Visible
|
||||
} else if bar_limit < number_of_bars {
|
||||
// active bars exceed limit; hidden
|
||||
Visibility::Hidden
|
||||
} else {
|
||||
Visibility::Visible
|
||||
};
|
||||
|
||||
match (output_level, visibility) {
|
||||
(OutputLevel::Default, Visibility::Visible) => BarType::Default,
|
||||
(OutputLevel::Quiet, Visibility::Visible) => BarType::Quiet,
|
||||
(OutputLevel::Default, Visibility::Hidden) => BarType::Hidden,
|
||||
(OutputLevel::Quiet, Visibility::Hidden) => BarType::Hidden,
|
||||
(OutputLevel::Silent | OutputLevel::SilentJSON, _) => BarType::Hidden,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_no_limit_visible() {
|
||||
let bar_type = determine_bar_type(0, 1, OutputLevel::Default);
|
||||
assert!(matches!(bar_type, BarType::Default));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_limit_exceeded_hidden() {
|
||||
let bar_type = determine_bar_type(1, 2, OutputLevel::Default);
|
||||
assert!(matches!(bar_type, BarType::Hidden));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_limit_not_exceeded_visible() {
|
||||
let bar_type = determine_bar_type(2, 1, OutputLevel::Default);
|
||||
assert!(matches!(bar_type, BarType::Default));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_quiet_visible() {
|
||||
let bar_type = determine_bar_type(0, 1, OutputLevel::Quiet);
|
||||
assert!(matches!(bar_type, BarType::Quiet));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_quiet_hidden() {
|
||||
let bar_type = determine_bar_type(1, 2, OutputLevel::Quiet);
|
||||
assert!(matches!(bar_type, BarType::Hidden));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_silent_hidden() {
|
||||
let bar_type = determine_bar_type(0, 1, OutputLevel::Silent);
|
||||
assert!(matches!(bar_type, BarType::Hidden));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_silent_json_hidden() {
|
||||
let bar_type = determine_bar_type(0, 1, OutputLevel::SilentJSON);
|
||||
assert!(matches!(bar_type, BarType::Hidden));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,9 +214,9 @@ impl FeroxScanner {
|
||||
.url(&self.target_url)
|
||||
.handles(self.handles.clone())
|
||||
.build()?;
|
||||
|
||||
let result = extractor.extract().await?;
|
||||
extraction_tasks.push(extractor.request_links(result).await?)
|
||||
if let Ok(result) = extractor.extract().await {
|
||||
extraction_tasks.push(extractor.request_links(result).await?)
|
||||
}
|
||||
}
|
||||
|
||||
let scanned_urls = self.handles.ferox_scans()?;
|
||||
@@ -251,60 +251,72 @@ impl FeroxScanner {
|
||||
// heuristics test block:
|
||||
let test = heuristics::HeuristicTests::new(self.handles.clone());
|
||||
|
||||
if let Ok(dirlist_result) = test.directory_listing(&self.target_url).await {
|
||||
if dirlist_result.is_some() {
|
||||
let dirlist_result = dirlist_result.unwrap();
|
||||
// at this point, we have a DirListingType, and it's not the None variant
|
||||
// which means we found directory listing based on the heuristic; now we need
|
||||
// to process the links that are available if --extract-links was used
|
||||
if let Ok(Some(dirlist_result)) = test.directory_listing(&self.target_url).await {
|
||||
// at this point, we have a DirListingType, and it's not the None variant
|
||||
// which means we found directory listing based on the heuristic; now we need
|
||||
// to process the links that are available if --extract-links was used
|
||||
|
||||
if self.handles.config.extract_links {
|
||||
let mut extractor = ExtractorBuilder::default()
|
||||
.response(&dirlist_result.response)
|
||||
.target(ExtractionTarget::DirectoryListing)
|
||||
.url(&self.target_url)
|
||||
.handles(self.handles.clone())
|
||||
.build()?;
|
||||
if self.handles.config.extract_links {
|
||||
let mut extractor = ExtractorBuilder::default()
|
||||
.response(&dirlist_result.response)
|
||||
.target(ExtractionTarget::DirectoryListing)
|
||||
.url(&self.target_url)
|
||||
.handles(self.handles.clone())
|
||||
.build()?;
|
||||
|
||||
let result = extractor.extract_from_dir_listing().await?;
|
||||
let result = extractor.extract_from_dir_listing().await?;
|
||||
|
||||
extraction_tasks.push(extractor.request_links(result).await?);
|
||||
extraction_tasks.push(extractor.request_links(result).await?);
|
||||
|
||||
log::trace!("exit: scan_url -> Directory listing heuristic");
|
||||
log::trace!("exit: scan_url -> Directory listing heuristic");
|
||||
|
||||
self.handles.stats.send(AddToF64Field(
|
||||
DirScanTimes,
|
||||
scan_timer.elapsed().as_secs_f64(),
|
||||
))?;
|
||||
self.handles.stats.send(AddToF64Field(
|
||||
DirScanTimes,
|
||||
scan_timer.elapsed().as_secs_f64(),
|
||||
))?;
|
||||
|
||||
self.handles.stats.send(SubtractFromUsizeField(
|
||||
TotalExpected,
|
||||
progress_bar.length().unwrap_or(0) as usize,
|
||||
))?;
|
||||
self.handles.stats.send(SubtractFromUsizeField(
|
||||
TotalExpected,
|
||||
progress_bar.length().unwrap_or(0) as usize,
|
||||
))?;
|
||||
}
|
||||
|
||||
let mut message = format!("=> {}", style("Directory listing").blue().bright());
|
||||
|
||||
if !self.handles.config.scan_dir_listings {
|
||||
write!(
|
||||
message,
|
||||
" (add {} to scan)",
|
||||
style("--scan-dir-listings").bright().yellow()
|
||||
)?;
|
||||
}
|
||||
|
||||
if !self.handles.config.extract_links {
|
||||
write!(
|
||||
message,
|
||||
" (remove {} to scan)",
|
||||
style("--dont-extract-links").bright().yellow()
|
||||
)?;
|
||||
}
|
||||
|
||||
if !self.handles.config.force_recursion && !self.handles.config.scan_dir_listings {
|
||||
for handle in extraction_tasks.into_iter().flatten() {
|
||||
_ = handle.await;
|
||||
}
|
||||
|
||||
let mut message = format!("=> {}", style("Directory listing").blue().bright());
|
||||
progress_bar.reset_eta();
|
||||
progress_bar.finish_with_message(message);
|
||||
|
||||
if !self.handles.config.extract_links {
|
||||
write!(
|
||||
message,
|
||||
" (remove {} to scan)",
|
||||
style("--dont-extract-links").bright().yellow()
|
||||
)?;
|
||||
if self.handles.config.limit_bars > 0 {
|
||||
let scans = self.handles.ferox_scans()?;
|
||||
let num_bars = scans.number_of_bars();
|
||||
ferox_scan.finish(num_bars)?;
|
||||
scans.make_visible();
|
||||
} else {
|
||||
ferox_scan.finish(0)?;
|
||||
}
|
||||
|
||||
if !self.handles.config.force_recursion {
|
||||
for handle in extraction_tasks.into_iter().flatten() {
|
||||
_ = handle.await;
|
||||
}
|
||||
|
||||
progress_bar.reset_eta();
|
||||
progress_bar.finish_with_message(message);
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -385,7 +397,14 @@ impl FeroxScanner {
|
||||
_ = handle.await;
|
||||
}
|
||||
|
||||
ferox_scan.finish()?;
|
||||
if self.handles.config.limit_bars > 0 {
|
||||
let scans = self.handles.ferox_scans()?;
|
||||
let num_bars = scans.number_of_bars();
|
||||
ferox_scan.finish(num_bars)?;
|
||||
scans.make_visible();
|
||||
} else {
|
||||
ferox_scan.finish(0)?;
|
||||
}
|
||||
|
||||
log::trace!("exit: scan_url");
|
||||
|
||||
|
||||
@@ -313,9 +313,12 @@ impl Requester {
|
||||
.set_status(ScanStatus::Cancelled)
|
||||
.unwrap_or_else(|e| log::warn!("Could not set scan status: {}", e));
|
||||
|
||||
let scans = self.handles.ferox_scans()?;
|
||||
let active_bars = scans.number_of_bars();
|
||||
|
||||
// kill the scan
|
||||
self.ferox_scan
|
||||
.abort()
|
||||
.abort(active_bars)
|
||||
.await
|
||||
.unwrap_or_else(|e| log::warn!("Could not bail on scan: {}", e));
|
||||
|
||||
@@ -475,12 +478,13 @@ impl Requester {
|
||||
|
||||
if self.handles.config.collect_words {
|
||||
if let Ok(mut guard) = TF_IDF.write() {
|
||||
let doc = Document::from_html(ferox_response.text());
|
||||
guard.add_document(doc);
|
||||
if guard.num_documents() % 12 == 0
|
||||
|| (guard.num_documents() < 5 && guard.num_documents() % 2 == 0)
|
||||
{
|
||||
guard.calculate_tf_idf_scores();
|
||||
if let Some(doc) = Document::from_html(ferox_response.text()) {
|
||||
guard.add_document(doc);
|
||||
if guard.num_documents() % 12 == 0
|
||||
|| (guard.num_documents() < 5 && guard.num_documents() % 2 == 0)
|
||||
{
|
||||
guard.calculate_tf_idf_scores();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -645,6 +649,8 @@ mod tests {
|
||||
1000,
|
||||
OutputLevel::Default,
|
||||
None,
|
||||
true,
|
||||
handles.clone(),
|
||||
);
|
||||
|
||||
scan.set_status(ScanStatus::Running).unwrap();
|
||||
@@ -1143,6 +1149,8 @@ mod tests {
|
||||
1000,
|
||||
OutputLevel::Default,
|
||||
None,
|
||||
true,
|
||||
Arc::new(Handles::for_testing(None, None).0),
|
||||
);
|
||||
scan.set_status(ScanStatus::Running).unwrap();
|
||||
scan.add_429();
|
||||
@@ -1176,7 +1184,7 @@ mod tests {
|
||||
200
|
||||
);
|
||||
|
||||
scan.finish().unwrap();
|
||||
scan.finish(0).unwrap();
|
||||
assert!(start.elapsed().as_millis() >= 2000);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ use super::*;
|
||||
/// try to hit struct field coverage of FileOutHandler
|
||||
async fn get_scan_by_url_bails_on_unfound_url() {
|
||||
let sem = Semaphore::new(10);
|
||||
let urls = FeroxScans::new(OutputLevel::Default);
|
||||
let urls = FeroxScans::new(OutputLevel::Default, 0);
|
||||
|
||||
let scanner = FeroxScanner::new(
|
||||
"http://localhost",
|
||||
|
||||
@@ -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)?;
|
||||
|
||||
@@ -55,7 +55,7 @@ fn save_writes_stats_object_to_disk() {
|
||||
stats.add_status_code(StatusCode::OK);
|
||||
stats.add_status_code(StatusCode::OK);
|
||||
let outfile = NamedTempFile::new().unwrap();
|
||||
if stats.save(174.33, outfile.path().to_str().unwrap()).is_ok() {}
|
||||
assert!(stats.save(174.33, outfile.path().to_str().unwrap()).is_ok());
|
||||
|
||||
assert!(stats.as_json().unwrap().contains("statistics"));
|
||||
assert!(stats.as_json().unwrap().contains("11")); // requests made
|
||||
|
||||
@@ -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()],
|
||||
|
||||
101
src/utils.rs
101
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
|
||||
@@ -563,6 +577,13 @@ pub fn parse_url_with_raw_path(url: &str) -> Result<Url> {
|
||||
bail!("url to parse has no authority and is therefore invalid");
|
||||
}
|
||||
|
||||
// thanks to @devx00: the possibility exists for Url to return true for
|
||||
// has_authority, but not have a host/port, so we'll check for that
|
||||
// and bail if it's the case
|
||||
if parsed.host().is_none() {
|
||||
bail!("url to parse doesn't have a host");
|
||||
}
|
||||
|
||||
// we have a valid url, the next step is to check the path and see if it's
|
||||
// something that url::Url::parse would silently transform
|
||||
//
|
||||
@@ -648,7 +669,7 @@ pub fn parse_url_with_raw_path(url: &str) -> Result<Url> {
|
||||
// each of the following is a string that we can expect url::Url::parse to
|
||||
// transform. The variety is to ensure we cover most common path traversal
|
||||
// encodings
|
||||
let transformation_detectors = vec![
|
||||
let transformation_detectors = [
|
||||
// ascii
|
||||
"..",
|
||||
// single url encoded
|
||||
@@ -729,6 +750,18 @@ mod tests {
|
||||
use crate::config::Configuration;
|
||||
use crate::scan_manager::{FeroxScans, ScanOrder};
|
||||
|
||||
#[test]
|
||||
/// parse_url_with_raw_path with javascript:// should not throw an unimplemented! error
|
||||
fn utils_parse_url_with_raw_path_javascript() {
|
||||
let url = "javascript://";
|
||||
let parsed = parse_url_with_raw_path(url);
|
||||
assert!(parsed.is_err());
|
||||
assert!(parsed
|
||||
.unwrap_err()
|
||||
.to_string()
|
||||
.contains("url to parse doesn't have a host"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// multiple tests for parse_url_with_raw_path
|
||||
fn utils_parse_url_with_raw_path() {
|
||||
@@ -942,7 +975,11 @@ mod tests {
|
||||
let tested_url = Url::parse("https://testdomain.com/denied/").unwrap();
|
||||
|
||||
let scans = Arc::new(FeroxScans::default());
|
||||
scans.add_directory_scan(scan_url, ScanOrder::Initial);
|
||||
scans.add_directory_scan(
|
||||
scan_url,
|
||||
ScanOrder::Initial,
|
||||
Arc::new(Handles::for_testing(None, None).0),
|
||||
);
|
||||
|
||||
let mut config = Configuration::new().unwrap();
|
||||
config.url_denylist = vec![Url::parse(deny_url).unwrap()];
|
||||
@@ -961,7 +998,11 @@ mod tests {
|
||||
let tested_url = Url::parse("https://testdomain.com/denied/").unwrap();
|
||||
|
||||
let scans = Arc::new(FeroxScans::default());
|
||||
scans.add_directory_scan(scan_url, ScanOrder::Initial);
|
||||
scans.add_directory_scan(
|
||||
scan_url,
|
||||
ScanOrder::Initial,
|
||||
Arc::new(Handles::for_testing(None, None).0),
|
||||
);
|
||||
|
||||
let mut config = Configuration::new().unwrap();
|
||||
config.url_denylist = vec![Url::parse(deny_url).unwrap()];
|
||||
@@ -980,7 +1021,11 @@ mod tests {
|
||||
let tested_url = Url::parse("https://testdomain.com/denied/").unwrap();
|
||||
|
||||
let scans = Arc::new(FeroxScans::default());
|
||||
scans.add_directory_scan(scan_url, ScanOrder::Initial);
|
||||
scans.add_directory_scan(
|
||||
scan_url,
|
||||
ScanOrder::Initial,
|
||||
Arc::new(Handles::for_testing(None, None).0),
|
||||
);
|
||||
|
||||
let mut config = Configuration::new().unwrap();
|
||||
config.url_denylist = vec![Url::parse(deny_url).unwrap()];
|
||||
@@ -1001,7 +1046,11 @@ mod tests {
|
||||
let tested_url = Url::parse("https://testdomain.com/denied/").unwrap();
|
||||
|
||||
let scans = Arc::new(FeroxScans::default());
|
||||
scans.add_directory_scan(scan_url, ScanOrder::Initial);
|
||||
scans.add_directory_scan(
|
||||
scan_url,
|
||||
ScanOrder::Initial,
|
||||
Arc::new(Handles::for_testing(None, None).0),
|
||||
);
|
||||
|
||||
let mut config = Configuration::new().unwrap();
|
||||
config.url_denylist = vec![Url::parse(deny_url).unwrap()];
|
||||
@@ -1029,7 +1078,11 @@ mod tests {
|
||||
let tested_url = Url::parse("https://testdomain.com/denied/").unwrap();
|
||||
|
||||
let scans = Arc::new(FeroxScans::default());
|
||||
scans.add_directory_scan(scan_url, ScanOrder::Initial);
|
||||
scans.add_directory_scan(
|
||||
scan_url,
|
||||
ScanOrder::Initial,
|
||||
Arc::new(Handles::for_testing(None, None).0),
|
||||
);
|
||||
|
||||
let mut config = Configuration::new().unwrap();
|
||||
config.url_denylist = vec![Url::parse(deny_url).unwrap()];
|
||||
@@ -1047,7 +1100,11 @@ mod tests {
|
||||
let tested_url = Url::parse("https://testdomain.com/api/denied/").unwrap();
|
||||
|
||||
let scans = Arc::new(FeroxScans::default());
|
||||
scans.add_directory_scan(scan_url, ScanOrder::Initial);
|
||||
scans.add_directory_scan(
|
||||
scan_url,
|
||||
ScanOrder::Initial,
|
||||
Arc::new(Handles::for_testing(None, None).0),
|
||||
);
|
||||
|
||||
let mut config = Configuration::new().unwrap();
|
||||
config.url_denylist = vec![Url::parse(deny_url).unwrap()];
|
||||
@@ -1066,7 +1123,11 @@ mod tests {
|
||||
let tested_url = Url::parse("https://testdomain.com/not-denied/").unwrap();
|
||||
|
||||
let scans = Arc::new(FeroxScans::default());
|
||||
scans.add_directory_scan(scan_url, ScanOrder::Initial);
|
||||
scans.add_directory_scan(
|
||||
scan_url,
|
||||
ScanOrder::Initial,
|
||||
Arc::new(Handles::for_testing(None, None).0),
|
||||
);
|
||||
|
||||
let mut config = Configuration::new().unwrap();
|
||||
config.url_denylist = vec![Url::parse(deny_url).unwrap()];
|
||||
@@ -1085,7 +1146,11 @@ mod tests {
|
||||
let tested_url = Url::parse("https://testdomain.com/stuff/").unwrap();
|
||||
|
||||
let scans = Arc::new(FeroxScans::default());
|
||||
scans.add_directory_scan(scan_url, ScanOrder::Initial);
|
||||
scans.add_directory_scan(
|
||||
scan_url,
|
||||
ScanOrder::Initial,
|
||||
Arc::new(Handles::for_testing(None, None).0),
|
||||
);
|
||||
|
||||
let mut config = Configuration::new().unwrap();
|
||||
config.url_denylist = vec![Url::parse(deny_url).unwrap()];
|
||||
@@ -1104,7 +1169,11 @@ mod tests {
|
||||
let tested_url = Url::parse("https://testdomain.com/api/not-denied/").unwrap();
|
||||
|
||||
let scans = Arc::new(FeroxScans::default());
|
||||
scans.add_directory_scan(scan_url, ScanOrder::Initial);
|
||||
scans.add_directory_scan(
|
||||
scan_url,
|
||||
ScanOrder::Initial,
|
||||
Arc::new(Handles::for_testing(None, None).0),
|
||||
);
|
||||
|
||||
let mut config = Configuration::new().unwrap();
|
||||
config.url_denylist = vec![Url::parse(deny_url).unwrap()];
|
||||
@@ -1124,7 +1193,11 @@ mod tests {
|
||||
let tested_url = Url::parse("https://testdomain.com/denied/").unwrap();
|
||||
|
||||
let scans = Arc::new(FeroxScans::default());
|
||||
scans.add_directory_scan(scan_url, ScanOrder::Initial);
|
||||
scans.add_directory_scan(
|
||||
scan_url,
|
||||
ScanOrder::Initial,
|
||||
Arc::new(Handles::for_testing(None, None).0),
|
||||
);
|
||||
|
||||
let mut config = Configuration::new().unwrap();
|
||||
config.regex_denylist = vec![Regex::new(deny_pattern).unwrap()];
|
||||
@@ -1145,7 +1218,11 @@ mod tests {
|
||||
let tested_https_url = Url::parse("https://testdomain.com/denied/").unwrap();
|
||||
|
||||
let scans = Arc::new(FeroxScans::default());
|
||||
scans.add_directory_scan(scan_url, ScanOrder::Initial);
|
||||
scans.add_directory_scan(
|
||||
scan_url,
|
||||
ScanOrder::Initial,
|
||||
Arc::new(Handles::for_testing(None, None).0),
|
||||
);
|
||||
|
||||
let mut config = Configuration::new().unwrap();
|
||||
config.regex_denylist = vec![Regex::new(deny_pattern).unwrap()];
|
||||
|
||||
@@ -1146,6 +1146,7 @@ fn banner_prints_parallel() {
|
||||
Command::cargo_bin("feroxbuster")
|
||||
.unwrap()
|
||||
.arg("--stdin")
|
||||
.arg("--quiet")
|
||||
.arg("--parallel")
|
||||
.arg("4316")
|
||||
.arg("--wordlist")
|
||||
@@ -1486,6 +1487,89 @@ fn banner_prints_force_recursion() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// test allows non-existent wordlist to trigger the banner printing to stderr
|
||||
/// expect to see all mandatory prints + scan-dir-listings
|
||||
fn banner_prints_scan_dir_listings() {
|
||||
Command::cargo_bin("feroxbuster")
|
||||
.unwrap()
|
||||
.arg("--url")
|
||||
.arg("http://localhost")
|
||||
.arg("--scan-dir-listings")
|
||||
.arg("--wordlist")
|
||||
.arg("/definitely/doesnt/exist/0cd7fed0-47f4-4b18-a1b0-ac39708c1676")
|
||||
.assert()
|
||||
.success()
|
||||
.stderr(
|
||||
predicate::str::contains("─┬─")
|
||||
.and(predicate::str::contains("Target Url"))
|
||||
.and(predicate::str::contains("http://localhost"))
|
||||
.and(predicate::str::contains("Threads"))
|
||||
.and(predicate::str::contains("Wordlist"))
|
||||
.and(predicate::str::contains("Status Codes"))
|
||||
.and(predicate::str::contains("Timeout (secs)"))
|
||||
.and(predicate::str::contains("User-Agent"))
|
||||
.and(predicate::str::contains("Scan Dir Listings"))
|
||||
.and(predicate::str::contains("─┴─")),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// test allows non-existent wordlist to trigger the banner printing to stderr
|
||||
/// expect to see all mandatory prints + protocol
|
||||
fn banner_prints_protocol() {
|
||||
Command::cargo_bin("feroxbuster")
|
||||
.unwrap()
|
||||
.arg("--url")
|
||||
.arg("localhost")
|
||||
.arg("--protocol")
|
||||
.arg("http")
|
||||
.arg("--wordlist")
|
||||
.arg("/definitely/doesnt/exist/0cd7fed0-47f4-4b18-a1b0-ac39708c1676")
|
||||
.assert()
|
||||
.success()
|
||||
.stderr(
|
||||
predicate::str::contains("─┬─")
|
||||
.and(predicate::str::contains("Target Url"))
|
||||
.and(predicate::str::contains("http://localhost"))
|
||||
.and(predicate::str::contains("Threads"))
|
||||
.and(predicate::str::contains("Wordlist"))
|
||||
.and(predicate::str::contains("Status Codes"))
|
||||
.and(predicate::str::contains("Timeout (secs)"))
|
||||
.and(predicate::str::contains("User-Agent"))
|
||||
.and(predicate::str::contains("Default Protocol"))
|
||||
.and(predicate::str::contains("─┴─")),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// test allows non-existent wordlist to trigger the banner printing to stderr
|
||||
/// expect to see all mandatory prints + protocol
|
||||
fn banner_prints_limit_dirs() {
|
||||
Command::cargo_bin("feroxbuster")
|
||||
.unwrap()
|
||||
.arg("--url")
|
||||
.arg("localhost")
|
||||
.arg("--limit-bars")
|
||||
.arg("3")
|
||||
.arg("--wordlist")
|
||||
.arg("/definitely/doesnt/exist/0cd7fed0-47f4-4b18-a1b0-ac39708c1676")
|
||||
.assert()
|
||||
.success()
|
||||
.stderr(
|
||||
predicate::str::contains("─┬─")
|
||||
.and(predicate::str::contains("Target Url"))
|
||||
.and(predicate::str::contains("http://localhost"))
|
||||
.and(predicate::str::contains("Threads"))
|
||||
.and(predicate::str::contains("Wordlist"))
|
||||
.and(predicate::str::contains("Status Codes"))
|
||||
.and(predicate::str::contains("Timeout (secs)"))
|
||||
.and(predicate::str::contains("User-Agent"))
|
||||
.and(predicate::str::contains("Limit Dir Scan Bars"))
|
||||
.and(predicate::str::contains("─┴─")),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// test allows non-existent wordlist to trigger the banner printing to stderr
|
||||
/// expect to see all mandatory prints + force recursion
|
||||
|
||||
@@ -640,3 +640,73 @@ fn extractor_recurses_into_403_directories() -> Result<(), Box<dyn std::error::E
|
||||
teardown_tmp_directory(tmp_dir);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// robots.txt requests shouldn't fire when --dont-extract-links is used
|
||||
fn robots_text_extraction_doesnt_run_with_dont_extract_links() {
|
||||
let srv = MockServer::start();
|
||||
let (tmp_dir, file) = setup_tmp_directory(&["LICENSE".to_string()], "wordlist").unwrap();
|
||||
|
||||
let mock = srv.mock(|when, then| {
|
||||
when.method(GET).path("/LICENSE");
|
||||
then.status(200).body("im a little teapot"); // 18
|
||||
});
|
||||
|
||||
let mock_two = srv.mock(|when, then| {
|
||||
when.method(GET).path("/robots.txt");
|
||||
then.status(200).body(
|
||||
r#"
|
||||
User-agent: *
|
||||
Crawl-delay: 10
|
||||
# CSS, JS, Images
|
||||
Allow: /misc/*.css$
|
||||
Disallow: /misc/stupidfile.php
|
||||
Disallow: /disallowed-subdir/
|
||||
"#,
|
||||
);
|
||||
});
|
||||
|
||||
let mock_file = srv.mock(|when, then| {
|
||||
when.method(GET).path("/misc/stupidfile.php");
|
||||
then.status(200).body("im a little teapot too"); // 22
|
||||
});
|
||||
|
||||
let mock_scanned_file = srv.mock(|when, then| {
|
||||
when.method(GET).path("/misc/LICENSE");
|
||||
then.status(200).body("i too, am a container for tea"); // 29
|
||||
});
|
||||
|
||||
let mock_dir = srv.mock(|when, _| {
|
||||
when.method(GET).path("/misc/");
|
||||
});
|
||||
|
||||
let mock_disallowed = srv.mock(|when, then| {
|
||||
when.method(GET).path("/disallowed-subdir");
|
||||
then.status(404);
|
||||
});
|
||||
|
||||
let cmd = Command::cargo_bin("feroxbuster")
|
||||
.unwrap()
|
||||
.arg("--url")
|
||||
.arg(srv.url("/"))
|
||||
.arg("--wordlist")
|
||||
.arg(file.as_os_str())
|
||||
.arg("--dont-extract-links")
|
||||
.arg("--no-recursion")
|
||||
.unwrap();
|
||||
|
||||
cmd.assert().success().stdout(
|
||||
predicate::str::contains("/LICENSE")
|
||||
.and(predicate::str::contains("18c"))
|
||||
.and(predicate::str::contains("/misc/stupidfile.php"))
|
||||
.not(),
|
||||
);
|
||||
|
||||
assert_eq!(mock.hits(), 1);
|
||||
assert_eq!(mock_dir.hits(), 0);
|
||||
assert_eq!(mock_two.hits(), 0);
|
||||
assert_eq!(mock_file.hits(), 0);
|
||||
assert_eq!(mock_disallowed.hits(), 0);
|
||||
assert_eq!(mock_scanned_file.hits(), 0);
|
||||
teardown_tmp_directory(tmp_dir);
|
||||
}
|
||||
|
||||
@@ -247,3 +247,95 @@ fn filters_similar_should_filter_response() {
|
||||
assert_eq!(not_similar.hits(), 1);
|
||||
teardown_tmp_directory(tmp_dir);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// when using --collect-backups, should only see results in output
|
||||
/// when the response shouldn't be otherwise filtered
|
||||
fn collect_backups_should_be_filtered() {
|
||||
let srv = MockServer::start();
|
||||
let (tmp_dir, file) = setup_tmp_directory(&["LICENSE".to_string()], "wordlist").unwrap();
|
||||
|
||||
let mock = srv.mock(|when: httpmock::When, then| {
|
||||
when.method(GET).path("/LICENSE");
|
||||
then.status(200).body("this is a test");
|
||||
});
|
||||
|
||||
let mock_two = srv.mock(|when, then| {
|
||||
when.method(GET).path("/LICENSE.bak");
|
||||
then.status(201)
|
||||
.body("im a backup file, but filtered out because im not 200");
|
||||
});
|
||||
|
||||
let cmd = Command::cargo_bin("feroxbuster")
|
||||
.unwrap()
|
||||
.arg("--url")
|
||||
.arg(srv.url("/"))
|
||||
.arg("--wordlist")
|
||||
.arg(file.as_os_str())
|
||||
.arg("--status-codes")
|
||||
.arg("200")
|
||||
.arg("--collect-backups")
|
||||
.unwrap();
|
||||
|
||||
cmd.assert().success().stdout(
|
||||
predicate::str::contains("/LICENSE")
|
||||
.and(predicate::str::contains("200"))
|
||||
.and(predicate::str::contains("/LICENSE.bak"))
|
||||
.not()
|
||||
.and(predicate::str::contains("201"))
|
||||
.not(),
|
||||
);
|
||||
|
||||
assert_eq!(mock.hits(), 1);
|
||||
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(())
|
||||
|
||||
@@ -108,10 +108,11 @@ fn main_parallel_spawns_children() -> Result<(), Box<dyn std::error::Error>> {
|
||||
|
||||
Command::cargo_bin("feroxbuster")
|
||||
.unwrap()
|
||||
.env("RUST_LOG", "trace")
|
||||
.arg("--stdin")
|
||||
.arg("--parallel")
|
||||
.arg("2")
|
||||
.arg("-vvvv")
|
||||
.arg("--quiet")
|
||||
.arg("--debug-log")
|
||||
.arg(outfile.as_os_str())
|
||||
.arg("--wordlist")
|
||||
@@ -172,6 +173,7 @@ fn main_parallel_creates_output_directory() -> Result<(), Box<dyn std::error::Er
|
||||
Command::cargo_bin("feroxbuster")
|
||||
.unwrap()
|
||||
.arg("--stdin")
|
||||
.arg("--quiet")
|
||||
.arg("--parallel")
|
||||
.arg("2")
|
||||
.arg("--output")
|
||||
|
||||
@@ -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();
|
||||
@@ -37,7 +38,7 @@ fn auto_bail_cancels_scan_with_timeouts() {
|
||||
let error_mock = srv.mock(|when, then| {
|
||||
when.method(GET)
|
||||
.path_matches(Regex::new("/[a-zA-Z]{6}error[a-zA-Z]{6}").unwrap());
|
||||
then.delay(Duration::new(3, 0))
|
||||
then.delay(Duration::new(2, 5000))
|
||||
.status(200)
|
||||
.body("verboten, nerd");
|
||||
});
|
||||
@@ -65,10 +66,10 @@ fn auto_bail_cancels_scan_with_timeouts() {
|
||||
.arg("--timeout")
|
||||
.arg("2")
|
||||
.arg("--threads")
|
||||
.arg("4")
|
||||
.arg("8")
|
||||
.arg("--debug-log")
|
||||
.arg(logfile.as_os_str())
|
||||
.arg("-vvvv")
|
||||
.arg("-vv")
|
||||
.arg("--json")
|
||||
.assert()
|
||||
.success();
|
||||
@@ -146,7 +147,7 @@ fn auto_bail_cancels_scan_with_403s() {
|
||||
.arg("4")
|
||||
.arg("--debug-log")
|
||||
.arg(logfile.as_os_str())
|
||||
.arg("-vvvv")
|
||||
.arg("-vv")
|
||||
.arg("--json")
|
||||
.assert()
|
||||
.success();
|
||||
@@ -228,7 +229,7 @@ fn auto_bail_cancels_scan_with_429s() {
|
||||
.arg("4")
|
||||
.arg("--debug-log")
|
||||
.arg(logfile.as_os_str())
|
||||
.arg("-vvvv")
|
||||
.arg("-vvv")
|
||||
.arg("--json")
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
@@ -430,6 +430,7 @@ fn scanner_single_request_scan_with_filtered_result() -> Result<(), Box<dyn std:
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic] // added in 2.11.0 for panicking trace-level logging
|
||||
/// send a single valid request, get a response, and write the logging messages to disk
|
||||
fn scanner_single_request_scan_with_debug_logging() {
|
||||
let srv = MockServer::start();
|
||||
@@ -467,6 +468,7 @@ fn scanner_single_request_scan_with_debug_logging() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic] // added in 2.11.0 for panicking trace-level logging
|
||||
/// send a single valid request, get a response, and write the logging messages to disk as NDJSON
|
||||
fn scanner_single_request_scan_with_debug_logging_as_json() {
|
||||
let srv = MockServer::start();
|
||||
@@ -693,7 +695,7 @@ fn collect_backups_makes_appropriate_requests() {
|
||||
let srv = MockServer::start();
|
||||
let (tmp_dir, file) = setup_tmp_directory(&["LICENSE.txt".to_string()], "wordlist").unwrap();
|
||||
|
||||
let valid_paths = vec![
|
||||
let valid_paths = [
|
||||
"/LICENSE.txt",
|
||||
"/LICENSE.txt~",
|
||||
"/LICENSE.txt.bak",
|
||||
|
||||
Reference in New Issue
Block a user