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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6403790d58c64fd35088e7052eb051074a153b48
4
- data.tar.gz: 8ca8d5eaa79005c5479719672f55c44bbd806b58
3
+ metadata.gz: 0e403b742bae642fd06efb786e0a8d0e8dcbf273
4
+ data.tar.gz: 7cd2a0a4b78a5ad4cef03a380c713f853e1e7522
5
5
  SHA512:
6
- metadata.gz: 9ea2a32f03d07f1435f2582ef49bef0cb3b89a7ae60cfc7597c09989d5fa5c6dbda3eb1dd136a6fbc30fb960ff9ccb234ab15a646cf845d7fc2ead316cd268d6
7
- data.tar.gz: 67e6065dbccd032dd3db6356397d24179db910570d3662f024ca3587ac4c7951d4ec4f6651671cebc548b2fbdae93f13025386c911fc386fb203f3b15d618429
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 'ssh_scan'
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
- :targets => [],
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[:targets] += target_parser.enumerateIPRange(ip)
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 |ip|
41
- options[:targets] += target_parser.enumerateIPRange(ip)
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[:port] = port.to_i
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[:targets].nil?
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[:targets].each do |target|
108
- unless target.ip_addr? || target.fqdn?
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: #{options[:targets]} is not a valid target"
130
+ puts "\nReason: #{socket} is not a valid target"
111
131
  exit 1
112
132
  end
113
133
  end
114
134
 
115
- unless (0..65535).include?(options[:port])
116
- puts opt_parser.help
117
- puts "\nReason: port supplied is not within acceptable range"
118
- exit 1
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])
@@ -33,7 +33,7 @@ module SSHScan
33
33
  def os_guess()
34
34
  case @string
35
35
  when /Ubuntu/i
36
- return SSHScan::OS::Ubuntu.new
36
+ return SSHScan::OS::Ubuntu.new(@string)
37
37
  when /CentOS/i
38
38
  return SSHScan::OS::CentOS.new
39
39
  when /RHEL|RedHat/i
@@ -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::DEFAULT_KEY_INIT_RAW
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(@ip, @port, connect_timeout: @timeout)
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[:hostname] = @target.fqdn? ? @target : ""
46
- result[:ip] = @ip
39
+ result[:ip] = @target
47
40
  result[:port] = @port
48
41
 
49
42
  if !@sock
@@ -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
- DEFAULT_KEY_INIT_RAW = ("d8eb97b11b6cacbc3285473f08004500019ceccf40004006663fc0a80a7c3ff" +
9
- "5db33cdfd0016982e6062988da97e801810154d2b00000101080a03a6399f3d" +
10
- "f735d6000001640414e33f813f8cdcc6b00a3d852ec1aea4980000001a64696" +
11
- "66669652d68656c6c6d616e2d67726f7570312d736861310000000f7373682d" +
12
- "6473732c7373682d727361000000576165733132382d6362632c336465732d6" +
13
- "362632c626c6f77666973682d6362632c6165733139322d6362632c61657332" +
14
- "35362d6362632c6165733132382d6374722c6165733139322d6374722c61657" +
15
- "33235362d637472000000576165733132382d6362632c336465732d6362632c" +
16
- "626c6f77666973682d6362632c6165733139322d6362632c6165733235362d6" +
17
- "362632c6165733132382d6374722c6165733139322d6374722c616573323536" +
18
- "2d63747200000021686d61632d6d64352c686d61632d736861312c686d61632" +
19
- "d726970656d6431363000000021686d61632d6d64352c686d61632d73686131" +
20
- "2c686d61632d726970656d64313630000000046e6f6e65000000046e6f6e650" +
21
- "00000000000000000000000006e05b3b4").unhexify
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
@@ -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
@@ -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 :cookie, :length => 16
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.compression_algorithms_client_to_server_string.split(",")
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
@@ -5,15 +5,38 @@ require 'net/ssh'
5
5
  module SSHScan
6
6
  class ScanEngine
7
7
 
8
- def scan_target(target, opts)
9
- port = opts[: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
- client = SSHScan::Client.new(target, port, timeout)
14
- client.connect()
15
- result = client.get_kex_result()
16
- return result if result.include?(:error)
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
- targets = opts[:targets]
107
+ sockets = opts[:sockets]
85
108
  threads = opts[:threads] || 5
86
109
 
87
110
  results = []
88
111
 
89
112
  work_queue = Queue.new
90
- targets.each {|x| work_queue.push x }
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 target = work_queue.pop(true)
95
- results << scan_target(target, opts)
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
- return [ip]
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
- return [ip]
27
+ socket = ip.concat(":").concat(port.to_s)
28
+ return [socket]
25
29
  end
26
30
  end
27
31
  end
@@ -1,3 +1,3 @@
1
1
  module SSHScan
2
- VERSION = '0.0.10'
2
+ VERSION = '0.0.11'
3
3
  end
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.10
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-30 00:00:00.000000000 Z
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