ssh_scan 0.0.16 → 0.0.17.pre
Sign up to get free protection for your applications and to get access to all the features.
- 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
|