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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7ed1c774b96d1bb34de978e7c39e82d2206e340f
4
- data.tar.gz: 873692c2cd13f0aac4e1bc7842deb2cc6858af7e
3
+ metadata.gz: 504c40725362aee1beeb3795b3ea1fc7eb8af8a8
4
+ data.tar.gz: fcd896ac346c21474fab15b925562ec5a7c6c3e6
5
5
  SHA512:
6
- metadata.gz: 25227d81b0b50a3576b8be8266a76fb70e7cac3d72b5c0ac3a764e57b0532fa2541e025e2322139517a1e4661dc049c26af6b6629090402783cf58819a7dcf4d
7
- data.tar.gz: fb790b5ddccedcc27d781b9795b5c49b3ad51546fbb9e68b7d434239f5d58047d97e0e02a7c59a15a5f536b32aad7d92d8b981bf97b02e4886321f97566aa509
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:
@@ -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
@@ -3,6 +3,7 @@ require 'ssh_scan/banner'
3
3
  require 'ssh_scan/protocol'
4
4
 
5
5
  module SSHScan
6
+ # House all the constants we need.
6
7
  module Constants
7
8
  DEFAULT_CLIENT_BANNER = SSHScan::Banner.new("SSH-2.0-ssh_scan")
8
9
  DEFAULT_SERVER_BANNER = SSHScan::Banner.new("SSH-2.0-server")
@@ -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,5 +1,6 @@
1
1
  module SSHScan
2
2
  module Error
3
+ # Connection closed from the other side.
3
4
  class ClosedConnection < RuntimeError
4
5
  def to_s
5
6
  "#{self.class.to_s.split('::')[-1]}"
@@ -1,5 +1,6 @@
1
1
  module SSHScan
2
2
  module Error
3
+ # Timed out trying to connect.
3
4
  class ConnectTimeout < RuntimeError
4
5
  def initialize(message)
5
6
  @message = message
@@ -1,5 +1,6 @@
1
1
  module SSHScan
2
2
  module Error
3
+ # Target refused connection attempt.
3
4
  class ConnectionRefused < RuntimeError
4
5
  def initialize(message)
5
6
  @message = message
@@ -1,5 +1,6 @@
1
1
  module SSHScan
2
2
  module Error
3
+ # Got disconnected somehow.
3
4
  class Disconnected < RuntimeError
4
5
  def initialize(message)
5
6
  @message = message
@@ -1,5 +1,6 @@
1
1
  module SSHScan
2
2
  module Error
3
+ # Target did not respond with an SSH banner.
3
4
  class NoBanner < RuntimeError
4
5
  def initialize(message)
5
6
  @message = message
@@ -1,5 +1,6 @@
1
1
  module SSHScan
2
2
  module Error
3
+ # Failed to do key exchange.
3
4
  class NoKexResponse < RuntimeError
4
5
  def initialize(message)
5
6
  @message = message
@@ -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
 
@@ -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+)/)
@@ -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
@@ -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 Key Exchange Algos: \
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 Algos: \
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 Encryption Ciphers: \
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 Compression Algos: \
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 Key Exchange Algos: \
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 Algos: \
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 Encryption Ciphers: \
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 Compression Algos: \
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 Authentication Methods: \
190
+ recommendations << "Remove these authentication methods: \
190
191
  #{out_of_policy_auth_methods.join(", ")}"
191
192
  end
192
193
 
@@ -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
@@ -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?
@@ -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
@@ -1,3 +1,3 @@
1
1
  module SSHScan
2
- VERSION = '0.0.20'
2
+ VERSION = '0.0.21'
3
3
  end
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.20
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-12 00:00:00.000000000 Z
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: