nexpose 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
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: []