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.
- data/lib/nexpose.rb +192 -50
- 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
|
-
|
143
|
-
@res = parse_xml(
|
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
|
-
|
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
|
-
|
1968
|
-
|
1969
|
-
|
1970
|
-
|
1971
|
-
|
1972
|
-
|
1973
|
-
|
1974
|
-
|
1975
|
-
|
1976
|
-
|
1977
|
-
|
1978
|
-
|
1979
|
-
|
1980
|
-
|
1981
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
2013
|
-
|
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:
|
4
|
+
hash: 25
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 0
|
9
|
-
-
|
10
|
-
version: 0.0.
|
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-
|
19
|
+
date: 2011-05-23 00:00:00 -05:00
|
19
20
|
default_executable:
|
20
|
-
dependencies:
|
21
|
-
|
22
|
-
|
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: []
|