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,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Ruby-Nessus Files
4
+ require 'ruby-nessus/ruby-nessus'
5
+ require 'ruby-nessus/version'
@@ -0,0 +1,126 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rubygems'
4
+ require 'ruby-nessus/ruby-nessus'
5
+ require 'ruby-nessus/log'
6
+ require 'optparse'
7
+
8
+ require 'pp'
9
+
10
+ module RubyNessus
11
+ class CLI
12
+ def initialize
13
+ @file = nil
14
+ @nessus_version = nil
15
+ @args = []
16
+ end
17
+
18
+ def self.run
19
+ new.run(*ARGV)
20
+ end
21
+
22
+ def run(*args)
23
+ optparse(*args)
24
+
25
+ Log.it 'Recess - Ruby-Nessus CLI'
26
+ Log.it "Version: #{RubyNessus::VERSION}"
27
+ Log.it
28
+
29
+ RubyNessus::Parse.new(@file.to_s) do |scan|
30
+ Log.h1 'SCAN Metadata'
31
+ Log.it
32
+ Log.h2 'Scan Title', scan.title
33
+ Log.h2 'Policy Title', scan.policy_title
34
+ Log.it
35
+ Log.h1 'SCAN Statistics'
36
+ Log.it
37
+ Log.h2 'Host Count', scan.host_count
38
+ Log.h2 'Open Port Count', scan.open_ports_count
39
+
40
+ unless scan.version == 1
41
+ Log.h2 'TCP Count', scan.tcp_count
42
+ Log.h2 'UDP Count', scan.udp_count
43
+ Log.h2 'ICMP Count', scan.icmp_count
44
+ end
45
+
46
+ Log.it
47
+ Log.h1 'EVENT Statistics'
48
+ Log.it
49
+
50
+ Log.informational 'Informational Severity Count', scan.informational_severity_count unless scan.version == 1
51
+
52
+ Log.low 'Low Severity Count', scan.low_severity_count
53
+ Log.medium 'Medium Severity Count', scan.medium_severity_count
54
+ Log.high 'High Severity Count', scan.high_severity_count
55
+ Log.h3 'Total Event Count', scan.total_event_count
56
+ Log.break
57
+ Log.it! "Low Event Percentage: #{scan.event_percentage_for('low', true)}"
58
+ Log.it! "Medium Event Percentage: #{scan.event_percentage_for('medium', true)}"
59
+ Log.it! "High Event Percentage: #{scan.event_percentage_for('high', true)}"
60
+ Log.it
61
+
62
+ Log.h1 'HOSTS'
63
+ Log.it
64
+
65
+ scan.each_host do |host|
66
+ Log.h2 'Hostname', host.hostname
67
+ Log.h5 'IP Address:', host.ip
68
+
69
+ unless scan.version == 1
70
+ Log.h5 'Informational Count', host.informational_severity_count
71
+ Log.h5 'Low Count', host.low_severity_count
72
+ Log.h5 'Medium Count', host.medium_severity_count
73
+ Log.h5 'High Count', host.high_severity_count
74
+ end
75
+ Log.it
76
+ end
77
+
78
+ Log.end
79
+ end
80
+ end
81
+
82
+ protected
83
+
84
+ def optparse(*args)
85
+ opts = OptionParser.new
86
+ opts.program_name = 'recess'
87
+ opts.banner = "Recess #{RubyNessus::VERSION}"
88
+ opts.separator 'usage: recess FILE [OPTIONS]'
89
+
90
+ opts.on('-f', '--file FILE', 'The .nessus file to parse.') do |file|
91
+ @file = file
92
+ end
93
+
94
+ opts.on('-f', '--file FILE', 'The .nessus file to parse.') do |file|
95
+ @file = file
96
+ end
97
+
98
+ opts.on('-h', '--help', 'This help summary page.') do |_help|
99
+ Log.it opts
100
+ Log.it
101
+ exit(-1)
102
+ end
103
+
104
+ opts.on('-v', '--version', 'Recess Version.') do |_version|
105
+ Log.it RubyNessus::VERSION
106
+ Log.it
107
+ exit(-1)
108
+ end
109
+
110
+ begin
111
+ @args = opts.parse!(args)
112
+ @file ||= @args[0]
113
+ if @file.nil?
114
+ Log.it opts
115
+ Log.it
116
+ exit(-1)
117
+ end
118
+ rescue => e
119
+ Log.error e.message
120
+ Log.it opts
121
+ Log.it
122
+ exit(-1)
123
+ end
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rainbow'
4
+
5
+ module RubyNessus
6
+ class Log
7
+ #
8
+ # Formatting
9
+ #
10
+ def self.it(msg = nil)
11
+ STDERR.puts msg.to_s
12
+ end
13
+
14
+ def self.it!(msg = nil)
15
+ STDERR.puts "\t#{msg}"
16
+ end
17
+
18
+ def self.break
19
+ STDERR.puts "\t"
20
+ STDERR.puts ''
21
+ end
22
+
23
+ def self.end
24
+ STDERR.puts "\n\n"
25
+ end
26
+
27
+ #
28
+ # Headers
29
+ #
30
+ def self.h1(title, msg = nil)
31
+ STDERR.puts Rainbow("-> #{title}: ").foreground(:green).bright + msg.to_s
32
+ end
33
+
34
+ def self.h2(title, msg = nil)
35
+ STDERR.puts Rainbow("\t#{title}: ").foreground(:blue).bright + msg.to_s
36
+ end
37
+
38
+ def self.h3(title, msg = nil)
39
+ STDERR.puts "\t#{title}: " + Rainbow(msg.to_s).foreground(:blue).underline
40
+ end
41
+
42
+ def self.h4(msg = nil)
43
+ STDERR.puts "\t\t- #{msg}"
44
+ end
45
+
46
+ def self.h5(title, msg = nil)
47
+ STDERR.puts "\t\t- #{title}: #{msg}"
48
+ end
49
+
50
+ #
51
+ # Errors
52
+ #
53
+ def self.error(msg = nil)
54
+ STDERR.puts Rainbow('ERROR: ').foreground(:red).bright + msg.to_s
55
+ end
56
+
57
+ def self.warn(msg = nil)
58
+ STDERR.puts Rainbow('WARNING: ').foreground(:yellow).bright + msg.to_s
59
+ end
60
+
61
+ def self.info(msg = nil)
62
+ STDERR.puts Rainbow('INFO: ').foreground(:green).bright + msg.to_s
63
+ end
64
+
65
+ #
66
+ # Event Severities
67
+ #
68
+ def self.informational(title, msg = nil)
69
+ STDERR.puts Rainbow("\t#{title}: ").foreground(:magenta).bright + msg.to_s
70
+ end
71
+
72
+ def self.low(title, msg = nil)
73
+ STDERR.puts Rainbow("\t#{title}: ").foreground(:green) + msg.to_s
74
+ end
75
+
76
+ def self.medium(title, msg = nil)
77
+ STDERR.puts Rainbow("\t#{title}: ").foreground(:yellow).bright + msg.to_s
78
+ end
79
+
80
+ def self.high(title, msg = nil)
81
+ STDERR.puts Rainbow("\t#{title}: ").foreground(:red).bright + msg.to_s
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ruby-nessus/log'
4
+ require 'ruby-nessus/version1/scan'
5
+ require 'ruby-nessus/version2/scan'
6
+
7
+ require 'nokogiri'
8
+ require 'date'
9
+ require 'time'
10
+
11
+ module RubyNessus
12
+ class Parse
13
+ def initialize(file = nil, options = {}, &block)
14
+ doc = file ? File.read(file) : options[:xml]
15
+ @xml = Nokogiri::XML.parse(doc)
16
+ @version = options[:version] || detect_version
17
+
18
+ @xml_parser = case @version
19
+ when 1
20
+ Version1::XML.new(@xml)
21
+ when 2
22
+ Version2::XML.new(@xml)
23
+ else
24
+ raise 'Error: Supported .Nessus Version are 1 and 2.'
25
+ end
26
+
27
+ yield(@xml_parser) if block
28
+ end
29
+
30
+ # Retrive scan from file
31
+ def scan
32
+ @xml_parser
33
+ end
34
+
35
+ # Try to detection version with the XML given
36
+ def detect_version
37
+ if @xml.at('NessusClientData')
38
+ 1
39
+ elsif @xml.at('NessusClientData_v2')
40
+ 2
41
+ else
42
+ raise 'Error: Supported .Nessus Version are 1 and 2.'
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ruby-nessus/parse'
4
+
5
+ module RubyNessus
6
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyNessus
4
+ VERSION = '2.0.0'
5
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ruby-nessus/version1/port'
4
+
5
+ module RubyNessus
6
+ module Version1
7
+ class Event
8
+ # Return the total event count for a given host.
9
+ # @return [Integer]
10
+ # Return the total event count for a given host.
11
+ # @example
12
+ # host.event_count #=> 3456
13
+ def initialize(event)
14
+ @event = event
15
+ end
16
+
17
+ # Return the event port.
18
+ # @return [Object]
19
+ # Return the event port object or port string.
20
+ # @example
21
+ # event.port #=> "https (443/tcp)"
22
+ # event.port.number #=> 443
23
+ # event.port.service #=> "https"
24
+ # event.port.protocol #=> "tcp"
25
+ def port
26
+ @port ||= Port.parse(@event.at('port').inner_text)
27
+ end
28
+
29
+ # Return the event severity.
30
+ # @return [String]
31
+ # Return the event severity.
32
+ # @example
33
+ # event.severity #=> 3
34
+ def severity
35
+ @severity ||= @event.at('severity').inner_text.to_i
36
+ end
37
+
38
+ # Return the event object nessus plugin id
39
+ # @return [String]
40
+ # Return the event object nessus plugin id
41
+ # @example
42
+ # event.plugin_id #=> 3245
43
+ def plugin_id
44
+ @plugin_id ||= @event.at('pluginID').inner_text.to_i
45
+ end
46
+
47
+ # Return the event name (plugin_name)
48
+ # @return [String]
49
+ # Return the event name (plugin_name)
50
+ # @example
51
+ # event.plugin_name #=> "PHP < 5.2.4 Multiple Vulnerabilities"
52
+ # event.name #=> "PHP < 5.2.4 Multiple Vulnerabilities"
53
+ def plugin_name
54
+ s = @event.at('pluginName').inner_text
55
+
56
+ @plugin_name ||= if s.empty?
57
+ false
58
+ else
59
+ @event.at('pluginName').inner_text || 'N/A'
60
+ end
61
+
62
+ @plugin_name
63
+ end
64
+ alias name plugin_name
65
+
66
+ # Return the event plugin output data
67
+ # @return [String]
68
+ # Return the event plugin output data
69
+ # @example
70
+ # event.output #=> "..."
71
+ # event.data #=> "..."
72
+ def data
73
+ d = @event.at('data').to_s || ''
74
+
75
+ @data ||= if d.empty?
76
+ false
77
+ else
78
+ @event.at('data').inner_text || 'N/A'
79
+ end
80
+ @data
81
+ end
82
+ alias output data
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,267 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyNessus
4
+ module Version1
5
+ class Host
6
+ include Enumerable
7
+
8
+ # Creates A New Host Object
9
+ # @param [Object] Host Object
10
+ # @example
11
+ # Host.new(object)
12
+ def initialize(host)
13
+ @host = host
14
+ end
15
+
16
+ def to_s
17
+ ip.to_s
18
+ end
19
+
20
+ # Return the Host Object hostname.
21
+ # @return [String]
22
+ # The Host Object Hostname
23
+ # @example
24
+ # host.hostname #=> "127.0.0.1"
25
+ def hostname
26
+ @hostname ||= @host.at('HostName').inner_text
27
+ end
28
+ alias ip hostname
29
+
30
+ # Return the host scan start time.
31
+ # @return [DateTime]
32
+ # The Host Scan Start Time
33
+ # @example
34
+ # scan.scan_start_time #=> 'Fri Nov 11 23:36:54 1985'
35
+ def scan_start_time
36
+ if @host.at('startTime').inner_text.empty?
37
+ false
38
+ else
39
+ @host_scan_time = DateTime.strptime(@host.at('startTime').inner_text, '%a %b %d %H:%M:%S %Y')
40
+ end
41
+ end
42
+
43
+ # Return the host scan stop time.
44
+ # @return [DateTime]
45
+ # The Host Scan Stop Time
46
+ # @example
47
+ # scan.scan_start_time #=> 'Fri Nov 11 23:36:54 1985'
48
+ def scan_stop_time
49
+ if @host.at('stopTime').inner_text.empty?
50
+ false
51
+ else
52
+ @host_scan_time = DateTime.strptime(@host.at('stopTime').inner_text, '%a %b %d %H:%M:%S %Y')
53
+ end
54
+ end
55
+
56
+ # Return the host run time.
57
+ # @return [String]
58
+ # The Host Scan Run Time
59
+ # @example
60
+ # scan.scan_run_time #=> '2 hours 5 minutes and 16 seconds'
61
+ def scan_runtime
62
+ get_runtime
63
+ end
64
+ alias runtime scan_runtime
65
+
66
+ # Return the Host Netbios Name.
67
+ # @return [String]
68
+ # The Host Netbios Name
69
+ # @example
70
+ # host.netbios_name #=> "SOMENAME4243"
71
+ def netbios_name
72
+ @netbios_name ||= @host.at('netbios_name').inner_text
73
+ end
74
+
75
+ # Return the Host Mac Address.
76
+ # @return [String]
77
+ # Return the Host Mac Address
78
+ # @example
79
+ # host.mac_addr #=> "00:11:22:33:44:55"
80
+ def mac_addr
81
+ @mac_addr ||= @host.at('mac_addr').inner_text
82
+ end
83
+ alias mac_address mac_addr
84
+
85
+ # Return the Host DNS Name.
86
+ # @return [String]
87
+ # Return the Host DNS Name
88
+ # @example
89
+ # host.dns_name #=> "snorby.org"
90
+ def dns_name
91
+ @dns_name ||= @host.at('dns_name').inner_text
92
+ end
93
+
94
+ # Return the Host OS Name.
95
+ # @return [String]
96
+ # Return the Host OS Name
97
+ # @example
98
+ # host.dns_name #=> "Microsoft Windows 2000, Microsoft Windows Server 2003"
99
+ def os_name
100
+ @os_name ||= @host.at('os_name').inner_text
101
+ end
102
+ alias operating_system os_name
103
+
104
+ # Return the open ports for a given host object.
105
+ # @return [Integer]
106
+ # Return the open ports for a given host object.
107
+ # @example
108
+ # host.open_ports #=> 213
109
+ def open_ports
110
+ @scanned_ports ||= @host.at('num_ports').inner_text.to_i
111
+ end
112
+
113
+ # Returns All Informational Event Objects For A Given Host.
114
+ # @yield [prog] If a block is given, it will be passed the newly
115
+ # created Event object.
116
+ # @yieldparam [EVENT] prog The newly created Event object.
117
+ # @return [Integer]
118
+ # Return The Informational Event Count For A Given Host.
119
+ # @example
120
+ # host.informational_events do |info|
121
+ # puts info.port
122
+ # puts info.data if info.data
123
+ # end
124
+ def informational_events(&block)
125
+ unless @informational_events
126
+ @informational_events = []
127
+ @informational_event_count = 0
128
+
129
+ @host.xpath('ReportItem').each do |event|
130
+ next if event.at('severity').inner_text.to_i != 0
131
+ @informational_events << Event.new(event)
132
+ @informational_event_count += 1
133
+ end
134
+
135
+ end
136
+
137
+ @informational_events.each(&block)
138
+ @informational_event_count
139
+ end
140
+
141
+ # Returns All Low Event Objects For A Given Host.
142
+ # @yield [prog] If a block is given, it will be passed the newly
143
+ # created Event object.
144
+ # @yieldparam [EVENT] prog The newly created Event object.
145
+ # @return [Integer]
146
+ # Return The Low Event Count For A Given Host.
147
+ # @example
148
+ # host.low_severity_events do |low|
149
+ # puts low.name if low.name
150
+ # end
151
+ def low_severity_events(&block)
152
+ @low_severity_count = @host.at('num_lo').inner_text.to_i
153
+
154
+ unless @low_severity_events
155
+ @low_severity_events = []
156
+
157
+ @host.xpath('ReportItem').each do |event|
158
+ next if event.at('severity').inner_text.to_i != 1
159
+ @low_severity_events << Event.new(event)
160
+ end
161
+
162
+ end
163
+
164
+ @low_severity_events.each(&block)
165
+ @low_severity_count
166
+ end
167
+
168
+ # Returns All Medium Event Objects For A Given Host.
169
+ # @yield [prog] If a block is given, it will be passed the newly
170
+ # created Event object.
171
+ # @yieldparam [EVENT] prog The newly created Event object.
172
+ # @return [Integer]
173
+ # Return The Medium Event Count For A Given Host.
174
+ # @example
175
+ # host.medium_severity_events do |medium|
176
+ # puts medium.name if medium.name
177
+ # end
178
+ def medium_severity_events(&block)
179
+ @high_severity_count = @host.at('num_med').inner_text.to_i
180
+
181
+ unless @medium_severity_events
182
+ @medium_severity_events = []
183
+
184
+ @host.xpath('ReportItem').each do |event|
185
+ next if event.at('severity').inner_text.to_i != 2
186
+ @medium_severity_events << Event.new(event)
187
+ end
188
+
189
+ end
190
+
191
+ @medium_severity_events.each(&block)
192
+ @high_severity_count
193
+ end
194
+
195
+ # Returns All High Event Objects For A Given Host.
196
+ # @yield [prog] If a block is given, it will be passed the newly
197
+ # created Event object.
198
+ # @yieldparam [EVENT] prog The newly created Event object.
199
+ # @return [Integer]
200
+ # Return The High Event Count For A Given Host.
201
+ # @example
202
+ # host.high_severity_events do |high|
203
+ # puts high.name if high.name
204
+ # end
205
+ def high_severity_events(&block)
206
+ @high_severity_count = @host.at('num_hi').inner_text.to_i
207
+
208
+ unless @high_severity_events
209
+ @high_severity_events = []
210
+
211
+ @host.xpath('ReportItem').each do |event|
212
+ next if event.at('severity').inner_text.to_i != 3
213
+ @high_severity_events << Event.new(event)
214
+ end
215
+
216
+ end
217
+
218
+ @high_severity_events.each(&block)
219
+ @high_severity_count
220
+ end
221
+
222
+ # Return the total event count for a given host.
223
+ # @return [Integer]
224
+ # Return the total event count for a given host.
225
+ # @example
226
+ # host.event_count #=> 3456
227
+ def event_count
228
+ (low_severity_events.to_i + medium_severity_events.to_i + high_severity_events.to_i).to_i
229
+ end
230
+
231
+ # Creates a new Event object to be parser
232
+ # @yield [prog] If a block is given, it will be passed the newly
233
+ # created Event object.
234
+ # @yieldparam [EVENT] prog The newly created Event object.
235
+ # @example
236
+ # host.events do |event|
237
+ # puts event.name if event.name
238
+ # puts event.port
239
+ # end
240
+ def each_event(&block)
241
+ @host.xpath('ReportItem').each do |event|
242
+ yield(Event.new(event)) if block
243
+ end
244
+ end
245
+
246
+ # Parses the events of the host.
247
+ # @return [Array<String>]
248
+ # The events of the host.
249
+ def events
250
+ to_enum(:each_event).to_a
251
+ end
252
+
253
+ private
254
+
255
+ def get_runtime
256
+ if scan_start_time && scan_stop_time
257
+ h = (Time.parse(scan_stop_time.to_s).strftime('%H').to_i - Time.parse(scan_start_time.to_s).strftime('%H').to_i).to_s.delete('-')
258
+ m = (Time.parse(scan_stop_time.to_s).strftime('%M').to_i - Time.parse(scan_start_time.to_s).strftime('%M').to_i).to_s.delete('-')
259
+ s = (Time.parse(scan_stop_time.to_s).strftime('%S').to_i - Time.parse(scan_start_time.to_s).strftime('%S').to_i).to_s.delete('-')
260
+ "#{h} hours #{m} minutes and #{s} seconds"
261
+ else
262
+ false
263
+ end
264
+ end
265
+ end
266
+ end
267
+ end