nexpose 0.0.1

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