ssh_scan 0.0.40 → 0.0.41
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CONTRIBUTING.md +3 -3
- data/bin/ssh_scan +19 -4
- data/lib/ssh_scan/public_key.rb +3 -2
- data/lib/ssh_scan/result.rb +11 -2
- data/lib/ssh_scan/scan_engine.rb +10 -0
- data/lib/ssh_scan/ssh_fp.rb +47 -0
- data/lib/ssh_scan/tests/test_dns_key_verification.rb +43 -0
- data/lib/ssh_scan/version.rb +1 -1
- data/lib/string_ext.rb +25 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 30ae6fc2118a4c57d91680ce36dfb5f05f04887b27e1c29c11e87a19440a220c
|
4
|
+
data.tar.gz: 8bb716eafcb9ddedd8141466463c12a1d18323b68a7bb01bc7c6edb02fe899ae
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b54db17fe59790ef8bec162fb4bb2949ab087ff7738217263a1454ec220fae500ccb21da7776407d8196dee9fa1a729c1cc472de8a200d64cb99e87e63cb0f93
|
7
|
+
data.tar.gz: f8516f26b7066e50d237bec19c56c4ae81f2a007c04d1a06af4e524f900807bb5e8924a7bcc58be5c9d6cea7435430ba7f13a244038ca24c3af6dac1116b8ff5
|
data/CONTRIBUTING.md
CHANGED
@@ -27,9 +27,9 @@ inclusion, but sending a pull request is much more awesome.
|
|
27
27
|
|
28
28
|
If you want your pull requests to be accepted, please follow the following guidelines:
|
29
29
|
|
30
|
-
- [**Add tests!**](
|
30
|
+
- [**Add tests!**](https://rspec.info/) Your patch won't be accepted (or will be delayed) if it doesn't have tests.
|
31
31
|
|
32
|
-
- [**Document any change in behaviour**](
|
32
|
+
- [**Document any change in behaviour**](https://yardoc.org/) Make sure the README and any other
|
33
33
|
relevant documentation are kept up-to-date.
|
34
34
|
|
35
35
|
- [**Create topic branches**](https://github.com/dchelimsky/rspec/wiki/Topic-Branches) Don't ask us to pull from your master branch.
|
@@ -37,7 +37,7 @@ If you want your pull requests to be accepted, please follow the following guide
|
|
37
37
|
- [**One pull request per feature**](https://help.github.com/articles/using-pull-requests) If you want to do more than one thing, send
|
38
38
|
multiple pull requests.
|
39
39
|
|
40
|
-
- [**Send coherent history**](
|
40
|
+
- [**Send coherent history**](https://stackoverflow.com/questions/6934752/git-combining-multiple-commits-before-pushing) Make sure each individual commit in your pull
|
41
41
|
request is meaningful. If you had to make multiple intermediate commits while
|
42
42
|
developing, please squash them before sending them to us.
|
43
43
|
|
data/bin/ssh_scan
CHANGED
@@ -20,7 +20,7 @@ options = {
|
|
20
20
|
"verbosity" => nil,
|
21
21
|
"logger" => Logger.new(STDERR),
|
22
22
|
"fingerprint_database" => ENV['HOME']+'/.ssh_scan_fingerprints.yml',
|
23
|
-
"output_type" =>
|
23
|
+
"output_type" => nil
|
24
24
|
}
|
25
25
|
|
26
26
|
# Reorder arguments before parsing
|
@@ -106,7 +106,8 @@ scan") do |file|
|
|
106
106
|
|
107
107
|
opts.on("-o", "--output [FilePath]",
|
108
108
|
"File to write JSON output to") do |file|
|
109
|
-
|
109
|
+
options["output"] = file
|
110
|
+
# $stdout.reopen(file, "w")
|
110
111
|
end
|
111
112
|
|
112
113
|
opts.on("--output-type [json, yaml]",
|
@@ -238,9 +239,23 @@ options["policy_file"] = SSHScan::Policy.from_file(options["policy"])
|
|
238
239
|
scan_engine = SSHScan::ScanEngine.new()
|
239
240
|
results = scan_engine.scan(options)
|
240
241
|
|
241
|
-
if options["output_type"] == "yaml"
|
242
|
+
if options["output_type"] == "yaml" && (options["output"].nil? || options["output"].empty?)
|
242
243
|
puts YAML.dump(results)
|
243
|
-
elsif options["output_type"] == "json"
|
244
|
+
elsif options["output_type"] == "json" && (options["output"].nil? || options["output"].empty?)
|
245
|
+
puts JSON.pretty_generate(results)
|
246
|
+
elsif (options["output_type"].nil? || options["output_type"].empty?) && (!options["output"].nil? && options["output"].split(".").last.downcase == "yml")
|
247
|
+
open(options["output"], 'w') do |f|
|
248
|
+
f.puts YAML.dump(results)
|
249
|
+
end
|
250
|
+
elsif (options["output_type"].nil? || options["output_type"].empty?) && (!options["output"].nil? && options["output"].split(".").last.downcase == "json")
|
251
|
+
open(options["output"], 'w') do |f|
|
252
|
+
f.puts JSON.pretty_generate(results)
|
253
|
+
end
|
254
|
+
elsif (options["output_type"].nil? || options["output_type"].empty?) && options["output"]
|
255
|
+
open(options["output"], 'w') do |f|
|
256
|
+
f.puts JSON.pretty_generate(results)
|
257
|
+
end
|
258
|
+
else
|
244
259
|
puts JSON.pretty_generate(results)
|
245
260
|
end
|
246
261
|
|
data/lib/ssh_scan/public_key.rb
CHANGED
@@ -39,10 +39,11 @@ module SSHScan
|
|
39
39
|
|
40
40
|
def fingerprint_sha1
|
41
41
|
SSHKey.sha1_fingerprint(@key_string)
|
42
|
-
end
|
42
|
+
end
|
43
43
|
|
44
44
|
def fingerprint_sha256
|
45
|
-
|
45
|
+
# We're translating this to hex because the SSHKEY default isn't as useful for comparing with SSHFP records
|
46
|
+
Base64.decode64(SSHKey.sha256_fingerprint(@key_string)).hexify(:delim => ":")
|
46
47
|
end
|
47
48
|
|
48
49
|
def to_hash
|
data/lib/ssh_scan/result.rb
CHANGED
@@ -157,12 +157,20 @@ module SSHScan
|
|
157
157
|
@auth_methods = auth_methods
|
158
158
|
end
|
159
159
|
|
160
|
+
def keys
|
161
|
+
@keys || {}
|
162
|
+
end
|
163
|
+
|
160
164
|
def keys=(keys)
|
161
165
|
@keys = keys
|
162
166
|
end
|
163
167
|
|
164
|
-
def
|
165
|
-
@
|
168
|
+
def dns_keys
|
169
|
+
@dns_keys
|
170
|
+
end
|
171
|
+
|
172
|
+
def dns_keys=(dns_keys)
|
173
|
+
@dns_keys = dns_keys
|
166
174
|
end
|
167
175
|
|
168
176
|
def duplicate_host_key_ips=(duplicate_host_key_ips)
|
@@ -250,6 +258,7 @@ module SSHScan
|
|
250
258
|
"languages_server_to_client" => self.languages_server_to_client,
|
251
259
|
"auth_methods" => self.auth_methods,
|
252
260
|
"keys" => self.keys,
|
261
|
+
"dns_keys" => self.dns_keys,
|
253
262
|
"duplicate_host_key_ips" => self.duplicate_host_key_ips.uniq,
|
254
263
|
"compliance" => @compliance,
|
255
264
|
"start_time" => self.start_time,
|
data/lib/ssh_scan/scan_engine.rb
CHANGED
@@ -3,6 +3,7 @@ require 'ssh_scan/client'
|
|
3
3
|
require 'ssh_scan/public_key'
|
4
4
|
require 'ssh_scan/fingerprint_database'
|
5
5
|
require 'ssh_scan/subprocess'
|
6
|
+
require 'ssh_scan/ssh_fp'
|
6
7
|
require 'net/ssh'
|
7
8
|
require 'logger'
|
8
9
|
require 'open3'
|
@@ -221,6 +222,15 @@ module SSHScan
|
|
221
222
|
end
|
222
223
|
end
|
223
224
|
|
225
|
+
# Decorate all the results with SSHFP records
|
226
|
+
sshfp = SSHScan::SshFp.new()
|
227
|
+
results.each do |result|
|
228
|
+
if !result.hostname.empty?
|
229
|
+
dns_keys = sshfp.query(result.hostname)
|
230
|
+
result.dns_keys = dns_keys
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
224
234
|
# Decorate all the results with compliance information
|
225
235
|
results.each do |result|
|
226
236
|
# Do this only when we have all the information we need
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'resolv'
|
2
|
+
|
3
|
+
module SSHScan
|
4
|
+
class SshFp
|
5
|
+
|
6
|
+
ALGO_MAP = {
|
7
|
+
0 => "reserved", # Reference: https://tools.ietf.org/html/rfc4255#section-2.4
|
8
|
+
1 => "rsa", # Reference: https://tools.ietf.org/html/rfc4255#section-2.4
|
9
|
+
2 => "dss", # Reference: https://tools.ietf.org/html/rfc4255#section-2.4
|
10
|
+
3 => "ecdsa", # Reference: https://tools.ietf.org/html/rfc6594#section-5.3.1
|
11
|
+
4 => "ed25519" # Reference: https://tools.ietf.org/html/rfc7479
|
12
|
+
}
|
13
|
+
|
14
|
+
|
15
|
+
FPTYPE_MAP = {
|
16
|
+
0 => "reserved", # Reference: https://tools.ietf.org/html/rfc4255#section-2.4
|
17
|
+
1 => "sha1", # Reference: https://tools.ietf.org/html/rfc4255#section-2.4
|
18
|
+
2 => "sha256" # Reference: https://tools.ietf.org/html/rfc6594#section-5.1.2
|
19
|
+
}
|
20
|
+
|
21
|
+
|
22
|
+
def query(fqdn)
|
23
|
+
sshfp_records = []
|
24
|
+
|
25
|
+
# Reference: https://stackoverflow.com/questions/28867626/how-to-use-resolvdnsresourcegeneric
|
26
|
+
# Note: this includes some fixes too, I'll post a direct link back to the SO article.
|
27
|
+
Resolv::DNS.open do |dns|
|
28
|
+
all_records = dns.getresources(fqdn, Resolv::DNS::Resource::IN::ANY ) rescue nil
|
29
|
+
all_records.each do |rr|
|
30
|
+
if rr.is_a? Resolv::DNS::Resource::Generic then
|
31
|
+
classname = rr.class.name.split('::').last
|
32
|
+
if classname == "Type44_Class1"
|
33
|
+
data = rr.data.bytes
|
34
|
+
algo = data[0].to_s
|
35
|
+
fptype = data[1].to_s
|
36
|
+
fp = data[2..-1]
|
37
|
+
hex = fp.map{|b| b.to_s(16).rjust(2,'0') }.join(':')
|
38
|
+
sshfp_records << {"fptype" => FPTYPE_MAP[fptype.to_i], "algo" => ALGO_MAP[algo.to_i], "hex" => hex}
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
return sshfp_records.sort_by { |k| k["hex"] }
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module SSHScan
|
2
|
+
module Tests
|
3
|
+
class DnsKeyVerification
|
4
|
+
def initialize(result)
|
5
|
+
@result = result
|
6
|
+
end
|
7
|
+
|
8
|
+
def pass?
|
9
|
+
@result.keys.each do |key,value|
|
10
|
+
valid = false
|
11
|
+
|
12
|
+
@result.dns_keys.each do |dns_key|
|
13
|
+
if key == dns_key["algo"] &&
|
14
|
+
value["fingerprints"].values.include?(dns_key["hex"])
|
15
|
+
valid = true
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# This means we fail any key that's offered that's not verifiable via information from DNS
|
20
|
+
return false unless valid == true
|
21
|
+
end
|
22
|
+
|
23
|
+
return true
|
24
|
+
end
|
25
|
+
|
26
|
+
def fail_description
|
27
|
+
if pass?
|
28
|
+
""
|
29
|
+
else
|
30
|
+
"One or more of the keys offered by the SSH service were not able to be verified using an SSHFS record"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def score_deduction
|
35
|
+
if pass?
|
36
|
+
0
|
37
|
+
else
|
38
|
+
-5
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/lib/ssh_scan/version.rb
CHANGED
data/lib/string_ext.rb
CHANGED
@@ -69,6 +69,31 @@ class String
|
|
69
69
|
end
|
70
70
|
end
|
71
71
|
|
72
|
+
# Stolen from: https://github.com/emonti/rbkb/blob/master/lib/rbkb/extends/string.rb
|
73
|
+
def hexify(opts={})
|
74
|
+
delim = opts[:delim]
|
75
|
+
pre = (opts[:prefix] || "")
|
76
|
+
suf = (opts[:suffix] || "")
|
77
|
+
|
78
|
+
if (rx=opts[:rx]) and not rx.kind_of? Regexp
|
79
|
+
raise "rx must be a regular expression for a character class"
|
80
|
+
end
|
81
|
+
|
82
|
+
hx = [("0".."9").to_a, ("a".."f").to_a].flatten
|
83
|
+
|
84
|
+
out=Array.new
|
85
|
+
|
86
|
+
self.each_byte do |c|
|
87
|
+
hc = if (rx and not rx.match c.chr)
|
88
|
+
c.chr
|
89
|
+
else
|
90
|
+
pre + (hx[(c >> 4)] + hx[(c & 0xf )]) + suf
|
91
|
+
end
|
92
|
+
out << (hc)
|
93
|
+
end
|
94
|
+
out.join(delim)
|
95
|
+
end
|
96
|
+
|
72
97
|
def fqdn?
|
73
98
|
begin
|
74
99
|
resolve_fqdn
|
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.41
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jonathan Claudius
|
@@ -12,7 +12,7 @@ authors:
|
|
12
12
|
autorequire:
|
13
13
|
bindir: bin
|
14
14
|
cert_chain: []
|
15
|
-
date: 2019-
|
15
|
+
date: 2019-05-03 00:00:00.000000000 Z
|
16
16
|
dependencies:
|
17
17
|
- !ruby/object:Gem::Dependency
|
18
18
|
name: bindata
|
@@ -190,6 +190,7 @@ files:
|
|
190
190
|
- lib/ssh_scan/public_key.rb
|
191
191
|
- lib/ssh_scan/result.rb
|
192
192
|
- lib/ssh_scan/scan_engine.rb
|
193
|
+
- lib/ssh_scan/ssh_fp.rb
|
193
194
|
- lib/ssh_scan/ssh_lib.rb
|
194
195
|
- lib/ssh_scan/ssh_lib/ciscossh.rb
|
195
196
|
- lib/ssh_scan/ssh_lib/cryptlib.rb
|
@@ -208,6 +209,7 @@ files:
|
|
208
209
|
- lib/ssh_scan/ssh_lib/unknown.rb
|
209
210
|
- lib/ssh_scan/subprocess.rb
|
210
211
|
- lib/ssh_scan/target_parser.rb
|
212
|
+
- lib/ssh_scan/tests/test_dns_key_verification.rb
|
211
213
|
- lib/ssh_scan/update.rb
|
212
214
|
- lib/ssh_scan/version.rb
|
213
215
|
- lib/string_ext.rb
|