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