aquatone 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 78769d828a09c8c64d631762851816956390be05
4
- data.tar.gz: fed735f12ef3d389c87033f55b7ccf2570afc985
3
+ metadata.gz: 1572ba4c07838fd7294dfc001c8ff23b2f7c19fc
4
+ data.tar.gz: 2c7fc94f9cef66e71fc8adeed1840a3170cf6367
5
5
  SHA512:
6
- metadata.gz: d1a29f270be14944375aa6b9d0231a81a37484d8b4ad5a9561c354cc4e7e40fe0385e1998dd4fee7cc6985741d04d867520d8be03f0e8c32ce8c5bba79e50980
7
- data.tar.gz: 2738fb5ced2b632399b26d42ed25d9c2e7cbbc84d2ee398cf1c111264fda511e4d696c725e64e21b2dbc8256058afc9f7486f05fa5499f8cca8bbf9362f0d047
6
+ metadata.gz: d5086248f07db25eeead317a570ee56cf3d622eb72e471d85c1c33611f3cfc4bba0be721a9f65658e6e078b264bbbb2327157574070e235d1542c4a85ba4ec6e
7
+ data.tar.gz: f81b9ab7409889e524c59fda6e53e7ce4e891a515f7492885f5fcd5e558ae8bdb187c2e6295ad5ee40324f51b12994dfbc39759a272a03fbcbd08b75e5f90fc6
@@ -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.1.0...HEAD
26
- [0.1.1]: https://github.com/olivierlacan/keep-a-changelog/compare/v0.1.0...v0.1.1
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 {
@@ -56,7 +56,7 @@ module Aquatone
56
56
  end
57
57
 
58
58
  def get_request(uri, options={})
59
- Aquatone::HttpClient.get(uri)
59
+ Aquatone::HttpClient.get(uri, options)
60
60
  end
61
61
 
62
62
  def post_request(uri, body=nil, options={})
@@ -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
@@ -112,14 +112,18 @@ module Aquatone
112
112
  end
113
113
 
114
114
  def asked_for_progress?
115
- while c = STDIN.read_nonblock(1)
116
- return true if c == "\n"
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 wildcard_ip?(ip)
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
- output_progress if asked_for_progress?
82
- visit = visit_page(host, port, domain)
83
- if visit['success']
84
- output("#{green('Processed:')} #{Aquatone::UrlMaker.make(host, port)} (#{domain}) - #{visit['status']}\n")
85
- @successful += 1
86
- else
87
- output(" #{red('Failed:')} #{Aquatone::UrlMaker.make(host, port)} (#{domain}) - #{visit['error']} #{visit['details']}\n")
88
- @failed += 1
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
@@ -1,3 +1,3 @@
1
1
  module Aquatone
2
- VERSION = "0.1.1".freeze
2
+ VERSION = "0.2.0".freeze
3
3
  end
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.1.1
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-18 00:00:00.000000000 Z
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