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