Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add statistics tracking #168

Merged
merged 44 commits into from
Jan 5, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
4093e7e
bumped version to 1.11.0
epi052 Dec 24, 2020
0885797
interim save to work on a bugfix
epi052 Dec 25, 2020
9aa2492
Merge branch 'master' into 123-auto-tune-scans
epi052 Dec 27, 2020
d41e01c
added statistics tracking to make_request
epi052 Dec 29, 2020
cfa5be0
removed swap files
epi052 Dec 29, 2020
b581bcd
reviewed banner; bumped crossterm to 0.19
epi052 Dec 29, 2020
a2053ec
reviewed extractor
epi052 Dec 29, 2020
873a38c
fixed tests to conform to new function definitions
epi052 Dec 29, 2020
6287270
appeased the all-mighty clippy
epi052 Dec 29, 2020
0718706
reviewed extractor
epi052 Dec 29, 2020
19a6548
reviewed heuristics
epi052 Dec 29, 2020
8f6c2e2
fixed macro import/export
epi052 Dec 29, 2020
6d1cd0d
fixed macro import/export
epi052 Dec 29, 2020
22c957d
added .rustfmt.toml to prevent module reordering in lib.rs
epi052 Dec 29, 2020
9d9ae1f
main reviewed
epi052 Dec 29, 2020
e74e58a
reviewed reporter
epi052 Dec 29, 2020
197c5e7
reviewed scanner
epi052 Dec 29, 2020
c13ec8d
reviewed utils/statistics
epi052 Dec 29, 2020
05a0857
total number of requests matches expected total
epi052 Dec 30, 2020
3b2b1be
added filtered responses to stats
epi052 Dec 30, 2020
d8af9c5
implemented serialization of statistics
epi052 Dec 30, 2020
6439efb
added rwlock to stats
epi052 Dec 31, 2020
0567c96
fmt/clippy; added total runtime
epi052 Jan 1, 2021
57a3f4f
incremental push to write tests against
epi052 Jan 1, 2021
07b31f5
added tests to statistics
epi052 Jan 1, 2021
31c5bf9
fixed stats::wilcard test
epi052 Jan 1, 2021
bc78e9c
pipeline clippy updated; addressed new clippy errors
epi052 Jan 1, 2021
eb58574
removed lint
epi052 Jan 1, 2021
5b80903
pipeline clippy updated; addressed new clippy errors
epi052 Jan 2, 2021
51b1731
added realtime stats bar
epi052 Jan 2, 2021
06fe552
fixed tests; added logic for all other StatErrors
epi052 Jan 3, 2021
8923529
bumped version to 1.11.1
epi052 Jan 3, 2021
ab3177f
removed global num_requests tracker; logic in statistics now
epi052 Jan 3, 2021
11cd021
removed statistics::summary and related functions
epi052 Jan 3, 2021
e55ba72
touched up config imports
epi052 Jan 3, 2021
1b9963c
implemented logic for resume_scan with statistics support
epi052 Jan 4, 2021
722bf4c
added stats output to debug logging
epi052 Jan 5, 2021
51ec832
added correctness test for Stats::merge_from
epi052 Jan 5, 2021
0fdfa2a
cleaned up code in extractor related to getting multiplier
epi052 Jan 5, 2021
12c1cd0
removed deadcode in statistics
epi052 Jan 5, 2021
8332b3c
fixed imports
epi052 Jan 5, 2021
2637105
fixed failing serialization tests
epi052 Jan 5, 2021
62efbe3
added explanations for new bar and other display stuff
epi052 Jan 5, 2021
74f3761
added images
epi052 Jan 5, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .rustfmt.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
reorder_modules = false
8 changes: 4 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "feroxbuster"
version = "1.11.0"
version = "1.11.1"
authors = ["Ben 'epi' Risher <epibar052@gmail.com>"]
license = "MIT"
edition = "2018"
Expand Down Expand Up @@ -30,15 +30,15 @@ reqwest = { version = "0.10", features = ["socks"] }
clap = "2.33"
lazy_static = "1.4"
toml = "0.5"
serde = { version = "1.0", features = ["derive"] }
serde = { version = "1.0", features = ["derive", "rc"] }
serde_json = "1.0"
uuid = { version = "0.8", features = ["v4"] }
indicatif = "0.15"
console = "0.13"
console = "0.14"
openssl = { version = "0.10", features = ["vendored"] }
dirs = "3.0"
regex = "1"
crossterm = "0.18"
crossterm = "0.19"
rlimit = "0.5"
ctrlc = "3.1"
fuzzyhash = "0.2"
Expand Down
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ Enumeration.
- [Threads and Connection Limits At A High-Level](#threads-and-connection-limits-at-a-high-level)
- [ferox-config.toml](#ferox-configtoml)
- [Command Line Parsing](#command-line-parsing)
- [Scan's Display Explained](#-scans-display-explained)
- [Discovered Resource](#discovered-resource)
- [Overall Scan Progress Bar](#overall-scan-progress-bar)
- [Directory Scan Progress Bar](#directory-scan-progress-bar)
- [Example Usage](#-example-usage)
- [Multiple Values](#multiple-values)
- [Include Headers](#include-headers)
Expand Down Expand Up @@ -460,6 +464,31 @@ OPTIONS:
-w, --wordlist <FILE> Path to the wordlist
```

## 📊 Scan's Display Explained

`feroxbuster` attempts to be intuitive and easy to understand, however, if you are wondering about any of the scan's
output and what it means, this is the section for you!

### Discovered Resource

When `feroxbuster` finds a response that you haven't filtered out, it's reported above the progress bars and looks similar to what's pictured below.

The number of lines, words, and bytes shown here can be used to [filter those responses](#filter-response-by-word-count--line-count--new-in-v160)

![response-bar-explained](img/response-bar-explained.png)

### Overall Scan Progress Bar

The top progress bar, colored yellow, tracks the overall scan status. Its fields are described in the image below.

![total-bar-explained](img/total-bar-explained.png)

### Directory Scan Progress Bar

All other progress bars, colored cyan, represent a scan of one particular directory and will look similar to what's below.

![dir-scan-bar-explained](img/dir-scan-bar-explained.png)

## 🧰 Example Usage

### Multiple Values
Expand Down
Binary file modified img/demo.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added img/dir-scan-bar-explained.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added img/response-bar-explained.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added img/total-bar-explained.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
94 changes: 72 additions & 22 deletions src/banner.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
use crate::config::{Configuration, CONFIGURATION};
use crate::utils::{make_request, status_colorizer};
use crate::{
config::{Configuration, CONFIGURATION},
statistics::StatCommand,
utils::{make_request, status_colorizer},
};
use console::{style, Emoji};
use reqwest::{Client, Url};
use serde_json::Value;
use std::io::Write;
use tokio::sync::mpsc::UnboundedSender;

/// macro helper to abstract away repetitive string formatting
macro_rules! format_banner_entry_helper {
Expand Down Expand Up @@ -67,8 +71,13 @@ enum UpdateStatus {
/// ex: v1.1.0
///
/// Returns `UpdateStatus`
async fn needs_update(client: &Client, url: &str, bin_version: &str) -> UpdateStatus {
log::trace!("enter: needs_update({:?}, {})", client, url);
async fn needs_update(
client: &Client,
url: &str,
bin_version: &str,
tx_stats: UnboundedSender<StatCommand>,
) -> UpdateStatus {
log::trace!("enter: needs_update({:?}, {}, {:?})", client, url, tx_stats);

let unknown = UpdateStatus::Unknown;

Expand All @@ -81,7 +90,7 @@ async fn needs_update(client: &Client, url: &str, bin_version: &str) -> UpdateSt
}
};

if let Ok(response) = make_request(&client, &api_url).await {
if let Ok(response) = make_request(&client, &api_url, tx_stats.clone()).await {
let body = response.text().await.unwrap_or_default();

let json_response: Value = serde_json::from_str(&body).unwrap_or_default();
Expand Down Expand Up @@ -137,8 +146,13 @@ fn format_emoji(emoji: &str) -> String {
/// Prints the banner to stdout.
///
/// Only prints those settings which are either always present, or passed in by the user.
pub async fn initialize<W>(targets: &[String], config: &Configuration, version: &str, mut writer: W)
where
pub async fn initialize<W>(
targets: &[String],
config: &Configuration,
version: &str,
mut writer: W,
tx_stats: UnboundedSender<StatCommand>,
) where
W: Write,
{
let artwork = format!(
Expand All @@ -150,7 +164,7 @@ by Ben "epi" Risher {} ver: {}"#,
Emoji("🤓", &format!("{:<2}", "\u{0020}")),
version
);
let status = needs_update(&CONFIGURATION.client, UPDATE_URL, version).await;
let status = needs_update(&CONFIGURATION.client, UPDATE_URL, version, tx_stats).await;

let top = "───────────────────────────┬──────────────────────";
let addl_section = "──────────────────────────────────────────────────";
Expand Down Expand Up @@ -536,59 +550,80 @@ by Ben "epi" Risher {} ver: {}"#,
#[cfg(test)]
mod tests {
use super::*;
use crate::VERSION;
use crate::{FeroxChannel, VERSION};
use httpmock::Method::GET;
use httpmock::MockServer;
use std::fs::read_to_string;
use std::io::stderr;
use std::time::Duration;
use tempfile::NamedTempFile;
use tokio::sync::mpsc;

#[tokio::test(core_threads = 1)]
/// test to hit no execution of targets for loop in banner
async fn banner_intialize_without_targets() {
let config = Configuration::default();
initialize(&[], &config, VERSION, stderr()).await;
let (tx, _): FeroxChannel<StatCommand> = mpsc::unbounded_channel();

initialize(&[], &config, VERSION, stderr(), tx).await;
}

#[tokio::test(core_threads = 1)]
/// test to hit no execution of statuscode for loop in banner
async fn banner_intialize_without_status_codes() {
let mut config = Configuration::default();
config.status_codes = vec![];
let config = Configuration {
status_codes: vec![],
..Default::default()
};

let (tx, _): FeroxChannel<StatCommand> = mpsc::unbounded_channel();

initialize(
&[String::from("http://localhost")],
&config,
VERSION,
stderr(),
tx,
)
.await;
}

#[tokio::test(core_threads = 1)]
/// test to hit an empty config file
async fn banner_intialize_without_config_file() {
let mut config = Configuration::default();
config.config = String::new();
let config = Configuration {
config: String::new(),
..Default::default()
};

let (tx, _): FeroxChannel<StatCommand> = mpsc::unbounded_channel();

initialize(
&[String::from("http://localhost")],
&config,
VERSION,
stderr(),
tx,
)
.await;
}

#[tokio::test(core_threads = 1)]
/// test to hit an empty config file
async fn banner_intialize_without_queries() {
let mut config = Configuration::default();
config.queries = vec![(String::new(), String::new())];
let config = Configuration {
queries: vec![(String::new(), String::new())],
..Default::default()
};

let (tx, _): FeroxChannel<StatCommand> = mpsc::unbounded_channel();

initialize(
&[String::from("http://localhost")],
&config,
VERSION,
stderr(),
tx,
)
.await;
}
Expand All @@ -598,11 +633,14 @@ mod tests {
async fn banner_intialize_with_mismatched_version() {
let config = Configuration::default();
let file = NamedTempFile::new().unwrap();
let (tx, _): FeroxChannel<StatCommand> = mpsc::unbounded_channel();

initialize(
&[String::from("http://localhost")],
&config,
"mismatched-version",
&file,
tx,
)
.await;
let contents = read_to_string(file.path()).unwrap();
Expand All @@ -614,7 +652,9 @@ mod tests {
#[tokio::test(core_threads = 1)]
/// test that
async fn banner_needs_update_returns_unknown_with_bad_url() {
let result = needs_update(&CONFIGURATION.client, &"", VERSION).await;
let (tx, _): FeroxChannel<StatCommand> = mpsc::unbounded_channel();

let result = needs_update(&CONFIGURATION.client, &"", VERSION, tx).await;
assert!(matches!(result, UpdateStatus::Unknown));
}

Expand All @@ -628,7 +668,9 @@ mod tests {
then.status(200).body("{\"tag_name\":\"v1.1.0\"}");
});

let result = needs_update(&CONFIGURATION.client, &srv.url("/latest"), "1.1.0").await;
let (tx, _): FeroxChannel<StatCommand> = mpsc::unbounded_channel();

let result = needs_update(&CONFIGURATION.client, &srv.url("/latest"), "1.1.0", tx).await;

assert_eq!(mock.hits(), 1);
assert!(matches!(result, UpdateStatus::UpToDate));
Expand All @@ -644,7 +686,9 @@ mod tests {
then.status(200).body("{\"tag_name\":\"v1.1.0\"}");
});

let result = needs_update(&CONFIGURATION.client, &srv.url("/latest"), "1.0.1").await;
let (tx, _): FeroxChannel<StatCommand> = mpsc::unbounded_channel();

let result = needs_update(&CONFIGURATION.client, &srv.url("/latest"), "1.0.1", tx).await;

assert_eq!(mock.hits(), 1);
assert!(matches!(result, UpdateStatus::OutOfDate));
Expand All @@ -662,7 +706,9 @@ mod tests {
.delay(Duration::from_secs(8));
});

let result = needs_update(&CONFIGURATION.client, &srv.url("/latest"), "1.0.1").await;
let (tx, _): FeroxChannel<StatCommand> = mpsc::unbounded_channel();

let result = needs_update(&CONFIGURATION.client, &srv.url("/latest"), "1.0.1", tx).await;

assert_eq!(mock.hits(), 1);
assert!(matches!(result, UpdateStatus::Unknown));
Expand All @@ -678,7 +724,9 @@ mod tests {
then.status(200).body("not json");
});

let result = needs_update(&CONFIGURATION.client, &srv.url("/latest"), "1.0.1").await;
let (tx, _): FeroxChannel<StatCommand> = mpsc::unbounded_channel();

let result = needs_update(&CONFIGURATION.client, &srv.url("/latest"), "1.0.1", tx).await;

assert_eq!(mock.hits(), 1);
assert!(matches!(result, UpdateStatus::Unknown));
Expand All @@ -695,7 +743,9 @@ mod tests {
.body("{\"no tag_name\": \"doesn't exist\"}");
});

let result = needs_update(&CONFIGURATION.client, &srv.url("/latest"), "1.0.1").await;
let (tx, _): FeroxChannel<StatCommand> = mpsc::unbounded_channel();

let result = needs_update(&CONFIGURATION.client, &srv.url("/latest"), "1.0.1", tx).await;

assert_eq!(mock.hits(), 1);
assert!(matches!(result, UpdateStatus::Unknown));
Expand Down
Loading