ruby-feroxbuster 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
[](https://github.com/postmodern/ruby-gobuster/actions/workflows/ruby.yml)
|
4
|
+
[](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
|