aquatone 0.1.1 → 0.2.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 +4 -4
- data/CHANGELOG.md +19 -2
- data/README.md +3 -0
- data/lib/aquatone/browser/drivers/nightmare.rb +5 -0
- data/lib/aquatone/collector.rb +1 -1
- data/lib/aquatone/collectors/censys.rb +83 -0
- data/lib/aquatone/collectors/crtsh.rb +19 -0
- data/lib/aquatone/collectors/passivetotal.rb +31 -0
- data/lib/aquatone/collectors/riddler.rb +43 -0
- data/lib/aquatone/command.rb +10 -6
- data/lib/aquatone/commands/discover.rb +9 -2
- data/lib/aquatone/commands/gather.rb +16 -10
- data/lib/aquatone/version.rb +1 -1
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1572ba4c07838fd7294dfc001c8ff23b2f7c19fc
|
4
|
+
data.tar.gz: 2c7fc94f9cef66e71fc8adeed1840a3170cf6367
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d5086248f07db25eeead317a570ee56cf3d622eb72e471d85c1c33611f3cfc4bba0be721a9f65658e6e078b264bbbb2327157574070e235d1542c4a85ba4ec6e
|
7
|
+
data.tar.gz: f81b9ab7409889e524c59fda6e53e7ce4e891a515f7492885f5fcd5e558ae8bdb187c2e6295ad5ee40324f51b12994dfbc39759a272a03fbcbd08b75e5f90fc6
|
data/CHANGELOG.md
CHANGED
@@ -9,6 +9,22 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
|
9
9
|
|
10
10
|
### Changed
|
11
11
|
|
12
|
+
|
13
|
+
## [0.2.0]
|
14
|
+
### Added
|
15
|
+
- New Collector: riddler.io (Thanks, [@jolle](https://github.com/jolle)!)
|
16
|
+
- New Collector: crt.sh (Thanks, [@jolle](https://github.com/jolle)!)
|
17
|
+
- New Collector: censys.io (Thanks, [@vortexau](https://github.com/vortexau)!)
|
18
|
+
- New Collector: passivetotal.org
|
19
|
+
|
20
|
+
### Changed
|
21
|
+
- Capture potential `NameError` exception in `asked_for_progress?` method,
|
22
|
+
related to known JRuby bug (issue #4)
|
23
|
+
- Capture potential `Errno::EBADF` exception in `asked_for_progress?` method (issue #15)
|
24
|
+
- Improve handling of error when aquatone-gather is run on a system without a graphical desktop session (X11)
|
25
|
+
- Exclude hosts resolving to broadcast addresses in aquatone-discover (issue #11)
|
26
|
+
|
27
|
+
|
12
28
|
## [0.1.1]
|
13
29
|
### Added
|
14
30
|
|
@@ -22,5 +38,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
|
22
38
|
|
23
39
|
### Changed
|
24
40
|
|
25
|
-
[Unreleased]: https://github.com/michenriksen/aquatone/compare/v0.
|
26
|
-
[0.
|
41
|
+
[Unreleased]: https://github.com/michenriksen/aquatone/compare/v0.2.0...HEAD
|
42
|
+
[0.2.0]: https://github.com/michenriksen/aquatone/compare/v0.1.1...v0.2.0
|
43
|
+
[0.1.1]: https://github.com/michenriksen/aquatone/compare/v0.1.0...v0.1.1
|
data/README.md
CHANGED
@@ -19,6 +19,9 @@ Finally, the tool itself can be installed with the following command in a termin
|
|
19
19
|
|
20
20
|
$ gem install aquatone
|
21
21
|
|
22
|
+
**IMPORTANT:** AQUATONE's screenshotting capabilities depend on being run on a system with a graphical desktop environment. It is strongly recommended to install and run AQUATONE in a [Kali linux](https://www.kali.org/) virtual machine.
|
23
|
+
**I will not provide support or bug fixing for other systems than Kali Linux.**
|
24
|
+
|
22
25
|
## Usage
|
23
26
|
|
24
27
|
### Discovery
|
@@ -1,6 +1,9 @@
|
|
1
1
|
module Aquatone
|
2
2
|
class Browser
|
3
3
|
module Drivers
|
4
|
+
|
5
|
+
class IncompatabilityError < StandardError; end
|
6
|
+
|
4
7
|
class Nightmare
|
5
8
|
attr_reader :url, :vhost, :html_destination, :headers_destination, :screenshot_destination, :options
|
6
9
|
|
@@ -23,6 +26,8 @@ module Aquatone
|
|
23
26
|
wout.close
|
24
27
|
command_output = rout.readlines.join("\n").strip
|
25
28
|
JSON.parse(command_output)
|
29
|
+
rescue JSON::ParserError
|
30
|
+
fail IncompatabilityError, "Nightmarejs must be run on a system with a graphical desktop session (X11)"
|
26
31
|
rescue => e
|
27
32
|
process.stop if process
|
28
33
|
return {
|
data/lib/aquatone/collector.rb
CHANGED
@@ -0,0 +1,83 @@
|
|
1
|
+
module Aquatone
|
2
|
+
module Collectors
|
3
|
+
class Censys < Aquatone::Collector
|
4
|
+
self.meta = {
|
5
|
+
:name => "Censys",
|
6
|
+
:author => "James McLean (@vortexau)",
|
7
|
+
:description => "Uses the Censys API to find hostnames in TLS certificates",
|
8
|
+
:require_keys => ["censys_secret","censys_id"],
|
9
|
+
}
|
10
|
+
|
11
|
+
API_BASE_URI = "https://www.censys.io/api/v1".freeze
|
12
|
+
API_RESULTS_PER_PAGE = 100.freeze
|
13
|
+
PAGES_TO_PROCESS = 10.freeze
|
14
|
+
|
15
|
+
def run
|
16
|
+
request_censys_page
|
17
|
+
end
|
18
|
+
|
19
|
+
def request_censys_page(page=1)
|
20
|
+
# Initial version only supporting Censys Certificates API
|
21
|
+
|
22
|
+
# Censys expects Basic Auth for requests.
|
23
|
+
auth = {
|
24
|
+
:username => get_key('censys_id'),
|
25
|
+
:password => get_key('censys_secret')
|
26
|
+
}
|
27
|
+
|
28
|
+
# Define this is JSON content
|
29
|
+
headers = {
|
30
|
+
'Content-Type' => 'application/json',
|
31
|
+
'Accept' => 'application/json'
|
32
|
+
}
|
33
|
+
|
34
|
+
# The post body itself, as JSON
|
35
|
+
query = {
|
36
|
+
'query' => url_escape("#{domain.name}"),
|
37
|
+
'page' => page,
|
38
|
+
'fields' => [ "parsed.names", "parsed.extensions.subject_alt_name.dns_names" ],
|
39
|
+
'flatten' => true
|
40
|
+
}
|
41
|
+
|
42
|
+
# Search API documented at https://censys.io/api/v1/docs/search
|
43
|
+
response = post_request(
|
44
|
+
"#{API_BASE_URI}/search/certificates",
|
45
|
+
query.to_json,
|
46
|
+
{
|
47
|
+
:basic_auth => auth,
|
48
|
+
:headers => headers
|
49
|
+
}
|
50
|
+
)
|
51
|
+
|
52
|
+
if response.code != 200
|
53
|
+
failure(response.parsed_response["error"] || "Censys API encountered error: #{response.code}")
|
54
|
+
end
|
55
|
+
|
56
|
+
# If nothing returned from Censys, return:
|
57
|
+
return unless response.parsed_response["results"]
|
58
|
+
|
59
|
+
response.parsed_response["results"].each do |result|
|
60
|
+
|
61
|
+
next unless result["parsed.extensions.subject_alt_name.dns_names"]
|
62
|
+
result["parsed.extensions.subject_alt_name.dns_names"].each do |altdns|
|
63
|
+
add_host(altdns) if altdns.end_with?(".#{domain.name}")
|
64
|
+
end
|
65
|
+
|
66
|
+
next unless result["parsed.names"]
|
67
|
+
result["parsed.names"].each do |parsedname|
|
68
|
+
add_host(parsedname) if parsedname.end_with?(".#{domain.name}")
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Get the next page of results
|
73
|
+
request_censys_page(page + 1) if next_page?(page, response.parsed_response)
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
def next_page?(page, body)
|
78
|
+
page <= PAGES_TO_PROCESS && body["metadata"]["pages"] && API_RESULTS_PER_PAGE * page < body["metadata"]["count"].to_i
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Aquatone
|
2
|
+
module Collectors
|
3
|
+
class Crtsh < Aquatone::Collector
|
4
|
+
self.meta = {
|
5
|
+
:name => "Certificate Search",
|
6
|
+
:author => "Joel (@jolle)",
|
7
|
+
:description => "Uses crt.sh by COMODO CA to find hostnames"
|
8
|
+
}
|
9
|
+
|
10
|
+
def run
|
11
|
+
response = get_request("https://crt.sh/?dNSName=%25.#{url_escape(domain.name)}")
|
12
|
+
|
13
|
+
response.body.to_enum(:scan, /<TD>([a-zA-Z0-9\*_.-]+\.#{Regexp.escape(domain.name)})<\/TD>/).map do |column|
|
14
|
+
add_host(column[0].gsub("*.", ""))
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Aquatone
|
2
|
+
module Collectors
|
3
|
+
class Passivetotal < Aquatone::Collector
|
4
|
+
self.meta = {
|
5
|
+
:name => "PassiveTotal",
|
6
|
+
:author => "Michael Henriksen (@michenriksen)",
|
7
|
+
:description => "Uses RiskIQ's PassiveTotal API to find hostnames",
|
8
|
+
:require_keys => ["passivetotal_key", "passivetotal_secret"]
|
9
|
+
}
|
10
|
+
|
11
|
+
API_BASE_URI = "https://api.passivetotal.org".freeze
|
12
|
+
|
13
|
+
def run
|
14
|
+
response = get_request(
|
15
|
+
"#{API_BASE_URI}/v2/enrichment/subdomains?query=.#{url_escape(domain.name)}",
|
16
|
+
:basic_auth => {:username => get_key("passivetotal_key"), :password => get_key("passivetotal_secret")}
|
17
|
+
)
|
18
|
+
body = response.parsed_response
|
19
|
+
if response.code != 200
|
20
|
+
failure(failure(body["message"] || "PassiveTotal API returned unexpected response code: #{response.code}"))
|
21
|
+
end
|
22
|
+
if !(body.key?("success") && body["success"])
|
23
|
+
failure("Request failed")
|
24
|
+
end
|
25
|
+
body["subdomains"].each do |subdomain|
|
26
|
+
add_host("#{subdomain}.#{domain.name}")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Aquatone
|
2
|
+
module Collectors
|
3
|
+
class Riddler < Aquatone::Collector
|
4
|
+
self.meta = {
|
5
|
+
:name => "Riddler",
|
6
|
+
:author => "Joel (@jolle)",
|
7
|
+
:description => "Uses Riddler by F-Secure to find hostnames",
|
8
|
+
:require_keys => ["riddler_username", "riddler_password"]
|
9
|
+
}
|
10
|
+
|
11
|
+
API_BASE_URI = "https://riddler.io"
|
12
|
+
|
13
|
+
def run
|
14
|
+
auth_response = post_request("#{API_BASE_URI}/auth/login", {
|
15
|
+
:email => get_key("riddler_username"),
|
16
|
+
:password => get_key("riddler_password")
|
17
|
+
}.to_json, {
|
18
|
+
:headers => { "Content-Type" => "application/json" }
|
19
|
+
})
|
20
|
+
|
21
|
+
if auth_response.code == 400 or auth_response.parsed_response["meta"]["code"] == 400
|
22
|
+
failure("Invalid credentials to Riddler.io")
|
23
|
+
elsif auth_response.code != 200 or auth_response.parsed_response["meta"]["code"] != 200
|
24
|
+
failure("Riddler.io auth API returned unexpected response code: #{response.code}")
|
25
|
+
end
|
26
|
+
|
27
|
+
token = auth_response.parsed_response["response"]["user"]["authentication_token"]
|
28
|
+
|
29
|
+
response = post_request("#{API_BASE_URI}/api/search", {
|
30
|
+
:query => "pld:#{url_escape(domain.name)}",
|
31
|
+
:output => "host",
|
32
|
+
:limit => 500
|
33
|
+
}.to_json, {
|
34
|
+
:headers => { "Content-Type" => "application/json", "Authentication-Token" => token }
|
35
|
+
})
|
36
|
+
|
37
|
+
response.parsed_response.each do |record|
|
38
|
+
add_host(record["host"]) unless record["host"].empty?
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/lib/aquatone/command.rb
CHANGED
@@ -112,14 +112,18 @@ module Aquatone
|
|
112
112
|
end
|
113
113
|
|
114
114
|
def asked_for_progress?
|
115
|
-
|
116
|
-
|
115
|
+
begin
|
116
|
+
while c = STDIN.read_nonblock(1)
|
117
|
+
return true if c == "\n"
|
118
|
+
end
|
119
|
+
false
|
120
|
+
rescue IO::EAGAINWaitReadable, Errno::EBADF
|
121
|
+
false
|
122
|
+
rescue Errno::EINTR, Errno::EAGAIN, EOFError
|
123
|
+
true
|
117
124
|
end
|
125
|
+
rescue NameError
|
118
126
|
false
|
119
|
-
rescue IO::EAGAINWaitReadable
|
120
|
-
false
|
121
|
-
rescue Errno::EINTR, Errno::EAGAIN, EOFError
|
122
|
-
true
|
123
127
|
end
|
124
128
|
|
125
129
|
def has_executable?(name)
|
@@ -103,8 +103,7 @@ module Aquatone
|
|
103
103
|
"#{(task_count.to_f / @hosts.count.to_f * 100.00).round(1)}% done\n")
|
104
104
|
end
|
105
105
|
if ip = @resolver.resolve(host)
|
106
|
-
next if
|
107
|
-
next if (options[:ignore_private] && private_ip?(ip))
|
106
|
+
next if exclude_ip?(ip)
|
108
107
|
@host_dictionary[host] = ip
|
109
108
|
output("#{ip.ljust(15)} #{bold(host)}\n")
|
110
109
|
end
|
@@ -157,6 +156,10 @@ module Aquatone
|
|
157
156
|
0 1 2 3 4 5 6 7 8 9).shuffle.take(10).join
|
158
157
|
end
|
159
158
|
|
159
|
+
def exclude_ip?(ip)
|
160
|
+
wildcard_ip?(ip) || (options[:ignore_private] && private_ip?(ip)) || broadcast_ip?(ip)
|
161
|
+
end
|
162
|
+
|
160
163
|
def wildcard_ip?(ip)
|
161
164
|
@wildcard_ips.include?(ip)
|
162
165
|
end
|
@@ -165,6 +168,10 @@ module Aquatone
|
|
165
168
|
ip =~ /(\A127\.)|(\A10\.)|(\A172\.1[6-9]\.)|(\A172\.2[0-9]\.)|(\A172\.3[0-1]\.)|(\A192\.168\.)/
|
166
169
|
end
|
167
170
|
|
171
|
+
def broadcast_ip?(ip)
|
172
|
+
ip == "255.255.255.255"
|
173
|
+
end
|
174
|
+
|
168
175
|
def skip_collector?(collector)
|
169
176
|
if options[:only_collectors]
|
170
177
|
if options[:only_collectors].include?(collector.sluggified_name)
|
@@ -78,17 +78,23 @@ module Aquatone
|
|
78
78
|
@tasks.shuffle.each do |task|
|
79
79
|
host, port, domain = task
|
80
80
|
pool.schedule do
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
81
|
+
begin
|
82
|
+
output_progress if asked_for_progress?
|
83
|
+
visit = visit_page(host, port, domain)
|
84
|
+
if visit['success']
|
85
|
+
output("#{green('Processed:')} #{Aquatone::UrlMaker.make(host, port)} (#{domain}) - #{visit['status']}\n")
|
86
|
+
@successful += 1
|
87
|
+
else
|
88
|
+
output(" #{red('Failed:')} #{Aquatone::UrlMaker.make(host, port)} (#{domain}) - #{visit['error']} #{visit['details']}\n")
|
89
|
+
@failed += 1
|
90
|
+
end
|
91
|
+
jitter_sleep
|
92
|
+
@task_count += 1
|
93
|
+
rescue Aquatone::Browser::Drivers::IncompatabilityError => e
|
94
|
+
output("\n")
|
95
|
+
output(red("Incompatability Error:") + " #{e.message}\n")
|
96
|
+
exit 1
|
89
97
|
end
|
90
|
-
jitter_sleep
|
91
|
-
@task_count += 1
|
92
98
|
end
|
93
99
|
end
|
94
100
|
pool.shutdown
|
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.2.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-06-
|
11
|
+
date: 2017-06-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: httparty
|
@@ -108,11 +108,15 @@ files:
|
|
108
108
|
- lib/aquatone/browser.rb
|
109
109
|
- lib/aquatone/browser/drivers/nightmare.rb
|
110
110
|
- lib/aquatone/collector.rb
|
111
|
+
- lib/aquatone/collectors/censys.rb
|
112
|
+
- lib/aquatone/collectors/crtsh.rb
|
111
113
|
- lib/aquatone/collectors/dictionary.rb
|
112
114
|
- lib/aquatone/collectors/dnsdb.rb
|
113
115
|
- lib/aquatone/collectors/gtr.rb
|
114
116
|
- lib/aquatone/collectors/hackertarget.rb
|
115
117
|
- lib/aquatone/collectors/netcraft.rb
|
118
|
+
- lib/aquatone/collectors/passivetotal.rb
|
119
|
+
- lib/aquatone/collectors/riddler.rb
|
116
120
|
- lib/aquatone/collectors/shodan.rb
|
117
121
|
- lib/aquatone/collectors/threatcrowd.rb
|
118
122
|
- lib/aquatone/collectors/virustotal.rb
|