ruby-nessus2 2.0

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.
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,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyNessus
4
+ module Version1
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
+ # Raw output string from nessus
13
+ attr_reader :raw_string
14
+
15
+ # Creates A New Port Object
16
+ # @param [String] service The Port Service.
17
+ # @param [Integer] number The Port number.
18
+ # @param [String] protocol The Port protocol.
19
+ # @param [String] raw output string from nessus.
20
+ # @example
21
+ # Port.new("ssh",22,"tcp", str)
22
+ def initialize(service, number, protocol, raw_string)
23
+ @service = service
24
+ @number = number
25
+ @protocol = protocol
26
+ @raw_string = raw_string
27
+ end
28
+
29
+ # Parse A passed port string and return a Port Object.
30
+ # @return [Object]
31
+ # New Port Object
32
+ # @example
33
+ # Port.parse(port)
34
+ def self.parse(str)
35
+ @full_port = str
36
+ components = str.match(/^([^\(]+)\((\d+)\/([^\)]+)\)/)
37
+
38
+ if components
39
+ return Port.new(components[1].strip, components[2].strip, components[3].strip, str)
40
+ else
41
+ return Port.new(false, false, false, str)
42
+ end
43
+ end
44
+
45
+ # Return true iF port protocol Ii tcp.
46
+ # @return [Boolean]
47
+ # Return True If The Port Protocol Is TCP.
48
+ def tcp?
49
+ @protocol == 'tcp'
50
+ end
51
+
52
+ # Return true iF port protocol Ii udp.
53
+ # @return [Boolean]
54
+ # Return True If The Port Protocol Is UDP.
55
+ def udp?
56
+ @protocol == 'udp'
57
+ end
58
+
59
+ # Return the port as a string.
60
+ # @return [String]
61
+ # Return The Port As A String
62
+ # @example
63
+ # port.to_s #=> https (443/tcp)
64
+ def to_s
65
+ if @service && @number && @protocol
66
+ "#{@service} (#{@number}/#{@protocol})"
67
+ else
68
+ @raw_string.to_s
69
+ end
70
+ end
71
+
72
+ # Return false if the port object number is nil
73
+ # @return [Boolean]
74
+ # Return false if the port object number is nil
75
+ def number
76
+ if @number
77
+ @number
78
+ else
79
+ false
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,404 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ruby-nessus/version1/host'
4
+ require 'ruby-nessus/version1/event'
5
+
6
+ module RubyNessus
7
+ # .Nessus Version 2 Schema
8
+ module Version1
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
+ #
23
+ # @yieldparam [XML] prog The newly created XML object.
24
+ #
25
+ # @example
26
+ # RubyNessus::XML.new(nessus_scan_file) do |scan|
27
+ # scan.report_name
28
+ # end
29
+ #
30
+ def initialize(xml)
31
+ @xml = xml
32
+ raise 'Error: Not A Version 1.0 .Nessus file.' unless @xml.at('NessusClientData')
33
+ end
34
+
35
+ def version
36
+ 1
37
+ end
38
+
39
+ #
40
+ # Return the nessus report title.
41
+ #
42
+ # @return [String]
43
+ # The Nessus Report Title
44
+ #
45
+ # @example
46
+ # scan.report_name #=> "My Super Cool Nessus Report"
47
+ #
48
+ def title
49
+ @report_name ||= @xml.xpath('//NessusClientData//Report//ReportName').inner_text.split(' - ').last
50
+ end
51
+
52
+ #
53
+ # Return the nessus report time.
54
+ #
55
+ # @return [String]
56
+ # The Nessus Report Time
57
+ #
58
+ # @example
59
+ # scan.report_time #=> "09/11/08 02:21:22 AM"
60
+ #
61
+ def time
62
+ datetime = @xml.xpath('//NessusClientData//Report//ReportName').inner_text.split(' - ').first
63
+ @report_time ||= DateTime.strptime(datetime, '%y/%m/%d %I:%M:%S %p')
64
+ end
65
+
66
+ #
67
+ # Return the scan start time.
68
+ #
69
+ # @return [DateTime]
70
+ # The Nessus Scan Start Time
71
+ #
72
+ # @example
73
+ # scan.start_time #=> 'Fri Nov 11 23:36:54 1985'
74
+ #
75
+ def start_time
76
+ @start_time = DateTime.strptime(@xml.xpath('//NessusClientData//Report//StartTime').inner_text, '%a %b %d %H:%M:%S %Y')
77
+ end
78
+
79
+ #
80
+ # Return the scan stop time.
81
+ #
82
+ # @return [DateTime]
83
+ # The Nessus Scan Stop Time
84
+ #
85
+ # @example
86
+ # scan.stop_time #=> 'Mon Nov 11 23:36:54 1985'
87
+ #
88
+ def stop_time
89
+ @stop_time = DateTime.strptime(@xml.xpath('//NessusClientData//Report//StopTime').inner_text, '%a %b %d %H:%M:%S %Y')
90
+ end
91
+
92
+ #
93
+ # Return the scan run time.
94
+ #
95
+ # @return [String]
96
+ # The Nessus Scan Run Time
97
+ #
98
+ # @example
99
+ # scan.runtime #=> '2 hours 5 minutes and 16 seconds'
100
+ #
101
+ def runtime
102
+ h = (Time.parse(stop_time.to_s).strftime('%H').to_i - Time.parse(start_time.to_s).strftime('%H').to_i).to_s.delete('-')
103
+ m = (Time.parse(stop_time.to_s).strftime('%M').to_i - Time.parse(start_time.to_s).strftime('%M').to_i).to_s.delete('-')
104
+ s = (Time.parse(stop_time.to_s).strftime('%S').to_i - Time.parse(start_time.to_s).strftime('%S').to_i).to_s.delete('-')
105
+ "#{h} hours #{m} minutes and #{s} seconds"
106
+ end
107
+
108
+ #
109
+ # Return the nessus scan policy name. When creating a nessus policy this is usually the title field.
110
+ #
111
+ # @return [String]
112
+ # The Nessus Scan Policy Name
113
+ #
114
+ def policy_title
115
+ @policy_name ||= @xml.xpath('//NessusClientData//Report//policyName').inner_text
116
+ end
117
+
118
+ #
119
+ # Return the nessus scan policy comments. This is the description field when creating a new policy with the Nessus GUI client.
120
+ #
121
+ # @return [String]
122
+ # The Nessus Scan Policy Comments
123
+ #
124
+ def policy_notes
125
+ @policy_comments ||= @xml.xpath('//NessusClientData//Report//policyComments').inner_text
126
+ end
127
+
128
+ #
129
+ # Return the hosts the were targeted for the initial scan.
130
+ # These are the hosts that were inputed when creating the scan.
131
+ #
132
+ # @return [Array<String>]
133
+ # Array of hosts
134
+ #
135
+ def target_hosts
136
+ hosts = []
137
+ @xml.xpath('//Targets/Target/value').each do |element|
138
+ hosts << element.inner_text
139
+ end
140
+ hosts.sort.uniq!
141
+ end
142
+
143
+ #
144
+ # Returns and array of the plugin ids userd for the passed .nessus scan.
145
+ #
146
+ # @return [Array]
147
+ # The Nessus Scan Plugin Ids
148
+ #
149
+ # @example
150
+ # scan.plugin_ids #=> [1234,2343,9742,5452,5343,2423,1233]
151
+ #
152
+ def plugin_ids
153
+ unless @plugin_ids
154
+ @plugin_ids = []
155
+
156
+ @xml.xpath('//PluginSelection').last.text.split(';').each do |id|
157
+ @plugin_ids << id
158
+ end
159
+ end
160
+
161
+ @plugin_ids
162
+ end
163
+
164
+ #
165
+ # Returns and array of the plugin names userd for the passed .nessus scan.
166
+ #
167
+ # @return [Array]
168
+ # The Nessus Scan Plugin Names
169
+ #
170
+ # @example
171
+ # scan.plugins #=> ["PHP < 5.2.1 Multiple Vulnerabilities", "PHP < 4.4.1 / 5.0.6 Multiple Vulnerabilities"]
172
+ #
173
+ def plugins
174
+ unless @plugins
175
+ # get elements with attribute:
176
+ @plugins = []
177
+
178
+ @xml.xpath('//pluginName').each do |x|
179
+ @plugins << x.inner_text unless x.inner_text.empty?
180
+ end
181
+
182
+ @plugins.uniq!
183
+ @plugins.sort!
184
+ end
185
+
186
+ @plugins
187
+ end
188
+
189
+ #
190
+ # Creates a new Host object to be parser
191
+ #
192
+ # @yield [prog] If a block is given, it will be passed the newly
193
+ # created Host object.
194
+ #
195
+ # @yieldparam [XML] prog The newly created Host object.
196
+ #
197
+ # @example
198
+ # scan.hosts do |host|
199
+ # puts host.hostname
200
+ # end
201
+ #
202
+ def each_host(&block)
203
+ hosts = []
204
+ @xml.xpath('//ReportHost').each do |host|
205
+ hosts << host.at('HostName').inner_text if host.at('HostName').inner_text
206
+ yield(Host.new(host)) if block
207
+ end
208
+ hosts
209
+ end
210
+
211
+ #
212
+ # Parses the hosts of the scan.
213
+ #
214
+ # @return [Array<String>]
215
+ # The Hosts of the scan.
216
+ #
217
+ def hosts
218
+ to_enum(:each_host).to_a
219
+ end
220
+
221
+ #
222
+ # Return the nessus scan host count.
223
+ #
224
+ # @return [Integer]
225
+ # The Nessus Scan Host Count
226
+ #
227
+ # @example
228
+ # scan.host_count #=> 23
229
+ #
230
+ def host_count
231
+ hosts.size
232
+ end
233
+
234
+ #
235
+ # Retunrs an array of all unique ports.
236
+ #
237
+ # @return [Array]
238
+ #
239
+ # @example
240
+ # scan.unique_ports #=> 234
241
+ #
242
+ def unique_ports
243
+ return if @unique_ports
244
+
245
+ @unique_ports = []
246
+ @xml.xpath('//ReportItem//port').each do |port|
247
+ @unique_ports << port.inner_text
248
+ end
249
+ @unique_ports.uniq!
250
+ @unique_ports.sort!
251
+ end
252
+
253
+ #
254
+ # Return the informational severity count.
255
+ #
256
+ # @return [Integer]
257
+ # The Informational Severity Count
258
+ #
259
+ # @example
260
+ # scan.informational_severity_count #=> 1203
261
+ #
262
+ def open_ports_count
263
+ count_severity[:open_ports].to_i
264
+ end
265
+
266
+ #
267
+ # Return the High severity count.
268
+ #
269
+ # @return [Integer]
270
+ # The High Severity Count
271
+ #
272
+ # @example
273
+ # scan.high_severity_count #=> 10
274
+ #
275
+ def high_severity_count
276
+ count_severity[:high].to_i
277
+ end
278
+
279
+ #
280
+ # Return the Medium severity count.
281
+ #
282
+ # @return [Integer]
283
+ # The Medium Severity Count
284
+ #
285
+ # @example
286
+ # scan.medium_severity_count #=> 234
287
+ #
288
+ def medium_severity_count
289
+ count_severity[:medium].to_i
290
+ end
291
+
292
+ #
293
+ # Return the Low severity count.
294
+ #
295
+ # @return [Integer]
296
+ # The Low Severity Count
297
+ #
298
+ # @example
299
+ # scan.low_severity_count #=> 114
300
+ #
301
+ def low_severity_count
302
+ count_severity[:low].to_i
303
+ end
304
+
305
+ #
306
+ # Return the Total severity count. [high, medium, low, informational]
307
+ #
308
+ # @return [Integer]
309
+ # The Total Severity Count
310
+ #
311
+ # @example
312
+ # scan.total_event_count #=> 1561
313
+ #
314
+ def total_event_count
315
+ count_severity[:all].to_i
316
+ end
317
+
318
+ #
319
+ # Return the Total severity count.
320
+ #
321
+ # @param [String] severity the severity in which to calculate percentage for.
322
+ #
323
+ # @param [Boolean] round round the result to the nearest whole number.
324
+ #
325
+ # @raise [ExceptionClass] One of the following severity options must be passed. [high, medium, low, informational, all]
326
+ #
327
+ # @return [Integer]
328
+ # The Percentage Of Events For A Passed Severity
329
+ #
330
+ # @example
331
+ # scan.event_percentage_for("low", true) #=> 11%
332
+ #
333
+ def event_percentage_for(type, round_percentage = false)
334
+ @sc ||= count_severity
335
+ if %w[high medium low all].include?(type)
336
+ calc = ((@sc[:"#{type}"].to_f / @sc[:all].to_f) * 100)
337
+ if round_percentage
338
+ return calc.round.to_s
339
+ else
340
+ return calc.to_s
341
+ end
342
+ else
343
+ raise "Error: #{type} is not an acceptable severity. Possible options include: all, high, medium, low and informational."
344
+ end
345
+ end
346
+
347
+ #
348
+ # Creates a new Host object to be parser from a passed search param.
349
+ #
350
+ # @param [String] hostname the hostname to build a Host object for.
351
+ #
352
+ # @yield [prog] If a block is given, it will be passed the newly
353
+ # created Host object.
354
+ #
355
+ # @yieldparam [XML] prog The newly created Host object.
356
+ #
357
+ # @example
358
+ # scan.find_by_hostname('127.0.0.1') do |host|
359
+ # puts host.hostname
360
+ # end
361
+ #
362
+ def find_by_hostname(hostname, &block)
363
+ raise "Error: hostname can't be blank." if hostname.nil? || hostname.empty?
364
+ @xml.xpath('//ReportHost[HostName]').each do |host|
365
+ next unless host.inner_text.match(hostname)
366
+ yield(Host.new(host)) if block
367
+ end
368
+ end
369
+
370
+ private
371
+
372
+ #
373
+ # Calculates an event hash of totals for severity counts.
374
+ #
375
+ # @return [hash]
376
+ # The Event Totals For Severity
377
+ #
378
+ def count_severity
379
+ unless @count
380
+ @count = {}
381
+ @open_ports = 0
382
+ @low = 0
383
+ @medium = 0
384
+ @high = 0
385
+
386
+ @xml.xpath('//ReportHost').each do |s|
387
+ @open_ports += s.at('num_ports').inner_text.to_i
388
+ @low += s.at('num_lo').inner_text.to_i
389
+ @medium += s.at('num_med').inner_text.to_i
390
+ @high += s.at('num_hi').inner_text.to_i
391
+ end
392
+
393
+ @count = { open_ports: @open_ports,
394
+ low: @low,
395
+ medium: @medium,
396
+ high: @high,
397
+ all: (@low + @medium + @high) }
398
+ end
399
+
400
+ @count
401
+ end
402
+ end
403
+ end
404
+ end