Compare commits

..

27 Commits

Author SHA1 Message Date
epi
71649d1296 Merge pull request #68 from epi052/67-duplicate-scans-occurring
fixed duplicate directory scans
2020-10-08 20:50:48 -05:00
epi
a89f2be37b fmt / clippy 2020-10-08 20:43:24 -05:00
epi
572e5b7a95 fixed duplicate directory scans 2020-10-08 20:39:13 -05:00
epi
2e71d91960 Merge pull request #64 from TGotwig/patch-1
Publish with Homebrew on MacOS & Linux 🍺
2020-10-08 13:04:46 -05:00
epi
f9cdd91da9 added tar.gz for homebrew installs 2020-10-08 07:15:29 -05:00
epi
003b7f39f7 added tar.gz for homebrew installs 2020-10-08 06:53:45 -05:00
epi
39dfe442e8 added tar.gz for homebrew installs 2020-10-08 06:35:13 -05:00
epi
7d75a2cfd4 added tar.gz for homebrew installs 2020-10-08 06:28:59 -05:00
epi
57d5ea1e01 version bumped to v1.0.2 2020-10-07 17:13:52 -05:00
epi
4b4af5a303 Merge pull request #62 from epi052/61-change-url-error-to-warning
timeouts logged as warnings, other errors remain the same
2020-10-07 17:13:07 -05:00
Thomas Gotwig
9657385282 Publish with Homebrew on MacOS & Linux 🍺
closes #63
2020-10-07 14:50:22 +02:00
epi
4279ac372c timeouts now logged as warnings, other errors remain the same 2020-10-06 17:13:28 -05:00
epi
1f66d17516 Update README.md 2020-10-06 06:07:11 -05:00
epi
bf2f9431c7 Update README.md 2020-10-06 06:06:32 -05:00
epi
859069359a Update README.md 2020-10-06 06:05:32 -05:00
epi
c370dcc172 Merge pull request #55 from epi052/jsav-docker-for-ferox
@jsav0 added Dockerfile based on alpine. Includes wordlists.
2020-10-05 20:50:04 -05:00
epi
30ce6a3171 added docker install section 2020-10-05 20:42:16 -05:00
epi
951bd87c0e updated url to point to latest instead of 1.0.0 2020-10-05 20:24:39 -05:00
epi
7c036e587e fixed possible thread panic due to multiple calls to join 2020-10-05 18:52:22 -05:00
epi
b733477a61 Update README.md 2020-10-05 16:18:40 -05:00
epi
58e367b5c3 Update README.md 2020-10-05 14:47:56 -05:00
epi
99021db091 Update README.md 2020-10-05 14:47:18 -05:00
epi
7f145f11df Update README.md 2020-10-05 14:46:13 -05:00
epi
68ee5883b8 Update README.md 2020-10-05 14:45:11 -05:00
jsavage
1a2c08393d added Dockerfile based on alpine. Includes wordlists 2020-10-05 08:53:43 -04:00
epi
9b929fdb15 Merge pull request #53 from joohoi/ffuf_corrections
[Documentation] README.md matrix fixes for ffuf
2020-10-05 06:23:48 -05:00
Joona Hoikkala
a87dc64e8e ffuf corrections for the README.md matrix 2020-10-05 10:51:08 +03:00
6 changed files with 233 additions and 16 deletions

View File

@@ -59,18 +59,40 @@ jobs:
name: feroxbuster_amd64.deb
path: ./target/x86_64-unknown-linux-musl/debian/*
build-rest:
build-macos:
runs-on: macos-latest
if: github.ref == 'refs/heads/master'
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
target: x86_64-apple-darwin
override: true
- uses: actions-rs/cargo@v1
with:
use-cross: true
command: build
args: --release --target=x86_64-apple-darwin
- name: Build tar.gz for homebrew installs
run: |
tar czf x86_64-macos-feroxbuster.tar.gz -C target/x86_64-apple-darwin/release feroxbuster
- uses: actions/upload-artifact@v2
with:
name: x86_64-macos-feroxbuster
path: target/x86_64-apple-darwin/release/feroxbuster
- uses: actions/upload-artifact@v2
with:
name: x86_64-macos-feroxbuster.tar.gz
path: x86_64-macos-feroxbuster.tar.gz
build-windows:
runs-on: ${{ matrix.os }}
if: github.ref == 'refs/heads/master'
strategy:
matrix:
type: [windows-x64, windows-x86, macos]
type: [windows-x64, windows-x86]
include:
- type: macos
os: macos-latest
target: x86_64-apple-darwin
name: x86_64-macos-feroxbuster
path: target/x86_64-apple-darwin/release/feroxbuster
- type: windows-x64
os: windows-latest
target: x86_64-pc-windows-msvc
@@ -97,3 +119,4 @@ jobs:
with:
name: ${{ matrix.name }}
path: ${{ matrix.path }}

View File

@@ -1,6 +1,6 @@
[package]
name = "feroxbuster"
version = "1.0.0"
version = "1.0.3"
authors = ["Ben 'epi' Risher <epibar052@gmail.com>"]
license = "MIT"
edition = "2018"
@@ -49,4 +49,4 @@ conf-files = ["/etc/feroxbuster/ferox-config.toml"]
assets = [
["target/release/feroxbuster", "/usr/bin/", "755"],
["ferox-config.toml.example", "/etc/feroxbuster/ferox-config.toml", "644"],
]
]

12
Dockerfile Normal file
View File

@@ -0,0 +1,12 @@
FROM alpine:latest
LABEL maintainer="wfnintr@null.net"
# download default wordlists
RUN apk add --no-cache --virtual .depends subversion && \
svn export https://github.com/danielmiessler/SecLists/trunk/Discovery/Web-Content /usr/share/seclists/Discovery/Web-Content && \
apk del .depends
# install latest release
RUN wget https://github.com/epi052/feroxbuster/releases/latest/download/x86_64-linux-feroxbuster.zip -qO feroxbuster.zip && unzip -d /usr/local/bin/ feroxbuster.zip feroxbuster && rm feroxbuster.zip && chmod +x /usr/local/bin/feroxbuster
ENTRYPOINT ["feroxbuster"]

View File

@@ -47,13 +47,25 @@
Ferox is short for Ferric Oxide. Ferric Oxide, simply put, is rust. The name rustbuster was taken, so I decided on a variation. 🤷
## 🤔 What's it do tho?
`feroxbuster` is a tool designed to perform [Forced Browsing](https://owasp.org/www-community/attacks/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.
📖 Table of Contents
-----------------
- [Downloads](#-downloads)
- [Installation](#-installation)
- [Download a Release](#download-a-release)
- [Homebrew on MacOS and Linux](#homebrew-on-macos-and-linux)
- [Cargo Install](#cargo-install)
- [apt Install](#apt-install)
- [Docker Install](#docker-install)
- [Configuration](#-configuration)
- [Default Values](#default-values)
- [ferox-config.toml](#ferox-configtoml)
@@ -80,6 +92,24 @@ Releases for multiple architectures can be found in the [Releases](https://githu
- Windows x86
- Windows x86_64
### Homebrew on MacOS and Linux
Installable by Homebrew throughout own formulas:
🍏 [MacOS](https://github.com/TGotwig/homebrew-feroxbuster/blob/main/feroxbuster.rb)
```shell
brew tap tgotwig/feroxbuster
brew install feroxbuster
```
🐧 [Linux](https://github.com/TGotwig/homebrew-linux-feroxbuster/blob/main/feroxbuster.rb)
```shell
brew tap tgotwig/linux-feroxbuster
brew install feroxbuster
```
### Cargo Install
`feroxbuster` is published on crates.io, making it easy to install if you already have rust installed on your system.
@@ -96,6 +126,61 @@ Head to the [Releases](https://github.com/epi052/feroxbuster/releases) section a
sudo apt install ./feroxbuster_amd64.deb
```
### Docker Install
> The following steps assume you have docker installed / setup
First, clone the repository.
```
git clone https://github.com/epi052/feroxbuster.git
cd feroxbuster
```
Next, build the image.
```
sudo docker build -t feroxbuster .
```
After that, you should be able to use `docker run` to perform scans with `feroxbuster`.
#### Basic usage
```
sudo docker run --init -it feroxbuster -u http://example.com -x js,html
```
#### Piping from stdin and proxying all requests through socks5 proxy
```
cat targets.txt | sudo docker run --net=host --init -i feroxbuster --stdin -x js,html --proxy socks5://127.0.0.1:9050
```
#### Mount a volume to pass in `ferox-config.toml`
You've got some options available if you want to pass in a config file. [`ferox-buster.toml`](#ferox-configtoml) can live in multiple locations and still be valid, so it's up to you how you'd like to pass it in. Below are a few valid examples:
```
sudo docker run --init -v $(pwd)/ferox-config.toml:/etc/feroxbuster/ferox-config.toml -it feroxbuster -u http://example.com
```
```
sudo docker run --init -v ~/.config/feroxbuster:/root/.config/feroxbuster -it feroxbuster -u http://example.com
```
Note: If you are on a SELinux enforced system, you will need to pass the `:Z` attribute also.
```
docker run --init -v (pwd)/ferox-config.toml:/etc/feroxbuster/ferox-config.toml:Z -it feroxbuster -u http://example.com
```
#### Define an alias for simplicity
```
alias feroxbuster="sudo docker run --init -v ~/.config/feroxbuster:/root/.config/feroxbuster -i feroxbuster"
```
## ⚙️ Configuration
### Default Values
Configuration begins with with the following built-in default values baked into the binary:
@@ -295,10 +380,10 @@ a few of the use-cases in which feroxbuster may be a better fit:
| allows recursion | ✔ | | ✔ |
| can specify query parameters | ✔ | | ✔ |
| SOCKS proxy support | ✔ | | |
| multiple target scan (via stdin or multiple -u) | ✔ | | |
| multiple target scan (via stdin or multiple -u) | ✔ | | |
| configuration file for default value override | ✔ | | ✔ |
| can accept urls via STDIN as part of a pipeline | ✔ | | |
| can accept wordlists via STDIN | | ✔ | |
| can accept urls via STDIN as part of a pipeline | ✔ | | |
| can accept wordlists via STDIN | | ✔ | |
| filter by response size | ✔ | | ✔ |
| auto-filter wildcard responses | ✔ | | ✔ |
| performs other scans (vhost, dns, etc) | | ✔ | ✔ |

View File

@@ -6,16 +6,25 @@ use crate::utils::{
use crate::{heuristics, progress};
use futures::future::{BoxFuture, FutureExt};
use futures::{stream, StreamExt};
use lazy_static::lazy_static;
use reqwest::{Response, Url};
use std::collections::HashSet;
use std::convert::TryInto;
use std::ops::Deref;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, RwLock};
use tokio::fs;
use tokio::io::{self, AsyncWriteExt};
use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender};
use tokio::task::JoinHandle;
static CALL_COUNT: AtomicUsize = AtomicUsize::new(0);
lazy_static! {
/// Global configuration state
static ref SCANNED_URLS: RwLock<HashSet<String>> = RwLock::new(HashSet::new());
}
/// Spawn a single consumer task (sc side of mpsc)
///
/// The consumer simply receives responses and writes them to the given output file if they meet
@@ -116,6 +125,43 @@ async fn spawn_terminal_reporter(mut report_channel: UnboundedReceiver<Response>
log::trace!("exit: spawn_terminal_reporter");
}
/// Adds the given url to `SCANNED_URLS`
///
/// If `SCANNED_URLS` did not already contain the url, return true; otherwise return false
fn add_url_to_list_of_scanned_urls(resp: &str, scanned_urls: &RwLock<HashSet<String>>) -> bool {
log::trace!(
"enter: add_url_to_list_of_scanned_urls({}, {:?})",
resp,
scanned_urls
);
match scanned_urls.write() {
// check new url against what's already been scanned
Ok(mut urls) => {
let normalized_url = if resp.ends_with('/') {
// append a / to the list of 'seen' urls, this is to prevent the case where
// 3xx and 2xx duplicate eachother
resp.to_string()
} else {
format!("{}/", resp)
};
// If the set did not contain resp, true is returned.
// If the set did contain resp, false is returned.
let response = urls.insert(normalized_url);
log::trace!("exit: add_url_to_list_of_scanned_urls -> {}", response);
response
}
Err(e) => {
// poisoned lock
log::error!("Set of scanned urls poisoned: {}", e);
log::trace!("exit: add_url_to_list_of_scanned_urls -> false");
false
}
}
}
/// Spawn a single consumer task (sc side of mpsc)
///
/// The consumer simply receives Urls and scans them
@@ -134,6 +180,13 @@ fn spawn_recursion_handler(
let boxed_future = async move {
let mut scans = vec![];
while let Some(resp) = recursion_channel.recv().await {
let unknown = add_url_to_list_of_scanned_urls(&resp, &SCANNED_URLS);
if !unknown {
// not unknown, i.e. we've seen the url before and don't need to scan again
continue;
}
log::info!("received {} on recursion channel", resp);
let clonedresp = resp.clone();
let clonedlist = wordlist.clone();
@@ -430,10 +483,14 @@ pub async fn scan_url(target_url: &str, wordlist: Arc<HashSet<String>>, base_dep
let progress_bar = progress::add_bar(&target_url, num_reqs_expected, false);
progress_bar.reset_elapsed();
if get_current_depth(&target_url) - base_depth == 0 {
if CALL_COUNT.load(Ordering::Relaxed) == 0 {
// join can only be called once, otherwise it causes the thread to panic
// when current depth - base depth equals zero, we're in the first call to scan_url
tokio::task::spawn_blocking(move || PROGRESS_BAR.join().unwrap());
CALL_COUNT.fetch_add(1, Ordering::Relaxed);
// this protection around join also allows us to add the first scanned url to SCANNED_URLS
// from within the scan_url function instead of the recursion handler
add_url_to_list_of_scanned_urls(&target_url, &SCANNED_URLS);
}
let wildcard_bar = progress_bar.clone();
@@ -580,4 +637,39 @@ mod tests {
assert_eq!(urls, expected[i]);
}
}
#[test]
/// add an unknown url to the hashset, expect true
fn add_url_to_list_of_scanned_urls_with_unknown_url() {
let urls = RwLock::new(HashSet::<String>::new());
let url = "http://unknown_url";
assert_eq!(add_url_to_list_of_scanned_urls(url, &urls), true);
}
#[test]
/// add a known url to the hashset, with a trailing slash, expect false
fn add_url_to_list_of_scanned_urls_with_known_url() {
let urls = RwLock::new(HashSet::<String>::new());
let url = "http://unknown_url/";
assert_eq!(urls.write().unwrap().insert(url.to_string()), true);
assert_eq!(add_url_to_list_of_scanned_urls(url, &urls), false);
}
#[test]
/// add a known url to the hashset, without a trailing slash, expect false
fn add_url_to_list_of_scanned_urls_with_known_url_without_slash() {
let urls = RwLock::new(HashSet::<String>::new());
let url = "http://unknown_url";
assert_eq!(
urls.write()
.unwrap()
.insert("http://unknown_url/".to_string()),
true
);
assert_eq!(add_url_to_list_of_scanned_urls(url, &urls), false);
}
}

View File

@@ -223,7 +223,12 @@ pub async fn make_request(client: &Client, url: &Url) -> FeroxResult<Response> {
}
Err(e) => {
log::trace!("exit: make_request -> {}", e);
log::error!("Error while making request: {}", e);
if e.to_string().contains("operation timed out") {
// only warn for timeouts, while actual errors are still left as errors
log::warn!("Error while making request: {}", e);
} else {
log::error!("Error while making request: {}", e);
}
Err(Box::new(e))
}
}