ssh_scan 0.0.14 → 0.0.15
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/README.md +5 -4
- data/bin/ssh_scan +15 -1
- data/lib/ssh_scan.rb +1 -0
- data/lib/ssh_scan/client.rb +23 -11
- data/lib/ssh_scan/fingerprint_database.rb +39 -0
- data/lib/ssh_scan/os/ubuntu.rb +2 -1
- data/lib/ssh_scan/scan_engine.rb +52 -19
- data/lib/ssh_scan/update.rb +64 -0
- data/lib/ssh_scan/version.rb +1 -1
- data/ssh_scan.gemspec +1 -0
- metadata +18 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c5d764d9a2898d145c0f46eee1c59bb0c6e7bcde
|
4
|
+
data.tar.gz: 9bd1e38f1e4c715bf01a9526b85a5cf4d5c7961f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d9dca78fe553facec68df4241f43ed13537ef08d5b39f9d50ddafa50e97ba0c24d76a836f527c677e7cd90092be970604a3729b0348e06dfd28daefb4aef34b1
|
7
|
+
data.tar.gz: 67329c5b6e9c652b4572c9d1085be7cf220d48cafe19cf4dfa2827d8849ea5e43afcc284f79638cd1fdb03fca79677b1b662c3bef64f61765e4ab51f442475a4
|
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -59,22 +59,23 @@ bundle install
|
|
59
59
|
|
60
60
|
Run `ssh_scan -h` to get this
|
61
61
|
|
62
|
-
|
62
|
+
ssh_scan v0.0.15 (https://github.com/mozilla/ssh_scan)
|
63
63
|
|
64
64
|
Usage: ssh_scan [options]
|
65
65
|
-t, --target [IP/Range/Hostname] IP/Ranges/Hostname to scan
|
66
66
|
-f, --file [FilePath] File Path of the file containing IP/Range/Hostnames to scan
|
67
67
|
-T, --timeout [seconds] Timeout per connect after which ssh_scan gives up on the host
|
68
|
-
-
|
68
|
+
-L, --logger [Log File Path] Enable logger
|
69
69
|
-O, --from_json [FilePath] File to read JSON output from
|
70
|
+
-o, --output [FilePath] File to write JSON output to
|
70
71
|
-p, --port [PORT] Port (Default: 22)
|
71
72
|
-P, --policy [FILE] Custom policy file (Default: Mozilla Modern)
|
72
73
|
--threads [NUMBER] Number of worker threads (Default: 5)
|
74
|
+
--fingerprint-db [FILE] File location of fingerprint database (Default: ./fingerprints.db)
|
73
75
|
-u, --unit-test [FILE] Throw appropriate exit codes based on compliance status
|
76
|
+
-V, --verbosity Set the logger level (Accepted Params: INFO, DEBUG, WARN, ERROR, FATAL)
|
74
77
|
-v, --version Display just version info
|
75
78
|
-h, --help Show this message
|
76
|
-
-L, --logger[Log File Path] Enable logger and set the log file
|
77
|
-
-V, --verbosity Set the logger level (Params: INFO, DEBUG, WARN, ERROR, FATAL)
|
78
79
|
|
79
80
|
Examples:
|
80
81
|
|
data/bin/ssh_scan
CHANGED
@@ -18,6 +18,7 @@ options = {
|
|
18
18
|
:threads => 5,
|
19
19
|
:verbosity => nil,
|
20
20
|
:logger => Logger.new(STDERR),
|
21
|
+
:fingerprint_database => "./fingerprints.db"
|
21
22
|
}
|
22
23
|
|
23
24
|
target_parser = SSHScan::TargetParser.new()
|
@@ -107,13 +108,18 @@ opt_parser = OptionParser.new do |opts|
|
|
107
108
|
options[:threads] = threads.to_i
|
108
109
|
end
|
109
110
|
|
111
|
+
opts.on("--fingerprint-db [FILE]",
|
112
|
+
"File location of fingerprint database (Default: ./fingerprints.db)") do |fingerprint_db|
|
113
|
+
options[:fingerprint_database] = fingerprint_db
|
114
|
+
end
|
115
|
+
|
110
116
|
opts.on("-u", "--unit-test [FILE]",
|
111
117
|
"Throw appropriate exit codes based on compliance status") do
|
112
118
|
options[:unit_test] = true
|
113
119
|
end
|
114
120
|
|
115
121
|
opts.on("-V", "--verbosity",
|
116
|
-
"Set the logger level (
|
122
|
+
"Set the logger level (Accepted Params: INFO, DEBUG, WARN, ERROR, FATAL)") do |verbosity|
|
117
123
|
options[:logger].level = case options[:verbosity]
|
118
124
|
when "INFO" then Logger::INFO
|
119
125
|
when "DEBUG" then Logger::DEBUG
|
@@ -183,6 +189,14 @@ unless File.exists?(options[:policy])
|
|
183
189
|
exit 1
|
184
190
|
end
|
185
191
|
|
192
|
+
# Check to see if we're running the latest released version
|
193
|
+
update = SSHScan::Update.new
|
194
|
+
if update.newer_gem_available?
|
195
|
+
options[:logger].warn("You're NOT using the latest version of ssh_scan, try 'gem update ssh_scan' to get the latest")
|
196
|
+
else
|
197
|
+
options[:logger].info("You're using the latest version of ssh_scan #{SSHScan::VERSION}")
|
198
|
+
end
|
199
|
+
|
186
200
|
options[:policy_file] = SSHScan::Policy.from_file(options[:policy])
|
187
201
|
|
188
202
|
# Perform scan and get results
|
data/lib/ssh_scan.rb
CHANGED
data/lib/ssh_scan/client.rb
CHANGED
@@ -71,20 +71,32 @@ module SSHScan
|
|
71
71
|
result[:ssh_lib] = @server_banner.ssh_lib_guess.common
|
72
72
|
result[:ssh_lib_cpe] = @server_banner.ssh_lib_guess.cpe
|
73
73
|
|
74
|
-
|
75
|
-
|
74
|
+
begin
|
75
|
+
@sock.write(kex_init_raw)
|
76
|
+
resp = @sock.read(4)
|
76
77
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
78
|
+
if resp.nil?
|
79
|
+
result[:error] = SSHScan::Error::NoKexResponse.new("service did not respond to our kex init request")
|
80
|
+
@sock = nil
|
81
|
+
return result
|
82
|
+
end
|
82
83
|
|
83
|
-
|
84
|
-
|
84
|
+
resp += @sock.read(resp.unpack("N").first)
|
85
|
+
@sock.close
|
85
86
|
|
86
|
-
|
87
|
-
|
87
|
+
kex_exchange_init = SSHScan::KeyExchangeInit.read(resp)
|
88
|
+
result.merge!(kex_exchange_init.to_hash)
|
89
|
+
rescue Errno::ETIMEDOUT => e
|
90
|
+
@error = SSHScan::Error::ConnectTimeout.new(e.message)
|
91
|
+
@sock = nil
|
92
|
+
rescue Errno::ECONNREFUSED,
|
93
|
+
Errno::ENETUNREACH,
|
94
|
+
Errno::ECONNRESET,
|
95
|
+
Errno::EACCES,
|
96
|
+
Errno::EHOSTUNREACH => e
|
97
|
+
result[:error] = SSHScan::Error::NoKexResponse.new("service did not respond to our kex init request")
|
98
|
+
@sock = nil
|
99
|
+
end
|
88
100
|
|
89
101
|
return result
|
90
102
|
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'sqlite3'
|
2
|
+
|
3
|
+
module SSHScan
|
4
|
+
class FingerprintDatabase
|
5
|
+
def initialize(database_name)
|
6
|
+
if File.exists?(database_name)
|
7
|
+
@db = ::SQLite3::Database.open(database_name)
|
8
|
+
else
|
9
|
+
@db = ::SQLite3::Database.new(database_name)
|
10
|
+
self.create_schema
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def create_schema
|
15
|
+
@db.execute <<-SQL
|
16
|
+
create table fingerprints (
|
17
|
+
fingerprint varchar(100),
|
18
|
+
ip varchar(100)
|
19
|
+
);
|
20
|
+
SQL
|
21
|
+
end
|
22
|
+
|
23
|
+
def clear_fingerprints(ip)
|
24
|
+
@db.execute "delete from fingerprints where ip like ( ? )", [ip]
|
25
|
+
end
|
26
|
+
|
27
|
+
def add_fingerprint(fingerprint, ip)
|
28
|
+
@db.execute "insert into fingerprints values ( ?, ? )", [fingerprint, ip]
|
29
|
+
end
|
30
|
+
|
31
|
+
def find_fingerprints(fingerprint)
|
32
|
+
ips = []
|
33
|
+
@db.execute( "select * from fingerprints where fingerprint like ( ? )", [fingerprint] ) do |row|
|
34
|
+
ips << row[1]
|
35
|
+
end
|
36
|
+
return ips
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/ssh_scan/os/ubuntu.rb
CHANGED
data/lib/ssh_scan/scan_engine.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'socket'
|
2
2
|
require 'ssh_scan/client'
|
3
3
|
require 'ssh_scan/crypto'
|
4
|
+
require 'ssh_scan/fingerprint_database'
|
4
5
|
require 'net/ssh'
|
5
6
|
|
6
7
|
module SSHScan
|
@@ -11,7 +12,6 @@ module SSHScan
|
|
11
12
|
if port.nil?
|
12
13
|
port = 22
|
13
14
|
end
|
14
|
-
policy = opts[:policy_file]
|
15
15
|
timeout = opts[:timeout]
|
16
16
|
result = []
|
17
17
|
|
@@ -46,7 +46,12 @@ module SSHScan
|
|
46
46
|
|
47
47
|
# Connect and get results (Net-SSH)
|
48
48
|
begin
|
49
|
-
net_ssh_session = Net::SSH::Transport::Session.new(
|
49
|
+
net_ssh_session = Net::SSH::Transport::Session.new(
|
50
|
+
target,
|
51
|
+
:port => port,
|
52
|
+
:timeout => timeout,
|
53
|
+
:paranoid => Net::SSH::Verifiers::Null.new
|
54
|
+
)
|
50
55
|
raise SSHScan::Error::ClosedConnection.new if net_ssh_session.closed?
|
51
56
|
auth_session = Net::SSH::Authentication::Session.new(net_ssh_session, :auth_methods => ["none"])
|
52
57
|
auth_session.authenticate("none", "test", "test")
|
@@ -76,25 +81,8 @@ module SSHScan
|
|
76
81
|
end
|
77
82
|
end
|
78
83
|
|
79
|
-
# Do this only when no errors were reported
|
80
|
-
if !policy.nil? &&
|
81
|
-
!result[:key_algorithms].nil? &&
|
82
|
-
!result[:server_host_key_algorithms].nil? &&
|
83
|
-
!result[:encryption_algorithms_client_to_server].nil? &&
|
84
|
-
!result[:encryption_algorithms_server_to_client].nil? &&
|
85
|
-
!result[:mac_algorithms_client_to_server].nil? &&
|
86
|
-
!result[:mac_algorithms_server_to_client].nil? &&
|
87
|
-
!result[:compression_algorithms_client_to_server].nil? &&
|
88
|
-
!result[:compression_algorithms_server_to_client].nil? &&
|
89
|
-
!result[:languages_client_to_server].nil? &&
|
90
|
-
!result[:languages_server_to_client].nil?
|
91
|
-
policy_mgr = SSHScan::PolicyManager.new(result, policy)
|
92
|
-
result['compliance'] = policy_mgr.compliance_results
|
93
|
-
end
|
94
|
-
|
95
84
|
# Add scan times
|
96
85
|
end_time = Time.now
|
97
|
-
|
98
86
|
result['start_time'] = start_time.to_s
|
99
87
|
result['end_time'] = end_time.to_s
|
100
88
|
result['scan_duration_seconds'] = end_time - start_time
|
@@ -126,6 +114,51 @@ module SSHScan
|
|
126
114
|
end
|
127
115
|
workers.map(&:join)
|
128
116
|
|
117
|
+
# Add all the fingerprints to our peristent FingerprintDatabase
|
118
|
+
fingerprint_db = SSHScan::FingerprintDatabase.new(opts[:fingerprint_database])
|
119
|
+
results.each do |result|
|
120
|
+
fingerprint_db.clear_fingerprints(result[:ip])
|
121
|
+
if result['fingerprints']
|
122
|
+
result['fingerprints'].values.each do |fingerprint|
|
123
|
+
fingerprint_db.add_fingerprint(fingerprint, result[:ip])
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
# Decorate all the results with duplicate keys
|
129
|
+
results.each do |result|
|
130
|
+
if result['fingerprints']
|
131
|
+
ip = result[:ip]
|
132
|
+
result['duplicate_host_key_ips'] = []
|
133
|
+
result['fingerprints'].values.each do |fingerprint|
|
134
|
+
fingerprint_db.find_fingerprints(fingerprint).each do |other_ip|
|
135
|
+
next if ip == other_ip
|
136
|
+
result['duplicate_host_key_ips'] << other_ip
|
137
|
+
end
|
138
|
+
end
|
139
|
+
result['duplicate_host_key_ips'].uniq!
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# Decorate all the results with compliance information
|
144
|
+
results.each do |result|
|
145
|
+
# Do this only when we have all the information we need
|
146
|
+
if !opts[:policy_file].nil? &&
|
147
|
+
!result[:key_algorithms].nil? &&
|
148
|
+
!result[:server_host_key_algorithms].nil? &&
|
149
|
+
!result[:encryption_algorithms_client_to_server].nil? &&
|
150
|
+
!result[:encryption_algorithms_server_to_client].nil? &&
|
151
|
+
!result[:mac_algorithms_client_to_server].nil? &&
|
152
|
+
!result[:mac_algorithms_server_to_client].nil? &&
|
153
|
+
!result[:compression_algorithms_client_to_server].nil? &&
|
154
|
+
!result[:compression_algorithms_server_to_client].nil? &&
|
155
|
+
!result[:languages_client_to_server].nil? &&
|
156
|
+
!result[:languages_server_to_client].nil?
|
157
|
+
policy_mgr = SSHScan::PolicyManager.new(result, opts[:policy_file])
|
158
|
+
result['compliance'] = policy_mgr.compliance_results
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
129
162
|
return results
|
130
163
|
end
|
131
164
|
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'ssh_scan/os'
|
2
|
+
require 'ssh_scan/ssh_lib'
|
3
|
+
require 'ssh_scan/version'
|
4
|
+
require 'net/http'
|
5
|
+
|
6
|
+
module SSHScan
|
7
|
+
class Update
|
8
|
+
def next_patch_version(version = SSHScan::VERSION)
|
9
|
+
major, minor, patch = version.split(".")
|
10
|
+
patch_num = patch.to_i
|
11
|
+
patch_num += 1
|
12
|
+
|
13
|
+
return [major, minor, patch_num.to_s].join(".")
|
14
|
+
end
|
15
|
+
|
16
|
+
def next_minor_version(version = SSHScan::VERSION)
|
17
|
+
major, minor, patch = version.split(".")
|
18
|
+
minor_num = minor.to_i
|
19
|
+
minor_num += 1
|
20
|
+
|
21
|
+
return [major, minor_num.to_s, "0"].join(".")
|
22
|
+
end
|
23
|
+
|
24
|
+
def next_major_version(version = SSHScan::VERSION)
|
25
|
+
major, minor, patch = version.split(".")
|
26
|
+
major_num = major.to_i
|
27
|
+
major_num += 1
|
28
|
+
|
29
|
+
return [major_num.to_s, "0", "0"].join(".")
|
30
|
+
end
|
31
|
+
|
32
|
+
def gem_exists?(version = SSHScan::VERSION)
|
33
|
+
uri = URI("https://rubygems.org/gems/ssh_scan/versions/#{version}")
|
34
|
+
|
35
|
+
begin
|
36
|
+
res = Net::HTTP.get_response(uri)
|
37
|
+
rescue
|
38
|
+
return false
|
39
|
+
end
|
40
|
+
|
41
|
+
if res.code != "200"
|
42
|
+
return false
|
43
|
+
else
|
44
|
+
return true
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def newer_gem_available?(version = SSHScan::VERSION)
|
49
|
+
if gem_exists?(next_patch_version(version))
|
50
|
+
return true
|
51
|
+
end
|
52
|
+
|
53
|
+
if gem_exists?(next_minor_version(version))
|
54
|
+
return true
|
55
|
+
end
|
56
|
+
|
57
|
+
if gem_exists?(next_major_version(version))
|
58
|
+
return true
|
59
|
+
end
|
60
|
+
|
61
|
+
return false
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
data/lib/ssh_scan/version.rb
CHANGED
data/ssh_scan.gemspec
CHANGED
@@ -29,6 +29,7 @@ Gem::Specification.new do |s|
|
|
29
29
|
s.add_dependency('bindata', '~> 2.0')
|
30
30
|
s.add_dependency('netaddr')
|
31
31
|
s.add_dependency('net-ssh')
|
32
|
+
s.add_dependency('sqlite3')
|
32
33
|
s.add_development_dependency('pry')
|
33
34
|
s.add_development_dependency('rspec', '~> 3.0')
|
34
35
|
s.add_development_dependency('rspec-its', '~> 1.2')
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ssh_scan
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.15
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jonathan Claudius
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2016-09-
|
12
|
+
date: 2016-09-21 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bindata
|
@@ -53,6 +53,20 @@ dependencies:
|
|
53
53
|
- - ">="
|
54
54
|
- !ruby/object:Gem::Version
|
55
55
|
version: '0'
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
name: sqlite3
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0'
|
63
|
+
type: :runtime
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
56
70
|
- !ruby/object:Gem::Dependency
|
57
71
|
name: pry
|
58
72
|
requirement: !ruby/object:Gem::Requirement
|
@@ -136,6 +150,7 @@ files:
|
|
136
150
|
- lib/ssh_scan/error/disconnected.rb
|
137
151
|
- lib/ssh_scan/error/no_banner.rb
|
138
152
|
- lib/ssh_scan/error/no_kex_response.rb
|
153
|
+
- lib/ssh_scan/fingerprint_database.rb
|
139
154
|
- lib/ssh_scan/os.rb
|
140
155
|
- lib/ssh_scan/os/centos.rb
|
141
156
|
- lib/ssh_scan/os/cisco.rb
|
@@ -160,6 +175,7 @@ files:
|
|
160
175
|
- lib/ssh_scan/ssh_lib/rosssh.rb
|
161
176
|
- lib/ssh_scan/ssh_lib/unknown.rb
|
162
177
|
- lib/ssh_scan/target_parser.rb
|
178
|
+
- lib/ssh_scan/update.rb
|
163
179
|
- lib/ssh_scan/version.rb
|
164
180
|
- lib/string_ext.rb
|
165
181
|
- policies/mozilla_intermediate.yml
|