silo_manager 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (5) hide show
  1. data/bin/silo_manager +437 -0
  2. data/lib/nexpose.rb +2734 -0
  3. data/lib/options.rb +34 -0
  4. data/lib/test.rb +8 -0
  5. metadata +64 -0
data/bin/silo_manager ADDED
@@ -0,0 +1,437 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: utf-8
3
+ # TODO: Fix multi-byte problem (copied and pasted from PDF)
4
+
5
+ require 'rubygems'
6
+ require File.expand_path(File.join(File.dirname(__FILE__), '../lib/nexpose'))
7
+ require File.expand_path(File.join(File.dirname(__FILE__), '../lib/options'))
8
+
9
+ ################################
10
+ # Multi-Tenant User Attributes #
11
+ ################################
12
+ MTU_ATTRS = ['authsrcid:integer:required', 'user-name:string:required',
13
+ 'full-name:string:required', 'email:string:optional', 'password:string:optional', 'enabled:boolean:required',
14
+ 'superuser:boolean:required']
15
+
16
+ SILO_ACCESS_ATTRS = ['all-groups:boolean:required', 'all-sites:boolean:required', 'default-silo:boolean:required',
17
+ 'role-name:string:required', 'silo-id:string:required']
18
+
19
+ ###########################
20
+ # Silo Profile Attributes #
21
+ ###########################
22
+ SILO_PROFILE_ATTRS = ['id:string:required', 'name:string:required', 'description:string:optional',
23
+ 'all-licensed-modules:boolean:required', 'all-global-engines:boolean:required', 'all-global-report-templates:boolean:required',
24
+ 'all-global-scan-templates:boolean:required']
25
+
26
+ ACCEPTED_REPORT_INPUTS = ['csv', 'db', 'html', 'ns-xml', 'pdf', 'qualys-xml', 'raw-xml', 'rtf', 'scap-xml', 'text']
27
+
28
+ ###################
29
+ # Silo Attributes #
30
+ ###################
31
+ SILO_CONFIG_ATTRS = ['id:string:required', 'name:string:required', 'silo-profile-id:string:required',
32
+ 'description:string:optional', 'max-assets:integer:required', 'max-hosted-assets:integer:required',
33
+ 'max-users:integer:required']
34
+
35
+ MERCHANT_ATTRS = ['acquirer-relationship:boolean:required', 'agent‐relationship:boolean:required',
36
+ 'payment-application:string:required', 'payment‐version:string:required', 'ecommerce:boolean:required',
37
+ 'grocery:boolean:required', 'mail-order:boolean:required', 'petroleum:boolean:required', 'retail:boolean:required',
38
+ 'telecommunication:boolean:required', 'travel:boolean:required', 'url:string:required', 'company:string:required',
39
+ 'email-address:string:optional', 'first-name:string:required', 'last-name:string:required', 'phone-number:string:required',
40
+ 'title:string:optional']
41
+
42
+ ADDRESS_ATTRS = ['city:string:required', 'country:string:required', 'line1:string:required', 'line2:string:required',
43
+ 'state:string:required', 'zip:string:required']
44
+
45
+ QSA_ATTRS = ['url:string:required', 'company:string:required', 'email-address:string:optional', 'first-name:string:required',
46
+ 'last-name:string:required', 'phone-number:string:required', 'title:string:optional']
47
+
48
+ ORG_ATTRS = ['url:string:required', 'company:string:required', 'email-address:string:optional', 'first-name:string:required',
49
+ 'last-name:string:required', 'phone-number:string:required', 'title:string:optional']
50
+
51
+ #-------------------------------------------------------------------------
52
+ #-------------------------------------------------------------------------
53
+ def get_integer_input name, return_main=false
54
+ integer_inputed = false
55
+ while !integer_inputed
56
+ puts "Enter an integer value for #{name}:"
57
+ begin
58
+ input = gets.chomp
59
+ if return_main and input =~ /:main/i
60
+ return nil
61
+ end
62
+
63
+ begin
64
+ input = Integer input
65
+ integer_inputed = true
66
+ rescue Exception => e
67
+ puts "Invalid input"
68
+ end
69
+ end
70
+ end
71
+ input
72
+ end
73
+
74
+ #-------------------------------------------------------------------------
75
+ #-------------------------------------------------------------------------
76
+ def get_string_input name, return_main=false
77
+ puts "Enter an string value for #{name}:"
78
+ input = gets.chomp
79
+ if return_main and input =~ /:main/i
80
+ return nil
81
+ end
82
+ input
83
+ end
84
+
85
+ #-------------------------------------------------------------------------
86
+ #-------------------------------------------------------------------------
87
+ def get_boolean_input name='', return_main=false
88
+ boolean_inputed = false
89
+ while !boolean_inputed
90
+ puts "Enter a boolean value (true/1 or false/0) for #{name}:"
91
+ begin
92
+ input = gets.chomp
93
+ if return_main and input =~ /:main/i
94
+ return nil
95
+ end
96
+
97
+ if input =~ /true|1/i
98
+ input = true
99
+ boolean_inputed = true
100
+ elsif input =~ /false|0/i
101
+ input = false
102
+ boolean_inputed = true
103
+ else
104
+ puts "Invalid input!"
105
+ end
106
+ end
107
+ end
108
+ input
109
+ end
110
+
111
+ #-------------------------------------------------------------------------
112
+ #-------------------------------------------------------------------------
113
+ def process_attrs attrs, title
114
+ puts title
115
+ puts "To return to the main menu type ':main'"
116
+
117
+ input_hash = {}
118
+
119
+ attrs.each do |attr|
120
+ parts = attr.split ":"
121
+ name = parts[0] + " (#{parts[2]})"
122
+ case parts[1]
123
+ when /boolean/
124
+ input = get_boolean_input name, true
125
+ when /integer/
126
+ input = get_integer_input name, true
127
+ when /string/
128
+ input = get_string_input name, true
129
+ end
130
+
131
+ if input.nil?
132
+ return nil
133
+ else
134
+ input_hash[parts[0]] = input
135
+ end
136
+ end
137
+
138
+ input_hash
139
+ end
140
+
141
+
142
+ #-------------------------------------------------------------------------
143
+ # Gets an comma separated list of input from the user and parses
144
+ # it into an array
145
+ #
146
+ # integer - if true, the input values must be an integers
147
+ # restricted_values - Input should be of a certain type
148
+ #-------------------------------------------------------------------------
149
+ def get_value_array integer=false, restricted_values=[]
150
+ output = []
151
+ success = false
152
+ while !success
153
+ puts "Enter a comma separated list of values or nothing to skip:"
154
+ if not restricted_values.empty?
155
+ puts "Input is restricted to: #{restricted_values.inspect}"
156
+ end
157
+
158
+ input = gets.chomp
159
+
160
+ if input =~ /:main/
161
+ return nil
162
+ end
163
+
164
+ if input.empty?
165
+ return []
166
+ end
167
+
168
+ begin
169
+ input.split(",").each do |part|
170
+
171
+ # Do validation first
172
+ if not restricted_values.empty?
173
+ if not restricted_values.include? part
174
+ "This is not an allowed input: #{part.to_s}"
175
+ raise
176
+ end
177
+ end
178
+
179
+ output << integer ? part.to_i : part.to_s
180
+ end
181
+ success = true
182
+ rescue Exception
183
+ puts "Invalid input!"
184
+ if integer
185
+ puts "Integer input only"
186
+ end
187
+ end
188
+ end
189
+
190
+ output
191
+ end
192
+
193
+ #-------------------------------------------------------------------------
194
+ #-------------------------------------------------------------------------
195
+ def enter_data? type
196
+ while true
197
+ puts "Do you wish to enter #{type} data (yes/no)?"
198
+ input = gets.chomp
199
+ if input =~ /yes/i
200
+ return true
201
+ elsif input =~ /no/i
202
+ return false
203
+ else
204
+ puts "Invalid input!"
205
+ end
206
+ end
207
+ end
208
+
209
+ #-------------------------------------------------------------------------
210
+ # Main method that builds the input map for creating a multi-tenant user
211
+ #-------------------------------------------------------------------------
212
+ def create_multi_tenant_user
213
+ user_config = process_attrs MTU_ATTRS, "User Configuration"
214
+ if not user_config
215
+ return
216
+ end
217
+
218
+ silo_configs = process_attrs SILO_ACCESS_ATTRS, "Silo Configuration"
219
+ if not silo_configs
220
+ return
221
+ end
222
+
223
+ if not silo_configs['all-sites']
224
+ puts "Site ID values:"
225
+ ids = get_value_array true
226
+ if not ids
227
+ return
228
+ end
229
+ if ids and not ids.empty?
230
+ silo_configs['allowed-sites'] = ids
231
+ end
232
+ end
233
+
234
+ if not silo_configs['all-groups']
235
+ puts "Group ID values:"
236
+ ids = get_value_array true
237
+ if not ids
238
+ return
239
+ end
240
+ if ids and not ids.empty?
241
+ silo_configs['allowed-groups'] = ids
242
+ end
243
+ end
244
+
245
+ begin
246
+ @client_api.login
247
+ @client_api.create_multi_tenant_user user_config, silo_configs
248
+ puts "Successfully created multi-tenant user!"
249
+ rescue Exception => e
250
+ puts e.message
251
+ end
252
+ end
253
+
254
+ #-------------------------------------------------------------------------
255
+ # Main method that builds the input map for creating a silo profile
256
+ #-------------------------------------------------------------------------
257
+ def create_silo_profile
258
+ silo_profile_config = process_attrs SILO_PROFILE_ATTRS, "Silo Profile Configuration"
259
+ if not silo_profile_config
260
+ return
261
+ end
262
+
263
+ permissions = {}
264
+
265
+ unless silo_profile_config['all-global-report-templates']
266
+ puts "Global report template names:"
267
+ names = get_value_array
268
+ if not names
269
+ return
270
+ end
271
+ if names and not names.empty?
272
+ permissions['global_report_templates'] = names
273
+ end
274
+ end
275
+
276
+ unless silo_profile_config['all-global-engines']
277
+ puts "Global scan engine names:"
278
+ names = get_value_array
279
+ if not names
280
+ return
281
+ end
282
+ if names and not names.empty?
283
+ permissions['global_scan_engines'] = names
284
+ end
285
+ end
286
+
287
+ unless silo_profile_config['all-global-scan-templates']
288
+ puts "Global scan template names:"
289
+ names = get_value_array
290
+ if not names
291
+ return
292
+ end
293
+ if names and not names.empty?
294
+ permissions['global_scan_templates'] = names
295
+ end
296
+ end
297
+
298
+ unless silo_profile_config['all-licensed-modules']
299
+ puts "Licensed module names:"
300
+ names = get_value_array
301
+ if not names
302
+ return
303
+ end
304
+ if names and not names.empty?
305
+ permissions['licensed_modules'] = names
306
+ end
307
+ end
308
+
309
+ puts "Restricted Report Format names:"
310
+ names = get_value_array false, ACCEPTED_REPORT_INPUTS
311
+ unless names
312
+ return
313
+ end
314
+ if names and not names.empty?
315
+ permissions['restricted_report_formats'] = names
316
+ end
317
+
318
+ puts "Restricted Report Section names:"
319
+ names = get_value_array
320
+ unless names
321
+ return
322
+ end
323
+ if names and not names.empty?
324
+ permissions['restricted_report_sections'] = names
325
+ end
326
+
327
+ begin
328
+ @client_api.login
329
+ @client_api.create_silo_profile silo_profile_config, permissions
330
+ puts "Successfully created silo profile!"
331
+ rescue Exception => e
332
+ puts e.message
333
+ end
334
+ end
335
+
336
+ #-------------------------------------------------------------------------
337
+ # Main method that builds the input map for creating a silo
338
+ #-------------------------------------------------------------------------
339
+ def create_silo
340
+ silo_config = process_attrs SILO_CONFIG_ATTRS, "Silo Configuration"
341
+ if not silo_config
342
+ return
343
+ end
344
+
345
+ if (enter_data? "Organization")
346
+ organization_data = process_attrs ORG_ATTRS, "Organization Data"
347
+ address_data = process_attrs ADDRESS_ATTRS, "Address Data"
348
+ silo_config['organization'] = organization_data
349
+ silo_config['organization']['address'] = address_data
350
+ end
351
+
352
+ if (enter_data? "Merchant")
353
+ merchant_data = process_attrs MERCHANT_ATTRS , "Merchant Data"
354
+ merchant_address = process_attrs ADDRESS_ATTRS, "Merchant Address Data"
355
+
356
+ puts "DBA values"
357
+ dba = get_value_array
358
+
359
+ puts "Industry values"
360
+ industries = get_value_array
361
+
362
+ qsa = process_attrs QSA_ATTRS, "QSA Data"
363
+ qsa_address = process_attrs ADDRESS_ATTRS, "QSA Address Data"
364
+
365
+
366
+ silo_config['merchant'] = merchant_data
367
+ silo_config['merchant']['address'] = merchant_address
368
+ silo_config['merchant']['other_industries'] = industries
369
+ silo_config['merchant']['dba'] = dba
370
+ silo_config['merchant']['qsa'] = qsa
371
+ silo_config['merchant']['qsa']['address'] = qsa_address
372
+ end
373
+
374
+ begin
375
+ @client_api.login
376
+ @client_api.create_silo silo_config
377
+ puts "Successfully created silo!"
378
+ rescue Exception => e
379
+ puts e.message
380
+ end
381
+ end
382
+
383
+ #-------------------------------------------------------------------------
384
+ # Main input screen
385
+ #-------------------------------------------------------------------------
386
+ def get_main_select
387
+ id_choosen = false
388
+ while !id_choosen
389
+ puts "\nChoose one of the following IDs:"
390
+ puts "1. Create a new multi-tenant user"
391
+ puts "2. Create a new silo-profile"
392
+ puts "3. Create a new silo"
393
+
394
+ id = gets.chomp
395
+ if id =~ /quit/i
396
+ exit 0
397
+ end
398
+
399
+ begin
400
+ id = id.to_i
401
+ id_choosen = true
402
+ rescue Exception => e
403
+ e.
404
+ puts "Input error"
405
+ end
406
+ end
407
+ id
408
+ end
409
+
410
+ ###############
411
+ # ENTRY POINT #
412
+ ###############
413
+ begin
414
+ options = Options.parse ARGV
415
+ begin
416
+ @client_api = Nexpose::Connection.new options.host, options.user, options.password, options.port
417
+ @client_api.login
418
+ rescue Exception => e
419
+ puts "Unable to connect to #{options.host}"
420
+ puts e.message
421
+ exit 1
422
+ end
423
+
424
+ while true
425
+ puts "To quit at anytime type 'quit'"
426
+ case get_main_select
427
+ when 1
428
+ create_multi_tenant_user
429
+ when 2
430
+ create_silo_profile
431
+ when 3
432
+ create_silo
433
+ else
434
+ puts "Invalid input"
435
+ end
436
+ end
437
+ end
data/lib/nexpose.rb ADDED
@@ -0,0 +1,2734 @@
1
+ #
2
+ # The NeXpose API
3
+ #
4
+ =begin
5
+
6
+ Copyright (C) 2009-2011, Rapid7 LLC
7
+ All rights reserved.
8
+
9
+ Redistribution and use in source and binary forms, with or without modification,
10
+ are permitted provided that the following conditions are met:
11
+
12
+ * Redistributions of source code must retain the above copyright notice,
13
+ this list of conditions and the following disclaimer.
14
+
15
+ * Redistributions in binary form must reproduce the above copyright notice,
16
+ this list of conditions and the following disclaimer in the documentation
17
+ and/or other materials provided with the distribution.
18
+
19
+ * Neither the name of Rapid7 LLC nor the names of its contributors
20
+ may be used to endorse or promote products derived from this software
21
+ without specific prior written permission.
22
+
23
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
24
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
25
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
26
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
27
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
28
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
29
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
30
+ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
31
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
32
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33
+
34
+ =end
35
+
36
+ #
37
+ # WARNING! This code makes an SSL connection to the NeXpose server, but does NOT
38
+ # verify the certificate at this time. This can be a security issue if
39
+ # an attacker is able to man-in-the-middle the connection between the
40
+ # Metasploit console and the NeXpose server. In the common case of
41
+ # running NeXpose and Metasploit on the same host, this is a low risk.
42
+ #
43
+
44
+ #
45
+ # WARNING! This code is still rough and going through substantive changes. While
46
+ # you can build tools using this library today, keep in mind that method
47
+ # names and parameters may change in the future.
48
+ #
49
+
50
+ require 'date'
51
+ require 'rexml/document'
52
+ require 'net/https'
53
+ require 'net/http'
54
+ require 'uri'
55
+ require 'rex/mime'
56
+
57
+ module Nexpose
58
+
59
+ module Sanitize
60
+ def replace_entities(str)
61
+ ret = str.dup
62
+ ret.gsub!(/&/, "&amp;")
63
+ ret.gsub!(/'/, "&apos;")
64
+ ret.gsub!(/"/, "&quot;")
65
+ ret.gsub!(/</, "&lt;")
66
+ ret.gsub!(/>/, "&gt;")
67
+ ret
68
+ end
69
+ end
70
+
71
+ class APIError < ::RuntimeError
72
+ attr_accessor :req, :reason
73
+ def initialize(req, reason = '')
74
+ self.req = req
75
+ self.reason = reason
76
+ end
77
+ def to_s
78
+ "NexposeAPI: #{self.reason}"
79
+ end
80
+ end
81
+
82
+ class AuthenticationFailed < APIError
83
+ def initialize(req)
84
+ self.req = req
85
+ self.reason = "Login Failed"
86
+ end
87
+ end
88
+
89
+ module XMLUtils
90
+ def parse_xml(xml)
91
+ ::REXML::Document.new(xml.to_s)
92
+ end
93
+ end
94
+
95
+ class APIRequest
96
+ include XMLUtils
97
+
98
+ attr_reader :http
99
+ attr_reader :uri
100
+ attr_reader :headers
101
+ attr_reader :retry_count
102
+ attr_reader :time_out
103
+ attr_reader :pause
104
+
105
+ attr_reader :req
106
+ attr_reader :res
107
+ attr_reader :sid
108
+ attr_reader :success
109
+
110
+ attr_reader :error
111
+ attr_reader :trace
112
+
113
+ attr_reader :raw_response
114
+ attr_reader :raw_response_data
115
+
116
+ def initialize(req, url, api_version)
117
+ @url = url
118
+ @req = req
119
+ @api_version = api_version
120
+ prepare_http_client
121
+ end
122
+
123
+ def prepare_http_client
124
+ @retry_count = 0
125
+ @retry_count_max = 10
126
+ @time_out = 30
127
+ @pause = 2
128
+ @uri = URI.parse(@url)
129
+ @http = Net::HTTP.new(@uri.host, @uri.port)
130
+ @http.use_ssl = true
131
+ #
132
+ # XXX: This is obviously a security issue, however, we handle this at the client level by forcing
133
+ # a confirmation when the nexpose host is not localhost. In a perfect world, we would present
134
+ # the server signature before accepting it, but this requires either a direct callback inside
135
+ # of this module back to whatever UI, or opens a race condition between accept and attempt.
136
+ #
137
+ @http.verify_mode = OpenSSL::SSL::VERIFY_NONE
138
+ @headers = {'Content-Type' => 'text/xml'}
139
+ @success = false
140
+ end
141
+
142
+ def execute
143
+ @conn_tries = 0
144
+
145
+ begin
146
+ prepare_http_client
147
+ @raw_response, @raw_response_data = @http.post(@uri.path, @req, @headers)
148
+ @res = parse_xml(@raw_response_data)
149
+
150
+ if(not @res.root)
151
+ @error = "NeXpose service returned invalid XML"
152
+ return @sid
153
+ end
154
+
155
+ @sid = attributes['session-id']
156
+
157
+ if(attributes['success'] and attributes['success'].to_i == 1)
158
+ @success = true
159
+ elsif @api_version =~ /1.2/ and @res and (@res.get_elements '//Exception').count < 1
160
+ @success = true
161
+ else
162
+ @success = false
163
+ @res.elements.each('//Failure/Exception') do |s|
164
+ s.elements.each('message') do |m|
165
+ @error = m.text
166
+ end
167
+ s.elements.each('stacktrace') do |m|
168
+ @trace = m.text
169
+ end
170
+ end
171
+ end
172
+ # This is a hack to handle corner cases where a heavily loaded NeXpose instance
173
+ # drops our HTTP connection before processing. We try 5 times to establish a
174
+ # connection in these situations. The actual exception occurs in the Ruby
175
+ # http library, which is why we use such generic error classes.
176
+ rescue ::ArgumentError, ::NoMethodError
177
+ if @conn_tries < 5
178
+ @conn_tries += 1
179
+ retry
180
+ end
181
+ rescue ::Timeout::Error
182
+ if @conn_tries < 5
183
+ @conn_tries += 1
184
+ retry
185
+ end
186
+ @error = "NeXpose host did not respond"
187
+ rescue ::Errno::EHOSTUNREACH,::Errno::ENETDOWN,::Errno::ENETUNREACH,::Errno::ENETRESET,::Errno::EHOSTDOWN,::Errno::EACCES,::Errno::EINVAL,::Errno::EADDRNOTAVAIL
188
+ @error = "NeXpose host is unreachable"
189
+ # Handle console-level interrupts
190
+ rescue ::Interrupt
191
+ @error = "received a user interrupt"
192
+ rescue ::Errno::ECONNRESET,::Errno::ECONNREFUSED,::Errno::ENOTCONN,::Errno::ECONNABORTED
193
+ @error = "NeXpose service is not available"
194
+ rescue ::REXML::ParseException
195
+ @error = "NeXpose has not been properly licensed"
196
+ end
197
+
198
+ if ! (@success or @error)
199
+ @error = "NeXpose service returned an unrecognized response: #{@raw_response_data.inspect}"
200
+ end
201
+
202
+ @sid
203
+ end
204
+
205
+ def attributes(*args)
206
+ return if not @res.root
207
+ @res.root.attributes(*args)
208
+ end
209
+
210
+ def self.execute(url, req, api_version)
211
+ obj = self.new(req, url, api_version)
212
+ obj.execute
213
+ if(not obj.success)
214
+ raise APIError.new(obj, "Action failed: #{obj.error}")
215
+ end
216
+ obj
217
+ end
218
+
219
+ end
220
+
221
+ module NexposeAPI
222
+
223
+ def make_xml(name, opts={}, data='', append_session_id=true)
224
+ xml = REXML::Element.new(name)
225
+ if(@session_id and append_session_id)
226
+ xml.attributes['session-id'] = @session_id
227
+ end
228
+
229
+ opts.keys.each do |k|
230
+ xml.attributes[k] = "#{opts[k]}"
231
+ end
232
+
233
+ xml.text = data
234
+
235
+ xml
236
+ end
237
+
238
+ def scan_stop(param)
239
+ r = execute(make_xml('ScanStopRequest', { 'scan-id' => param }))
240
+ r.success
241
+ end
242
+
243
+ def scan_status(param)
244
+ r = execute(make_xml('ScanStatusRequest', { 'scan-id' => param }))
245
+ r.success ? r.attributes['status'] : nil
246
+ end
247
+
248
+ def scan_activity
249
+ r = execute(make_xml('ScanActivityRequest', { }))
250
+ if(r.success)
251
+ res = []
252
+ r.res.elements.each("//ScanSummary") do |scan|
253
+ res << {
254
+ :scan_id => scan.attributes['scan-id'].to_i,
255
+ :site_id => scan.attributes['site-id'].to_i,
256
+ :engine_id => scan.attributes['engine-id'].to_i,
257
+ :status => scan.attributes['status'].to_s,
258
+ :start_time => Date.parse(scan.attributes['startTime'].to_s).to_time
259
+ }
260
+ end
261
+ return res
262
+ else
263
+ return false
264
+ end
265
+ end
266
+
267
+ def scan_statistics(param)
268
+ r = execute(make_xml('ScanStatisticsRequest', {'scan-id' => param }))
269
+ if(r.success)
270
+ res = {}
271
+ r.res.elements.each("//ScanSummary/nodes") do |node|
272
+ res[:nodes] = {}
273
+ node.attributes.keys.each do |k|
274
+ res[:nodes][k] = node.attributes[k].to_i
275
+ end
276
+ end
277
+ r.res.elements.each("//ScanSummary/tasks") do |task|
278
+ res[:task] = {}
279
+ task.attributes.keys.each do |k|
280
+ res[:task][k] = task.attributes[k].to_i
281
+ end
282
+ end
283
+ r.res.elements.each("//ScanSummary/vulnerabilities") do |vuln|
284
+ res[:vulns] ||= {}
285
+ k = vuln.attributes['status'] + (vuln.attributes['severity'] ? ("-" + vuln.attributes['severity']) : '')
286
+ res[:vulns][k] = vuln.attributes['count'].to_i
287
+ end
288
+ r.res.elements.each("//ScanSummary") do |summ|
289
+ res[:summary] = {}
290
+ summ.attributes.keys.each do |k|
291
+ res[:summary][k] = summ.attributes[k]
292
+ if (res[:summary][k] =~ /^\d+$/)
293
+ res[:summary][k] = res[:summary][k].to_i
294
+ end
295
+ end
296
+ end
297
+ r.res.elements.each("//ScanSummary/message") do |message|
298
+ res[:message] = message.text
299
+ end
300
+ return res
301
+ else
302
+ return false
303
+ end
304
+ end
305
+
306
+ def report_generate(param)
307
+ r = execute(make_xml('ReportGenerateRequest', { 'report-id' => param }))
308
+ r.success
309
+ end
310
+
311
+ def report_last(param)
312
+ r = execute(make_xml('ReportHistoryRequest', { 'reportcfg-id' => param }))
313
+ res = nil
314
+ if(r.success)
315
+ stk = []
316
+ r.res.elements.each("//ReportSummary") do |rep|
317
+ stk << [ rep.attributes['id'].to_i, rep.attributes['report-URI'] ]
318
+ end
319
+ if (stk.length > 0)
320
+ stk.sort!{|a,b| b[0] <=> a[0]}
321
+ res = stk[0][1]
322
+ end
323
+ end
324
+ res
325
+ end
326
+
327
+ def report_history(param)
328
+ execute(make_xml('ReportHistoryRequest', { 'reportcfg-id' => param }))
329
+ end
330
+
331
+ def report_config_delete(param)
332
+ r = execute(make_xml('ReportDeleteRequest', { 'reportcfg-id' => param }))
333
+ r.success
334
+ end
335
+
336
+ def report_delete(param)
337
+ r = execute(make_xml('ReportDeleteRequest', { 'report-id' => param }))
338
+ r.success
339
+ end
340
+
341
+ def device_delete(param)
342
+ r = execute(make_xml('DeviceDeleteRequest', { 'site-id' => param }))
343
+ r.success
344
+ end
345
+
346
+ def asset_group_delete(connection, id, debug = false)
347
+ r = execute(make_xml('AssetGroupDeleteRequest', { 'group-id' => param }))
348
+ r.success
349
+ end
350
+
351
+ #-------------------------------------------------------------------------
352
+ # Returns all asset group information
353
+ #-------------------------------------------------------------------------
354
+ def asset_groups_listing()
355
+ r = execute(make_xml('AssetGroupListingRequest'))
356
+
357
+ if r.success
358
+ res = []
359
+ r.res.elements.each('//AssetGroupSummary') do |group|
360
+ res << {
361
+ :asset_group_id => group.attributes['id'].to_i,
362
+ :name => group.attributes['name'].to_s,
363
+ :description => group.attributes['description'].to_s,
364
+ :risk_score => group.attributes['riskscore'].to_f,
365
+ }
366
+ end
367
+ res
368
+ else
369
+ false
370
+ end
371
+ end
372
+
373
+ #-------------------------------------------------------------------------
374
+ # Returns an asset group configuration information for a specific group ID
375
+ #-------------------------------------------------------------------------
376
+ def asset_group_config(group_id)
377
+ r = execute(make_xml('AssetGroupConfigRequest', {'group-id' => group_id}))
378
+
379
+ if r.success
380
+ res = []
381
+ r.res.elements.each('//Devices/device') do |device_info|
382
+ res << {
383
+ :device_id => device_info.attributes['id'].to_i,
384
+ :site_id => device_info.attributes['site-id'].to_i,
385
+ :address => device_info.attributes['address'].to_s,
386
+ :riskfactor => device_info.attributes['riskfactor'].to_f,
387
+ }
388
+ end
389
+ res
390
+ else
391
+ false
392
+ end
393
+ end
394
+
395
+ #-----------------------------------------------------------------------
396
+ # Starts device specific site scanning.
397
+ #
398
+ # devices - An Array of device IDs
399
+ # hosts - An Array of Hashes [o]=>{:range=>"to,from"} [1]=>{:host=>host}
400
+ #-----------------------------------------------------------------------
401
+ def site_device_scan_start(site_id, devices, hosts)
402
+
403
+ if hosts == nil and devices == nil
404
+ raise ArgumentError.new("Both the device and host list is nil")
405
+ end
406
+
407
+ xml = make_xml('SiteDevicesScanRequest', {'site-id' => site_id})
408
+
409
+ if devices != nil
410
+ inner_xml = REXML::Element.new 'Devices'
411
+ for device_id in devices
412
+ inner_xml.add_element 'device', {'id' => "#{device_id}"}
413
+ end
414
+ xml.add_element inner_xml
415
+ end
416
+
417
+ if hosts != nil
418
+ inner_xml = REXML::Element.new 'Hosts'
419
+ hosts.each_index do |x|
420
+ if hosts[x].key? :range
421
+ to = hosts[x][:range].split(',')[0]
422
+ from = hosts[x][:range].split(',')[1]
423
+ inner_xml.add_element 'range', {'to' => "#{to}", 'from' => "#{from}"}
424
+ end
425
+ if hosts[x].key? :host
426
+ host_element = REXML::Element.new 'host'
427
+ host_element.text = "#{hosts[x][:host]}"
428
+ inner_xml.add_element host_element
429
+ end
430
+ end
431
+ xml.add_element inner_xml
432
+ end
433
+
434
+ r = execute xml
435
+ if r.success
436
+ r.res.elements.each('//Scan') do |scan_info|
437
+ return {
438
+ :scan_id => scan_info.attributes['scan-id'].to_i,
439
+ :engine_id => scan_info.attributes['engine-id'].to_i
440
+ }
441
+ end
442
+ else
443
+ false
444
+ end
445
+ end
446
+
447
+ def site_delete(param)
448
+ r = execute(make_xml('SiteDeleteRequest', { 'site-id' => param }))
449
+ r.success
450
+ end
451
+
452
+ def site_listing
453
+ r = execute(make_xml('SiteListingRequest', { }))
454
+
455
+ if(r.success)
456
+ res = []
457
+ r.res.elements.each("//SiteSummary") do |site|
458
+ res << {
459
+ :site_id => site.attributes['id'].to_i,
460
+ :name => site.attributes['name'].to_s,
461
+ :risk_factor => site.attributes['risk_factor'].to_f,
462
+ :risk_score => site.attributes['risk_score'].to_f,
463
+ }
464
+ end
465
+ return res
466
+ else
467
+ return false
468
+ end
469
+ end
470
+
471
+ #-----------------------------------------------------------------------
472
+ # TODO: Needs to be expanded to included details
473
+ #-----------------------------------------------------------------------
474
+ def site_scan_history(site_id)
475
+ r = execute(make_xml('SiteScanHistoryRequest', {'site-id' => site_id.to_s}))
476
+
477
+ if (r.success)
478
+ res = []
479
+ r.res.elements.each("//ScanSummary") do |site_scan_history|
480
+ res << {
481
+ :site_id => site_scan_history.attributes['site-id'].to_i,
482
+ :scan_id => site_scan_history.attributes['scan-id'].to_i,
483
+ :engine_id => site_scan_history.attributes['engine-id'].to_i,
484
+ :start_time => site_scan_history.attributes['startTime'].to_s,
485
+ :end_time => site_scan_history.attributes['endTime'].to_s
486
+ }
487
+ end
488
+ return res
489
+ else
490
+ false
491
+ end
492
+ end
493
+
494
+ ###################
495
+ # SILO MANAGEMENT #
496
+ ###################
497
+
498
+ #-------------------------------------------------------------------------
499
+ # Creates a multi-tenant user
500
+ #
501
+ # user_config - A map of the user data.
502
+ #
503
+ # REQUIRED PARAMS
504
+ # user-id, authsrcid, user-name, full-name, enabled, superuser
505
+ #
506
+ # OPTIONAL PARAMS
507
+ # email, password
508
+ #
509
+ # silo_configs - An array of maps of silo specific data
510
+ #
511
+ # REQUIRED PARAMS
512
+ # silo-id, role-name, all-groups, all-sites, default-silo
513
+ #
514
+ # allowed_groups/allowed_sites - An array of ids
515
+ #-------------------------------------------------------------------------
516
+ def create_multi_tenant_user(user_config, silo_configs)
517
+ xml = make_xml('MultiTenantUserCreateRequest')
518
+ mtu_config_xml = make_xml('MultiTenantUserConfig', user_config, '', false)
519
+
520
+ # Add the silo access
521
+ silo_xml = make_xml('SiloAccesses', {}, '', false)
522
+ silo_config_xml = make_xml('SiloAccess', {}, '', false)
523
+ silo_configs.keys.each do |k|
524
+ if k.eql? 'allowed_sites'
525
+ allowed_sites_xml = make_xml('AllowedSites', {}, '', false)
526
+ silo_configs['allowed_sites'].each do |allowed_site|
527
+ allowed_sites_xml.add_element make_xml('AllowedSite', {'id' => allowed_site}, '', false)
528
+ end
529
+ silo_config_xml.add_element allowed_sites_xml
530
+ elsif k.eql? 'allowed_groups'
531
+ allowed_groups_xml = make_xml('AllowedGroups', {}, '', false)
532
+ silo_configs['allowed_groups'].each do |allowed_group|
533
+ allowed_groups_xml.add_element make_xml('AllowedGroup', {'id' => allowed_group}, '', false)
534
+ end
535
+ silo_config_xml.add_element allowed_groups_xml
536
+ else
537
+ silo_config_xml.attributes[k] = silo_configs[k]
538
+ end
539
+ end
540
+ silo_xml.add_element silo_config_xml
541
+ mtu_config_xml.add_element silo_xml
542
+ xml.add_element mtu_config_xml
543
+ r = execute xml, '1.2'
544
+ r.success
545
+ end
546
+
547
+ #-------------------------------------------------------------------------
548
+ # Creates a silo profile
549
+ #
550
+ # silo_config - A map of the silo data.
551
+ #
552
+ # REQUIRED PARAMS
553
+ # id, name, all‐licensed-modules, all‐global-engines, all-global-report-templates, all‐global-scan‐templates
554
+ #
555
+ # OPTIONAL PARAMS
556
+ # description
557
+ #
558
+ # permissions - A map of an array of maps of silo specific data
559
+ #
560
+ # REQUIRED PARAMS
561
+ # silo-id, role-name, all-groups, all-sites, default-silo
562
+ #
563
+ # allowed_groups/allowed_sites - An array of ids
564
+ #-------------------------------------------------------------------------
565
+ def create_silo_profile silo_profile_config, permissions
566
+ xml = make_xml 'SiloProfileCreateRequest'
567
+ xml.add_element make_xml('SiloProfileConfig', silo_profile_config, '', false)
568
+
569
+ # Add the permissions
570
+ if permissions['global_report_templates']
571
+ grt_xml = make_xml('GlobalReportTemplates', {}, '', false)
572
+ permissions['global_report_templates'].each do |name|
573
+ grt_xml.add_element make_xml('GlobalReportTemplate', {'name' => name}, '', false)
574
+ end
575
+ xml.add_element grt_xml
576
+ end
577
+
578
+ if permissions['global_scan_engines']
579
+ gse_xml = make_xml('GlobalScanEngines', {}, '', false)
580
+ permissions['global_scan_engines'].each do |name|
581
+ gse_xml.add_element make_xml('GlobalScanEngine', {'name' => name}, '', false)
582
+ end
583
+ xml.add_element gse_xml
584
+ end
585
+
586
+ if permissions['global_scan_templates']
587
+ gst_xml = make_xml('GlobalScanTemplates', {}, '', false)
588
+ permissions['global_scan_templates'].each do |name|
589
+ gst_xml.add_element make_xml('GlobalScanTemplate', {'name' => name}, '', false)
590
+ end
591
+ xml.add_element gst_xml
592
+ end
593
+
594
+ if permissions['licensed_modules']
595
+ lm_xml = make_xml('LicensedModules', {}, '', false)
596
+ permissions['licensed_modules'].each do |name|
597
+ lm_xml.add_element make_xml('LicensedModule', {'name' => name}, '', false)
598
+ end
599
+ xml.add_element lm_xml
600
+ end
601
+
602
+ if permissions['restricted_report_formats']
603
+ rrf_xml = make_xml('RestrictedReportFormats', {}, '', false)
604
+ permissions['restricted_report_formats'].each do |name|
605
+ rrf_xml.add_element make_xml('RestrictedReportFormat', {'name' => name}, '', false)
606
+ end
607
+ xml.add_element rrf_xml
608
+ end
609
+
610
+ if permissions['restricted_report_sections']
611
+ rrs_xml = make_xml('RestrictedReportSections', {}, '', false)
612
+ permissions['restricted_report_sections'].each do |name|
613
+ rrs_xml.add_element make_xml('RestrictedReportSection', {'name' => name}, '', false)
614
+ end
615
+ xml.add_element rrs_xml
616
+ end
617
+
618
+ r = execute xml, '1.2'
619
+ r.success
620
+ end
621
+
622
+ #-------------------------------------------------------------------------
623
+ # Creates a silo profile
624
+ #
625
+ # silo_config - A map of the silo creation data.
626
+ #
627
+ # REQUIRED PARAMS
628
+ # id, name, silo-profile-id, max-assets, max-hosted-assets, max-users
629
+ #
630
+ # OPTIONAL PARAMS
631
+ # description
632
+ #-------------------------------------------------------------------------
633
+ def create_silo silo_config
634
+ xml = make_xml 'SiloCreateRequest'
635
+ silo_config_xml = make_xml 'SiloConfig', {}, '', false
636
+
637
+ # Add the attributes
638
+ silo_config.keys.each do |key|
639
+ if not 'merchant'.eql? key and not 'organization'.eql? key
640
+ silo_config_xml.attributes[key] = silo_config[key]
641
+ end
642
+ end
643
+
644
+ # Add Organization info
645
+ if silo_config['organization']
646
+ org_xml = make_xml 'Organization', silo_config['organization'], '', false
647
+ silo_config['organization'].keys.each do |key|
648
+ if not 'address'.eql? key
649
+ org_xml.attributes[key] = silo_config['organization'][key]
650
+ end
651
+ end
652
+
653
+ address_xml = make_xml 'Address', silo_config['organization']['address'], '', false
654
+ org_xml.add_element address_xml
655
+ silo_config_xml.add_element org_xml
656
+ end
657
+
658
+ # Add Merchant info
659
+ if silo_config['merchant']
660
+ merchant_xml = make_xml 'Merchant', {}, '', false
661
+
662
+ # add attributes only
663
+ silo_config['merchant'].keys.each do |key|
664
+ if not 'dba'.eql? key and not 'other_industries'.eql? key and not 'qsa'.eql? key and not 'address'.eql? key
665
+ merchant_xml.attributes[key] = silo_config[key]
666
+ end
667
+ end
668
+
669
+ #Now add the complex data types
670
+ if silo_config['merchant']['dba']
671
+ dba_xml = make_xml 'DBAs', {}, '', false
672
+ silo_config['merchant']['dbas'].each do |name|
673
+ dba_xml.add_element make_xml('DBA', {'name' => name}, '', false)
674
+ end
675
+ merchant_xml.add_element dba_xml
676
+ end
677
+
678
+ if silo_config['merchant']['other_industries']
679
+ ois_xml = make_xml 'OtherIndustries', {}, '', false
680
+ silo_config['merchant']['other_industries'].each do |name|
681
+ ois_xml.add_element make_xml('Industry', {'name' => name}, '', false)
682
+ end
683
+ merchant_xml.add_element ois_xml
684
+ end
685
+
686
+ if silo_config['merchant']['qsa']
687
+ qsa_xml = make_xml 'QSA', {}, '', false
688
+ silo_config['merchant']['qsa'].keys.each do |key|
689
+ if not 'address'.eql? key
690
+ qsa_xml.attributes[key] = silo_config['merchant']['qsa'][key]
691
+ end
692
+ end
693
+
694
+ # Add the address for this QSA
695
+ address_xml = make_xml 'Address', silo_config['merchant']['qsa']['address'], '', false
696
+
697
+ qsa_xml.add_element address_xml
698
+ merchant_xml.add_element qsa_xml
699
+ end
700
+ silo_config_xml.add_element merchant_xml
701
+ end
702
+
703
+ xml.add_element silo_config_xml
704
+ r = execute xml, '1.2'
705
+ r.success
706
+ end
707
+
708
+ def site_device_listing(site_id)
709
+ r = execute(make_xml('SiteDeviceListingRequest', { 'site-id' => site_id.to_s }))
710
+
711
+ if(r.success)
712
+ res = []
713
+ r.res.elements.each("//device") do |device|
714
+ res << {
715
+ :device_id => device.attributes['id'].to_i,
716
+ :address => device.attributes['address'].to_s,
717
+ :risk_factor => device.attributes['risk_factor'].to_f,
718
+ :risk_score => device.attributes['risk_score'].to_f,
719
+ }
720
+ end
721
+ return res
722
+ else
723
+ return false
724
+ end
725
+ end
726
+
727
+ def report_template_listing
728
+ r = execute(make_xml('ReportTemplateListingRequest', { }))
729
+
730
+ if(r.success)
731
+ res = []
732
+ r.res.elements.each("//ReportTemplateSummary") do |template|
733
+ desc = ''
734
+ template.elements.each("//description") do |ent|
735
+ desc = ent.text
736
+ end
737
+
738
+ res << {
739
+ :template_id => template.attributes['id'].to_s,
740
+ :name => template.attributes['name'].to_s,
741
+ :description => desc.to_s
742
+ }
743
+ end
744
+ return res
745
+ else
746
+ return false
747
+ end
748
+ end
749
+
750
+
751
+ def console_command(cmd_string)
752
+ xml = make_xml('ConsoleCommandRequest', { })
753
+ cmd = REXML::Element.new('Command')
754
+ cmd.text = cmd_string
755
+ xml << cmd
756
+
757
+ r = execute(xml)
758
+
759
+ if(r.success)
760
+ res = ""
761
+ r.res.elements.each("//Output") do |out|
762
+ res << out.text.to_s
763
+ end
764
+
765
+ return res
766
+ else
767
+ return false
768
+ end
769
+ end
770
+
771
+ def system_information
772
+ r = execute(make_xml('SystemInformationRequest', { }))
773
+
774
+ if(r.success)
775
+ res = {}
776
+ r.res.elements.each("//Statistic") do |stat|
777
+ res[ stat.attributes['name'].to_s ] = stat.text.to_s
778
+ end
779
+
780
+ return res
781
+ else
782
+ return false
783
+ end
784
+ end
785
+
786
+ end
787
+
788
+ # === Description
789
+ # Object that represents a connection to a NeXpose Security Console.
790
+ #
791
+ # === Examples
792
+ # # Create a new Nexpose Connection on the default port
793
+ # nsc = Connection.new("10.1.40.10","nxadmin","password")
794
+ #
795
+ # # Login to NSC and Establish a Session ID
796
+ # nsc.login()
797
+ #
798
+ # # Check Session ID
799
+ # if (nsc.session_id)
800
+ # puts "Login Successful"
801
+ # else
802
+ # puts "Login Failure"
803
+ # end
804
+ #
805
+ # # //Logout
806
+ # logout_success = nsc.logout()
807
+ # if (! logout_success)
808
+ # puts "Logout Failure" + "<p>" + nsc.error_msg.to_s
809
+ # end
810
+ #
811
+ class Connection
812
+ include XMLUtils
813
+ include NexposeAPI
814
+
815
+ # true if an error condition exists; false otherwise
816
+ attr_reader :error
817
+ # Error message string
818
+ attr_reader :error_msg
819
+ # The last XML request sent by this object
820
+ attr_reader :request_xml
821
+ # The last XML response received by this object
822
+ attr_reader :response_xml
823
+ # Session ID of this connection
824
+ attr_reader :session_id
825
+ # The hostname or IP Address of the NSC
826
+ attr_reader :host
827
+ # The port of the NSC (default is 3780)
828
+ attr_reader :port
829
+ # The username used to login to the NSC
830
+ attr_reader :username
831
+ # The password used to login to the NSC
832
+ attr_reader :password
833
+ # The URL for communication
834
+ attr_reader :url
835
+
836
+ # Constructor for Connection
837
+ def initialize(ip, user, pass, port = 3780)
838
+ @host = ip
839
+ @port = port
840
+ @username = user
841
+ @password = pass
842
+ @session_id = nil
843
+ @error = false
844
+ @url = "https://#{@host}:#{@port}/api/VERSION_STRING/xml"
845
+ end
846
+
847
+ # Establish a new connection and Session ID
848
+ def login
849
+ begin
850
+ r = execute(make_xml('LoginRequest', { 'sync-id' => 0, 'password' => @password, 'user-id' => @username }))
851
+ rescue APIError
852
+ raise AuthenticationFailed.new(r)
853
+ end
854
+ if(r.success)
855
+ @session_id = r.sid
856
+ return true
857
+ end
858
+ end
859
+
860
+ # Logout of the current connection
861
+ def logout
862
+ r = execute(make_xml('LogoutRequest', {'sync-id' => 0}))
863
+ if(r.success)
864
+ return true
865
+ end
866
+ raise APIError.new(r, 'Logout failed')
867
+ end
868
+
869
+ # Execute an API request
870
+ def execute(xml, version = '1.1')
871
+ @api_version = version
872
+ APIRequest.execute(@url.sub('VERSION_STRING', @api_version),xml.to_s, @api_version)
873
+ end
874
+
875
+ # Download a specific URL
876
+ def download(url)
877
+ uri = URI.parse(url)
878
+ http = Net::HTTP.new(@host, @port)
879
+ http.use_ssl = true
880
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE # XXX: security issue
881
+ headers = {'Cookie' => "nexposeCCSessionID=#{@session_id}"}
882
+ resp, data = http.get(uri.path, headers)
883
+ data
884
+ end
885
+ end
886
+
887
+ # === Description
888
+ # Object that represents a listing of all of the sites available on an NSC.
889
+ #
890
+ # === Example
891
+ # # Create a new Nexpose Connection on the default port and Login
892
+ # nsc = Connection.new("10.1.40.10","nxadmin","password")
893
+ # nsc->login();
894
+ #
895
+ # # Get Site Listing
896
+ # sitelisting = SiteListing.new(nsc)
897
+ #
898
+ # # Enumerate through all of the SiteSummaries
899
+ # sitelisting.sites.each do |sitesummary|
900
+ # # Do some operation on each site
901
+ # end
902
+ #
903
+ class SiteListing
904
+ # true if an error condition exists; false otherwise
905
+ attr_reader :error
906
+ # Error message string
907
+ attr_reader :error_msg
908
+ # The last XML request sent by this object
909
+ attr_reader :request_xml
910
+ # The last XML response received by this object
911
+ attr_reader :response_xml
912
+ # The NSC Connection associated with this object
913
+ attr_reader :connection
914
+ # Array containing SiteSummary objects for each site in the connection
915
+ attr_reader :sites
916
+ # The number of sites
917
+ attr_reader :site_count
918
+
919
+ # Constructor
920
+ # SiteListing (connection)
921
+ def initialize(connection)
922
+ @sites = []
923
+
924
+ @connection = connection
925
+
926
+ r = @connection.execute('<SiteListingRequest session-id="' + @connection.session_id.to_s + '"/>')
927
+
928
+ if (r.success)
929
+ parse(r.res)
930
+ else
931
+ raise APIError.new(r, "Failed to get site listing")
932
+ end
933
+ end
934
+
935
+ def parse(r)
936
+ r.elements.each('SiteListingResponse/SiteSummary') do |s|
937
+ site_summary = SiteSummary.new(
938
+ s.attributes['id'].to_s,
939
+ s.attributes['name'].to_s,
940
+ s.attributes['description'].to_s,
941
+ s.attributes['riskfactor'].to_s
942
+ )
943
+ @sites.push(site_summary)
944
+ end
945
+ @site_count = @sites.length
946
+ end
947
+ end
948
+
949
+ # === Description
950
+ # Object that represents the summary of a NeXpose Site.
951
+ #
952
+ class SiteSummary
953
+ # The Site ID
954
+ attr_reader :id
955
+ # The Site Name
956
+ attr_reader :site_name
957
+ # A Description of the Site
958
+ attr_reader :description
959
+ # User assigned risk multiplier
960
+ attr_reader :riskfactor
961
+
962
+ # Constructor
963
+ # SiteSummary(id, site_name, description, riskfactor = 1)
964
+ def initialize(id, site_name, description, riskfactor = 1)
965
+ @id = id
966
+ @site_name = site_name
967
+ @description = description
968
+ @riskfactor = riskfactor
969
+ end
970
+
971
+ def _set_id(id)
972
+ @id = id
973
+ end
974
+ end
975
+
976
+ # === Description
977
+ # Object that represents a single IP address or an inclusive range of IP addresses. If to is nil then the from field will be used to specify a single IP Address only.
978
+ #
979
+ class IPRange
980
+ # Start of Range *Required
981
+ attr_reader :from;
982
+ # End of Range *Optional (If Null then IPRange is a single IP Address)
983
+ attr_reader :to;
984
+
985
+ def initialize(from, to = nil)
986
+ @from = from
987
+ @to = to
988
+ end
989
+
990
+ include Sanitize
991
+ def to_xml
992
+ if (to and not to.empty?)
993
+ return %Q{<range from="#{from}" to="#{to}"/>}
994
+ else
995
+ return %Q{<range from="#{from}"/>}
996
+ end
997
+ end
998
+ end
999
+
1000
+ # === Description
1001
+ # Object that represents a hostname to be added to a site.
1002
+ class HostName
1003
+
1004
+ # The hostname
1005
+ attr_reader :hostname
1006
+
1007
+ def initialize(hostname)
1008
+ @hostname = hostname
1009
+ end
1010
+
1011
+ include Sanitize
1012
+ def to_xml
1013
+ "<hostname>#{replace_entities(hostname)}</hostname>"
1014
+ end
1015
+ end
1016
+
1017
+ # === Description
1018
+ # Object that represents the configuration of a Site. This object is automatically created when a new Site object is instantiated.
1019
+ #
1020
+ class SiteConfig
1021
+ # true if an error condition exists; false otherwise
1022
+ attr_reader :error
1023
+ # Error message string
1024
+ attr_reader :error_msg
1025
+ # The last XML request sent by this object
1026
+ attr_reader :request_xml
1027
+ # The last XML response received by this object
1028
+ attr_reader :response_xml
1029
+ # The NSC Connection associated with this object
1030
+ attr_reader :connection
1031
+ # The Site ID
1032
+ attr_reader :site_id
1033
+ # The Site Name
1034
+ attr_reader :site_name
1035
+ # A Description of the Site
1036
+ attr_reader :description
1037
+ # User assigned risk multiplier
1038
+ attr_reader :riskfactor
1039
+ # Array containing ((IPRange|HostName)*)
1040
+ attr_reader :hosts
1041
+ # Array containing (AdminCredentials*)
1042
+ attr_reader :credentials
1043
+ # Array containing ((SmtpAlera|SnmpAlert|SyslogAlert)*)
1044
+ attr_reader :alerts
1045
+ # ScanConfig object which holds Schedule and ScanTrigger Objects
1046
+ attr_reader :scanConfig
1047
+
1048
+ def initialize()
1049
+ @xml_tag_stack = Array.new()
1050
+ @hosts = Array.new()
1051
+ @credentials = Array.new()
1052
+ @alerts = Array.new()
1053
+ @error = false
1054
+ end
1055
+
1056
+ # Adds a new host to the hosts array
1057
+ def addHost(host)
1058
+ @hosts.push(host)
1059
+ end
1060
+
1061
+ # Adds a new alert to the alerts array
1062
+ def addAlert(alert)
1063
+ @alerts.push(alert)
1064
+ end
1065
+
1066
+ # Adds a new set of credentials to the credentials array
1067
+ def addCredentials(credential)
1068
+ @credentials.push(credential)
1069
+ end
1070
+
1071
+ # TODO
1072
+ def getSiteConfig(connection,site_id)
1073
+ @connection = connection
1074
+ @site_id = site_id
1075
+
1076
+ r = APIRequest.execute(@connection.url,'<SiteConfigRequest session-id="' + @connection.session_id + '" site-id="' + @site_id + '"/>')
1077
+ parse(r.res)
1078
+ end
1079
+
1080
+ def _set_site_id(site_id)
1081
+ @site_id = site_id
1082
+ end
1083
+
1084
+ def _set_site_name(site_name)
1085
+ @site_name = site_name
1086
+ end
1087
+
1088
+ def _set_description(description)
1089
+ @description = description
1090
+ end
1091
+
1092
+ def _set_riskfactor(riskfactor)
1093
+ @riskfactor = riskfactor
1094
+ end
1095
+
1096
+ def _set_scanConfig(scanConfig)
1097
+ @scanConfig = scanConfig
1098
+ end
1099
+
1100
+ def _set_connection(connection)
1101
+ @connection = connection
1102
+ end
1103
+ =begin
1104
+ <SiteConfigResponse success='1'>
1105
+ <Site name='Site1' id='243' description='' riskfactor='1.0'>
1106
+ <Hosts>
1107
+ <range from='127.0.0.1'/>
1108
+ </Hosts>
1109
+ <Credentials>
1110
+ </Credentials>
1111
+ <Alerting>
1112
+ </Alerting>
1113
+ <ScanConfig configID='243' name='Full audit' configVersion='3' engineID='2' templateID='full-audit'>
1114
+ <Schedules>
1115
+ </Schedules>
1116
+ <ScanTriggers>
1117
+ </ScanTriggers>
1118
+ </ScanConfig>
1119
+ </Site>
1120
+
1121
+ =end
1122
+
1123
+ def parse(response)
1124
+ response.elements.each('SiteConfigResponse/Site') do |s|
1125
+ @site_id = s.attributes['id']
1126
+ @site_name = s.attributes['name']
1127
+ @description = s.attributes['description']
1128
+ @riskfactor = s.attributes['riskfactor']
1129
+ s.elements.each('Hosts/range') do |r|
1130
+ @hosts.push(IPRange.new(r.attributes['from'],r.attributes['to']))
1131
+ end
1132
+ s.elements.each('ScanConfig') do |c|
1133
+ @scanConfig = ScanConfig.new(c.attributes['configID'],
1134
+ c.attributes['name'],
1135
+ c.attributes['configVersion'],
1136
+ c.attributes['templateID'])
1137
+ s.elements.each('Schedule') do |schedule|
1138
+ schedule = new Schedule(schedule.attributes["type"], schedule.attributes["interval"], schedule.attributes["start"], schedule.attributes["enabled"])
1139
+ @scanConfig.addSchedule(schedule)
1140
+ end
1141
+ end
1142
+
1143
+ s.elements.each('Alerting/Alert') do |a|
1144
+
1145
+ a.elements.each('smtpAlert') do |smtp|
1146
+ smtp_alert = SmtpAlert.new(a.attributes["name"], smtp.attributes["sender"], smtp.attributes["limitText"], a.attributes["enabled"])
1147
+
1148
+ smtp.elements.each('recipient') do |recipient|
1149
+ smtp_alert.addRecipient(recipient.text)
1150
+ end
1151
+ @alerts.push(smtp_alert)
1152
+ end
1153
+
1154
+ a.elements.each('snmpAlert') do |snmp|
1155
+ snmp_alert = SnmpAlert.new(a.attributes["name"], snmp.attributes["community"], snmp.attributes["server"], a.attributes["enabled"])
1156
+ @alerts.push(snmp_alert)
1157
+ end
1158
+ a.elements.each('syslogAlert') do |syslog|
1159
+ syslog_alert = SyslogAlert.new(a.attributes["name"], syslog.attributes["server"], a.attributes["enabled"])
1160
+ @alerts.push(syslog_alert)
1161
+ end
1162
+
1163
+ a.elements.each('vulnFilter') do |vulnFilter|
1164
+
1165
+ #vulnfilter = new VulnFilter.new(a.attributes["typemask"], a.attributes["severityThreshold"], $attrs["MAXALERTS"])
1166
+ # Pop off the top alert on the stack
1167
+ #$alert = @alerts.pop()
1168
+ # Add the new recipient string to the Alert Object
1169
+ #$alert.setVulnFilter($vulnfilter)
1170
+ # Push the alert back on to the alert stack
1171
+ #array_push($this->alerts, $alert)
1172
+ end
1173
+
1174
+ a.elements.each('scanFilter') do |scanFilter|
1175
+ #<scanFilter scanStop='0' scanFailed='0' scanStart='1'/>
1176
+ #scanfilter = ScanFilter.new(scanFilter.attributes['scanStop'],scanFilter.attributes['scanFailed'],scanFilter.attributes['scanStart'])
1177
+ #alert = @alerts.pop()
1178
+ #alert.setScanFilter(scanfilter)
1179
+ #@alerts.push(alert)
1180
+ end
1181
+ end
1182
+ end
1183
+ end
1184
+ end
1185
+
1186
+ # === Description
1187
+ # Object that represents the scan history of a site.
1188
+ #
1189
+ class SiteScanHistory
1190
+ # true if an error condition exists; false otherwise
1191
+ attr_reader :error
1192
+ # Error message string
1193
+ attr_reader :error_msg
1194
+ # The last XML request sent by this object
1195
+ attr_reader :request_xml
1196
+ # The last XML response received by this object
1197
+ attr_reader :response_xml
1198
+ # The NSC Connection associated with this object
1199
+ attr_reader :connection
1200
+ # The Site ID
1201
+ attr_reader :site_id
1202
+ # //Array containing (ScanSummary*)
1203
+ attr_reader :scan_summaries
1204
+
1205
+ def initialize(connection, id)
1206
+ @site_id = id
1207
+ @error = false
1208
+ @connection = connection
1209
+ @scan_summaries = Array.new()
1210
+
1211
+ r = @connection.execute('<SiteScanHistoryRequest' + ' session-id="' + @connection.session_id + '" site-id="' + @site_id + '"/>')
1212
+ status = r.success
1213
+ end
1214
+ end
1215
+
1216
+ # === Description
1217
+ # Object that represents a listing of devices for a site or the entire NSC. Note that only devices which are accessible to the account used to create the connection object will be returned. This object is created and populated automatically with the instantiation of a new Site object.
1218
+ #
1219
+ class SiteDeviceListing
1220
+
1221
+ # true if an error condition exists; false otherwise
1222
+ attr_reader :error
1223
+ # Error message string
1224
+ attr_reader :error_msg
1225
+ # The last XML request sent by this object
1226
+ attr_reader :request_xml
1227
+ # The last XML response received by this object
1228
+ attr_reader :response_xml
1229
+ # The NSC Connection associated with this object
1230
+ attr_reader :connection
1231
+ # The Site ID. 0 if all sites are specified.
1232
+ attr_reader :site_id
1233
+ # //Array of (Device)*
1234
+ attr_reader :devices
1235
+
1236
+ def initialize(connection, site_id = 0)
1237
+
1238
+ @site_id = site_id
1239
+ @error = false
1240
+ @connection = connection
1241
+ @devices = Array.new()
1242
+
1243
+ r = nil
1244
+ if (@site_id)
1245
+ r = @connection.execute('<SiteDeviceListingRequest session-id="' + connection.session_id + '" site-id="' + @site_id + '"/>')
1246
+ else
1247
+ r = @connection.execute('<SiteDeviceListingRequest session-id="' + connection.session_id + '"/>')
1248
+ end
1249
+
1250
+ if(r.success)
1251
+ response.elements.each('SiteDeviceListingResponse/SiteDevices/device') do |d|
1252
+ @devices.push(Device.new(d.attributes['id'],@site_id,d.attributes["address"],d.attributes["riskfactor"],d.attributes['riskscore']))
1253
+ end
1254
+ end
1255
+ end
1256
+ end
1257
+
1258
+ # === Description
1259
+ # Object that represents a site, including the site configuration, scan history, and device listing.
1260
+ #
1261
+ # === Example
1262
+ # # Create a new Nexpose Connection on the default port and Login
1263
+ # nsc = Connection.new("10.1.40.10","nxadmin","password")
1264
+ # nsc.login()
1265
+ #
1266
+ # # Get an Existing Site
1267
+ # site_existing = Site.new(nsc,184)
1268
+ #
1269
+ # # Create a New Site, add some hosts, and save it to the NSC
1270
+ # site = Site.new(nsc)
1271
+ # site.setSiteConfig("New Site", "New Site Created in the API")
1272
+ #
1273
+ # # Add the hosts
1274
+ # site.site_config.addHost(HostName.new("localhost"))
1275
+ # site.site_config.addHost(IPRange.new("192.168.7.1","192.168.7.255"))
1276
+ # site.site_config.addHost(IPRange.new("10.1.20.30"))
1277
+ #
1278
+ # status = site.saveSite()
1279
+ #
1280
+ class Site
1281
+ # true if an error condition exists; false otherwise
1282
+ attr_reader :error
1283
+ # Error message string
1284
+ attr_reader :error_msg
1285
+ # The last XML request sent by this object
1286
+ attr_reader :request_xml
1287
+ # The last XML response received by this object
1288
+ attr_reader :response_xml
1289
+ # The NSC Connection associated with this object
1290
+ attr_reader :connection
1291
+ # The Site ID
1292
+ # site_id = -1 means create a new site. The NSC will assign a new site_id on SiteSave.
1293
+ attr_reader :site_id
1294
+ # A summary overview of this site
1295
+ # SiteSummary Object
1296
+ attr_reader :site_summary
1297
+ # The configuration of this site
1298
+ # SiteConfig Object
1299
+ attr_reader :site_config
1300
+ # The device listing for this site
1301
+ # SiteDeviceListing Object
1302
+ attr_reader :site_device_listing
1303
+ # The scan history of this site
1304
+ # SiteScanHistory Object
1305
+ attr_reader :site_scan_history
1306
+
1307
+ def initialize(connection, site_id = -1)
1308
+ @error = false
1309
+ @connection = connection
1310
+ @site_id = site_id
1311
+
1312
+ # If site_id > 0 then get SiteConfig
1313
+ if (@site_id.to_i > 0)
1314
+ # Create new SiteConfig object
1315
+ @site_config = SiteConfig.new()
1316
+ # Populate SiteConfig Obect with Data from the NSC
1317
+ @site_config.getSiteConfig(@connection,@site_id)
1318
+ @site_summary = SiteSummary.new(@site_id, @site_config.site_name, @site_config.description, @site_config.riskfactor)
1319
+ @site_scan_history = SiteScanHistory.new(@connection,@site_id)
1320
+ @site_device_listing = SiteDeviceListing.new(@connection,@site_id)
1321
+
1322
+ else
1323
+ # Just in case user enters a number > -1 or = 0
1324
+ @site_id = -1
1325
+
1326
+ @site_config = SiteConfig.new()
1327
+ setSiteConfig("New Site " + rand(999999999999).to_s,"")
1328
+ @site_summary = nil
1329
+
1330
+ end
1331
+
1332
+ end
1333
+
1334
+ # Creates a new site summary
1335
+ def setSiteSummary(site_name, description, riskfactor = 1)
1336
+ @site_summary = SiteSummary.new(-1,site_name,description,riskfactor)
1337
+
1338
+ end
1339
+
1340
+ # Creates a new site configuration
1341
+ def setSiteConfig(site_name, description, riskfactor = 1)
1342
+ setSiteSummary(site_name,description,riskfactor)
1343
+ @site_config = SiteConfig.new()
1344
+ @site_config._set_site_id(-1)
1345
+ @site_config._set_site_name(site_name)
1346
+ @site_config._set_description(description)
1347
+ @site_config._set_riskfactor(riskfactor)
1348
+ @site_config._set_scanConfig(ScanConfig.new(-1,"tmp","full-audit"))
1349
+ @site_config._set_connection(@connection)
1350
+
1351
+ end
1352
+
1353
+ # Initiates a scan of this site. If successful returns scan_id and engine_id in an associative array. Returns false if scan is unsuccessful.
1354
+ def scanSite()
1355
+ r = @connection.execute('<SiteScanRequest session-id="' + "#{@connection.session_id}" + '" site-id="' + "#{@site_id}" + '"/>')
1356
+ if(r.success)
1357
+ res = {}
1358
+ r.res.elements.each('//Scan/') do |s|
1359
+ res[:scan_id] = s.attributes['scan-id']
1360
+ res[:engine_id] = s.attributes['engine-id']
1361
+ end
1362
+ return res
1363
+ else
1364
+ return false
1365
+ end
1366
+ end
1367
+
1368
+ # Saves this site in the NSC
1369
+ def saveSite()
1370
+ r = @connection.execute('<SiteSaveRequest session-id="' + @connection.session_id + '">' + getSiteXML() + ' </SiteSaveRequest>')
1371
+ if (r.success)
1372
+ @site_id = r.attributes['site-id']
1373
+ @site_config._set_site_id(@site_id)
1374
+ @site_config.scanConfig._set_configID(@site_id)
1375
+ @site_config.scanConfig._set_name(@site_id)
1376
+ return true
1377
+ else
1378
+ return false
1379
+ end
1380
+ end
1381
+
1382
+ def deleteSite()
1383
+ r = @connection.execute('<SiteDeleteRequest session-id="' + @connection.session_id.to_s + '" site-id="' + @site_id + '"/>')
1384
+ r.success
1385
+ end
1386
+
1387
+
1388
+ def printSite()
1389
+ puts "Site ID: " + @site_summary.id
1390
+ puts "Site Name: " + @site_summary.site_name
1391
+ puts "Site Description: " + @site_summary.description
1392
+ puts "Site Risk Factor: " + @site_summary.riskfactor
1393
+ end
1394
+
1395
+ def getSiteXML()
1396
+
1397
+ xml = '<Site id="' + "#{@site_config.site_id}" + '" name="' + "#{@site_config.site_name}" + '" description="' + "#{@site_config.description}" + '" riskfactor="' + "#{@site_config.riskfactor}" + '">'
1398
+
1399
+ xml << ' <Hosts>'
1400
+ @site_config.hosts.each do |h|
1401
+ xml << h.to_xml if h.respond_to? :to_xml
1402
+ end
1403
+ xml << '</Hosts>'
1404
+
1405
+ xml << '<Credentials>'
1406
+ @site_config.credentials.each do |c|
1407
+ xml << c.to_xml if c.respond_to? :to_xml
1408
+ end
1409
+ xml << ' </Credentials>'
1410
+
1411
+ xml << ' <Alerting>'
1412
+ @site_config.alerts.each do |a|
1413
+ xml << a.to_xml if a.respond_to? :to_xml
1414
+ end
1415
+ xml << ' </Alerting>'
1416
+
1417
+ xml << ' <ScanConfig configID="' + "#{@site_config.scanConfig.configID}" + '" name="' + "#{@site_config.scanConfig.name}" + '" templateID="' + "#{@site_config.scanConfig.templateID}" + '" configVersion="' + "#{@site_config.scanConfig.configVersion}" + '">'
1418
+
1419
+ xml << ' <Schedules>'
1420
+ @site_config.scanConfig.schedules.each do |s|
1421
+ xml << ' <Schedule enabled="' + s.enabled + '" type="' + s.type + '" interval="' + s.interval + '" start="' + s.start + '"/>'
1422
+ end
1423
+ xml << ' </Schedules>'
1424
+
1425
+ xml << ' <ScanTriggers>'
1426
+ @site_config.scanConfig.scanTriggers.each do |s|
1427
+
1428
+ if (s.class.to_s == "Nexpose::AutoUpdate")
1429
+ xml << ' <autoUpdate enabled="' + s.enabled + '" incremental="' + s.incremental + '"/>'
1430
+ end
1431
+ end
1432
+
1433
+ xml << ' </ScanTriggers>'
1434
+
1435
+ xml << ' </ScanConfig>'
1436
+
1437
+ xml << ' </Site>'
1438
+
1439
+ return xml
1440
+ end
1441
+ end
1442
+
1443
+ # === Description
1444
+ # Object that represents administrative credentials to be used during a scan. When retrived from an existing site configuration the credentials will be returned as a security blob and can only be passed back as is during a Site Save operation. This object can only be used to create a new set of credentials.
1445
+ #
1446
+ class AdminCredentials
1447
+ # Security blob for an existing set of credentials
1448
+ attr_reader :securityblob
1449
+ # Designates if this object contains user defined credentials or a security blob
1450
+ attr_reader :isblob
1451
+ # The service for these credentials. Can be All.
1452
+ attr_reader :service
1453
+ # The host for these credentials. Can be Any.
1454
+ attr_reader :host
1455
+ # The port on which to use these credentials.
1456
+ attr_reader :port
1457
+ # The user id or username
1458
+ attr_reader :userid
1459
+ # The password
1460
+ attr_reader :password
1461
+ # The realm for these credentials
1462
+ attr_reader :realm
1463
+
1464
+
1465
+ def initialize(isblob = false)
1466
+ @isblob = isblob
1467
+ end
1468
+
1469
+ # Sets the credentials information for this object.
1470
+ def setCredentials(service, host, port, userid, password, realm)
1471
+ @isblob = false
1472
+ @securityblob = nil
1473
+ @service = service
1474
+ @host = host
1475
+ @port = port
1476
+ @userid = userid
1477
+ @password = password
1478
+ @realm = realm
1479
+ end
1480
+
1481
+ # TODO: add description
1482
+ def setService(service)
1483
+ @service = service
1484
+ end
1485
+
1486
+ def setHost(host)
1487
+ @host = host
1488
+ end
1489
+
1490
+ # TODO: add description
1491
+ def setBlob(securityblob)
1492
+ @isblob = true
1493
+ @securityblob = securityblob
1494
+ end
1495
+
1496
+ include Sanitize
1497
+ def to_xml
1498
+ xml = ''
1499
+ xml << '<adminCredentials'
1500
+ xml << %Q{ service="#{replace_entities(service)}"} if (service)
1501
+ xml << %Q{ userid="#{replace_entities(userid)}"} if (userid)
1502
+ xml << %Q{ password="#{replace_entities(password)}"} if (password)
1503
+ xml << %Q{ realm="#{replace_entities(realm)}"} if (realm)
1504
+ xml << %Q{ host="#{replace_entities(host)}"} if (host)
1505
+ xml << %Q{ port="#{replace_entities(port)}"} if (port)
1506
+ xml << '>'
1507
+ xml << replace_entities(securityblob) if (isblob)
1508
+ xml << '</adminCredentials>'
1509
+
1510
+ xml
1511
+ end
1512
+ end
1513
+
1514
+
1515
+ # === Description
1516
+ # Object that represents an SMTP (Email) Alert.
1517
+ #
1518
+ class SmtpAlert
1519
+ # A unique name for this alert
1520
+ attr_reader :name
1521
+ # If this alert is enabled or not
1522
+ attr_reader :enabled
1523
+ # The email address of the sender
1524
+ attr_reader :sender
1525
+ # Limit the text for mobile devices
1526
+ attr_reader :limitText
1527
+ # Array containing Strings of email addresses
1528
+ # Array of strings with the email addresses of the intended recipients
1529
+ attr_reader :recipients
1530
+ # The vulnerability filter to trigger the alert
1531
+ attr_reader :vulnFilter
1532
+ # The alert type
1533
+ attr_reader :type
1534
+
1535
+ def initialize(name, sender, limitText, enabled = 1)
1536
+ @type = :smtp
1537
+ @name = name
1538
+ @sender = sender
1539
+ @enabled = enabled
1540
+ @limitText = limitText
1541
+ @recipients = Array.new()
1542
+ # Sets default vuln filter - All Events
1543
+ setVulnFilter(VulnFilter.new("50790400",1))
1544
+ end
1545
+
1546
+ # Adds a new Recipient to the recipients array
1547
+ def addRecipient(recipient)
1548
+ @recipients.push(recipient)
1549
+ end
1550
+
1551
+ # Sets the Vulnerability Filter for this alert.
1552
+ def setVulnFilter(vulnFilter)
1553
+ @vulnFilter = vulnFilter
1554
+ end
1555
+
1556
+ include Sanitize
1557
+ def to_xml
1558
+ xml = "<smtpAlert"
1559
+ xml << %Q{ name="#{replace_entities(name)}"}
1560
+ xml << %Q{ enabled="#{replace_entities(enabled)}"}
1561
+ xml << %Q{ sender="#{replace_entities(sender)}"}
1562
+ xml << %Q{ limitText="#{replace_entities(limitText)}">}
1563
+ recipients.each do |recpt|
1564
+ xml << "<recipient>#{replace_entities(recpt)}</recipient>"
1565
+ end
1566
+ xml << vulnFilter.to_xml
1567
+ xml << "</smtpAlert>"
1568
+ xml
1569
+ end
1570
+ end
1571
+
1572
+ # === Description
1573
+ # Object that represents an SNMP Alert.
1574
+ #
1575
+ class SnmpAlert
1576
+
1577
+ # A unique name for this alert
1578
+ attr_reader :name
1579
+ # If this alert is enabled or not
1580
+ attr_reader :enabled
1581
+ # The community string
1582
+ attr_reader :community
1583
+ # The SNMP server to sent this alert
1584
+ attr_reader :server
1585
+ # The vulnerability filter to trigger the alert
1586
+ attr_reader :vulnFilter
1587
+ # The alert type
1588
+ attr_reader :type
1589
+
1590
+ def initialize(name, community, server, enabled = 1)
1591
+ @type = :snmp
1592
+ @name = name
1593
+ @community = community
1594
+ @server = server
1595
+ @enabled = enabled
1596
+ # Sets default vuln filter - All Events
1597
+ setVulnFilter(VulnFilter.new("50790400",1))
1598
+ end
1599
+
1600
+ # Sets the Vulnerability Filter for this alert.
1601
+ def setVulnFilter(vulnFilter)
1602
+ @vulnFilter = vulnFilter
1603
+ end
1604
+
1605
+ include Sanitize
1606
+ def to_xml
1607
+ xml = "<snmpAlert"
1608
+ xml << %Q{ name="#{replace_entities(name)}"}
1609
+ xml << %Q{ enabled="#{replace_entities(enabled)}"}
1610
+ xml << %Q{ community="#{replace_entities(community)}"}
1611
+ xml << %Q{ server="#{replace_entities(server)}">}
1612
+ xml << vulnFilter.to_xml
1613
+ xml << "</snmpAlert>"
1614
+ xml
1615
+ end
1616
+
1617
+ end
1618
+
1619
+ # === Description
1620
+ # Object that represents a Syslog Alert.
1621
+ #
1622
+ class SyslogAlert
1623
+
1624
+ # A unique name for this alert
1625
+ attr_reader :name
1626
+ # If this alert is enabled or not
1627
+ attr_reader :enabled
1628
+ # The Syslog server to sent this alert
1629
+ attr_reader :server
1630
+ # The vulnerability filter to trigger the alert
1631
+ attr_reader :vulnFilter
1632
+ # The alert type
1633
+ attr_reader :type
1634
+
1635
+ def initialize(name, server, enabled = 1)
1636
+ @type = :syslog
1637
+ @name = name
1638
+ @server = server
1639
+ @enabled = enabled
1640
+ # Sets default vuln filter - All Events
1641
+ setVulnFilter(VulnFilter.new("50790400",1))
1642
+
1643
+ end
1644
+
1645
+ # Sets the Vulnerability Filter for this alert.
1646
+ def setVulnFilter(vulnFilter)
1647
+ @vulnFilter = vulnFilter
1648
+ end
1649
+
1650
+ include Sanitize
1651
+ def to_xml
1652
+ xml = "<syslogAlert"
1653
+ xml << %Q{ name="#{replace_entities(name)}"}
1654
+ xml << %Q{ enabled="#{replace_entities(enabled)}"}
1655
+ xml << %Q{ server="#{replace_entities(server)}">}
1656
+ xml << vulnFilter.to_xml
1657
+ xml << "</syslogAlert>"
1658
+ xml
1659
+ end
1660
+
1661
+ end
1662
+
1663
+ # TODO: review
1664
+ # <scanFilter scanStop='0' scanFailed='0' scanStart='1'/>
1665
+ # === Description
1666
+ #
1667
+ class ScanFilter
1668
+
1669
+ attr_reader :scanStop
1670
+ attr_reader :scanFailed
1671
+ attr_reader :scanStart
1672
+
1673
+ def initialize(scanstop, scanFailed, scanStart)
1674
+
1675
+ @scanStop = scanStop
1676
+ @scanFailed = scanFailed
1677
+ @scanStart = scanStart
1678
+
1679
+ end
1680
+
1681
+ end
1682
+
1683
+ # TODO: review
1684
+ # === Description
1685
+ #
1686
+ class VulnFilter
1687
+
1688
+ attr_reader :typeMask
1689
+ attr_reader :maxAlerts
1690
+ attr_reader :severityThreshold
1691
+
1692
+ def initialize(typeMask, severityThreshold, maxAlerts = -1)
1693
+ @typeMask = typeMask
1694
+ @maxAlerts = maxAlerts
1695
+ @severityThreshold = severityThreshold
1696
+ end
1697
+
1698
+ include Sanitize
1699
+ def to_xml
1700
+ xml = "<vulnFilter "
1701
+ xml << %Q{ typeMask="#{replace_entities(typeMask)}"}
1702
+ xml << %Q{ maxAlerts="#{replace_entities(maxAlerts)}"}
1703
+ xml << %Q{ severityThreshold="#{replace_entities(severityThreshold)}"}
1704
+ xml << "/>"
1705
+
1706
+ xml
1707
+ end
1708
+
1709
+ end
1710
+
1711
+ # TODO add engineID
1712
+ # === Description
1713
+ # Object that represents the scanning configuration for a Site.
1714
+ #
1715
+ class ScanConfig
1716
+ # A unique ID for this scan configuration
1717
+ attr_reader :configID
1718
+ # The name of the scan template
1719
+ attr_reader :name
1720
+ # The ID of the scan template used full-audit, exhaustive-audit, web-audit, dos-audit, internet-audit, network-audit
1721
+ attr_reader :templateID
1722
+ # The configuration version (default is 2)
1723
+ attr_reader :configVersion
1724
+ # Array of (Schedule)*
1725
+ attr_reader :schedules
1726
+ # Array of (ScanTrigger)*
1727
+ attr_reader :scanTriggers
1728
+
1729
+ def initialize(configID, name, templateID, configVersion = 2)
1730
+
1731
+ @configID = configID
1732
+ @name = name
1733
+ @templateID = templateID
1734
+ @configVersion = configVersion
1735
+ @schedules = Array.new()
1736
+ @scanTriggers = Array.new()
1737
+
1738
+ end
1739
+
1740
+ # Adds a new Schedule for this ScanConfig
1741
+ def addSchedule(schedule)
1742
+ @schedules.push(schedule)
1743
+ end
1744
+
1745
+ # Adds a new ScanTrigger to the scanTriggers array
1746
+ def addScanTrigger(scanTrigger)
1747
+ @scanTriggers.push(scanTrigger)
1748
+ end
1749
+
1750
+ def _set_configID(configID)
1751
+ @configID = configID
1752
+ end
1753
+
1754
+ def _set_name(name)
1755
+ @name = name
1756
+ end
1757
+
1758
+ end
1759
+
1760
+ # === Description
1761
+ # Object that holds a scan schedule
1762
+ #
1763
+ class Schedule
1764
+ # Type of Schedule (daily|hourly|monthly|weekly)
1765
+ attr_reader :type
1766
+ # The schedule interval
1767
+ attr_reader :interval
1768
+ # The date and time to start the first scan
1769
+ attr_reader :start
1770
+ # Enable or disable this schedule
1771
+ attr_reader :enabled
1772
+ # The date and time to disable to schedule. If null then the schedule will run forever.
1773
+ attr_reader :notValidAfter
1774
+ # Scan on the same date each time
1775
+ attr_reader :byDate
1776
+
1777
+ def initialize(type, interval, start, enabled = 1)
1778
+
1779
+ @type = type
1780
+ @interval = interval
1781
+ @start = start
1782
+ @enabled = enabled
1783
+
1784
+ end
1785
+
1786
+
1787
+
1788
+ end
1789
+
1790
+ # === Description
1791
+ # Object that holds an event that triggers the start of a scan.
1792
+ #
1793
+ class ScanTrigger
1794
+ # Type of Trigger (AutoUpdate)
1795
+ attr_reader :type
1796
+ # Enable or disable this scan trigger
1797
+ attr_reader :enabled
1798
+ # Sets the trigger to start an incremental scan or a full scan
1799
+ attr_reader :incremental
1800
+
1801
+ def initialize(type, incremental, enabled = 1)
1802
+
1803
+ @type = type
1804
+ @incremental = incremental
1805
+ @enabled = enabled
1806
+
1807
+ end
1808
+
1809
+ end
1810
+
1811
+ # === Description
1812
+ # Object that represents a single device in an NSC.
1813
+ #
1814
+ class Device
1815
+
1816
+ # A unique device ID (assigned by the NSC)
1817
+ attr_reader :id
1818
+ # The site ID of this devices site
1819
+ attr_reader :site_id
1820
+ # IP Address or Hostname of this device
1821
+ attr_reader :address
1822
+ # User assigned risk multiplier
1823
+ attr_reader :riskfactor
1824
+ # NeXpose risk score
1825
+ attr_reader :riskscore
1826
+
1827
+ def initialize(id, site_id, address, riskfactor=1, riskscore=0)
1828
+ @id = id
1829
+ @site_id = site_id
1830
+ @address = address
1831
+ @riskfactor = riskfactor
1832
+ @riskscore = riskscore
1833
+
1834
+ end
1835
+
1836
+ end
1837
+
1838
+
1839
+ # === Description
1840
+ # Object that represents a summary of a scan.
1841
+ #
1842
+ class ScanSummary
1843
+ # The Scan ID of the Scan
1844
+ attr_reader :scan_id
1845
+ # The Engine ID used to perform the scan
1846
+ attr_reader :engine_id
1847
+ # TODO: add description
1848
+ attr_reader :name
1849
+ # The scan start time
1850
+ attr_reader :startTime
1851
+ # The scan finish time
1852
+ attr_reader :endTime
1853
+ # The scan status (running|finished|stopped|error| dispatched|paused|aborted|uknown)
1854
+ attr_reader :status
1855
+ # The number of pending tasks
1856
+ attr_reader :tasks_pending
1857
+ # The number of active tasks
1858
+ attr_reader :tasks_active
1859
+ # The number of completed tasks
1860
+ attr_reader :tasks_completed
1861
+ # The number of "live" nodes
1862
+ attr_reader :nodes_live
1863
+ # The number of "dead" nodes
1864
+ attr_reader :nodes_dead
1865
+ # The number of filtered nodes
1866
+ attr_reader :nodes_filtered
1867
+ # The number of unresolved nodes
1868
+ attr_reader :nodes_unresolved
1869
+ # The number of "other" nodes
1870
+ attr_reader :nodes_other
1871
+ # Confirmed vulnerabilities found (indexed by severity)
1872
+ # Associative array, indexed by severity
1873
+ attr_reader :vuln_exploit
1874
+ # Unconfirmed vulnerabilities found (indexed by severity)
1875
+ # Associative array, indexed by severity
1876
+ attr_reader :vuln_version
1877
+ # Not vulnerable checks run (confirmed)
1878
+ attr_reader :not_vuln_exploit
1879
+ # Not vulnerable checks run (unconfirmed)
1880
+ attr_reader :not_vuln_version
1881
+ # Vulnerability check errors
1882
+ attr_reader :vuln_error
1883
+ # Vulnerability checks disabled
1884
+ attr_reader :vuln_disabled
1885
+ # Vulnerability checks other
1886
+ attr_reader :vuln_other
1887
+
1888
+ # Constructor
1889
+ # ScanSummary(can_id, $engine_id, $name, tartTime, $endTime, tatus)
1890
+ def initialize(scan_id, engine_id, name, startTime, endTime, status)
1891
+
1892
+ @scan_id = scan_id
1893
+ @engine_id = engine_id
1894
+ @name = name
1895
+ @startTime = startTime
1896
+ @endTime = endTime
1897
+ @status = status
1898
+
1899
+ end
1900
+
1901
+ end
1902
+
1903
+ # TODO
1904
+ # === Description
1905
+ # Object that represents the overview statistics for a particular scan.
1906
+ #
1907
+ # === Examples
1908
+ #
1909
+ # # Create a new Nexpose Connection on the default port and Login
1910
+ # nsc = Connection.new("10.1.40.10","nxadmin","password")
1911
+ # nsc.login()
1912
+ #
1913
+ # # Get a Site (Site ID = 12) from the NSC
1914
+ # site = new Site(nsc,12)
1915
+ #
1916
+ # # Start a Scan of this site and pause for 1 minute
1917
+ # scan1 = site.scanSite()
1918
+ # sleep(60)
1919
+ #
1920
+ # # Get the Scan Statistics for this scan
1921
+ # scanStatistics = new ScanStatistics(nsc,scan1["scan_id"])
1922
+ #
1923
+ # # Print out number of confirmed vulnerabilities with a 10 severity
1924
+ # puts scanStatistics.scansummary.vuln_exploit[10]
1925
+ #
1926
+ # # Print out the number of pending tasks left in the scan
1927
+ # puts scanStatistics.scan_summary.tasks_pending
1928
+ #
1929
+ class ScanStatistics
1930
+ # true if an error condition exists; false otherwise
1931
+ attr_reader :error
1932
+ # Error message string
1933
+ attr_reader :error_msg
1934
+ # The last XML request sent by this object
1935
+ attr_reader :request_xml
1936
+ # The last XML response received by this object
1937
+ attr_reader :reseponse_xml
1938
+ # The Scan ID
1939
+ attr_reader :scan_id
1940
+ # The ScanSummary of the scan
1941
+ attr_reader :scan_summary
1942
+ # The NSC Connection associated with this object
1943
+ attr_reader :connection
1944
+
1945
+ # Vulnerability checks other
1946
+ attr_reader :vuln_other
1947
+ def initialize(connection, scan_id)
1948
+ @error = false
1949
+ @connection = connection
1950
+ @scan_id = scan_id
1951
+ end
1952
+ end
1953
+
1954
+ # ==== Description
1955
+ # Object that represents a listing of all of the scan engines available on to an NSC.
1956
+ #
1957
+ class EngineListing
1958
+ # true if an error condition exists; false otherwise
1959
+ attr_reader :error
1960
+ # Error message string
1961
+ attr_reader :error_msg
1962
+ # The last XML request sent by this object
1963
+ attr_reader :request_xml
1964
+ # The last XML response received by this object
1965
+ attr_reader :response_xml
1966
+ # The NSC Connection associated with this object
1967
+ attr_reader :connection
1968
+ # Array containing (EngineSummary*)
1969
+ attr_reader :engines
1970
+ # The number of scan engines
1971
+ attr_reader :engine_count
1972
+
1973
+ # Constructor
1974
+ # EngineListing (connection)
1975
+ def initialize(connection)
1976
+ @connection = connection
1977
+ end
1978
+ end
1979
+
1980
+ # ==== Description
1981
+ # Object that represents the summary of a scan engine.
1982
+ #
1983
+ # ==== Examples
1984
+ #
1985
+ # # Create a new Nexpose Connection on the default port and Login
1986
+ # nsc = Connection.new("10.1.40.10","nxadmin","password")
1987
+ # nsc.login()
1988
+ #
1989
+ # # Get the engine listing for the connection
1990
+ # enginelisting = EngineListing.new(nsc)
1991
+ #
1992
+ # # Print out the status of the first scan engine
1993
+ # puts enginelisting.engines[0].status
1994
+ #
1995
+ class EngineSummary
1996
+ # A unique ID that identifies this scan engine
1997
+ attr_reader :id
1998
+ # The name of this scan engine
1999
+ attr_reader :name
2000
+ # The hostname or IP address of the engine
2001
+ attr_reader :address
2002
+ # The port there the engine is listening
2003
+ attr_reader :port
2004
+ # The engine status (active|pending-auth| incompatible|not-responding|unknown)
2005
+ attr_reader :status
2006
+
2007
+ # Constructor
2008
+ # EngineSummary(id, name, address, port, status)
2009
+ def initialize(id, name, address, port, status)
2010
+ @id = id
2011
+ @name = name
2012
+ @address = address
2013
+ @port = port
2014
+ @status = status
2015
+ end
2016
+
2017
+ end
2018
+
2019
+
2020
+ # TODO
2021
+ class EngineActivity
2022
+ # true if an error condition exists; false otherwise
2023
+ attr_reader :error
2024
+ # Error message string
2025
+ attr_reader :error_msg
2026
+ # The last XML request sent by this object
2027
+ attr_reader :request_xml
2028
+ # The last XML response received by this object
2029
+ attr_reader :response_xml
2030
+ # The NSC Connection associated with this object
2031
+ attr_reader :connection
2032
+ # The Engine ID
2033
+ attr_reader :engine_id
2034
+ # Array containing (ScanSummary*)
2035
+ attr_reader :scan_summaries
2036
+
2037
+
2038
+ end
2039
+
2040
+ # === Description
2041
+ # Object that represents a listing of all of the vulnerabilities in the vulnerability database
2042
+ #
2043
+ class VulnerabilityListing
2044
+
2045
+ # true if an error condition exists; false otherwise
2046
+ attr_reader :error
2047
+ # Error message string
2048
+ attr_reader :error_msg
2049
+ # The last XML request sent by this object
2050
+ attr_reader :request_xml
2051
+ # The last XML response received by this object
2052
+ attr_reader :response_xml
2053
+ # The NSC Connection associated with this object
2054
+ attr_reader :connection
2055
+ # Array containing (VulnerabilitySummary*)
2056
+ attr_reader :vulnerability_summaries
2057
+ # The number of vulnerability definitions
2058
+ attr_reader :vulnerability_count
2059
+
2060
+ # Constructor
2061
+ # VulnerabilityListing(connection)
2062
+ def initialize(connection)
2063
+ @error = false
2064
+ @vulnerability_summaries = []
2065
+ @connection = connection
2066
+
2067
+ r = @connection.execute('<VulnerabilityListingRequest session-id="' + @connection.session_id + '"/>')
2068
+
2069
+ if (r.success)
2070
+ r.res.elements.each('VulnerabilityListingResponse/VulnerabilitySummary') do |v|
2071
+ @vulnerability_summaries.push(VulnerabilitySummary.new(v.attributes['id'],v.attributes["title"],v.attributes["severity"]))
2072
+ end
2073
+ else
2074
+ @error = true
2075
+ @error_msg = 'VulnerabilitySummaryRequest Parse Error'
2076
+ end
2077
+ @vulnerability_count = @vulnerability_summaries.length
2078
+ end
2079
+ end
2080
+
2081
+ # === Description
2082
+ # Object that represents the summary of an entry in the vulnerability database
2083
+ #
2084
+ class VulnerabilitySummary
2085
+
2086
+ # The unique ID string for this vulnerability
2087
+ attr_reader :id
2088
+ # The title of this vulnerability
2089
+ attr_reader :title
2090
+ # The severity of this vulnerability (1 – 10)
2091
+ attr_reader :severity
2092
+
2093
+ # Constructor
2094
+ # VulnerabilitySummary(id, title, severity)
2095
+ def initialize(id, title, severity)
2096
+ @id = id
2097
+ @title = title
2098
+ @severity = severity
2099
+
2100
+ end
2101
+
2102
+ end
2103
+
2104
+ # === Description
2105
+ #
2106
+ class Reference
2107
+
2108
+ attr_reader :source
2109
+ attr_reader :reference
2110
+
2111
+ def initialize(source, reference)
2112
+ @source = source
2113
+ @reference = reference
2114
+ end
2115
+ end
2116
+
2117
+ # === Description
2118
+ # Object that represents the details for an entry in the vulnerability database
2119
+ #
2120
+ class VulnerabilityDetail
2121
+ # true if an error condition exists; false otherwise
2122
+ attr_reader :error
2123
+ # Error message string
2124
+ attr_reader :error_msg
2125
+ # The last XML request sent by this object
2126
+ attr_reader :request_xml
2127
+ # The last XML response received by this object
2128
+ attr_reader :response_xml
2129
+ # The NSC Connection associated with this object
2130
+ attr_reader :connection
2131
+ # The unique ID string for this vulnerability
2132
+ attr_reader :id
2133
+ # The title of this vulnerability
2134
+ attr_reader :title
2135
+ # The severity of this vulnerability (1 – 10)
2136
+ attr_reader :severity
2137
+ # The pciSeverity of this vulnerability
2138
+ attr_reader :pciSeverity
2139
+ # The CVSS score of this vulnerability
2140
+ attr_reader :cvssScore
2141
+ # The CVSS vector of this vulnerability
2142
+ attr_reader :cvssVector
2143
+ # The date this vulnerability was published
2144
+ attr_reader :published
2145
+ # The date this vulnerability was added to NeXpose
2146
+ attr_reader :added
2147
+ # The last date this vulnerability was modified
2148
+ attr_reader :modified
2149
+ # The HTML Description of this vulnerability
2150
+ attr_reader :description
2151
+ # External References for this vulnerability
2152
+ # Array containing (Reference)
2153
+ attr_reader :references
2154
+ # The HTML Solution for this vulnerability
2155
+ attr_reader :solution
2156
+
2157
+ # Constructor
2158
+ # VulnerabilityListing(connection,id)
2159
+ def initialize(connection, id)
2160
+
2161
+ @error = false
2162
+ @connection = connection
2163
+ @id = id
2164
+ @references = []
2165
+
2166
+ r = @connection.execute('<VulnerabilityDetailsRequest session-id="' + @connection.session_id + '" vuln-id="' + @id + '"/>')
2167
+
2168
+ if (r.success)
2169
+ r.res.elements.each('VulnerabilityDetailsResponse/Vulnerability') do |v|
2170
+ @id = v.attributes['id']
2171
+ @title = v.attributes["title"]
2172
+ @severity = v.attributes["severity"]
2173
+ @pciSeverity = v.attributes['pciSeverity']
2174
+ @cvssScore = v.attributes['cvssScore']
2175
+ @cvssVector = v.attributes['cvssVector']
2176
+ @published = v.attributes['published']
2177
+ @added = v.attributes['added']
2178
+ @modified = v.attributes['modified']
2179
+
2180
+ v.elements.each('description') do |d|
2181
+ @description = d.to_s.gsub(/\<\/?description\>/i, '')
2182
+ end
2183
+
2184
+ v.elements.each('solution') do |s|
2185
+ @solution = s.to_s.gsub(/\<\/?solution\>/i, '')
2186
+ end
2187
+
2188
+ v.elements.each('references/reference') do |r|
2189
+ @references.push(Reference.new(r.attributes['source'],r.text))
2190
+ end
2191
+ end
2192
+ else
2193
+ @error = true
2194
+ @error_msg = 'VulnerabilitySummaryRequest Parse Error'
2195
+ end
2196
+
2197
+ end
2198
+ end
2199
+
2200
+ # === Description
2201
+ # Object that represents the summary of a Report Configuration.
2202
+ #
2203
+ class ReportConfigSummary
2204
+ # The Report Configuration ID
2205
+ attr_reader :id
2206
+ # A unique name for the Report
2207
+ attr_reader :name
2208
+ # The report format
2209
+ attr_reader :format
2210
+ # The date of the last report generation
2211
+ attr_reader :last_generated_on
2212
+ # Relative URI of the last generated report
2213
+ attr_reader :last_generated_uri
2214
+
2215
+ # Constructor
2216
+ # ReportConfigSummary(id, name, format, last_generated_on, last_generated_uri)
2217
+ def initialize(id, name, format, last_generated_on, last_generated_uri)
2218
+
2219
+ @id = id
2220
+ @name = name
2221
+ @format = format
2222
+ @last_generated_on = last_generated_on
2223
+ @last_generated_uri = last_generated_uri
2224
+
2225
+ end
2226
+
2227
+ end
2228
+
2229
+ # === Description
2230
+ # Object that represents the schedule on which to automatically generate new reports.
2231
+ class ReportHistory
2232
+
2233
+ # true if an error condition exists; false otherwise
2234
+ attr_reader :error
2235
+ # Error message string
2236
+ attr_reader :error_msg
2237
+ # The last XML request sent by this object
2238
+ attr_reader :request_xml
2239
+ # The last XML response received by this object
2240
+ attr_reader :response_xml
2241
+ # The NSC Connection associated with this object
2242
+ attr_reader :connection
2243
+ # The report definition (report config) ID
2244
+ # Report definition ID
2245
+ attr_reader :config_id
2246
+ # Array (ReportSummary*)
2247
+ attr_reader :report_summaries
2248
+
2249
+
2250
+ def initialize(connection, config_id)
2251
+
2252
+ @error = false
2253
+ @connection = connection
2254
+ @config_id = config_id
2255
+ @report_summaries = []
2256
+
2257
+ reportHistory_request = APIRequest.new('<ReportHistoryRequest session-id="' + "#{connection.session_id}" + '" reportcfg-id="' + "#{@config_id}" + '"/>',@connection.geturl())
2258
+ reportHistory_request.execute()
2259
+ @response_xml = reportHistory_request.response_xml
2260
+ @request_xml = reportHistory_request.request_xml
2261
+
2262
+ end
2263
+
2264
+ def xml_parse(response)
2265
+ response = REXML::Document.new(response.to_s)
2266
+ status = response.root.attributes['success']
2267
+ if (status == '1')
2268
+ response.elements.each('ReportHistoryResponse/ReportSummary') do |r|
2269
+ @report_summaries.push(ReportSummary.new(r.attributes["id"], r.attributes["cfg-id"], r.attributes["status"], r.attributes["generated-on"],r.attributes['report-uri']))
2270
+ end
2271
+ else
2272
+ @error = true
2273
+ @error_msg = 'Error ReportHistoryReponse'
2274
+ end
2275
+ end
2276
+
2277
+ end
2278
+
2279
+ # === Description
2280
+ # Object that represents the summary of a single report.
2281
+ class ReportSummary
2282
+
2283
+ # The Report ID
2284
+ attr_reader :id
2285
+ # The Report Configuration ID
2286
+ attr_reader :cfg_id
2287
+ # The status of this report
2288
+ # available | generating | failed
2289
+ attr_reader :status
2290
+ # The date on which this report was generated
2291
+ attr_reader :generated_on
2292
+ # The relative URI of the report
2293
+ attr_reader :report_uri
2294
+
2295
+ def initialize(id, cfg_id, status, generated_on, report_uri)
2296
+
2297
+ @id = id
2298
+ @cfg_id = cfg_id
2299
+ @status = status
2300
+ @generated_on = generated_on
2301
+ @report_uri = report_uri
2302
+
2303
+ end
2304
+
2305
+ end
2306
+
2307
+ # === Description
2308
+ #
2309
+ class ReportAdHoc
2310
+ include XMLUtils
2311
+
2312
+ attr_reader :error
2313
+ attr_reader :error_msg
2314
+ attr_reader :connection
2315
+ # Report Template ID strong e.g. full-audit
2316
+ attr_reader :template_id
2317
+ # pdf|html|xml|text|csv|raw-xml
2318
+ attr_reader :format
2319
+ # Array of (ReportFilter)*
2320
+ attr_reader :filters
2321
+ attr_reader :request_xml
2322
+ attr_reader :response_xml
2323
+ attr_reader :report_decoded
2324
+
2325
+
2326
+ def initialize(connection, template_id = 'full-audit', format = 'raw-xml')
2327
+
2328
+ @error = false
2329
+ @connection = connection
2330
+ @filters = Array.new()
2331
+ @template_id = template_id
2332
+ @format = format
2333
+
2334
+ end
2335
+
2336
+ def addFilter(filter_type, id)
2337
+
2338
+ # filter_type can be site|group|device|scan
2339
+ # id is the ID number. For scan, you can use 'last' for the most recently run scan
2340
+ filter = ReportFilter.new(filter_type, id)
2341
+ filters.push(filter)
2342
+
2343
+ end
2344
+
2345
+ def generate()
2346
+ request_xml = '<ReportAdhocGenerateRequest session-id="' + @connection.session_id + '">'
2347
+ request_xml += '<AdhocReportConfig template-id="' + @template_id + '" format="' + @format + '">'
2348
+ request_xml += '<Filters>'
2349
+ @filters.each do |f|
2350
+ request_xml += '<filter type="' + f.type + '" id="'+ f.id.to_s + '"/>'
2351
+ end
2352
+ request_xml += '</Filters>'
2353
+ request_xml += '</AdhocReportConfig>'
2354
+ request_xml += '</ReportAdhocGenerateRequest>'
2355
+
2356
+ ad_hoc_request = APIRequest.new(request_xml, @connection.url)
2357
+ ad_hoc_request.execute()
2358
+
2359
+ content_type_response = ad_hoc_request.raw_response.header['Content-Type']
2360
+ if content_type_response =~ /multipart\/mixed;\s*boundary=([^\s]+)/
2361
+ # NeXpose sends an incorrect boundary format which breaks parsing
2362
+ # Eg: boundary=XXX; charset=XXX
2363
+ # Fix by removing everything from the last semi-colon onward
2364
+ last_semi_colon_index = content_type_response.index(/;/, content_type_response.index(/boundary/))
2365
+ content_type_response = content_type_response[0, last_semi_colon_index]
2366
+
2367
+ data = "Content-Type: " + content_type_response + "\r\n\r\n" + ad_hoc_request.raw_response_data
2368
+ doc = Rex::MIME::Message.new data
2369
+ doc.parts.each do |part|
2370
+ if /.*base64.*/ =~ part.header.to_s
2371
+ return parse_xml(part.content.unpack("m*")[0])
2372
+ end
2373
+ end
2374
+ end
2375
+ end
2376
+
2377
+ end
2378
+
2379
+ # === Description
2380
+ # Object that represents the configuration of a report definition.
2381
+ #
2382
+ class ReportConfig
2383
+
2384
+ # true if an error condition exists; false otherwise
2385
+ attr_reader :error
2386
+ # Error message string
2387
+ attr_reader :error_msg
2388
+ # The last XML request sent by this object
2389
+ attr_reader :request_xml
2390
+ # The last XML response received by this object
2391
+ attr_reader :response_xml
2392
+ # The NSC Connection associated with this object
2393
+ attr_reader :connection
2394
+ # The ID for this report definition
2395
+ attr_reader :config_id
2396
+ # A unique name for this report definition
2397
+ attr_reader :name
2398
+ # The template ID used for this report definition
2399
+ attr_reader :template_id
2400
+ # html, db, txt, xml, raw-xml, csv, pdf
2401
+ attr_reader :format
2402
+ # XXX new
2403
+ attr_reader :timezone
2404
+ # XXX new
2405
+ attr_reader :owner
2406
+ # Array of (ReportFilter)* - The Sites, Asset Groups, or Devices to run the report against
2407
+ attr_reader :filters
2408
+ # Automatically generate a new report at the conclusion of a scan
2409
+ # 1 or 0
2410
+ attr_reader :generate_after_scan
2411
+ # Schedule to generate reports
2412
+ # ReportSchedule Object
2413
+ attr_reader :schedule
2414
+ # Store the reports on the server
2415
+ # 1 or 0
2416
+ attr_reader :storeOnServer
2417
+ # Location to store the report on the server
2418
+ attr_reader :store_location
2419
+ # Form to send the report via email
2420
+ # "file", "zip", "url", or NULL (don’t send email)
2421
+ attr_reader :email_As
2422
+ # Send the Email to all Authorized Users
2423
+ # boolean - Send the Email to all Authorized Users
2424
+ attr_reader :email_to_all
2425
+ # Array containing the email addresses of the recipients
2426
+ attr_reader :email_recipients
2427
+ # IP Address or Hostname of SMTP Relay Server
2428
+ attr_reader :smtp_relay_server
2429
+ # Sets the FROM field of the Email
2430
+ attr_reader :sender
2431
+ # TODO
2432
+ attr_reader :db_export
2433
+ # TODO
2434
+ attr_reader :csv_export
2435
+ # TODO
2436
+ attr_reader :xml_export
2437
+
2438
+
2439
+ def initialize(connection, config_id = -1)
2440
+
2441
+ @error = false
2442
+ @connection = connection
2443
+ @config_id = config_id
2444
+ @xml_tag_stack = Array.new()
2445
+ @filters = Array.new()
2446
+ @email_recipients = Array.new()
2447
+ @name = "New Report " + rand(999999999).to_s
2448
+
2449
+ r = @connection.execute('<ReportConfigRequest session-id="' + @connection.session_id.to_s + '" reportcfg-id="' + @config_id.to_s + '"/>')
2450
+ if (r.success)
2451
+ r.res.elements.each('ReportConfigResponse/ReportConfig') do |r|
2452
+ @name = r.attributes['name']
2453
+ @format = r.attributes['format']
2454
+ @timezone = r.attributes['timezone']
2455
+ @id = r.attributes['id']
2456
+ @template_id = r.attributes['template-id']
2457
+ @owner = r.attributes['owner']
2458
+ end
2459
+ else
2460
+ @error = true
2461
+ @error_msg = 'Error ReportHistoryReponse'
2462
+ end
2463
+ end
2464
+
2465
+ # === Description
2466
+ # Generate a new report on this report definition. Returns the new report ID.
2467
+ def generateReport(debug = false)
2468
+ return generateReport(@connection, @config_id, debug)
2469
+ end
2470
+
2471
+ # === Description
2472
+ # Save the report definition to the NSC.
2473
+ # Returns the config-id.
2474
+ def saveReport()
2475
+ r = @connection.execute('<ReportSaveRequest session-id="' + @connection.session_id.to_s + '">' + getXML().to_s + ' </ReportSaveRequest>')
2476
+ if(r.success)
2477
+ @config_id = r.attributes['reportcfg-id']
2478
+ return true
2479
+ end
2480
+ return false
2481
+ end
2482
+
2483
+ # === Description
2484
+ # Adds a new filter to the report config
2485
+ def addFilter(filter_type, id)
2486
+ filter = ReportFilter.new(filter_type,id)
2487
+ @filters.push(filter)
2488
+ end
2489
+
2490
+ # === Description
2491
+ # Adds a new email recipient
2492
+ def addEmailRecipient(recipient)
2493
+ @email_recipients.push(recipient)
2494
+ end
2495
+
2496
+ # === Description
2497
+ # Sets the schedule for this report config
2498
+ def setSchedule(schedule)
2499
+ @schedule = schedule
2500
+ end
2501
+
2502
+ def getXML()
2503
+
2504
+ xml = '<ReportConfig id="' + @config_id.to_s + '" name="' + @name.to_s + '" template-id="' + @template_id.to_s + '" format="' + @format.to_s + '">'
2505
+
2506
+ xml += ' <Filters>'
2507
+
2508
+ @filters.each do |f|
2509
+ xml += ' <' + f.type.to_s + ' id="' + f.id.to_s + '"/>'
2510
+ end
2511
+
2512
+ xml += ' </Filters>'
2513
+
2514
+ xml += ' <Generate after-scan="' + @generate_after_scan.to_s + '">'
2515
+
2516
+ if (@schedule)
2517
+ xml += ' <Schedule type="' + @schedule.type.to_s + '" interval="' + @schedule.interval.to_s + '" start="' + @schedule.start.to_s + '"/>'
2518
+ end
2519
+
2520
+ xml += ' </Generate>'
2521
+
2522
+ xml += ' <Delivery>'
2523
+
2524
+ xml += ' <Storage storeOnServer="' + @storeOnServer.to_s + '">'
2525
+
2526
+ if (@store_location and @store_location.length > 0)
2527
+ xml += ' <location>' + @store_location.to_s + '</location>'
2528
+ end
2529
+
2530
+ xml += ' </Storage>'
2531
+
2532
+
2533
+ xml += ' </Delivery>'
2534
+
2535
+ xml += ' </ReportConfig>'
2536
+
2537
+ return xml
2538
+ end
2539
+
2540
+ def set_name(name)
2541
+ @name = name
2542
+ end
2543
+
2544
+ def set_template_id(template_id)
2545
+ @template_id = template_id
2546
+ end
2547
+
2548
+ def set_format(format)
2549
+ @format = format
2550
+ end
2551
+
2552
+ def set_email_As(email_As)
2553
+ @email_As = email_As
2554
+ end
2555
+
2556
+ def set_storeOnServer(storeOnServer)
2557
+ @storeOnServer = storeOnServer
2558
+ end
2559
+
2560
+ def set_smtp_relay_server(smtp_relay_server)
2561
+ @smtp_relay_server = smtp_relay_server
2562
+ end
2563
+
2564
+ def set_sender(sender)
2565
+ @sender = sender
2566
+ end
2567
+
2568
+ def set_generate_after_scan(generate_after_scan)
2569
+ @generate_after_scan = generate_after_scan
2570
+ end
2571
+ end
2572
+
2573
+ # === Description
2574
+ # Object that represents a report filter which determines which sites, asset
2575
+ # groups, and/or devices that a report is run against. gtypes are
2576
+ # "SiteFilter", "AssetGroupFilter", "DeviceFilter", or "ScanFilter". gid is
2577
+ # the site-id, assetgroup-id, or devce-id. ScanFilter, if used, specifies
2578
+ # a specifies a specific scan to use as the data source for the report. The gid
2579
+ # can be a specific scan-id or "first" for the first run scan, or “last” for
2580
+ # the last run scan.
2581
+ #
2582
+ class ReportFilter
2583
+
2584
+ attr_reader :type
2585
+ attr_reader :id
2586
+
2587
+ def initialize(type, id)
2588
+
2589
+ @type = type
2590
+ @id = id
2591
+
2592
+ end
2593
+
2594
+ end
2595
+
2596
+ # === Description
2597
+ # Object that represents the schedule on which to automatically generate new reports.
2598
+ #
2599
+ class ReportSchedule
2600
+
2601
+ # The type of schedule
2602
+ # (daily, hourly, monthly, weekly)
2603
+ attr_reader :type
2604
+ # The frequency with which to run the scan
2605
+ attr_reader :interval
2606
+ # The earliest date to generate the report
2607
+ attr_reader :start
2608
+
2609
+ def initialize(type, interval, start)
2610
+
2611
+ @type = type
2612
+ @interval = interval
2613
+ @start = start
2614
+
2615
+ end
2616
+
2617
+
2618
+ end
2619
+
2620
+ class ReportTemplateListing
2621
+
2622
+ attr_reader :error_msg
2623
+ attr_reader :error
2624
+ attr_reader :request_xml
2625
+ attr_reader :response_xml
2626
+ attr_reader :connection
2627
+ attr_reader :xml_tag_stack
2628
+ attr_reader :report_template_summaries#; //Array (ReportTemplateSummary*)
2629
+
2630
+
2631
+ def ReportTemplateListing(connection)
2632
+
2633
+ @error = nil
2634
+ @connection = connection
2635
+ @report_template_summaries = Array.new()
2636
+
2637
+ r = @connection.execute('<ReportTemplateListingRequest session-id="' + connection.session_id.to_s + '"/>')
2638
+ if (r.success)
2639
+ r.res.elements.each('ReportTemplateListingResponse/ReportTemplateSummary') do |r|
2640
+ @report_template_summaries.push(ReportTemplateSumary.new(r.attributes['id'],r.attributes['name']))
2641
+ end
2642
+ else
2643
+ @error = true
2644
+ @error_msg = 'ReportTemplateListingRequest Parse Error'
2645
+ end
2646
+
2647
+ end
2648
+
2649
+ end
2650
+
2651
+
2652
+ class ReportTemplateSummary
2653
+
2654
+ attr_reader :id
2655
+ attr_reader :name
2656
+ attr_reader :description
2657
+
2658
+ def ReportTemplateSummary(id, name, description)
2659
+
2660
+ @id = id
2661
+ @name = name
2662
+ @description = description
2663
+
2664
+ end
2665
+
2666
+ end
2667
+
2668
+
2669
+ class ReportSection
2670
+
2671
+ attr_reader :name
2672
+ attr_reader :properties
2673
+
2674
+ def ReportSection(name)
2675
+
2676
+ @properties = Array.new()
2677
+ @name = name
2678
+ end
2679
+
2680
+
2681
+ def addProperty(name, value)
2682
+
2683
+ @properties[name.to_s] = value
2684
+ end
2685
+
2686
+ end
2687
+
2688
+
2689
+ # TODO add
2690
+ def self.site_device_scan(connection, site_id, device_array, host_array, debug = false)
2691
+
2692
+ request_xml = '<SiteDevicesScanRequest session-id="' + connection.session_id.to_s + '" site-id="' + site_id.to_s + '">'
2693
+ request_xml += '<Devices>'
2694
+ device_array.each do |d|
2695
+ request_xml += '<device id="' + d.to_s + '"/>'
2696
+ end
2697
+ request_xml += '</Devices>'
2698
+ request_xml += '<Hosts>'
2699
+ # The host array can only by single IP addresses for now. TODO: Expand to full API Spec.
2700
+ host_array.each do |h|
2701
+ request_xml += '<range from="' + h.to_s + '"/>'
2702
+ end
2703
+ request_xml += '</Hosts>'
2704
+ request_xml += '</SiteDevicesScanRequest>'
2705
+
2706
+ r = connection.execute(request_xml)
2707
+ r.success ? { :engine_id => r.attributes['engine_id'], :scan_id => r.attributes['scan-id'] } : nil
2708
+ end
2709
+
2710
+ # === Description
2711
+ # TODO
2712
+ def self.getAttribute(attribute, xml)
2713
+ value = ''
2714
+ #@value = substr(substr(strstr(strstr(@xml,@attribute),'"'),1),0,strpos(substr(strstr(strstr(@xml,@attribute),'"'),1),'"'))
2715
+ return value
2716
+ end
2717
+
2718
+ # === Description
2719
+ # Returns an ISO 8601 formatted date/time stamp. All dates in NeXpose must use this format.
2720
+ def self.get_iso_8601_date(int_date)
2721
+ #@date_mod = date('Ymd\THis000', @int_date)
2722
+ date_mod = ''
2723
+ return date_mod
2724
+ end
2725
+
2726
+ # ==== Description
2727
+ # Echos the last XML API request and response for the specified object. (Useful for debugging)
2728
+ def self.printXML(object)
2729
+ puts "request" + object.request_xml.to_s
2730
+ puts "response is " + object.response_xml.to_s
2731
+ end
2732
+
2733
+ end
2734
+