ruby-feroxbuster 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.document +3 -0
- data/.github/workflows/ruby.yml +27 -0
- data/.gitignore +12 -0
- data/.rspec +1 -0
- data/.yardopts +1 -0
- data/ChangeLog.md +10 -0
- data/Gemfile +17 -0
- data/LICENSE.txt +20 -0
- data/README.md +87 -0
- data/Rakefile +23 -0
- data/gemspec.yml +28 -0
- data/lib/feroxbuster/command.rb +134 -0
- data/lib/feroxbuster/output_file.rb +124 -0
- data/lib/feroxbuster/parsers/json.rb +121 -0
- data/lib/feroxbuster/parsers/txt.rb +48 -0
- data/lib/feroxbuster/response.rb +117 -0
- data/lib/feroxbuster/statistics.rb +238 -0
- data/lib/feroxbuster/version.rb +4 -0
- data/ruby-feroxbuster.gemspec +61 -0
- data/spec/command_spec.rb +66 -0
- data/spec/fixtures/output.json +5 -0
- data/spec/fixtures/output.txt +4 -0
- data/spec/output_file_spec.rb +116 -0
- data/spec/parsers/json_spec.rb +92 -0
- data/spec/parsers/txt_spec.rb +88 -0
- data/spec/spec_helper.rb +3 -0
- metadata +112 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 93982d828d9029176f21dc60dc00bcc6d58ee980a4c52d37afa18cea4fd2fe3e
|
4
|
+
data.tar.gz: b1225c989137297136368816c9547dca26a38412e166d7f675794ccf91475c3a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d7d98099731b1aa773f378f676bd24a5b09618d6cdd30cbbf324ea9641d04b866e418ab3d1c1daed00208f24341e15b3e3ffb204c63b820955cb2077c877085d
|
7
|
+
data.tar.gz: febaa1f1c1011743478b626e4395a3f6a212185f4e6712ab053f06834a8b86eb04a316d1832393d162088779c4bd84614e19fb07c2a4783b62c22a8c73deffb2
|
data/.document
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
name: CI
|
2
|
+
|
3
|
+
on: [ push, pull_request ]
|
4
|
+
|
5
|
+
jobs:
|
6
|
+
tests:
|
7
|
+
runs-on: ubuntu-latest
|
8
|
+
strategy:
|
9
|
+
fail-fast: false
|
10
|
+
matrix:
|
11
|
+
ruby:
|
12
|
+
- 2.6
|
13
|
+
- 2.7
|
14
|
+
- 3.0
|
15
|
+
- jruby
|
16
|
+
- truffleruby
|
17
|
+
name: Ruby ${{ matrix.ruby }}
|
18
|
+
steps:
|
19
|
+
- uses: actions/checkout@v2
|
20
|
+
- name: Set up Ruby
|
21
|
+
uses: ruby/setup-ruby@v1
|
22
|
+
with:
|
23
|
+
ruby-version: ${{ matrix.ruby }}
|
24
|
+
- name: Install dependencies
|
25
|
+
run: bundle install --jobs 4 --retry 3
|
26
|
+
- name: Run tests
|
27
|
+
run: bundle exec rake test
|
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--colour --format documentation
|
data/.yardopts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--markup markdown --title 'Ruby Feroxbuster Documentation' --protected
|
data/ChangeLog.md
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
### 0.1.0 / 2021-04-22
|
2
|
+
|
3
|
+
* Initial release:
|
4
|
+
* Added {Feroxbuster::Command}.
|
5
|
+
* Added {Feroxbuster::OutputFile}.
|
6
|
+
* Added {Feroxbuster::Parsers::JSON}.
|
7
|
+
* Added {Feroxbuster::Parsers::TXT}.
|
8
|
+
* Added {Feroxbuster::Response}.
|
9
|
+
* Added {Feroxbuster::Statistics}.
|
10
|
+
|
data/Gemfile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
gemspec
|
4
|
+
|
5
|
+
# gem 'command_mapper', '~> 0.2', github: 'postmodern/command_mapper.rb'
|
6
|
+
|
7
|
+
group :development do
|
8
|
+
gem 'rake'
|
9
|
+
gem 'rubygems-tasks', '~> 0.2'
|
10
|
+
gem 'rspec', '~> 3.0'
|
11
|
+
gem 'simplecov', '~> 0.7'
|
12
|
+
|
13
|
+
gem 'kramdown'
|
14
|
+
gem 'redcarpet', platform: :mri
|
15
|
+
gem 'yard', '~> 0.9'
|
16
|
+
gem 'yard-spellcheck', require: false
|
17
|
+
end
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2022 Hal Brodigan
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
'Software'), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
17
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
18
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
19
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
20
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
# ruby-feroxbuster
|
2
|
+
|
3
|
+
[![CI](https://github.com/postmodern/ruby-feroxbuster/actions/workflows/ruby.yml/badge.svg)](https://github.com/postmodern/ruby-gobuster/actions/workflows/ruby.yml)
|
4
|
+
[![Gem Version](https://badge.fury.io/rb/ruby-feroxbuster.svg)](https://badge.fury.io/rb/ruby-gobuster)
|
5
|
+
|
6
|
+
* [Source](https://github.com/postmodern/ruby-feroxbuster/)
|
7
|
+
* [Issues](https://github.com/postmodern/ruby-feroxbuster/issues)
|
8
|
+
* [Documentation](http://rubydoc.info/gems/ruby-feroxbuster/frames)
|
9
|
+
|
10
|
+
## Description
|
11
|
+
|
12
|
+
A Ruby interface to [feroxbuster], a simple, fast, recursive content discovery
|
13
|
+
tool written in Rust.
|
14
|
+
|
15
|
+
## Features
|
16
|
+
|
17
|
+
* Provides a [Ruby interface][Feroxbuster::Command] for running the
|
18
|
+
`feroxbuster` command.
|
19
|
+
* Supports [parsing][Feroxbuster::OutputFile] `feroxbuster` `.txt` and `.json`
|
20
|
+
output files.
|
21
|
+
|
22
|
+
[Feroxbuster::Command]: https://rubydoc.info/gems/ruby-feroxbuster/Feroxbuster/Command
|
23
|
+
[Feroxbuster::OutputFile]: https://rubydoc.info/gems/ruby-amass/Feroxbuster/OutputFile
|
24
|
+
|
25
|
+
## Examples
|
26
|
+
|
27
|
+
Run `feroxbuster --wordlist /path/to/wordlist.txt --url https://example.com` from Ruby:
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
require 'feroxbuster/command'
|
31
|
+
|
32
|
+
Feroxbuster::Command.run(wordlist: '/path/to/wordlist.txt', url: 'https://example.com')
|
33
|
+
```
|
34
|
+
|
35
|
+
Parser an `feroxbuster` JSON file:
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
require 'feroxbuster/output_file'
|
39
|
+
|
40
|
+
output_file = Feroxbuster::OutputFile.new('/path/to/feroxbuster.json')
|
41
|
+
output_file.each do |object|
|
42
|
+
p object
|
43
|
+
end
|
44
|
+
```
|
45
|
+
|
46
|
+
```
|
47
|
+
#<Feroxbuster::Response:0x000056472fab3390 @url="https://github.com/test", @original_url="https://github.com", @path="/test", @wildcard=false, @status=200, @method="GET", @content_length=0, @line_count=2010, @word_count=11253, @headers={"permissions-policy"=>"interest-cohort=()", "accept-ranges"=>"bytes", "referrer-policy"=>"origin-when-cross-origin, strict-origin-when-cross-origin", "x-content-type-options"=>"nosniff", "cache-control"=>"max-age=0, private, must-revalidate", "expect-ct"=>"max-age=2592000, report-uri=\"https://api.github.com/_private/browser/errors\"", "server"=>"GitHub.com", "x-github-request-id"=>"85A8:0E10:20B03E:27719A:6260B1D5", "transfer-encoding"=>"chunked", "date"=>"Thu, 21 Apr 2022 01:22:16 GMT", "strict-transport-security"=>"max-age=31536000; includeSubdomains; preload", "etag"=>"W/\"7c98cb0440eb94eddcfd360497fae419\"", "x-frame-options"=>"deny", "content-security-policy"=>"default-src 'none'; base-uri 'self'; block-all-mixed-content; child-src github.com/assets-cdn/worker/ gist.github.com/assets-cdn/worker/; connect-src 'self' uploads.github.com objects-origin.githubusercontent.com www.githubstatus.com collector.github.com raw.githubusercontent.com api.github.com github-cloud.s3.amazonaws.com github-production-repository-file-5c1aeb.s3.amazonaws.com github-production-upload-manifest-file-7fdce7.s3.amazonaws.com github-production-user-asset-6210df.s3.amazonaws.com cdn.optimizely.com logx.optimizely.com/v1/events translator.github.com *.actions.githubusercontent.com wss://*.actions.githubusercontent.com online.visualstudio.com/api/v1/locations github-production-repository-image-32fea6.s3.amazonaws.com github-production-release-asset-2e65be.s3.amazonaws.com insights.github.com wss://alive.github.com; font-src github.githubassets.com; form-action 'self' github.com gist.github.com objects-origin.githubusercontent.com; frame-ancestors 'none'; frame-src render.githubusercontent.com viewscreen.githubusercontent.com notebooks.githubusercontent.com; img-src 'self' data: github.githubassets.com identicons.github.com github-cloud.s3.amazonaws.com secured-user-images.githubusercontent.com/ *.githubusercontent.com; manifest-src 'self'; media-src github.com user-images.githubusercontent.com/; script-src github.githubassets.com; style-src 'unsafe-inline' github.githubassets.com; worker-src github.com/assets-cdn/worker/ gist.github.com/assets-cdn/worker/", "x-xss-protection"=>"0", "set-cookie"=>"logged_in=no; Path=/; Domain=github.com; Expires=Fri, 21 Apr 2023 01:22:29 GMT; HttpOnly; Secure; SameSite=Lax", "vary"=>"X-Requested-With, X-PJAX-Container, Accept-Encoding, Accept, X-Requested-With", "content-type"=>"text/html; charset=utf-8"}, @extension="">
|
48
|
+
#<Feroxbuster::Response:0x000056472fab20f8 @url="https://github.com/dev", @original_url="https://github.com", @path="/dev", @wildcard=false, @status=200, @method="GET", @content_length=0, @line_count=2067, @word_count=11308, @headers={"date"=>"Thu, 21 Apr 2022 01:22:17 GMT", "cache-control"=>"max-age=0, private, must-revalidate", "content-security-policy"=>"default-src 'none'; base-uri 'self'; block-all-mixed-content; child-src github.com/assets-cdn/worker/ gist.github.com/assets-cdn/worker/; connect-src 'self' uploads.github.com objects-origin.githubusercontent.com www.githubstatus.com collector.github.com raw.githubusercontent.com api.github.com github-cloud.s3.amazonaws.com github-production-repository-file-5c1aeb.s3.amazonaws.com github-production-upload-manifest-file-7fdce7.s3.amazonaws.com github-production-user-asset-6210df.s3.amazonaws.com cdn.optimizely.com logx.optimizely.com/v1/events translator.github.com *.actions.githubusercontent.com wss://*.actions.githubusercontent.com online.visualstudio.com/api/v1/locations github-production-repository-image-32fea6.s3.amazonaws.com github-production-release-asset-2e65be.s3.amazonaws.com insights.github.com wss://alive.github.com; font-src github.githubassets.com; form-action 'self' github.com gist.github.com objects-origin.githubusercontent.com; frame-ancestors 'none'; frame-src render.githubusercontent.com viewscreen.githubusercontent.com notebooks.githubusercontent.com; img-src 'self' data: github.githubassets.com identicons.github.com github-cloud.s3.amazonaws.com secured-user-images.githubusercontent.com/ *.githubusercontent.com; manifest-src 'self'; media-src github.com user-images.githubusercontent.com/; script-src github.githubassets.com; style-src 'unsafe-inline' github.githubassets.com; worker-src github.com/assets-cdn/worker/ gist.github.com/assets-cdn/worker/", "set-cookie"=>"logged_in=no; Path=/; Domain=github.com; Expires=Fri, 21 Apr 2023 01:22:29 GMT; HttpOnly; Secure; SameSite=Lax", "permissions-policy"=>"interest-cohort=()", "x-frame-options"=>"deny", "server"=>"GitHub.com", "accept-ranges"=>"bytes", "etag"=>"W/\"3ca853f280bb864544f7842cdaa124e5\"", "vary"=>"X-Requested-With, X-PJAX-Container, Accept-Encoding, Accept, X-Requested-With", "referrer-policy"=>"origin-when-cross-origin, strict-origin-when-cross-origin", "x-github-request-id"=>"85AC:2947:2319C4:29E4C8:6260B1D5", "transfer-encoding"=>"chunked", "expect-ct"=>"max-age=2592000, report-uri=\"https://api.github.com/_private/browser/errors\"", "x-content-type-options"=>"nosniff", "content-type"=>"text/html; charset=utf-8", "x-xss-protection"=>"0", "strict-transport-security"=>"max-age=31536000; includeSubdomains; preload"}, @extension="">
|
49
|
+
#<Feroxbuster::Response:0x000056472fab0f28 @url="https://github.com/www", @original_url="https://github.com", @path="/www", @wildcard=false, @status=200, @method="GET", @content_length=0, @line_count=1813, @word_count=10399, @headers={"content-type"=>"text/html; charset=utf-8", "accept-ranges"=>"bytes", "date"=>"Thu, 21 Apr 2022 01:22:16 GMT", "transfer-encoding"=>"chunked", "vary"=>"X-Requested-With, X-PJAX-Container, Accept-Encoding, Accept, X-Requested-With", "x-xss-protection"=>"0", "x-github-request-id"=>"85B4:4678:28A012:2F9830:6260B1D5", "content-security-policy"=>"default-src 'none'; base-uri 'self'; block-all-mixed-content; child-src github.com/assets-cdn/worker/ gist.github.com/assets-cdn/worker/; connect-src 'self' uploads.github.com objects-origin.githubusercontent.com www.githubstatus.com collector.github.com raw.githubusercontent.com api.github.com github-cloud.s3.amazonaws.com github-production-repository-file-5c1aeb.s3.amazonaws.com github-production-upload-manifest-file-7fdce7.s3.amazonaws.com github-production-user-asset-6210df.s3.amazonaws.com cdn.optimizely.com logx.optimizely.com/v1/events translator.github.com *.actions.githubusercontent.com wss://*.actions.githubusercontent.com online.visualstudio.com/api/v1/locations github-production-repository-image-32fea6.s3.amazonaws.com github-production-release-asset-2e65be.s3.amazonaws.com insights.github.com wss://alive.github.com; font-src github.githubassets.com; form-action 'self' github.com gist.github.com objects-origin.githubusercontent.com; frame-ancestors 'none'; frame-src render.githubusercontent.com viewscreen.githubusercontent.com notebooks.githubusercontent.com; img-src 'self' data: github.githubassets.com identicons.github.com github-cloud.s3.amazonaws.com secured-user-images.githubusercontent.com/ *.githubusercontent.com; manifest-src 'self'; media-src github.com user-images.githubusercontent.com/; script-src github.githubassets.com; style-src 'unsafe-inline' github.githubassets.com; worker-src github.com/assets-cdn/worker/ gist.github.com/assets-cdn/worker/", "referrer-policy"=>"origin-when-cross-origin, strict-origin-when-cross-origin", "etag"=>"W/\"e03d055bf6b701eb6ecf1fa7c1a137f0\"", "permissions-policy"=>"interest-cohort=()", "strict-transport-security"=>"max-age=31536000; includeSubdomains; preload", "x-frame-options"=>"deny", "set-cookie"=>"logged_in=no; Path=/; Domain=github.com; Expires=Fri, 21 Apr 2023 01:22:29 GMT; HttpOnly; Secure; SameSite=Lax", "expect-ct"=>"max-age=2592000, report-uri=\"https://api.github.com/_private/browser/errors\"", "server"=>"GitHub.com", "cache-control"=>"max-age=0, private, must-revalidate", "x-content-type-options"=>"nosniff"}, @extension="">
|
50
|
+
#<Feroxbuster::Response:0x000056472fabbcc0 @url="https://github.com/", @original_url="https://github.com", @path="/", @wildcard=false, @status=200, @method="GET", @content_length=0, @line_count=2596, @word_count=13002, @headers={"cache-control"=>"max-age=0, private, must-revalidate", "server"=>"GitHub.com", "set-cookie"=>"logged_in=no; Path=/; Domain=github.com; Expires=Fri, 21 Apr 2023 01:22:29 GMT; HttpOnly; Secure; SameSite=Lax", "permissions-policy"=>"interest-cohort=()", "vary"=>"X-PJAX, X-PJAX-Container, Accept-Language, Accept-Encoding, Accept, X-Requested-With", "content-security-policy"=>"default-src 'none'; base-uri 'self'; block-all-mixed-content; child-src github.com/assets-cdn/worker/ gist.github.com/assets-cdn/worker/; connect-src 'self' uploads.github.com objects-origin.githubusercontent.com www.githubstatus.com collector.github.com raw.githubusercontent.com api.github.com github-cloud.s3.amazonaws.com github-production-repository-file-5c1aeb.s3.amazonaws.com github-production-upload-manifest-file-7fdce7.s3.amazonaws.com github-production-user-asset-6210df.s3.amazonaws.com cdn.optimizely.com logx.optimizely.com/v1/events translator.github.com *.actions.githubusercontent.com wss://*.actions.githubusercontent.com online.visualstudio.com/api/v1/locations github-production-repository-image-32fea6.s3.amazonaws.com github-production-release-asset-2e65be.s3.amazonaws.com insights.github.com wss://alive.github.com github.githubassets.com; font-src github.githubassets.com; form-action 'self' github.com gist.github.com objects-origin.githubusercontent.com; frame-ancestors 'none'; frame-src render.githubusercontent.com viewscreen.githubusercontent.com notebooks.githubusercontent.com; img-src 'self' data: github.githubassets.com identicons.github.com github-cloud.s3.amazonaws.com secured-user-images.githubusercontent.com/ *.githubusercontent.com customer-stories-feed.github.com spotlights-feed.github.com; manifest-src 'self'; media-src github.com user-images.githubusercontent.com/ github.githubassets.com; script-src github.githubassets.com; style-src 'unsafe-inline' github.githubassets.com; worker-src github.com/assets-cdn/worker/ gist.github.com/assets-cdn/worker/", "x-xss-protection"=>"0", "expect-ct"=>"max-age=2592000, report-uri=\"https://api.github.com/_private/browser/errors\"", "x-github-request-id"=>"85B2:4670:5B88C:BE8AA:6260B1D5", "date"=>"Thu, 21 Apr 2022 01:22:22 GMT", "strict-transport-security"=>"max-age=31536000; includeSubdomains; preload", "transfer-encoding"=>"chunked", "x-content-type-options"=>"nosniff", "etag"=>"W/\"b1bb181559a6360603ad7f0ed22c198c\"", "accept-ranges"=>"bytes", "x-frame-options"=>"deny", "referrer-policy"=>"origin-when-cross-origin, strict-origin-when-cross-origin", "content-type"=>"text/html; charset=utf-8", "content-language"=>"en-US"}, @extension="">
|
51
|
+
#<Feroxbuster::Statistics:0x000056472fabacd0 @timeouts=0, @requests=18, @expected_per_scan=6, @total_expected=12, @errors=0, @successes=12, @redirects=0, @client_errors=6, @server_errors=0, @total_scans=2, @initial_targets=0, @links_extracted=0, @extensions_collected=0, @status_200s=12, @status_301s=0, @status_302s=0, @status_401s=0, @status_403s=0, @status_429s=0, @status_500s=0, @status_503s=0, @status_504s=0, @status_508s=0, @wildcards_filtered=0, @responses_filtered=0, @resources_discovered=4, @url_format_errors=0, @redirection_errors=0, @connection_errors=0, @request_errors=0, @directory_scan_times=[0.434531853, 0.434228035], @total_runtime=[1.6527268240000001]>
|
52
|
+
```
|
53
|
+
|
54
|
+
## Requirements
|
55
|
+
|
56
|
+
* [ruby] >= 2.0.0
|
57
|
+
* [feroxbuster] >= 2.7.0
|
58
|
+
* [command_mapper] ~> 0.2, >= 0.2.1
|
59
|
+
|
60
|
+
[ruby]: https://www.ruby-lang.org/
|
61
|
+
[command_mapper]: https://github.com/postmodern/command_mapper.rb#readme
|
62
|
+
|
63
|
+
## Install
|
64
|
+
|
65
|
+
```shell
|
66
|
+
$ gem install ruby-feroxbuster
|
67
|
+
```
|
68
|
+
|
69
|
+
### gemspec
|
70
|
+
|
71
|
+
```ruby
|
72
|
+
gemspec.add_dependency 'ruby-feroxbuster', '~> 0.1'
|
73
|
+
```
|
74
|
+
|
75
|
+
### Gemfile
|
76
|
+
|
77
|
+
```ruby
|
78
|
+
gem 'ruby-feroxbuster', '~> 0.1'
|
79
|
+
```
|
80
|
+
|
81
|
+
## License
|
82
|
+
|
83
|
+
Copyright (c) 2022 Hal Brodigan
|
84
|
+
|
85
|
+
See {file:LICENSE.txt} for license information.
|
86
|
+
|
87
|
+
[feroxbuster]: https://github.com/epi052/feroxbuster#readme
|
data/Rakefile
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
|
5
|
+
begin
|
6
|
+
require 'bundler/setup'
|
7
|
+
rescue LoadError => e
|
8
|
+
warn e.message
|
9
|
+
warn "Run `gem install bundler` to install Bundler"
|
10
|
+
exit -1
|
11
|
+
end
|
12
|
+
|
13
|
+
require 'rubygems/tasks'
|
14
|
+
Gem::Tasks.new
|
15
|
+
|
16
|
+
require 'rspec/core/rake_task'
|
17
|
+
RSpec::Core::RakeTask.new
|
18
|
+
task :test => :spec
|
19
|
+
task :default => :spec
|
20
|
+
|
21
|
+
require 'yard'
|
22
|
+
YARD::Rake::YardocTask.new
|
23
|
+
task :doc => :yard
|
data/gemspec.yml
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
name: ruby-feroxbuster
|
2
|
+
summary: A Ruby interface to feroxbuster.
|
3
|
+
description:
|
4
|
+
A Ruby interface to feroxbuster, a simple, fast, recursive content discovery
|
5
|
+
tool written in Rust.
|
6
|
+
|
7
|
+
license: MIT
|
8
|
+
authors: Postmodern
|
9
|
+
email: postmodern.mod3@gmail.com
|
10
|
+
homepage: https://github.com/postmodern/ruby-feroxbuster#readme
|
11
|
+
has_yard: true
|
12
|
+
|
13
|
+
metadata:
|
14
|
+
documentation_uri: https://rubydoc.info/gems/ruby-feroxbuster
|
15
|
+
source_code_uri: https://github.com/postmodern/ruby-feroxbuster
|
16
|
+
bug_tracker_uri: https://github.com/postmodern/ruby-feroxbuster/issues
|
17
|
+
changelog_uri: https://github.com/postmodern/ruby-feroxbuster/blob/master/ChangeLog.md
|
18
|
+
rubygems_mfa_required: 'true'
|
19
|
+
|
20
|
+
required_ruby_version: ">= 2.0.0"
|
21
|
+
|
22
|
+
requirements: feroxbuster >= 2.7.0
|
23
|
+
|
24
|
+
dependencies:
|
25
|
+
command_mapper: ~> 0.2, >= 0.2.1
|
26
|
+
|
27
|
+
development_dependencies:
|
28
|
+
bundler: ~> 2.0
|
@@ -0,0 +1,134 @@
|
|
1
|
+
require 'command_mapper/command'
|
2
|
+
|
3
|
+
module Feroxbuster
|
4
|
+
class Command < CommandMapper::Command
|
5
|
+
|
6
|
+
#
|
7
|
+
# Represents the type for the `--time-limit` option.
|
8
|
+
#
|
9
|
+
# @api private
|
10
|
+
#
|
11
|
+
class TimeSpec < CommandMapper::Types::Str
|
12
|
+
|
13
|
+
#
|
14
|
+
# Validates the given value.
|
15
|
+
#
|
16
|
+
# @param [Object] value
|
17
|
+
# The given value to validate.
|
18
|
+
#
|
19
|
+
# @return [true, (false, String)]
|
20
|
+
# Returns true if the value is considered valid, or false and a
|
21
|
+
# validation message if the value is not valid.
|
22
|
+
#
|
23
|
+
def validate(value)
|
24
|
+
valid, message = super(value)
|
25
|
+
|
26
|
+
unless valid
|
27
|
+
return [valid, message]
|
28
|
+
end
|
29
|
+
|
30
|
+
value = value.to_s
|
31
|
+
|
32
|
+
unless value =~ /\A\d+(?:m|s|ms|ns)\z/
|
33
|
+
return [false, "must be a number and end with 'm', 's', 'ms', or 'ns'"]
|
34
|
+
end
|
35
|
+
|
36
|
+
return true
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
#
|
42
|
+
# Represents an HTTP status code (100 - 599).
|
43
|
+
#
|
44
|
+
# @api private
|
45
|
+
#
|
46
|
+
class StatusCode < CommandMapper::Types::Num
|
47
|
+
|
48
|
+
def initialize
|
49
|
+
super(range: 100..599)
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
command 'feroxbuster' do
|
55
|
+
option '--help'
|
56
|
+
option '--version'
|
57
|
+
|
58
|
+
# Target selection:
|
59
|
+
option '--resume-from', value: {type: InputFile.new}
|
60
|
+
option '--stdin'
|
61
|
+
option '--url', value: true
|
62
|
+
|
63
|
+
# Composite settings:
|
64
|
+
option '--burp'
|
65
|
+
option '--burp-replay'
|
66
|
+
option '--smart'
|
67
|
+
option '--thorough'
|
68
|
+
|
69
|
+
# Proxy settings:
|
70
|
+
option '--proxy', value: true
|
71
|
+
option '--replay-proxy', value: true
|
72
|
+
option '--replay-codes', value: {type: StatusCode.new}, repeats: true
|
73
|
+
|
74
|
+
# Request settings:
|
75
|
+
option '--user-agent', value: true
|
76
|
+
option '--random-agent'
|
77
|
+
option '--cookies', value: true, repeats: true
|
78
|
+
option '--data', value: true
|
79
|
+
option '--add-slash'
|
80
|
+
option '--headers', value: true, repeats: true
|
81
|
+
option '--methods', name: :http_methods, value: true, repeats: true
|
82
|
+
option '--query', value: true, repeats: true
|
83
|
+
|
84
|
+
# Request filters:
|
85
|
+
option '--dont-scan', value: true, repeats: true
|
86
|
+
|
87
|
+
# Response filters:
|
88
|
+
option '--filter-status', value: {type: StatusCode.new}, repeats: true
|
89
|
+
option '--filter-similar-to', value: true, repeats: true
|
90
|
+
option '--filter-lines', value: true, repeats: true
|
91
|
+
option '--status-codes', value: {type: StatusCode.new},
|
92
|
+
repeats: true
|
93
|
+
option '--filter-size', value: {type: Num.new}, repeats: true
|
94
|
+
option '--filter-words', value: {type: Num.new}, repeats: true
|
95
|
+
option '--filter-regex', value: true, repeats: true
|
96
|
+
|
97
|
+
# Client settings:
|
98
|
+
option '--insecure'
|
99
|
+
option '--redirects'
|
100
|
+
option '--timeout', value: {type: Num.new}
|
101
|
+
|
102
|
+
# Scan settings:
|
103
|
+
option '--auto-bail'
|
104
|
+
option '--auto-tune'
|
105
|
+
option '--depth', value: {type: Num.new}
|
106
|
+
option '--dont-filter'
|
107
|
+
option '--extract-links'
|
108
|
+
option '--force-recursion'
|
109
|
+
option '--scan-list', value: {type: Num.new}
|
110
|
+
option '--no-recursion'
|
111
|
+
option '--parallel', value: {type: Num.new}
|
112
|
+
option '--rate-limit', value: {type: Num.new}
|
113
|
+
option '--threads', value: {type: Num.new}
|
114
|
+
option '--time-limit', value: {type: TimeSpec.new}
|
115
|
+
option '--wordlist', value: {type: InputFile.new}
|
116
|
+
|
117
|
+
# Dynamic collection settings:
|
118
|
+
option '--collect-backups'
|
119
|
+
option '--collect-extensions'
|
120
|
+
option '--collect-words'
|
121
|
+
option '--dont-collect', value: true, repeats: true
|
122
|
+
|
123
|
+
# Output settings:
|
124
|
+
option '--debug-log', value: true
|
125
|
+
option '--json'
|
126
|
+
option '--no-state'
|
127
|
+
option '--output', value: true
|
128
|
+
option '--quiet'
|
129
|
+
option '--silent'
|
130
|
+
option '--verbosity'
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
require 'feroxbuster/parsers/json'
|
2
|
+
require 'feroxbuster/parsers/txt'
|
3
|
+
|
4
|
+
module Feroxbuster
|
5
|
+
#
|
6
|
+
# Represents either a `.json` or `.txt` output file.
|
7
|
+
#
|
8
|
+
# ## Example
|
9
|
+
#
|
10
|
+
# require 'feroxbuster/output_file'
|
11
|
+
#
|
12
|
+
# output_file = Feroxbuster::OutputFile.new('/path/to/feroxbuster.json')
|
13
|
+
# output_file.each do |response|
|
14
|
+
# p response
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# @api public
|
18
|
+
#
|
19
|
+
class OutputFile
|
20
|
+
|
21
|
+
# Mapping of formats to parsers.
|
22
|
+
#
|
23
|
+
# @api semipublic
|
24
|
+
PARSERS = {
|
25
|
+
json: Parsers::JSON,
|
26
|
+
txt: Parsers::TXT
|
27
|
+
}
|
28
|
+
|
29
|
+
# The path to the output file.
|
30
|
+
#
|
31
|
+
# @return [String]
|
32
|
+
attr_reader :path
|
33
|
+
|
34
|
+
# The format of the output file.
|
35
|
+
#
|
36
|
+
# @return [:json, :txt]
|
37
|
+
attr_reader :format
|
38
|
+
|
39
|
+
# The parser for the output file format.
|
40
|
+
#
|
41
|
+
# @return [Parsers::JSON, Parsers::TXT]
|
42
|
+
#
|
43
|
+
# @api private
|
44
|
+
attr_reader :parser
|
45
|
+
|
46
|
+
#
|
47
|
+
# Initializes the output file.
|
48
|
+
#
|
49
|
+
# @param [String] path
|
50
|
+
# The path to the output file.
|
51
|
+
#
|
52
|
+
# @param [:json, :txt] format
|
53
|
+
# The optional format of the output file. If not given, it will be
|
54
|
+
# inferred by the path's file extension.
|
55
|
+
#
|
56
|
+
def initialize(path, format: self.class.infer_format(path))
|
57
|
+
@path = File.expand_path(path)
|
58
|
+
@format = format
|
59
|
+
|
60
|
+
@parser = PARSERS.fetch(format) do
|
61
|
+
raise(ArgumentError,"unrecognized file type: #{@path.inspect}")
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Mapping of file extensions to formats
|
66
|
+
#
|
67
|
+
# @api semipublic
|
68
|
+
FILE_FORMATS = {
|
69
|
+
'.json' => :json,
|
70
|
+
'.txt' => :txt
|
71
|
+
}
|
72
|
+
|
73
|
+
#
|
74
|
+
# Infers the format from the output file's extension name.
|
75
|
+
#
|
76
|
+
# @param [String] path
|
77
|
+
# The path to the output file.
|
78
|
+
#
|
79
|
+
# @return [:json, :txt]
|
80
|
+
# The output format inferred from the file's extension name.
|
81
|
+
#
|
82
|
+
# @raise [ArgumentError]
|
83
|
+
# The output format could not be inferred from the file's name.
|
84
|
+
#
|
85
|
+
# @api semipublic
|
86
|
+
#
|
87
|
+
def self.infer_format(path)
|
88
|
+
FILE_FORMATS.fetch(File.extname(path)) do
|
89
|
+
raise(ArgumentError,"could not infer format of #{path}")
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
#
|
94
|
+
# Parses the contents of the output file.
|
95
|
+
#
|
96
|
+
# @yield [hostname]
|
97
|
+
# The given block will be passed each parsed hostname.
|
98
|
+
#
|
99
|
+
# @yieldparam [Hostname] hostname
|
100
|
+
# A parsed hostname from the output file.
|
101
|
+
#
|
102
|
+
# @return [Enumerator]
|
103
|
+
# If no block is given, an Enumerator object will be returned.
|
104
|
+
#
|
105
|
+
def each(&block)
|
106
|
+
return enum_for(__method__) unless block
|
107
|
+
|
108
|
+
File.open(@path) do |file|
|
109
|
+
@parser.parse(file,&block)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
#
|
114
|
+
# Converts the output file to a String.
|
115
|
+
#
|
116
|
+
# @return [String]
|
117
|
+
# The path to the output file.
|
118
|
+
#
|
119
|
+
def to_s
|
120
|
+
@path
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
require 'feroxbuster/response'
|
2
|
+
require 'feroxbuster/statistics'
|
3
|
+
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
module Feroxbuster
|
7
|
+
module Parsers
|
8
|
+
#
|
9
|
+
# Parses single-line JSON hashes.
|
10
|
+
#
|
11
|
+
# @api semipublic
|
12
|
+
#
|
13
|
+
module JSON
|
14
|
+
#
|
15
|
+
# Parses a single-line of JSON.
|
16
|
+
#
|
17
|
+
# @param [IO] io
|
18
|
+
# The IO stream to parse.
|
19
|
+
#
|
20
|
+
# @yield [data]
|
21
|
+
# The given block will be passed each parsed data.
|
22
|
+
#
|
23
|
+
# @yieldparam [Response, Statistics] data
|
24
|
+
# The parsed data. {Statistics} will be yielded last.
|
25
|
+
#
|
26
|
+
# @return [Enumerator]
|
27
|
+
# If no block is given, an Enumerator will be returned.
|
28
|
+
#
|
29
|
+
def self.parse(io)
|
30
|
+
return enum_for(__method__,io) unless block_given?
|
31
|
+
|
32
|
+
io.each_line do |line|
|
33
|
+
line.chomp!
|
34
|
+
json = ::JSON.parse(line)
|
35
|
+
|
36
|
+
case json["type"]
|
37
|
+
when "response"
|
38
|
+
yield map_response(json)
|
39
|
+
when "statistics"
|
40
|
+
yield map_statistics(json)
|
41
|
+
else
|
42
|
+
raise(NotImplementedError,"unsupported JSON type: #{json['type']}")
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
#
|
50
|
+
# Maps a response type JSON Hash to a {Response} object.
|
51
|
+
#
|
52
|
+
# @param [Hash{Symbol => Object}] json
|
53
|
+
# The parsed JSON Hash.
|
54
|
+
#
|
55
|
+
# @return [Respnse]
|
56
|
+
# The resulting response object.
|
57
|
+
#
|
58
|
+
def self.map_response(json)
|
59
|
+
Response.new(
|
60
|
+
url: json['url'],
|
61
|
+
original_url: json['original_url'],
|
62
|
+
path: json['path'],
|
63
|
+
wildcard: json['wildcard'],
|
64
|
+
status: json['status'],
|
65
|
+
method: json['method'],
|
66
|
+
content_length: json['content_length'],
|
67
|
+
line_count: json['line_count'],
|
68
|
+
word_count: json['word_count'],
|
69
|
+
headers: json['headers'],
|
70
|
+
extension: json['extension']
|
71
|
+
)
|
72
|
+
end
|
73
|
+
|
74
|
+
#
|
75
|
+
# Maps a statistics type JSON Hash to a {Statistics} object.
|
76
|
+
#
|
77
|
+
# @param [Hash{Symbol => Object}] json
|
78
|
+
# The parsed JSON Hash.
|
79
|
+
#
|
80
|
+
# @return [Statistics]
|
81
|
+
# The resulting statistics object.
|
82
|
+
#
|
83
|
+
def self.map_statistics(json)
|
84
|
+
Statistics.new(
|
85
|
+
timeouts: json['timeouts'],
|
86
|
+
requests: json['requests'],
|
87
|
+
expected_per_scan: json['expected_per_scan'],
|
88
|
+
total_expected: json['total_expected'],
|
89
|
+
errors: json['errors'],
|
90
|
+
successes: json['successes'],
|
91
|
+
redirects: json['redirects'],
|
92
|
+
client_errors: json['client_errors'],
|
93
|
+
server_errors: json['server_errors'],
|
94
|
+
total_scans: json['total_scans'],
|
95
|
+
initial_targets: json['initial_targets'],
|
96
|
+
links_extracted: json['links_extracted'],
|
97
|
+
extensions_collected: json['extensions_collected'],
|
98
|
+
status_200s: json['status_200s'],
|
99
|
+
status_301s: json['status_301s'],
|
100
|
+
status_302s: json['status_302s'],
|
101
|
+
status_401s: json['status_401s'],
|
102
|
+
status_403s: json['status_403s'],
|
103
|
+
status_429s: json['status_429s'],
|
104
|
+
status_500s: json['status_500s'],
|
105
|
+
status_503s: json['status_503s'],
|
106
|
+
status_504s: json['status_504s'],
|
107
|
+
status_508s: json['status_508s'],
|
108
|
+
wildcards_filtered: json['wildcards_filtered'],
|
109
|
+
responses_filtered: json['responses_filtered'],
|
110
|
+
resources_discovered: json['resources_discovered'],
|
111
|
+
url_format_errors: json['url_format_errors'],
|
112
|
+
redirection_errors: json['redirection_errors'],
|
113
|
+
connection_errors: json['connection_errors'],
|
114
|
+
request_errors: json['request_errors'],
|
115
|
+
directory_scan_times: json['directory_scan_times'],
|
116
|
+
total_runtime: json['total_runtime']
|
117
|
+
)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|