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 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: