nexpose 0.8.9 → 0.8.10
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.
- checksums.yaml +4 -4
- data/lib/nexpose/ajax.rb +31 -0
- data/lib/nexpose/device.rb +65 -1
- data/lib/nexpose/discovery.rb +38 -6
- data/lib/nexpose/discovery/filter.rb +9 -0
- data/lib/nexpose/scan.rb +44 -2
- data/lib/nexpose/site.rb +17 -9
- metadata +2 -3
- data/lib/nexpose/settings.xml +0 -14
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 81928c1e3298ba80b864e0d14499f58a9a41eae2
|
4
|
+
data.tar.gz: dcbe6c5b9df95b254fa98f3eb5ec2041492d389a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: aa9189178c6c2d497635cc871473f78277abe75374bcf32ac883303463b079b73bc5bdacdac0d8292fcfbd0d326f1d6754235fe2b3ab8d19dcbb260c651cb09e
|
7
|
+
data.tar.gz: 7fc4dc7d5a376c7a6e1a0f440e48b532cb73acb1c4470973528fb228be3d8ccbfb60d1d6acd55ce9c1ee00d3aed91dea79b691b484cfddd3bd0374bef0a5690e
|
data/lib/nexpose/ajax.rb
CHANGED
@@ -119,6 +119,15 @@ module Nexpose
|
|
119
119
|
uri
|
120
120
|
end
|
121
121
|
|
122
|
+
def preserving_preference(nsc, pref)
|
123
|
+
begin
|
124
|
+
orig = _get_rows(nsc, pref)
|
125
|
+
yield
|
126
|
+
ensure
|
127
|
+
_set_rows(nsc, pref, orig)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
122
131
|
###
|
123
132
|
# Internal helper methods
|
124
133
|
|
@@ -154,5 +163,27 @@ module Nexpose
|
|
154
163
|
raise Nexpose::APIError.new(response, "#{req_type} request to #{request.path} failed. #{request.body}")
|
155
164
|
end
|
156
165
|
end
|
166
|
+
|
167
|
+
def _get_rows(nsc, pref)
|
168
|
+
uri = '/ajax/user_pref_get.txml'
|
169
|
+
resp = get(nsc, uri, CONTENT_TYPE::XML, 'name' => "#{pref}.rows")
|
170
|
+
xml = REXML::Document.new(resp)
|
171
|
+
if val = REXML::XPath.first(xml, 'GetUserPref/userPref')
|
172
|
+
val.text.to_i
|
173
|
+
else
|
174
|
+
10
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def _set_rows(nsc, pref, value)
|
179
|
+
uri = '/ajax/user_pref_set.txml'
|
180
|
+
params = { 'name' => "#{pref}.rows",
|
181
|
+
'value' => value }
|
182
|
+
resp = get(nsc, uri, CONTENT_TYPE::XML, params)
|
183
|
+
xml = REXML::Document.new(resp)
|
184
|
+
if attr = REXML::XPath.first(xml, 'SetUserPref/@success')
|
185
|
+
attr.value == '1'
|
186
|
+
end
|
187
|
+
end
|
157
188
|
end
|
158
189
|
end
|
data/lib/nexpose/device.rb
CHANGED
@@ -93,6 +93,22 @@ module Nexpose
|
|
93
93
|
alias_method :asset_vulns, :list_device_vulns
|
94
94
|
alias_method :device_vulns, :list_device_vulns
|
95
95
|
|
96
|
+
# Retrieve a list of assets which completed in a given scan. If called
|
97
|
+
# during a scan, this method returns currently completed assets. A
|
98
|
+
# "completed" asset can be in one of three states: completed successfully,
|
99
|
+
# failed due to an error, or stopped by a user.
|
100
|
+
#
|
101
|
+
# @param [Fixnum] scan_id Unique identifier of a scan.
|
102
|
+
# @return [Array[CompletedAsset]] List of completed assets.
|
103
|
+
#
|
104
|
+
def completed_assets(scan_id)
|
105
|
+
uri = "/data/asset/scan/#{scan_id}/complete-assets"
|
106
|
+
AJAX.preserving_preference(self, 'scan-complete-assets') do
|
107
|
+
data = DataTable._get_json_table(self, uri, {}, 500, nil, false)
|
108
|
+
data.map(&CompletedAsset.method(:parse_json))
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
96
112
|
def delete_device(device_id)
|
97
113
|
r = execute(make_xml('DeviceDeleteRequest', { 'device-id' => device_id }))
|
98
114
|
r.success
|
@@ -104,7 +120,6 @@ module Nexpose
|
|
104
120
|
# Object that represents a single device in a Nexpose security console.
|
105
121
|
#
|
106
122
|
class Device
|
107
|
-
|
108
123
|
# A unique device ID (assigned automatically by the Nexpose console).
|
109
124
|
attr_reader :id
|
110
125
|
# IP Address or Hostname of this device.
|
@@ -124,4 +139,53 @@ module Nexpose
|
|
124
139
|
@risk_score = risk_score.to_f
|
125
140
|
end
|
126
141
|
end
|
142
|
+
|
143
|
+
# Summary object of a completed asset for a scan.
|
144
|
+
#
|
145
|
+
class CompletedAsset
|
146
|
+
# Unique identifier of an asset.
|
147
|
+
attr_reader :id
|
148
|
+
# IP address of the asset.
|
149
|
+
attr_reader :ip
|
150
|
+
# Host name of the asset, if discovered.
|
151
|
+
attr_reader :host_name
|
152
|
+
# Operating system fingerprint of the asset.
|
153
|
+
attr_reader :os
|
154
|
+
# Number of vulnerabilities discovered on the asset.
|
155
|
+
attr_reader :vulns
|
156
|
+
# Status of the asset on scan completion.
|
157
|
+
# One of :completed, :error, or :stopped.
|
158
|
+
attr_reader :status
|
159
|
+
# Time it took to scan the asset, in milliseconds.
|
160
|
+
attr_reader :duration
|
161
|
+
|
162
|
+
# Internal constructor to be called by #parse_json.
|
163
|
+
def initialize(&block)
|
164
|
+
instance_eval(&block) if block_given?
|
165
|
+
end
|
166
|
+
|
167
|
+
# Convenience method for assessing "ip" as "ip_address".
|
168
|
+
def ip_address
|
169
|
+
ip
|
170
|
+
end
|
171
|
+
|
172
|
+
# Convenience method for assessing "os" as "operating_system".
|
173
|
+
def operating_system
|
174
|
+
os
|
175
|
+
end
|
176
|
+
|
177
|
+
# Internal method for converting a JSON representation into a CompletedScan
|
178
|
+
# object.
|
179
|
+
def self.parse_json(json)
|
180
|
+
new do
|
181
|
+
@id = json['id'].to_i
|
182
|
+
@ip = json['ipAddress']
|
183
|
+
@host_name = json['hostName']
|
184
|
+
@os = json['operatingSystem']
|
185
|
+
@vulns = json['vulnerabilityCount']
|
186
|
+
@status = json['scanStatusTranslation'].downcase.to_sym
|
187
|
+
@duration = json['duration']
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
127
191
|
end
|
data/lib/nexpose/discovery.rb
CHANGED
@@ -33,6 +33,14 @@ module Nexpose
|
|
33
33
|
module Protocol
|
34
34
|
HTTP = 'HTTP'
|
35
35
|
HTTPS = 'HTTPS'
|
36
|
+
LDAP = 'LDAP'
|
37
|
+
LDAPS = 'LDAPS'
|
38
|
+
end
|
39
|
+
|
40
|
+
module Type
|
41
|
+
VSPHERE = 'VSPHERE'
|
42
|
+
AWS = 'AWS'
|
43
|
+
ACTIVESYNC = 'ACTIVESYNC'
|
36
44
|
end
|
37
45
|
|
38
46
|
# A unique identifier for this connection.
|
@@ -41,6 +49,9 @@ module Nexpose
|
|
41
49
|
# A unique name for this connection.
|
42
50
|
attr_accessor :name
|
43
51
|
|
52
|
+
# Type of discovery connection
|
53
|
+
attr_accessor :type
|
54
|
+
|
44
55
|
# The IP address or fully qualified domain name of the server.
|
45
56
|
attr_accessor :address
|
46
57
|
|
@@ -70,6 +81,7 @@ module Nexpose
|
|
70
81
|
#
|
71
82
|
def initialize(name, address, user, password = nil)
|
72
83
|
@name, @address, @user, @password = name, address, user, password
|
84
|
+
@type = nil # for backwards compatibilitly, at some point should set this to Type::VSPHERE
|
73
85
|
@id = -1
|
74
86
|
@port = 443
|
75
87
|
@protocol = Protocol::HTTPS
|
@@ -131,12 +143,13 @@ module Nexpose
|
|
131
143
|
|
132
144
|
def as_xml
|
133
145
|
xml = REXML::Element.new('DiscoveryConnection')
|
134
|
-
xml.
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
146
|
+
xml.attributes['name'] = @name
|
147
|
+
xml.attributes['address'] = @address
|
148
|
+
xml.attributes['port'] = @port
|
149
|
+
xml.attributes['protocol'] = @protocol
|
150
|
+
xml.attributes['user-name'] = @user
|
151
|
+
xml.attributes['password'] = @password
|
152
|
+
xml.attributes['type'] = @type if @type
|
140
153
|
xml
|
141
154
|
end
|
142
155
|
|
@@ -188,4 +201,23 @@ module Nexpose
|
|
188
201
|
end
|
189
202
|
end
|
190
203
|
end
|
204
|
+
|
205
|
+
class MobileDiscoveryConnection < DiscoveryConnection
|
206
|
+
# Create a new Mobile discovery connection.
|
207
|
+
#
|
208
|
+
# @param [String] name Name to assign to this connection.
|
209
|
+
# @param [DiscoveryConnection::Protocol] protocol The protocol to use for discovery - LDAPS or LDAP
|
210
|
+
# @param [String] address IP or fully qualified domain name of the
|
211
|
+
# connection server.
|
212
|
+
# @param [String] user User name for credentials on this connection.
|
213
|
+
# @param [String] password Password for credentials on this connection.
|
214
|
+
#
|
215
|
+
def initialize(name, protocol, address, user, password = nil)
|
216
|
+
@name, @protocol, @address, @user, @password = name, protocol, address, user, password
|
217
|
+
@type = Type::ACTIVESYNC
|
218
|
+
@id = -1
|
219
|
+
@port = 443 #port not used for mobile connection
|
220
|
+
end
|
221
|
+
|
222
|
+
end
|
191
223
|
end
|
@@ -2,6 +2,7 @@ module Nexpose
|
|
2
2
|
module Search
|
3
3
|
module Field
|
4
4
|
|
5
|
+
###### vSphere Filters ######
|
5
6
|
# Valid Operators: IS, IS_NOT, CONTAINS, NOT_CONTAINS, STARTS_WITH
|
6
7
|
CLUSTER = 'CLUSTER'
|
7
8
|
|
@@ -23,6 +24,14 @@ module Nexpose
|
|
23
24
|
|
24
25
|
# Valid Operators: IS, IS_NOT, CONTAINS, NOT_CONTAINS, STARTS_WITH
|
25
26
|
VIRTUAL_MACHINE_NAME = 'VM'
|
27
|
+
|
28
|
+
###### Mobile Filters ######
|
29
|
+
# Valid Operators: CONTAINS, NOT_CONTAINS
|
30
|
+
OPERATING_SYSTEM = 'DEVICE_OPERATING_SYSTEM'
|
31
|
+
|
32
|
+
# Valid Operators: IS, IS_NOT, CONTAINS, NOT_CONTAINS, STARTS_WITH
|
33
|
+
USER = 'DEVICE_USER_DISPLAY_NAME'
|
34
|
+
|
26
35
|
end
|
27
36
|
|
28
37
|
module Value
|
data/lib/nexpose/scan.rb
CHANGED
@@ -520,7 +520,6 @@ module Nexpose
|
|
520
520
|
# and vuln-potential.
|
521
521
|
#
|
522
522
|
class Status
|
523
|
-
|
524
523
|
attr_reader :severities, :count
|
525
524
|
|
526
525
|
def initialize(severity = nil, count = 0)
|
@@ -547,7 +546,6 @@ module Nexpose
|
|
547
546
|
# Struct class for tracking scan launch information.
|
548
547
|
#
|
549
548
|
class Scan
|
550
|
-
|
551
549
|
# The scan ID when a scan is successfully launched.
|
552
550
|
attr_reader :id
|
553
551
|
# The engine the scan was dispatched to.
|
@@ -578,4 +576,48 @@ module Nexpose
|
|
578
576
|
UNKNOWN = 'unknown'
|
579
577
|
end
|
580
578
|
end
|
579
|
+
|
580
|
+
# Summary object of a completed scan for a site.
|
581
|
+
#
|
582
|
+
class CompletedScan
|
583
|
+
# Unique identifier of a scan.
|
584
|
+
attr_reader :id
|
585
|
+
# Start time of the scan.
|
586
|
+
attr_reader :start_time
|
587
|
+
# Completion time of the scan.
|
588
|
+
attr_reader :end_time
|
589
|
+
# Elapsed time of the scan in milliseconds.
|
590
|
+
attr_reader :duration
|
591
|
+
# Number of vulnerabilities discovered in the scan.
|
592
|
+
attr_reader :vulns
|
593
|
+
# Number of live assets discovered in the scan.
|
594
|
+
attr_reader :assets
|
595
|
+
# Cumulative risk score for all assets in the scan.
|
596
|
+
attr_reader :risk_score
|
597
|
+
# Scan type. One of :scheduled or :manual
|
598
|
+
attr_reader :type
|
599
|
+
# Name of the engine where the scan was run. Not the unique ID.
|
600
|
+
attr_reader :engine_name
|
601
|
+
|
602
|
+
# Internal constructor to be called by #parse_json.
|
603
|
+
def initialize(&block)
|
604
|
+
instance_eval(&block) if block_given?
|
605
|
+
end
|
606
|
+
|
607
|
+
# Internal method for converting a JSON representation into a CompletedScan
|
608
|
+
# object.
|
609
|
+
def self.parse_json(json)
|
610
|
+
new do
|
611
|
+
@id = json['scanID']
|
612
|
+
@start_time = Time.at(json['startTime'] / 1000)
|
613
|
+
@end_time = Time.at(json['endTime'] / 1000)
|
614
|
+
@duration = json['duration']
|
615
|
+
@vulns = json['vulnerabilityCount']
|
616
|
+
@assets = json['liveHosts']
|
617
|
+
@risk_score = json['riskScore']
|
618
|
+
@type = json['startedByCD'] == 'S' ? :scheduled : :manual
|
619
|
+
@engine_name = json['scanEngineName']
|
620
|
+
end
|
621
|
+
end
|
622
|
+
end
|
581
623
|
end
|
data/lib/nexpose/site.rb
CHANGED
@@ -29,7 +29,7 @@ module Nexpose
|
|
29
29
|
# @return Whether or not the delete request succeeded.
|
30
30
|
#
|
31
31
|
def delete_site(site_id)
|
32
|
-
r = execute(make_xml('SiteDeleteRequest', {'site-id' => site_id}))
|
32
|
+
r = execute(make_xml('SiteDeleteRequest', { 'site-id' => site_id }))
|
33
33
|
r.success
|
34
34
|
end
|
35
35
|
|
@@ -40,7 +40,7 @@ module Nexpose
|
|
40
40
|
# each scan run to date on the site provided.
|
41
41
|
#
|
42
42
|
def site_scan_history(site_id)
|
43
|
-
r = execute(make_xml('SiteScanHistoryRequest', {'site-id' => site_id}))
|
43
|
+
r = execute(make_xml('SiteScanHistoryRequest', { 'site-id' => site_id }))
|
44
44
|
scans = []
|
45
45
|
if r.success
|
46
46
|
r.res.elements.each('SiteScanHistoryResponse/ScanSummary') do |scan_event|
|
@@ -59,7 +59,18 @@ module Nexpose
|
|
59
59
|
# @return [ScanSummary] details of the last completed scan for a site.
|
60
60
|
#
|
61
61
|
def last_scan(site_id)
|
62
|
-
site_scan_history(site_id).select
|
62
|
+
site_scan_history(site_id).select(&:end_time).max_by(&:end_time)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Retrieve a history of the completed scans for a given site.
|
66
|
+
#
|
67
|
+
# @param [FixNum] site_id Site ID to find scans for.
|
68
|
+
# @return [CompletedScan] details of the completed scans for the site.
|
69
|
+
#
|
70
|
+
def completed_scans(site_id)
|
71
|
+
table = { 'table-id' => 'site-completed-scans' }
|
72
|
+
data = DataTable._get_json_table(self, "/data/scan/site/#{site_id}", table)
|
73
|
+
data.map(&CompletedScan.method(:parse_json))
|
63
74
|
end
|
64
75
|
end
|
65
76
|
|
@@ -178,7 +189,7 @@ module Nexpose
|
|
178
189
|
#
|
179
190
|
# @param [String] hostname FQDN or DNS-resolvable host name of an asset.
|
180
191
|
def remove_host(hostname)
|
181
|
-
@assets = assets.reject { |asset| asset == HostName.new(hostname) }
|
192
|
+
@assets = assets.reject { |asset| asset == HostName.new(hostname) }
|
182
193
|
end
|
183
194
|
|
184
195
|
# Adds an asset to this site by IP address.
|
@@ -228,8 +239,8 @@ module Nexpose
|
|
228
239
|
#
|
229
240
|
def remove_asset(asset)
|
230
241
|
begin
|
231
|
-
# If the asset registers as a valid IP,
|
232
|
-
|
242
|
+
# If the asset registers as a valid IP, remove as IP.
|
243
|
+
IPAddr.new(asset)
|
233
244
|
remove_ip(asset)
|
234
245
|
rescue ArgumentError => e
|
235
246
|
if e.message == 'invalid address'
|
@@ -531,7 +542,6 @@ module Nexpose
|
|
531
542
|
# Object that represents the summary of a Nexpose Site.
|
532
543
|
#
|
533
544
|
class SiteSummary
|
534
|
-
|
535
545
|
# The Site ID.
|
536
546
|
attr_reader :id
|
537
547
|
# The Site Name.
|
@@ -557,7 +567,6 @@ module Nexpose
|
|
557
567
|
# Object that represents a hostname to be added to a site.
|
558
568
|
#
|
559
569
|
class HostName
|
560
|
-
|
561
570
|
# Named host (usually DNS or Netbios name).
|
562
571
|
attr_accessor :host
|
563
572
|
|
@@ -595,7 +604,6 @@ module Nexpose
|
|
595
604
|
# If to is nil then the from field will be used to specify a single IP Address only.
|
596
605
|
#
|
597
606
|
class IPRange
|
598
|
-
|
599
607
|
# Start of range *Required
|
600
608
|
attr_accessor :from
|
601
609
|
# End of range *Optional (If nil then IPRange is a single IP Address)
|
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.10
|
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-29 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: rex
|
@@ -96,7 +96,6 @@ files:
|
|
96
96
|
- lib/nexpose/role.rb
|
97
97
|
- lib/nexpose/scan.rb
|
98
98
|
- lib/nexpose/scan_template.rb
|
99
|
-
- lib/nexpose/settings.xml
|
100
99
|
- lib/nexpose/shared_cred.rb
|
101
100
|
- lib/nexpose/silo.rb
|
102
101
|
- lib/nexpose/silo_profile.rb
|
data/lib/nexpose/settings.xml
DELETED
@@ -1,14 +0,0 @@
|
|
1
|
-
<GlobalSettings>
|
2
|
-
<riskModel id="real_risk" recalculation_duration="do_not_recalculate"></riskModel>
|
3
|
-
<RiskConfig includeRiskModifiers="false">
|
4
|
-
<RiskModifiers veryHigh="2" high="1.5" medium="1" low="0.75" veryLow="0.5"></RiskModifiers>
|
5
|
-
</RiskConfig>
|
6
|
-
<ControlsScan>
|
7
|
-
<enableControlsScan enabled="0"></enableControlsScan>
|
8
|
-
</ControlsScan>
|
9
|
-
<ExcludedHosts>
|
10
|
-
<range from="10.4.18.219"></range>
|
11
|
-
<range from="10.4.25.117"></range>
|
12
|
-
</ExcludedHosts>
|
13
|
-
</GlobalSettings>
|
14
|
-
|