ssh_scan 0.0.10 → 0.0.11
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 +2 -0
- data/bin/ssh_scan +39 -16
- data/lib/ssh_scan/banner.rb +1 -1
- data/lib/ssh_scan/client.rb +3 -10
- data/lib/ssh_scan/constants.rb +33 -14
- data/lib/ssh_scan/os/ubuntu.rb +194 -1
- data/lib/ssh_scan/protocol.rb +126 -4
- data/lib/ssh_scan/scan_engine.rb +33 -10
- data/lib/ssh_scan/target_parser.rb +7 -3
- data/lib/ssh_scan/version.rb +1 -1
- data/lib/string_ext.rb +21 -0
- data/ssh_scan.gemspec +2 -1
- metadata +17 -3
- data/lib/ssh_scan/basic_server.rb +0 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0e403b742bae642fd06efb786e0a8d0e8dcbf273
|
4
|
+
data.tar.gz: 7cd2a0a4b78a5ad4cef03a380c713f853e1e7522
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 33fd2a7cdaef54565ad83ec4337e8631f2cef0b52bcf0e4c99651eab3356c635447b360b435f1ffccff9d1453916c6c05a9b401d9f97c7ff9576fef5ba7bcf2d
|
7
|
+
data.tar.gz: d3b172476eb920ae6cceb1fdeabbf143acf42693f6536f9cfac05f010b0da552d9f7836f2bc643a419e0abf0e68f8e6b03849e5af40a7b388743206400736ec7
|
data/README.md
CHANGED
@@ -64,6 +64,7 @@ Run `ssh_scan -h` to get this
|
|
64
64
|
-f, --file [FilePath] File Path of the file containing IP/Range/Hostnames to scan
|
65
65
|
-T, --timeout [seconds] Timeout per connect after which ssh_scan gives up on the host
|
66
66
|
-o, --output [FilePath] File to write JSON output to
|
67
|
+
-O, --from_json [FilePath] File to read JSON output from
|
67
68
|
-p, --port [PORT] Port (Default: 22)
|
68
69
|
-P, --policy [FILE] Custom policy file (Default: Mozilla Modern)
|
69
70
|
--threads [NUMBER] Number of worker threads (Default: 5)
|
@@ -79,6 +80,7 @@ Run `ssh_scan -h` to get this
|
|
79
80
|
ssh_scan -t ::1 -T 5
|
80
81
|
ssh_scan -f hosts.txt
|
81
82
|
ssh_scan -o output.json
|
83
|
+
ssh_scan -O output.json -o rescan_output.json
|
82
84
|
ssh_scan -t 192.168.1.1 -p 22222
|
83
85
|
ssh_scan -t 192.168.1.1 -P custom_policy.yml
|
84
86
|
ssh_scan -t 192.168.1.1 --unit-test -P custom_policy.yml
|
data/bin/ssh_scan
CHANGED
@@ -3,14 +3,14 @@
|
|
3
3
|
# Path setting slight of hand
|
4
4
|
$:.unshift File.join(File.dirname(__FILE__), "../lib")
|
5
5
|
|
6
|
-
require '
|
7
|
-
require 'optparse'
|
6
|
+
require 'json'
|
8
7
|
require 'netaddr'
|
8
|
+
require 'optparse'
|
9
|
+
require 'ssh_scan'
|
9
10
|
|
10
11
|
#Default options
|
11
12
|
options = {
|
12
|
-
:
|
13
|
-
:port => 22,
|
13
|
+
:sockets => [],
|
14
14
|
:policy => File.expand_path("../../policies/mozilla_modern.yml", __FILE__),
|
15
15
|
:unit_test => false,
|
16
16
|
:timeout => 2,
|
@@ -26,7 +26,7 @@ opt_parser = OptionParser.new do |opts|
|
|
26
26
|
opts.on("-t", "--target [IP/Range/Hostname]", Array,
|
27
27
|
"IP/Ranges/Hostname to scan") do |ips|
|
28
28
|
ips.each do |ip|
|
29
|
-
options[:
|
29
|
+
options[:sockets] += target_parser.enumerateIPRange(ip)
|
30
30
|
end
|
31
31
|
end
|
32
32
|
|
@@ -37,8 +37,10 @@ opt_parser = OptionParser.new do |opts|
|
|
37
37
|
exit
|
38
38
|
end
|
39
39
|
File.open(file).each do |line|
|
40
|
-
line.chomp.split(',').each do |
|
41
|
-
|
40
|
+
line.chomp.split(',').each do |socket|
|
41
|
+
ip, port = socket.chomp.split(':')
|
42
|
+
port = port.nil? ? 22 : port
|
43
|
+
options[:sockets] += target_parser.enumerateIPRange(ip, port)
|
42
44
|
end
|
43
45
|
end
|
44
46
|
end
|
@@ -48,6 +50,20 @@ opt_parser = OptionParser.new do |opts|
|
|
48
50
|
options[:timeout] = timeout.to_i
|
49
51
|
end
|
50
52
|
|
53
|
+
opts.on("-O", "--from_json [FilePath]",
|
54
|
+
"File to read JSON output from") do |file|
|
55
|
+
unless File.exists?(file)
|
56
|
+
puts "\nReason: Invalid file"
|
57
|
+
exit
|
58
|
+
end
|
59
|
+
file = open(file)
|
60
|
+
json = file.read
|
61
|
+
parsed_json = JSON.parse(json)
|
62
|
+
parsed_json.each do |host|
|
63
|
+
options[:sockets] += target_parser.enumerateIPRange(host['ip'], host['port'])
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
51
67
|
opts.on("-o", "--output [FilePath]",
|
52
68
|
"File to write JSON output to") do |file|
|
53
69
|
$stdout.reopen(file, "w")
|
@@ -55,7 +71,9 @@ opt_parser = OptionParser.new do |opts|
|
|
55
71
|
|
56
72
|
opts.on("-p", "--port [PORT]",
|
57
73
|
"Port (Default: 22)") do |port|
|
58
|
-
options[:
|
74
|
+
socket = options[:sockets].shift
|
75
|
+
ip = socket.chomp.split(':').shift
|
76
|
+
options[:sockets] += target_parser.enumerateIPRange(ip, port)
|
59
77
|
end
|
60
78
|
|
61
79
|
opts.on("-P", "--policy [FILE]",
|
@@ -88,6 +106,7 @@ opt_parser = OptionParser.new do |opts|
|
|
88
106
|
puts " ssh_scan -t ::1 -T 5"
|
89
107
|
puts " ssh_scan -f hosts.txt"
|
90
108
|
puts " ssh_scan -o output.json"
|
109
|
+
puts " ssh_scan -O output.json -o rescan_output.json"
|
91
110
|
puts " ssh_scan -t 192.168.1.1 -p 22222"
|
92
111
|
puts " ssh_scan -t 192.168.1.1 -P custom_policy.yml"
|
93
112
|
puts " ssh_scan -t 192.168.1.1 --unit-test -P custom_policy.yml"
|
@@ -98,24 +117,28 @@ end
|
|
98
117
|
|
99
118
|
opt_parser.parse!
|
100
119
|
|
101
|
-
if options[:
|
120
|
+
if options[:sockets].nil?
|
102
121
|
puts opt_parser.help
|
103
122
|
puts "\nReason: no target specified"
|
104
123
|
exit 1
|
105
124
|
end
|
106
125
|
|
107
|
-
options[:
|
108
|
-
|
126
|
+
options[:sockets].each do |socket|
|
127
|
+
ip, port = socket.chomp.split(':')
|
128
|
+
unless ip.ip_addr? || ip.fqdn?
|
109
129
|
puts opt_parser.help
|
110
|
-
puts "\nReason: #{
|
130
|
+
puts "\nReason: #{socket} is not a valid target"
|
111
131
|
exit 1
|
112
132
|
end
|
113
133
|
end
|
114
134
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
135
|
+
options[:sockets].each do |socket|
|
136
|
+
ip, port = socket.chomp.split(':')
|
137
|
+
unless (0..65535).include?(port.to_i)
|
138
|
+
puts opt_parser.help
|
139
|
+
puts "\nReason: port supplied is not within acceptable range"
|
140
|
+
exit 1
|
141
|
+
end
|
119
142
|
end
|
120
143
|
|
121
144
|
unless File.exists?(options[:policy])
|
data/lib/ssh_scan/banner.rb
CHANGED
data/lib/ssh_scan/client.rb
CHANGED
@@ -10,21 +10,15 @@ module SSHScan
|
|
10
10
|
@target = target
|
11
11
|
@timeout = timeout
|
12
12
|
|
13
|
-
if @target.ip_addr?
|
14
|
-
@ip = @target
|
15
|
-
else
|
16
|
-
@ip = @target.resolve_fqdn()
|
17
|
-
end
|
18
|
-
|
19
13
|
@port = port
|
20
14
|
@client_banner = SSHScan::Constants::DEFAULT_CLIENT_BANNER
|
21
15
|
@server_banner = nil
|
22
|
-
@kex_init_raw = SSHScan::Constants::
|
16
|
+
@kex_init_raw = SSHScan::Constants::DEFAULT_KEY_INIT.to_binary_s
|
23
17
|
end
|
24
18
|
|
25
19
|
def connect()
|
26
20
|
begin
|
27
|
-
@sock = Socket.tcp(@
|
21
|
+
@sock = Socket.tcp(@target, @port, connect_timeout: @timeout)
|
28
22
|
rescue Errno::ETIMEDOUT => e
|
29
23
|
@error = SSHScan::Error::ConnectTimeout.new(e.message)
|
30
24
|
@sock = nil
|
@@ -42,8 +36,7 @@ module SSHScan
|
|
42
36
|
# Common options for all cases
|
43
37
|
result = {}
|
44
38
|
result[:ssh_scan_version] = SSHScan::VERSION
|
45
|
-
result[:
|
46
|
-
result[:ip] = @ip
|
39
|
+
result[:ip] = @target
|
47
40
|
result[:port] = @port
|
48
41
|
|
49
42
|
if !@sock
|
data/lib/ssh_scan/constants.rb
CHANGED
@@ -1,23 +1,42 @@
|
|
1
1
|
require 'string_ext'
|
2
2
|
require 'ssh_scan/banner'
|
3
|
+
require 'ssh_scan/protocol'
|
3
4
|
|
4
5
|
module SSHScan
|
5
6
|
module Constants
|
6
7
|
DEFAULT_CLIENT_BANNER = SSHScan::Banner.new("SSH-2.0-ssh_scan")
|
7
8
|
DEFAULT_SERVER_BANNER = SSHScan::Banner.new("SSH-2.0-server")
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
9
|
+
|
10
|
+
default_key_init_opts = {
|
11
|
+
:cookie => "e33f813f8cdcc6b00a3d852ec1aea498".unhexify,
|
12
|
+
:padding => "6e05b3b4".unhexify,
|
13
|
+
:key_algorithms => ["diffie-hellman-group1-sha1"],
|
14
|
+
:server_host_key_algorithms => ["ssh-dss","ssh-rsa"],
|
15
|
+
:encryption_algorithms_client_to_server => ["aes128-cbc","3des-cbc","blowfish-cbc","aes192-cbc","aes256-cbc","aes128-ctr","aes192-ctr","aes256-ctr"],
|
16
|
+
:encryption_algorithms_server_to_client => ["aes128-cbc","3des-cbc","blowfish-cbc","aes192-cbc","aes256-cbc","aes128-ctr","aes192-ctr","aes256-ctr"],
|
17
|
+
:mac_algorithms_client_to_server => ["hmac-md5","hmac-sha1","hmac-ripemd160"],
|
18
|
+
:mac_algorithms_server_to_client => ["hmac-md5","hmac-sha1","hmac-ripemd160"],
|
19
|
+
:compression_algorithms_client_to_server => ["none"],
|
20
|
+
:compression_algorithms_server_to_client => ["none"],
|
21
|
+
:languages_client_to_server => [],
|
22
|
+
:languages_server_to_client => [],
|
23
|
+
}
|
24
|
+
|
25
|
+
DEFAULT_KEY_INIT = SSHScan::KeyExchangeInit.from_hash(default_key_init_opts)
|
26
|
+
|
27
|
+
DEFAULT_KEY_INIT_RAW = ("000001640414e33f813f8cdcc6b00a3d852ec1aea4980000001a6" +
|
28
|
+
"469666669652d68656c6c6d616e2d67726f7570312d7368613100" +
|
29
|
+
"00000f7373682d6473732c7373682d72736100000057616573313" +
|
30
|
+
"2382d6362632c336465732d6362632c626c6f77666973682d6362" +
|
31
|
+
"632c6165733139322d6362632c6165733235362d6362632c61657" +
|
32
|
+
"33132382d6374722c6165733139322d6374722c6165733235362d" +
|
33
|
+
"637472000000576165733132382d6362632c336465732d6362632" +
|
34
|
+
"c626c6f77666973682d6362632c6165733139322d6362632c6165" +
|
35
|
+
"733235362d6362632c6165733132382d6374722c6165733139322" +
|
36
|
+
"d6374722c6165733235362d63747200000021686d61632d6d6435" +
|
37
|
+
"2c686d61632d736861312c686d61632d726970656d64313630000" +
|
38
|
+
"00021686d61632d6d64352c686d61632d736861312c686d61632d" +
|
39
|
+
"726970656d64313630000000046e6f6e65000000046e6f6e65000" +
|
40
|
+
"000000000000000000000006e05b3b4").unhexify
|
22
41
|
end
|
23
42
|
end
|
data/lib/ssh_scan/os/ubuntu.rb
CHANGED
@@ -1,12 +1,205 @@
|
|
1
1
|
module SSHScan
|
2
2
|
module OS
|
3
3
|
class Ubuntu
|
4
|
+
class Version
|
5
|
+
def initialize(version_string)
|
6
|
+
@version_string = version_string
|
7
|
+
end
|
8
|
+
def to_s
|
9
|
+
@version_string
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# Obtained from scraping ChangeLog on Launchpad
|
14
|
+
FINGERPRINTS = {
|
15
|
+
"4.10" => ["3.8.1p1-11ubuntu3.3", "3.8.1p1-11ubuntu3.2", "3.8.1p1-11ubuntu3"],
|
16
|
+
"5.04" =>
|
17
|
+
["3.9p1-1ubuntu2.3",
|
18
|
+
"3.9p1-1ubuntu2.2",
|
19
|
+
"3.9p1-1ubuntu2.1",
|
20
|
+
"3.9p1-1ubuntu2"],
|
21
|
+
"5.10" => ["4.1p1-7ubuntu4.2", "4.1p1-7ubuntu4.1", "4.1p1-7ubuntu4"],
|
22
|
+
"6.04" =>
|
23
|
+
["4.2p1-7ubuntu3.5",
|
24
|
+
"4.2p1-7ubuntu3.4",
|
25
|
+
"4.2p1-7ubuntu3.3",
|
26
|
+
"4.2p1-7ubuntu3.2",
|
27
|
+
"4.2p1-7ubuntu3.1",
|
28
|
+
"4.2p1-7ubuntu3",
|
29
|
+
"4.2p1-7ubuntu2",
|
30
|
+
"4.2p1-7ubuntu1",
|
31
|
+
"4.2p1-5ubuntu2",
|
32
|
+
"4.2p1-5ubuntu1"],
|
33
|
+
"6.10" =>
|
34
|
+
["4.3p2-5ubuntu1.2",
|
35
|
+
"4.3p2-5ubuntu1.1",
|
36
|
+
"4.3p2-5ubuntu1",
|
37
|
+
"4.3p2-4ubuntu1",
|
38
|
+
"4.3p2-2ubuntu5",
|
39
|
+
"4.3p2-2ubuntu4",
|
40
|
+
"4.3p2-2ubuntu3",
|
41
|
+
"4.3p2-2ubuntu2",
|
42
|
+
"4.3p2-2ubuntu1"],
|
43
|
+
"7.04" => [],
|
44
|
+
"7.10" =>
|
45
|
+
["4.6p1-5ubuntu0.6",
|
46
|
+
"4.6p1-5ubuntu0.5",
|
47
|
+
"4.6p1-5ubuntu0.4",
|
48
|
+
"4.6p1-5ubuntu0.3",
|
49
|
+
"4.6p1-5ubuntu0.2",
|
50
|
+
"4.6p1-5ubuntu0.1",
|
51
|
+
"4.6p1-5build1",
|
52
|
+
"4.3p2-10ubuntu1"],
|
53
|
+
"8.04" =>
|
54
|
+
["4.7p1-8ubuntu3",
|
55
|
+
"4.7p1-8ubuntu2",
|
56
|
+
"4.7p1-8ubuntu1.2",
|
57
|
+
"4.7p1-8ubuntu1.1",
|
58
|
+
"4.7p1-8ubuntu1",
|
59
|
+
"4.7p1-7ubuntu1",
|
60
|
+
"4.7p1-6ubuntu1",
|
61
|
+
"4.7p1-5ubuntu1",
|
62
|
+
"4.7p1-4ubuntu1"],
|
63
|
+
"8.10" =>
|
64
|
+
["5.1p1-3ubuntu1",
|
65
|
+
"5.1p1-1ubuntu2",
|
66
|
+
"5.1p1-1ubuntu1",
|
67
|
+
"4.7p1-12ubuntu4",
|
68
|
+
"4.7p1-12ubuntu3",
|
69
|
+
"4.7p1-12ubuntu2",
|
70
|
+
"4.7p1-12ubuntu1",
|
71
|
+
"4.7p1-10ubuntu1",
|
72
|
+
"4.7p1-9ubuntu1"],
|
73
|
+
"9.04" => ["5.1p1-5ubuntu1", "5.1p1-4ubuntu1"],
|
74
|
+
"9.10" => ["5.1p1-6ubuntu2", "5.1p1-6ubuntu1", "5.1p1-5ubuntu2"],
|
75
|
+
"10.04" =>
|
76
|
+
["5.3p1-3ubuntu7.1",
|
77
|
+
"5.3p1-3ubuntu7",
|
78
|
+
"5.3p1-3ubuntu6",
|
79
|
+
"5.3p1-3ubuntu5",
|
80
|
+
"5.3p1-3ubuntu4",
|
81
|
+
"5.3p1-3ubuntu3",
|
82
|
+
"5.3p1-3ubuntu2",
|
83
|
+
"5.3p1-3ubuntu1",
|
84
|
+
"5.3p1-1ubuntu2",
|
85
|
+
"5.3p1-1ubuntu1",
|
86
|
+
"5.2p1-2ubuntu1",
|
87
|
+
"5.2p1-1ubuntu1",
|
88
|
+
"5.1p1-8ubuntu2",
|
89
|
+
"5.1p1-8ubuntu1"],
|
90
|
+
"10.10" =>
|
91
|
+
["5.5p1-4ubuntu6",
|
92
|
+
"5.5p1-4ubuntu5",
|
93
|
+
"5.5p1-4ubuntu4",
|
94
|
+
"5.5p1-4ubuntu3",
|
95
|
+
"5.5p1-4ubuntu2",
|
96
|
+
"5.5p1-4ubuntu1",
|
97
|
+
"5.5p1-3ubuntu1"],
|
98
|
+
"11.04" =>
|
99
|
+
["5.8p1-1ubuntu3",
|
100
|
+
"5.8p1-1ubuntu2",
|
101
|
+
"5.8p1-1ubuntu1",
|
102
|
+
"5.7p1-2ubuntu1",
|
103
|
+
"5.7p1-1ubuntu1",
|
104
|
+
"5.6p1-2ubuntu4",
|
105
|
+
"5.6p1-2ubuntu3",
|
106
|
+
"5.6p1-2ubuntu2",
|
107
|
+
"5.6p1-2ubuntu1",
|
108
|
+
"5.6p1-1ubuntu1"],
|
109
|
+
"11.10" => ["5.8p1-7ubuntu1", "5.8p1-4ubuntu2", "5.8p1-4ubuntu1"],
|
110
|
+
"12.04" =>
|
111
|
+
["5.9p1-5ubuntu1.10",
|
112
|
+
"5.9p1-5ubuntu1.9",
|
113
|
+
"5.9p1-5ubuntu1.8",
|
114
|
+
"5.9p1-5ubuntu1.7",
|
115
|
+
"5.9p1-5ubuntu1.6",
|
116
|
+
"5.9p1-5ubuntu1.4",
|
117
|
+
"5.9p1-5ubuntu1.3",
|
118
|
+
"5.9p1-5ubuntu1.2",
|
119
|
+
"5.9p1-5ubuntu1.1",
|
120
|
+
"5.9p1-5ubuntu1",
|
121
|
+
"5.9p1-4ubuntu1",
|
122
|
+
"5.9p1-3ubuntu1",
|
123
|
+
"5.9p1-2ubuntu2",
|
124
|
+
"5.9p1-2ubuntu1",
|
125
|
+
"5.9p1-1ubuntu1"],
|
126
|
+
"12.10" =>
|
127
|
+
["6.0p1-3ubuntu1.2",
|
128
|
+
"6.0p1-3ubuntu1.1",
|
129
|
+
"6.0p1-3ubuntu1",
|
130
|
+
"6.0p1-2ubuntu1",
|
131
|
+
"6.0p1-1ubuntu1"],
|
132
|
+
"13.04" => ["6.1p1-1ubuntu1"],
|
133
|
+
"13.10" =>
|
134
|
+
["6.2p2-6ubuntu0.5",
|
135
|
+
"6.2p2-6ubuntu0.4",
|
136
|
+
"6.2p2-6ubuntu0.3",
|
137
|
+
"6.2p2-6ubuntu0.2",
|
138
|
+
"6.2p2-6ubuntu0.1"],
|
139
|
+
"14.04" =>
|
140
|
+
["6.6p1-2ubuntu2.8",
|
141
|
+
"6.6p1-2ubuntu2.7",
|
142
|
+
"6.6p1-2ubuntu2.6",
|
143
|
+
"6.6p1-2ubuntu2.5",
|
144
|
+
"6.6p1-2ubuntu2.4",
|
145
|
+
"6.6p1-2ubuntu2.3",
|
146
|
+
"6.6p1-2ubuntu2.2",
|
147
|
+
"6.6p1-2ubuntu2",
|
148
|
+
"6.6p1-2ubuntu1",
|
149
|
+
"6.2p2-6ubuntu1"],
|
150
|
+
"14.10" => ["6.6p1-5build1"],
|
151
|
+
"15.04" =>
|
152
|
+
["6.7p1-5ubuntu1.4",
|
153
|
+
"6.7p1-5ubuntu1.3",
|
154
|
+
"6.7p1-5ubuntu1.2",
|
155
|
+
"6.7p1-5ubuntu1"],
|
156
|
+
"15.10" =>
|
157
|
+
["6.9p1-2ubuntu0.2", "6.9p1-2ubuntu0.1", "6.7p1-6ubuntu2", "6.7p1-6ubuntu1"],
|
158
|
+
"16.04" => ["7.2p2-4ubuntu2.1", "7.2p2-4ubuntu2", "7.2p2-4ubuntu1"],
|
159
|
+
"16.10" => []
|
160
|
+
}
|
161
|
+
|
162
|
+
def initialize(banner)
|
163
|
+
@banner = banner
|
164
|
+
@version = Ubuntu::Version.new(ubuntu_version_guess)
|
165
|
+
end
|
166
|
+
|
4
167
|
def common
|
5
168
|
"ubuntu"
|
6
169
|
end
|
7
170
|
|
171
|
+
def fingerprints
|
172
|
+
OS::Ubuntu::FINGERPRINTS
|
173
|
+
end
|
174
|
+
|
175
|
+
def ubuntu_version
|
176
|
+
@version
|
177
|
+
end
|
178
|
+
|
179
|
+
def ubuntu_version_guess
|
180
|
+
possible_versions = []
|
181
|
+
OS::Ubuntu::FINGERPRINTS.keys.each do |ubuntu_version|
|
182
|
+
OS::Ubuntu::FINGERPRINTS[ubuntu_version].uniq.each do |banner|
|
183
|
+
openssh_ps, ubuntu_sig = banner.split("-")
|
184
|
+
openssh_version = openssh_ps
|
185
|
+
# If the version is like 6.6p1, deduce that
|
186
|
+
if openssh_ps.include?("p")
|
187
|
+
openssh_version = openssh_ps.split("p")[0]
|
188
|
+
end
|
189
|
+
if @banner.include?("OpenSSH_#{openssh_version}") and @banner.include?(ubuntu_sig)
|
190
|
+
possible_versions << ubuntu_version
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
possible_versions.uniq!
|
195
|
+
if possible_versions.length > 0
|
196
|
+
return possible_versions.join("|")
|
197
|
+
end
|
198
|
+
return nil
|
199
|
+
end
|
200
|
+
|
8
201
|
def cpe
|
9
|
-
"o:canonical:ubuntu"
|
202
|
+
"o:canonical:ubuntu:#{@version}"
|
10
203
|
end
|
11
204
|
end
|
12
205
|
end
|
data/lib/ssh_scan/protocol.rb
CHANGED
@@ -7,9 +7,9 @@ module SSHScan
|
|
7
7
|
class KeyExchangeInit < BinData::Record
|
8
8
|
endian :big
|
9
9
|
uint32 :packet_length
|
10
|
-
uint8 :padding_length
|
11
|
-
uint8 :message_code
|
12
|
-
string :
|
10
|
+
uint8 :padding_length, :initial_value => 4
|
11
|
+
uint8 :message_code, :initial_value => 20
|
12
|
+
string :cookie_string, :length => 16
|
13
13
|
uint32 :kex_algorithms_length
|
14
14
|
string :key_algorithms_string, :length => lambda { self.kex_algorithms_length }
|
15
15
|
uint32 :server_host_key_algorithms_length
|
@@ -32,50 +32,155 @@ module SSHScan
|
|
32
32
|
string :languages_server_to_client_string, :length => lambda { self.languages_server_to_client_length }
|
33
33
|
uint8 :kex_first_packet_follows
|
34
34
|
uint32 :reserved
|
35
|
+
string :padding_string, :read_length => lambda { self.padding_length }
|
36
|
+
|
37
|
+
def cookie
|
38
|
+
self.cookie_string
|
39
|
+
end
|
40
|
+
|
41
|
+
def cookie=(string)
|
42
|
+
self.cookie_string = string
|
43
|
+
end
|
44
|
+
|
45
|
+
def padding
|
46
|
+
self.padding_string
|
47
|
+
end
|
48
|
+
|
49
|
+
def padding=(string)
|
50
|
+
self.padding_string = string
|
51
|
+
end
|
35
52
|
|
36
53
|
def key_algorithms
|
37
54
|
self.key_algorithms_string.split(",")
|
38
55
|
end
|
39
56
|
|
57
|
+
def parse_string_or_array(data)
|
58
|
+
case data
|
59
|
+
when String
|
60
|
+
string = data
|
61
|
+
when Array
|
62
|
+
string = data.join(",")
|
63
|
+
else
|
64
|
+
raise ArgumentError
|
65
|
+
end
|
66
|
+
|
67
|
+
return string
|
68
|
+
end
|
69
|
+
|
70
|
+
def key_algorithms=(data)
|
71
|
+
string = parse_string_or_array(data)
|
72
|
+
self.key_algorithms_string = string
|
73
|
+
self.kex_algorithms_length = string.size
|
74
|
+
recalc_packet_length
|
75
|
+
end
|
76
|
+
|
40
77
|
def server_host_key_algorithms
|
41
78
|
self.server_host_key_algorithms_string.split(",")
|
42
79
|
end
|
43
80
|
|
81
|
+
def server_host_key_algorithms=(data)
|
82
|
+
string = parse_string_or_array(data)
|
83
|
+
self.server_host_key_algorithms_string = string
|
84
|
+
self.server_host_key_algorithms_length = string.size
|
85
|
+
recalc_packet_length
|
86
|
+
end
|
87
|
+
|
44
88
|
def encryption_algorithms_client_to_server
|
45
89
|
self.encryption_algorithms_client_to_server_string.split(",")
|
46
90
|
end
|
47
91
|
|
92
|
+
def encryption_algorithms_client_to_server=(data)
|
93
|
+
string = parse_string_or_array(data)
|
94
|
+
self.encryption_algorithms_client_to_server_string = string
|
95
|
+
self.encryption_algorithms_client_to_server_length = string.size
|
96
|
+
recalc_packet_length
|
97
|
+
end
|
98
|
+
|
48
99
|
def encryption_algorithms_server_to_client
|
49
100
|
self.encryption_algorithms_server_to_client_string.split(",")
|
50
101
|
end
|
51
102
|
|
103
|
+
def encryption_algorithms_server_to_client=(data)
|
104
|
+
string = parse_string_or_array(data)
|
105
|
+
self.encryption_algorithms_server_to_client_string = string
|
106
|
+
self.encryption_algorithms_server_to_client_length = string.size
|
107
|
+
recalc_packet_length
|
108
|
+
end
|
109
|
+
|
52
110
|
def mac_algorithms_client_to_server
|
53
111
|
self.mac_algorithms_client_to_server_string.split(",")
|
54
112
|
end
|
55
113
|
|
114
|
+
def mac_algorithms_client_to_server=(data)
|
115
|
+
string = parse_string_or_array(data)
|
116
|
+
self.mac_algorithms_client_to_server_string = string
|
117
|
+
self.mac_algorithms_client_to_server_length = string.size
|
118
|
+
recalc_packet_length
|
119
|
+
end
|
120
|
+
|
56
121
|
def mac_algorithms_server_to_client
|
57
122
|
self.mac_algorithms_server_to_client_string.split(",")
|
58
123
|
end
|
59
124
|
|
125
|
+
def mac_algorithms_server_to_client=(data)
|
126
|
+
string = parse_string_or_array(data)
|
127
|
+
self.mac_algorithms_server_to_client_string = string
|
128
|
+
self.mac_algorithms_server_to_client_length = string.size
|
129
|
+
recalc_packet_length
|
130
|
+
end
|
131
|
+
|
60
132
|
def compression_algorithms_client_to_server
|
61
133
|
self.compression_algorithms_client_to_server_string.split(",")
|
62
134
|
end
|
63
135
|
|
136
|
+
def compression_algorithms_client_to_server=(data)
|
137
|
+
string = parse_string_or_array(data)
|
138
|
+
self.compression_algorithms_client_to_server_string = string
|
139
|
+
self.compression_algorithms_client_to_server_length = string.size
|
140
|
+
recalc_packet_length
|
141
|
+
end
|
142
|
+
|
64
143
|
def compression_algorithms_server_to_client
|
65
|
-
self.
|
144
|
+
self.compression_algorithms_server_to_client_string.split(",")
|
145
|
+
end
|
146
|
+
|
147
|
+
def compression_algorithms_server_to_client=(data)
|
148
|
+
string = parse_string_or_array(data)
|
149
|
+
self.compression_algorithms_server_to_client_string = string
|
150
|
+
self.compression_algorithms_server_to_client_length = string.size
|
151
|
+
recalc_packet_length
|
66
152
|
end
|
67
153
|
|
68
154
|
def languages_client_to_server
|
69
155
|
self.languages_client_to_server_string.split(",")
|
70
156
|
end
|
71
157
|
|
158
|
+
def languages_client_to_server=(data)
|
159
|
+
string = parse_string_or_array(data)
|
160
|
+
self.languages_client_to_server_string = string
|
161
|
+
self.languages_client_to_server_length = string.size
|
162
|
+
recalc_packet_length
|
163
|
+
end
|
164
|
+
|
72
165
|
def languages_server_to_client
|
73
166
|
self.languages_server_to_client_string.split(",")
|
74
167
|
end
|
75
168
|
|
169
|
+
def languages_server_to_client=(data)
|
170
|
+
string = parse_string_or_array(data)
|
171
|
+
self.languages_server_to_client_string = string
|
172
|
+
self.languages_server_to_client_length = string.size
|
173
|
+
recalc_packet_length
|
174
|
+
end
|
175
|
+
|
176
|
+
def recalc_packet_length
|
177
|
+
self.packet_length = self.to_binary_s.size - self.padding_length
|
178
|
+
end
|
179
|
+
|
76
180
|
# Summarize as Hash
|
77
181
|
def to_hash
|
78
182
|
{
|
183
|
+
:cookie => self.cookie.hexify,
|
79
184
|
:key_algorithms => key_algorithms,
|
80
185
|
:server_host_key_algorithms => server_host_key_algorithms,
|
81
186
|
:encryption_algorithms_client_to_server => encryption_algorithms_client_to_server,
|
@@ -89,6 +194,23 @@ module SSHScan
|
|
89
194
|
}
|
90
195
|
end
|
91
196
|
|
197
|
+
def self.from_hash(opts)
|
198
|
+
kex_init = SSHScan::KeyExchangeInit.new()
|
199
|
+
kex_init.cookie = opts[:cookie]
|
200
|
+
kex_init.padding = opts[:padding]
|
201
|
+
kex_init.key_algorithms = opts[:key_algorithms]
|
202
|
+
kex_init.server_host_key_algorithms = opts[:server_host_key_algorithms]
|
203
|
+
kex_init.encryption_algorithms_client_to_server = opts[:encryption_algorithms_client_to_server]
|
204
|
+
kex_init.encryption_algorithms_server_to_client = opts[:encryption_algorithms_server_to_client]
|
205
|
+
kex_init.mac_algorithms_client_to_server = opts[:mac_algorithms_client_to_server]
|
206
|
+
kex_init.mac_algorithms_server_to_client = opts[:mac_algorithms_server_to_client]
|
207
|
+
kex_init.compression_algorithms_client_to_server = opts[:compression_algorithms_client_to_server]
|
208
|
+
kex_init.compression_algorithms_server_to_client = opts[:compression_algorithms_server_to_client]
|
209
|
+
kex_init.languages_client_to_server = opts[:languages_client_to_server]
|
210
|
+
kex_init.languages_server_to_client = opts[:languages_server_to_client]
|
211
|
+
return kex_init
|
212
|
+
end
|
213
|
+
|
92
214
|
# Summarize as JSON
|
93
215
|
def to_json
|
94
216
|
self.to_hash.to_json
|
data/lib/ssh_scan/scan_engine.rb
CHANGED
@@ -5,15 +5,38 @@ require 'net/ssh'
|
|
5
5
|
module SSHScan
|
6
6
|
class ScanEngine
|
7
7
|
|
8
|
-
def scan_target(
|
9
|
-
port =
|
8
|
+
def scan_target(socket, opts)
|
9
|
+
target, port = socket.chomp.split(':')
|
10
10
|
policy = opts[:policy_file]
|
11
11
|
timeout = opts[:timeout]
|
12
|
+
result = []
|
12
13
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
14
|
+
if target.fqdn?
|
15
|
+
if target.resolve_fqdn_as_ipv6.nil?
|
16
|
+
client = SSHScan::Client.new(target.resolve_fqdn_as_ipv4.to_s, port, timeout)
|
17
|
+
client.connect()
|
18
|
+
result = client.get_kex_result()
|
19
|
+
result[:hostname] = target
|
20
|
+
return result if result.include?(:error)
|
21
|
+
else
|
22
|
+
client = SSHScan::Client.new(target.resolve_fqdn_as_ipv6.to_s, port, timeout)
|
23
|
+
client.connect()
|
24
|
+
result = client.get_kex_result()
|
25
|
+
if result.include?(:error)
|
26
|
+
client = SSHScan::Client.new(target.resolve_fqdn_as_ipv4.to_s, port, timeout)
|
27
|
+
client.connect()
|
28
|
+
result = client.get_kex_result()
|
29
|
+
result[:hostname] = target
|
30
|
+
return result if result.include?(:error)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
else
|
34
|
+
client = SSHScan::Client.new(target, port, timeout)
|
35
|
+
client.connect()
|
36
|
+
result = client.get_kex_result()
|
37
|
+
result[:hostname] = ""
|
38
|
+
return result if result.include?(:error)
|
39
|
+
end
|
17
40
|
|
18
41
|
# Connect and get results (Net-SSH)
|
19
42
|
begin
|
@@ -81,18 +104,18 @@ module SSHScan
|
|
81
104
|
end
|
82
105
|
|
83
106
|
def scan(opts)
|
84
|
-
|
107
|
+
sockets = opts[:sockets]
|
85
108
|
threads = opts[:threads] || 5
|
86
109
|
|
87
110
|
results = []
|
88
111
|
|
89
112
|
work_queue = Queue.new
|
90
|
-
|
113
|
+
sockets.each {|x| work_queue.push x }
|
91
114
|
workers = (0...threads).map do |worker_num|
|
92
115
|
Thread.new do
|
93
116
|
begin
|
94
|
-
while
|
95
|
-
results << scan_target(
|
117
|
+
while socket = work_queue.pop(true)
|
118
|
+
results << scan_target(socket, opts)
|
96
119
|
end
|
97
120
|
rescue ThreadError => e
|
98
121
|
raise e unless e.to_s.match(/queue empty/)
|
@@ -3,9 +3,10 @@ require 'string_ext'
|
|
3
3
|
|
4
4
|
module SSHScan
|
5
5
|
class TargetParser
|
6
|
-
def enumerateIPRange(ip)
|
6
|
+
def enumerateIPRange(ip, port = "22")
|
7
7
|
if ip.fqdn?
|
8
|
-
|
8
|
+
socket = ip.concat(":").concat(port.to_s)
|
9
|
+
return [socket]
|
9
10
|
else
|
10
11
|
if ip.include? "-"
|
11
12
|
octets = ip.split('.')
|
@@ -13,15 +14,18 @@ module SSHScan
|
|
13
14
|
lower = NetAddr::CIDR.create(octets.join('.') + "." + range[0])
|
14
15
|
upper = NetAddr::CIDR.create(octets.join('.') + "." + range[1])
|
15
16
|
ip_array = NetAddr.range(lower, upper,:Inclusive => true)
|
17
|
+
ip_array.map! { |ip| ip.concat(":").concat(port.to_s) }
|
16
18
|
return ip_array
|
17
19
|
elsif ip.include? "/"
|
18
20
|
cidr = NetAddr::CIDR.create(ip)
|
19
21
|
ip_array = cidr.enumerate
|
20
22
|
ip_array.delete(cidr.network)
|
21
23
|
ip_array.delete(cidr.last)
|
24
|
+
ip_array.map! { |ip| ip.concat(":").concat(port.to_s) }
|
22
25
|
return ip_array
|
23
26
|
else
|
24
|
-
|
27
|
+
socket = ip.concat(":").concat(port.to_s)
|
28
|
+
return [socket]
|
25
29
|
end
|
26
30
|
end
|
27
31
|
end
|
data/lib/ssh_scan/version.rb
CHANGED
data/lib/string_ext.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'ipaddr'
|
2
|
+
require 'resolv'
|
2
3
|
|
3
4
|
# Extend string to include some helpful stuff
|
4
5
|
class String
|
@@ -6,6 +7,10 @@ class String
|
|
6
7
|
[self].pack("H*")
|
7
8
|
end
|
8
9
|
|
10
|
+
def hexify
|
11
|
+
self.each_byte.map { |b| b.to_s(16).rjust(2,'0') }.join
|
12
|
+
end
|
13
|
+
|
9
14
|
def ip_addr?
|
10
15
|
begin
|
11
16
|
IPAddr.new(self)
|
@@ -18,6 +23,22 @@ class String
|
|
18
23
|
return true
|
19
24
|
end
|
20
25
|
|
26
|
+
def resolve_fqdn_as_ipv6
|
27
|
+
Resolv::DNS.open do |dns|
|
28
|
+
ress = dns.getresources self, Resolv::DNS::Resource::IN::AAAA
|
29
|
+
temp = ress.map { |r| r.address }
|
30
|
+
return temp[0]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def resolve_fqdn_as_ipv4
|
35
|
+
Resolv::DNS.open do |dns|
|
36
|
+
ress = dns.getresources self, Resolv::DNS::Resource::IN::A
|
37
|
+
temp = ress.map { |r| r.address }
|
38
|
+
return temp[0]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
21
42
|
def resolve_fqdn
|
22
43
|
@fqdn ||= TCPSocket.gethostbyname(self)[3]
|
23
44
|
end
|
data/ssh_scan.gemspec
CHANGED
@@ -4,7 +4,7 @@ require 'ssh_scan/version'
|
|
4
4
|
Gem::Specification.new do |s|
|
5
5
|
s.name = 'ssh_scan'
|
6
6
|
s.version = SSHScan::VERSION
|
7
|
-
s.authors = ["Jonathan Claudius"]
|
7
|
+
s.authors = ["Jonathan Claudius", "Jinank Jain"]
|
8
8
|
s.date = Date.today.to_s
|
9
9
|
s.email = 'claudijd@yahoo.com'
|
10
10
|
s.platform = Gem::Platform::RUBY
|
@@ -30,6 +30,7 @@ Gem::Specification.new do |s|
|
|
30
30
|
s.add_dependency('net-ssh')
|
31
31
|
s.add_dependency('netaddr')
|
32
32
|
s.add_dependency('timeout')
|
33
|
+
s.add_dependency('json')
|
33
34
|
s.add_development_dependency('pry')
|
34
35
|
s.add_development_dependency('rspec', '~> 3.0')
|
35
36
|
s.add_development_dependency('rspec-its', '~> 1.2')
|
metadata
CHANGED
@@ -1,14 +1,15 @@
|
|
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.11
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jonathan Claudius
|
8
|
+
- Jinank Jain
|
8
9
|
autorequire:
|
9
10
|
bindir: bin
|
10
11
|
cert_chain: []
|
11
|
-
date: 2016-08-
|
12
|
+
date: 2016-08-31 00:00:00.000000000 Z
|
12
13
|
dependencies:
|
13
14
|
- !ruby/object:Gem::Dependency
|
14
15
|
name: bindata
|
@@ -66,6 +67,20 @@ dependencies:
|
|
66
67
|
- - ">="
|
67
68
|
- !ruby/object:Gem::Version
|
68
69
|
version: '0'
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
name: json
|
72
|
+
requirement: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
type: :runtime
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - ">="
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0'
|
69
84
|
- !ruby/object:Gem::Dependency
|
70
85
|
name: pry
|
71
86
|
requirement: !ruby/object:Gem::Requirement
|
@@ -139,7 +154,6 @@ files:
|
|
139
154
|
- bin/ssh_scan
|
140
155
|
- lib/ssh_scan.rb
|
141
156
|
- lib/ssh_scan/banner.rb
|
142
|
-
- lib/ssh_scan/basic_server.rb
|
143
157
|
- lib/ssh_scan/client.rb
|
144
158
|
- lib/ssh_scan/constants.rb
|
145
159
|
- lib/ssh_scan/error.rb
|
@@ -1,15 +0,0 @@
|
|
1
|
-
require 'sinatra'
|
2
|
-
require 'ssh_scan'
|
3
|
-
|
4
|
-
get '/api/v1/scan/:ip' do
|
5
|
-
if ip = params['ip']
|
6
|
-
policy = SSHScan::Policy.from_file(File.expand_path("../../policies/mozilla_intermediate.yml", __FILE__))
|
7
|
-
scan_engine = SSHScan::ScanEngine.new()
|
8
|
-
result = scan_engine.scan(ip, 22, policy)
|
9
|
-
content_type :json
|
10
|
-
return JSON.pretty_generate(result)
|
11
|
-
else
|
12
|
-
status 500
|
13
|
-
body "Error: did not supply a valid IP to be scanned"
|
14
|
-
end
|
15
|
-
end
|