nexpose 0.0.2 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. data/lib/nexpose.rb +192 -50
  2. metadata +24 -7
data/lib/nexpose.rb CHANGED
@@ -52,6 +52,7 @@ require 'rexml/document'
52
52
  require 'net/https'
53
53
  require 'net/http'
54
54
  require 'uri'
55
+ require 'rex/mime'
55
56
 
56
57
  module Nexpose
57
58
 
@@ -109,6 +110,9 @@ class APIRequest
109
110
  attr_reader :error
110
111
  attr_reader :trace
111
112
 
113
+ attr_reader :raw_response
114
+ attr_reader :raw_response_data
115
+
112
116
  def initialize(req, url)
113
117
  @url = url
114
118
  @req = req
@@ -139,8 +143,8 @@ class APIRequest
139
143
 
140
144
  begin
141
145
  prepare_http_client
142
- resp, data = @http.post(@uri.path, @req, @headers)
143
- @res = parse_xml(data)
146
+ @raw_response, @raw_response_data = @http.post(@uri.path, @req, @headers)
147
+ @res = parse_xml(@raw_response_data)
144
148
 
145
149
  if(not @res.root)
146
150
  @error = "NeXpose service returned invalid XML"
@@ -286,7 +290,10 @@ module NexposeAPI
286
290
  res[:summary][k] = res[:summary][k].to_i
287
291
  end
288
292
  end
289
- end
293
+ end
294
+ r.res.elements.each("//ScanSummary/message") do |message|
295
+ res[:message] = message.text
296
+ end
290
297
  return res
291
298
  else
292
299
  return false
@@ -338,6 +345,102 @@ module NexposeAPI
338
345
  r.success
339
346
  end
340
347
 
348
+ #-------------------------------------------------------------------------
349
+ # Returns all asset group information
350
+ #-------------------------------------------------------------------------
351
+ def asset_groups_listing()
352
+ r = execute(make_xml('AssetGroupListingRequest'))
353
+
354
+ if r.success
355
+ res = []
356
+ r.res.elements.each('//AssetGroupSummary') do |group|
357
+ res << {
358
+ :asset_group_id => group.attributes['id'].to_i,
359
+ :name => group.attributes['name'].to_s,
360
+ :description => group.attributes['description'].to_s,
361
+ :risk_score => group.attributes['riskscore'].to_f,
362
+ }
363
+ end
364
+ res
365
+ else
366
+ false
367
+ end
368
+ end
369
+
370
+ #-------------------------------------------------------------------------
371
+ # Returns an asset group configuration information for a specific group ID
372
+ #-------------------------------------------------------------------------
373
+ def asset_group_config(group_id)
374
+ r = execute(make_xml('AssetGroupConfigRequest', {'group-id' => group_id}))
375
+
376
+ if r.success
377
+ res = []
378
+ r.res.elements.each('//Devices/device') do |device_info|
379
+ res << {
380
+ :device_id => device_info.attributes['id'].to_i,
381
+ :site_id => device_info.attributes['site-id'].to_i,
382
+ :address => device_info.attributes['address'].to_s,
383
+ :riskfactor => device_info.attributes['riskfactor'].to_f,
384
+ }
385
+ end
386
+ res
387
+ else
388
+ false
389
+ end
390
+ end
391
+
392
+ #-----------------------------------------------------------------------
393
+ # Starts device specific site scanning.
394
+ #
395
+ # devices - An Array of device IDs
396
+ # hosts - An Array of Hashes [o]=>{:range=>"to,from"} [1]=>{:host=>host}
397
+ #-----------------------------------------------------------------------
398
+ def site_device_scan_start(site_id, devices, hosts)
399
+
400
+ if hosts == nil and devices == nil
401
+ raise ArgumentError.new("Both the device and host list is nil")
402
+ end
403
+
404
+ xml = make_xml('SiteDevicesScanRequest', {'site-id' => site_id})
405
+
406
+ if devices != nil
407
+ inner_xml = REXML::Element.new 'Devices'
408
+ for device_id in devices
409
+ inner_xml.add_element 'device', {'id' => "#{device_id}"}
410
+ end
411
+ xml.add_element inner_xml
412
+ end
413
+
414
+ if hosts != nil
415
+ inner_xml = REXML::Element.new 'Hosts'
416
+ hosts.each_index do |x|
417
+ if hosts[x].key? :range
418
+ to = hosts[x][:range].split(',')[0]
419
+ from = hosts[x][:range].split(',')[1]
420
+ inner_xml.add_element 'range', {'to' => "#{to}", 'from' => "#{from}"}
421
+ end
422
+ if hosts[x].key? :host
423
+ host_element = REXML::Element.new 'host'
424
+ host_element.text = "#{hosts[x][:host]}"
425
+ inner_xml.add_element host_element
426
+ end
427
+ end
428
+ xml.add_element inner_xml
429
+ end
430
+
431
+ r = execute xml
432
+ if r.success
433
+ r.res.elements.each('//Scan') do |scan_info|
434
+ return {
435
+ :scan_id => scan_info.attributes['scan-id'].to_i,
436
+ :engine_id => scan_info.attributes['engine-id'].to_i
437
+ }
438
+ end
439
+ else
440
+ false
441
+ end
442
+ end
443
+
341
444
  def site_delete(param)
342
445
  r = execute(make_xml('SiteDeleteRequest', { 'site-id' => param }))
343
446
  r.success
@@ -360,7 +463,30 @@ module NexposeAPI
360
463
  else
361
464
  return false
362
465
  end
363
- end
466
+ end
467
+
468
+ #-----------------------------------------------------------------------
469
+ # TODO: Needs to be expanded to included details
470
+ #-----------------------------------------------------------------------
471
+ def site_scan_history(site_id)
472
+ r.execute(make_xml('SiteScanHistoryRequest', {'site-id' => site_id.to_s}))
473
+
474
+ if (r.success)
475
+ res = []
476
+ r.res.elements.each("//ScanSummary") do |site_scan_history|
477
+ res << {
478
+ :site_id => site_scan_history.attributes['site-id'].to_i,
479
+ :scan_id => site_scan_history.attributes['scan-id'].to_i,
480
+ :engine_id => site_scan_history.attributes['engine-id'].to_i,
481
+ :start_time => site_scan_history.attributes['startTime'].to_s,
482
+ :end_time => site_scan_history.attributes['endTime'].to_s
483
+ }
484
+ end
485
+ return res
486
+ else
487
+ false
488
+ end
489
+ end
364
490
 
365
491
  def site_device_listing(site_id)
366
492
  r = execute(make_xml('SiteDeviceListingRequest', { 'site-id' => site_id.to_s }))
@@ -1962,61 +2088,76 @@ end
1962
2088
 
1963
2089
  # === Description
1964
2090
  #
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')
2091
+ class ReportAdHoc
2092
+ include XMLUtils
2093
+
2094
+ attr_reader :error
2095
+ attr_reader :error_msg
2096
+ attr_reader :connection
2097
+ # Report Template ID strong e.g. full-audit
2098
+ attr_reader :template_id
2099
+ # pdf|html|xml|text|csv|raw-xml
2100
+ attr_reader :format
2101
+ # Array of (ReportFilter)*
2102
+ attr_reader :filters
2103
+ attr_reader :request_xml
2104
+ attr_reader :response_xml
2105
+ attr_reader :report_decoded
2106
+
2107
+
2108
+ def initialize(connection, template_id = 'full-audit', format = 'raw-xml')
2109
+
2110
+ @error = false
2111
+ @connection = connection
2112
+ @filters = Array.new()
2113
+ @template_id = template_id
2114
+ @format = format
1982
2115
 
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)
2116
+ end
1993
2117
 
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)
2118
+ def addFilter(filter_type, id)
1998
2119
 
1999
- end
2120
+ # filter_type can be site|group|device|scan
2121
+ # id is the ID number. For scan, you can use 'last' for the most recently run scan
2122
+ filter = ReportFilter.new(filter_type, id)
2123
+ filters.push(filter)
2000
2124
 
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
2125
  end
2008
- request_xml += '</Filters>'
2009
- request_xml += '</AdhocReportConfig>'
2010
- request_xml += '</ReportAdhocGenerateRequest>'
2011
2126
 
2012
- myReportAdHoc_request = APIRequest.new(request_xml, @connection.geturl())
2013
- myReportAdHoc_request.execute()
2127
+ def generate()
2128
+ request_xml = '<ReportAdhocGenerateRequest session-id="' + @connection.session_id + '">'
2129
+ request_xml += '<AdhocReportConfig template-id="' + @template_id + '" format="' + @format + '">'
2130
+ request_xml += '<Filters>'
2131
+ @filters.each do |f|
2132
+ request_xml += '<filter type="' + f.type + '" id="'+ f.id.to_s + '"/>'
2133
+ end
2134
+ request_xml += '</Filters>'
2135
+ request_xml += '</AdhocReportConfig>'
2136
+ request_xml += '</ReportAdhocGenerateRequest>'
2137
+
2138
+ ad_hoc_request = APIRequest.new(request_xml, @connection.url)
2139
+ ad_hoc_request.execute()
2140
+
2141
+ content_type_response = ad_hoc_request.raw_response.header['Content-Type']
2142
+ if content_type_response =~ /multipart\/mixed;\s*boundary=([^\s]+)/
2143
+ # NeXpose sends an incorrect boundary format which breaks parsing
2144
+ # Eg: boundary=XXX; charset=XXX
2145
+ # Fix by removing everything from the last semi-colon onward
2146
+ last_semi_colon_index = content_type_response.index(/;/, content_type_response.index(/boundary/))
2147
+ content_type_response = content_type_response[0, last_semi_colon_index]
2148
+
2149
+ data = "Content-Type: " + content_type_response + "\r\n\r\n" + ad_hoc_request.raw_response_data
2150
+ doc = Rex::MIME::Message.new data
2151
+ doc.parts.each do |part|
2152
+ if /.*base64.*/ =~ part.header.to_s
2153
+ return parse_xml(part.content.unpack("m*")[0])
2154
+ end
2155
+ end
2156
+ end
2157
+ end
2014
2158
 
2015
- myReportAdHoc_response = myReportAdHoc_request.response_xml
2016
2159
  end
2017
2160
 
2018
- end
2019
-
2020
2161
  # === Description
2021
2162
  # Object that represents the configuration of a report definition.
2022
2163
  #
@@ -2108,6 +2249,7 @@ class ReportConfig
2108
2249
  def generateReport(debug = false)
2109
2250
  return generateReport(@connection, @config_id, debug)
2110
2251
  end
2252
+
2111
2253
  # === Description
2112
2254
  # Save the report definition to the NSC.
2113
2255
  # Returns the config-id.
metadata CHANGED
@@ -1,27 +1,44 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nexpose
3
3
  version: !ruby/object:Gem::Version
4
- hash: 27
4
+ hash: 25
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 0
9
- - 2
10
- version: 0.0.2
9
+ - 3
10
+ version: 0.0.3
11
11
  platform: ruby
12
12
  authors:
13
13
  - HD Moore
14
+ - Chris Lee
14
15
  autorequire:
15
16
  bindir: bin
16
17
  cert_chain: []
17
18
 
18
- date: 2011-04-25 00:00:00 -05:00
19
+ date: 2011-05-23 00:00:00 -05:00
19
20
  default_executable:
20
- dependencies: []
21
-
22
- description: This gem provides a Ruby API to the NeXpose vulnerability management product by Rapid7. This version is based on Metasploit SVN revision 12430
21
+ dependencies:
22
+ - !ruby/object:Gem::Dependency
23
+ name: librex
24
+ prerelease: false
25
+ requirement: &id001 !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ">="
29
+ - !ruby/object:Gem::Version
30
+ hash: 95
31
+ segments:
32
+ - 0
33
+ - 0
34
+ - 32
35
+ version: 0.0.32
36
+ type: :runtime
37
+ version_requirements: *id001
38
+ description: This gem provides a Ruby API to the NeXpose vulnerability management product by Rapid7. This version is based on Metasploit SVN revision 12696
23
39
  email:
24
40
  - hdm@metasploit.com
41
+ - chris.lee@rapid7.com
25
42
  executables: []
26
43
 
27
44
  extensions: []