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