nexpose 0.8.4 → 0.8.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/nexpose.rb +8 -0
- data/lib/nexpose/discovery.rb +43 -5
- data/lib/nexpose/scan.rb +20 -43
- data/lib/nexpose/site.rb +63 -19
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a14fda65d152d074a2adba56c7180417b04c9569
|
4
|
+
data.tar.gz: 6e349f9013e13f8b2972ada209543dee513d612a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: aaf259cadc6172ca32f9a07d6a15240dab9c0baf0f565ffe070c16472c7d3c0768ae86850cc4850eaf9221bf292d00f9c5619566fa62f447e83289e930ef0b04
|
7
|
+
data.tar.gz: a7b6825ce9cd0898acdd6784e106c7a3823c88ec1bf227ccd13de18d0355132aea1532d14cf3b458af9f412bb7561a9f900d02715a745edaab7b9f9f6e9724f0
|
data/lib/nexpose.rb
CHANGED
@@ -103,3 +103,11 @@ module Nexpose
|
|
103
103
|
puts 'response: ' + object.response_xml.to_s
|
104
104
|
end
|
105
105
|
end
|
106
|
+
|
107
|
+
# Monkey patch from ActiveSupport which rex 2.0.3 incorrectly replies upon.
|
108
|
+
# This enables the multipart MIME handling in Connection#import_scan
|
109
|
+
class String
|
110
|
+
def blank?
|
111
|
+
self !~ /\S/
|
112
|
+
end
|
113
|
+
end
|
data/lib/nexpose/discovery.rb
CHANGED
@@ -35,6 +35,11 @@ module Nexpose
|
|
35
35
|
HTTPS = 'HTTPS'
|
36
36
|
end
|
37
37
|
|
38
|
+
module Type
|
39
|
+
V_SPHERE = 'VSPHERE'
|
40
|
+
AWS = 'AWS'
|
41
|
+
end
|
42
|
+
|
38
43
|
# A unique identifier for this connection.
|
39
44
|
attr_accessor :id
|
40
45
|
|
@@ -42,6 +47,7 @@ module Nexpose
|
|
42
47
|
attr_accessor :name
|
43
48
|
|
44
49
|
# The IP address or fully qualified domain name of the server.
|
50
|
+
# For AWS connections, this is the address of the regional server.
|
45
51
|
attr_accessor :address
|
46
52
|
|
47
53
|
# A user name that can be used to log into the server.
|
@@ -56,6 +62,12 @@ module Nexpose
|
|
56
62
|
# The port used for connecting to the server. A valid port from 1 to 65535.
|
57
63
|
attr_accessor :port
|
58
64
|
|
65
|
+
# The type of discovery connection.
|
66
|
+
attr_accessor :type
|
67
|
+
|
68
|
+
# Whether the console and scan engine are inside the AWS network.
|
69
|
+
attr_accessor :inside_network
|
70
|
+
|
59
71
|
# Whether or not the connection is active.
|
60
72
|
# Discovery is only possible when the connection is active.
|
61
73
|
attr_accessor :status
|
@@ -73,6 +85,7 @@ module Nexpose
|
|
73
85
|
@id = -1
|
74
86
|
@port = 443
|
75
87
|
@protocol = Protocol::HTTPS
|
88
|
+
@type = Type::V_SPHERE
|
76
89
|
end
|
77
90
|
|
78
91
|
# Save this discovery connection to a Nexpose console.
|
@@ -130,13 +143,35 @@ module Nexpose
|
|
130
143
|
end
|
131
144
|
|
132
145
|
def as_xml
|
146
|
+
# TODO
|
147
|
+
# <DiscoveryConfig id="-1" name="aws" port="443" protocol="HTTPS" username="asdf" type="AWS" usePrivateIPAddress="0" engineid="2" region="ec2.us-west-1.amazonaws.com" password="asdf"> </DiscoveryConfig>
|
148
|
+
# <DiscoveryConfig id="-1" name="external AWS" port="443" protocol="HTTPS" username="acess-key-id" type="AWS" usePrivateIPAddress="0" engineid="2" region="ec2.sa-east-1.amazonaws.com" password="secret Access Key">
|
149
|
+
# <DiscoveryConfig id="-1" name="internal-aws" port="443" protocol="HTTPS" username=" " type="AWS" usePrivateIPAddress="1" engineid="2" region="ec2.ap-northeast-1.amazonaws.com" password="Not required">
|
150
|
+
# <DiscoveryConfig id="-1" name="V the Final Battle" fqdn="vcenter001.osdc.lax.rapid7.com" port="443" protocol="HTTPS" username="TOR\lmnexpose" type="VSPHERE" usePrivateIPAddress="0" engineid="2" region="ec2.us-east-1.amazonaws.com" password="22yQCA0P8nSvfrE">
|
151
|
+
# <DiscoveryConfig id="-1" name="duplicate vSphere" fqdn="vcenter001.osdc.lax.rapid7.com" port="443" protocol="HTTPS" username="blah" type="VSPHERE" usePrivateIPAddress="0" engineid="2" region="ec2.us-east-1.amazonaws.com" password="blah">
|
152
|
+
# <DiscoveryConfig id="10" name ="vCenter" fqdn ="vcenter001.osdc.lax.rapid7.com" port ="443" protocol ="HTTPS" username ="TOR\lmnexpose" type="VSPHERE" usePrivateIPAddress="0"> </DiscoveryConfig>
|
133
153
|
xml = REXML::Element.new('DiscoveryConnection')
|
134
|
-
xml.add_attributes({ '
|
135
|
-
'
|
154
|
+
xml.add_attributes({ 'id' => @id,
|
155
|
+
'name' => @name,
|
136
156
|
'port' => @port,
|
137
|
-
'protocol' => @protocol
|
138
|
-
|
139
|
-
|
157
|
+
'protocol' => @protocol })
|
158
|
+
case @type
|
159
|
+
when Type::AWS
|
160
|
+
xml.add_attributes({ 'region' => @address })
|
161
|
+
if @inside_network
|
162
|
+
xml.add_attributes({ 'usePrivateIPAddress' => 1,
|
163
|
+
'username' => ' ',
|
164
|
+
'password' => 'Not required' })
|
165
|
+
else
|
166
|
+
xml.add_attributes({ 'usePrivateIPAddress' => 0,
|
167
|
+
'username' => @user,
|
168
|
+
'password' => @password })
|
169
|
+
end
|
170
|
+
when Type::V_SPHERE
|
171
|
+
xml.add_attributes({ 'fqdn' => @address,
|
172
|
+
'username' => @user,
|
173
|
+
'password' => @password })
|
174
|
+
end
|
140
175
|
xml
|
141
176
|
end
|
142
177
|
|
@@ -145,6 +180,9 @@ module Nexpose
|
|
145
180
|
end
|
146
181
|
|
147
182
|
def self.parse(xml)
|
183
|
+
# <DiscoveryConfig id="10" name ="vCenter" fqdn ="vcenter001.osdc.lax.rapid7.com" port ="443" protocol ="HTTPS" username ="TOR\lmnexpose" type="VSPHERE" usePrivateIPAddress="0"> </DiscoveryConfig>
|
184
|
+
# <DiscoveryConfig id="34" name ="external AWS" region ="ec2.sa-east-1.amazonaws.com" port ="443" protocol ="HTTPS" username ="acess-key-id" type="AWS" usePrivateIPAddress="0">
|
185
|
+
# <DiscoveryConfig id="32" name ="my-aws" region ="ec2.ap-northeast-1.amazonaws.com" port ="443" protocol ="HTTPS" username =" " type="AWS" usePrivateIPAddress="1">
|
148
186
|
conn = new(xml.attributes['name'],
|
149
187
|
xml.attributes['address'],
|
150
188
|
xml.attributes['user-name'])
|
data/lib/nexpose/scan.rb
CHANGED
@@ -256,51 +256,28 @@ module Nexpose
|
|
256
256
|
# @return [String] An empty string on success.
|
257
257
|
#
|
258
258
|
def import_scan(site_id, zip_file)
|
259
|
+
data = Rex::MIME::Message.new
|
260
|
+
data.add_part(site_id.to_s, nil, nil, 'form-data; name="siteid"')
|
261
|
+
data.add_part(self.session_id, nil, nil, 'form-data; name="nexposeCCSessionID"')
|
262
|
+
scan = File.new(zip_file, 'rb')
|
263
|
+
data.add_part(scan.read, 'application/zip', 'binary',
|
264
|
+
"form-data; name=\"scan\"; filename=\"#{zip_file}\"")
|
259
265
|
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
# # pending discovery of what to do.
|
264
|
-
|
265
|
-
# data = Rex::MIME::Message.new
|
266
|
-
# data.add_part(site_id.to_s, nil, nil, 'form-data; name="siteid"')
|
267
|
-
# data.add_part(self.session_id, nil, nil, 'form-data; name="nexposeCCSessionID"')
|
268
|
-
|
269
|
-
# scan = File.new(zip_file, 'rb')
|
270
|
-
# data.add_part(scan.read, 'application/zip', nil,
|
271
|
-
# "form-data; name=\"scan\"; filename=\"#{zip_file}\"")
|
272
|
-
|
273
|
-
# post = Net::HTTP::Post.new('/data/scan/import')
|
274
|
-
# ## rex 2.0.3 has a bug that requires this monkey-patch for Message#to_s
|
275
|
-
# # class String
|
276
|
-
# # def blank?
|
277
|
-
# # self !~ /\S/
|
278
|
-
# # end
|
279
|
-
# # end
|
280
|
-
# post.body = data.to_s
|
281
|
-
# post.set_content_type("multipart/form-data; boundary=#{data.bound}")
|
282
|
-
# AJAX._headers(nsc, post)
|
283
|
-
|
284
|
-
# http = AJAX._https(nsc)
|
285
|
-
# http.request(post)
|
266
|
+
post = Net::HTTP::Post.new('/data/scan/import')
|
267
|
+
post.body = data.to_s
|
268
|
+
post.set_content_type('multipart/form-data', boundary: data.bound)
|
286
269
|
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
begin
|
299
|
-
request.execute
|
300
|
-
rescue RestClient::Forbidden => fourOhThree
|
301
|
-
raise Nexpose::PermissionError.new(fourOhThree)
|
302
|
-
rescue RestClient::InternalServerError => e
|
303
|
-
raise Nexpose::APIError.new(request, e)
|
270
|
+
# Avoiding AJAX#request, because the data can cause binary dump on error.
|
271
|
+
http = AJAX._https(self)
|
272
|
+
AJAX._headers(self, post)
|
273
|
+
response = http.request(post)
|
274
|
+
case response
|
275
|
+
when Net::HTTPOK
|
276
|
+
response.body
|
277
|
+
when Net::HTTPUnauthorized
|
278
|
+
raise Nexpose::PermissionError.new(response)
|
279
|
+
else
|
280
|
+
raise Nexpose::APIError.new(post, response.body)
|
304
281
|
end
|
305
282
|
end
|
306
283
|
|
data/lib/nexpose/site.rb
CHANGED
@@ -174,6 +174,13 @@ module Nexpose
|
|
174
174
|
@assets << HostName.new(hostname)
|
175
175
|
end
|
176
176
|
|
177
|
+
# Remove an asset to this site by host name.
|
178
|
+
#
|
179
|
+
# @param [String] hostname FQDN or DNS-resolvable host name of an asset.
|
180
|
+
def remove_host(hostname)
|
181
|
+
@assets = assets.reject { |asset| asset == HostName.new(hostname) }
|
182
|
+
end
|
183
|
+
|
177
184
|
# Adds an asset to this site by IP address.
|
178
185
|
#
|
179
186
|
# @param [String] ip IP address of an asset.
|
@@ -181,6 +188,13 @@ module Nexpose
|
|
181
188
|
@assets << IPRange.new(ip)
|
182
189
|
end
|
183
190
|
|
191
|
+
# Remove an asset to this site by IP address.
|
192
|
+
#
|
193
|
+
# @param [String] ip IP address of an asset.
|
194
|
+
def remove_ip(ip)
|
195
|
+
@assets = assets.reject { |asset| asset == IPRange.new(ip) }
|
196
|
+
end
|
197
|
+
|
184
198
|
# Adds assets to this site by IP address range.
|
185
199
|
#
|
186
200
|
# @param [String] from Beginning IP address of a range.
|
@@ -189,6 +203,14 @@ module Nexpose
|
|
189
203
|
@assets << IPRange.new(from, to)
|
190
204
|
end
|
191
205
|
|
206
|
+
# Remove assets to this site by IP address range.
|
207
|
+
#
|
208
|
+
# @param [String] from Beginning IP address of a range.
|
209
|
+
# @param [String] to Ending IP address of a range.
|
210
|
+
def remove_ip_range(from, to)
|
211
|
+
@assets = assets.reject { |asset| asset == IPRange.new(from, to) }
|
212
|
+
end
|
213
|
+
|
192
214
|
# Adds an asset to this site, resolving whether an IP or hostname is
|
193
215
|
# provided.
|
194
216
|
#
|
@@ -208,6 +230,25 @@ module Nexpose
|
|
208
230
|
end
|
209
231
|
end
|
210
232
|
|
233
|
+
# Remove an asset to this site, resolving whether an IP or hostname is
|
234
|
+
# provided.
|
235
|
+
#
|
236
|
+
# @param [String] asset Identifier of an asset, either IP or host name.
|
237
|
+
#
|
238
|
+
def remove_asset(asset)
|
239
|
+
begin
|
240
|
+
# If the asset registers as a valid IP, store as IP.
|
241
|
+
ip = IPAddr.new(asset)
|
242
|
+
remove_ip(asset)
|
243
|
+
rescue ArgumentError => e
|
244
|
+
if e.message == 'invalid address'
|
245
|
+
remove_host(asset)
|
246
|
+
else
|
247
|
+
raise "Unable to parse asset: '#{asset}'. #{e.message}"
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
211
252
|
# Load an existing configuration from a Nexpose instance.
|
212
253
|
#
|
213
254
|
# @param [Connection] connection Connection to console where site exists.
|
@@ -253,8 +294,8 @@ module Nexpose
|
|
253
294
|
|
254
295
|
# Have to retrieve and attach shared creds, or saving will fail.
|
255
296
|
xml = _append_shared_creds_to_xml(connection, as_xml)
|
256
|
-
response = AJAX.post(connection, '/
|
257
|
-
saved = REXML::XPath.first(REXML::Document.new(response), '
|
297
|
+
response = AJAX.post(connection, '/data/site/config', xml)
|
298
|
+
saved = REXML::XPath.first(REXML::Document.new(response), 'ajaxResponse')
|
258
299
|
raise APIError.new(response, 'Failed to save dynamic site.') if saved.nil? || saved.attributes['success'].to_i != 1
|
259
300
|
|
260
301
|
save_dynamic_criteria(connection) unless new_site
|
@@ -297,8 +338,22 @@ module Nexpose
|
|
297
338
|
# @return [Fixnum] Site ID.
|
298
339
|
#
|
299
340
|
def save_dynamic_criteria(nsc)
|
300
|
-
|
301
|
-
|
341
|
+
# Several parameters are passed through the URI
|
342
|
+
params = { 'configID' => @discovery_connection_id,
|
343
|
+
'entityid' => @id > 0 ? @id : false,
|
344
|
+
'mode' => @id > 0 ? 'edit' : false }
|
345
|
+
uri = AJAX.parameterize_uri('/data/site/saveSite', params)
|
346
|
+
|
347
|
+
# JSON body of POST request contains details.
|
348
|
+
details = { 'dynamic' => true,
|
349
|
+
'name' => @name,
|
350
|
+
'tag' => @description.nil? ? '' : @description,
|
351
|
+
'riskFactor' => @risk_factor,
|
352
|
+
# 'vCenter' => @discovery_connection_id,
|
353
|
+
'searchCriteria' => @criteria.nil? ? { 'operator' => 'AND' } : @criteria.to_map }
|
354
|
+
json = JSON.generate(details)
|
355
|
+
|
356
|
+
response = AJAX.post(nsc, uri, json, AJAX::CONTENT_TYPE::JSON)
|
302
357
|
json = JSON.parse(response)
|
303
358
|
if json['response'] =~ /success/
|
304
359
|
if @id < 1
|
@@ -335,7 +390,9 @@ module Nexpose
|
|
335
390
|
xml.attributes['name'] = @name
|
336
391
|
xml.attributes['description'] = @description
|
337
392
|
xml.attributes['riskfactor'] = @risk_factor
|
338
|
-
xml.attributes['isDynamic']
|
393
|
+
xml.attributes['isDynamic'] = '1' if dynamic?
|
394
|
+
# TODO This should be set to 'Amazon Web Services' for AWS.
|
395
|
+
xml.attributes['dynamicConfigType'] = 'vSphere' if dynamic?
|
339
396
|
|
340
397
|
if @description && !@description.empty?
|
341
398
|
elem = REXML::Element.new('Description')
|
@@ -394,19 +451,6 @@ module Nexpose
|
|
394
451
|
as_xml.to_s
|
395
452
|
end
|
396
453
|
|
397
|
-
def to_dynamic_map
|
398
|
-
details = { 'dynamic' => true,
|
399
|
-
'name' => @name,
|
400
|
-
'tag' => @description.nil? ? '' : @description,
|
401
|
-
'riskFactor' => @risk_factor,
|
402
|
-
# 'vCenter' => @discovery_connection_id,
|
403
|
-
'searchCriteria' => @criteria.nil? ? { 'operator' => 'AND' } : @criteria.to_map }
|
404
|
-
params = { 'configID' => @discovery_connection_id,
|
405
|
-
'entityid' => @id > 0 ? @id : false,
|
406
|
-
'mode' => @id > 0 ? 'edit' : false,
|
407
|
-
'entityDetails' => details }
|
408
|
-
end
|
409
|
-
|
410
454
|
# Parse a response from a Nexpose console into a valid Site object.
|
411
455
|
#
|
412
456
|
# @param [REXML::Document] rexml XML document to parse.
|
@@ -475,7 +519,7 @@ module Nexpose
|
|
475
519
|
end
|
476
520
|
|
477
521
|
def _append_shared_creds_to_xml(connection, xml)
|
478
|
-
xml_w_creds = AJAX.get(connection, "/
|
522
|
+
xml_w_creds = AJAX.get(connection, "/data/site/config?siteid=#{@id}")
|
479
523
|
cred_xml = REXML::XPath.first(REXML::Document.new(xml_w_creds), 'Site/Credentials')
|
480
524
|
unless cred_xml.nil?
|
481
525
|
creds = REXML::XPath.first(xml, 'Credentials')
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: nexpose
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.8.
|
4
|
+
version: 0.8.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- HD Moore
|
@@ -11,7 +11,7 @@ authors:
|
|
11
11
|
autorequire:
|
12
12
|
bindir: bin
|
13
13
|
cert_chain: []
|
14
|
-
date: 2014-10-
|
14
|
+
date: 2014-10-09 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: rex
|