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