ssh_scan 0.0.8 → 0.0.9.beta.1
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/.travis.yml +2 -0
- data/README.md +28 -16
- data/bin/ssh_scan +39 -15
- data/lib/ssh_scan/banner.rb +5 -1
- data/lib/ssh_scan/os/redhat.rb +13 -0
- data/lib/ssh_scan/os/ubuntu.rb +1 -1
- data/lib/ssh_scan/os/windows.rb +13 -0
- data/lib/ssh_scan/os.rb +2 -0
- data/lib/ssh_scan/policy_manager.rb +4 -4
- data/lib/ssh_scan/scan_engine.rb +45 -38
- data/lib/ssh_scan/ssh_lib/openssh.rb +22 -1
- data/lib/ssh_scan/version.rb +1 -1
- data/ssh_scan.gemspec +1 -0
- metadata +20 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c1c87c8107e90d90c415e7bc82fa379a8b0f53b4
|
4
|
+
data.tar.gz: fe3e44a14d6eb1f93a8a86423150520c187dfb70
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c0fe202b82f180482f8c56690b89f26a99966a8c1de2f454e29c5d4bc8490a796fdd5ae88f36a74eed42cb1c438fa5c579662571042b9e3e35157e2c4fcd171d
|
7
|
+
data.tar.gz: 3efc0086e634f5c2119e5de4e4b118844c0c62ba2eb3fbe912e48c51a64c44c5812cb7763e18f59817e1bb1721cb6b647819f733359aa250c18726be3f05573a
|
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -8,26 +8,41 @@ A SSH configuration and policy scanner
|
|
8
8
|
|
9
9
|
## Key Benefits
|
10
10
|
|
11
|
-
- **Minimal Dependancies** - Uses native Ruby and BinData to do
|
11
|
+
- **Minimal Dependancies** - Uses native Ruby and BinData to do its work, no heavy dependancies.
|
12
12
|
- **Not Just a Script** - Implementation is portable for use in another project or for automation of tasks.
|
13
|
-
- **Simple** - Just point ssh_scan at an SSH service and get a JSON report of what
|
13
|
+
- **Simple** - Just point `ssh_scan` at an SSH service and get a JSON report of what it supports and its policy status.
|
14
14
|
- **Configurable** - Make your own custom policies that fit your unique policy requirements.
|
15
15
|
|
16
16
|
## Setup
|
17
17
|
|
18
|
-
To install as a gem, type
|
18
|
+
To install as a gem, type:
|
19
19
|
|
20
20
|
```bash
|
21
21
|
gem install ssh_scan
|
22
22
|
ssh_scan
|
23
23
|
```
|
24
24
|
|
25
|
-
To install from source, type
|
25
|
+
To install from source, type:
|
26
26
|
|
27
27
|
```bash
|
28
|
+
# clone repo
|
28
29
|
git clone https://github.com/mozilla/ssh_scan.git
|
29
30
|
cd ssh_scan
|
30
|
-
|
31
|
+
|
32
|
+
# install rvm,
|
33
|
+
# you might have to provide root to install missing packages
|
34
|
+
gpg2 --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3
|
35
|
+
curl -sSL https://get.rvm.io | bash -s stable
|
36
|
+
|
37
|
+
# install Ruby 2.1.3 with rvm,
|
38
|
+
# again, you might have to install missing devel packages
|
39
|
+
rvm install 2.1.3
|
40
|
+
rvm use 2.1.3
|
41
|
+
|
42
|
+
# resolve dependencies
|
43
|
+
gem install bundler
|
44
|
+
bundle install
|
45
|
+
|
31
46
|
./bin/ssh_scan
|
32
47
|
```
|
33
48
|
|
@@ -54,32 +69,29 @@ Run `ssh_scan -h` to get this
|
|
54
69
|
ssh_scan -t 192.168.1.1 -P custom_policy.yml
|
55
70
|
ssh_scan -t 192.168.1.1 --unit-test -P custom_policy.yml
|
56
71
|
|
57
|
-
See here for [example video](https://asciinema.org/a/7pliiw5zqhj7eqvz7q437u6vx)
|
58
|
-
|
59
|
-
See here for [example
|
60
|
-
|
61
|
-
See here for [example policies](https://github.com/mozilla/ssh_scan/blob/master/policies)
|
72
|
+
- See here for [example video](https://asciinema.org/a/7pliiw5zqhj7eqvz7q437u6vx)
|
73
|
+
- See here for [example output](https://github.com/mozilla/ssh_scan/blob/master/examples/192.168.1.1.json)
|
74
|
+
- See here for [example policies](https://github.com/mozilla/ssh_scan/blob/master/policies)
|
62
75
|
|
63
76
|
## Rubies Supported
|
64
77
|
|
65
78
|
This project is integrated with [travis-ci](http://about.travis-ci.org/) and is regularly tested to work with the following rubies:
|
66
79
|
|
80
|
+
* [ruby-head](https://github.com/ruby/ruby)
|
81
|
+
* [2.3.0](https://github.com/ruby/ruby/tree/ruby_2_1)
|
82
|
+
* [2.2.0](https://github.com/ruby/ruby/tree/ruby_2_1)
|
67
83
|
* [2.1.3](https://github.com/ruby/ruby/tree/ruby_2_1)
|
68
84
|
* [2.1.0](https://github.com/ruby/ruby/tree/ruby_2_1)
|
69
85
|
* [2.0.0](https://github.com/ruby/ruby/tree/ruby_2_0_0)
|
70
|
-
* [1.9.3](https://github.com/ruby/ruby/tree/ruby_1_9_3)
|
71
|
-
* [ruby-head](https://github.com/ruby/ruby)
|
72
|
-
* [jruby-head](http://jruby.org/)
|
73
|
-
* [jruby-19mode](http://jruby.org/)
|
74
86
|
|
75
87
|
To checkout the current build status for these rubies, click [here](https://travis-ci.org/#!/mozilla/ssh_scan).
|
76
88
|
|
77
89
|
## Contributing
|
78
90
|
|
79
|
-
If you are interested in contributing to this project, please see [CONTRIBUTING.md](https://github.com/mozilla/ssh_scan/blob/master/CONTRIBUTING.md)
|
91
|
+
If you are interested in contributing to this project, please see [CONTRIBUTING.md](https://github.com/mozilla/ssh_scan/blob/master/CONTRIBUTING.md).
|
80
92
|
|
81
93
|
## Credits
|
82
94
|
|
83
95
|
**Sources of Inspiration for ssh_scan**
|
84
96
|
|
85
|
-
- [**Mozilla OpenSSH Security Guide**](https://wiki.mozilla.org/Security/Guidelines/OpenSSH) - For providing a sane baseline policy recommendation for SSH configuration parameters (eg. Ciphers,
|
97
|
+
- [**Mozilla OpenSSH Security Guide**](https://wiki.mozilla.org/Security/Guidelines/OpenSSH) - For providing a sane baseline policy recommendation for SSH configuration parameters (eg. Ciphers, MACs, and KexAlgos).
|
data/bin/ssh_scan
CHANGED
@@ -5,10 +5,11 @@ $:.unshift File.join(File.dirname(__FILE__), "../lib")
|
|
5
5
|
|
6
6
|
require 'ssh_scan'
|
7
7
|
require 'optparse'
|
8
|
+
require 'netaddr'
|
8
9
|
|
9
10
|
#Default options
|
10
11
|
options = {
|
11
|
-
:
|
12
|
+
:targets => [],
|
12
13
|
:port => 22,
|
13
14
|
:policy => File.expand_path("../../policies/mozilla_modern.yml", __FILE__),
|
14
15
|
:unit_test => false
|
@@ -17,9 +18,26 @@ opt_parser = OptionParser.new do |opts|
|
|
17
18
|
opts.banner = "ssh_scan v#{SSHScan::VERSION} (https://github.com/mozilla/ssh_scan)\n\n" +
|
18
19
|
"Usage: ssh_scan [options]"
|
19
20
|
|
20
|
-
opts.on("-t", "--target [IP/Hostname]",
|
21
|
-
"IP/Hostname (IPv4/IPv6/FQDNs)") do |
|
22
|
-
|
21
|
+
opts.on("-t", "--target [IP/Hostname]", Array,
|
22
|
+
"IP/Hostname (IPv4/IPv6/FQDNs)") do |ips|
|
23
|
+
ips.each do |ip|
|
24
|
+
if ip.fqdn?
|
25
|
+
options[:targets] += [ip]
|
26
|
+
else
|
27
|
+
options[:targets] += NetAddr::CIDR.create(ip).enumerate
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
opts.on("-f", "--file [FilePath]",
|
33
|
+
"File Path of the file containing IPs") do |file|
|
34
|
+
txt = open(file)
|
35
|
+
options[:targets] = txt.read.chomp.split(',')
|
36
|
+
end
|
37
|
+
|
38
|
+
opts.on("-o", "--outputFile [FilePath]",
|
39
|
+
"Writing JSON documents to disk") do |file|
|
40
|
+
$stdout.reopen(file, "w")
|
23
41
|
end
|
24
42
|
|
25
43
|
opts.on("-p", "--port [PORT]",
|
@@ -49,6 +67,8 @@ opt_parser = OptionParser.new do |opts|
|
|
49
67
|
puts "\n ssh_scan -t 192.168.1.1"
|
50
68
|
puts " ssh_scan -t server.example.com"
|
51
69
|
puts " ssh_scan -t ::1"
|
70
|
+
puts " ssh_scan -f filePath"
|
71
|
+
puts " ssh_scan -o filePath"
|
52
72
|
puts " ssh_scan -t 192.168.1.1 -p 22222"
|
53
73
|
puts " ssh_scan -t 192.168.1.1 -P custom_policy.yml"
|
54
74
|
puts " ssh_scan -t 192.168.1.1 --unit-test -P custom_policy.yml"
|
@@ -59,16 +79,18 @@ end
|
|
59
79
|
|
60
80
|
opt_parser.parse!
|
61
81
|
|
62
|
-
if options[:
|
82
|
+
if options[:targets].nil?
|
63
83
|
puts opt_parser.help
|
64
84
|
puts "\nReason: no target specified"
|
65
85
|
exit
|
66
86
|
end
|
67
87
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
88
|
+
options[:targets].each do |target|
|
89
|
+
unless target.ip_addr? || target.fqdn?
|
90
|
+
puts opt_parser.help
|
91
|
+
puts "\nReason: #{options[:targets]} is not a valid target"
|
92
|
+
exit
|
93
|
+
end
|
72
94
|
end
|
73
95
|
|
74
96
|
unless (0..65535).include?(options[:port])
|
@@ -87,12 +109,14 @@ options[:policy_file] = SSHScan::Policy.from_file(options[:policy])
|
|
87
109
|
|
88
110
|
# Perform scan and get results
|
89
111
|
scan_engine = SSHScan::ScanEngine.new()
|
90
|
-
|
112
|
+
results = scan_engine.scan(options)
|
91
113
|
|
92
|
-
puts JSON.pretty_generate(
|
114
|
+
puts JSON.pretty_generate(results)
|
93
115
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
116
|
+
results.each do |result|
|
117
|
+
if result["compliance"] && result["compliance"][:compliant] == false
|
118
|
+
exit 1 #non-zero means a false
|
119
|
+
else
|
120
|
+
exit 0 #non-zero means pass
|
121
|
+
end
|
98
122
|
end
|
data/lib/ssh_scan/banner.rb
CHANGED
@@ -22,7 +22,7 @@ module SSHScan
|
|
22
22
|
def ssh_lib_guess()
|
23
23
|
case @string
|
24
24
|
when /OpenSSH/i
|
25
|
-
return SSHScan::SSHLib::OpenSSH.new()
|
25
|
+
return SSHScan::SSHLib::OpenSSH.new(@string)
|
26
26
|
when /LibSSH/i
|
27
27
|
return SSHScan::SSHLib::LibSSH.new()
|
28
28
|
else
|
@@ -36,10 +36,14 @@ module SSHScan
|
|
36
36
|
return SSHScan::OS::Ubuntu.new
|
37
37
|
when /CentOS/i
|
38
38
|
return SSHScan::OS::CentOS.new
|
39
|
+
when /RHEL|RedHat/i
|
40
|
+
return SSHScan::OS::RedHat.new
|
39
41
|
when /FreeBSD/i
|
40
42
|
return SSHScan::OS::FreeBSD.new
|
41
43
|
when /Debian/i
|
42
44
|
return SSHScan::OS::Debian.new
|
45
|
+
when /Windows/i
|
46
|
+
return SSHScan::OS::Windows.new
|
43
47
|
else
|
44
48
|
return SSHScan::OS::Unknown.new
|
45
49
|
end
|
data/lib/ssh_scan/os/ubuntu.rb
CHANGED
data/lib/ssh_scan/os.rb
CHANGED
@@ -103,10 +103,10 @@ module SSHScan
|
|
103
103
|
recommendations = []
|
104
104
|
|
105
105
|
# Add these items to be compliant
|
106
|
-
recommendations << "Add these Key Exchange Algos: #{missing_policy_kex.join(",
|
107
|
-
recommendations << "Add these MAC Algos: #{missing_policy_macs.join(",
|
108
|
-
recommendations << "Add these Encryption Ciphers: #{missing_policy_encryption.join(",
|
109
|
-
recommendations << "Add these Compression Algos: #{missing_policy_compression.join(",
|
106
|
+
recommendations << "Add these Key Exchange Algos: #{missing_policy_kex.join(",")}" unless missing_policy_kex.empty?
|
107
|
+
recommendations << "Add these MAC Algos: #{missing_policy_macs.join(",")}" unless missing_policy_macs.empty?
|
108
|
+
recommendations << "Add these Encryption Ciphers: #{missing_policy_encryption.join(",")}" unless missing_policy_encryption.empty?
|
109
|
+
recommendations << "Add these Compression Algos: #{missing_policy_compression.join(",")}" unless missing_policy_compression.empty?
|
110
110
|
|
111
111
|
# Remove these items to be compliant
|
112
112
|
recommendations << "Remove these Key Exchange Algos: #{out_of_policy_kex.join(", ")}" unless out_of_policy_kex.empty?
|
data/lib/ssh_scan/scan_engine.rb
CHANGED
@@ -6,49 +6,56 @@ module SSHScan
|
|
6
6
|
class ScanEngine
|
7
7
|
|
8
8
|
def scan(opts)
|
9
|
-
|
9
|
+
targets = opts[:targets]
|
10
10
|
port = opts[:port]
|
11
11
|
policy = opts[:policy_file]
|
12
12
|
|
13
13
|
# Connect and get results (native)
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
14
|
+
result = []
|
15
|
+
targets.each_with_index do |target, index|
|
16
|
+
client = SSHScan::Client.new(target, port)
|
17
|
+
client.connect()
|
18
|
+
result.push(client.get_kex_result())
|
19
|
+
|
20
|
+
# Connect and get results (Net-SSH)
|
21
|
+
net_ssh_session = Net::SSH::Transport::Session.new(target, :port => port)
|
22
|
+
auth_session = Net::SSH::Authentication::Session.new(net_ssh_session, :auth_methods => ["none"])
|
23
|
+
auth_session.authenticate("none", "test", "test")
|
24
|
+
result[index]['auth_methods'] = auth_session.allowed_auth_methods
|
25
|
+
host_key = net_ssh_session.host_keys.first
|
26
|
+
net_ssh_session.close
|
27
|
+
|
28
|
+
# only supporting RSA for the moment
|
29
|
+
unless host_key.is_a?(OpenSSL::PKey::RSA)
|
30
|
+
raise "Unknown host key type, need to add this host_key type"
|
31
|
+
end
|
32
|
+
|
33
|
+
# only supporting RSA for the moment
|
34
|
+
unless OpenSSL::PKey::RSA
|
35
|
+
raise "Unknown host key type, need to add this host_key type"
|
36
|
+
end
|
37
|
+
|
38
|
+
data_string = OpenSSL::ASN1::Sequence([
|
39
|
+
OpenSSL::ASN1::Integer.new(host_key.public_key.n),
|
40
|
+
OpenSSL::ASN1::Integer.new(host_key.public_key.e)
|
41
|
+
])
|
42
|
+
|
43
|
+
fingerprint_md5 = OpenSSL::Digest::MD5.hexdigest(data_string.to_der).scan(/../).join(':')
|
44
|
+
fingerprint_sha1 = OpenSSL::Digest::SHA1.hexdigest(data_string.to_der).scan(/../).join(':')
|
45
|
+
fingerprint_sha256 = OpenSSL::Digest::SHA256.hexdigest(data_string.to_der).scan(/../).join(':')
|
46
|
+
|
47
|
+
result[index]['fingerprints'] = {
|
48
|
+
"md5" => fingerprint_md5,
|
49
|
+
"sha1" => fingerprint_sha1,
|
50
|
+
"sha256" => fingerprint_sha256,
|
51
|
+
}
|
52
|
+
|
53
|
+
# If policy defined, then add compliance results
|
54
|
+
unless policy.nil?
|
55
|
+
policy_mgr = SSHScan::PolicyManager.new(result[index], policy)
|
56
|
+
result[index]['compliance'] = policy_mgr.compliance_results
|
57
|
+
end
|
29
58
|
end
|
30
|
-
|
31
|
-
data_string = OpenSSL::ASN1::Sequence([
|
32
|
-
OpenSSL::ASN1::Integer.new(host_key.public_key.n),
|
33
|
-
OpenSSL::ASN1::Integer.new(host_key.public_key.e)
|
34
|
-
])
|
35
|
-
|
36
|
-
fingerprint_md5 = OpenSSL::Digest::MD5.hexdigest(data_string.to_der).scan(/../).join(':')
|
37
|
-
fingerprint_sha1 = OpenSSL::Digest::SHA1.hexdigest(data_string.to_der).scan(/../).join(':')
|
38
|
-
fingerprint_sha256 = OpenSSL::Digest::SHA256.hexdigest(data_string.to_der).scan(/../).join(':')
|
39
|
-
|
40
|
-
result['fingerprints'] = {
|
41
|
-
"md5" => fingerprint_md5,
|
42
|
-
"sha1" => fingerprint_sha1,
|
43
|
-
"sha256" => fingerprint_sha256,
|
44
|
-
}
|
45
|
-
|
46
|
-
# If policy defined, then add compliance results
|
47
|
-
unless policy.nil?
|
48
|
-
policy_mgr = SSHScan::PolicyManager.new(result, policy)
|
49
|
-
result['compliance'] = policy_mgr.compliance_results
|
50
|
-
end
|
51
|
-
|
52
59
|
return result
|
53
60
|
end
|
54
61
|
end
|
@@ -1,12 +1,33 @@
|
|
1
1
|
module SSHScan
|
2
2
|
module SSHLib
|
3
3
|
class OpenSSH
|
4
|
+
class Version
|
5
|
+
def initialize(version_string)
|
6
|
+
@version_string = version_string
|
7
|
+
end
|
8
|
+
|
9
|
+
def to_s
|
10
|
+
@version_string
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(banner = nil)
|
15
|
+
@banner = banner
|
16
|
+
end
|
17
|
+
|
18
|
+
def version()
|
19
|
+
return nil if @banner.nil?
|
20
|
+
match = @banner.match(/OpenSSH_(\d+[\.\d+]+(p)?(\d+)?)/)
|
21
|
+
return nil if match.nil?
|
22
|
+
return OpenSSH::Version.new(match[1])
|
23
|
+
end
|
24
|
+
|
4
25
|
def common
|
5
26
|
"openssh"
|
6
27
|
end
|
7
28
|
|
8
29
|
def cpe
|
9
|
-
"a:openssh:openssh"
|
30
|
+
"a:openssh:openssh" << (":" + version.to_s) unless version.nil?
|
10
31
|
end
|
11
32
|
end
|
12
33
|
end
|
data/lib/ssh_scan/version.rb
CHANGED
data/ssh_scan.gemspec
CHANGED
@@ -28,6 +28,7 @@ Gem::Specification.new do |s|
|
|
28
28
|
|
29
29
|
s.add_dependency('bindata', '~> 2.0')
|
30
30
|
s.add_dependency('net-ssh')
|
31
|
+
s.add_dependency('netaddr')
|
31
32
|
s.add_development_dependency('pry')
|
32
33
|
s.add_development_dependency('rspec', '~> 3.0')
|
33
34
|
s.add_development_dependency('rspec-its', '~> 1.2')
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
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.9.beta.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jonathan Claudius
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-08-
|
11
|
+
date: 2016-08-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bindata
|
@@ -38,6 +38,20 @@ dependencies:
|
|
38
38
|
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: netaddr
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
41
55
|
- !ruby/object:Gem::Dependency
|
42
56
|
name: pry
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -118,8 +132,10 @@ files:
|
|
118
132
|
- lib/ssh_scan/os/centos.rb
|
119
133
|
- lib/ssh_scan/os/debian.rb
|
120
134
|
- lib/ssh_scan/os/freebsd.rb
|
135
|
+
- lib/ssh_scan/os/redhat.rb
|
121
136
|
- lib/ssh_scan/os/ubuntu.rb
|
122
137
|
- lib/ssh_scan/os/unknown.rb
|
138
|
+
- lib/ssh_scan/os/windows.rb
|
123
139
|
- lib/ssh_scan/policy.rb
|
124
140
|
- lib/ssh_scan/policy_manager.rb
|
125
141
|
- lib/ssh_scan/protocol.rb
|
@@ -148,9 +164,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
148
164
|
version: '0'
|
149
165
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
150
166
|
requirements:
|
151
|
-
- - "
|
167
|
+
- - ">"
|
152
168
|
- !ruby/object:Gem::Version
|
153
|
-
version:
|
169
|
+
version: 1.3.1
|
154
170
|
requirements: []
|
155
171
|
rubyforge_project:
|
156
172
|
rubygems_version: 2.6.2
|