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