mirror of
https://github.com/epi052/feroxbuster.git
synced 2026-06-01 04:41:12 -03:00
Compare commits
40 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
85cba02b81 | ||
|
|
a93fe91459 | ||
|
|
4b811a42b9 | ||
|
|
678d371ca4 | ||
|
|
4f31ed1847 | ||
|
|
a7185f4262 | ||
|
|
a78f6b714d | ||
|
|
f9fe4d9874 | ||
|
|
0d365c034b | ||
|
|
49ee66f766 | ||
|
|
771a9556f1 | ||
|
|
48e53be244 | ||
|
|
57be47d30d | ||
|
|
dddbf916fa | ||
|
|
1267358017 | ||
|
|
46ff0120bc | ||
|
|
0333e48c65 | ||
|
|
23279eb1ed | ||
|
|
88260e0b04 | ||
|
|
e6f7a00ba0 | ||
|
|
d42806729d | ||
|
|
21f7a0715e | ||
|
|
0b36011ff5 | ||
|
|
22e936232d | ||
|
|
39040b2edf | ||
|
|
02de644f8c | ||
|
|
d71b77cb75 | ||
|
|
0dcdc2a496 | ||
|
|
2fff6bda4e | ||
|
|
d3e807c92f | ||
|
|
c3968e241f | ||
|
|
3cf056dac7 | ||
|
|
729140bece | ||
|
|
416f34861b | ||
|
|
9f52731582 | ||
|
|
dd4f3e0aac | ||
|
|
260943f153 | ||
|
|
79d81da0f3 | ||
|
|
088b44bc72 | ||
|
|
6784e9428a |
4
.github/workflows/check.yml
vendored
4
.github/workflows/check.yml
vendored
@@ -1,6 +1,6 @@
|
|||||||
name: CI Pipeline
|
name: CI Pipeline
|
||||||
|
|
||||||
on: [push]
|
on: [push, pull_request]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
check:
|
check:
|
||||||
@@ -61,4 +61,4 @@ jobs:
|
|||||||
- uses: actions-rs/cargo@v1
|
- uses: actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
command: clippy
|
command: clippy
|
||||||
args: --all-targets --all-features -- -D warnings -A clippy::unnecessary_unwrap
|
args: --all-targets --all-features -- -D warnings -A clippy::unnecessary_unwrap -A clippy::deref_addrof
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "feroxbuster"
|
name = "feroxbuster"
|
||||||
version = "1.5.3"
|
version = "1.6.1"
|
||||||
authors = ["Ben 'epi' Risher <epibar052@gmail.com>"]
|
authors = ["Ben 'epi' Risher <epibar052@gmail.com>"]
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|||||||
52
README.md
52
README.md
@@ -61,6 +61,7 @@ This attack is also known as Predictable Resource Location, File Enumeration, Di
|
|||||||
-----------------
|
-----------------
|
||||||
- [Installation](#-installation)
|
- [Installation](#-installation)
|
||||||
- [Download a Release](#download-a-release)
|
- [Download a Release](#download-a-release)
|
||||||
|
- [Snap Install](#snap-install)
|
||||||
- [Homebrew on MacOS and Linux](#homebrew-on-macos-and-linux)
|
- [Homebrew on MacOS and Linux](#homebrew-on-macos-and-linux)
|
||||||
- [Cargo Install](#cargo-install)
|
- [Cargo Install](#cargo-install)
|
||||||
- [apt Install](#apt-install)
|
- [apt Install](#apt-install)
|
||||||
@@ -68,6 +69,7 @@ This attack is also known as Predictable Resource Location, File Enumeration, Di
|
|||||||
- [Docker Install](#docker-install)
|
- [Docker Install](#docker-install)
|
||||||
- [Configuration](#%EF%B8%8F-configuration)
|
- [Configuration](#%EF%B8%8F-configuration)
|
||||||
- [Default Values](#default-values)
|
- [Default Values](#default-values)
|
||||||
|
- [Threads and Connection Limits At A High-Level](#threads-and-connection-limits-at-a-high-level)
|
||||||
- [ferox-config.toml](#ferox-configtoml)
|
- [ferox-config.toml](#ferox-configtoml)
|
||||||
- [Command Line Parsing](#command-line-parsing)
|
- [Command Line Parsing](#command-line-parsing)
|
||||||
- [Example Usage](#-example-usage)
|
- [Example Usage](#-example-usage)
|
||||||
@@ -115,9 +117,34 @@ Expand-Archive .\feroxbuster.zip
|
|||||||
.\feroxbuster\feroxbuster.exe -V
|
.\feroxbuster\feroxbuster.exe -V
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Snap Install
|
||||||
|
|
||||||
|
Install using `snap`
|
||||||
|
|
||||||
|
```
|
||||||
|
sudo snap install feroxbuster
|
||||||
|
```
|
||||||
|
|
||||||
|
The only gotcha here is that the snap package can only read wordlists from a few specific locations. There are a few
|
||||||
|
possible solutions, of which two are shown below.
|
||||||
|
|
||||||
|
If the wordlist is on the same partition as your home directory, it can be hard-linked into `~/snap/feroxbuster/common`
|
||||||
|
|
||||||
|
```
|
||||||
|
ln /path/to/the/wordlist ~/snap/feroxbuster/common
|
||||||
|
./feroxbuster -u http://localhost -w ~/snap/feroxbuster/common/wordlist
|
||||||
|
```
|
||||||
|
|
||||||
|
If the wordlist is on a separate partition, hard-linking won't work. You'll need to copy it into the snap directory.
|
||||||
|
|
||||||
|
```
|
||||||
|
cp /path/to/the/wordlist ~/snap/feroxbuster/common
|
||||||
|
./feroxbuster -u http://localhost -w ~/snap/feroxbuster/common/wordlist
|
||||||
|
```
|
||||||
|
|
||||||
### Homebrew on MacOS and Linux
|
### Homebrew on MacOS and Linux
|
||||||
|
|
||||||
Installable by Homebrew throughout own formulas:
|
Install using Homebrew via tap
|
||||||
|
|
||||||
🍏 [MacOS](https://github.com/TGotwig/homebrew-feroxbuster/blob/main/feroxbuster.rb)
|
🍏 [MacOS](https://github.com/TGotwig/homebrew-feroxbuster/blob/main/feroxbuster.rb)
|
||||||
|
|
||||||
@@ -230,6 +257,23 @@ Configuration begins with with the following built-in default values baked into
|
|||||||
- auto-filter wildcards - `true`
|
- auto-filter wildcards - `true`
|
||||||
- output: `stdout`
|
- output: `stdout`
|
||||||
|
|
||||||
|
### Threads and Connection Limits At A High-Level
|
||||||
|
|
||||||
|
This section explains how the `-t` and `-L` options work together to determine the overall aggressiveness of a scan. The combination of the two values set by these options determines how hard your target will get hit and to some extent also determines how many resources will be consumed on your local machine.
|
||||||
|
|
||||||
|
#### A Note on Green Threads
|
||||||
|
|
||||||
|
`feroxbuster` uses so-called [green threads](https://en.wikipedia.org/wiki/Green_threads) as opposed to traditional kernel/OS threads. This means (at a high-level) that the threads are implemented entirely in userspace, within a single running process. As a result, a scan with 30 green threads will appear to the OS to be a single process with no additional light-weight processes associated with it as far as the kernel is concerned. As such, there will not be any impact to process (`nproc`) limits when specifying larger values for `-t`. However, these threads will still consume file descriptors, so you will need to ensure that you have a suitable `nlimit` set when scaling up the amount of threads. More detailed documentation on setting appropriate `nlimit` values can be found in the [No File Descriptors Available](#no-file-descriptors-available) section of the FAQ
|
||||||
|
|
||||||
|
#### Threads and Connection Limits: The Implementation
|
||||||
|
|
||||||
|
* Threads: The `-t` option specifies the maximum amount of active threads *per-directory* during a scan
|
||||||
|
* Connection Limits: The `-L` option specifies the maximum amount of active connections per thread
|
||||||
|
|
||||||
|
#### Threads and Connection Limits: Examples
|
||||||
|
|
||||||
|
To truly have only 30 active requests to a site at any given time, `-t 30 -L 1` is necessary. Using `-t 30 -L 2` will result in a maximum of 60 total requests being processed at any given time for that site. And so on. For a conversation on this, please see [Issue #126](https://github.com/epi052/feroxbuster/issues/126) which may provide more (or less) clarity :wink:
|
||||||
|
|
||||||
### ferox-config.toml
|
### ferox-config.toml
|
||||||
After setting built-in default values, any values defined in a `ferox-config.toml` config file will override the
|
After setting built-in default values, any values defined in a `ferox-config.toml` config file will override the
|
||||||
built-in defaults.
|
built-in defaults.
|
||||||
@@ -297,6 +341,8 @@ A pre-made configuration file with examples of all available settings can be fou
|
|||||||
# extract_links = true
|
# extract_links = true
|
||||||
# depth = 1
|
# depth = 1
|
||||||
# filter_size = [5174]
|
# filter_size = [5174]
|
||||||
|
# filter_word_count = [993]
|
||||||
|
# filter_line_count = [35, 36]
|
||||||
# queries = [["name","value"], ["rick", "astley"]]
|
# queries = [["name","value"], ["rick", "astley"]]
|
||||||
|
|
||||||
# headers can be specified on multiple lines or as an inline table
|
# headers can be specified on multiple lines or as an inline table
|
||||||
@@ -337,8 +383,10 @@ FLAGS:
|
|||||||
OPTIONS:
|
OPTIONS:
|
||||||
-d, --depth <RECURSION_DEPTH> Maximum recursion depth, a depth of 0 is infinite recursion (default: 4)
|
-d, --depth <RECURSION_DEPTH> Maximum recursion depth, a depth of 0 is infinite recursion (default: 4)
|
||||||
-x, --extensions <FILE_EXTENSION>... File extension(s) to search for (ex: -x php -x pdf js)
|
-x, --extensions <FILE_EXTENSION>... File extension(s) to search for (ex: -x php -x pdf js)
|
||||||
|
-N, --filter-lines <LINES>... Filter out messages of a particular line count (ex: -N 20 -N 31,30)
|
||||||
-S, --filter-size <SIZE>... Filter out messages of a particular size (ex: -S 5120 -S 4927,1970)
|
-S, --filter-size <SIZE>... Filter out messages of a particular size (ex: -S 5120 -S 4927,1970)
|
||||||
-C, --filter-status <STATUS_CODE>... Filter out status codes (deny list) (ex: -C 200 -C 401)
|
-C, --filter-status <STATUS_CODE>... Filter out status codes (deny list) (ex: -C 200 -C 401)
|
||||||
|
-W, --filter-words <WORDS>... Filter out messages of a particular word count (ex: -W 312 -W 91,82)
|
||||||
-H, --headers <HEADER>... Specify HTTP headers (ex: -H Header:val 'stuff: things')
|
-H, --headers <HEADER>... Specify HTTP headers (ex: -H Header:val 'stuff: things')
|
||||||
-o, --output <FILE> Output file to write results to (default: stdout)
|
-o, --output <FILE> Output file to write results to (default: stdout)
|
||||||
-p, --proxy <PROXY> Proxy to use for requests (ex: http(s)://host:port, socks5://host:port)
|
-p, --proxy <PROXY> Proxy to use for requests (ex: http(s)://host:port, socks5://host:port)
|
||||||
@@ -511,7 +559,7 @@ a few of the use-cases in which feroxbuster may be a better fit:
|
|||||||
| configuration file for default value override | ✔ | | ✔ |
|
| configuration file for default value override | ✔ | | ✔ |
|
||||||
| can accept urls via STDIN as part of a pipeline | ✔ | | ✔ |
|
| can accept urls via STDIN as part of a pipeline | ✔ | | ✔ |
|
||||||
| can accept wordlists via STDIN | | ✔ | ✔ |
|
| can accept wordlists via STDIN | | ✔ | ✔ |
|
||||||
| filter by response size | ✔ | | ✔ |
|
| filter based on response size, wordcount, and linecount | ✔ | | ✔ |
|
||||||
| auto-filter wildcard responses | ✔ | | ✔ |
|
| auto-filter wildcard responses | ✔ | | ✔ |
|
||||||
| performs other scans (vhost, dns, etc) | | ✔ | ✔ |
|
| performs other scans (vhost, dns, etc) | | ✔ | ✔ |
|
||||||
| time delay / rate limiting | | ✔ | ✔ |
|
| time delay / rate limiting | | ✔ | ✔ |
|
||||||
|
|||||||
@@ -30,6 +30,8 @@
|
|||||||
# extract_links = true
|
# extract_links = true
|
||||||
# depth = 1
|
# depth = 1
|
||||||
# filter_size = [5174]
|
# filter_size = [5174]
|
||||||
|
# filter_word_count = [993]
|
||||||
|
# filter_line_count = [35, 36]
|
||||||
# queries = [["name","value"], ["rick", "astley"]]
|
# queries = [["name","value"], ["rick", "astley"]]
|
||||||
|
|
||||||
# headers can be specified on multiple lines or as an inline table
|
# headers can be specified on multiple lines or as an inline table
|
||||||
|
|||||||
41
snapcraft.yaml
Normal file
41
snapcraft.yaml
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
name: feroxbuster
|
||||||
|
version: git
|
||||||
|
summary: A simple, fast, recursive content discovery tool written in Rust
|
||||||
|
description: |
|
||||||
|
feroxbuster is a tool designed to perform Forced Browsing.
|
||||||
|
|
||||||
|
Forced browsing is an attack where the aim is to enumerate and access resources that are not referenced by the web application, but are still accessible by an attacker.
|
||||||
|
|
||||||
|
feroxbuster uses brute force combined with a wordlist to search for unlinked content in target directories. These resources may store sensitive information about web applications and operational systems, such as source code, credentials, internal network addressing, etc...
|
||||||
|
|
||||||
|
This attack is also known as Predictable Resource Location, File Enumeration, Directory Enumeration, and Resource Enumeration.
|
||||||
|
|
||||||
|
|
||||||
|
base: core18
|
||||||
|
|
||||||
|
plugs:
|
||||||
|
etc-feroxbuster:
|
||||||
|
interface: system-files
|
||||||
|
read:
|
||||||
|
- /etc/feroxbuster
|
||||||
|
dot-config-feroxbuster:
|
||||||
|
interface: personal-files
|
||||||
|
read:
|
||||||
|
- $HOME/.config/feroxbuster
|
||||||
|
|
||||||
|
architectures:
|
||||||
|
- build-on: amd64
|
||||||
|
- build-on: i386
|
||||||
|
|
||||||
|
parts:
|
||||||
|
feroxbuster:
|
||||||
|
plugin: rust
|
||||||
|
source: .
|
||||||
|
|
||||||
|
apps:
|
||||||
|
feroxbuster:
|
||||||
|
command: bin/feroxbuster
|
||||||
|
plugs:
|
||||||
|
- etc-feroxbuster
|
||||||
|
- dot-config-feroxbuster
|
||||||
|
- network
|
||||||
@@ -297,6 +297,24 @@ by Ben "epi" Risher {} ver: {}"#,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for filter in &config.filter_word_count {
|
||||||
|
writeln!(
|
||||||
|
&mut writer,
|
||||||
|
"{}",
|
||||||
|
format_banner_entry!("\u{1f4a2}", "Word Count Filter", filter)
|
||||||
|
)
|
||||||
|
.unwrap_or_default(); // 💢
|
||||||
|
}
|
||||||
|
|
||||||
|
for filter in &config.filter_line_count {
|
||||||
|
writeln!(
|
||||||
|
&mut writer,
|
||||||
|
"{}",
|
||||||
|
format_banner_entry!("\u{1f4a2}", "Line Count Filter", filter)
|
||||||
|
)
|
||||||
|
.unwrap_or_default(); // 💢
|
||||||
|
}
|
||||||
|
|
||||||
if config.extract_links {
|
if config.extract_links {
|
||||||
writeln!(
|
writeln!(
|
||||||
&mut writer,
|
&mut writer,
|
||||||
|
|||||||
@@ -32,31 +32,33 @@ pub fn initialize(
|
|||||||
.default_headers(header_map)
|
.default_headers(header_map)
|
||||||
.redirect(policy);
|
.redirect(policy);
|
||||||
|
|
||||||
let client = if proxy.is_some() && !proxy.unwrap().is_empty() {
|
let client = match proxy {
|
||||||
match Proxy::all(proxy.unwrap()) {
|
// a proxy is specified, need to add it to the client
|
||||||
Ok(proxy_obj) => client.proxy(proxy_obj),
|
Some(some_proxy) => {
|
||||||
Err(e) => {
|
if !some_proxy.is_empty() {
|
||||||
eprintln!(
|
// it's not an empty string
|
||||||
"{} {} Could not add proxy ({:?}) to Client configuration",
|
match Proxy::all(some_proxy) {
|
||||||
status_colorizer("ERROR"),
|
Ok(proxy_obj) => client.proxy(proxy_obj),
|
||||||
module_colorizer("Client::initialize"),
|
Err(e) => {
|
||||||
proxy
|
eprintln!(
|
||||||
);
|
"{} {} {}",
|
||||||
eprintln!(
|
status_colorizer("ERROR"),
|
||||||
"{} {} {}",
|
module_colorizer("Client::initialize"),
|
||||||
status_colorizer("ERROR"),
|
e
|
||||||
module_colorizer("Client::initialize"),
|
);
|
||||||
e
|
|
||||||
);
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
panic!();
|
panic!();
|
||||||
#[cfg(not(test))]
|
#[cfg(not(test))]
|
||||||
exit(1);
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
client // Some("") was used?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
// no proxy specified
|
||||||
client
|
None => client,
|
||||||
};
|
};
|
||||||
|
|
||||||
match client.build() {
|
match client.build() {
|
||||||
|
|||||||
168
src/config.rs
168
src/config.rs
@@ -163,6 +163,14 @@ pub struct Configuration {
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub filter_size: Vec<u64>,
|
pub filter_size: Vec<u64>,
|
||||||
|
|
||||||
|
/// Filter out messages of a particular line count
|
||||||
|
#[serde(default)]
|
||||||
|
pub filter_line_count: Vec<usize>,
|
||||||
|
|
||||||
|
/// Filter out messages of a particular word count
|
||||||
|
#[serde(default)]
|
||||||
|
pub filter_word_count: Vec<usize>,
|
||||||
|
|
||||||
/// Don't auto-filter wildcard responses
|
/// Don't auto-filter wildcard responses
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub dont_filter: bool,
|
pub dont_filter: bool,
|
||||||
@@ -240,6 +248,8 @@ impl Default for Configuration {
|
|||||||
queries: Vec::new(),
|
queries: Vec::new(),
|
||||||
extensions: Vec::new(),
|
extensions: Vec::new(),
|
||||||
filter_size: Vec::new(),
|
filter_size: Vec::new(),
|
||||||
|
filter_line_count: Vec::new(),
|
||||||
|
filter_word_count: Vec::new(),
|
||||||
filter_status: Vec::new(),
|
filter_status: Vec::new(),
|
||||||
headers: HashMap::new(),
|
headers: HashMap::new(),
|
||||||
depth: depth(),
|
depth: depth(),
|
||||||
@@ -270,6 +280,8 @@ impl Configuration {
|
|||||||
/// - **insecure**: `false` (don't be insecure, i.e. don't allow invalid certs)
|
/// - **insecure**: `false` (don't be insecure, i.e. don't allow invalid certs)
|
||||||
/// - **extensions**: `None`
|
/// - **extensions**: `None`
|
||||||
/// - **filter_size**: `None`
|
/// - **filter_size**: `None`
|
||||||
|
/// - **filter_word_count**: `None`
|
||||||
|
/// - **filter_line_count**: `None`
|
||||||
/// - **headers**: `None`
|
/// - **headers**: `None`
|
||||||
/// - **queries**: `None`
|
/// - **queries**: `None`
|
||||||
/// - **no_recursion**: `false` (recursively scan enumerated sub-directories)
|
/// - **no_recursion**: `false` (recursively scan enumerated sub-directories)
|
||||||
@@ -352,36 +364,30 @@ impl Configuration {
|
|||||||
|
|
||||||
let args = parser::initialize().get_matches();
|
let args = parser::initialize().get_matches();
|
||||||
|
|
||||||
// the .is_some appears clunky, but it allows default values to be incrementally
|
macro_rules! update_config_if_present {
|
||||||
// overwritten from Struct defaults, to file config, to command line args, soooo ¯\_(ツ)_/¯
|
($c:expr, $m:ident, $v:expr, $t:ty) => {
|
||||||
if args.value_of("threads").is_some() {
|
match value_t!($m, $v, $t) {
|
||||||
let threads = value_t!(args.value_of("threads"), usize).unwrap_or_else(|e| e.exit());
|
Ok(value) => *$c = value, // Update value
|
||||||
config.threads = threads;
|
Err(clap::Error {
|
||||||
|
kind: clap::ErrorKind::ArgumentNotFound,
|
||||||
|
message: _,
|
||||||
|
info: _,
|
||||||
|
}) => {
|
||||||
|
// Do nothing if argument not found
|
||||||
|
}
|
||||||
|
Err(e) => e.exit(), // Exit with error on parse error
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if args.value_of("depth").is_some() {
|
update_config_if_present!(&mut config.threads, args, "threads", usize);
|
||||||
let depth = value_t!(args.value_of("depth"), usize).unwrap_or_else(|e| e.exit());
|
update_config_if_present!(&mut config.depth, args, "depth", usize);
|
||||||
config.depth = depth;
|
update_config_if_present!(&mut config.scan_limit, args, "scan_limit", usize);
|
||||||
}
|
update_config_if_present!(&mut config.wordlist, args, "wordlist", String);
|
||||||
|
update_config_if_present!(&mut config.output, args, "output", String);
|
||||||
|
|
||||||
if args.value_of("scan_limit").is_some() {
|
if let Some(arg) = args.values_of("status_codes") {
|
||||||
let scan_limit =
|
config.status_codes = arg
|
||||||
value_t!(args.value_of("scan_limit"), usize).unwrap_or_else(|e| e.exit());
|
|
||||||
config.scan_limit = scan_limit;
|
|
||||||
}
|
|
||||||
|
|
||||||
if args.value_of("wordlist").is_some() {
|
|
||||||
config.wordlist = String::from(args.value_of("wordlist").unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
if args.value_of("output").is_some() {
|
|
||||||
config.output = String::from(args.value_of("output").unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
if args.values_of("status_codes").is_some() {
|
|
||||||
config.status_codes = args
|
|
||||||
.values_of("status_codes")
|
|
||||||
.unwrap() // already known good
|
|
||||||
.map(|code| {
|
.map(|code| {
|
||||||
StatusCode::from_bytes(code.as_bytes())
|
StatusCode::from_bytes(code.as_bytes())
|
||||||
.unwrap_or_else(|e| report_and_exit(&e.to_string()))
|
.unwrap_or_else(|e| report_and_exit(&e.to_string()))
|
||||||
@@ -390,11 +396,9 @@ impl Configuration {
|
|||||||
.collect();
|
.collect();
|
||||||
}
|
}
|
||||||
|
|
||||||
if args.values_of("replay_codes").is_some() {
|
if let Some(arg) = args.values_of("replay_codes") {
|
||||||
// replay codes passed in by the user
|
// replay codes passed in by the user
|
||||||
config.replay_codes = args
|
config.replay_codes = arg
|
||||||
.values_of("replay_codes")
|
|
||||||
.unwrap() // already known good
|
|
||||||
.map(|code| {
|
.map(|code| {
|
||||||
StatusCode::from_bytes(code.as_bytes())
|
StatusCode::from_bytes(code.as_bytes())
|
||||||
.unwrap_or_else(|e| report_and_exit(&e.to_string()))
|
.unwrap_or_else(|e| report_and_exit(&e.to_string()))
|
||||||
@@ -406,10 +410,8 @@ impl Configuration {
|
|||||||
config.replay_codes = config.status_codes.clone();
|
config.replay_codes = config.status_codes.clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
if args.values_of("filter_status").is_some() {
|
if let Some(arg) = args.values_of("filter_status") {
|
||||||
config.filter_status = args
|
config.filter_status = arg
|
||||||
.values_of("filter_status")
|
|
||||||
.unwrap() // already known good
|
|
||||||
.map(|code| {
|
.map(|code| {
|
||||||
StatusCode::from_bytes(code.as_bytes())
|
StatusCode::from_bytes(code.as_bytes())
|
||||||
.unwrap_or_else(|e| report_and_exit(&e.to_string()))
|
.unwrap_or_else(|e| report_and_exit(&e.to_string()))
|
||||||
@@ -418,20 +420,32 @@ impl Configuration {
|
|||||||
.collect();
|
.collect();
|
||||||
}
|
}
|
||||||
|
|
||||||
if args.values_of("extensions").is_some() {
|
if let Some(arg) = args.values_of("extensions") {
|
||||||
config.extensions = args
|
config.extensions = arg.map(|val| val.to_string()).collect();
|
||||||
.values_of("extensions")
|
}
|
||||||
.unwrap()
|
|
||||||
.map(|val| val.to_string())
|
if let Some(arg) = args.values_of("filter_size") {
|
||||||
|
config.filter_size = arg
|
||||||
|
.map(|size| {
|
||||||
|
size.parse::<u64>()
|
||||||
|
.unwrap_or_else(|e| report_and_exit(&e.to_string()))
|
||||||
|
})
|
||||||
.collect();
|
.collect();
|
||||||
}
|
}
|
||||||
|
|
||||||
if args.values_of("filter_size").is_some() {
|
if let Some(arg) = args.values_of("filter_words") {
|
||||||
config.filter_size = args
|
config.filter_word_count = arg
|
||||||
.values_of("filter_size")
|
|
||||||
.unwrap() // already known good
|
|
||||||
.map(|size| {
|
.map(|size| {
|
||||||
size.parse::<u64>()
|
size.parse::<usize>()
|
||||||
|
.unwrap_or_else(|e| report_and_exit(&e.to_string()))
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(arg) = args.values_of("filter_lines") {
|
||||||
|
config.filter_line_count = arg
|
||||||
|
.map(|size| {
|
||||||
|
size.parse::<usize>()
|
||||||
.unwrap_or_else(|e| report_and_exit(&e.to_string()))
|
.unwrap_or_else(|e| report_and_exit(&e.to_string()))
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
@@ -442,11 +456,11 @@ impl Configuration {
|
|||||||
// consider a user specifying quiet = true in ferox-config.toml
|
// consider a user specifying quiet = true in ferox-config.toml
|
||||||
// if the line below is outside of the if, we'd overwrite true with
|
// if the line below is outside of the if, we'd overwrite true with
|
||||||
// false if no -q is used on the command line
|
// false if no -q is used on the command line
|
||||||
config.quiet = args.is_present("quiet");
|
config.quiet = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if args.is_present("dont_filter") {
|
if args.is_present("dont_filter") {
|
||||||
config.dont_filter = args.is_present("dont_filter");
|
config.dont_filter = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if args.occurrences_of("verbosity") > 0 {
|
if args.occurrences_of("verbosity") > 0 {
|
||||||
@@ -456,19 +470,19 @@ impl Configuration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if args.is_present("no_recursion") {
|
if args.is_present("no_recursion") {
|
||||||
config.no_recursion = args.is_present("no_recursion");
|
config.no_recursion = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if args.is_present("add_slash") {
|
if args.is_present("add_slash") {
|
||||||
config.add_slash = args.is_present("add_slash");
|
config.add_slash = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if args.is_present("extract_links") {
|
if args.is_present("extract_links") {
|
||||||
config.extract_links = args.is_present("extract_links");
|
config.extract_links = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if args.is_present("stdin") {
|
if args.is_present("stdin") {
|
||||||
config.stdin = args.is_present("stdin");
|
config.stdin = true;
|
||||||
} else {
|
} else {
|
||||||
config.target_url = String::from(args.value_of("url").unwrap());
|
config.target_url = String::from(args.value_of("url").unwrap());
|
||||||
}
|
}
|
||||||
@@ -476,33 +490,21 @@ impl Configuration {
|
|||||||
////
|
////
|
||||||
// organizational breakpoint; all options below alter the Client configuration
|
// organizational breakpoint; all options below alter the Client configuration
|
||||||
////
|
////
|
||||||
if args.value_of("proxy").is_some() {
|
update_config_if_present!(&mut config.proxy, args, "proxy", String);
|
||||||
config.proxy = String::from(args.value_of("proxy").unwrap());
|
update_config_if_present!(&mut config.replay_proxy, args, "replay_proxy", String);
|
||||||
}
|
update_config_if_present!(&mut config.user_agent, args, "user_agent", String);
|
||||||
|
update_config_if_present!(&mut config.timeout, args, "timeout", u64);
|
||||||
if args.value_of("replay_proxy").is_some() {
|
|
||||||
config.replay_proxy = String::from(args.value_of("replay_proxy").unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
if args.value_of("user_agent").is_some() {
|
|
||||||
config.user_agent = String::from(args.value_of("user_agent").unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
if args.value_of("timeout").is_some() {
|
|
||||||
let timeout = value_t!(args.value_of("timeout"), u64).unwrap_or_else(|e| e.exit());
|
|
||||||
config.timeout = timeout;
|
|
||||||
}
|
|
||||||
|
|
||||||
if args.is_present("redirects") {
|
if args.is_present("redirects") {
|
||||||
config.redirects = args.is_present("redirects");
|
config.redirects = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if args.is_present("insecure") {
|
if args.is_present("insecure") {
|
||||||
config.insecure = args.is_present("insecure");
|
config.insecure = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if args.values_of("headers").is_some() {
|
if let Some(headers) = args.values_of("headers") {
|
||||||
for val in args.values_of("headers").unwrap() {
|
for val in headers {
|
||||||
let mut split_val = val.split(':');
|
let mut split_val = val.split(':');
|
||||||
|
|
||||||
// explicitly take first split value as header's name
|
// explicitly take first split value as header's name
|
||||||
@@ -515,8 +517,8 @@ impl Configuration {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if args.values_of("queries").is_some() {
|
if let Some(queries) = args.values_of("queries") {
|
||||||
for val in args.values_of("queries").unwrap() {
|
for val in queries {
|
||||||
// same basic logic used as reading in the headers HashMap above
|
// same basic logic used as reading in the headers HashMap above
|
||||||
let mut split_val = val.split('=');
|
let mut split_val = val.split('=');
|
||||||
|
|
||||||
@@ -616,6 +618,8 @@ impl Configuration {
|
|||||||
settings.stdin = settings_to_merge.stdin;
|
settings.stdin = settings_to_merge.stdin;
|
||||||
settings.depth = settings_to_merge.depth;
|
settings.depth = settings_to_merge.depth;
|
||||||
settings.filter_size = settings_to_merge.filter_size;
|
settings.filter_size = settings_to_merge.filter_size;
|
||||||
|
settings.filter_word_count = settings_to_merge.filter_word_count;
|
||||||
|
settings.filter_line_count = settings_to_merge.filter_line_count;
|
||||||
settings.filter_status = settings_to_merge.filter_status;
|
settings.filter_status = settings_to_merge.filter_status;
|
||||||
settings.dont_filter = settings_to_merge.dont_filter;
|
settings.dont_filter = settings_to_merge.dont_filter;
|
||||||
settings.scan_limit = settings_to_merge.scan_limit;
|
settings.scan_limit = settings_to_merge.scan_limit;
|
||||||
@@ -678,6 +682,8 @@ mod tests {
|
|||||||
extract_links = true
|
extract_links = true
|
||||||
depth = 1
|
depth = 1
|
||||||
filter_size = [4120]
|
filter_size = [4120]
|
||||||
|
filter_word_count = [994, 992]
|
||||||
|
filter_line_count = [34]
|
||||||
filter_status = [201]
|
filter_status = [201]
|
||||||
"#;
|
"#;
|
||||||
let tmp_dir = TempDir::new().unwrap();
|
let tmp_dir = TempDir::new().unwrap();
|
||||||
@@ -714,6 +720,8 @@ mod tests {
|
|||||||
assert_eq!(config.queries, Vec::new());
|
assert_eq!(config.queries, Vec::new());
|
||||||
assert_eq!(config.extensions, Vec::<String>::new());
|
assert_eq!(config.extensions, Vec::<String>::new());
|
||||||
assert_eq!(config.filter_size, Vec::<u64>::new());
|
assert_eq!(config.filter_size, Vec::<u64>::new());
|
||||||
|
assert_eq!(config.filter_word_count, Vec::<usize>::new());
|
||||||
|
assert_eq!(config.filter_line_count, Vec::<usize>::new());
|
||||||
assert_eq!(config.filter_status, Vec::<u16>::new());
|
assert_eq!(config.filter_status, Vec::<u16>::new());
|
||||||
assert_eq!(config.headers, HashMap::new());
|
assert_eq!(config.headers, HashMap::new());
|
||||||
}
|
}
|
||||||
@@ -865,6 +873,20 @@ mod tests {
|
|||||||
assert_eq!(config.filter_size, vec![4120]);
|
assert_eq!(config.filter_size, vec![4120]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
/// parse the test config and see that the value parsed is correct
|
||||||
|
fn config_reads_filter_word_count() {
|
||||||
|
let config = setup_config_test();
|
||||||
|
assert_eq!(config.filter_word_count, vec![994, 992]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
/// parse the test config and see that the value parsed is correct
|
||||||
|
fn config_reads_filter_line_count() {
|
||||||
|
let config = setup_config_test();
|
||||||
|
assert_eq!(config.filter_line_count, vec![34]);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
/// parse the test config and see that the value parsed is correct
|
/// parse the test config and see that the value parsed is correct
|
||||||
fn config_reads_filter_status() {
|
fn config_reads_filter_status() {
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ impl FeroxFilter for WildcardFilter {
|
|||||||
/// Examine size, dynamic, and content_len to determine whether or not the response received
|
/// Examine size, dynamic, and content_len to determine whether or not the response received
|
||||||
/// is a wildcard response and therefore should be filtered out
|
/// is a wildcard response and therefore should be filtered out
|
||||||
fn should_filter_response(&self, response: &FeroxResponse) -> bool {
|
fn should_filter_response(&self, response: &FeroxResponse) -> bool {
|
||||||
log::trace!("enter: should_filter_response({:?} {:?})", self, response);
|
log::trace!("enter: should_filter_response({:?} {})", self, response);
|
||||||
|
|
||||||
// quick return if dont_filter is set
|
// quick return if dont_filter is set
|
||||||
if CONFIGURATION.dont_filter {
|
if CONFIGURATION.dont_filter {
|
||||||
@@ -114,7 +114,7 @@ pub struct StatusCodeFilter {
|
|||||||
impl FeroxFilter for StatusCodeFilter {
|
impl FeroxFilter for StatusCodeFilter {
|
||||||
/// Check `filter_code` against what was passed in via -C|--filter-status
|
/// Check `filter_code` against what was passed in via -C|--filter-status
|
||||||
fn should_filter_response(&self, response: &FeroxResponse) -> bool {
|
fn should_filter_response(&self, response: &FeroxResponse) -> bool {
|
||||||
log::trace!("enter: should_filter_response({:?} {:?})", self, response);
|
log::trace!("enter: should_filter_response({:?} {})", self, response);
|
||||||
|
|
||||||
if response.status().as_u16() == self.filter_code {
|
if response.status().as_u16() == self.filter_code {
|
||||||
log::debug!(
|
log::debug!(
|
||||||
|
|||||||
@@ -89,8 +89,10 @@ pub async fn wildcard_test(
|
|||||||
|
|
||||||
if !CONFIGURATION.quiet {
|
if !CONFIGURATION.quiet {
|
||||||
let msg = format!(
|
let msg = format!(
|
||||||
"{} {:>10} Wildcard response is dynamic; {} ({} + url length) responses; toggle this behavior by using {}\n",
|
"{} {:>8}l {:>8}w {:>8}c Wildcard response is dynamic; {} ({} + url length) responses; toggle this behavior by using {}\n",
|
||||||
status_colorizer("WLD"),
|
status_colorizer("WLD"),
|
||||||
|
ferox_response.line_count(),
|
||||||
|
ferox_response.word_count(),
|
||||||
wildcard.dynamic,
|
wildcard.dynamic,
|
||||||
style("auto-filtering").yellow(),
|
style("auto-filtering").yellow(),
|
||||||
style(wc_length - url_len).cyan(),
|
style(wc_length - url_len).cyan(),
|
||||||
@@ -110,8 +112,10 @@ pub async fn wildcard_test(
|
|||||||
|
|
||||||
if !CONFIGURATION.quiet {
|
if !CONFIGURATION.quiet {
|
||||||
let msg = format!(
|
let msg = format!(
|
||||||
"{} {:>10} Wildcard response is static; {} {} responses; toggle this behavior by using {}\n",
|
"{} {:>8}l {:>8}w {:>8}c Wildcard response is static; {} {} responses; toggle this behavior by using {}\n",
|
||||||
status_colorizer("WLD"),
|
status_colorizer("WLD"),
|
||||||
|
ferox_response.line_count(),
|
||||||
|
ferox_response.word_count(),
|
||||||
wc_length,
|
wc_length,
|
||||||
style("auto-filtering").yellow(),
|
style("auto-filtering").yellow(),
|
||||||
style(wc_length).cyan(),
|
style(wc_length).cyan(),
|
||||||
@@ -183,14 +187,18 @@ async fn make_wildcard_request(
|
|||||||
.contains(&response.status().as_u16())
|
.contains(&response.status().as_u16())
|
||||||
{
|
{
|
||||||
// found a wildcard response
|
// found a wildcard response
|
||||||
let ferox_response = FeroxResponse::from(response, false).await;
|
let ferox_response = FeroxResponse::from(response, true).await;
|
||||||
let url_len = get_url_path_length(&ferox_response.url());
|
let url_len = get_url_path_length(&ferox_response.url());
|
||||||
let content_len = ferox_response.content_length();
|
let content_len = ferox_response.content_length();
|
||||||
|
let content_words = ferox_response.word_count();
|
||||||
|
let content_lines = ferox_response.line_count();
|
||||||
|
|
||||||
if !CONFIGURATION.quiet && !should_filter_response(&ferox_response) {
|
if !CONFIGURATION.quiet && !should_filter_response(&ferox_response) {
|
||||||
let msg = format!(
|
let msg = format!(
|
||||||
"{} {:>10} Got {} for {} (url length: {})\n",
|
"{} {:>8}l {:>8}w {:>8}c Got {} for {} (url length: {})\n",
|
||||||
wildcard,
|
wildcard,
|
||||||
|
content_lines,
|
||||||
|
content_words,
|
||||||
content_len,
|
content_len,
|
||||||
status_colorizer(&ferox_response.status().as_str()),
|
status_colorizer(&ferox_response.status().as_str()),
|
||||||
ferox_response.url(),
|
ferox_response.url(),
|
||||||
@@ -212,8 +220,10 @@ async fn make_wildcard_request(
|
|||||||
let next_loc_str = next_loc.to_str().unwrap_or("Unknown");
|
let next_loc_str = next_loc.to_str().unwrap_or("Unknown");
|
||||||
if !CONFIGURATION.quiet && !should_filter_response(&ferox_response) {
|
if !CONFIGURATION.quiet && !should_filter_response(&ferox_response) {
|
||||||
let msg = format!(
|
let msg = format!(
|
||||||
"{} {:>10} {} redirects to => {}\n",
|
"{} {:>8}l {:>8}w {:>8}c {} redirects to => {}\n",
|
||||||
wildcard,
|
wildcard,
|
||||||
|
content_lines,
|
||||||
|
content_words,
|
||||||
content_len,
|
content_len,
|
||||||
ferox_response.url(),
|
ferox_response.url(),
|
||||||
next_loc_str
|
next_loc_str
|
||||||
@@ -229,7 +239,7 @@ async fn make_wildcard_request(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log::trace!("exit: make_wildcard_request -> {:?}", ferox_response);
|
log::trace!("exit: make_wildcard_request -> {}", ferox_response);
|
||||||
return Some(ferox_response);
|
return Some(ferox_response);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
26
src/lib.rs
26
src/lib.rs
@@ -103,6 +103,19 @@ pub struct FeroxResponse {
|
|||||||
headers: HeaderMap,
|
headers: HeaderMap,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Implement Display for FeroxResponse
|
||||||
|
impl fmt::Display for FeroxResponse {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"FeroxResponse {{ url: {}, status: {}, content-length: {} }}",
|
||||||
|
self.url(),
|
||||||
|
self.status(),
|
||||||
|
self.content_length()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// `FeroxResponse` implementation
|
/// `FeroxResponse` implementation
|
||||||
impl FeroxResponse {
|
impl FeroxResponse {
|
||||||
/// Get the `StatusCode` of this `FeroxResponse`
|
/// Get the `StatusCode` of this `FeroxResponse`
|
||||||
@@ -163,6 +176,19 @@ impl FeroxResponse {
|
|||||||
self.url.query_pairs().count() > 0 || has_extension
|
self.url.query_pairs().count() > 0 || has_extension
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns line count of the response text.
|
||||||
|
pub fn line_count(&self) -> usize {
|
||||||
|
self.text().lines().count()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns word count of the response text.
|
||||||
|
pub fn word_count(&self) -> usize {
|
||||||
|
self.text()
|
||||||
|
.lines()
|
||||||
|
.map(|s| s.split_whitespace().count())
|
||||||
|
.sum()
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a new `FeroxResponse` from the given `Response`
|
/// Create a new `FeroxResponse` from the given `Response`
|
||||||
pub async fn from(response: Response, read_body: bool) -> Self {
|
pub async fn from(response: Response, read_body: bool) -> Self {
|
||||||
let url = response.url().clone();
|
let url = response.url().clone();
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ pub fn initialize(verbosity: u8) {
|
|||||||
0 => (),
|
0 => (),
|
||||||
1 => env::set_var("RUST_LOG", "warn"),
|
1 => env::set_var("RUST_LOG", "warn"),
|
||||||
2 => env::set_var("RUST_LOG", "info"),
|
2 => env::set_var("RUST_LOG", "info"),
|
||||||
3 => env::set_var("RUST_LOG", "debug,hyper=info,reqwest=info"),
|
3 => env::set_var("RUST_LOG", "feroxbuster=debug,info"),
|
||||||
_ => env::set_var("RUST_LOG", "trace,hyper=info,reqwest=info"),
|
_ => env::set_var("RUST_LOG", "feroxbuster=trace,info"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -55,9 +55,10 @@ pub fn initialize(verbosity: u8) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let msg = format!(
|
let msg = format!(
|
||||||
"{} {:10.03} {}\n",
|
"{} {:10.03} {} {}\n",
|
||||||
style(level_name).bg(level_color).black(),
|
style(level_name).bg(level_color).black(),
|
||||||
style(t).dim(),
|
style(t).dim(),
|
||||||
|
record.target(),
|
||||||
style(record.args()).dim(),
|
style(record.args()).dim(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ pub fn initialize() -> App<'static, 'static> {
|
|||||||
.long("verbosity")
|
.long("verbosity")
|
||||||
.takes_value(false)
|
.takes_value(false)
|
||||||
.multiple(true)
|
.multiple(true)
|
||||||
.help("Increase verbosity level (use -vv or more for greater effect)"),
|
.help("Increase verbosity level (use -vv or more for greater effect. [CAUTION] 4 -v's is probably too much)"),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("proxy")
|
Arg::with_name("proxy")
|
||||||
@@ -218,6 +218,30 @@ pub fn initialize() -> App<'static, 'static> {
|
|||||||
"Filter out messages of a particular size (ex: -S 5120 -S 4927,1970)",
|
"Filter out messages of a particular size (ex: -S 5120 -S 4927,1970)",
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("filter_words")
|
||||||
|
.short("W")
|
||||||
|
.long("filter-words")
|
||||||
|
.value_name("WORDS")
|
||||||
|
.takes_value(true)
|
||||||
|
.multiple(true)
|
||||||
|
.use_delimiter(true)
|
||||||
|
.help(
|
||||||
|
"Filter out messages of a particular word count (ex: -W 312 -W 91,82)",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("filter_lines")
|
||||||
|
.short("N")
|
||||||
|
.long("filter-lines")
|
||||||
|
.value_name("LINES")
|
||||||
|
.takes_value(true)
|
||||||
|
.multiple(true)
|
||||||
|
.use_delimiter(true)
|
||||||
|
.help(
|
||||||
|
"Filter out messages of a particular line count (ex: -N 20 -N 31,30)",
|
||||||
|
),
|
||||||
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("filter_status")
|
Arg::with_name("filter_status")
|
||||||
.short("C")
|
.short("C")
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use crate::config::{CONFIGURATION, PROGRESS_PRINTER};
|
use crate::config::{CONFIGURATION, PROGRESS_PRINTER};
|
||||||
use crate::utils::{ferox_print, make_request, status_colorizer};
|
use crate::utils::{create_report_string, ferox_print, make_request};
|
||||||
use crate::{FeroxChannel, FeroxResponse};
|
use crate::{FeroxChannel, FeroxResponse};
|
||||||
use console::strip_ansi_codes;
|
use console::strip_ansi_codes;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
@@ -95,21 +95,13 @@ async fn spawn_terminal_reporter(
|
|||||||
log::trace!("received {} on reporting channel", resp.url());
|
log::trace!("received {} on reporting channel", resp.url());
|
||||||
|
|
||||||
if CONFIGURATION.status_codes.contains(&resp.status().as_u16()) {
|
if CONFIGURATION.status_codes.contains(&resp.status().as_u16()) {
|
||||||
let report = if CONFIGURATION.quiet {
|
let report = create_report_string(
|
||||||
// -q used, just need the url
|
resp.status().as_str(),
|
||||||
format!("{}\n", resp.url())
|
&resp.line_count().to_string(),
|
||||||
} else {
|
&resp.word_count().to_string(),
|
||||||
// normal printing with status and size
|
&resp.content_length().to_string(),
|
||||||
let status = status_colorizer(&resp.status().as_str());
|
&resp.url().to_string(),
|
||||||
format!(
|
);
|
||||||
// example output
|
|
||||||
// 200 3280 https://localhost.com/FAQ
|
|
||||||
"{} {:>10} {}\n",
|
|
||||||
status,
|
|
||||||
resp.content_length(),
|
|
||||||
resp.url()
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
// print to stdout
|
// print to stdout
|
||||||
ferox_print(&report, &PROGRESS_PRINTER);
|
ferox_print(&report, &PROGRESS_PRINTER);
|
||||||
|
|||||||
@@ -285,7 +285,7 @@ fn create_urls(target_url: &str, word: &str, extensions: &[String]) -> Vec<Url>
|
|||||||
/// handles 2xx and 3xx responses by either checking if the url ends with a / (2xx)
|
/// handles 2xx and 3xx responses by either checking if the url ends with a / (2xx)
|
||||||
/// or if the Location header is present and matches the base url + / (3xx)
|
/// or if the Location header is present and matches the base url + / (3xx)
|
||||||
fn response_is_directory(response: &FeroxResponse) -> bool {
|
fn response_is_directory(response: &FeroxResponse) -> bool {
|
||||||
log::trace!("enter: is_directory({:?})", response);
|
log::trace!("enter: is_directory({})", response);
|
||||||
|
|
||||||
if response.status().is_redirection() {
|
if response.status().is_redirection() {
|
||||||
// status code is 3xx
|
// status code is 3xx
|
||||||
@@ -311,10 +311,7 @@ fn response_is_directory(response: &FeroxResponse) -> bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
log::debug!(
|
log::debug!("expected Location header, but none was found: {}", response);
|
||||||
"expected Location header, but none was found: {:?}",
|
|
||||||
response
|
|
||||||
);
|
|
||||||
log::trace!("exit: is_directory -> false");
|
log::trace!("exit: is_directory -> false");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -370,7 +367,7 @@ async fn try_recursion(
|
|||||||
transmitter: UnboundedSender<String>,
|
transmitter: UnboundedSender<String>,
|
||||||
) {
|
) {
|
||||||
log::trace!(
|
log::trace!(
|
||||||
"enter: try_recursion({:?}, {}, {:?})",
|
"enter: try_recursion({}, {}, {:?})",
|
||||||
response,
|
response,
|
||||||
base_depth,
|
base_depth,
|
||||||
transmitter
|
transmitter
|
||||||
@@ -421,6 +418,12 @@ pub fn should_filter_response(response: &FeroxResponse) -> bool {
|
|||||||
if CONFIGURATION
|
if CONFIGURATION
|
||||||
.filter_size
|
.filter_size
|
||||||
.contains(&response.content_length())
|
.contains(&response.content_length())
|
||||||
|
|| CONFIGURATION
|
||||||
|
.filter_line_count
|
||||||
|
.contains(&response.line_count())
|
||||||
|
|| CONFIGURATION
|
||||||
|
.filter_word_count
|
||||||
|
.contains(&response.word_count())
|
||||||
{
|
{
|
||||||
// filtered value from --filter-size, size filters and wildcards are two separate filters
|
// filtered value from --filter-size, size filters and wildcards are two separate filters
|
||||||
// and are applied independently
|
// and are applied independently
|
||||||
@@ -470,7 +473,7 @@ async fn make_requests(
|
|||||||
for url in urls {
|
for url in urls {
|
||||||
if let Ok(response) = make_request(&CONFIGURATION.client, &url).await {
|
if let Ok(response) = make_request(&CONFIGURATION.client, &url).await {
|
||||||
// response came back without error, convert it to FeroxResponse
|
// response came back without error, convert it to FeroxResponse
|
||||||
let ferox_response = FeroxResponse::from(response, CONFIGURATION.extract_links).await;
|
let ferox_response = FeroxResponse::from(response, true).await;
|
||||||
|
|
||||||
// do recursion if appropriate
|
// do recursion if appropriate
|
||||||
if !CONFIGURATION.no_recursion {
|
if !CONFIGURATION.no_recursion {
|
||||||
@@ -513,8 +516,7 @@ async fn make_requests(
|
|||||||
Err(_) => continue,
|
Err(_) => continue,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut new_ferox_response =
|
let mut new_ferox_response = FeroxResponse::from(new_response, true).await;
|
||||||
FeroxResponse::from(new_response, CONFIGURATION.extract_links).await;
|
|
||||||
|
|
||||||
// filter if necessary
|
// filter if necessary
|
||||||
if should_filter_response(&new_ferox_response) {
|
if should_filter_response(&new_ferox_response) {
|
||||||
@@ -523,11 +525,7 @@ async fn make_requests(
|
|||||||
|
|
||||||
if new_ferox_response.is_file() {
|
if new_ferox_response.is_file() {
|
||||||
// very likely a file, simply request and report
|
// very likely a file, simply request and report
|
||||||
log::debug!(
|
log::debug!("Singular extraction: {}", new_ferox_response);
|
||||||
"Singular extraction: {} ({})",
|
|
||||||
new_ferox_response.url(),
|
|
||||||
new_ferox_response.status().as_str(),
|
|
||||||
);
|
|
||||||
|
|
||||||
send_report(report_chan.clone(), new_ferox_response);
|
send_report(report_chan.clone(), new_ferox_response);
|
||||||
|
|
||||||
@@ -535,11 +533,7 @@ async fn make_requests(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !CONFIGURATION.no_recursion {
|
if !CONFIGURATION.no_recursion {
|
||||||
log::debug!(
|
log::debug!("Recursive extraction: {}", new_ferox_response);
|
||||||
"Recursive extraction: {} ({})",
|
|
||||||
new_ferox_response.url(),
|
|
||||||
new_ferox_response.status().as_str()
|
|
||||||
);
|
|
||||||
|
|
||||||
if new_ferox_response.status().is_success()
|
if new_ferox_response.status().is_success()
|
||||||
&& !new_ferox_response.url().as_str().ends_with('/')
|
&& !new_ferox_response.url().as_str().ends_with('/')
|
||||||
@@ -565,7 +559,7 @@ async fn make_requests(
|
|||||||
|
|
||||||
/// Simple helper to send a `FeroxResponse` over the tx side of an `mpsc::unbounded_channel`
|
/// Simple helper to send a `FeroxResponse` over the tx side of an `mpsc::unbounded_channel`
|
||||||
fn send_report(report_sender: UnboundedSender<FeroxResponse>, response: FeroxResponse) {
|
fn send_report(report_sender: UnboundedSender<FeroxResponse>, response: FeroxResponse) {
|
||||||
log::trace!("enter: send_report({:?}, {:?}", report_sender, response);
|
log::trace!("enter: send_report({:?}, {}", report_sender, response);
|
||||||
|
|
||||||
match report_sender.send(response) {
|
match report_sender.send(response) {
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
|
|||||||
46
src/utils.rs
46
src/utils.rs
@@ -1,8 +1,10 @@
|
|||||||
use crate::{FeroxError, FeroxResult};
|
use crate::{
|
||||||
|
config::{CONFIGURATION, PROGRESS_PRINTER},
|
||||||
|
FeroxError, FeroxResult,
|
||||||
|
};
|
||||||
use console::{strip_ansi_codes, style, user_attended};
|
use console::{strip_ansi_codes, style, user_attended};
|
||||||
use indicatif::ProgressBar;
|
use indicatif::ProgressBar;
|
||||||
use reqwest::Url;
|
use reqwest::{Client, Response, Url};
|
||||||
use reqwest::{Client, Response};
|
|
||||||
#[cfg(not(target_os = "windows"))]
|
#[cfg(not(target_os = "windows"))]
|
||||||
use rlimit::{getrlimit, setrlimit, Resource, Rlim};
|
use rlimit::{getrlimit, setrlimit, Resource, Rlim};
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
@@ -244,7 +246,6 @@ pub async fn make_request(client: &Client, url: &Url) -> FeroxResult<Response> {
|
|||||||
|
|
||||||
match client.get(url.to_owned()).send().await {
|
match client.get(url.to_owned()).send().await {
|
||||||
Ok(resp) => {
|
Ok(resp) => {
|
||||||
log::debug!("requested Url: {}", resp.url());
|
|
||||||
log::trace!("exit: make_request -> {:?}", resp);
|
log::trace!("exit: make_request -> {:?}", resp);
|
||||||
Ok(resp)
|
Ok(resp)
|
||||||
}
|
}
|
||||||
@@ -253,6 +254,19 @@ pub async fn make_request(client: &Client, url: &Url) -> FeroxResult<Response> {
|
|||||||
if e.to_string().contains("operation timed out") {
|
if e.to_string().contains("operation timed out") {
|
||||||
// only warn for timeouts, while actual errors are still left as errors
|
// only warn for timeouts, while actual errors are still left as errors
|
||||||
log::warn!("Error while making request: {}", e);
|
log::warn!("Error while making request: {}", e);
|
||||||
|
} else if e.is_redirect() {
|
||||||
|
if let Some(last_redirect) = e.url() {
|
||||||
|
// get where we were headed (last_redirect) and where we came from (url)
|
||||||
|
let fancy_message = format!("{} !=> {}", url, last_redirect);
|
||||||
|
|
||||||
|
let report = if let Some(msg_status) = e.status() {
|
||||||
|
create_report_string(msg_status.as_str(), "-1", "-1", "-1", &fancy_message)
|
||||||
|
} else {
|
||||||
|
create_report_string("UNK", "-1", "-1", "-1", &fancy_message)
|
||||||
|
};
|
||||||
|
|
||||||
|
ferox_print(&report, &PROGRESS_PRINTER)
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
log::error!("Error while making request: {}", e);
|
log::error!("Error while making request: {}", e);
|
||||||
}
|
}
|
||||||
@@ -261,6 +275,30 @@ pub async fn make_request(client: &Client, url: &Url) -> FeroxResult<Response> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Helper to create the standard line for output to file/terminal
|
||||||
|
///
|
||||||
|
/// example output:
|
||||||
|
/// 200 127l 283w 4134c http://localhost/faq
|
||||||
|
pub fn create_report_string(
|
||||||
|
status: &str,
|
||||||
|
line_count: &str,
|
||||||
|
word_count: &str,
|
||||||
|
content_length: &str,
|
||||||
|
url: &str,
|
||||||
|
) -> String {
|
||||||
|
if CONFIGURATION.quiet {
|
||||||
|
// -q used, just need the url
|
||||||
|
format!("{}\n", url)
|
||||||
|
} else {
|
||||||
|
// normal printing with status and sizes
|
||||||
|
let color_status = status_colorizer(status);
|
||||||
|
format!(
|
||||||
|
"{} {:>8}l {:>8}w {:>8}c {}\n",
|
||||||
|
color_status, line_count, word_count, content_length, url
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Attempts to set the soft limit for the RLIMIT_NOFILE resource
|
/// Attempts to set the soft limit for the RLIMIT_NOFILE resource
|
||||||
///
|
///
|
||||||
/// RLIMIT_NOFILE is the maximum number of file descriptors that can be opened by this process
|
/// RLIMIT_NOFILE is the maximum number of file descriptors that can be opened by this process
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ fn banner_prints_headers() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
#[test]
|
#[test]
|
||||||
/// test allows non-existent wordlist to trigger the banner printing to stderr
|
/// test allows non-existent wordlist to trigger the banner printing to stderr
|
||||||
/// expect to see all mandatory prints + multiple size filters
|
/// expect to see all mandatory prints + multiple size filters
|
||||||
fn banner_prints_filter_sizes() -> Result<(), Box<dyn std::error::Error>> {
|
fn banner_prints_filter_sizes() {
|
||||||
Command::cargo_bin("feroxbuster")
|
Command::cargo_bin("feroxbuster")
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.arg("--url")
|
.arg("--url")
|
||||||
@@ -126,6 +126,14 @@ fn banner_prints_filter_sizes() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
.arg("789456123")
|
.arg("789456123")
|
||||||
.arg("--filter-size")
|
.arg("--filter-size")
|
||||||
.arg("44444444")
|
.arg("44444444")
|
||||||
|
.arg("-N")
|
||||||
|
.arg("678")
|
||||||
|
.arg("--filter-lines")
|
||||||
|
.arg("679")
|
||||||
|
.arg("-W")
|
||||||
|
.arg("93")
|
||||||
|
.arg("--filter-words")
|
||||||
|
.arg("94")
|
||||||
.assert()
|
.assert()
|
||||||
.success()
|
.success()
|
||||||
.stderr(
|
.stderr(
|
||||||
@@ -138,11 +146,16 @@ fn banner_prints_filter_sizes() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
.and(predicate::str::contains("Timeout (secs)"))
|
.and(predicate::str::contains("Timeout (secs)"))
|
||||||
.and(predicate::str::contains("User-Agent"))
|
.and(predicate::str::contains("User-Agent"))
|
||||||
.and(predicate::str::contains("Size Filter"))
|
.and(predicate::str::contains("Size Filter"))
|
||||||
|
.and(predicate::str::contains("Word Count Filter"))
|
||||||
|
.and(predicate::str::contains("Line Count Filter"))
|
||||||
.and(predicate::str::contains("789456123"))
|
.and(predicate::str::contains("789456123"))
|
||||||
.and(predicate::str::contains("44444444"))
|
.and(predicate::str::contains("44444444"))
|
||||||
|
.and(predicate::str::contains("93"))
|
||||||
|
.and(predicate::str::contains("94"))
|
||||||
|
.and(predicate::str::contains("678"))
|
||||||
|
.and(predicate::str::contains("679"))
|
||||||
.and(predicate::str::contains("─┴─")),
|
.and(predicate::str::contains("─┴─")),
|
||||||
);
|
);
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
Reference in New Issue
Block a user