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.
- checksums.yaml +7 -0
- data/.drone.yml +51 -0
- data/.gitignore +5 -0
- data/.rspec +1 -0
- data/.rubocop.yml +4 -0
- data/.rubocop_todo.yml +124 -0
- data/.travis.yml +13 -0
- data/.yardopts +1 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +75 -0
- data/LICENSE.txt +20 -0
- data/README.md +181 -0
- data/Rakefile +21 -0
- data/bin/recess +10 -0
- data/examples/example.rb +46 -0
- data/examples/example_bid.rb +28 -0
- data/examples/example_cpe.rb +28 -0
- data/examples/example_cve.rb +36 -0
- data/examples/example_v1.nessus +1 -0
- data/examples/example_v2.nessus +2076 -0
- data/examples/example_v3.nessus +7449 -0
- data/lib/ruby-nessus.rb +5 -0
- data/lib/ruby-nessus/cli.rb +126 -0
- data/lib/ruby-nessus/log.rb +84 -0
- data/lib/ruby-nessus/parse.rb +46 -0
- data/lib/ruby-nessus/ruby-nessus.rb +6 -0
- data/lib/ruby-nessus/version.rb +5 -0
- data/lib/ruby-nessus/version1/event.rb +85 -0
- data/lib/ruby-nessus/version1/host.rb +267 -0
- data/lib/ruby-nessus/version1/port.rb +84 -0
- data/lib/ruby-nessus/version1/scan.rb +404 -0
- data/lib/ruby-nessus/version2/event.rb +410 -0
- data/lib/ruby-nessus/version2/host.rb +522 -0
- data/lib/ruby-nessus/version2/port.rb +75 -0
- data/lib/ruby-nessus/version2/scan.rb +393 -0
- data/ruby-nessus.gemspec +28 -0
- data/spec/ruby-nessus/parse_spec.rb +40 -0
- data/spec/ruby-nessus/version1/event_spec.rb +69 -0
- data/spec/ruby-nessus/version1/host_spec.rb +75 -0
- data/spec/ruby-nessus/version1/scan_spec.rb +97 -0
- data/spec/ruby-nessus/version2/event_spec.rb +225 -0
- data/spec/ruby-nessus/version2/host_spec.rb +148 -0
- data/spec/ruby-nessus/version2/scan_spec.rb +96 -0
- data/spec/ruby-nessus/version_spec.rb +11 -0
- data/spec/spec_fixtures/example_v1.nessus +1 -0
- data/spec/spec_fixtures/example_v2.nessus +2080 -0
- data/spec/spec_fixtures/example_v_wrong.nessus +3 -0
- data/spec/spec_fixtures/xml.rb +15 -0
- data/spec/spec_helper.rb +7 -0
- 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
|