mirror of
https://github.com/epi052/feroxbuster.git
synced 2026-06-05 00:01:12 -03:00
Compare commits
43 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
393e775285 | ||
|
|
cf6c02307c | ||
|
|
88b9bc3a01 | ||
|
|
d1f90efb09 | ||
|
|
df4fad07a9 | ||
|
|
56d533117e | ||
|
|
9549e27f19 | ||
|
|
1677b51c2d | ||
|
|
d4f9442d38 | ||
|
|
8191fa1a5e | ||
|
|
4811b37aa4 | ||
|
|
941cad5844 | ||
|
|
d59af94f62 | ||
|
|
cf403c4d4a | ||
|
|
57a2b1cbab | ||
|
|
ef195bd653 | ||
|
|
9b1a24bca3 | ||
|
|
c6aefbfa97 | ||
|
|
42bad85208 | ||
|
|
f5709739fa | ||
|
|
248f56ed7a | ||
|
|
3de6ed9696 | ||
|
|
4bad39f4b9 | ||
|
|
9b303d8b5a | ||
|
|
7e0b003216 | ||
|
|
dc36a7bf4d | ||
|
|
d33632c421 | ||
|
|
7dc6a867a5 | ||
|
|
b937a0191e | ||
|
|
d57a83956c | ||
|
|
71efd78f03 | ||
|
|
139006d0a7 | ||
|
|
b5abb8b6e8 | ||
|
|
a076a333df | ||
|
|
461ed0a9ff | ||
|
|
4381569a0f | ||
|
|
a52bd10340 | ||
|
|
56a1144865 | ||
|
|
23ab009c08 | ||
|
|
fa4e3d5d88 | ||
|
|
ad7a1ffe44 | ||
|
|
0e4f8893f8 | ||
|
|
8e0b801ec5 |
5
.cargo/config
Normal file
5
.cargo/config
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
[target.armv7-unknown-linux-gnueabihf]
|
||||||
|
linker = "arm-linux-gnueabihf-gcc"
|
||||||
|
|
||||||
|
[target.aarch64-unknown-linux-gnu]
|
||||||
|
linker = "aarch64-linux-gnu-gcc"
|
||||||
4
.github/FUNDING.yml
vendored
Normal file
4
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# These are supported funding model platforms
|
||||||
|
|
||||||
|
github: [epi052]
|
||||||
|
ko_fi: epi052
|
||||||
18
.github/workflows/build.yml
vendored
18
.github/workflows/build.yml
vendored
@@ -8,7 +8,7 @@ jobs:
|
|||||||
if: github.ref == 'refs/heads/main'
|
if: github.ref == 'refs/heads/main'
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
type: [ubuntu-x64, ubuntu-x86]
|
type: [ubuntu-x64, ubuntu-x86, armv7, aarch64]
|
||||||
include:
|
include:
|
||||||
- type: ubuntu-x64
|
- type: ubuntu-x64
|
||||||
os: ubuntu-latest
|
os: ubuntu-latest
|
||||||
@@ -22,12 +22,24 @@ jobs:
|
|||||||
name: x86-linux-feroxbuster
|
name: x86-linux-feroxbuster
|
||||||
path: target/i686-unknown-linux-musl/release/feroxbuster
|
path: target/i686-unknown-linux-musl/release/feroxbuster
|
||||||
pkg_config_path: /usr/lib/i686-linux-gnu/pkgconfig
|
pkg_config_path: /usr/lib/i686-linux-gnu/pkgconfig
|
||||||
|
- type: armv7
|
||||||
|
os: ubuntu-latest
|
||||||
|
target: armv7-unknown-linux-gnueabihf
|
||||||
|
name: armv7-feroxbuster
|
||||||
|
path: target/armv7-unknown-linux-gnueabihf/release/feroxbuster
|
||||||
|
pkg_config_path: /usr/lib/x86_64-linux-gnu/pkgconfig
|
||||||
|
- type: aarch64
|
||||||
|
os: ubuntu-latest
|
||||||
|
target: aarch64-unknown-linux-gnu
|
||||||
|
name: aarch64-feroxbuster
|
||||||
|
path: target/aarch64-unknown-linux-gnu/release/feroxbuster
|
||||||
|
pkg_config_path: /usr/lib/x86_64-linux-gnu/pkgconfig
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Install System Dependencies
|
- name: Install System Dependencies
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y --no-install-recommends libssl-dev pkg-config
|
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-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
toolchain: stable
|
toolchain: stable
|
||||||
@@ -43,7 +55,7 @@ jobs:
|
|||||||
args: --release --target=${{ matrix.target }}
|
args: --release --target=${{ matrix.target }}
|
||||||
- name: Strip symbols from binary
|
- name: Strip symbols from binary
|
||||||
run: |
|
run: |
|
||||||
strip -s ${{ matrix.path }}
|
strip -s ${{ matrix.path }} || arm-linux-gnueabihf-strip -s ${{ matrix.path }} || aarch64-linux-gnu-strip -s ${{ matrix.path }}
|
||||||
- name: Build tar.gz for homebrew installs
|
- name: Build tar.gz for homebrew installs
|
||||||
if: matrix.type == 'ubuntu-x64'
|
if: matrix.type == 'ubuntu-x64'
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
557
Cargo.lock
generated
557
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
16
Cargo.toml
16
Cargo.toml
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "feroxbuster"
|
name = "feroxbuster"
|
||||||
version = "2.2.2"
|
version = "2.2.4"
|
||||||
authors = ["Ben 'epi' Risher <epibar052@gmail.com>"]
|
authors = ["Ben 'epi' Risher <epibar052@gmail.com>"]
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
@@ -21,12 +21,12 @@ regex = "1"
|
|||||||
lazy_static = "1.4"
|
lazy_static = "1.4"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
futures = { version = "0.3.13"}
|
futures = { version = "0.3.14"}
|
||||||
tokio = { version = "1.2.0", features = ["full"] }
|
tokio = { version = "1.5.0", features = ["full"] }
|
||||||
tokio-util = {version = "0.6.3", features = ["codec"]}
|
tokio-util = {version = "0.6.6", features = ["codec"]}
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
env_logger = "0.8.3"
|
env_logger = "0.8.3"
|
||||||
reqwest = { version = "0.11.1", features = ["socks"] }
|
reqwest = { version = "0.11", features = ["socks"] }
|
||||||
clap = "2.33"
|
clap = "2.33"
|
||||||
lazy_static = "1.4"
|
lazy_static = "1.4"
|
||||||
toml = "0.5"
|
toml = "0.5"
|
||||||
@@ -40,16 +40,16 @@ dirs = "3.0"
|
|||||||
regex = "1"
|
regex = "1"
|
||||||
crossterm = "0.19"
|
crossterm = "0.19"
|
||||||
rlimit = "0.5.4"
|
rlimit = "0.5.4"
|
||||||
ctrlc = "3.1.8"
|
ctrlc = "3.1.9"
|
||||||
fuzzyhash = "0.2.1"
|
fuzzyhash = "0.2.1"
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
leaky-bucket = "0.10.0"
|
leaky-bucket = "0.10.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempfile = "3.1"
|
tempfile = "3.1"
|
||||||
httpmock = "0.5.2"
|
httpmock = "0.5.8"
|
||||||
assert_cmd = "1.0.3"
|
assert_cmd = "1.0.3"
|
||||||
predicates = "1.0.7"
|
predicates = "1.0.8"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
lto = true
|
lto = true
|
||||||
|
|||||||
18
README.md
18
README.md
@@ -119,8 +119,9 @@ Enumeration.
|
|||||||
|
|
||||||
### Download a Release
|
### Download a Release
|
||||||
|
|
||||||
Releases for multiple architectures can be found in the [Releases](https://github.com/epi052/feroxbuster/releases)
|
Releases for `armv7`, `aarch64`, and an `x86_64 .deb` can be found in the [Releases](https://github.com/epi052/feroxbuster/releases) section.
|
||||||
section. The latest release for each of the following systems can be downloaded and executed as shown below.
|
|
||||||
|
All other OS/architecture combinations can be installed dynamically using one of the methods shown below.
|
||||||
|
|
||||||
#### Linux (32 and 64-bit) & MacOS
|
#### Linux (32 and 64-bit) & MacOS
|
||||||
|
|
||||||
@@ -224,6 +225,14 @@ Install `feroxbuster-git` on Arch Linux with your AUR helper of choice:
|
|||||||
yay -S feroxbuster-git
|
yay -S feroxbuster-git
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### BlackArch install
|
||||||
|
|
||||||
|
Install `feroxbuster` on BlackArch Linux:
|
||||||
|
|
||||||
|
```
|
||||||
|
pacman -S feroxbuster
|
||||||
|
```
|
||||||
|
|
||||||
### Docker Install
|
### Docker Install
|
||||||
|
|
||||||
> The following steps assume you have docker installed / setup
|
> The following steps assume you have docker installed / setup
|
||||||
@@ -827,10 +836,11 @@ Below is an example of the Scan Cancel Menu™.
|
|||||||
Using the menu is pretty simple:
|
Using the menu is pretty simple:
|
||||||
- Press `ENTER` to view the menu
|
- Press `ENTER` to view the menu
|
||||||
- Choose a scan to cancel by entering its scan index (`1`)
|
- Choose a scan to cancel by entering its scan index (`1`)
|
||||||
- more than one scan can be selected by using a comma-separated list (`1,2,3` ... etc)
|
- more than one scan can be selected by using a comma-separated list of indexes and/or ranges (`1-4,8,9-13` ... etc)
|
||||||
- Confirm selections, after which all non-cancelled scans will resume
|
- Confirm selections, after which all non-cancelled scans will resume
|
||||||
|
- To skip confirmation, simply add a `-f` somewhere in your input (`3-5 -f`)
|
||||||
|
|
||||||
Here is a short demonstration of cancelling two in-progress scans found via recursion.
|
Here is a short demonstration of force cancelling a range of scans followed by a single scan with interactive prompt.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 46 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 313 KiB After Width: | Height: | Size: 670 KiB |
@@ -3,59 +3,63 @@
|
|||||||
BASE_URL=https://github.com/epi052/feroxbuster/releases/latest/download
|
BASE_URL=https://github.com/epi052/feroxbuster/releases/latest/download
|
||||||
|
|
||||||
MAC_ZIP=x86_64-macos-feroxbuster.zip
|
MAC_ZIP=x86_64-macos-feroxbuster.zip
|
||||||
MAC_URL="${BASE_URL}/${MAC_ZIP}"
|
MAC_URL="$BASE_URL/$MAC_ZIP"
|
||||||
|
|
||||||
LIN32_ZIP=x86-linux-feroxbuster.zip
|
LIN32_ZIP=x86-linux-feroxbuster.zip
|
||||||
LIN32_URL="${BASE_URL}/${LIN32_ZIP}"
|
LIN32_URL="$BASE_URL/$LIN32_ZIP"
|
||||||
|
|
||||||
LIN64_ZIP=x86_64-linux-feroxbuster.zip
|
LIN64_ZIP=x86_64-linux-feroxbuster.zip
|
||||||
LIN64_URL="${BASE_URL}/${LIN64_ZIP}"
|
LIN64_URL="$BASE_URL/$LIN64_ZIP"
|
||||||
|
|
||||||
EMOJI_URL=https://gist.github.com/epi052/8196b550ea51d0907ad4b93751b1b57d/raw/6112c9f32ae07922983fdc549c54fd3fb9a38e4c/NotoColorEmoji.ttf
|
EMOJI_URL=https://gist.github.com/epi052/8196b550ea51d0907ad4b93751b1b57d/raw/6112c9f32ae07922983fdc549c54fd3fb9a38e4c/NotoColorEmoji.ttf
|
||||||
|
|
||||||
echo "[+] Installing feroxbuster!"
|
echo "[+] Installing feroxbuster!"
|
||||||
|
|
||||||
if [[ "$(uname)" == "Darwin" ]]; then
|
which unzip &>/dev/null
|
||||||
echo "[=] Found MacOS, downloading from ${MAC_URL}"
|
if [ "$?" = "0" ]; then
|
||||||
|
echo "[+] unzip found"
|
||||||
curl -sLO "${MAC_URL}"
|
else
|
||||||
unzip -o "${MAC_ZIP}" > /dev/null
|
echo "[ ] unzip not found, exiting. "
|
||||||
rm "${MAC_ZIP}"
|
exit -1
|
||||||
elif [[ "$(expr substr $(uname -s) 1 5)" == "Linux" ]]; then
|
|
||||||
if [[ $(getconf LONG_BIT) == 32 ]]; then
|
|
||||||
echo "[=] Found 32-bit Linux, downloading from ${LIN32_URL}"
|
|
||||||
|
|
||||||
curl -sLO "${LIN32_URL}"
|
|
||||||
unzip -o "${LIN32_ZIP}" > /dev/null
|
|
||||||
rm "${LIN32_ZIP}"
|
|
||||||
else
|
|
||||||
echo "[=] Found 64-bit Linux, downloading from ${LIN64_URL}"
|
|
||||||
|
|
||||||
curl -sLO "${LIN64_URL}"
|
|
||||||
unzip -o "${LIN64_ZIP}" > /dev/null
|
|
||||||
rm "${LIN64_ZIP}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -e ~/.fonts/NotoColorEmoji.ttf ]]; then
|
|
||||||
echo "[=] Found Noto Emoji Font, skipping install"
|
|
||||||
else
|
|
||||||
echo "[=] Installing Noto Emoji Font"
|
|
||||||
mkdir -p ~/.fonts
|
|
||||||
pushd ~/.fonts 2>&1 >/dev/null
|
|
||||||
|
|
||||||
curl -sLO "${EMOJI_URL}"
|
|
||||||
|
|
||||||
fc-cache -f -v >/dev/null
|
|
||||||
|
|
||||||
popd 2>&1 >/dev/null
|
|
||||||
echo "[+] Noto Emoji Font installed"
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [[ "$(uname)" == "Darwin" ]]; then
|
||||||
|
echo "[=] Found MacOS, downloading from $MAC_URL"
|
||||||
|
|
||||||
|
curl -sLO "$MAC_URL"
|
||||||
|
unzip -o "$MAC_ZIP" >/dev/null
|
||||||
|
rm "$MAC_ZIP"
|
||||||
|
elif [[ "$(expr substr $(uname -s) 1 5)" == "Linux" ]]; then
|
||||||
|
if [[ $(getconf LONG_BIT) == 32 ]]; then
|
||||||
|
echo "[=] Found 32-bit Linux, downloading from $LIN32_URL"
|
||||||
|
|
||||||
|
curl -sLO "$LIN32_URL"
|
||||||
|
unzip -o "$LIN32_ZIP" >/dev/null
|
||||||
|
rm "$LIN32_ZIP"
|
||||||
|
else
|
||||||
|
echo "[=] Found 64-bit Linux, downloading from $LIN64_URL"
|
||||||
|
|
||||||
|
curl -sLO "$LIN64_URL"
|
||||||
|
unzip -o "$LIN64_ZIP" >/dev/null
|
||||||
|
rm "$LIN64_ZIP"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -e ~/.fonts/NotoColorEmoji.ttf ]]; then
|
||||||
|
echo "[=] Found Noto Emoji Font, skipping install"
|
||||||
|
else
|
||||||
|
echo "[=] Installing Noto Emoji Font"
|
||||||
|
mkdir -p ~/.fonts
|
||||||
|
pushd ~/.fonts 2>&1 >/dev/null
|
||||||
|
|
||||||
|
curl -sLO "$EMOJI_URL"
|
||||||
|
|
||||||
|
fc-cache -f -v >/dev/null
|
||||||
|
|
||||||
|
popd 2>&1 >/dev/null
|
||||||
|
echo "[+] Noto Emoji Font installed"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
chmod +x ./feroxbuster
|
chmod +x ./feroxbuster
|
||||||
|
|
||||||
echo "[+] Installed feroxbuster version $(./feroxbuster -V)"
|
echo "[+] Installed feroxbuster version $(./feroxbuster -V)"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -364,9 +364,10 @@ fn config_reads_headers() {
|
|||||||
/// parse the test config and see that the values parsed are correct
|
/// parse the test config and see that the values parsed are correct
|
||||||
fn config_reads_queries() {
|
fn config_reads_queries() {
|
||||||
let config = setup_config_test();
|
let config = setup_config_test();
|
||||||
let mut queries = vec![];
|
let queries = vec![
|
||||||
queries.push(("name".to_string(), "value".to_string()));
|
("name".to_string(), "value".to_string()),
|
||||||
queries.push(("rick".to_string(), "astley".to_string()));
|
("rick".to_string(), "astley".to_string()),
|
||||||
|
];
|
||||||
assert_eq!(config.queries, queries);
|
assert_eq!(config.queries, queries);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -139,8 +139,8 @@ impl TermOutHandler {
|
|||||||
Self {
|
Self {
|
||||||
receiver,
|
receiver,
|
||||||
tx_file,
|
tx_file,
|
||||||
config,
|
|
||||||
file_task,
|
file_task,
|
||||||
|
config,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,9 +31,10 @@ impl Menu {
|
|||||||
let separator = "─".to_string();
|
let separator = "─".to_string();
|
||||||
|
|
||||||
let instructions = format!(
|
let instructions = format!(
|
||||||
"Enter a {} list of indexes to {} (ex: 2,3)",
|
"Enter a {} list of indexes/ranges to {} ({}: 1-4,8,9-13)",
|
||||||
style("comma-separated").yellow(),
|
style("comma-separated").yellow(),
|
||||||
style("cancel").red(),
|
style("cancel").red(),
|
||||||
|
style("ex").cyan(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let name = format!(
|
let name = format!(
|
||||||
@@ -43,14 +44,22 @@ impl Menu {
|
|||||||
"💀"
|
"💀"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let force_msg = format!(
|
||||||
|
"Add {} to {} confirmation ({}: 3-5 -f)",
|
||||||
|
style("-f").yellow(),
|
||||||
|
style("skip").yellow(),
|
||||||
|
style("ex").cyan(),
|
||||||
|
);
|
||||||
|
|
||||||
let longest = measure_text_width(&instructions).max(measure_text_width(&name));
|
let longest = measure_text_width(&instructions).max(measure_text_width(&name));
|
||||||
|
|
||||||
let border = separator.repeat(longest);
|
let border = separator.repeat(longest);
|
||||||
|
|
||||||
let padded_name = pad_str(&name, longest, Alignment::Center, None);
|
let padded_name = pad_str(&name, longest, Alignment::Center, None);
|
||||||
|
let padded_force = pad_str(&force_msg, longest, Alignment::Center, None);
|
||||||
|
|
||||||
let header = format!("{}\n{}\n{}", border, padded_name, border);
|
let header = format!("{}\n{}\n{}", border, padded_name, border);
|
||||||
let footer = format!("{}\n{}\n{}", border, instructions, border);
|
let footer = format!("{}\n{}\n{}\n{}", border, instructions, padded_force, border);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
separator,
|
separator,
|
||||||
@@ -93,23 +102,71 @@ impl Menu {
|
|||||||
self.term.write_line(msg).unwrap_or_default();
|
self.term.write_line(msg).unwrap_or_default();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// split a string into vec of usizes
|
/// Helper for parsing a usize from a str
|
||||||
pub(super) fn split_to_nums(&self, line: &str) -> Vec<usize> {
|
fn str_to_usize(&self, value: &str) -> usize {
|
||||||
line.split(',')
|
if value.is_empty() {
|
||||||
.map(|s| {
|
return 0;
|
||||||
s.trim().to_string().parse::<usize>().unwrap_or_else(|e| {
|
}
|
||||||
self.println(&format!("Found non-numeric input: {}", e));
|
|
||||||
0
|
value
|
||||||
})
|
.trim()
|
||||||
|
.to_string()
|
||||||
|
.parse::<usize>()
|
||||||
|
.unwrap_or_else(|e| {
|
||||||
|
self.println(&format!("Found non-numeric input: {}: {:?}", e, value));
|
||||||
|
0
|
||||||
})
|
})
|
||||||
.filter(|m| *m != 0)
|
}
|
||||||
.collect()
|
|
||||||
|
/// split a comma delimited string into vec of usizes
|
||||||
|
pub(super) fn split_to_nums(&self, line: &str) -> Vec<usize> {
|
||||||
|
let mut nums = Vec::new();
|
||||||
|
let values = line.split(',');
|
||||||
|
|
||||||
|
for mut value in values {
|
||||||
|
value = value.trim();
|
||||||
|
|
||||||
|
if value.contains('-') {
|
||||||
|
// range of two values, needs further processing
|
||||||
|
|
||||||
|
let range: Vec<usize> = value
|
||||||
|
.split('-')
|
||||||
|
.map(|s| self.str_to_usize(s))
|
||||||
|
.filter(|m| *m != 0)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if range.len() != 2 {
|
||||||
|
// expecting [1, 4] or similar, if a 0 was used, we'd be left with a vec of size 1
|
||||||
|
self.println(&format!("Found invalid range of scans: {}", value));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
(range[0]..=range[1]).for_each(|n| {
|
||||||
|
// iterate from lower to upper bound and add all interim values, skipping
|
||||||
|
// any already known
|
||||||
|
if !nums.contains(&n) {
|
||||||
|
nums.push(n)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
let value = self.str_to_usize(value);
|
||||||
|
|
||||||
|
if value != 0 && !nums.contains(&value) {
|
||||||
|
// the zeroth scan is always skipped, skip already known values
|
||||||
|
nums.push(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nums
|
||||||
}
|
}
|
||||||
|
|
||||||
/// get comma-separated list of scan indexes from the user
|
/// get comma-separated list of scan indexes from the user
|
||||||
pub(super) fn get_scans_from_user(&self) -> Option<Vec<usize>> {
|
pub(super) fn get_scans_from_user(&self) -> Option<(Vec<usize>, bool)> {
|
||||||
if let Ok(line) = self.term.read_line() {
|
if let Ok(line) = self.term.read_line() {
|
||||||
Some(self.split_to_nums(&line))
|
let force = line.contains("-f");
|
||||||
|
let line = line.replace("-f", "");
|
||||||
|
Some((self.split_to_nums(&line), force))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -252,7 +252,7 @@ impl FeroxScans {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Given a list of indexes, cancel their associated FeroxScans
|
/// Given a list of indexes, cancel their associated FeroxScans
|
||||||
async fn cancel_scans(&self, indexes: Vec<usize>) -> usize {
|
async fn cancel_scans(&self, indexes: Vec<usize>, force: bool) -> usize {
|
||||||
let menu_pause_duration = Duration::from_millis(SLEEP_DURATION);
|
let menu_pause_duration = Duration::from_millis(SLEEP_DURATION);
|
||||||
|
|
||||||
let mut num_cancelled = 0_usize;
|
let mut num_cancelled = 0_usize;
|
||||||
@@ -273,7 +273,11 @@ impl FeroxScans {
|
|||||||
Err(..) => continue,
|
Err(..) => continue,
|
||||||
};
|
};
|
||||||
|
|
||||||
let input = self.menu.confirm_cancellation(&selected.url);
|
let input = if force {
|
||||||
|
'y'
|
||||||
|
} else {
|
||||||
|
self.menu.confirm_cancellation(&selected.url)
|
||||||
|
};
|
||||||
|
|
||||||
if input == 'y' || input == '\n' {
|
if input == 'y' || input == '\n' {
|
||||||
self.menu.println(&format!("Stopping {}...", selected.url));
|
self.menu.println(&format!("Stopping {}...", selected.url));
|
||||||
@@ -305,8 +309,8 @@ impl FeroxScans {
|
|||||||
|
|
||||||
let mut num_cancelled = 0_usize;
|
let mut num_cancelled = 0_usize;
|
||||||
|
|
||||||
if let Some(input) = self.menu.get_scans_from_user() {
|
if let Some((input, force)) = self.menu.get_scans_from_user() {
|
||||||
num_cancelled += self.cancel_scans(input).await;
|
num_cancelled += self.cancel_scans(input, force).await;
|
||||||
};
|
};
|
||||||
|
|
||||||
self.menu.clear_screen();
|
self.menu.clear_screen();
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ impl FeroxSerialize for FeroxState {
|
|||||||
|
|
||||||
/// Simple call to produce a JSON string using the given FeroxState
|
/// Simple call to produce a JSON string using the given FeroxState
|
||||||
fn as_json(&self) -> Result<String> {
|
fn as_json(&self) -> Result<String> {
|
||||||
Ok(serde_json::to_string(&self)
|
serde_json::to_string(&self)
|
||||||
.with_context(|| fmt_err("Could not convert scan's running state to JSON"))?)
|
.with_context(|| fmt_err("Could not convert scan's running state to JSON"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -521,9 +521,13 @@ fn menu_print_header_and_footer() {
|
|||||||
fn split_to_nums_is_correct() {
|
fn split_to_nums_is_correct() {
|
||||||
let menu = Menu::new();
|
let menu = Menu::new();
|
||||||
|
|
||||||
let nums = menu.split_to_nums("1, 3, 4");
|
let nums = menu.split_to_nums("1, 3, 4, 7 - 12, 10-10, 10-11, 9-12, 12-6, -1, 4-");
|
||||||
|
|
||||||
assert_eq!(nums, vec![1, 3, 4]);
|
assert_eq!(nums, vec![1, 3, 4, 7, 8, 9, 10, 11, 12]);
|
||||||
|
assert_eq!(menu.split_to_nums("9-12"), vec![9, 10, 11, 12]);
|
||||||
|
assert!(menu.split_to_nums("-12").is_empty());
|
||||||
|
assert!(menu.split_to_nums("12-").is_empty());
|
||||||
|
assert!(menu.split_to_nums("\n").is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ pub async fn start_max_time_thread(handles: Arc<Handles>) {
|
|||||||
log::trace!("exit: start_max_time_thread");
|
log::trace!("exit: start_max_time_thread");
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
panic!(handles);
|
panic!("{:?}", handles);
|
||||||
#[cfg(not(test))]
|
#[cfg(not(test))]
|
||||||
let _ = TermInputHandler::sigint_handler(handles.clone());
|
let _ = TermInputHandler::sigint_handler(handles.clone());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
use std::{
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
convert::TryFrom,
|
||||||
fs::File,
|
fs::File,
|
||||||
io::BufReader,
|
io::BufReader,
|
||||||
sync::{
|
sync::{
|
||||||
@@ -9,7 +11,8 @@ use std::{
|
|||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use reqwest::StatusCode;
|
use reqwest::StatusCode;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{ser::SerializeStruct, Deserialize, Deserializer, Serialize, Serializer};
|
||||||
|
use serde_json::Value;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
traits::FeroxSerialize,
|
traits::FeroxSerialize,
|
||||||
@@ -19,9 +22,8 @@ use crate::{
|
|||||||
use super::{error::StatError, field::StatField};
|
use super::{error::StatError, field::StatField};
|
||||||
|
|
||||||
/// Data collection of statistics related to a scan
|
/// Data collection of statistics related to a scan
|
||||||
#[derive(Default, Deserialize, Debug, Serialize)]
|
#[derive(Default, Debug)]
|
||||||
pub struct Stats {
|
pub struct Stats {
|
||||||
#[serde(rename = "type")]
|
|
||||||
/// Name of this type of struct, used for serialization, i.e. `{"type":"statistics"}`
|
/// Name of this type of struct, used for serialization, i.e. `{"type":"statistics"}`
|
||||||
kind: String,
|
kind: String,
|
||||||
|
|
||||||
@@ -125,11 +127,9 @@ pub struct Stats {
|
|||||||
total_runtime: Mutex<Vec<f64>>,
|
total_runtime: Mutex<Vec<f64>>,
|
||||||
|
|
||||||
/// tracker for the number of extensions the user specified
|
/// tracker for the number of extensions the user specified
|
||||||
#[serde(skip)]
|
|
||||||
num_extensions: usize,
|
num_extensions: usize,
|
||||||
|
|
||||||
/// tracker for whether to use json during serialization or not
|
/// tracker for whether to use json during serialization or not
|
||||||
#[serde(skip)]
|
|
||||||
json: bool,
|
json: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,6 +147,301 @@ impl FeroxSerialize for Stats {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Serialize implementation for Stats
|
||||||
|
impl Serialize for Stats {
|
||||||
|
/// Function that handles serialization of Stats
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
let mut state = serializer.serialize_struct("Stats", 32)?;
|
||||||
|
|
||||||
|
state.serialize_field("type", &self.kind)?;
|
||||||
|
state.serialize_field("timeouts", &atomic_load!(self.timeouts))?;
|
||||||
|
state.serialize_field("requests", &atomic_load!(self.requests))?;
|
||||||
|
state.serialize_field("expected_per_scan", &atomic_load!(self.expected_per_scan))?;
|
||||||
|
state.serialize_field("total_expected", &atomic_load!(self.total_expected))?;
|
||||||
|
state.serialize_field("errors", &atomic_load!(self.errors))?;
|
||||||
|
state.serialize_field("successes", &atomic_load!(self.successes))?;
|
||||||
|
state.serialize_field("redirects", &atomic_load!(self.redirects))?;
|
||||||
|
state.serialize_field("client_errors", &atomic_load!(self.client_errors))?;
|
||||||
|
state.serialize_field("server_errors", &atomic_load!(self.server_errors))?;
|
||||||
|
state.serialize_field("total_scans", &atomic_load!(self.total_scans))?;
|
||||||
|
state.serialize_field("initial_targets", &atomic_load!(self.initial_targets))?;
|
||||||
|
state.serialize_field("links_extracted", &atomic_load!(self.links_extracted))?;
|
||||||
|
state.serialize_field("status_200s", &atomic_load!(self.status_200s))?;
|
||||||
|
state.serialize_field("status_301s", &atomic_load!(self.status_301s))?;
|
||||||
|
state.serialize_field("status_302s", &atomic_load!(self.status_302s))?;
|
||||||
|
state.serialize_field("status_401s", &atomic_load!(self.status_401s))?;
|
||||||
|
state.serialize_field("status_403s", &atomic_load!(self.status_403s))?;
|
||||||
|
state.serialize_field("status_429s", &atomic_load!(self.status_429s))?;
|
||||||
|
state.serialize_field("status_500s", &atomic_load!(self.status_500s))?;
|
||||||
|
state.serialize_field("status_503s", &atomic_load!(self.status_503s))?;
|
||||||
|
state.serialize_field("status_504s", &atomic_load!(self.status_504s))?;
|
||||||
|
state.serialize_field("status_508s", &atomic_load!(self.status_508s))?;
|
||||||
|
state.serialize_field("wildcards_filtered", &atomic_load!(self.wildcards_filtered))?;
|
||||||
|
state.serialize_field("responses_filtered", &atomic_load!(self.responses_filtered))?;
|
||||||
|
state.serialize_field(
|
||||||
|
"resources_discovered",
|
||||||
|
&atomic_load!(self.resources_discovered),
|
||||||
|
)?;
|
||||||
|
state.serialize_field("url_format_errors", &atomic_load!(self.url_format_errors))?;
|
||||||
|
state.serialize_field("redirection_errors", &atomic_load!(self.redirection_errors))?;
|
||||||
|
state.serialize_field("connection_errors", &atomic_load!(self.connection_errors))?;
|
||||||
|
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.end()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deserialize implementation for Stats
|
||||||
|
impl<'a> Deserialize<'a> for Stats {
|
||||||
|
/// Deserialize a Stats object from a serde_json::Value
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'a>,
|
||||||
|
{
|
||||||
|
let stats = Self::new(0, false);
|
||||||
|
|
||||||
|
let map: HashMap<String, Value> = HashMap::deserialize(deserializer)?;
|
||||||
|
|
||||||
|
for (key, value) in &map {
|
||||||
|
match key.as_str() {
|
||||||
|
"timeouts" => {
|
||||||
|
if let Some(num) = value.as_u64() {
|
||||||
|
if let Ok(parsed) = usize::try_from(num) {
|
||||||
|
atomic_increment!(stats.timeouts, parsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"requests" => {
|
||||||
|
if let Some(num) = value.as_u64() {
|
||||||
|
if let Ok(parsed) = usize::try_from(num) {
|
||||||
|
atomic_increment!(stats.requests, parsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"expected_per_scan" => {
|
||||||
|
if let Some(num) = value.as_u64() {
|
||||||
|
if let Ok(parsed) = usize::try_from(num) {
|
||||||
|
atomic_increment!(stats.expected_per_scan, parsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"total_expected" => {
|
||||||
|
if let Some(num) = value.as_u64() {
|
||||||
|
if let Ok(parsed) = usize::try_from(num) {
|
||||||
|
atomic_increment!(stats.total_expected, parsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"errors" => {
|
||||||
|
if let Some(num) = value.as_u64() {
|
||||||
|
if let Ok(parsed) = usize::try_from(num) {
|
||||||
|
atomic_increment!(stats.errors, parsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"successes" => {
|
||||||
|
if let Some(num) = value.as_u64() {
|
||||||
|
if let Ok(parsed) = usize::try_from(num) {
|
||||||
|
atomic_increment!(stats.successes, parsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"redirects" => {
|
||||||
|
if let Some(num) = value.as_u64() {
|
||||||
|
if let Ok(parsed) = usize::try_from(num) {
|
||||||
|
atomic_increment!(stats.redirects, parsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"client_errors" => {
|
||||||
|
if let Some(num) = value.as_u64() {
|
||||||
|
if let Ok(parsed) = usize::try_from(num) {
|
||||||
|
atomic_increment!(stats.client_errors, parsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"server_errors" => {
|
||||||
|
if let Some(num) = value.as_u64() {
|
||||||
|
if let Ok(parsed) = usize::try_from(num) {
|
||||||
|
atomic_increment!(stats.server_errors, parsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"total_scans" => {
|
||||||
|
if let Some(num) = value.as_u64() {
|
||||||
|
if let Ok(parsed) = usize::try_from(num) {
|
||||||
|
atomic_increment!(stats.total_scans, parsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"initial_targets" => {
|
||||||
|
if let Some(num) = value.as_u64() {
|
||||||
|
if let Ok(parsed) = usize::try_from(num) {
|
||||||
|
atomic_increment!(stats.initial_targets, parsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"links_extracted" => {
|
||||||
|
if let Some(num) = value.as_u64() {
|
||||||
|
if let Ok(parsed) = usize::try_from(num) {
|
||||||
|
atomic_increment!(stats.links_extracted, parsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"status_200s" => {
|
||||||
|
if let Some(num) = value.as_u64() {
|
||||||
|
if let Ok(parsed) = usize::try_from(num) {
|
||||||
|
atomic_increment!(stats.status_200s, parsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"status_301s" => {
|
||||||
|
if let Some(num) = value.as_u64() {
|
||||||
|
if let Ok(parsed) = usize::try_from(num) {
|
||||||
|
atomic_increment!(stats.status_301s, parsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"status_302s" => {
|
||||||
|
if let Some(num) = value.as_u64() {
|
||||||
|
if let Ok(parsed) = usize::try_from(num) {
|
||||||
|
atomic_increment!(stats.status_302s, parsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"status_401s" => {
|
||||||
|
if let Some(num) = value.as_u64() {
|
||||||
|
if let Ok(parsed) = usize::try_from(num) {
|
||||||
|
atomic_increment!(stats.status_401s, parsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"status_403s" => {
|
||||||
|
if let Some(num) = value.as_u64() {
|
||||||
|
if let Ok(parsed) = usize::try_from(num) {
|
||||||
|
atomic_increment!(stats.status_403s, parsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"status_429s" => {
|
||||||
|
if let Some(num) = value.as_u64() {
|
||||||
|
if let Ok(parsed) = usize::try_from(num) {
|
||||||
|
atomic_increment!(stats.status_429s, parsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"status_500s" => {
|
||||||
|
if let Some(num) = value.as_u64() {
|
||||||
|
if let Ok(parsed) = usize::try_from(num) {
|
||||||
|
atomic_increment!(stats.status_500s, parsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"status_503s" => {
|
||||||
|
if let Some(num) = value.as_u64() {
|
||||||
|
if let Ok(parsed) = usize::try_from(num) {
|
||||||
|
atomic_increment!(stats.status_503s, parsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"status_504s" => {
|
||||||
|
if let Some(num) = value.as_u64() {
|
||||||
|
if let Ok(parsed) = usize::try_from(num) {
|
||||||
|
atomic_increment!(stats.status_504s, parsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"status_508s" => {
|
||||||
|
if let Some(num) = value.as_u64() {
|
||||||
|
if let Ok(parsed) = usize::try_from(num) {
|
||||||
|
atomic_increment!(stats.status_508s, parsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"wildcards_filtered" => {
|
||||||
|
if let Some(num) = value.as_u64() {
|
||||||
|
if let Ok(parsed) = usize::try_from(num) {
|
||||||
|
atomic_increment!(stats.wildcards_filtered, parsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"responses_filtered" => {
|
||||||
|
if let Some(num) = value.as_u64() {
|
||||||
|
if let Ok(parsed) = usize::try_from(num) {
|
||||||
|
atomic_increment!(stats.responses_filtered, parsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"resources_discovered" => {
|
||||||
|
if let Some(num) = value.as_u64() {
|
||||||
|
if let Ok(parsed) = usize::try_from(num) {
|
||||||
|
atomic_increment!(stats.resources_discovered, parsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"url_format_errors" => {
|
||||||
|
if let Some(num) = value.as_u64() {
|
||||||
|
if let Ok(parsed) = usize::try_from(num) {
|
||||||
|
atomic_increment!(stats.url_format_errors, parsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"redirection_errors" => {
|
||||||
|
if let Some(num) = value.as_u64() {
|
||||||
|
if let Ok(parsed) = usize::try_from(num) {
|
||||||
|
atomic_increment!(stats.redirection_errors, parsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"connection_errors" => {
|
||||||
|
if let Some(num) = value.as_u64() {
|
||||||
|
if let Ok(parsed) = usize::try_from(num) {
|
||||||
|
atomic_increment!(stats.connection_errors, parsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"request_errors" => {
|
||||||
|
if let Some(num) = value.as_u64() {
|
||||||
|
if let Ok(parsed) = usize::try_from(num) {
|
||||||
|
atomic_increment!(stats.request_errors, parsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"directory_scan_times" => {
|
||||||
|
if let Some(arr) = value.as_array() {
|
||||||
|
for val in arr {
|
||||||
|
if let Some(parsed) = val.as_f64() {
|
||||||
|
if let Ok(mut guard) = stats.directory_scan_times.lock() {
|
||||||
|
guard.push(parsed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"total_runtime" => {
|
||||||
|
if let Some(arr) = value.as_array() {
|
||||||
|
for val in arr {
|
||||||
|
if let Some(parsed) = val.as_f64() {
|
||||||
|
if let Ok(mut guard) = stats.total_runtime.lock() {
|
||||||
|
guard.push(parsed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(stats)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// implementation of statistics data collection struct
|
/// implementation of statistics data collection struct
|
||||||
impl Stats {
|
impl Stats {
|
||||||
/// Small wrapper for default to set `kind` to "statistics" and `total_runtime` to have at least
|
/// Small wrapper for default to set `kind` to "statistics" and `total_runtime` to have at least
|
||||||
|
|||||||
Reference in New Issue
Block a user