ssh_scan 0.0.16 → 0.0.17.pre
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 +15 -1
- data/.travis.yml +2 -0
- data/Gemfile +1 -0
- data/README.md +35 -33
- data/Rakefile +40 -16
- data/bin/ssh_scan +91 -53
- data/bin/ssh_scan_worker +14 -0
- data/lib/ssh_scan.rb +0 -1
- data/lib/ssh_scan/client.rb +10 -4
- data/lib/ssh_scan/constants.rb +67 -18
- data/lib/ssh_scan/crypto.rb +3 -18
- data/lib/ssh_scan/error/closed_connection.rb +1 -1
- data/lib/ssh_scan/error/connect_timeout.rb +1 -1
- data/lib/ssh_scan/error/connection_refused.rb +1 -1
- data/lib/ssh_scan/error/disconnected.rb +1 -1
- data/lib/ssh_scan/error/no_banner.rb +1 -1
- data/lib/ssh_scan/error/no_kex_response.rb +1 -1
- data/lib/ssh_scan/os/raspbian.rb +2 -4
- data/lib/ssh_scan/os/ubuntu.rb +103 -58
- data/lib/ssh_scan/policy.rb +2 -1
- data/lib/ssh_scan/policy_manager.rb +67 -18
- data/lib/ssh_scan/protocol.rb +53 -21
- data/lib/ssh_scan/scan_engine.rb +78 -44
- data/lib/ssh_scan/ssh_lib/dropbear.rb +2 -4
- data/lib/ssh_scan/target_parser.rb +3 -3
- data/lib/ssh_scan/update.rb +3 -3
- data/lib/ssh_scan/version.rb +1 -2
- data/lib/ssh_scan/worker.rb +119 -0
- data/lib/string_ext.rb +2 -1
- data/ssh_scan.gemspec +4 -8
- metadata +28 -96
- data/bin/ssh_scan_api +0 -36
- data/lib/ssh_scan/api.rb +0 -124
- data/lib/ssh_scan/fingerprint_database.rb +0 -39
- data/policies/mozilla_intermediate.yml +0 -19
- data/policies/mozilla_modern.yml +0 -30
data/lib/ssh_scan/policy.rb
CHANGED
@@ -2,7 +2,8 @@ require 'yaml'
|
|
2
2
|
|
3
3
|
module SSHScan
|
4
4
|
class Policy
|
5
|
-
attr_reader :name, :kex, :macs, :encryption, :compression,
|
5
|
+
attr_reader :name, :kex, :macs, :encryption, :compression,
|
6
|
+
:references, :auth_methods, :ssh_version
|
6
7
|
|
7
8
|
def initialize(opts = {})
|
8
9
|
@name = opts['name'] || []
|
@@ -6,7 +6,9 @@ module SSHScan
|
|
6
6
|
end
|
7
7
|
|
8
8
|
def out_of_policy_encryption
|
9
|
-
target_encryption =
|
9
|
+
target_encryption =
|
10
|
+
@result[:encryption_algorithms_client_to_server] |
|
11
|
+
@result[:encryption_algorithms_server_to_client]
|
10
12
|
outliers = []
|
11
13
|
target_encryption.each do |target_enc|
|
12
14
|
outliers << target_enc unless @policy.encryption.include?(target_enc)
|
@@ -15,7 +17,9 @@ module SSHScan
|
|
15
17
|
end
|
16
18
|
|
17
19
|
def missing_policy_encryption
|
18
|
-
target_encryption =
|
20
|
+
target_encryption =
|
21
|
+
@result[:encryption_algorithms_client_to_server] |
|
22
|
+
@result[:encryption_algorithms_server_to_client]
|
19
23
|
outliers = []
|
20
24
|
@policy.encryption.each do |encryption|
|
21
25
|
if target_encryption.include?(encryption) == false
|
@@ -26,7 +30,9 @@ module SSHScan
|
|
26
30
|
end
|
27
31
|
|
28
32
|
def out_of_policy_macs
|
29
|
-
target_macs =
|
33
|
+
target_macs =
|
34
|
+
@result[:mac_algorithms_server_to_client] |
|
35
|
+
@result[:mac_algorithms_client_to_server]
|
30
36
|
outliers = []
|
31
37
|
target_macs.each do |target_mac|
|
32
38
|
outliers << target_mac unless @policy.macs.include?(target_mac)
|
@@ -35,7 +41,9 @@ module SSHScan
|
|
35
41
|
end
|
36
42
|
|
37
43
|
def missing_policy_macs
|
38
|
-
target_macs =
|
44
|
+
target_macs =
|
45
|
+
@result[:mac_algorithms_server_to_client] |
|
46
|
+
@result[:mac_algorithms_client_to_server]
|
39
47
|
outliers = []
|
40
48
|
|
41
49
|
@policy.macs.each do |mac|
|
@@ -68,16 +76,21 @@ module SSHScan
|
|
68
76
|
end
|
69
77
|
|
70
78
|
def out_of_policy_compression
|
71
|
-
target_compressions =
|
79
|
+
target_compressions =
|
80
|
+
@result[:compression_algorithms_server_to_client] |
|
81
|
+
@result[:compression_algorithms_client_to_server]
|
72
82
|
outliers = []
|
73
83
|
target_compressions.each do |target_compression|
|
74
|
-
outliers << target_compression unless
|
84
|
+
outliers << target_compression unless
|
85
|
+
@policy.compression.include?(target_compression)
|
75
86
|
end
|
76
87
|
return outliers
|
77
88
|
end
|
78
89
|
|
79
90
|
def missing_policy_compression
|
80
|
-
target_compressions =
|
91
|
+
target_compressions =
|
92
|
+
@result[:compression_algorithms_server_to_client] |
|
93
|
+
@result[:compression_algorithms_client_to_server]
|
81
94
|
outliers = []
|
82
95
|
|
83
96
|
@policy.compression.each do |compression|
|
@@ -124,27 +137,63 @@ module SSHScan
|
|
124
137
|
missing_policy_kex.empty? &&
|
125
138
|
missing_policy_compression.empty? &&
|
126
139
|
out_of_policy_auth_methods.empty? &&
|
127
|
-
out_of_policy_ssh_version
|
140
|
+
!out_of_policy_ssh_version
|
128
141
|
end
|
129
142
|
|
130
143
|
def recommendations
|
131
144
|
recommendations = []
|
132
145
|
|
133
146
|
# Add these items to be compliant
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
147
|
+
if missing_policy_kex.any?
|
148
|
+
recommendations << "Add these Key Exchange Algos: \
|
149
|
+
#{missing_policy_kex.join(",")}"
|
150
|
+
end
|
151
|
+
|
152
|
+
if missing_policy_macs.any?
|
153
|
+
recommendations << "Add these MAC Algos: \
|
154
|
+
#{missing_policy_macs.join(",")}"
|
155
|
+
end
|
156
|
+
|
157
|
+
if missing_policy_encryption.any?
|
158
|
+
recommendations << "Add these Encryption Ciphers: \
|
159
|
+
#{missing_policy_encryption.join(",")}"
|
160
|
+
end
|
161
|
+
|
162
|
+
if missing_policy_compression.any?
|
163
|
+
recommendations << "Add these Compression Algos: \
|
164
|
+
#{missing_policy_compression.join(",")}"
|
165
|
+
end
|
138
166
|
|
139
167
|
# Remove these items to be compliant
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
168
|
+
if out_of_policy_kex.any?
|
169
|
+
recommendations << "Remove these Key Exchange Algos: \
|
170
|
+
#{out_of_policy_kex.join(", ")}"
|
171
|
+
end
|
172
|
+
|
173
|
+
if out_of_policy_macs.any?
|
174
|
+
recommendations << "Remove these MAC Algos: \
|
175
|
+
#{out_of_policy_macs.join(", ")}"
|
176
|
+
end
|
177
|
+
|
178
|
+
if out_of_policy_encryption.any?
|
179
|
+
recommendations << "Remove these Encryption Ciphers: \
|
180
|
+
#{out_of_policy_encryption.join(", ")}"
|
181
|
+
end
|
182
|
+
|
183
|
+
if out_of_policy_compression.any?
|
184
|
+
recommendations << "Remove these Compression Algos: \
|
185
|
+
#{out_of_policy_compression.join(", ")}"
|
186
|
+
end
|
187
|
+
|
188
|
+
if out_of_policy_auth_methods.any?
|
189
|
+
recommendations << "Remove these Authentication Methods: \
|
190
|
+
#{out_of_policy_auth_methods.join(", ")}"
|
191
|
+
end
|
145
192
|
|
146
193
|
# Update these items to be compliant
|
147
|
-
|
194
|
+
if out_of_policy_ssh_version
|
195
|
+
recommendations << "Update your ssh version to: #{@policy.ssh_version}"
|
196
|
+
end
|
148
197
|
|
149
198
|
return recommendations
|
150
199
|
end
|
data/lib/ssh_scan/protocol.rb
CHANGED
@@ -11,28 +11,50 @@ module SSHScan
|
|
11
11
|
uint8 :message_code, :initial_value => 20
|
12
12
|
string :cookie_string, :length => 16
|
13
13
|
uint32 :kex_algorithms_length
|
14
|
-
string :key_algorithms_string, :length => lambda {
|
14
|
+
string :key_algorithms_string, :length => lambda {
|
15
|
+
self.kex_algorithms_length
|
16
|
+
}
|
15
17
|
uint32 :server_host_key_algorithms_length
|
16
|
-
string :server_host_key_algorithms_string, :length => lambda {
|
18
|
+
string :server_host_key_algorithms_string, :length => lambda {
|
19
|
+
self.server_host_key_algorithms_length
|
20
|
+
}
|
17
21
|
uint32 :encryption_algorithms_client_to_server_length
|
18
|
-
string :encryption_algorithms_client_to_server_string, :length => lambda {
|
22
|
+
string :encryption_algorithms_client_to_server_string, :length => lambda {
|
23
|
+
self.encryption_algorithms_client_to_server_length
|
24
|
+
}
|
19
25
|
uint32 :encryption_algorithms_server_to_client_length
|
20
|
-
string :encryption_algorithms_server_to_client_string, :length => lambda {
|
26
|
+
string :encryption_algorithms_server_to_client_string, :length => lambda {
|
27
|
+
self.encryption_algorithms_server_to_client_length
|
28
|
+
}
|
21
29
|
uint32 :mac_algorithms_client_to_server_length
|
22
|
-
string :mac_algorithms_client_to_server_string, :length => lambda {
|
30
|
+
string :mac_algorithms_client_to_server_string, :length => lambda {
|
31
|
+
self.mac_algorithms_client_to_server_length
|
32
|
+
}
|
23
33
|
uint32 :mac_algorithms_server_to_client_length
|
24
|
-
string :mac_algorithms_server_to_client_string, :length => lambda {
|
34
|
+
string :mac_algorithms_server_to_client_string, :length => lambda {
|
35
|
+
self.mac_algorithms_server_to_client_length
|
36
|
+
}
|
25
37
|
uint32 :compression_algorithms_client_to_server_length
|
26
|
-
string :compression_algorithms_client_to_server_string, :length => lambda {
|
38
|
+
string :compression_algorithms_client_to_server_string, :length => lambda {
|
39
|
+
self.compression_algorithms_client_to_server_length
|
40
|
+
}
|
27
41
|
uint32 :compression_algorithms_server_to_client_length
|
28
|
-
string :compression_algorithms_server_to_client_string, :length => lambda {
|
42
|
+
string :compression_algorithms_server_to_client_string, :length => lambda {
|
43
|
+
self.compression_algorithms_server_to_client_length
|
44
|
+
}
|
29
45
|
uint32 :languages_client_to_server_length
|
30
|
-
string :languages_client_to_server_string, :length => lambda {
|
46
|
+
string :languages_client_to_server_string, :length => lambda {
|
47
|
+
self.languages_client_to_server_length
|
48
|
+
}
|
31
49
|
uint32 :languages_server_to_client_length
|
32
|
-
string :languages_server_to_client_string, :length => lambda {
|
50
|
+
string :languages_server_to_client_string, :length => lambda {
|
51
|
+
self.languages_server_to_client_length
|
52
|
+
}
|
33
53
|
uint8 :kex_first_packet_follows
|
34
54
|
uint32 :reserved
|
35
|
-
string :padding_string, :read_length => lambda {
|
55
|
+
string :padding_string, :read_length => lambda {
|
56
|
+
self.padding_length
|
57
|
+
}
|
36
58
|
|
37
59
|
def cookie
|
38
60
|
self.cookie_string
|
@@ -183,12 +205,16 @@ module SSHScan
|
|
183
205
|
:cookie => self.cookie.hexify,
|
184
206
|
:key_algorithms => key_algorithms,
|
185
207
|
:server_host_key_algorithms => server_host_key_algorithms,
|
186
|
-
:encryption_algorithms_client_to_server =>
|
187
|
-
|
208
|
+
:encryption_algorithms_client_to_server =>
|
209
|
+
encryption_algorithms_client_to_server,
|
210
|
+
:encryption_algorithms_server_to_client =>
|
211
|
+
encryption_algorithms_server_to_client,
|
188
212
|
:mac_algorithms_client_to_server => mac_algorithms_client_to_server,
|
189
213
|
:mac_algorithms_server_to_client => mac_algorithms_server_to_client,
|
190
|
-
:compression_algorithms_client_to_server =>
|
191
|
-
|
214
|
+
:compression_algorithms_client_to_server =>
|
215
|
+
compression_algorithms_client_to_server,
|
216
|
+
:compression_algorithms_server_to_client =>
|
217
|
+
compression_algorithms_server_to_client,
|
192
218
|
:languages_client_to_server => languages_client_to_server,
|
193
219
|
:languages_server_to_client => languages_server_to_client,
|
194
220
|
}
|
@@ -200,12 +226,18 @@ module SSHScan
|
|
200
226
|
kex_init.padding = opts[:padding]
|
201
227
|
kex_init.key_algorithms = opts[:key_algorithms]
|
202
228
|
kex_init.server_host_key_algorithms = opts[:server_host_key_algorithms]
|
203
|
-
kex_init.encryption_algorithms_client_to_server =
|
204
|
-
|
205
|
-
kex_init.
|
206
|
-
|
207
|
-
kex_init.
|
208
|
-
|
229
|
+
kex_init.encryption_algorithms_client_to_server =
|
230
|
+
opts[:encryption_algorithms_client_to_server]
|
231
|
+
kex_init.encryption_algorithms_server_to_client =
|
232
|
+
opts[:encryption_algorithms_server_to_client]
|
233
|
+
kex_init.mac_algorithms_client_to_server =
|
234
|
+
opts[:mac_algorithms_client_to_server]
|
235
|
+
kex_init.mac_algorithms_server_to_client =
|
236
|
+
opts[:mac_algorithms_server_to_client]
|
237
|
+
kex_init.compression_algorithms_client_to_server =
|
238
|
+
opts[:compression_algorithms_client_to_server]
|
239
|
+
kex_init.compression_algorithms_server_to_client =
|
240
|
+
opts[:compression_algorithms_server_to_client]
|
209
241
|
kex_init.languages_client_to_server = opts[:languages_client_to_server]
|
210
242
|
kex_init.languages_server_to_client = opts[:languages_server_to_client]
|
211
243
|
return kex_init
|
data/lib/ssh_scan/scan_engine.rb
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
require 'socket'
|
2
2
|
require 'ssh_scan/client'
|
3
3
|
require 'ssh_scan/crypto'
|
4
|
-
require 'ssh_scan/fingerprint_database'
|
4
|
+
#require 'ssh_scan/fingerprint_database'
|
5
5
|
require 'net/ssh'
|
6
|
+
require 'logger'
|
6
7
|
|
7
8
|
module SSHScan
|
8
9
|
class ScanEngine
|
@@ -12,24 +13,30 @@ module SSHScan
|
|
12
13
|
if port.nil?
|
13
14
|
port = 22
|
14
15
|
end
|
15
|
-
timeout = opts[
|
16
|
+
timeout = opts["timeout"]
|
16
17
|
result = []
|
17
18
|
|
18
19
|
start_time = Time.now
|
19
20
|
|
20
21
|
if target.fqdn?
|
21
22
|
if target.resolve_fqdn_as_ipv6.nil?
|
22
|
-
client = SSHScan::Client.new(
|
23
|
+
client = SSHScan::Client.new(
|
24
|
+
target.resolve_fqdn_as_ipv4.to_s, port, timeout
|
25
|
+
)
|
23
26
|
client.connect()
|
24
27
|
result = client.get_kex_result()
|
25
28
|
result[:hostname] = target
|
26
29
|
return result if result.include?(:error)
|
27
30
|
else
|
28
|
-
client = SSHScan::Client.new(
|
31
|
+
client = SSHScan::Client.new(
|
32
|
+
target.resolve_fqdn_as_ipv6.to_s, port, timeout
|
33
|
+
)
|
29
34
|
client.connect()
|
30
35
|
result = client.get_kex_result()
|
31
36
|
if result.include?(:error)
|
32
|
-
client = SSHScan::Client.new(
|
37
|
+
client = SSHScan::Client.new(
|
38
|
+
target.resolve_fqdn_as_ipv4.to_s, port, timeout
|
39
|
+
)
|
33
40
|
client.connect()
|
34
41
|
result = client.get_kex_result()
|
35
42
|
result[:hostname] = target
|
@@ -53,10 +60,11 @@ module SSHScan
|
|
53
60
|
:paranoid => Net::SSH::Verifiers::Null.new
|
54
61
|
)
|
55
62
|
raise SSHScan::Error::ClosedConnection.new if net_ssh_session.closed?
|
56
|
-
auth_session = Net::SSH::Authentication::Session.new(
|
63
|
+
auth_session = Net::SSH::Authentication::Session.new(
|
64
|
+
net_ssh_session, :auth_methods => ["none"]
|
65
|
+
)
|
57
66
|
auth_session.authenticate("none", "test", "test")
|
58
67
|
result['auth_methods'] = auth_session.allowed_auth_methods
|
59
|
-
host_key = net_ssh_session.host_keys.first
|
60
68
|
net_ssh_session.close
|
61
69
|
rescue Net::SSH::ConnectionTimeout => e
|
62
70
|
result[:error] = e
|
@@ -71,13 +79,32 @@ module SSHScan
|
|
71
79
|
raise e
|
72
80
|
end
|
73
81
|
else
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
82
|
+
result['fingerprints'] = {}
|
83
|
+
host_keys = `ssh-keyscan -t rsa,dsa #{target} 2>/dev/null`.split
|
84
|
+
host_keys_len = host_keys.length - 1
|
85
|
+
|
86
|
+
for i in 0..host_keys_len
|
87
|
+
if host_keys[i].eql? "ssh-dss"
|
88
|
+
pkey = SSHScan::Crypto::PublicKey.new(host_keys[i + 1])
|
89
|
+
result['fingerprints'].merge!({
|
90
|
+
"dsa" => {
|
91
|
+
"md5" => pkey.fingerprint_md5,
|
92
|
+
"sha1" => pkey.fingerprint_sha1,
|
93
|
+
"sha256" => pkey.fingerprint_sha256,
|
94
|
+
}
|
95
|
+
})
|
96
|
+
end
|
97
|
+
|
98
|
+
if host_keys[i].eql? "ssh-rsa"
|
99
|
+
pkey = SSHScan::Crypto::PublicKey.new(host_keys[i + 1])
|
100
|
+
result['fingerprints'].merge!({
|
101
|
+
"rsa" => {
|
102
|
+
"md5" => pkey.fingerprint_md5,
|
103
|
+
"sha1" => pkey.fingerprint_sha1,
|
104
|
+
"sha256" => pkey.fingerprint_sha256,
|
105
|
+
}
|
106
|
+
})
|
107
|
+
end
|
81
108
|
end
|
82
109
|
end
|
83
110
|
|
@@ -91,21 +118,20 @@ module SSHScan
|
|
91
118
|
end
|
92
119
|
|
93
120
|
def scan(opts)
|
94
|
-
sockets = opts[
|
95
|
-
threads = opts[
|
96
|
-
logger = opts[
|
121
|
+
sockets = opts["sockets"]
|
122
|
+
threads = opts["threads"] || 5
|
123
|
+
logger = opts["logger"] || Logger.new(STDOUT)
|
97
124
|
|
98
125
|
results = []
|
99
126
|
|
100
127
|
work_queue = Queue.new
|
128
|
+
|
101
129
|
sockets.each {|x| work_queue.push x }
|
102
|
-
workers = (0...threads).map do
|
130
|
+
workers = (0...threads).map do
|
103
131
|
Thread.new do
|
104
132
|
begin
|
105
133
|
while socket = work_queue.pop(true)
|
106
|
-
logger.info("Started ssh_scan of #{socket}")
|
107
134
|
results << scan_target(socket, opts)
|
108
|
-
logger.info("Completed ssh_scan of #{socket}")
|
109
135
|
end
|
110
136
|
rescue ThreadError => e
|
111
137
|
raise e unless e.to_s.match(/queue empty/)
|
@@ -115,35 +141,41 @@ module SSHScan
|
|
115
141
|
workers.map(&:join)
|
116
142
|
|
117
143
|
# Add all the fingerprints to our peristent FingerprintDatabase
|
118
|
-
fingerprint_db = SSHScan::FingerprintDatabase.new(
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
144
|
+
# fingerprint_db = SSHScan::FingerprintDatabase.new(
|
145
|
+
# opts[:fingerprint_database]
|
146
|
+
# )
|
147
|
+
# results.each do |result|
|
148
|
+
# fingerprint_db.clear_fingerprints(result[:ip])
|
149
|
+
# if result['fingerprints']
|
150
|
+
# result['fingerprints'].values.each do |host_key_algo|
|
151
|
+
# host_key_algo.values.each do |fingerprint|
|
152
|
+
# fingerprint_db.add_fingerprint(fingerprint, result[:ip])
|
153
|
+
# end
|
154
|
+
# end
|
155
|
+
# end
|
156
|
+
# end
|
127
157
|
|
128
158
|
# Decorate all the results with duplicate keys
|
129
|
-
results.each do |result|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
159
|
+
# results.each do |result|
|
160
|
+
# if result['fingerprints']
|
161
|
+
# ip = result[:ip]
|
162
|
+
# result['duplicate_host_key_ips'] = []
|
163
|
+
# result['fingerprints'].values.each do |host_key_algo|
|
164
|
+
# host_key_algo.values.each do |fingerprint|
|
165
|
+
# fingerprint_db.find_fingerprints(fingerprint).each do |other_ip|
|
166
|
+
# next if ip == other_ip
|
167
|
+
# result['duplicate_host_key_ips'] << other_ip
|
168
|
+
# end
|
169
|
+
# end
|
170
|
+
# end
|
171
|
+
# result['duplicate_host_key_ips'].uniq!
|
172
|
+
# end
|
173
|
+
# end
|
142
174
|
|
143
175
|
# Decorate all the results with compliance information
|
144
176
|
results.each do |result|
|
145
177
|
# Do this only when we have all the information we need
|
146
|
-
if !opts[
|
178
|
+
if !opts["policy"].nil? &&
|
147
179
|
!result[:key_algorithms].nil? &&
|
148
180
|
!result[:server_host_key_algorithms].nil? &&
|
149
181
|
!result[:encryption_algorithms_client_to_server].nil? &&
|
@@ -154,7 +186,9 @@ module SSHScan
|
|
154
186
|
!result[:compression_algorithms_server_to_client].nil? &&
|
155
187
|
!result[:languages_client_to_server].nil? &&
|
156
188
|
!result[:languages_server_to_client].nil?
|
157
|
-
|
189
|
+
|
190
|
+
policy = SSHScan::Policy.from_file(opts["policy"])
|
191
|
+
policy_mgr = SSHScan::PolicyManager.new(result, policy)
|
158
192
|
result['compliance'] = policy_mgr.compliance_results
|
159
193
|
end
|
160
194
|
end
|