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