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