aquatone 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +12 -0
- data/CHANGELOG.md +18 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +168 -0
- data/Rakefile +10 -0
- data/aquatone.gemspec +29 -0
- data/aquatone.js +164 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/exe/aquatone-discover +129 -0
- data/exe/aquatone-gather +55 -0
- data/exe/aquatone-scan +76 -0
- data/lib/aquatone.rb +43 -0
- data/lib/aquatone/assessment.rb +40 -0
- data/lib/aquatone/browser.rb +18 -0
- data/lib/aquatone/browser/drivers/nightmare.rb +52 -0
- data/lib/aquatone/collector.rb +106 -0
- data/lib/aquatone/collectors/dictionary.rb +20 -0
- data/lib/aquatone/collectors/dnsdb.rb +45 -0
- data/lib/aquatone/collectors/gtr.rb +58 -0
- data/lib/aquatone/collectors/hackertarget.rb +24 -0
- data/lib/aquatone/collectors/netcraft.rb +48 -0
- data/lib/aquatone/collectors/shodan.rb +45 -0
- data/lib/aquatone/collectors/threatcrowd.rb +25 -0
- data/lib/aquatone/collectors/virustotal.rb +24 -0
- data/lib/aquatone/command.rb +152 -0
- data/lib/aquatone/commands/discover.rb +187 -0
- data/lib/aquatone/commands/gather.rb +167 -0
- data/lib/aquatone/commands/scan.rb +108 -0
- data/lib/aquatone/domain.rb +33 -0
- data/lib/aquatone/http_client.rb +5 -0
- data/lib/aquatone/key_store.rb +72 -0
- data/lib/aquatone/port_lists.rb +36 -0
- data/lib/aquatone/report.rb +88 -0
- data/lib/aquatone/resolver.rb +47 -0
- data/lib/aquatone/thread_pool.rb +31 -0
- data/lib/aquatone/url_maker.rb +27 -0
- data/lib/aquatone/validation.rb +22 -0
- data/lib/aquatone/version.rb +3 -0
- data/subdomains.lst +8214 -0
- data/templates/default.html.erb +225 -0
- metadata +159 -0
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "aquatone"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "aquatone"
|
4
|
+
|
5
|
+
options = {
|
6
|
+
:fallback_nameservers => %w(8.8.8.8 8.8.4.4),
|
7
|
+
:threads => 5,
|
8
|
+
:ignore_private => true
|
9
|
+
}
|
10
|
+
|
11
|
+
OptionParser.new do |opts|
|
12
|
+
opts.banner = "Usage: aquatone-discover OPTIONS"
|
13
|
+
|
14
|
+
opts.on("-d", "--domain DOMAIN", "Domain name to assess") do |v|
|
15
|
+
if !Aquatone::Validation.valid_domain_name?(v)
|
16
|
+
puts "#{v} doesn't look like a valid domain name."
|
17
|
+
exit 1
|
18
|
+
end
|
19
|
+
options[:domain] = v
|
20
|
+
end
|
21
|
+
|
22
|
+
opts.on("--nameservers NAMESERVERS", "Nameservers to use") do |v|
|
23
|
+
ips = v.split(",").map(&:strip).uniq
|
24
|
+
if ips.empty?
|
25
|
+
puts "Nameservers can't be empty."
|
26
|
+
exit 1
|
27
|
+
end
|
28
|
+
ips.each do |ip|
|
29
|
+
if !Aquatone::Validation.valid_ip?(ip)
|
30
|
+
puts "#{ip} is not a valid IP address."
|
31
|
+
exit 1
|
32
|
+
end
|
33
|
+
end
|
34
|
+
options[:nameservers] = ips
|
35
|
+
end
|
36
|
+
|
37
|
+
opts.on("--fallback-nameservers NAMESERVERS", "Nameservers to fall back to") do |v|
|
38
|
+
ips = v.split(",").map(&:strip).uniq
|
39
|
+
if ips.empty?
|
40
|
+
puts "Fallback nameservers can't be empty."
|
41
|
+
exit 1
|
42
|
+
end
|
43
|
+
ips.each do |ip|
|
44
|
+
if !Aquatone::Validation.valid_ip?(ip)
|
45
|
+
puts "#{ip} is not a valid IP address."
|
46
|
+
exit 1
|
47
|
+
end
|
48
|
+
end
|
49
|
+
options[:fallback_nameservers] = ips
|
50
|
+
end
|
51
|
+
|
52
|
+
opts.on("--[no-]ignore-private", "Ignore hosts resolving to private IP addresses") do |v|
|
53
|
+
options[:ignore_private] = v
|
54
|
+
end
|
55
|
+
|
56
|
+
opts.on("--set-key KEY VALUE", "Save a key to key store") do |key|
|
57
|
+
if ARGV.empty?
|
58
|
+
puts "No key value given."
|
59
|
+
exit 1
|
60
|
+
end
|
61
|
+
key = key.to_s.downcase
|
62
|
+
value = ARGV.join(" ").strip
|
63
|
+
Aquatone::KeyStore.set(key, value)
|
64
|
+
puts "Saved key #{key} with value #{value}."
|
65
|
+
exit
|
66
|
+
end
|
67
|
+
|
68
|
+
opts.on("--list-collectors", "See information on collectors") do
|
69
|
+
Aquatone::Collector.descendants.each do |collector|
|
70
|
+
puts "Name.......: #{collector.meta[:name]}"
|
71
|
+
puts "Description: #{collector.meta[:description]}" if collector.meta[:description]
|
72
|
+
puts "Author.....: #{collector.meta[:author]}"
|
73
|
+
puts "Key........: #{collector.sluggified_name}\n\n"
|
74
|
+
puts "--------------------------------------------------\n\n"
|
75
|
+
end
|
76
|
+
exit
|
77
|
+
end
|
78
|
+
|
79
|
+
opts.on("--only-collectors COLLECTORS", "Only run specified collectors") do |v|
|
80
|
+
known_collectors = Aquatone::Collector.descendants.map(&:sluggified_name)
|
81
|
+
collectors = v.split(",").map(&:strip).uniq
|
82
|
+
collectors.each do |collector|
|
83
|
+
if !known_collectors.include?(collector)
|
84
|
+
puts "Unknown collector key: #{collector}"
|
85
|
+
exit 1
|
86
|
+
end
|
87
|
+
end
|
88
|
+
options[:only_collectors] = collectors
|
89
|
+
end
|
90
|
+
|
91
|
+
opts.on("--disable-collectors COLLECTORS", "Disable specified collectors") do |v|
|
92
|
+
known_collectors = Aquatone::Collector.descendants.map(&:sluggified_name)
|
93
|
+
collectors = v.split(",").map(&:strip).uniq
|
94
|
+
collectors.each do |collector|
|
95
|
+
if !known_collectors.include?(collector)
|
96
|
+
puts "Unknown collector key: #{collector}"
|
97
|
+
exit 1
|
98
|
+
end
|
99
|
+
end
|
100
|
+
options[:disable_collectors] = collectors
|
101
|
+
end
|
102
|
+
|
103
|
+
opts.on("-t", "--threads THREADS", "Number of concurrent threads to use") do |v|
|
104
|
+
options[:threads] = v.to_i
|
105
|
+
end
|
106
|
+
|
107
|
+
opts.on("-s", "--sleep SECONDS", "Seconds to sleep between lookups") do |v|
|
108
|
+
if v.to_i < 1
|
109
|
+
puts "Sleep can't be less than 1"
|
110
|
+
exit 1
|
111
|
+
end
|
112
|
+
options[:sleep] = v.to_i
|
113
|
+
end
|
114
|
+
|
115
|
+
opts.on("-j", "--jitter PERCENTAGE", "Jitter factor for sleep intervals") do |v|
|
116
|
+
if !v.to_i.between?(1, 100)
|
117
|
+
puts "Jitter factor must be between 1 and 100"
|
118
|
+
exit 1
|
119
|
+
end
|
120
|
+
options[:jitter] = v.to_f
|
121
|
+
end
|
122
|
+
|
123
|
+
opts.on("-h", "--help", "Show help") do
|
124
|
+
puts opts
|
125
|
+
exit 0
|
126
|
+
end
|
127
|
+
end.parse!
|
128
|
+
|
129
|
+
Aquatone::Commands::Discover.run(options)
|
data/exe/aquatone-gather
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "aquatone"
|
4
|
+
|
5
|
+
options = {
|
6
|
+
:threads => 5,
|
7
|
+
:timeout => 15
|
8
|
+
}
|
9
|
+
|
10
|
+
OptionParser.new do |opts|
|
11
|
+
opts.banner = "Usage: aquatone-gather OPTIONS"
|
12
|
+
|
13
|
+
opts.on("-d", "--domain DOMAIN", "Domain name to assess") do |v|
|
14
|
+
if !Aquatone::Validation.valid_domain_name?(v)
|
15
|
+
puts "#{v} doesn't look like a valid domain name."
|
16
|
+
exit 1
|
17
|
+
end
|
18
|
+
options[:domain] = v
|
19
|
+
end
|
20
|
+
|
21
|
+
opts.on("-t", "--threads THREADS", "Number of concurrent threads to use") do |v|
|
22
|
+
options[:threads] = v.to_i
|
23
|
+
end
|
24
|
+
|
25
|
+
opts.on("--timeout SECONDS", "Timeout for page renderings") do |v|
|
26
|
+
if v.to_i < 1
|
27
|
+
puts "Timeout must be at least 1 second"
|
28
|
+
exit 1
|
29
|
+
end
|
30
|
+
options[:timeout] = v.to_i
|
31
|
+
end
|
32
|
+
|
33
|
+
opts.on("-s", "--sleep SECONDS", "Seconds to sleep between lookups") do |v|
|
34
|
+
if v.to_i < 1
|
35
|
+
puts "Sleep can't be less than 1"
|
36
|
+
exit 1
|
37
|
+
end
|
38
|
+
options[:sleep] = v.to_i
|
39
|
+
end
|
40
|
+
|
41
|
+
opts.on("-j", "--jitter PERCENTAGE", "Jitter factor for sleep intervals") do |v|
|
42
|
+
if !v.to_i.between?(1, 100)
|
43
|
+
puts "Jitter factor must be between 1 and 100"
|
44
|
+
exit 1
|
45
|
+
end
|
46
|
+
options[:jitter] = v.to_f
|
47
|
+
end
|
48
|
+
|
49
|
+
opts.on("-h", "--help", "Show help") do
|
50
|
+
puts opts
|
51
|
+
exit 0
|
52
|
+
end
|
53
|
+
end.parse!
|
54
|
+
|
55
|
+
Aquatone::Commands::Gather.run(options)
|
data/exe/aquatone-scan
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "aquatone"
|
4
|
+
|
5
|
+
options = {
|
6
|
+
:ports => Aquatone::PortLists::MEDIUM,
|
7
|
+
:threads => 5,
|
8
|
+
:timeout => 0.5
|
9
|
+
}
|
10
|
+
|
11
|
+
OptionParser.new do |opts|
|
12
|
+
opts.banner = "Usage: aquatone-scan OPTIONS"
|
13
|
+
|
14
|
+
opts.on("-d", "--domain DOMAIN", "Domain name to assess") do |v|
|
15
|
+
if !Aquatone::Validation.valid_domain_name?(v)
|
16
|
+
puts "#{v} doesn't look like a valid domain name."
|
17
|
+
exit 1
|
18
|
+
end
|
19
|
+
options[:domain] = v
|
20
|
+
end
|
21
|
+
|
22
|
+
opts.on("-p", "--ports PORTS", "Ports to scan") do |v|
|
23
|
+
if v =~ /\A[\d,\s]{1,}\z/
|
24
|
+
ports = v.split(",").map { |p| p.strip.to_i }
|
25
|
+
ports.each do |p|
|
26
|
+
if !Aquatone::Validation.valid_tcp_port?(p)
|
27
|
+
puts "#{p} is not a valid TCP port."
|
28
|
+
exit 1
|
29
|
+
end
|
30
|
+
end
|
31
|
+
options[:ports] = ports
|
32
|
+
else
|
33
|
+
begin
|
34
|
+
options[:ports] = Aquatone::PortLists.port_list_by_name(v.downcase)
|
35
|
+
rescue Aquatone::PortLists::UnknownPortListName => e
|
36
|
+
puts e.message
|
37
|
+
exit 1
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
opts.on("--timeout SECONDS", "Timeout in seconds for port probes") do |v|
|
43
|
+
if v.to_f <= 0
|
44
|
+
puts "Timeout can't be zero"
|
45
|
+
exit 1
|
46
|
+
end
|
47
|
+
options[:timeout] = v.to_f
|
48
|
+
end
|
49
|
+
|
50
|
+
opts.on("-t", "--threads THREADS", "Number of concurrent threads to use") do |v|
|
51
|
+
options[:threads] = v.to_i
|
52
|
+
end
|
53
|
+
|
54
|
+
opts.on("-s", "--sleep SECONDS", "Seconds to sleep between port probes") do |v|
|
55
|
+
if v.to_i < 1
|
56
|
+
puts "Sleep can't be less than 1"
|
57
|
+
exit 1
|
58
|
+
end
|
59
|
+
options[:sleep] = v.to_i
|
60
|
+
end
|
61
|
+
|
62
|
+
opts.on("-j", "--jitter PERCENTAGE", "Jitter factor for sleep intervals") do |v|
|
63
|
+
if !v.to_i.between?(1, 100)
|
64
|
+
puts "Jitter factor must be between 1 and 100"
|
65
|
+
exit 1
|
66
|
+
end
|
67
|
+
options[:jitter] = v.to_f
|
68
|
+
end
|
69
|
+
|
70
|
+
opts.on("-h", "--help", "Show help") do
|
71
|
+
puts opts
|
72
|
+
exit 0
|
73
|
+
end
|
74
|
+
end.parse!
|
75
|
+
|
76
|
+
Aquatone::Commands::Scan.run(options)
|
data/lib/aquatone.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
require "resolv"
|
2
|
+
require "ipaddr"
|
3
|
+
require "socket"
|
4
|
+
require "timeout"
|
5
|
+
require "shellwords"
|
6
|
+
require "optparse"
|
7
|
+
|
8
|
+
require "httparty"
|
9
|
+
require "childprocess"
|
10
|
+
|
11
|
+
require "aquatone/version"
|
12
|
+
require "aquatone/port_lists"
|
13
|
+
require "aquatone/url_maker"
|
14
|
+
require "aquatone/validation"
|
15
|
+
require "aquatone/thread_pool"
|
16
|
+
require "aquatone/http_client"
|
17
|
+
require "aquatone/browser"
|
18
|
+
require "aquatone/browser/drivers/nightmare"
|
19
|
+
require "aquatone/domain"
|
20
|
+
require "aquatone/resolver"
|
21
|
+
require "aquatone/assessment"
|
22
|
+
require "aquatone/report"
|
23
|
+
require "aquatone/command"
|
24
|
+
require "aquatone/collector"
|
25
|
+
|
26
|
+
module Aquatone
|
27
|
+
AQUATONE_ROOT = File.expand_path(File.join(File.dirname(__FILE__), "..")).freeze
|
28
|
+
DEFAULT_AQUATONE_PATH = File.join(Dir.home, "aquatone").freeze
|
29
|
+
|
30
|
+
def self.aquatone_path
|
31
|
+
ENV['AQUATONEPATH'] || DEFAULT_AQUATONE_PATH
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
require "aquatone/key_store"
|
36
|
+
|
37
|
+
Dir[File.join(File.dirname(__FILE__), "aquatone", "collectors", "*.rb")].each do |collector|
|
38
|
+
require collector
|
39
|
+
end
|
40
|
+
|
41
|
+
require "aquatone/commands/discover"
|
42
|
+
require "aquatone/commands/scan"
|
43
|
+
require "aquatone/commands/gather"
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Aquatone
|
2
|
+
class Assessment
|
3
|
+
attr_reader :domain
|
4
|
+
|
5
|
+
def initialize(domain)
|
6
|
+
@domain = domain
|
7
|
+
initialize_assessment_directory
|
8
|
+
end
|
9
|
+
|
10
|
+
def has_file?(name)
|
11
|
+
File.exist?(File.join(path, name))
|
12
|
+
end
|
13
|
+
|
14
|
+
def read_file(name)
|
15
|
+
File.read(File.join(path, name))
|
16
|
+
end
|
17
|
+
|
18
|
+
def write_file(name, data, mode = "w")
|
19
|
+
File.open(File.join(path, name), mode) do |file|
|
20
|
+
file.write(data)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def make_directory(name)
|
25
|
+
dir = File.join(path, name)
|
26
|
+
Dir.mkdir(dir) unless Dir.exist?(dir)
|
27
|
+
end
|
28
|
+
|
29
|
+
def path
|
30
|
+
File.join(Aquatone.aquatone_path, domain)
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def initialize_assessment_directory
|
36
|
+
Dir.mkdir(Aquatone.aquatone_path) unless Dir.exist?(Aquatone.aquatone_path)
|
37
|
+
Dir.mkdir(path) unless Dir.exist?(path)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Aquatone
|
2
|
+
class Browser
|
3
|
+
def self.visit(url, vhost, html_destination, headers_destination, screenshot_destination, options)
|
4
|
+
driver = make_driver(url, vhost, html_destination, headers_destination, screenshot_destination, options)
|
5
|
+
visit = driver.visit
|
6
|
+
if !visit["success"]
|
7
|
+
visit = driver.visit
|
8
|
+
end
|
9
|
+
visit
|
10
|
+
end
|
11
|
+
|
12
|
+
protected
|
13
|
+
|
14
|
+
def self.make_driver(url, vhost, html_destination, headers_destination, screenshot_destination, options)
|
15
|
+
Aquatone::Browser::Drivers::Nightmare.new(url, vhost, html_destination, headers_destination, screenshot_destination, options)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Aquatone
|
2
|
+
class Browser
|
3
|
+
module Drivers
|
4
|
+
class Nightmare
|
5
|
+
attr_reader :url, :vhost, :html_destination, :headers_destination, :screenshot_destination, :options
|
6
|
+
|
7
|
+
def initialize(url, vhost, html_destination, headers_destination, screenshot_destination, options)
|
8
|
+
@url = url
|
9
|
+
@vhost = vhost
|
10
|
+
@html_destination = html_destination
|
11
|
+
@headers_destination = headers_destination
|
12
|
+
@screenshot_destination = screenshot_destination
|
13
|
+
@options = options
|
14
|
+
end
|
15
|
+
|
16
|
+
def visit
|
17
|
+
rout, wout = IO.pipe
|
18
|
+
process = ChildProcess.build(*construct_command)
|
19
|
+
process.cwd = Aquatone::AQUATONE_ROOT
|
20
|
+
process.io.stdout = wout
|
21
|
+
process.start
|
22
|
+
process.poll_for_exit(options[:timeout])
|
23
|
+
wout.close
|
24
|
+
command_output = rout.readlines.join("\n").strip
|
25
|
+
JSON.parse(command_output)
|
26
|
+
rescue => e
|
27
|
+
process.stop if process
|
28
|
+
return {
|
29
|
+
"success" => false,
|
30
|
+
"error" => e.is_a?(ChildProcess::TimeoutError) ? "Timeout" : "#{e.class}: #{e.message}",
|
31
|
+
"code" => 0,
|
32
|
+
"details" => ""
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def construct_command
|
39
|
+
[
|
40
|
+
"node",
|
41
|
+
File.join(Aquatone::AQUATONE_ROOT, "aquatone.js"),
|
42
|
+
Shellwords.escape(url),
|
43
|
+
Shellwords.escape(vhost),
|
44
|
+
Shellwords.escape(html_destination),
|
45
|
+
Shellwords.escape(headers_destination),
|
46
|
+
Shellwords.escape(screenshot_destination)
|
47
|
+
]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|