ssh_scan 0.0.20 → 0.0.21
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/README.md +6 -0
- data/lib/ssh_scan/banner.rb +17 -0
- data/lib/ssh_scan/constants.rb +1 -0
- data/lib/ssh_scan/crypto.rb +11 -0
- data/lib/ssh_scan/error/closed_connection.rb +1 -0
- data/lib/ssh_scan/error/connect_timeout.rb +1 -0
- data/lib/ssh_scan/error/connection_refused.rb +1 -0
- data/lib/ssh_scan/error/disconnected.rb +1 -0
- data/lib/ssh_scan/error/no_banner.rb +1 -0
- data/lib/ssh_scan/error/no_kex_response.rb +1 -0
- data/lib/ssh_scan/fingerprint_database.rb +12 -0
- data/lib/ssh_scan/os/raspbian.rb +5 -0
- data/lib/ssh_scan/os/ubuntu.rb +4 -0
- data/lib/ssh_scan/policy.rb +10 -0
- data/lib/ssh_scan/policy_manager.rb +10 -9
- data/lib/ssh_scan/protocol.rb +6 -0
- data/lib/ssh_scan/scan_engine.rb +9 -0
- data/lib/ssh_scan/target_parser.rb +5 -0
- data/lib/ssh_scan/update.rb +9 -0
- data/lib/ssh_scan/version.rb +1 -1
- data/ssh_scan.gemspec +1 -0
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 504c40725362aee1beeb3795b3ea1fc7eb8af8a8
|
4
|
+
data.tar.gz: fcd896ac346c21474fab15b925562ec5a7c6c3e6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ed59a66f3da720810d4834c4128c1e3b5effd68e572d46664a5d4151f89d5cc95f9a0094cdc69409d5413b1f643362fcf23289a1a9cfd4fc7f2857ffd31d199e
|
7
|
+
data.tar.gz: e7a761c9eb05247ec41d1fa8acb0b11d7265636934c7b5d76a940d7541212fe6a3675686ee4e8055d8f9fe3fe91d0e080ddd3e7c6cdc444ae7c589f987937879
|
data/README.md
CHANGED
@@ -99,6 +99,12 @@ Examples:
|
|
99
99
|
- See here for [example output](https://github.com/mozilla/ssh_scan/blob/master/examples/192.168.1.1.json)
|
100
100
|
- See here for [example policies](https://github.com/mozilla/ssh_scan/blob/master/config/policies)
|
101
101
|
|
102
|
+
## ssh_scan as a service/api?
|
103
|
+
|
104
|
+
This project is soley for ssh_scan engine/command-line usage.
|
105
|
+
|
106
|
+
If you would like to run ssh_scan as a service, please refer to [the ssh_scan_api project](https://github.com/mozilla/ssh_scan_api)
|
107
|
+
|
102
108
|
## Rubies Supported
|
103
109
|
|
104
110
|
This project is integrated with [travis-ci](http://about.travis-ci.org/) and is regularly tested to work with the following rubies:
|
data/lib/ssh_scan/banner.rb
CHANGED
@@ -7,10 +7,19 @@ module SSHScan
|
|
7
7
|
@string = string
|
8
8
|
end
|
9
9
|
|
10
|
+
# Create {SSHScan::Banner} object based on target's SSH banner.
|
11
|
+
# @param string [String] String from which the banner should be
|
12
|
+
# constructed.
|
13
|
+
# @return [SSHScan::Banner] {SSHScan::Banner} object
|
14
|
+
# constructed from string.
|
10
15
|
def self.read(string)
|
11
16
|
return SSHScan::Banner.new(string)
|
12
17
|
end
|
13
18
|
|
19
|
+
# Guess target's SSH version.
|
20
|
+
# @return [String] If SSH version string looks like "SSH-1.81"
|
21
|
+
# or "SSH-number" then return the number, else return
|
22
|
+
# "unknown"
|
14
23
|
def ssh_version()
|
15
24
|
if version = @string.match(/SSH-(\d+[\.\d+]+)/)[1]
|
16
25
|
return version.to_f
|
@@ -19,6 +28,10 @@ module SSHScan
|
|
19
28
|
end
|
20
29
|
end
|
21
30
|
|
31
|
+
# Guess target's SSH Library (OpenSSH, LibSSH ...).
|
32
|
+
# See {SSHScan::SSHLib} for a list of SSH libraries supported.
|
33
|
+
# @return [SSHScan::SSHLib] Guessed {SSHScan::SSHLib} instance,
|
34
|
+
# otherwise {SSHScan::SSHLib::Unknown} instance.
|
22
35
|
def ssh_lib_guess()
|
23
36
|
case @string
|
24
37
|
when /OpenSSH/i
|
@@ -54,6 +67,10 @@ module SSHScan
|
|
54
67
|
end
|
55
68
|
end
|
56
69
|
|
70
|
+
# Guess target's OS (Ubuntu, CentOS ...).
|
71
|
+
# See {SSHScan::OS} for a list of OS(s) supported.
|
72
|
+
# @return [SSHScan::OS] Guessed {SSHScan::OS} instance,
|
73
|
+
# otherwise {SSHScan::OS::Unknown} instance.
|
57
74
|
def os_guess()
|
58
75
|
case @string
|
59
76
|
when /Ubuntu/i
|
data/lib/ssh_scan/constants.rb
CHANGED
data/lib/ssh_scan/crypto.rb
CHANGED
@@ -3,13 +3,18 @@ require 'sshkey'
|
|
3
3
|
require 'base64'
|
4
4
|
|
5
5
|
module SSHScan
|
6
|
+
# All cryptography related methods.
|
6
7
|
module Crypto
|
8
|
+
# House methods helpful in analysing SSH public keys.
|
7
9
|
class PublicKey
|
8
10
|
def initialize(key)
|
9
11
|
@key = key
|
10
12
|
end
|
11
13
|
|
12
14
|
# Is the current key known to be in our known bad key list
|
15
|
+
# @return [Boolean] true if this {SSHScan::Crypto::PublicKey}
|
16
|
+
# instance's key is also in {SSHScan::Crypto}'s
|
17
|
+
# bad_public_keys, otherwise false
|
13
18
|
def bad_key?
|
14
19
|
SSHScan::Crypto.bad_public_keys.each do |other_key|
|
15
20
|
if self.fingerprint_sha256 == other_key.fingerprint_sha256
|
@@ -20,14 +25,20 @@ module SSHScan
|
|
20
25
|
return false
|
21
26
|
end
|
22
27
|
|
28
|
+
# Generate MD5 fingerprint for this {SSHScan::Crypto::PublicKey} instance.
|
29
|
+
# @return [String] formatted MD5 fingerprint
|
23
30
|
def fingerprint_md5
|
24
31
|
OpenSSL::Digest::MD5.hexdigest(::Base64.decode64(@key)).scan(/../).join(':')
|
25
32
|
end
|
26
33
|
|
34
|
+
# Generate SHA1 fingerprint for this {SSHScan::Crypto::PublicKey} instance.
|
35
|
+
# @return [String] formatted SHA1 fingerprint
|
27
36
|
def fingerprint_sha1
|
28
37
|
OpenSSL::Digest::SHA1.hexdigest(::Base64.decode64(@key)).scan(/../).join(':')
|
29
38
|
end
|
30
39
|
|
40
|
+
# Generate SHA256 fingerprint for this {SSHScan::Crypto::PublicKey} instance.
|
41
|
+
# @return [String] formatted SHA256 fingerprint
|
31
42
|
def fingerprint_sha256
|
32
43
|
OpenSSL::Digest::SHA256.hexdigest(::Base64.decode64(@key)).scan(/../).join(':')
|
33
44
|
end
|
@@ -1,17 +1,24 @@
|
|
1
1
|
require 'yaml/store'
|
2
2
|
|
3
3
|
module SSHScan
|
4
|
+
# Create and/or maintain a fingerprint database using YAML Store.
|
4
5
|
class FingerprintDatabase
|
5
6
|
def initialize(database_name)
|
6
7
|
@store = YAML::Store.new(database_name)
|
7
8
|
end
|
8
9
|
|
10
|
+
# Empty the fingerprints database for given IP.
|
11
|
+
# @param ip [String] IP for which fingerprints should be
|
12
|
+
# cleared.
|
9
13
|
def clear_fingerprints(ip)
|
10
14
|
@store.transaction do
|
11
15
|
@store[ip] = []
|
12
16
|
end
|
13
17
|
end
|
14
18
|
|
19
|
+
# Insert a (fingerprint, IP) record.
|
20
|
+
# @param fingerprint [String] fingerprint to insert
|
21
|
+
# @param ip [String] IP for which fingerprint has to be added
|
15
22
|
def add_fingerprint(fingerprint, ip)
|
16
23
|
@store.transaction do
|
17
24
|
@store[ip] = [] if @store[ip].nil?
|
@@ -19,6 +26,11 @@ module SSHScan
|
|
19
26
|
end
|
20
27
|
end
|
21
28
|
|
29
|
+
# Find IPs that have the given fingerprint.
|
30
|
+
# @param fingerprint [String] fingerprint for which search
|
31
|
+
# should be performed
|
32
|
+
# @return [Array<String>] return unique IPs for which the given
|
33
|
+
# fingerprint has an entry
|
22
34
|
def find_fingerprints(fingerprint)
|
23
35
|
ip_matches = []
|
24
36
|
|
data/lib/ssh_scan/os/raspbian.rb
CHANGED
@@ -18,6 +18,11 @@ module SSHScan
|
|
18
18
|
@version = Raspbian::Version.new(raspbian_version_guess)
|
19
19
|
end
|
20
20
|
|
21
|
+
# Guess Raspbian OS version. Typically, Raspbian banners
|
22
|
+
# are like "SSH-2.0-Raspbian-something", where something
|
23
|
+
# is the Raspbian version.
|
24
|
+
# @return [String] version string matched from banner, nil
|
25
|
+
# if not matched
|
21
26
|
def raspbian_version_guess
|
22
27
|
return nil if @banner.nil?
|
23
28
|
match = @banner.match(/SSH-2.0-Raspbian-(\d+)/)
|
data/lib/ssh_scan/os/ubuntu.rb
CHANGED
@@ -217,6 +217,10 @@ module SSHScan
|
|
217
217
|
"ubuntu"
|
218
218
|
end
|
219
219
|
|
220
|
+
# Get the FINGERPRINTS constant hash, generated from the
|
221
|
+
# scraping script.
|
222
|
+
# @return [Hash<String, Array<String>>] FINGERPRINTS constant
|
223
|
+
# hash
|
220
224
|
def fingerprints
|
221
225
|
OS::Ubuntu::FINGERPRINTS
|
222
226
|
end
|
data/lib/ssh_scan/policy.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
require 'yaml'
|
2
2
|
|
3
3
|
module SSHScan
|
4
|
+
# Policy methods that deal with key exchange, macs, encryption methods,
|
5
|
+
# compression methods and more.
|
4
6
|
class Policy
|
5
7
|
attr_reader :name, :kex, :macs, :encryption, :compression,
|
6
8
|
:references, :auth_methods, :ssh_version
|
@@ -16,11 +18,19 @@ module SSHScan
|
|
16
18
|
@ssh_version = opts['ssh_version'] || false
|
17
19
|
end
|
18
20
|
|
21
|
+
# Generate a {SSHScan::Policy} object from YAML file.
|
22
|
+
# @param file [String] filepath
|
23
|
+
# @return [SSHScan::Policy] new instance with parameters loaded
|
24
|
+
# from YAML file
|
19
25
|
def self.from_file(file)
|
20
26
|
opts = YAML.load_file(file)
|
21
27
|
self.new(opts)
|
22
28
|
end
|
23
29
|
|
30
|
+
# Generate a {SSHScan::Policy} object from YAML string.
|
31
|
+
# @param string [String] YAML string
|
32
|
+
# @return [SSHScan::Policy] new instance with parameters loaded
|
33
|
+
# from given string
|
24
34
|
def self.from_string(string)
|
25
35
|
opts = YAML.load(string)
|
26
36
|
self.new(opts)
|
@@ -1,4 +1,5 @@
|
|
1
1
|
module SSHScan
|
2
|
+
# Policy management methods, compliance checking and recommendations.
|
2
3
|
class PolicyManager
|
3
4
|
def initialize(result, policy)
|
4
5
|
@policy = policy
|
@@ -145,48 +146,48 @@ module SSHScan
|
|
145
146
|
|
146
147
|
# Add these items to be compliant
|
147
148
|
if missing_policy_kex.any?
|
148
|
-
recommendations << "Add these
|
149
|
+
recommendations << "Add these key exchange algorithms: \
|
149
150
|
#{missing_policy_kex.join(",")}"
|
150
151
|
end
|
151
152
|
|
152
153
|
if missing_policy_macs.any?
|
153
|
-
recommendations << "Add these MAC
|
154
|
+
recommendations << "Add these MAC algorithms: \
|
154
155
|
#{missing_policy_macs.join(",")}"
|
155
156
|
end
|
156
157
|
|
157
158
|
if missing_policy_encryption.any?
|
158
|
-
recommendations << "Add these
|
159
|
+
recommendations << "Add these encryption ciphers: \
|
159
160
|
#{missing_policy_encryption.join(",")}"
|
160
161
|
end
|
161
162
|
|
162
163
|
if missing_policy_compression.any?
|
163
|
-
recommendations << "Add these
|
164
|
+
recommendations << "Add these compression algorithms: \
|
164
165
|
#{missing_policy_compression.join(",")}"
|
165
166
|
end
|
166
167
|
|
167
168
|
# Remove these items to be compliant
|
168
169
|
if out_of_policy_kex.any?
|
169
|
-
recommendations << "Remove these
|
170
|
+
recommendations << "Remove these key exchange algorithms: \
|
170
171
|
#{out_of_policy_kex.join(", ")}"
|
171
172
|
end
|
172
173
|
|
173
174
|
if out_of_policy_macs.any?
|
174
|
-
recommendations << "Remove these MAC
|
175
|
+
recommendations << "Remove these MAC algorithms: \
|
175
176
|
#{out_of_policy_macs.join(", ")}"
|
176
177
|
end
|
177
178
|
|
178
179
|
if out_of_policy_encryption.any?
|
179
|
-
recommendations << "Remove these
|
180
|
+
recommendations << "Remove these encryption ciphers: \
|
180
181
|
#{out_of_policy_encryption.join(", ")}"
|
181
182
|
end
|
182
183
|
|
183
184
|
if out_of_policy_compression.any?
|
184
|
-
recommendations << "Remove these
|
185
|
+
recommendations << "Remove these compression algorithms: \
|
185
186
|
#{out_of_policy_compression.join(", ")}"
|
186
187
|
end
|
187
188
|
|
188
189
|
if out_of_policy_auth_methods.any?
|
189
|
-
recommendations << "Remove these
|
190
|
+
recommendations << "Remove these authentication methods: \
|
190
191
|
#{out_of_policy_auth_methods.join(", ")}"
|
191
192
|
end
|
192
193
|
|
data/lib/ssh_scan/protocol.rb
CHANGED
@@ -200,6 +200,7 @@ module SSHScan
|
|
200
200
|
end
|
201
201
|
|
202
202
|
# Summarize as Hash
|
203
|
+
# @return [Hash] summary
|
203
204
|
def to_hash
|
204
205
|
{
|
205
206
|
:cookie => self.cookie.hexify,
|
@@ -220,6 +221,10 @@ module SSHScan
|
|
220
221
|
}
|
221
222
|
end
|
222
223
|
|
224
|
+
# Generate a {SSHScan::KeyExchangeInit} object from given Ruby hash.
|
225
|
+
# @param opts [Hash] options
|
226
|
+
# @return [SSHScan::KeyExchangeInit] {SSHScan::KeyExchangeInit}
|
227
|
+
# instance initialized using passed opts
|
223
228
|
def self.from_hash(opts)
|
224
229
|
kex_init = SSHScan::KeyExchangeInit.new()
|
225
230
|
kex_init.cookie = opts[:cookie]
|
@@ -244,6 +249,7 @@ module SSHScan
|
|
244
249
|
end
|
245
250
|
|
246
251
|
# Summarize as JSON
|
252
|
+
# @return [String] JSON representation for summary hash
|
247
253
|
def to_json
|
248
254
|
self.to_hash.to_json
|
249
255
|
end
|
data/lib/ssh_scan/scan_engine.rb
CHANGED
@@ -6,8 +6,13 @@ require 'net/ssh'
|
|
6
6
|
require 'logger'
|
7
7
|
|
8
8
|
module SSHScan
|
9
|
+
# Handle scanning of targets.
|
9
10
|
class ScanEngine
|
10
11
|
|
12
|
+
# Scan a single target.
|
13
|
+
# @param socket [String] ip:port specification
|
14
|
+
# @param opts [Hash] options (timeout, ...)
|
15
|
+
# @return [Hash] result
|
11
16
|
def scan_target(socket, opts)
|
12
17
|
target, port = socket.chomp.split(':')
|
13
18
|
if port.nil?
|
@@ -119,6 +124,10 @@ module SSHScan
|
|
119
124
|
return result
|
120
125
|
end
|
121
126
|
|
127
|
+
# Utilize multiple threads to scan multiple targets, combine
|
128
|
+
# results and check for compliance.
|
129
|
+
# @param opts [Hash] options (sockets, threads ...)
|
130
|
+
# @return [Hash] results
|
122
131
|
def scan(opts)
|
123
132
|
sockets = opts["sockets"]
|
124
133
|
threads = opts["threads"] || 5
|
@@ -2,7 +2,12 @@ require 'netaddr'
|
|
2
2
|
require 'string_ext'
|
3
3
|
|
4
4
|
module SSHScan
|
5
|
+
# Enumeration methods for IP notations.
|
5
6
|
class TargetParser
|
7
|
+
# Enumerate CIDR addresses, single IPs and IP ranges.
|
8
|
+
# @param ip [String] IP address
|
9
|
+
# @param port [Fixnum] port
|
10
|
+
# @return [Array] array of enumerated addresses
|
6
11
|
def enumerateIPRange(ip,port)
|
7
12
|
if ip.fqdn?
|
8
13
|
if port.nil?
|
data/lib/ssh_scan/update.rb
CHANGED
@@ -4,6 +4,7 @@ require 'ssh_scan/version'
|
|
4
4
|
require 'net/http'
|
5
5
|
|
6
6
|
module SSHScan
|
7
|
+
# Handle {SSHScan} updates.
|
7
8
|
class Update
|
8
9
|
def initialize
|
9
10
|
@errors = []
|
@@ -33,6 +34,9 @@ module SSHScan
|
|
33
34
|
return [major_num.to_s, "0", "0"].join(".")
|
34
35
|
end
|
35
36
|
|
37
|
+
# Returns true if the given gem version exists.
|
38
|
+
# @param version [String] version string
|
39
|
+
# @return [Boolean] true if given gem exists, else false
|
36
40
|
def gem_exists?(version = SSHScan::VERSION)
|
37
41
|
uri = URI("https://rubygems.org/gems/ssh_scan/versions/#{version}")
|
38
42
|
|
@@ -54,6 +58,11 @@ module SSHScan
|
|
54
58
|
@errors.uniq
|
55
59
|
end
|
56
60
|
|
61
|
+
# Tries to check if the next patch, minor or major version
|
62
|
+
# is available or not. If so, returns true.
|
63
|
+
# @param version [String] version string
|
64
|
+
# @return [Boolean] true if next major/minor version available,
|
65
|
+
# else false
|
57
66
|
def newer_gem_available?(version = SSHScan::VERSION)
|
58
67
|
if gem_exists?(next_patch_version(version))
|
59
68
|
return true
|
data/lib/ssh_scan/version.rb
CHANGED
data/ssh_scan.gemspec
CHANGED
@@ -28,6 +28,7 @@ Gem::Specification.new do |s|
|
|
28
28
|
s.summary = 'Ruby-based SSH Scanner'
|
29
29
|
s.description = 'A Ruby-based SSH scanner for configuration and policy scanning'
|
30
30
|
s.homepage = 'http://rubygems.org/gems/ssh_scan'
|
31
|
+
s.metadata["yard.run"] = "yri" # use "yard" to build full HTML docs
|
31
32
|
|
32
33
|
s.add_dependency('bindata', '~> 2.0')
|
33
34
|
s.add_dependency('netaddr')
|
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.21
|
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: 2017-05-
|
15
|
+
date: 2017-05-25 00:00:00.000000000 Z
|
16
16
|
dependencies:
|
17
17
|
- !ruby/object:Gem::Dependency
|
18
18
|
name: bindata
|
@@ -391,7 +391,8 @@ files:
|
|
391
391
|
homepage: http://rubygems.org/gems/ssh_scan
|
392
392
|
licenses:
|
393
393
|
- ruby
|
394
|
-
metadata:
|
394
|
+
metadata:
|
395
|
+
yard.run: yri
|
395
396
|
post_install_message:
|
396
397
|
rdoc_options: []
|
397
398
|
require_paths:
|