aquatone 0.3.0 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 89f0cbe4f00ec77bae2d323be2978261680a75d8
4
- data.tar.gz: 32948fb248e6614400c878ea2ac4681a5da9558b
3
+ metadata.gz: d8ad3da3d450ecd7fd41c64cd0b18f0a3e3ae00b
4
+ data.tar.gz: 240fbce3dc353f146a32be28566201b2b62fd2c6
5
5
  SHA512:
6
- metadata.gz: 941e0e136a451eb295184a6b35dde232cbcae72624a4e421a5c304f89f70c281744cc85c05eafe951f210fd744ec6b3406362b554a4cab9a46b666e58c7be254
7
- data.tar.gz: 48ccd44ff3e7fa4cfd05bd7221e6287479f9b80b362342915789ffe673c12a9f8a25fc8ef63524286eee401f4fccddf0fb8395810ca1e352aa6b81c0445325df
6
+ metadata.gz: 244cc756b2cb1c65d36281fbeae199d290a71434f73a12c96e855995eff2a091aae442212d13ee604ac6b34d6d74196813ff3b01bd5b809b16512c86b438c44e
7
+ data.tar.gz: b109618403ee14536a67a8e75334e88279578f2eb9532edd8ea772c7bf1e443a8747a01038eadfc44bd5a42000ab90571be269432340eef1257161f6d3d40110
@@ -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.3.0...HEAD
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
@@ -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
@@ -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 = domain
34
- @hosts = []
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
- @hosts << host unless @hosts.include?(host)
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 = "https://www.censys.io/api/v1".freeze
12
- API_RESULTS_PER_PAGE = 100.freeze
13
- PAGES_TO_PROCESS = 10.freeze
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 <= PAGES_TO_PROCESS && body["metadata"]["pages"] && API_RESULTS_PER_PAGE * page < body["metadata"]["count"].to_i
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
- DICTIONARY = File.join(Aquatone::AQUATONE_ROOT, "subdomains.lst").freeze
13
+ DEFAULT_DICTIONARY = File.join(Aquatone::AQUATONE_ROOT, "subdomains.lst").freeze
11
14
 
12
15
  def run
13
- dictionary = File.open(DICTIONARY, "r")
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 = "https://www.google.com/transparencyreport/jsonp/ct/search"
12
- PAGES_TO_PROCESS = 30.freeze
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
- PAGES_TO_PROCESS.times do
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 = "http://searchdns.netcraft.com/".freeze
11
- HOSTNAME_REGEX = /<a href="http:\/\/(.*?)\/" rel="nofollow">/.freeze
12
- RESULTS_PER_PAGE = 20.freeze
13
- PAGES_TO_PROCESS = 10.freeze
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
- PAGES_TO_PROCESS.times do |i|
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 = "https://api.shodan.io/shodan".freeze
12
- API_RESULTS_PER_PAGE = 100.freeze
13
- PAGES_TO_PROCESS = 10.freeze
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 <= PAGES_TO_PROCESS && body["total"] && API_RESULTS_PER_PAGE * page < body["total"].to_i
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
@@ -1,3 +1,3 @@
1
1
  module Aquatone
2
- VERSION = "0.3.0".freeze
2
+ VERSION = "0.4.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.3.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-07-21 00:00:00.000000000 Z
11
+ date: 2017-08-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: httparty