ruby-nessus2 2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/.drone.yml +51 -0
  3. data/.gitignore +5 -0
  4. data/.rspec +1 -0
  5. data/.rubocop.yml +4 -0
  6. data/.rubocop_todo.yml +124 -0
  7. data/.travis.yml +13 -0
  8. data/.yardopts +1 -0
  9. data/Gemfile +6 -0
  10. data/Gemfile.lock +75 -0
  11. data/LICENSE.txt +20 -0
  12. data/README.md +181 -0
  13. data/Rakefile +21 -0
  14. data/bin/recess +10 -0
  15. data/examples/example.rb +46 -0
  16. data/examples/example_bid.rb +28 -0
  17. data/examples/example_cpe.rb +28 -0
  18. data/examples/example_cve.rb +36 -0
  19. data/examples/example_v1.nessus +1 -0
  20. data/examples/example_v2.nessus +2076 -0
  21. data/examples/example_v3.nessus +7449 -0
  22. data/lib/ruby-nessus.rb +5 -0
  23. data/lib/ruby-nessus/cli.rb +126 -0
  24. data/lib/ruby-nessus/log.rb +84 -0
  25. data/lib/ruby-nessus/parse.rb +46 -0
  26. data/lib/ruby-nessus/ruby-nessus.rb +6 -0
  27. data/lib/ruby-nessus/version.rb +5 -0
  28. data/lib/ruby-nessus/version1/event.rb +85 -0
  29. data/lib/ruby-nessus/version1/host.rb +267 -0
  30. data/lib/ruby-nessus/version1/port.rb +84 -0
  31. data/lib/ruby-nessus/version1/scan.rb +404 -0
  32. data/lib/ruby-nessus/version2/event.rb +410 -0
  33. data/lib/ruby-nessus/version2/host.rb +522 -0
  34. data/lib/ruby-nessus/version2/port.rb +75 -0
  35. data/lib/ruby-nessus/version2/scan.rb +393 -0
  36. data/ruby-nessus.gemspec +28 -0
  37. data/spec/ruby-nessus/parse_spec.rb +40 -0
  38. data/spec/ruby-nessus/version1/event_spec.rb +69 -0
  39. data/spec/ruby-nessus/version1/host_spec.rb +75 -0
  40. data/spec/ruby-nessus/version1/scan_spec.rb +97 -0
  41. data/spec/ruby-nessus/version2/event_spec.rb +225 -0
  42. data/spec/ruby-nessus/version2/host_spec.rb +148 -0
  43. data/spec/ruby-nessus/version2/scan_spec.rb +96 -0
  44. data/spec/ruby-nessus/version_spec.rb +11 -0
  45. data/spec/spec_fixtures/example_v1.nessus +1 -0
  46. data/spec/spec_fixtures/example_v2.nessus +2080 -0
  47. data/spec/spec_fixtures/example_v_wrong.nessus +3 -0
  48. data/spec/spec_fixtures/xml.rb +15 -0
  49. data/spec/spec_helper.rb +7 -0
  50. metadata +190 -0
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyNessus
4
+ module Version2
5
+ class Port
6
+ # Port Service
7
+ attr_reader :service
8
+ # Port number
9
+ attr_reader :number
10
+ # Port Protocol
11
+ attr_reader :protocol
12
+
13
+ #
14
+ # Creates A New Port Object
15
+ #
16
+ # @param [String] number The port number.
17
+ #
18
+ # @param [String] service The port service.
19
+ #
20
+ # @param [String] protocol The port protocol.
21
+ #
22
+ # @example
23
+ # Port.new(number, service, )
24
+ #
25
+ def initialize(number, service, protocol)
26
+ @number = number
27
+ @service = service
28
+ @protocol = protocol
29
+ end
30
+
31
+ #
32
+ # Return true if the port protocol is tcp.
33
+ #
34
+ # @return [Boolean]
35
+ # Return True If The Port Protocol Is TCP.
36
+ #
37
+ def tcp?
38
+ @protocol.to_s == 'tcp'
39
+ end
40
+
41
+ #
42
+ # Return true if the port protocol is udp.
43
+ #
44
+ # @return [Boolean]
45
+ # Return True If The Port Protocol Is UDP.
46
+ #
47
+ def udp?
48
+ @protocol.to_s == 'udp'
49
+ end
50
+
51
+ #
52
+ # Return true if the port protocol is icmp.
53
+ #
54
+ # @return [Boolean]
55
+ # Return True If The Port Protocol Is ICMP.
56
+ #
57
+ def icmp?
58
+ @protocol.to_s == 'icmp'
59
+ end
60
+
61
+ #
62
+ # Return the port as a string.
63
+ #
64
+ # @return [String]
65
+ # Return The Port As A String
66
+ #
67
+ # @example
68
+ # port.to_s #=> https (443/tcp)
69
+ #
70
+ def to_s
71
+ "#{@service} (#{@number}/#{@protocol})"
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,393 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ruby-nessus/version2/host'
4
+ require 'ruby-nessus/version2/event'
5
+
6
+ module RubyNessus
7
+ # .Nessus Version 2 Schema
8
+ module Version2
9
+ # File to parse
10
+ attr_reader :file
11
+
12
+ class XML
13
+ include Enumerable
14
+
15
+ #
16
+ # Creates a new .Nessus (XML) object to be parser
17
+ #
18
+ # @param [String] file The Nessus xml results file to parse.
19
+ #
20
+ # @yield [prog] If a block is given, it will be passed the newly
21
+ # created XML object.
22
+ # @yieldparam [XML] prog The newly created XML object.
23
+ #
24
+ # @example
25
+ # RubyNessus::XML.new(nessus_scan_file) do |scan|
26
+ # scan.report_name
27
+ # end
28
+ #
29
+ def initialize(xml)
30
+ @xml = xml
31
+ raise 'Error: Not A Version 2.0 .Nessus file.' unless @xml.at('NessusClientData_v2')
32
+ end
33
+
34
+ def version
35
+ 2
36
+ end
37
+
38
+ #
39
+ # Return the nessus report title.
40
+ #
41
+ # @return [String]
42
+ # The Nessus Report Title
43
+ #
44
+ # @example
45
+ # scan.report_name #=> "My Super Cool Nessus Report"
46
+ #
47
+ def title
48
+ @report_name ||= @xml.at('Report/@name').inner_text
49
+ end
50
+
51
+ #
52
+ # Return the nessus scan policy name. When creating a nessus policy this is usually the title field.
53
+ #
54
+ # @return [String]
55
+ # The Nessus Scan Policy Name
56
+ #
57
+ def policy_title
58
+ @policy_name ||= @xml.at('//Policy/policyName').inner_text
59
+ end
60
+
61
+ #
62
+ # Return the nessus scan policy comments. This is the description field when creating a new policy with the Nessus GUI client.
63
+ #
64
+ # @return [String]
65
+ # The Nessus Scan Policy Comments
66
+ #
67
+ def policy_notes
68
+ @policy_notes ||= @xml.at('//Policy/policyComments').inner_text
69
+ end
70
+
71
+ #
72
+ # Return the hosts the were targeted for the initial scan.
73
+ # These are the hosts that were inputed when creating the scan.
74
+ #
75
+ # @return [Array<String>]
76
+ # Array of hosts
77
+ #
78
+ def target_hosts
79
+ @xml.xpath('//Preferences/ServerPreferences/preference').each do |element|
80
+ return element.children[3].inner_text.split(',') if element.children[1].inner_text == 'TARGET'
81
+ end
82
+ nil
83
+ end
84
+
85
+ #
86
+ # Creates a new Host object to be parser
87
+ #
88
+ # @yield [prog] If a block is given, it will be passed the newly
89
+ # created Host object.
90
+ # @yieldparam [XML] prog The newly created Host object.
91
+ #
92
+ # @example
93
+ # scan.hosts do |host|
94
+ # puts host.hostname
95
+ # end
96
+ #
97
+ def each_host(&block)
98
+ hosts.each(&block)
99
+ end
100
+
101
+ #
102
+ # Parses the hosts of the scan.
103
+ #
104
+ # @return [Array<String>]
105
+ # The Hosts of the scan.
106
+ #
107
+ def hosts
108
+ @xml.xpath('//ReportHost').map do |host|
109
+ Host.new(host)
110
+ end
111
+ end
112
+
113
+ #
114
+ # Return the nessus scan host count.
115
+ #
116
+ # @return [Integer]
117
+ # The Nessus Scan Host Count
118
+ #
119
+ # @example
120
+ # scan.host_count #=> 23
121
+ #
122
+ def host_count
123
+ each_host.size
124
+ end
125
+
126
+ #
127
+ # Retunrs an array of all unique ports.
128
+ #
129
+ # @return [Array]
130
+ #
131
+ # @example
132
+ # scan.unique_ports #=> 234
133
+ #
134
+ def unique_ports
135
+ unless @unique_ports
136
+ @unique_ports = []
137
+ @xml.xpath('//ReportItem').each do |port|
138
+ @unique_ports << port['port']
139
+ end
140
+ @unique_ports.uniq!
141
+ @unique_ports.sort!
142
+ end
143
+ end
144
+
145
+ #
146
+ # Return the Open Ports count.
147
+ #
148
+ # @return [Integer]
149
+ # The Open Ports Count
150
+ #
151
+ # @example
152
+ # scan.open_ports_count #=> 1203
153
+ #
154
+ def open_ports_count
155
+ count_stats[:open_ports].to_i
156
+ end
157
+
158
+ #
159
+ # Return the TCP Event Count.
160
+ #
161
+ # @return [Integer]
162
+ # The TCP Event Count
163
+ #
164
+ # @example
165
+ # scan.tcp_count #=> 3
166
+ #
167
+ def tcp_count
168
+ count_stats[:tcp].to_i
169
+ end
170
+
171
+ #
172
+ # Return the UDP Event Count.
173
+ #
174
+ # @return [Integer]
175
+ # The UDP Event Count
176
+ #
177
+ # @example
178
+ # scan.udp_count #=> 3
179
+ #
180
+ def udp_count
181
+ count_stats[:udp].to_i
182
+ end
183
+
184
+ #
185
+ # Return the ICMP Event Count.
186
+ #
187
+ # @return [Integer]
188
+ # The ICMP Event Count
189
+ #
190
+ # @example
191
+ # scan.icmp_count #=> 3
192
+ #
193
+ def icmp_count
194
+ count_stats[:icmp].to_i
195
+ end
196
+
197
+ #
198
+ # Return the informational severity count.
199
+ #
200
+ # @return [Integer]
201
+ # The Informational Severity Count
202
+ #
203
+ # @example
204
+ # scan.informational_severity_count #=> 1203
205
+ #
206
+ def informational_severity_count
207
+ count_stats[:informational].to_i
208
+ end
209
+
210
+ #
211
+ # Return the Critical severity count.
212
+ #
213
+ # @return [Integer]
214
+ # The Critical Severity Count
215
+ #
216
+ # @example
217
+ # scan.critical_severity_count #=> 10
218
+ #
219
+ def critical_severity_count
220
+ count_stats[:critical].to_i
221
+ end
222
+
223
+ #
224
+ # Return the High severity count.
225
+ #
226
+ # @return [Integer]
227
+ # The High Severity Count
228
+ #
229
+ # @example
230
+ # scan.high_severity_count #=> 10
231
+ #
232
+ def high_severity_count
233
+ count_stats[:high].to_i
234
+ end
235
+
236
+ #
237
+ # Return the Medium severity count.
238
+ #
239
+ # @return [Integer]
240
+ # The Medium Severity Count
241
+ #
242
+ # @example
243
+ # scan.medium_severity_count #=> 234
244
+ #
245
+ def medium_severity_count
246
+ count_stats[:medium].to_i
247
+ end
248
+
249
+ #
250
+ # Return the Low severity count.
251
+ #
252
+ # @return [Integer]
253
+ # The Low Severity Count
254
+ #
255
+ # @example
256
+ # scan.low_severity_count #=> 114
257
+ #
258
+ def low_severity_count
259
+ count_stats[:low].to_i
260
+ end
261
+
262
+ #
263
+ # Return the Total severity count. [critical, high, medium, low, informational]
264
+ #
265
+ # @param [true, false] argname only true or false
266
+ #
267
+ # @return [Integer]
268
+ # The Total Severity Count
269
+ #
270
+ # @example
271
+ # scan.total_event_count #=> 1561
272
+ #
273
+ def total_event_count(count_informational = nil)
274
+ if count_informational
275
+ count_stats[:all].to_i + informational_severity_count
276
+ else
277
+ count_stats[:all].to_i
278
+ end
279
+ end
280
+
281
+ #
282
+ # Return the Total severity count.
283
+ #
284
+ # @param [String] severity the severity in which to calculate percentage for.
285
+ #
286
+ # @param [Boolean] round round the result to the nearest whole number.
287
+ #
288
+ # @raise [ExceptionClass] One of the following severity options must be passed. [critical, high, medium, low, informational, all]
289
+ #
290
+ # @return [Integer]
291
+ # The Percentage Of Events For A Passed Severity
292
+ #
293
+ # @example
294
+ # scan.event_percentage_for("low", true) #=> 11%
295
+ #
296
+ def event_percentage_for(type, round_percentage = nil)
297
+ @sc ||= count_stats
298
+ if %w[critical high medium low tcp udp icmp all].include?(type)
299
+ calc = ((@sc[:"#{type}"].to_f / @sc[:all].to_f) * 100)
300
+ if round_percentage
301
+ return calc.round.to_s
302
+ else
303
+ return calc.to_s
304
+ end
305
+ else
306
+ raise "Error: #{type} is not an acceptable severity. Possible options include: all, tdp, udp, icmp, critical, high, medium and low."
307
+ end
308
+ end
309
+
310
+ #
311
+ # Creates a new Host object to be parser from a passed search param.
312
+ #
313
+ # @param [String] hostname the hostname to build a Host object for.
314
+ #
315
+ # @yield [prog] If a block is given, it will be passed the newly
316
+ # created Host object.
317
+ #
318
+ # @yieldparam [XML] prog The newly created Host object.
319
+ #
320
+ # @example
321
+ # scan.find_by_hostname('127.0.0.1') do |host|
322
+ # puts host.hostname
323
+ # end
324
+ #
325
+ def find_by_hostname(hostname, &block)
326
+ raise "Error: hostname can't be blank." if hostname.nil? || hostname.empty?
327
+ @xml.xpath('//ReportHost').each do |host|
328
+ next unless host['name'].match(hostname)
329
+ yield(Host.new(host)) if block
330
+ end
331
+ end
332
+
333
+ private
334
+
335
+ #
336
+ # Calculates an event hash of totals for severity counts.
337
+ #
338
+ # @return [Hash]
339
+ # The Event Totals For Severity
340
+ #
341
+ def count_stats
342
+ unless @count
343
+ @count = {}
344
+ @open_ports = 0
345
+ @tcp = 0
346
+ @udp = 0
347
+ @icmp = 0
348
+ @informational = 0
349
+ @low = 0
350
+ @medium = 0
351
+ @high = 0
352
+ @critical = 0
353
+
354
+ @xml.xpath('//ReportItem').each do |s|
355
+ case s['severity'].to_i
356
+ when 0
357
+ @informational += 1
358
+ when 1
359
+ @low += 1
360
+ when 2
361
+ @medium += 1
362
+ when 3
363
+ @high += 1
364
+ when 4
365
+ @critical += 1
366
+ end
367
+
368
+ unless s['severity'].to_i == 0
369
+ @tcp += 1 if s['protocol'] == 'tcp'
370
+ @udp += 1 if s['protocol'] == 'udp'
371
+ @icmp += 1 if s['protocol'] == 'icmp'
372
+ end
373
+
374
+ @open_ports += 1 if s['port'].to_i != 0
375
+ end
376
+
377
+ @count = { open_ports: @open_ports,
378
+ tcp: @tcp,
379
+ udp: @udp,
380
+ icmp: @icmp,
381
+ informational: @informational,
382
+ low: @low,
383
+ medium: @medium,
384
+ high: @high,
385
+ critical: @critical,
386
+ all: (@low + @medium + @high + @critical) }
387
+ end
388
+
389
+ @count
390
+ end
391
+ end
392
+ end
393
+ end