aquatone 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +15 -1
- data/exe/aquatone-discover +8 -0
- data/lib/aquatone/collector.rb +27 -4
- data/lib/aquatone/collectors/censys.rb +17 -8
- data/lib/aquatone/collectors/dictionary.rb +15 -3
- data/lib/aquatone/collectors/gtr.rb +14 -4
- data/lib/aquatone/collectors/netcraft.rb +16 -6
- data/lib/aquatone/collectors/shodan.rb +15 -5
- data/lib/aquatone/commands/discover.rb +1 -1
- data/lib/aquatone/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d8ad3da3d450ecd7fd41c64cd0b18f0a3e3ae00b
|
4
|
+
data.tar.gz: 240fbce3dc353f146a32be28566201b2b62fd2c6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 244cc756b2cb1c65d36281fbeae199d290a71434f73a12c96e855995eff2a091aae442212d13ee604ac6b34d6d74196813ff3b01bd5b809b16512c86b438c44e
|
7
|
+
data.tar.gz: b109618403ee14536a67a8e75334e88279578f2eb9532edd8ea772c7bf1e443a8747a01038eadfc44bd5a42000ab90571be269432340eef1257161f6d3d40110
|
data/CHANGELOG.md
CHANGED
@@ -10,6 +10,19 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
|
10
10
|
### Changed
|
11
11
|
|
12
12
|
|
13
|
+
## [0.4.0]
|
14
|
+
### Added
|
15
|
+
- Collector module defined CLI options: Collectors can now define their own CLI options for `aquatone-discover`,
|
16
|
+
e.g. `--wordlist` to make the Dictionary collector use a custom wordlist instead of the built-in one.
|
17
|
+
See `aquatone-discover --help` for all new options.
|
18
|
+
|
19
|
+
### Changed
|
20
|
+
|
21
|
+
### Fixed
|
22
|
+
- Performance improvement in the way collector modules check for duplicate hosts (was only an issue with
|
23
|
+
very large results or dictionaries)
|
24
|
+
|
25
|
+
|
13
26
|
## [0.3.0]
|
14
27
|
### Added
|
15
28
|
- New Tool: aquatone-takeover: Check discovered hosts for subdomain takeover vulnerabilities
|
@@ -47,7 +60,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
|
47
60
|
|
48
61
|
### Changed
|
49
62
|
|
50
|
-
[Unreleased]: https://github.com/michenriksen/aquatone/compare/v0.
|
63
|
+
[Unreleased]: https://github.com/michenriksen/aquatone/compare/v0.4.0...HEAD
|
64
|
+
[0.4.0]: https://github.com/michenriksen/aquatone/compare/v0.3.0...v0.4.0
|
51
65
|
[0.3.0]: https://github.com/michenriksen/aquatone/compare/v0.2.0...v0.3.0
|
52
66
|
[0.2.0]: https://github.com/michenriksen/aquatone/compare/v0.1.1...v0.2.0
|
53
67
|
[0.1.1]: https://github.com/michenriksen/aquatone/compare/v0.1.0...v0.1.1
|
data/exe/aquatone-discover
CHANGED
@@ -121,6 +121,14 @@ OptionParser.new do |opts|
|
|
121
121
|
options[:jitter] = v.to_f
|
122
122
|
end
|
123
123
|
|
124
|
+
Aquatone::Collector.descendants.each do |collector|
|
125
|
+
collector.cli_options.each_pair do |option, description|
|
126
|
+
opts.on("--#{option}", description) do |v|
|
127
|
+
options[option.split(" ").first.gsub("-", "_").to_sym] = v
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
124
132
|
opts.on("-h", "--help", "Show help") do
|
125
133
|
puts opts
|
126
134
|
exit 0
|
data/lib/aquatone/collector.rb
CHANGED
@@ -23,15 +23,21 @@ module Aquatone
|
|
23
23
|
collectors.sort { |x, y| x.priority <=> y.priority }
|
24
24
|
end
|
25
25
|
|
26
|
+
def self.cli_options
|
27
|
+
meta.key?(:cli_options) ? meta[:cli_options] : {}
|
28
|
+
end
|
29
|
+
|
26
30
|
def self.sluggified_name
|
27
31
|
return meta[:slug].downcase if meta[:slug]
|
28
32
|
meta[:name].strip.downcase.gsub(/[^a-z0-9]+/, '-').gsub("--", "-")
|
29
33
|
end
|
30
34
|
|
31
|
-
def initialize(domain)
|
35
|
+
def initialize(domain, options)
|
32
36
|
check_key_requirements!
|
33
|
-
@domain
|
34
|
-
@
|
37
|
+
@domain = domain
|
38
|
+
@options = options
|
39
|
+
@hosts = []
|
40
|
+
@host_dictionary = {}
|
35
41
|
end
|
36
42
|
|
37
43
|
def run
|
@@ -52,7 +58,9 @@ module Aquatone
|
|
52
58
|
def add_host(host)
|
53
59
|
host.downcase!
|
54
60
|
return unless Aquatone::Validation.valid_domain_name?(host)
|
55
|
-
|
61
|
+
return if @host_dictionary.key?(host)
|
62
|
+
@host_dictionary[host] = true
|
63
|
+
@hosts << host
|
56
64
|
end
|
57
65
|
|
58
66
|
def get_request(uri, options={})
|
@@ -83,6 +91,14 @@ module Aquatone
|
|
83
91
|
Aquatone::KeyStore.key?(name)
|
84
92
|
end
|
85
93
|
|
94
|
+
def get_cli_option(name)
|
95
|
+
@options[name.to_s.gsub("-", "_").to_sym]
|
96
|
+
end
|
97
|
+
|
98
|
+
def has_cli_option?(name)
|
99
|
+
@options.key?(name.to_s.gsub("-", "_").to_sym)
|
100
|
+
end
|
101
|
+
|
86
102
|
def failure(message)
|
87
103
|
fail Error, message
|
88
104
|
end
|
@@ -101,6 +117,13 @@ module Aquatone
|
|
101
117
|
fail InvalidMetadataError, "Metadata is missing key: name" unless meta.key?(:name)
|
102
118
|
fail InvalidMetadataError, "Metadata is missing key: author" unless meta.key?(:author)
|
103
119
|
fail InvalidMetadataError, "Metadata is missing key: description" unless meta.key?(:description)
|
120
|
+
if meta.key?(:cli_options)
|
121
|
+
fail InvalidMetadataError, "Metadata CLI options is not a hash" unless meta[:cli_options].is_a?(Hash)
|
122
|
+
meta[:cli_options].each_pair do |option, description|
|
123
|
+
fail InvalidMetadataError, "CLI option name is not a string" unless option.is_a?(String)
|
124
|
+
fail InvalidMetadataError, "CLI option details is not a string" unless description.is_a?(String)
|
125
|
+
end
|
126
|
+
end
|
104
127
|
end
|
105
128
|
end
|
106
129
|
end
|
@@ -6,11 +6,14 @@ module Aquatone
|
|
6
6
|
:author => "James McLean (@vortexau)",
|
7
7
|
:description => "Uses the Censys API to find hostnames in TLS certificates",
|
8
8
|
:require_keys => ["censys_secret","censys_id"],
|
9
|
+
:cli_options => {
|
10
|
+
"censys-pages PAGES" => "Number of Censys API pages to process (default: 10)"
|
11
|
+
}
|
9
12
|
}
|
10
13
|
|
11
|
-
API_BASE_URI
|
12
|
-
API_RESULTS_PER_PAGE
|
13
|
-
|
14
|
+
API_BASE_URI = "https://www.censys.io/api/v1".freeze
|
15
|
+
API_RESULTS_PER_PAGE = 100.freeze
|
16
|
+
DEFAULT_PAGES_TO_PROCESS = 10.freeze
|
14
17
|
|
15
18
|
def run
|
16
19
|
request_censys_page
|
@@ -21,10 +24,10 @@ module Aquatone
|
|
21
24
|
|
22
25
|
# Censys expects Basic Auth for requests.
|
23
26
|
auth = {
|
24
|
-
:username => get_key('censys_id'),
|
27
|
+
:username => get_key('censys_id'),
|
25
28
|
:password => get_key('censys_secret')
|
26
29
|
}
|
27
|
-
|
30
|
+
|
28
31
|
# Define this is JSON content
|
29
32
|
headers = {
|
30
33
|
'Content-Type' => 'application/json',
|
@@ -41,11 +44,11 @@ module Aquatone
|
|
41
44
|
|
42
45
|
# Search API documented at https://censys.io/api/v1/docs/search
|
43
46
|
response = post_request(
|
44
|
-
"#{API_BASE_URI}/search/certificates",
|
47
|
+
"#{API_BASE_URI}/search/certificates",
|
45
48
|
query.to_json,
|
46
49
|
{
|
47
50
|
:basic_auth => auth,
|
48
|
-
:headers => headers
|
51
|
+
:headers => headers
|
49
52
|
}
|
50
53
|
)
|
51
54
|
|
@@ -75,9 +78,15 @@ module Aquatone
|
|
75
78
|
end
|
76
79
|
|
77
80
|
def next_page?(page, body)
|
78
|
-
page <=
|
81
|
+
page <= pages_to_process && body["metadata"]["pages"] && API_RESULTS_PER_PAGE * page < body["metadata"]["count"].to_i
|
79
82
|
end
|
80
83
|
|
84
|
+
def pages_to_process
|
85
|
+
if has_cli_option?("censys-pages")
|
86
|
+
return get_cli_option("censys-pages").to_i
|
87
|
+
end
|
88
|
+
DEFAULT_PAGES_TO_PROCESS
|
89
|
+
end
|
81
90
|
end
|
82
91
|
end
|
83
92
|
end
|
@@ -4,13 +4,25 @@ module Aquatone
|
|
4
4
|
self.meta = {
|
5
5
|
:name => "Dictionary",
|
6
6
|
:author => "Michael Henriksen (@michenriksen)",
|
7
|
-
:description => "Uses a dictionary to find hostnames"
|
7
|
+
:description => "Uses a dictionary to find hostnames",
|
8
|
+
:cli_options => {
|
9
|
+
"wordlist WORDLIST" => "OPTIONAL: wordlist/dictionary file to use for subdomain bruteforcing"
|
10
|
+
}
|
8
11
|
}
|
9
12
|
|
10
|
-
|
13
|
+
DEFAULT_DICTIONARY = File.join(Aquatone::AQUATONE_ROOT, "subdomains.lst").freeze
|
11
14
|
|
12
15
|
def run
|
13
|
-
|
16
|
+
if has_cli_option?("wordlist")
|
17
|
+
file = File.expand_path(get_cli_option("wordlist"))
|
18
|
+
if !File.readable?(file)
|
19
|
+
failure("Wordlist file #{file} is not readable or does not exist")
|
20
|
+
end
|
21
|
+
dictionary = File.open(file, "r")
|
22
|
+
else
|
23
|
+
dictionary = File.open(DEFAULT_DICTIONARY, "r")
|
24
|
+
end
|
25
|
+
|
14
26
|
dictionary.each_line do |subdomain|
|
15
27
|
add_host("#{subdomain.strip}.#{domain.name}")
|
16
28
|
end
|
@@ -5,15 +5,18 @@ module Aquatone
|
|
5
5
|
:name => "Google Transparency Report",
|
6
6
|
:author => "Michael Henriksen (@michenriksen)",
|
7
7
|
:description => "Uses Google Transparency Report to find hostnames",
|
8
|
-
:slug => "gtr"
|
8
|
+
:slug => "gtr",
|
9
|
+
:cli_options => {
|
10
|
+
"gtr-pages PAGES" => "Number of Google Transparency Report pages to process (default: 30)"
|
11
|
+
}
|
9
12
|
}
|
10
13
|
|
11
|
-
BASE_URI
|
12
|
-
|
14
|
+
BASE_URI = "https://www.google.com/transparencyreport/jsonp/ct/search"
|
15
|
+
DEFAULT_PAGES_TO_PROCESS = 30.freeze
|
13
16
|
|
14
17
|
def run
|
15
18
|
token = nil
|
16
|
-
|
19
|
+
pages_to_process.times do
|
17
20
|
response = parse_response(request_page(token))
|
18
21
|
response["results"].each do |result|
|
19
22
|
host = result["subject"]
|
@@ -53,6 +56,13 @@ module Aquatone
|
|
53
56
|
return false unless host.end_with?(".#{domain.name}")
|
54
57
|
true
|
55
58
|
end
|
59
|
+
|
60
|
+
def pages_to_process
|
61
|
+
if has_cli_option?("gtr-pages")
|
62
|
+
return get_cli_option("gtr-pages").to_i
|
63
|
+
end
|
64
|
+
DEFAULT_PAGES_TO_PROCESS
|
65
|
+
end
|
56
66
|
end
|
57
67
|
end
|
58
68
|
end
|
@@ -4,18 +4,21 @@ module Aquatone
|
|
4
4
|
self.meta = {
|
5
5
|
:name => "Netcraft",
|
6
6
|
:author => "Michael Henriksen (@michenriksen)",
|
7
|
-
:description => "Uses searchdns.netcraft.com to find hostnames"
|
7
|
+
:description => "Uses searchdns.netcraft.com to find hostnames",
|
8
|
+
:cli_options => {
|
9
|
+
"netcraft-pages PAGES" => "Number of Netcraft pages to process (default: 10)"
|
10
|
+
}
|
8
11
|
}
|
9
12
|
|
10
|
-
BASE_URI
|
11
|
-
HOSTNAME_REGEX
|
12
|
-
RESULTS_PER_PAGE
|
13
|
-
|
13
|
+
BASE_URI = "http://searchdns.netcraft.com/".freeze
|
14
|
+
HOSTNAME_REGEX = /<a href="http:\/\/(.*?)\/" rel="nofollow">/.freeze
|
15
|
+
RESULTS_PER_PAGE = 20.freeze
|
16
|
+
DEFAULT_PAGES_TO_PROCESS = 10.freeze
|
14
17
|
|
15
18
|
def run
|
16
19
|
last = nil
|
17
20
|
count = 0
|
18
|
-
|
21
|
+
pages_to_process.times do |i|
|
19
22
|
page = i + 1
|
20
23
|
if page == 1
|
21
24
|
uri = "#{BASE_URI}/?restriction=site+contains&host=*.#{url_escape(domain.name)}&lookup=wait..&position=limited"
|
@@ -43,6 +46,13 @@ module Aquatone
|
|
43
46
|
end
|
44
47
|
hosts
|
45
48
|
end
|
49
|
+
|
50
|
+
def pages_to_process
|
51
|
+
if has_cli_option?("netcraft-pages")
|
52
|
+
return get_cli_option("netcraft-pages").to_i
|
53
|
+
end
|
54
|
+
DEFAULT_PAGES_TO_PROCESS
|
55
|
+
end
|
46
56
|
end
|
47
57
|
end
|
48
58
|
end
|
@@ -5,12 +5,15 @@ module Aquatone
|
|
5
5
|
:name => "Shodan",
|
6
6
|
:author => "Michael Henriksen (@michenriksen)",
|
7
7
|
:description => "Uses the Shodan API to find hostnames",
|
8
|
-
:require_keys => ["shodan"]
|
8
|
+
:require_keys => ["shodan"],
|
9
|
+
:cli_options => {
|
10
|
+
"shodan-pages PAGES" => "Number of Shodan API pages to process (default: 10)"
|
11
|
+
}
|
9
12
|
}
|
10
13
|
|
11
|
-
API_BASE_URI
|
12
|
-
API_RESULTS_PER_PAGE
|
13
|
-
|
14
|
+
API_BASE_URI = "https://api.shodan.io/shodan".freeze
|
15
|
+
API_RESULTS_PER_PAGE = 100.freeze
|
16
|
+
DEFAULT_PAGES_TO_PROCESS = 10.freeze
|
14
17
|
|
15
18
|
def run
|
16
19
|
request_shodan_page
|
@@ -38,7 +41,14 @@ module Aquatone
|
|
38
41
|
end
|
39
42
|
|
40
43
|
def next_page?(page, body)
|
41
|
-
page <=
|
44
|
+
page <= pages_to_process && body["total"] && API_RESULTS_PER_PAGE * page < body["total"].to_i
|
45
|
+
end
|
46
|
+
|
47
|
+
def pages_to_process
|
48
|
+
if has_cli_option?("shodan-pages")
|
49
|
+
return get_cli_option("shodan-pages").to_i
|
50
|
+
end
|
51
|
+
DEFAULT_PAGES_TO_PROCESS
|
42
52
|
end
|
43
53
|
end
|
44
54
|
end
|
@@ -77,7 +77,7 @@ module Aquatone
|
|
77
77
|
next if skip_collector?(collector)
|
78
78
|
output("Running collector: #{bold(collector.meta[:name])}... ")
|
79
79
|
begin
|
80
|
-
collector_instance = collector.new(@domain)
|
80
|
+
collector_instance = collector.new(@domain, options)
|
81
81
|
hosts = collector_instance.execute!
|
82
82
|
output("Done (#{hosts.count} #{hosts.count == 1 ? 'host' : 'hosts'})\n")
|
83
83
|
@hosts += hosts
|
data/lib/aquatone/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: aquatone
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Michael Henriksen
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-08-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: httparty
|