ssh_scan 0.0.14 → 0.0.15
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/.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
|