nexpose 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (7) hide show
  1. data/README +0 -0
  2. data/README.markdown +7 -7
  3. data/Rakefile +22 -22
  4. data/lib/nexpose.rb +2895 -2516
  5. data/nexpose.gemspec +24 -0
  6. data/nexpose.rb +2895 -0
  7. metadata +46 -55
data/nexpose.gemspec ADDED
@@ -0,0 +1,24 @@
1
+ # encoding: utf-8
2
+
3
+ APP_NAME = "nexpose"
4
+ VERSION = "0.0.5"
5
+ REVISION = "12878"
6
+
7
+ Gem::Specification.new do |s|
8
+ s.name = APP_NAME
9
+ s.version = VERSION
10
+ s.homepage = "http://www.metasploit.com/"
11
+ s.summary = "Ruby API for Rapid7 NeXpose"
12
+ s.description = "This gem provides a Ruby API to the NeXpose vulnerability management product by Rapid7. This version is based on Metasploit SVN revision #{REVISION}"
13
+ s.license = "BSD"
14
+ s.authors = ["HD Moore", "Chris Lee"]
15
+ s.email = ["hdm@metasploit.com", "christopher_lee@rapid7.com"]
16
+ s.files = Dir['[A-Z]*'] + Dir['lib/**/*']
17
+ s.require_paths = ["lib"]
18
+ s.extra_rdoc_files = ["README.markdown"]
19
+ s.required_ruby_version = ">= 1.8.7"
20
+ s.platform = "ruby"
21
+
22
+ s.add_dependency("librex", ">= 0.0.32")
23
+ s.add_dependency("rex", ">= 1.0.2")
24
+ end
data/nexpose.rb ADDED
@@ -0,0 +1,2895 @@
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!(/&/, "&")
63
+ ret.gsub!(/'/, "'")
64
+ ret.gsub!(/"/, """)
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='1.1')
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='1.1')
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
+ res
489
+ else
490
+ false
491
+ end
492
+ end
493
+
494
+ ###################
495
+ # SILO MANAGEMENT #
496
+ ###################
497
+
498
+ #########################
499
+ # MULTI-TENANT USER OPS #
500
+ #########################
501
+
502
+ #-------------------------------------------------------------------------
503
+ # Creates a multi-tenant user
504
+ #
505
+ # user_config - A map of the user data.
506
+ #
507
+ # REQUIRED PARAMS
508
+ # user-id, authsrcid, user-name, full-name, enabled, superuser
509
+ #
510
+ # OPTIONAL PARAMS
511
+ # email, password
512
+ #
513
+ # silo_configs - An array of maps of silo specific data
514
+ #
515
+ # REQUIRED PARAMS
516
+ # silo-id, role-name, all-groups, all-sites, default-silo
517
+ #
518
+ # allowed_groups/allowed_sites - An array of ids
519
+ #-------------------------------------------------------------------------
520
+ def create_multi_tenant_user(user_config, silo_configs)
521
+ xml = make_xml('MultiTenantUserCreateRequest')
522
+ mtu_config_xml = make_xml('MultiTenantUserConfig', user_config, '', false)
523
+
524
+ # Add the silo access
525
+ silo_xml = make_xml('SiloAccesses', {}, '', false)
526
+ silo_config_xml = make_xml('SiloAccess', {}, '', false)
527
+ silo_configs.keys.each do |k|
528
+ if k.eql? 'allowed_sites'
529
+ allowed_sites_xml = make_xml('AllowedSites', {}, '', false)
530
+ silo_configs['allowed_sites'].each do |allowed_site|
531
+ allowed_sites_xml.add_element make_xml('AllowedSite', {'id' => allowed_site}, '', false)
532
+ end
533
+ silo_config_xml.add_element allowed_sites_xml
534
+ elsif k.eql? 'allowed_groups'
535
+ allowed_groups_xml = make_xml('AllowedGroups', {}, '', false)
536
+ silo_configs['allowed_groups'].each do |allowed_group|
537
+ allowed_groups_xml.add_element make_xml('AllowedGroup', {'id' => allowed_group}, '', false)
538
+ end
539
+ silo_config_xml.add_element allowed_groups_xml
540
+ else
541
+ silo_config_xml.attributes[k] = silo_configs[k]
542
+ end
543
+ end
544
+ silo_xml.add_element silo_config_xml
545
+ mtu_config_xml.add_element silo_xml
546
+ xml.add_element mtu_config_xml
547
+ r = execute xml, '1.2'
548
+ r.success
549
+ end
550
+
551
+
552
+ #-------------------------------------------------------------------------
553
+ # Lists all the multi-tenant users and their attributes.
554
+ #-------------------------------------------------------------------------
555
+ def list_mtu
556
+ xml = make_xml('MultiTenantUserListingRequest')
557
+ r = execute xml, '1.2'
558
+
559
+ if r.success
560
+ res = []
561
+ r.res.elements.each("//MultiTenantUserSummary") do |mtu|
562
+ res << {
563
+ :id => mtu.attributes['id'],
564
+ :full_name => mtu.attributes['full-name'],
565
+ :user_name => mtu.attributes['user-name'],
566
+ :email => mtu.attributes['email'],
567
+ :super_user => mtu.attributes['superuser'],
568
+ :enabled => mtu.attributes['enabled'],
569
+ :auth_module => mtu.attributes['auth-module'],
570
+ :silo_count => mtu.attributes['silo-count'],
571
+ :locked => mtu.attributes['locked']
572
+ }
573
+ end
574
+ res
575
+ else
576
+ false
577
+ end
578
+ end
579
+
580
+ #-------------------------------------------------------------------------
581
+ # Delete a multi-tenant user
582
+ #-------------------------------------------------------------------------
583
+ def delete_mtu user_name, user_id
584
+ using_user_name = (user_name and not user_name.empty?)
585
+ xml = make_xml('MultiTenantUserDeleteRequest', (using_user_name ? {'user-name' => user_name} : {'user-id' => user_id}))
586
+ r = execute xml, '1.2'
587
+ r.success
588
+ end
589
+
590
+ ####################
591
+ # SILO PROFILE OPS #
592
+ ####################
593
+
594
+ #-------------------------------------------------------------------------
595
+ # Creates a silo profile
596
+ #
597
+ # silo_config - A map of the silo data.
598
+ #
599
+ # REQUIRED PARAMS
600
+ # id, name, all‐licensed-modules, all‐global-engines, all-global-report-templates, all‐global-scan‐templates
601
+ #
602
+ # OPTIONAL PARAMS
603
+ # description
604
+ #
605
+ # permissions - A map of an array of maps of silo specific data
606
+ #
607
+ # REQUIRED PARAMS
608
+ # silo-id, role-name, all-groups, all-sites, default-silo
609
+ #
610
+ # allowed_groups/allowed_sites - An array of ids
611
+ #-------------------------------------------------------------------------
612
+ def create_silo_profile silo_profile_config, permissions
613
+ xml = make_xml 'SiloProfileCreateRequest'
614
+ spc_xml = make_xml('SiloProfileConfig', silo_profile_config, '', false)
615
+
616
+ # Add the permissions
617
+ if permissions['global_report_templates']
618
+ grt_xml = make_xml('GlobalReportTemplates', {}, '', false)
619
+ permissions['global_report_templates'].each do |name|
620
+ grt_xml.add_element make_xml('GlobalReportTemplate', {'name' => name}, '', false)
621
+ end
622
+ spc_xml.add_element grt_xml
623
+ end
624
+
625
+ if permissions['global_scan_engines']
626
+ gse_xml = make_xml('GlobalScanEngines', {}, '', false)
627
+ permissions['global_scan_engines'].each do |name|
628
+ gse_xml.add_element make_xml('GlobalScanEngine', {'name' => name}, '', false)
629
+ end
630
+ spc_xml.add_element gse_xml
631
+ end
632
+
633
+ if permissions['global_scan_templates']
634
+ gst_xml = make_xml('GlobalScanTemplates', {}, '', false)
635
+ permissions['global_scan_templates'].each do |name|
636
+ gst_xml.add_element make_xml('GlobalScanTemplate', {'name' => name}, '', false)
637
+ end
638
+ spc_xml.add_element gst_xml
639
+ end
640
+
641
+ if permissions['licensed_modules']
642
+ lm_xml = make_xml('LicensedModules', {}, '', false)
643
+ permissions['licensed_modules'].each do |name|
644
+ lm_xml.add_element make_xml('LicensedModule', {'name' => name}, '', false)
645
+ end
646
+ spc_xml.add_element lm_xml
647
+ end
648
+
649
+ if permissions['restricted_report_formats']
650
+ rrf_xml = make_xml('RestrictedReportFormats', {}, '', false)
651
+ permissions['restricted_report_formats'].each do |name|
652
+ rrf_xml.add_element make_xml('RestrictedReportFormat', {'name' => name}, '', false)
653
+ end
654
+ spc_xml.add_element rrf_xml
655
+ end
656
+
657
+ if permissions['restricted_report_sections']
658
+ rrs_xml = make_xml('RestrictedReportSections', {}, '', false)
659
+ permissions['restricted_report_sections'].each do |name|
660
+ rrs_xml.add_element make_xml('RestrictedReportSection', {'name' => name}, '', false)
661
+ end
662
+ spc_xml.add_element rrs_xml
663
+ end
664
+
665
+ xml.add_element spc_xml
666
+ r = execute xml, '1.2'
667
+ r.success
668
+ end
669
+
670
+ #-------------------------------------------------------------------------
671
+ # Lists all the silo profiles and their attributes.
672
+ #-------------------------------------------------------------------------
673
+ def list_silo_profiles
674
+ xml = make_xml('SiloProfileListingRequest')
675
+ r = execute xml, '1.2'
676
+
677
+ if r.success
678
+ res = []
679
+ r.res.elements.each("//SiloProfileSummary") do |silo_profile|
680
+ res << {
681
+ :id => silo_profile.attributes['id'],
682
+ :name => silo_profile.attributes['name'],
683
+ :description => silo_profile.attributes['description'],
684
+ :global_report_template_count => silo_profile.attributes['global-report-template-count'],
685
+ :global_scan_engine_count => silo_profile.attributes['global-scan-engine-count'],
686
+ :global_scan_template_count => silo_profile.attributes['global-scan-template-count'],
687
+ :licensed_module_count => silo_profile.attributes['licensed-module-count'],
688
+ :restricted_report_section_count => silo_profile.attributes['restricted-report-section-count'],
689
+ :all_licensed_modules => silo_profile.attributes['all-licensed-modules'],
690
+ :all_global_engines => silo_profile.attributes['all-global-engines'],
691
+ :all_global_report_templates => silo_profile.attributes['all-global-report-templates'],
692
+ :all_global_scan_templates => silo_profile.attributes['all-global-scan-templates']
693
+ }
694
+ end
695
+ res
696
+ else
697
+ false
698
+ end
699
+ end
700
+
701
+ #-------------------------------------------------------------------------
702
+ # Delete a silo profile
703
+ #-------------------------------------------------------------------------
704
+ def delete_silo_profile name, id
705
+ using_name = (name and not name.empty?)
706
+ xml = make_xml('SiloProfileDeleteRequest', (using_name ? {'name' => name} : {'silo-profile-id' => id}))
707
+ r = execute xml, '1.2'
708
+ r.success
709
+ end
710
+
711
+ ####################
712
+ # SILO OPS #
713
+ ####################
714
+
715
+ #-------------------------------------------------------------------------
716
+ # Creates a silo
717
+ #
718
+ # silo_config - A map of the silo creation data.
719
+ #
720
+ # REQUIRED PARAMS
721
+ # id, name, silo-profile-id, max-assets, max-hosted-assets, max-users
722
+ #
723
+ # OPTIONAL PARAMS
724
+ # description
725
+ #-------------------------------------------------------------------------
726
+ def create_silo silo_config
727
+ xml = make_xml 'SiloCreateRequest'
728
+ silo_config_xml = make_xml 'SiloConfig', {}, '', false
729
+
730
+ # Add the attributes
731
+ silo_config.keys.each do |key|
732
+ if not 'merchant'.eql? key and not 'organization'.eql? key
733
+ silo_config_xml.attributes[key] = silo_config[key]
734
+ end
735
+ end
736
+
737
+ # Add Organization info
738
+ if silo_config['organization']
739
+ org_xml = make_xml 'Organization', {}, '', false
740
+ silo_config['organization'].keys.each do |key|
741
+ if not 'address'.eql? key
742
+ org_xml.attributes[key] = silo_config['organization'][key]
743
+ end
744
+ end
745
+
746
+ address_xml = make_xml 'Address', silo_config['organization']['address'], '', false
747
+ org_xml.add_element address_xml
748
+ silo_config_xml.add_element org_xml
749
+ end
750
+
751
+ # Add Merchant info
752
+ if silo_config['merchant']
753
+ merchant_xml = make_xml 'Merchant', {}, '', false
754
+
755
+ silo_config['merchant'].keys.each do |key|
756
+ if not 'dba'.eql? key and not 'other_industries'.eql? key and not 'qsa'.eql? key and not 'address'.eql? key
757
+ merchant_xml.attributes[key] = silo_config['merchant'][key]
758
+ end
759
+ end
760
+
761
+ # Add the merchant address
762
+ merchant_address_xml = make_xml 'Address', silo_config['merchant']['address'], '', false
763
+ merchant_xml.add_element merchant_address_xml
764
+
765
+ #Now add the complex data types
766
+ if silo_config['merchant']['dba']
767
+ dba_xml = make_xml 'DBAs', {}, '', false
768
+ silo_config['merchant']['dba'].each do |name|
769
+ dba_xml.add_element make_xml('DBA', {'name' => name}, '', false)
770
+ end
771
+ merchant_xml.add_element dba_xml
772
+ end
773
+
774
+ if silo_config['merchant']['other_industries']
775
+ ois_xml = make_xml 'OtherIndustries', {}, '', false
776
+ silo_config['merchant']['other_industries'].each do |name|
777
+ ois_xml.add_element make_xml('Industry', {'name' => name}, '', false)
778
+ end
779
+ merchant_xml.add_element ois_xml
780
+ end
781
+
782
+ if silo_config['merchant']['qsa']
783
+ qsa_xml = make_xml 'QSA', {}, '', false
784
+ silo_config['merchant']['qsa'].keys.each do |key|
785
+ if not 'address'.eql? key
786
+ qsa_xml.attributes[key] = silo_config['merchant']['qsa'][key]
787
+ end
788
+ end
789
+
790
+ # Add the address for this QSA
791
+ address_xml = make_xml 'Address', silo_config['merchant']['qsa']['address'], '', false
792
+
793
+ qsa_xml.add_element address_xml
794
+ merchant_xml.add_element qsa_xml
795
+ end
796
+ silo_config_xml.add_element merchant_xml
797
+ end
798
+
799
+ xml.add_element silo_config_xml
800
+ r = execute xml, '1.2'
801
+ r.success
802
+ end
803
+
804
+ #-------------------------------------------------------------------------
805
+ # Lists all the silos and their attributes.
806
+ #-------------------------------------------------------------------------
807
+ def list_silos
808
+ xml = make_xml('SiloListingRequest')
809
+ r = execute xml, '1.2'
810
+
811
+ if r.success
812
+ res = []
813
+ r.res.elements.each("//SiloSummary") do |silo_profile|
814
+ res << {
815
+ :id => silo_profile.attributes['id'],
816
+ :name => silo_profile.attributes['name'],
817
+ :description => silo_profile.attributes['description']
818
+ }
819
+ end
820
+ res
821
+ else
822
+ false
823
+ end
824
+ end
825
+
826
+ #-------------------------------------------------------------------------
827
+ # Delete a silo
828
+ #-------------------------------------------------------------------------
829
+ def delete_silo name, id
830
+ using_name = (name and not name.empty?)
831
+ xml = make_xml('SiloDeleteRequest', (using_name ? {'silo-name' => name} : {'silo-id' => id}))
832
+ r = execute xml, '1.2'
833
+ r.success
834
+ end
835
+
836
+ def site_device_listing(site_id)
837
+ r = execute(make_xml('SiteDeviceListingRequest', { 'site-id' => site_id.to_s }))
838
+
839
+ if(r.success)
840
+ res = []
841
+ r.res.elements.each("//device") do |device|
842
+ res << {
843
+ :device_id => device.attributes['id'].to_i,
844
+ :address => device.attributes['address'].to_s,
845
+ :risk_factor => device.attributes['risk_factor'].to_f,
846
+ :risk_score => device.attributes['risk_score'].to_f,
847
+ }
848
+ end
849
+ return res
850
+ else
851
+ return false
852
+ end
853
+ end
854
+
855
+ def report_template_listing
856
+ r = execute(make_xml('ReportTemplateListingRequest', { }))
857
+
858
+ if(r.success)
859
+ res = []
860
+ r.res.elements.each("//ReportTemplateSummary") do |template|
861
+ desc = ''
862
+ template.elements.each("//description") do |ent|
863
+ desc = ent.text
864
+ end
865
+
866
+ res << {
867
+ :template_id => template.attributes['id'].to_s,
868
+ :name => template.attributes['name'].to_s,
869
+ :description => desc.to_s
870
+ }
871
+ end
872
+ return res
873
+ else
874
+ return false
875
+ end
876
+ end
877
+
878
+
879
+ def console_command(cmd_string)
880
+ xml = make_xml('ConsoleCommandRequest', { })
881
+ cmd = REXML::Element.new('Command')
882
+ cmd.text = cmd_string
883
+ xml << cmd
884
+
885
+ r = execute(xml)
886
+
887
+ if(r.success)
888
+ res = ""
889
+ r.res.elements.each("//Output") do |out|
890
+ res << out.text.to_s
891
+ end
892
+
893
+ return res
894
+ else
895
+ return false
896
+ end
897
+ end
898
+
899
+ def system_information
900
+ r = execute(make_xml('SystemInformationRequest', { }))
901
+
902
+ if(r.success)
903
+ res = {}
904
+ r.res.elements.each("//Statistic") do |stat|
905
+ res[ stat.attributes['name'].to_s ] = stat.text.to_s
906
+ end
907
+
908
+ return res
909
+ else
910
+ return false
911
+ end
912
+ end
913
+
914
+ end
915
+
916
+ # === Description
917
+ # Object that represents a connection to a NeXpose Security Console.
918
+ #
919
+ # === Examples
920
+ # # Create a new Nexpose Connection on the default port
921
+ # nsc = Connection.new("10.1.40.10","nxadmin","password")
922
+ #
923
+ # # Login to NSC and Establish a Session ID
924
+ # nsc.login()
925
+ #
926
+ # # Check Session ID
927
+ # if (nsc.session_id)
928
+ # puts "Login Successful"
929
+ # else
930
+ # puts "Login Failure"
931
+ # end
932
+ #
933
+ # # //Logout
934
+ # logout_success = nsc.logout()
935
+ # if (! logout_success)
936
+ # puts "Logout Failure" + "<p>" + nsc.error_msg.to_s
937
+ # end
938
+ #
939
+ class Connection
940
+ include XMLUtils
941
+ include NexposeAPI
942
+
943
+ # true if an error condition exists; false otherwise
944
+ attr_reader :error
945
+ # Error message string
946
+ attr_reader :error_msg
947
+ # The last XML request sent by this object
948
+ attr_reader :request_xml
949
+ # The last XML response received by this object
950
+ attr_reader :response_xml
951
+ # Session ID of this connection
952
+ attr_reader :session_id
953
+ # The hostname or IP Address of the NSC
954
+ attr_reader :host
955
+ # The port of the NSC (default is 3780)
956
+ attr_reader :port
957
+ # The username used to login to the NSC
958
+ attr_reader :username
959
+ # The password used to login to the NSC
960
+ attr_reader :password
961
+ # The URL for communication
962
+ attr_reader :url
963
+
964
+ # Constructor for Connection
965
+ def initialize(ip, user, pass, port = 3780, silo_id = nil)
966
+ @host = ip
967
+ @port = port
968
+ @username = user
969
+ @password = pass
970
+ @silo_id = silo_id
971
+ @session_id = nil
972
+ @error = false
973
+ @url = "https://#{@host}:#{@port}/api/VERSION_STRING/xml"
974
+ end
975
+
976
+ # Establish a new connection and Session ID
977
+ def login
978
+ begin
979
+ login_hash = { 'sync-id' => 0, 'password' => @password, 'user-id' => @username }
980
+ unless @silo_id.nil?
981
+ login_hash['silo-id'] = @silo_id
982
+ end
983
+ r = execute(make_xml('LoginRequest', login_hash))
984
+ rescue APIError
985
+ raise AuthenticationFailed.new(r)
986
+ end
987
+ if(r.success)
988
+ @session_id = r.sid
989
+ return true
990
+ end
991
+ end
992
+
993
+ # Logout of the current connection
994
+ def logout
995
+ r = execute(make_xml('LogoutRequest', {'sync-id' => 0}))
996
+ if(r.success)
997
+ return true
998
+ end
999
+ raise APIError.new(r, 'Logout failed')
1000
+ end
1001
+
1002
+ # Execute an API request
1003
+ def execute(xml, version = '1.1')
1004
+ @api_version = version
1005
+ APIRequest.execute(@url.sub('VERSION_STRING', @api_version),xml.to_s, @api_version)
1006
+ end
1007
+
1008
+ # Download a specific URL
1009
+ def download(url)
1010
+ uri = URI.parse(url)
1011
+ http = Net::HTTP.new(@host, @port)
1012
+ http.use_ssl = true
1013
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE # XXX: security issue
1014
+ headers = {'Cookie' => "nexposeCCSessionID=#{@session_id}"}
1015
+ resp, data = http.get(uri.path, headers)
1016
+ data
1017
+ end
1018
+ end
1019
+
1020
+ # === Description
1021
+ # Object that represents a listing of all of the sites available on an NSC.
1022
+ #
1023
+ # === Example
1024
+ # # Create a new Nexpose Connection on the default port and Login
1025
+ # nsc = Connection.new("10.1.40.10","nxadmin","password")
1026
+ # nsc->login();
1027
+ #
1028
+ # # Get Site Listing
1029
+ # sitelisting = SiteListing.new(nsc)
1030
+ #
1031
+ # # Enumerate through all of the SiteSummaries
1032
+ # sitelisting.sites.each do |sitesummary|
1033
+ # # Do some operation on each site
1034
+ # end
1035
+ #
1036
+ class SiteListing
1037
+ # true if an error condition exists; false otherwise
1038
+ attr_reader :error
1039
+ # Error message string
1040
+ attr_reader :error_msg
1041
+ # The last XML request sent by this object
1042
+ attr_reader :request_xml
1043
+ # The last XML response received by this object
1044
+ attr_reader :response_xml
1045
+ # The NSC Connection associated with this object
1046
+ attr_reader :connection
1047
+ # Array containing SiteSummary objects for each site in the connection
1048
+ attr_reader :sites
1049
+ # The number of sites
1050
+ attr_reader :site_count
1051
+
1052
+ # Constructor
1053
+ # SiteListing (connection)
1054
+ def initialize(connection)
1055
+ @sites = []
1056
+
1057
+ @connection = connection
1058
+
1059
+ r = @connection.execute('<SiteListingRequest session-id="' + @connection.session_id.to_s + '"/>')
1060
+
1061
+ if (r.success)
1062
+ parse(r.res)
1063
+ else
1064
+ raise APIError.new(r, "Failed to get site listing")
1065
+ end
1066
+ end
1067
+
1068
+ def parse(r)
1069
+ r.elements.each('SiteListingResponse/SiteSummary') do |s|
1070
+ site_summary = SiteSummary.new(
1071
+ s.attributes['id'].to_s,
1072
+ s.attributes['name'].to_s,
1073
+ s.attributes['description'].to_s,
1074
+ s.attributes['riskfactor'].to_s
1075
+ )
1076
+ @sites.push(site_summary)
1077
+ end
1078
+ @site_count = @sites.length
1079
+ end
1080
+ end
1081
+
1082
+ # === Description
1083
+ # Object that represents the summary of a NeXpose Site.
1084
+ #
1085
+ class SiteSummary
1086
+ # The Site ID
1087
+ attr_reader :id
1088
+ # The Site Name
1089
+ attr_reader :site_name
1090
+ # A Description of the Site
1091
+ attr_reader :description
1092
+ # User assigned risk multiplier
1093
+ attr_reader :riskfactor
1094
+
1095
+ # Constructor
1096
+ # SiteSummary(id, site_name, description, riskfactor = 1)
1097
+ def initialize(id, site_name, description, riskfactor = 1)
1098
+ @id = id
1099
+ @site_name = site_name
1100
+ @description = description
1101
+ @riskfactor = riskfactor
1102
+ end
1103
+
1104
+ def _set_id(id)
1105
+ @id = id
1106
+ end
1107
+ end
1108
+
1109
+ # === Description
1110
+ # 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.
1111
+ #
1112
+ class IPRange
1113
+ # Start of Range *Required
1114
+ attr_reader :from;
1115
+ # End of Range *Optional (If Null then IPRange is a single IP Address)
1116
+ attr_reader :to;
1117
+
1118
+ def initialize(from, to = nil)
1119
+ @from = from
1120
+ @to = to
1121
+ end
1122
+
1123
+ include Sanitize
1124
+ def to_xml
1125
+ if (to and not to.empty?)
1126
+ return %Q{<range from="#{from}" to="#{to}"/>}
1127
+ else
1128
+ return %Q{<range from="#{from}"/>}
1129
+ end
1130
+ end
1131
+ end
1132
+
1133
+ # === Description
1134
+ # Object that represents a hostname to be added to a site.
1135
+ class HostName
1136
+
1137
+ # The hostname
1138
+ attr_reader :hostname
1139
+
1140
+ def initialize(hostname)
1141
+ @hostname = hostname
1142
+ end
1143
+
1144
+ include Sanitize
1145
+ def to_xml
1146
+ "<hostname>#{replace_entities(hostname)}</hostname>"
1147
+ end
1148
+ end
1149
+
1150
+ # === Description
1151
+ # Object that represents the configuration of a Site. This object is automatically created when a new Site object is instantiated.
1152
+ #
1153
+ class SiteConfig
1154
+ # true if an error condition exists; false otherwise
1155
+ attr_reader :error
1156
+ # Error message string
1157
+ attr_reader :error_msg
1158
+ # The last XML request sent by this object
1159
+ attr_reader :request_xml
1160
+ # The last XML response received by this object
1161
+ attr_reader :response_xml
1162
+ # The NSC Connection associated with this object
1163
+ attr_reader :connection
1164
+ # The Site ID
1165
+ attr_reader :site_id
1166
+ # The Site Name
1167
+ attr_reader :site_name
1168
+ # A Description of the Site
1169
+ attr_reader :description
1170
+ # User assigned risk multiplier
1171
+ attr_reader :riskfactor
1172
+ # Array containing ((IPRange|HostName)*)
1173
+ attr_reader :hosts
1174
+ # Array containing (AdminCredentials*)
1175
+ attr_reader :credentials
1176
+ # Array containing ((SmtpAlera|SnmpAlert|SyslogAlert)*)
1177
+ attr_reader :alerts
1178
+ # ScanConfig object which holds Schedule and ScanTrigger Objects
1179
+ attr_reader :scanConfig
1180
+
1181
+ def initialize()
1182
+ @xml_tag_stack = Array.new()
1183
+ @hosts = Array.new()
1184
+ @credentials = Array.new()
1185
+ @alerts = Array.new()
1186
+ @error = false
1187
+ end
1188
+
1189
+ # Adds a new host to the hosts array
1190
+ def addHost(host)
1191
+ @hosts.push(host)
1192
+ end
1193
+
1194
+ # Adds a new alert to the alerts array
1195
+ def addAlert(alert)
1196
+ @alerts.push(alert)
1197
+ end
1198
+
1199
+ # Adds a new set of credentials to the credentials array
1200
+ def addCredentials(credential)
1201
+ @credentials.push(credential)
1202
+ end
1203
+
1204
+ # TODO
1205
+ def getSiteConfig(connection,site_id)
1206
+ @connection = connection
1207
+ @site_id = site_id
1208
+
1209
+ r = APIRequest.execute(@connection.url,'<SiteConfigRequest session-id="' + @connection.session_id + '" site-id="' + @site_id + '"/>')
1210
+ parse(r.res)
1211
+ end
1212
+
1213
+ def _set_site_id(site_id)
1214
+ @site_id = site_id
1215
+ end
1216
+
1217
+ def _set_site_name(site_name)
1218
+ @site_name = site_name
1219
+ end
1220
+
1221
+ def _set_description(description)
1222
+ @description = description
1223
+ end
1224
+
1225
+ def _set_riskfactor(riskfactor)
1226
+ @riskfactor = riskfactor
1227
+ end
1228
+
1229
+ def _set_scanConfig(scanConfig)
1230
+ @scanConfig = scanConfig
1231
+ end
1232
+
1233
+ def _set_connection(connection)
1234
+ @connection = connection
1235
+ end
1236
+ =begin
1237
+ <SiteConfigResponse success='1'>
1238
+ <Site name='Site1' id='243' description='' riskfactor='1.0'>
1239
+ <Hosts>
1240
+ <range from='127.0.0.1'/>
1241
+ </Hosts>
1242
+ <Credentials>
1243
+ </Credentials>
1244
+ <Alerting>
1245
+ </Alerting>
1246
+ <ScanConfig configID='243' name='Full audit' configVersion='3' engineID='2' templateID='full-audit'>
1247
+ <Schedules>
1248
+ </Schedules>
1249
+ <ScanTriggers>
1250
+ </ScanTriggers>
1251
+ </ScanConfig>
1252
+ </Site>
1253
+
1254
+ =end
1255
+
1256
+ def parse(response)
1257
+ response.elements.each('SiteConfigResponse/Site') do |s|
1258
+ @site_id = s.attributes['id']
1259
+ @site_name = s.attributes['name']
1260
+ @description = s.attributes['description']
1261
+ @riskfactor = s.attributes['riskfactor']
1262
+ s.elements.each('Hosts/range') do |r|
1263
+ @hosts.push(IPRange.new(r.attributes['from'],r.attributes['to']))
1264
+ end
1265
+ s.elements.each('ScanConfig') do |c|
1266
+ @scanConfig = ScanConfig.new(c.attributes['configID'],
1267
+ c.attributes['name'],
1268
+ c.attributes['configVersion'],
1269
+ c.attributes['templateID'])
1270
+ s.elements.each('Schedule') do |schedule|
1271
+ schedule = new Schedule(schedule.attributes["type"], schedule.attributes["interval"], schedule.attributes["start"], schedule.attributes["enabled"])
1272
+ @scanConfig.addSchedule(schedule)
1273
+ end
1274
+ end
1275
+
1276
+ s.elements.each('Alerting/Alert') do |a|
1277
+
1278
+ a.elements.each('smtpAlert') do |smtp|
1279
+ smtp_alert = SmtpAlert.new(a.attributes["name"], smtp.attributes["sender"], smtp.attributes["limitText"], a.attributes["enabled"])
1280
+
1281
+ smtp.elements.each('recipient') do |recipient|
1282
+ smtp_alert.addRecipient(recipient.text)
1283
+ end
1284
+ @alerts.push(smtp_alert)
1285
+ end
1286
+
1287
+ a.elements.each('snmpAlert') do |snmp|
1288
+ snmp_alert = SnmpAlert.new(a.attributes["name"], snmp.attributes["community"], snmp.attributes["server"], a.attributes["enabled"])
1289
+ @alerts.push(snmp_alert)
1290
+ end
1291
+ a.elements.each('syslogAlert') do |syslog|
1292
+ syslog_alert = SyslogAlert.new(a.attributes["name"], syslog.attributes["server"], a.attributes["enabled"])
1293
+ @alerts.push(syslog_alert)
1294
+ end
1295
+
1296
+ a.elements.each('vulnFilter') do |vulnFilter|
1297
+
1298
+ #vulnfilter = new VulnFilter.new(a.attributes["typemask"], a.attributes["severityThreshold"], $attrs["MAXALERTS"])
1299
+ # Pop off the top alert on the stack
1300
+ #$alert = @alerts.pop()
1301
+ # Add the new recipient string to the Alert Object
1302
+ #$alert.setVulnFilter($vulnfilter)
1303
+ # Push the alert back on to the alert stack
1304
+ #array_push($this->alerts, $alert)
1305
+ end
1306
+
1307
+ a.elements.each('scanFilter') do |scanFilter|
1308
+ #<scanFilter scanStop='0' scanFailed='0' scanStart='1'/>
1309
+ #scanfilter = ScanFilter.new(scanFilter.attributes['scanStop'],scanFilter.attributes['scanFailed'],scanFilter.attributes['scanStart'])
1310
+ #alert = @alerts.pop()
1311
+ #alert.setScanFilter(scanfilter)
1312
+ #@alerts.push(alert)
1313
+ end
1314
+ end
1315
+ end
1316
+ end
1317
+ end
1318
+
1319
+ # === Description
1320
+ # Object that represents the scan history of a site.
1321
+ #
1322
+ class SiteScanHistory
1323
+ # true if an error condition exists; false otherwise
1324
+ attr_reader :error
1325
+ # Error message string
1326
+ attr_reader :error_msg
1327
+ # The last XML request sent by this object
1328
+ attr_reader :request_xml
1329
+ # The last XML response received by this object
1330
+ attr_reader :response_xml
1331
+ # The NSC Connection associated with this object
1332
+ attr_reader :connection
1333
+ # The Site ID
1334
+ attr_reader :site_id
1335
+ # //Array containing (ScanSummary*)
1336
+ attr_reader :scan_summaries
1337
+
1338
+ def initialize(connection, id)
1339
+ @site_id = id
1340
+ @error = false
1341
+ @connection = connection
1342
+ @scan_summaries = Array.new()
1343
+
1344
+ r = @connection.execute('<SiteScanHistoryRequest' + ' session-id="' + @connection.session_id + '" site-id="' + @site_id + '"/>')
1345
+ status = r.success
1346
+ end
1347
+ end
1348
+
1349
+ # === Description
1350
+ # 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.
1351
+ #
1352
+ class SiteDeviceListing
1353
+
1354
+ # true if an error condition exists; false otherwise
1355
+ attr_reader :error
1356
+ # Error message string
1357
+ attr_reader :error_msg
1358
+ # The last XML request sent by this object
1359
+ attr_reader :request_xml
1360
+ # The last XML response received by this object
1361
+ attr_reader :response_xml
1362
+ # The NSC Connection associated with this object
1363
+ attr_reader :connection
1364
+ # The Site ID. 0 if all sites are specified.
1365
+ attr_reader :site_id
1366
+ # //Array of (Device)*
1367
+ attr_reader :devices
1368
+
1369
+ def initialize(connection, site_id = 0)
1370
+
1371
+ @site_id = site_id
1372
+ @error = false
1373
+ @connection = connection
1374
+ @devices = Array.new()
1375
+
1376
+ r = nil
1377
+ if (@site_id)
1378
+ r = @connection.execute('<SiteDeviceListingRequest session-id="' + connection.session_id + '" site-id="' + @site_id + '"/>')
1379
+ else
1380
+ r = @connection.execute('<SiteDeviceListingRequest session-id="' + connection.session_id + '"/>')
1381
+ end
1382
+
1383
+ if(r.success)
1384
+ response.elements.each('SiteDeviceListingResponse/SiteDevices/device') do |d|
1385
+ @devices.push(Device.new(d.attributes['id'],@site_id,d.attributes["address"],d.attributes["riskfactor"],d.attributes['riskscore']))
1386
+ end
1387
+ end
1388
+ end
1389
+ end
1390
+
1391
+ # === Description
1392
+ # Object that represents a site, including the site configuration, scan history, and device listing.
1393
+ #
1394
+ # === Example
1395
+ # # Create a new Nexpose Connection on the default port and Login
1396
+ # nsc = Connection.new("10.1.40.10","nxadmin","password")
1397
+ # nsc.login()
1398
+ #
1399
+ # # Get an Existing Site
1400
+ # site_existing = Site.new(nsc,184)
1401
+ #
1402
+ # # Create a New Site, add some hosts, and save it to the NSC
1403
+ # site = Site.new(nsc)
1404
+ # site.setSiteConfig("New Site", "New Site Created in the API")
1405
+ #
1406
+ # # Add the hosts
1407
+ # site.site_config.addHost(HostName.new("localhost"))
1408
+ # site.site_config.addHost(IPRange.new("192.168.7.1","192.168.7.255"))
1409
+ # site.site_config.addHost(IPRange.new("10.1.20.30"))
1410
+ #
1411
+ # status = site.saveSite()
1412
+ #
1413
+ class Site
1414
+ # true if an error condition exists; false otherwise
1415
+ attr_reader :error
1416
+ # Error message string
1417
+ attr_reader :error_msg
1418
+ # The last XML request sent by this object
1419
+ attr_reader :request_xml
1420
+ # The last XML response received by this object
1421
+ attr_reader :response_xml
1422
+ # The NSC Connection associated with this object
1423
+ attr_reader :connection
1424
+ # The Site ID
1425
+ # site_id = -1 means create a new site. The NSC will assign a new site_id on SiteSave.
1426
+ attr_reader :site_id
1427
+ # A summary overview of this site
1428
+ # SiteSummary Object
1429
+ attr_reader :site_summary
1430
+ # The configuration of this site
1431
+ # SiteConfig Object
1432
+ attr_reader :site_config
1433
+ # The device listing for this site
1434
+ # SiteDeviceListing Object
1435
+ attr_reader :site_device_listing
1436
+ # The scan history of this site
1437
+ # SiteScanHistory Object
1438
+ attr_reader :site_scan_history
1439
+
1440
+ def initialize(connection, site_id = -1)
1441
+ @error = false
1442
+ @connection = connection
1443
+ @site_id = site_id
1444
+
1445
+ # If site_id > 0 then get SiteConfig
1446
+ if (@site_id.to_i > 0)
1447
+ # Create new SiteConfig object
1448
+ @site_config = SiteConfig.new()
1449
+ # Populate SiteConfig Obect with Data from the NSC
1450
+ @site_config.getSiteConfig(@connection,@site_id)
1451
+ @site_summary = SiteSummary.new(@site_id, @site_config.site_name, @site_config.description, @site_config.riskfactor)
1452
+ @site_scan_history = SiteScanHistory.new(@connection,@site_id)
1453
+ @site_device_listing = SiteDeviceListing.new(@connection,@site_id)
1454
+
1455
+ else
1456
+ # Just in case user enters a number > -1 or = 0
1457
+ @site_id = -1
1458
+
1459
+ @site_config = SiteConfig.new()
1460
+ setSiteConfig("New Site " + rand(999999999999).to_s,"")
1461
+ @site_summary = nil
1462
+
1463
+ end
1464
+
1465
+ end
1466
+
1467
+ # Creates a new site summary
1468
+ def setSiteSummary(site_name, description, riskfactor = 1)
1469
+ @site_summary = SiteSummary.new(-1,site_name,description,riskfactor)
1470
+
1471
+ end
1472
+
1473
+ # Creates a new site configuration
1474
+ def setSiteConfig(site_name, description, riskfactor = 1)
1475
+ setSiteSummary(site_name,description,riskfactor)
1476
+ @site_config = SiteConfig.new()
1477
+ @site_config._set_site_id(-1)
1478
+ @site_config._set_site_name(site_name)
1479
+ @site_config._set_description(description)
1480
+ @site_config._set_riskfactor(riskfactor)
1481
+ @site_config._set_scanConfig(ScanConfig.new(-1,"tmp","full-audit"))
1482
+ @site_config._set_connection(@connection)
1483
+
1484
+ end
1485
+
1486
+ # Initiates a scan of this site. If successful returns scan_id and engine_id in an associative array. Returns false if scan is unsuccessful.
1487
+ def scanSite()
1488
+ r = @connection.execute('<SiteScanRequest session-id="' + "#{@connection.session_id}" + '" site-id="' + "#{@site_id}" + '"/>')
1489
+ if(r.success)
1490
+ res = {}
1491
+ r.res.elements.each('//Scan/') do |s|
1492
+ res[:scan_id] = s.attributes['scan-id']
1493
+ res[:engine_id] = s.attributes['engine-id']
1494
+ end
1495
+ return res
1496
+ else
1497
+ return false
1498
+ end
1499
+ end
1500
+
1501
+ # Saves this site in the NSC
1502
+ def saveSite()
1503
+ r = @connection.execute('<SiteSaveRequest session-id="' + @connection.session_id + '">' + getSiteXML() + ' </SiteSaveRequest>')
1504
+ if (r.success)
1505
+ @site_id = r.attributes['site-id']
1506
+ @site_config._set_site_id(@site_id)
1507
+ @site_config.scanConfig._set_configID(@site_id)
1508
+ @site_config.scanConfig._set_name(@site_id)
1509
+ return true
1510
+ else
1511
+ return false
1512
+ end
1513
+ end
1514
+
1515
+ def deleteSite()
1516
+ r = @connection.execute('<SiteDeleteRequest session-id="' + @connection.session_id.to_s + '" site-id="' + @site_id + '"/>')
1517
+ r.success
1518
+ end
1519
+
1520
+
1521
+ def printSite()
1522
+ puts "Site ID: " + @site_summary.id
1523
+ puts "Site Name: " + @site_summary.site_name
1524
+ puts "Site Description: " + @site_summary.description
1525
+ puts "Site Risk Factor: " + @site_summary.riskfactor
1526
+ end
1527
+
1528
+ def getSiteXML()
1529
+
1530
+ xml = '<Site id="' + "#{@site_config.site_id}" + '" name="' + "#{@site_config.site_name}" + '" description="' + "#{@site_config.description}" + '" riskfactor="' + "#{@site_config.riskfactor}" + '">'
1531
+
1532
+ xml << ' <Hosts>'
1533
+ @site_config.hosts.each do |h|
1534
+ xml << h.to_xml if h.respond_to? :to_xml
1535
+ end
1536
+ xml << '</Hosts>'
1537
+
1538
+ xml << '<Credentials>'
1539
+ @site_config.credentials.each do |c|
1540
+ xml << c.to_xml if c.respond_to? :to_xml
1541
+ end
1542
+ xml << ' </Credentials>'
1543
+
1544
+ xml << ' <Alerting>'
1545
+ @site_config.alerts.each do |a|
1546
+ xml << a.to_xml if a.respond_to? :to_xml
1547
+ end
1548
+ xml << ' </Alerting>'
1549
+
1550
+ xml << ' <ScanConfig configID="' + "#{@site_config.scanConfig.configID}" + '" name="' + "#{@site_config.scanConfig.name}" + '" templateID="' + "#{@site_config.scanConfig.templateID}" + '" configVersion="' + "#{@site_config.scanConfig.configVersion}" + '">'
1551
+
1552
+ xml << ' <Schedules>'
1553
+ @site_config.scanConfig.schedules.each do |s|
1554
+ xml << ' <Schedule enabled="' + s.enabled + '" type="' + s.type + '" interval="' + s.interval + '" start="' + s.start + '"/>'
1555
+ end
1556
+ xml << ' </Schedules>'
1557
+
1558
+ xml << ' <ScanTriggers>'
1559
+ @site_config.scanConfig.scanTriggers.each do |s|
1560
+
1561
+ if (s.class.to_s == "Nexpose::AutoUpdate")
1562
+ xml << ' <autoUpdate enabled="' + s.enabled + '" incremental="' + s.incremental + '"/>'
1563
+ end
1564
+ end
1565
+
1566
+ xml << ' </ScanTriggers>'
1567
+
1568
+ xml << ' </ScanConfig>'
1569
+
1570
+ xml << ' </Site>'
1571
+
1572
+ return xml
1573
+ end
1574
+ end
1575
+
1576
+ # === Description
1577
+ # 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.
1578
+ #
1579
+ class AdminCredentials
1580
+ # Security blob for an existing set of credentials
1581
+ attr_reader :securityblob
1582
+ # Designates if this object contains user defined credentials or a security blob
1583
+ attr_reader :isblob
1584
+ # The service for these credentials. Can be All.
1585
+ attr_reader :service
1586
+ # The host for these credentials. Can be Any.
1587
+ attr_reader :host
1588
+ # The port on which to use these credentials.
1589
+ attr_reader :port
1590
+ # The user id or username
1591
+ attr_reader :userid
1592
+ # The password
1593
+ attr_reader :password
1594
+ # The realm for these credentials
1595
+ attr_reader :realm
1596
+
1597
+
1598
+ def initialize(isblob = false)
1599
+ @isblob = isblob
1600
+ end
1601
+
1602
+ # Sets the credentials information for this object.
1603
+ def setCredentials(service, host, port, userid, password, realm)
1604
+ @isblob = false
1605
+ @securityblob = nil
1606
+ @service = service
1607
+ @host = host
1608
+ @port = port
1609
+ @userid = userid
1610
+ @password = password
1611
+ @realm = realm
1612
+ end
1613
+
1614
+ # TODO: add description
1615
+ def setService(service)
1616
+ @service = service
1617
+ end
1618
+
1619
+ def setHost(host)
1620
+ @host = host
1621
+ end
1622
+
1623
+ # TODO: add description
1624
+ def setBlob(securityblob)
1625
+ @isblob = true
1626
+ @securityblob = securityblob
1627
+ end
1628
+
1629
+ include Sanitize
1630
+ def to_xml
1631
+ xml = ''
1632
+ xml << '<adminCredentials'
1633
+ xml << %Q{ service="#{replace_entities(service)}"} if (service)
1634
+ xml << %Q{ userid="#{replace_entities(userid)}"} if (userid)
1635
+ xml << %Q{ password="#{replace_entities(password)}"} if (password)
1636
+ xml << %Q{ realm="#{replace_entities(realm)}"} if (realm)
1637
+ xml << %Q{ host="#{replace_entities(host)}"} if (host)
1638
+ xml << %Q{ port="#{replace_entities(port)}"} if (port)
1639
+ xml << '>'
1640
+ xml << replace_entities(securityblob) if (isblob)
1641
+ xml << '</adminCredentials>'
1642
+
1643
+ xml
1644
+ end
1645
+ end
1646
+
1647
+
1648
+ # === Description
1649
+ # Object that represents an SMTP (Email) Alert.
1650
+ #
1651
+ class SmtpAlert
1652
+ # A unique name for this alert
1653
+ attr_reader :name
1654
+ # If this alert is enabled or not
1655
+ attr_reader :enabled
1656
+ # The email address of the sender
1657
+ attr_reader :sender
1658
+ # Limit the text for mobile devices
1659
+ attr_reader :limitText
1660
+ # Array containing Strings of email addresses
1661
+ # Array of strings with the email addresses of the intended recipients
1662
+ attr_reader :recipients
1663
+ # The vulnerability filter to trigger the alert
1664
+ attr_reader :vulnFilter
1665
+ # The alert type
1666
+ attr_reader :type
1667
+
1668
+ def initialize(name, sender, limitText, enabled = 1)
1669
+ @type = :smtp
1670
+ @name = name
1671
+ @sender = sender
1672
+ @enabled = enabled
1673
+ @limitText = limitText
1674
+ @recipients = Array.new()
1675
+ # Sets default vuln filter - All Events
1676
+ setVulnFilter(VulnFilter.new("50790400",1))
1677
+ end
1678
+
1679
+ # Adds a new Recipient to the recipients array
1680
+ def addRecipient(recipient)
1681
+ @recipients.push(recipient)
1682
+ end
1683
+
1684
+ # Sets the Vulnerability Filter for this alert.
1685
+ def setVulnFilter(vulnFilter)
1686
+ @vulnFilter = vulnFilter
1687
+ end
1688
+
1689
+ include Sanitize
1690
+ def to_xml
1691
+ xml = "<smtpAlert"
1692
+ xml << %Q{ name="#{replace_entities(name)}"}
1693
+ xml << %Q{ enabled="#{replace_entities(enabled)}"}
1694
+ xml << %Q{ sender="#{replace_entities(sender)}"}
1695
+ xml << %Q{ limitText="#{replace_entities(limitText)}">}
1696
+ recipients.each do |recpt|
1697
+ xml << "<recipient>#{replace_entities(recpt)}</recipient>"
1698
+ end
1699
+ xml << vulnFilter.to_xml
1700
+ xml << "</smtpAlert>"
1701
+ xml
1702
+ end
1703
+ end
1704
+
1705
+ # === Description
1706
+ # Object that represents an SNMP Alert.
1707
+ #
1708
+ class SnmpAlert
1709
+
1710
+ # A unique name for this alert
1711
+ attr_reader :name
1712
+ # If this alert is enabled or not
1713
+ attr_reader :enabled
1714
+ # The community string
1715
+ attr_reader :community
1716
+ # The SNMP server to sent this alert
1717
+ attr_reader :server
1718
+ # The vulnerability filter to trigger the alert
1719
+ attr_reader :vulnFilter
1720
+ # The alert type
1721
+ attr_reader :type
1722
+
1723
+ def initialize(name, community, server, enabled = 1)
1724
+ @type = :snmp
1725
+ @name = name
1726
+ @community = community
1727
+ @server = server
1728
+ @enabled = enabled
1729
+ # Sets default vuln filter - All Events
1730
+ setVulnFilter(VulnFilter.new("50790400",1))
1731
+ end
1732
+
1733
+ # Sets the Vulnerability Filter for this alert.
1734
+ def setVulnFilter(vulnFilter)
1735
+ @vulnFilter = vulnFilter
1736
+ end
1737
+
1738
+ include Sanitize
1739
+ def to_xml
1740
+ xml = "<snmpAlert"
1741
+ xml << %Q{ name="#{replace_entities(name)}"}
1742
+ xml << %Q{ enabled="#{replace_entities(enabled)}"}
1743
+ xml << %Q{ community="#{replace_entities(community)}"}
1744
+ xml << %Q{ server="#{replace_entities(server)}">}
1745
+ xml << vulnFilter.to_xml
1746
+ xml << "</snmpAlert>"
1747
+ xml
1748
+ end
1749
+
1750
+ end
1751
+
1752
+ # === Description
1753
+ # Object that represents a Syslog Alert.
1754
+ #
1755
+ class SyslogAlert
1756
+
1757
+ # A unique name for this alert
1758
+ attr_reader :name
1759
+ # If this alert is enabled or not
1760
+ attr_reader :enabled
1761
+ # The Syslog server to sent this alert
1762
+ attr_reader :server
1763
+ # The vulnerability filter to trigger the alert
1764
+ attr_reader :vulnFilter
1765
+ # The alert type
1766
+ attr_reader :type
1767
+
1768
+ def initialize(name, server, enabled = 1)
1769
+ @type = :syslog
1770
+ @name = name
1771
+ @server = server
1772
+ @enabled = enabled
1773
+ # Sets default vuln filter - All Events
1774
+ setVulnFilter(VulnFilter.new("50790400",1))
1775
+
1776
+ end
1777
+
1778
+ # Sets the Vulnerability Filter for this alert.
1779
+ def setVulnFilter(vulnFilter)
1780
+ @vulnFilter = vulnFilter
1781
+ end
1782
+
1783
+ include Sanitize
1784
+ def to_xml
1785
+ xml = "<syslogAlert"
1786
+ xml << %Q{ name="#{replace_entities(name)}"}
1787
+ xml << %Q{ enabled="#{replace_entities(enabled)}"}
1788
+ xml << %Q{ server="#{replace_entities(server)}">}
1789
+ xml << vulnFilter.to_xml
1790
+ xml << "</syslogAlert>"
1791
+ xml
1792
+ end
1793
+
1794
+ end
1795
+
1796
+ # TODO: review
1797
+ # <scanFilter scanStop='0' scanFailed='0' scanStart='1'/>
1798
+ # === Description
1799
+ #
1800
+ class ScanFilter
1801
+
1802
+ attr_reader :scanStop
1803
+ attr_reader :scanFailed
1804
+ attr_reader :scanStart
1805
+
1806
+ def initialize(scanstop, scanFailed, scanStart)
1807
+
1808
+ @scanStop = scanStop
1809
+ @scanFailed = scanFailed
1810
+ @scanStart = scanStart
1811
+
1812
+ end
1813
+
1814
+ end
1815
+
1816
+ # TODO: review
1817
+ # === Description
1818
+ #
1819
+ class VulnFilter
1820
+
1821
+ attr_reader :typeMask
1822
+ attr_reader :maxAlerts
1823
+ attr_reader :severityThreshold
1824
+
1825
+ def initialize(typeMask, severityThreshold, maxAlerts = -1)
1826
+ @typeMask = typeMask
1827
+ @maxAlerts = maxAlerts
1828
+ @severityThreshold = severityThreshold
1829
+ end
1830
+
1831
+ include Sanitize
1832
+ def to_xml
1833
+ xml = "<vulnFilter "
1834
+ xml << %Q{ typeMask="#{replace_entities(typeMask)}"}
1835
+ xml << %Q{ maxAlerts="#{replace_entities(maxAlerts)}"}
1836
+ xml << %Q{ severityThreshold="#{replace_entities(severityThreshold)}"}
1837
+ xml << "/>"
1838
+
1839
+ xml
1840
+ end
1841
+
1842
+ end
1843
+
1844
+ # TODO add engineID
1845
+ # === Description
1846
+ # Object that represents the scanning configuration for a Site.
1847
+ #
1848
+ class ScanConfig
1849
+ # A unique ID for this scan configuration
1850
+ attr_reader :configID
1851
+ # The name of the scan template
1852
+ attr_reader :name
1853
+ # The ID of the scan template used full-audit, exhaustive-audit, web-audit, dos-audit, internet-audit, network-audit
1854
+ attr_reader :templateID
1855
+ # The configuration version (default is 2)
1856
+ attr_reader :configVersion
1857
+ # Array of (Schedule)*
1858
+ attr_reader :schedules
1859
+ # Array of (ScanTrigger)*
1860
+ attr_reader :scanTriggers
1861
+
1862
+ def initialize(configID, name, templateID, configVersion = 2)
1863
+
1864
+ @configID = configID
1865
+ @name = name
1866
+ @templateID = templateID
1867
+ @configVersion = configVersion
1868
+ @schedules = Array.new()
1869
+ @scanTriggers = Array.new()
1870
+
1871
+ end
1872
+
1873
+ # Adds a new Schedule for this ScanConfig
1874
+ def addSchedule(schedule)
1875
+ @schedules.push(schedule)
1876
+ end
1877
+
1878
+ # Adds a new ScanTrigger to the scanTriggers array
1879
+ def addScanTrigger(scanTrigger)
1880
+ @scanTriggers.push(scanTrigger)
1881
+ end
1882
+
1883
+ def _set_configID(configID)
1884
+ @configID = configID
1885
+ end
1886
+
1887
+ def _set_name(name)
1888
+ @name = name
1889
+ end
1890
+
1891
+ end
1892
+
1893
+ # === Description
1894
+ # Object that holds a scan schedule
1895
+ #
1896
+ class Schedule
1897
+ # Type of Schedule (daily|hourly|monthly|weekly)
1898
+ attr_reader :type
1899
+ # The schedule interval
1900
+ attr_reader :interval
1901
+ # The date and time to start the first scan
1902
+ attr_reader :start
1903
+ # Enable or disable this schedule
1904
+ attr_reader :enabled
1905
+ # The date and time to disable to schedule. If null then the schedule will run forever.
1906
+ attr_reader :notValidAfter
1907
+ # Scan on the same date each time
1908
+ attr_reader :byDate
1909
+
1910
+ def initialize(type, interval, start, enabled = 1)
1911
+
1912
+ @type = type
1913
+ @interval = interval
1914
+ @start = start
1915
+ @enabled = enabled
1916
+
1917
+ end
1918
+
1919
+
1920
+
1921
+ end
1922
+
1923
+ # === Description
1924
+ # Object that holds an event that triggers the start of a scan.
1925
+ #
1926
+ class ScanTrigger
1927
+ # Type of Trigger (AutoUpdate)
1928
+ attr_reader :type
1929
+ # Enable or disable this scan trigger
1930
+ attr_reader :enabled
1931
+ # Sets the trigger to start an incremental scan or a full scan
1932
+ attr_reader :incremental
1933
+
1934
+ def initialize(type, incremental, enabled = 1)
1935
+
1936
+ @type = type
1937
+ @incremental = incremental
1938
+ @enabled = enabled
1939
+
1940
+ end
1941
+
1942
+ end
1943
+
1944
+ # === Description
1945
+ # Object that represents a single device in an NSC.
1946
+ #
1947
+ class Device
1948
+
1949
+ # A unique device ID (assigned by the NSC)
1950
+ attr_reader :id
1951
+ # The site ID of this devices site
1952
+ attr_reader :site_id
1953
+ # IP Address or Hostname of this device
1954
+ attr_reader :address
1955
+ # User assigned risk multiplier
1956
+ attr_reader :riskfactor
1957
+ # NeXpose risk score
1958
+ attr_reader :riskscore
1959
+
1960
+ def initialize(id, site_id, address, riskfactor=1, riskscore=0)
1961
+ @id = id
1962
+ @site_id = site_id
1963
+ @address = address
1964
+ @riskfactor = riskfactor
1965
+ @riskscore = riskscore
1966
+
1967
+ end
1968
+
1969
+ end
1970
+
1971
+
1972
+ # === Description
1973
+ # Object that represents a summary of a scan.
1974
+ #
1975
+ class ScanSummary
1976
+ # The Scan ID of the Scan
1977
+ attr_reader :scan_id
1978
+ # The Engine ID used to perform the scan
1979
+ attr_reader :engine_id
1980
+ # TODO: add description
1981
+ attr_reader :name
1982
+ # The scan start time
1983
+ attr_reader :startTime
1984
+ # The scan finish time
1985
+ attr_reader :endTime
1986
+ # The scan status (running|finished|stopped|error| dispatched|paused|aborted|uknown)
1987
+ attr_reader :status
1988
+ # The number of pending tasks
1989
+ attr_reader :tasks_pending
1990
+ # The number of active tasks
1991
+ attr_reader :tasks_active
1992
+ # The number of completed tasks
1993
+ attr_reader :tasks_completed
1994
+ # The number of "live" nodes
1995
+ attr_reader :nodes_live
1996
+ # The number of "dead" nodes
1997
+ attr_reader :nodes_dead
1998
+ # The number of filtered nodes
1999
+ attr_reader :nodes_filtered
2000
+ # The number of unresolved nodes
2001
+ attr_reader :nodes_unresolved
2002
+ # The number of "other" nodes
2003
+ attr_reader :nodes_other
2004
+ # Confirmed vulnerabilities found (indexed by severity)
2005
+ # Associative array, indexed by severity
2006
+ attr_reader :vuln_exploit
2007
+ # Unconfirmed vulnerabilities found (indexed by severity)
2008
+ # Associative array, indexed by severity
2009
+ attr_reader :vuln_version
2010
+ # Not vulnerable checks run (confirmed)
2011
+ attr_reader :not_vuln_exploit
2012
+ # Not vulnerable checks run (unconfirmed)
2013
+ attr_reader :not_vuln_version
2014
+ # Vulnerability check errors
2015
+ attr_reader :vuln_error
2016
+ # Vulnerability checks disabled
2017
+ attr_reader :vuln_disabled
2018
+ # Vulnerability checks other
2019
+ attr_reader :vuln_other
2020
+
2021
+ # Constructor
2022
+ # ScanSummary(can_id, $engine_id, $name, tartTime, $endTime, tatus)
2023
+ def initialize(scan_id, engine_id, name, startTime, endTime, status)
2024
+
2025
+ @scan_id = scan_id
2026
+ @engine_id = engine_id
2027
+ @name = name
2028
+ @startTime = startTime
2029
+ @endTime = endTime
2030
+ @status = status
2031
+
2032
+ end
2033
+
2034
+ end
2035
+
2036
+ # TODO
2037
+ # === Description
2038
+ # Object that represents the overview statistics for a particular scan.
2039
+ #
2040
+ # === Examples
2041
+ #
2042
+ # # Create a new Nexpose Connection on the default port and Login
2043
+ # nsc = Connection.new("10.1.40.10","nxadmin","password")
2044
+ # nsc.login()
2045
+ #
2046
+ # # Get a Site (Site ID = 12) from the NSC
2047
+ # site = new Site(nsc,12)
2048
+ #
2049
+ # # Start a Scan of this site and pause for 1 minute
2050
+ # scan1 = site.scanSite()
2051
+ # sleep(60)
2052
+ #
2053
+ # # Get the Scan Statistics for this scan
2054
+ # scanStatistics = new ScanStatistics(nsc,scan1["scan_id"])
2055
+ #
2056
+ # # Print out number of confirmed vulnerabilities with a 10 severity
2057
+ # puts scanStatistics.scansummary.vuln_exploit[10]
2058
+ #
2059
+ # # Print out the number of pending tasks left in the scan
2060
+ # puts scanStatistics.scan_summary.tasks_pending
2061
+ #
2062
+ class ScanStatistics
2063
+ # true if an error condition exists; false otherwise
2064
+ attr_reader :error
2065
+ # Error message string
2066
+ attr_reader :error_msg
2067
+ # The last XML request sent by this object
2068
+ attr_reader :request_xml
2069
+ # The last XML response received by this object
2070
+ attr_reader :reseponse_xml
2071
+ # The Scan ID
2072
+ attr_reader :scan_id
2073
+ # The ScanSummary of the scan
2074
+ attr_reader :scan_summary
2075
+ # The NSC Connection associated with this object
2076
+ attr_reader :connection
2077
+
2078
+ # Vulnerability checks other
2079
+ attr_reader :vuln_other
2080
+ def initialize(connection, scan_id)
2081
+ @error = false
2082
+ @connection = connection
2083
+ @scan_id = scan_id
2084
+ end
2085
+ end
2086
+
2087
+ # ==== Description
2088
+ # Object that represents a listing of all of the scan engines available on to an NSC.
2089
+ #
2090
+ class EngineListing
2091
+ # true if an error condition exists; false otherwise
2092
+ attr_reader :error
2093
+ # Error message string
2094
+ attr_reader :error_msg
2095
+ # The last XML request sent by this object
2096
+ attr_reader :request_xml
2097
+ # The last XML response received by this object
2098
+ attr_reader :response_xml
2099
+ # The NSC Connection associated with this object
2100
+ attr_reader :connection
2101
+ # Array containing (EngineSummary*)
2102
+ attr_reader :engines
2103
+ # The number of scan engines
2104
+ attr_reader :engine_count
2105
+
2106
+ # Constructor
2107
+ # EngineListing (connection)
2108
+ def initialize(connection)
2109
+ @connection = connection
2110
+ @engines = []
2111
+ @engine_count = 0
2112
+ @error = false
2113
+ r = @connection.execute('<EngineListingRequest session-id="' + @connection.session_id + '"/>', '1.2')
2114
+
2115
+ if (r.success)
2116
+ r.res.elements.each('EngineListingResponse/EngineSummary') do |v|
2117
+ @engines.push(EngineSummary.new(v.attributes['id'], v.attributes['name'], v.attributes['address'],
2118
+ v.attributes['port'], v.attributes['status']))
2119
+ end
2120
+ else
2121
+ @error = true
2122
+ @error_msg = 'EngineListingRequest Parse Error'
2123
+ end
2124
+ @engine_count = @engines.length
2125
+ end
2126
+ end
2127
+
2128
+ # ==== Description
2129
+ # Object that represents the summary of a scan engine.
2130
+ #
2131
+ # ==== Examples
2132
+ #
2133
+ # # Create a new Nexpose Connection on the default port and Login
2134
+ # nsc = Connection.new("10.1.40.10","nxadmin","password")
2135
+ # nsc.login()
2136
+ #
2137
+ # # Get the engine listing for the connection
2138
+ # enginelisting = EngineListing.new(nsc)
2139
+ #
2140
+ # # Print out the status of the first scan engine
2141
+ # puts enginelisting.engines[0].status
2142
+ #
2143
+ class EngineSummary
2144
+ # A unique ID that identifies this scan engine
2145
+ attr_reader :id
2146
+ # The name of this scan engine
2147
+ attr_reader :name
2148
+ # The hostname or IP address of the engine
2149
+ attr_reader :address
2150
+ # The port there the engine is listening
2151
+ attr_reader :port
2152
+ # The engine status (active|pending-auth| incompatible|not-responding|unknown)
2153
+ attr_reader :status
2154
+
2155
+ # Constructor
2156
+ # EngineSummary(id, name, address, port, status)
2157
+ def initialize(id, name, address, port, status)
2158
+ @id = id
2159
+ @name = name
2160
+ @address = address
2161
+ @port = port
2162
+ @status = status
2163
+ end
2164
+
2165
+ end
2166
+
2167
+
2168
+ # TODO
2169
+ class EngineActivity
2170
+ # true if an error condition exists; false otherwise
2171
+ attr_reader :error
2172
+ # Error message string
2173
+ attr_reader :error_msg
2174
+ # The last XML request sent by this object
2175
+ attr_reader :request_xml
2176
+ # The last XML response received by this object
2177
+ attr_reader :response_xml
2178
+ # The NSC Connection associated with this object
2179
+ attr_reader :connection
2180
+ # The Engine ID
2181
+ attr_reader :engine_id
2182
+ # Array containing (ScanSummary*)
2183
+ attr_reader :scan_summaries
2184
+
2185
+
2186
+ end
2187
+
2188
+ # === Description
2189
+ # Object that represents a listing of all of the vulnerabilities in the vulnerability database
2190
+ #
2191
+ class VulnerabilityListing
2192
+
2193
+ # true if an error condition exists; false otherwise
2194
+ attr_reader :error
2195
+ # Error message string
2196
+ attr_reader :error_msg
2197
+ # The last XML request sent by this object
2198
+ attr_reader :request_xml
2199
+ # The last XML response received by this object
2200
+ attr_reader :response_xml
2201
+ # The NSC Connection associated with this object
2202
+ attr_reader :connection
2203
+ # Array containing (VulnerabilitySummary*)
2204
+ attr_reader :vulnerability_summaries
2205
+ # The number of vulnerability definitions
2206
+ attr_reader :vulnerability_count
2207
+
2208
+ # Constructor
2209
+ # VulnerabilityListing(connection)
2210
+ def initialize(connection)
2211
+ @error = false
2212
+ @vulnerability_summaries = []
2213
+ @connection = connection
2214
+
2215
+ r = @connection.execute('<VulnerabilityListingRequest session-id="' + @connection.session_id + '"/>')
2216
+
2217
+ if (r.success)
2218
+ r.res.elements.each('VulnerabilityListingResponse/VulnerabilitySummary') do |v|
2219
+ @vulnerability_summaries.push(VulnerabilitySummary.new(v.attributes['id'],v.attributes["title"],v.attributes["severity"]))
2220
+ end
2221
+ else
2222
+ @error = true
2223
+ @error_msg = 'VulnerabilitySummaryRequest Parse Error'
2224
+ end
2225
+ @vulnerability_count = @vulnerability_summaries.length
2226
+ end
2227
+ end
2228
+
2229
+ # === Description
2230
+ # Object that represents the summary of an entry in the vulnerability database
2231
+ #
2232
+ class VulnerabilitySummary
2233
+
2234
+ # The unique ID string for this vulnerability
2235
+ attr_reader :id
2236
+ # The title of this vulnerability
2237
+ attr_reader :title
2238
+ # The severity of this vulnerability (1 – 10)
2239
+ attr_reader :severity
2240
+
2241
+ # Constructor
2242
+ # VulnerabilitySummary(id, title, severity)
2243
+ def initialize(id, title, severity)
2244
+ @id = id
2245
+ @title = title
2246
+ @severity = severity
2247
+
2248
+ end
2249
+
2250
+ end
2251
+
2252
+ # === Description
2253
+ #
2254
+ class Reference
2255
+
2256
+ attr_reader :source
2257
+ attr_reader :reference
2258
+
2259
+ def initialize(source, reference)
2260
+ @source = source
2261
+ @reference = reference
2262
+ end
2263
+ end
2264
+
2265
+ # === Description
2266
+ # Object that represents the details for an entry in the vulnerability database
2267
+ #
2268
+ class VulnerabilityDetail
2269
+ # true if an error condition exists; false otherwise
2270
+ attr_reader :error
2271
+ # Error message string
2272
+ attr_reader :error_msg
2273
+ # The last XML request sent by this object
2274
+ attr_reader :request_xml
2275
+ # The last XML response received by this object
2276
+ attr_reader :response_xml
2277
+ # The NSC Connection associated with this object
2278
+ attr_reader :connection
2279
+ # The unique ID string for this vulnerability
2280
+ attr_reader :id
2281
+ # The title of this vulnerability
2282
+ attr_reader :title
2283
+ # The severity of this vulnerability (1 – 10)
2284
+ attr_reader :severity
2285
+ # The pciSeverity of this vulnerability
2286
+ attr_reader :pciSeverity
2287
+ # The CVSS score of this vulnerability
2288
+ attr_reader :cvssScore
2289
+ # The CVSS vector of this vulnerability
2290
+ attr_reader :cvssVector
2291
+ # The date this vulnerability was published
2292
+ attr_reader :published
2293
+ # The date this vulnerability was added to NeXpose
2294
+ attr_reader :added
2295
+ # The last date this vulnerability was modified
2296
+ attr_reader :modified
2297
+ # The HTML Description of this vulnerability
2298
+ attr_reader :description
2299
+ # External References for this vulnerability
2300
+ # Array containing (Reference)
2301
+ attr_reader :references
2302
+ # The HTML Solution for this vulnerability
2303
+ attr_reader :solution
2304
+
2305
+ # Constructor
2306
+ # VulnerabilityListing(connection,id)
2307
+ def initialize(connection, id)
2308
+
2309
+ @error = false
2310
+ @connection = connection
2311
+ @id = id
2312
+ @references = []
2313
+
2314
+ r = @connection.execute('<VulnerabilityDetailsRequest session-id="' + @connection.session_id + '" vuln-id="' + @id + '"/>')
2315
+
2316
+ if (r.success)
2317
+ r.res.elements.each('VulnerabilityDetailsResponse/Vulnerability') do |v|
2318
+ @id = v.attributes['id']
2319
+ @title = v.attributes["title"]
2320
+ @severity = v.attributes["severity"]
2321
+ @pciSeverity = v.attributes['pciSeverity']
2322
+ @cvssScore = v.attributes['cvssScore']
2323
+ @cvssVector = v.attributes['cvssVector']
2324
+ @published = v.attributes['published']
2325
+ @added = v.attributes['added']
2326
+ @modified = v.attributes['modified']
2327
+
2328
+ v.elements.each('description') do |d|
2329
+ @description = d.to_s.gsub(/\<\/?description\>/i, '')
2330
+ end
2331
+
2332
+ v.elements.each('solution') do |s|
2333
+ @solution = s.to_s.gsub(/\<\/?solution\>/i, '')
2334
+ end
2335
+
2336
+ v.elements.each('references/reference') do |r|
2337
+ @references.push(Reference.new(r.attributes['source'],r.text))
2338
+ end
2339
+ end
2340
+ else
2341
+ @error = true
2342
+ @error_msg = 'VulnerabilitySummaryRequest Parse Error'
2343
+ end
2344
+
2345
+ end
2346
+ end
2347
+
2348
+ # === Description
2349
+ # Object that represents the summary of a Report Configuration.
2350
+ #
2351
+ class ReportConfigSummary
2352
+ # The Report Configuration ID
2353
+ attr_reader :id
2354
+ # A unique name for the Report
2355
+ attr_reader :name
2356
+ # The report format
2357
+ attr_reader :format
2358
+ # The date of the last report generation
2359
+ attr_reader :last_generated_on
2360
+ # Relative URI of the last generated report
2361
+ attr_reader :last_generated_uri
2362
+
2363
+ # Constructor
2364
+ # ReportConfigSummary(id, name, format, last_generated_on, last_generated_uri)
2365
+ def initialize(id, name, format, last_generated_on, last_generated_uri)
2366
+
2367
+ @id = id
2368
+ @name = name
2369
+ @format = format
2370
+ @last_generated_on = last_generated_on
2371
+ @last_generated_uri = last_generated_uri
2372
+
2373
+ end
2374
+
2375
+ end
2376
+
2377
+ # === Description
2378
+ # Object that represents the schedule on which to automatically generate new reports.
2379
+ class ReportHistory
2380
+
2381
+ # true if an error condition exists; false otherwise
2382
+ attr_reader :error
2383
+ # Error message string
2384
+ attr_reader :error_msg
2385
+ # The last XML request sent by this object
2386
+ attr_reader :request_xml
2387
+ # The last XML response received by this object
2388
+ attr_reader :response_xml
2389
+ # The NSC Connection associated with this object
2390
+ attr_reader :connection
2391
+ # The report definition (report config) ID
2392
+ # Report definition ID
2393
+ attr_reader :config_id
2394
+ # Array (ReportSummary*)
2395
+ attr_reader :report_summaries
2396
+
2397
+
2398
+ def initialize(connection, config_id)
2399
+
2400
+ @error = false
2401
+ @connection = connection
2402
+ @config_id = config_id
2403
+ @report_summaries = []
2404
+
2405
+ reportHistory_request = APIRequest.new('<ReportHistoryRequest session-id="' + "#{connection.session_id}" + '" reportcfg-id="' + "#{@config_id}" + '"/>',@connection.geturl())
2406
+ reportHistory_request.execute()
2407
+ @response_xml = reportHistory_request.response_xml
2408
+ @request_xml = reportHistory_request.request_xml
2409
+
2410
+ end
2411
+
2412
+ def xml_parse(response)
2413
+ response = REXML::Document.new(response.to_s)
2414
+ status = response.root.attributes['success']
2415
+ if (status == '1')
2416
+ response.elements.each('ReportHistoryResponse/ReportSummary') do |r|
2417
+ @report_summaries.push(ReportSummary.new(r.attributes["id"], r.attributes["cfg-id"], r.attributes["status"], r.attributes["generated-on"],r.attributes['report-uri']))
2418
+ end
2419
+ else
2420
+ @error = true
2421
+ @error_msg = 'Error ReportHistoryReponse'
2422
+ end
2423
+ end
2424
+
2425
+ end
2426
+
2427
+ # === Description
2428
+ # Object that represents the summary of a single report.
2429
+ class ReportSummary
2430
+
2431
+ # The Report ID
2432
+ attr_reader :id
2433
+ # The Report Configuration ID
2434
+ attr_reader :cfg_id
2435
+ # The status of this report
2436
+ # available | generating | failed
2437
+ attr_reader :status
2438
+ # The date on which this report was generated
2439
+ attr_reader :generated_on
2440
+ # The relative URI of the report
2441
+ attr_reader :report_uri
2442
+
2443
+ def initialize(id, cfg_id, status, generated_on, report_uri)
2444
+
2445
+ @id = id
2446
+ @cfg_id = cfg_id
2447
+ @status = status
2448
+ @generated_on = generated_on
2449
+ @report_uri = report_uri
2450
+
2451
+ end
2452
+
2453
+ end
2454
+
2455
+ # === Description
2456
+ #
2457
+ class ReportAdHoc
2458
+ include XMLUtils
2459
+
2460
+ attr_reader :error
2461
+ attr_reader :error_msg
2462
+ attr_reader :connection
2463
+ # Report Template ID strong e.g. full-audit
2464
+ attr_reader :template_id
2465
+ # pdf|html|xml|text|csv|raw-xml
2466
+ attr_reader :format
2467
+ # Array of (ReportFilter)*
2468
+ attr_reader :filters
2469
+ attr_reader :request_xml
2470
+ attr_reader :response_xml
2471
+ attr_reader :report_decoded
2472
+
2473
+
2474
+ def initialize(connection, template_id = 'full-audit', format = 'raw-xml')
2475
+
2476
+ @error = false
2477
+ @connection = connection
2478
+ @filters = Array.new()
2479
+ @template_id = template_id
2480
+ @format = format
2481
+
2482
+ end
2483
+
2484
+ def addFilter(filter_type, id)
2485
+
2486
+ # filter_type can be site|group|device|scan
2487
+ # id is the ID number. For scan, you can use 'last' for the most recently run scan
2488
+ filter = ReportFilter.new(filter_type, id)
2489
+ filters.push(filter)
2490
+
2491
+ end
2492
+
2493
+ def generate()
2494
+ request_xml = '<ReportAdhocGenerateRequest session-id="' + @connection.session_id + '">'
2495
+ request_xml += '<AdhocReportConfig template-id="' + @template_id + '" format="' + @format + '">'
2496
+ request_xml += '<Filters>'
2497
+ @filters.each do |f|
2498
+ request_xml += '<filter type="' + f.type + '" id="'+ f.id.to_s + '"/>'
2499
+ end
2500
+ request_xml += '</Filters>'
2501
+ request_xml += '</AdhocReportConfig>'
2502
+ request_xml += '</ReportAdhocGenerateRequest>'
2503
+
2504
+ ad_hoc_request = APIRequest.new(request_xml, @connection.url)
2505
+ ad_hoc_request.execute()
2506
+
2507
+ content_type_response = ad_hoc_request.raw_response.header['Content-Type']
2508
+ if content_type_response =~ /multipart\/mixed;\s*boundary=([^\s]+)/
2509
+ # NeXpose sends an incorrect boundary format which breaks parsing
2510
+ # Eg: boundary=XXX; charset=XXX
2511
+ # Fix by removing everything from the last semi-colon onward
2512
+ last_semi_colon_index = content_type_response.index(/;/, content_type_response.index(/boundary/))
2513
+ content_type_response = content_type_response[0, last_semi_colon_index]
2514
+
2515
+ data = "Content-Type: " + content_type_response + "\r\n\r\n" + ad_hoc_request.raw_response_data
2516
+ doc = Rex::MIME::Message.new data
2517
+ doc.parts.each do |part|
2518
+ if /.*base64.*/ =~ part.header.to_s
2519
+ return parse_xml(part.content.unpack("m*")[0])
2520
+ end
2521
+ end
2522
+ end
2523
+ end
2524
+
2525
+ end
2526
+
2527
+ # === Description
2528
+ # Object that represents the configuration of a report definition.
2529
+ #
2530
+ class ReportConfig
2531
+
2532
+ # true if an error condition exists; false otherwise
2533
+ attr_reader :error
2534
+ # Error message string
2535
+ attr_reader :error_msg
2536
+ # The last XML request sent by this object
2537
+ attr_reader :request_xml
2538
+ # The last XML response received by this object
2539
+ attr_reader :response_xml
2540
+ # The NSC Connection associated with this object
2541
+ attr_reader :connection
2542
+ # The ID for this report definition
2543
+ attr_reader :config_id
2544
+ # A unique name for this report definition
2545
+ attr_reader :name
2546
+ # The template ID used for this report definition
2547
+ attr_reader :template_id
2548
+ # html, db, txt, xml, raw-xml, csv, pdf
2549
+ attr_reader :format
2550
+ # XXX new
2551
+ attr_reader :timezone
2552
+ # XXX new
2553
+ attr_reader :owner
2554
+ # Array of (ReportFilter)* - The Sites, Asset Groups, or Devices to run the report against
2555
+ attr_reader :filters
2556
+ # Automatically generate a new report at the conclusion of a scan
2557
+ # 1 or 0
2558
+ attr_reader :generate_after_scan
2559
+ # Schedule to generate reports
2560
+ # ReportSchedule Object
2561
+ attr_reader :schedule
2562
+ # Store the reports on the server
2563
+ # 1 or 0
2564
+ attr_reader :storeOnServer
2565
+ # Location to store the report on the server
2566
+ attr_reader :store_location
2567
+ # Form to send the report via email
2568
+ # "file", "zip", "url", or NULL (don’t send email)
2569
+ attr_reader :email_As
2570
+ # Send the Email to all Authorized Users
2571
+ # boolean - Send the Email to all Authorized Users
2572
+ attr_reader :email_to_all
2573
+ # Array containing the email addresses of the recipients
2574
+ attr_reader :email_recipients
2575
+ # IP Address or Hostname of SMTP Relay Server
2576
+ attr_reader :smtp_relay_server
2577
+ # Sets the FROM field of the Email
2578
+ attr_reader :sender
2579
+ # TODO
2580
+ attr_reader :db_export
2581
+ # TODO
2582
+ attr_reader :csv_export
2583
+ # TODO
2584
+ attr_reader :xml_export
2585
+
2586
+
2587
+ def initialize(connection, config_id = -1)
2588
+
2589
+ @error = false
2590
+ @connection = connection
2591
+ @config_id = config_id
2592
+ @xml_tag_stack = Array.new()
2593
+ @filters = Array.new()
2594
+ @email_recipients = Array.new()
2595
+ @name = "New Report " + rand(999999999).to_s
2596
+
2597
+ r = @connection.execute('<ReportConfigRequest session-id="' + @connection.session_id.to_s + '" reportcfg-id="' + @config_id.to_s + '"/>')
2598
+ if (r.success)
2599
+ r.res.elements.each('ReportConfigResponse/ReportConfig') do |r|
2600
+ @name = r.attributes['name']
2601
+ @format = r.attributes['format']
2602
+ @timezone = r.attributes['timezone']
2603
+ @id = r.attributes['id']
2604
+ @template_id = r.attributes['template-id']
2605
+ @owner = r.attributes['owner']
2606
+ end
2607
+ else
2608
+ @error = true
2609
+ @error_msg = 'Error ReportHistoryReponse'
2610
+ end
2611
+ end
2612
+
2613
+ # === Description
2614
+ # Generate a new report on this report definition. Returns the new report ID.
2615
+ def generateReport(debug = false)
2616
+ return generateReport(@connection, @config_id, debug)
2617
+ end
2618
+
2619
+ # === Description
2620
+ # Save the report definition to the NSC.
2621
+ # Returns the config-id.
2622
+ def saveReport()
2623
+ r = @connection.execute('<ReportSaveRequest session-id="' + @connection.session_id.to_s + '">' + getXML().to_s + ' </ReportSaveRequest>')
2624
+ if(r.success)
2625
+ @config_id = r.attributes['reportcfg-id']
2626
+ return true
2627
+ end
2628
+ return false
2629
+ end
2630
+
2631
+ # === Description
2632
+ # Adds a new filter to the report config
2633
+ def addFilter(filter_type, id)
2634
+ filter = ReportFilter.new(filter_type,id)
2635
+ @filters.push(filter)
2636
+ end
2637
+
2638
+ # === Description
2639
+ # Adds a new email recipient
2640
+ def addEmailRecipient(recipient)
2641
+ @email_recipients.push(recipient)
2642
+ end
2643
+
2644
+ # === Description
2645
+ # Sets the schedule for this report config
2646
+ def setSchedule(schedule)
2647
+ @schedule = schedule
2648
+ end
2649
+
2650
+ def getXML()
2651
+
2652
+ xml = '<ReportConfig id="' + @config_id.to_s + '" name="' + @name.to_s + '" template-id="' + @template_id.to_s + '" format="' + @format.to_s + '">'
2653
+
2654
+ xml += ' <Filters>'
2655
+
2656
+ @filters.each do |f|
2657
+ xml += ' <' + f.type.to_s + ' id="' + f.id.to_s + '"/>'
2658
+ end
2659
+
2660
+ xml += ' </Filters>'
2661
+
2662
+ xml += ' <Generate after-scan="' + @generate_after_scan.to_s + '">'
2663
+
2664
+ if (@schedule)
2665
+ xml += ' <Schedule type="' + @schedule.type.to_s + '" interval="' + @schedule.interval.to_s + '" start="' + @schedule.start.to_s + '"/>'
2666
+ end
2667
+
2668
+ xml += ' </Generate>'
2669
+
2670
+ xml += ' <Delivery>'
2671
+
2672
+ xml += ' <Storage storeOnServer="' + @storeOnServer.to_s + '">'
2673
+
2674
+ if (@store_location and @store_location.length > 0)
2675
+ xml += ' <location>' + @store_location.to_s + '</location>'
2676
+ end
2677
+
2678
+ xml += ' </Storage>'
2679
+
2680
+
2681
+ xml += ' </Delivery>'
2682
+
2683
+ xml += ' </ReportConfig>'
2684
+
2685
+ return xml
2686
+ end
2687
+
2688
+ def set_name(name)
2689
+ @name = name
2690
+ end
2691
+
2692
+ def set_template_id(template_id)
2693
+ @template_id = template_id
2694
+ end
2695
+
2696
+ def set_format(format)
2697
+ @format = format
2698
+ end
2699
+
2700
+ def set_email_As(email_As)
2701
+ @email_As = email_As
2702
+ end
2703
+
2704
+ def set_storeOnServer(storeOnServer)
2705
+ @storeOnServer = storeOnServer
2706
+ end
2707
+
2708
+ def set_smtp_relay_server(smtp_relay_server)
2709
+ @smtp_relay_server = smtp_relay_server
2710
+ end
2711
+
2712
+ def set_sender(sender)
2713
+ @sender = sender
2714
+ end
2715
+
2716
+ def set_generate_after_scan(generate_after_scan)
2717
+ @generate_after_scan = generate_after_scan
2718
+ end
2719
+ end
2720
+
2721
+ # === Description
2722
+ # Object that represents a report filter which determines which sites, asset
2723
+ # groups, and/or devices that a report is run against. gtypes are
2724
+ # "SiteFilter", "AssetGroupFilter", "DeviceFilter", or "ScanFilter". gid is
2725
+ # the site-id, assetgroup-id, or devce-id. ScanFilter, if used, specifies
2726
+ # a specifies a specific scan to use as the data source for the report. The gid
2727
+ # can be a specific scan-id or "first" for the first run scan, or “last” for
2728
+ # the last run scan.
2729
+ #
2730
+ class ReportFilter
2731
+
2732
+ attr_reader :type
2733
+ attr_reader :id
2734
+
2735
+ def initialize(type, id)
2736
+
2737
+ @type = type
2738
+ @id = id
2739
+
2740
+ end
2741
+
2742
+ end
2743
+
2744
+ # === Description
2745
+ # Object that represents the schedule on which to automatically generate new reports.
2746
+ #
2747
+ class ReportSchedule
2748
+
2749
+ # The type of schedule
2750
+ # (daily, hourly, monthly, weekly)
2751
+ attr_reader :type
2752
+ # The frequency with which to run the scan
2753
+ attr_reader :interval
2754
+ # The earliest date to generate the report
2755
+ attr_reader :start
2756
+
2757
+ def initialize(type, interval, start)
2758
+
2759
+ @type = type
2760
+ @interval = interval
2761
+ @start = start
2762
+
2763
+ end
2764
+
2765
+
2766
+ end
2767
+
2768
+ class ReportTemplateListing
2769
+
2770
+ attr_reader :error_msg
2771
+ attr_reader :error
2772
+ attr_reader :request_xml
2773
+ attr_reader :response_xml
2774
+ attr_reader :connection
2775
+ attr_reader :xml_tag_stack
2776
+ attr_reader :report_template_summaries#; //Array (ReportTemplateSummary*)
2777
+
2778
+
2779
+ def ReportTemplateListing(connection)
2780
+
2781
+ @error = nil
2782
+ @connection = connection
2783
+ @report_template_summaries = Array.new()
2784
+
2785
+ r = @connection.execute('<ReportTemplateListingRequest session-id="' + connection.session_id.to_s + '"/>')
2786
+ if (r.success)
2787
+ r.res.elements.each('ReportTemplateListingResponse/ReportTemplateSummary') do |r|
2788
+ @report_template_summaries.push(ReportTemplateSumary.new(r.attributes['id'],r.attributes['name']))
2789
+ end
2790
+ else
2791
+ @error = true
2792
+ @error_msg = 'ReportTemplateListingRequest Parse Error'
2793
+ end
2794
+
2795
+ end
2796
+
2797
+ end
2798
+
2799
+
2800
+ class ReportTemplateSummary
2801
+
2802
+ attr_reader :id
2803
+ attr_reader :name
2804
+ attr_reader :description
2805
+
2806
+ def ReportTemplateSummary(id, name, description)
2807
+
2808
+ @id = id
2809
+ @name = name
2810
+ @description = description
2811
+
2812
+ end
2813
+
2814
+ end
2815
+
2816
+
2817
+ class ReportSection
2818
+
2819
+ attr_reader :name
2820
+ attr_reader :properties
2821
+
2822
+ def ReportSection(name)
2823
+
2824
+ @properties = Array.new()
2825
+ @name = name
2826
+ end
2827
+
2828
+
2829
+ def addProperty(name, value)
2830
+
2831
+ @properties[name.to_s] = value
2832
+ end
2833
+
2834
+ end
2835
+
2836
+
2837
+ # TODO add
2838
+ def self.site_device_scan(connection, site_id, device_array, host_array, debug = false)
2839
+
2840
+ request_xml = '<SiteDevicesScanRequest session-id="' + connection.session_id.to_s + '" site-id="' + site_id.to_s + '">'
2841
+ request_xml += '<Devices>'
2842
+ device_array.each do |d|
2843
+ request_xml += '<device id="' + d.to_s + '"/>'
2844
+ end
2845
+ request_xml += '</Devices>'
2846
+ request_xml += '<Hosts>'
2847
+ # The host array can only by single IP addresses for now. TODO: Expand to full API Spec.
2848
+ host_array.each do |h|
2849
+ request_xml += '<range from="' + h.to_s + '"/>'
2850
+ end
2851
+ request_xml += '</Hosts>'
2852
+ request_xml += '</SiteDevicesScanRequest>'
2853
+
2854
+ r = connection.execute(request_xml)
2855
+ r.success ? {:engine_id => r.attributes['engine_id'], :scan_id => r.attributes['scan-id']} : nil
2856
+ end
2857
+
2858
+ # === Description
2859
+ # TODO
2860
+ def self.getAttribute(attribute, xml)
2861
+ value = ''
2862
+ #@value = substr(substr(strstr(strstr(@xml,@attribute),'"'),1),0,strpos(substr(strstr(strstr(@xml,@attribute),'"'),1),'"'))
2863
+ yvalue
2864
+ end
2865
+
2866
+ # === Description
2867
+ # Returns an ISO 8601 formatted date/time stamp. All dates in NeXpose must use this format.
2868
+ def self.get_iso_8601_date(int_date)
2869
+ #@date_mod = date('Ymd\THis000', @int_date)
2870
+ date_mod = ''
2871
+ return date_mod
2872
+ end
2873
+
2874
+ # ==== Description
2875
+ # Echos the last XML API request and response for the specified object. (Useful for debugging)
2876
+ def self.printXML(object)
2877
+ puts "request" + object.request_xml.to_s
2878
+ puts "response is " + object.response_xml.to_s
2879
+ end
2880
+
2881
+
2882
+ def self.testa(ip, port, user, passwd)
2883
+ nsc = Connection.new(ip, user, passwd, port)
2884
+
2885
+ nsc.login
2886
+ site_listing = SiteListing.new(nsc)
2887
+
2888
+ site_listing.sites.each do |site|
2889
+ puts "name is #{site.site_name}"
2890
+ puts "id is #{site.id}"
2891
+ end
2892
+ end
2893
+
2894
+
2895
+ end