nexpose 0.2.8 → 0.5.0

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.
@@ -0,0 +1,194 @@
1
+ module Nexpose
2
+ module NexposeAPI
3
+ include XMLUtils
4
+
5
+ # Removes a scan engine from the list of available engines.
6
+ #
7
+ # @param [Fixnum] engine_id Unique ID of an existing engine to remove.
8
+ # @param [String] scope Whether the engine is global or silo scoped.
9
+ # @return [Boolean] true if engine successfully deleted.
10
+ #
11
+ def delete_engine(engine_id, scope = 'silo')
12
+ xml = make_xml('EngineDeleteRequest',
13
+ {'engine-id' => engine_id, 'scope' => scope})
14
+ response = execute(xml, '1.2')
15
+ response.success
16
+ end
17
+
18
+ # Provide a list of current scan activities for a specific Scan Engine.
19
+ #
20
+ # @return [Array[ScanSummary]] Array of ScanSummary objects associated with
21
+ # each active scan on the engine.
22
+ #
23
+ def engine_activity(engine_id)
24
+ xml = make_xml('EngineActivityRequest', {'engine-id' => engine_id})
25
+ r = execute(xml)
26
+ arr = []
27
+ if r.success
28
+ r.res.elements.each('//ScanSummary') do |scan_event|
29
+ arr << ScanSummary.parse(scan_event)
30
+ end
31
+ end
32
+ arr
33
+ end
34
+
35
+ # Retrieve a list of all Scan Engines managed by the Security Console.
36
+ #
37
+ # @return [Array[EngineSummary]] Array of EngineSummary objects associated
38
+ # with each engine associated with this security console.
39
+ #
40
+ def list_engines
41
+ response = execute(make_xml('EngineListingRequest'))
42
+ arr = []
43
+ if response.success
44
+ response.res.elements.each('//EngineSummary') do |engine|
45
+ arr << EngineSummary.new(engine.attributes['id'].to_i,
46
+ engine.attributes['name'],
47
+ engine.attributes['address'],
48
+ engine.attributes['port'].to_i,
49
+ engine.attributes['status'])
50
+ end
51
+ end
52
+ arr
53
+ end
54
+
55
+ alias_method :engines, :list_engines
56
+ end
57
+
58
+ # Object representing the current details of a scan engine attached to the
59
+ # security console.
60
+ #
61
+ class EngineSummary
62
+
63
+ # A unique ID that identifies this scan engine.
64
+ attr_reader :id
65
+ # The name of this scan engine.
66
+ attr_reader :name
67
+ # The hostname or IP address of the engine.
68
+ attr_reader :address
69
+ # The port there the engine is listening.
70
+ attr_reader :port
71
+ # The engine status. One of: active, pending-auth, incompatible,
72
+ # not-responding, unknown
73
+ attr_reader :status
74
+ # A parameter that specifies whether the engine has a global
75
+ # or silo-specific scope.
76
+ attr_reader :scope
77
+
78
+ def initialize(id, name, address, port, status, scope = 'silo')
79
+ @id = id
80
+ @name = name
81
+ @address = address
82
+ @port = port
83
+ @status = status
84
+ @scope = scope
85
+ end
86
+ end
87
+
88
+ # Engine connnection to a Nexpose console.
89
+ #
90
+ class Engine
91
+
92
+ # Unique numeric identifier for the scan engine, assigned by the console
93
+ # in the order of creation.
94
+ attr_accessor :id
95
+ # The IP address or DNS name of a scan engine.
96
+ attr_accessor :address
97
+ # A name assigned to the scan engine by the security console.
98
+ attr_accessor :name
99
+ # The port on which the engine listens for requests from the security
100
+ # console.
101
+ attr_accessor :port
102
+ # Whether the engine has a global or silo-specific scope.
103
+ attr_accessor :scope
104
+ # Relative priority of a scan engine.
105
+ # One of: very-low, low, normal, high, very-high
106
+ attr_accessor :priority
107
+
108
+ # Sites to which the scan engine is assigned.
109
+ attr_accessor :sites
110
+
111
+ def initialize(address, name = nil, port = 40814)
112
+ @id = -1
113
+ @address = address
114
+ @name = name
115
+ @name ||= address
116
+ @port = port
117
+ @scope = 'silo'
118
+ @sites = []
119
+ end
120
+
121
+ def self.load(connection, id)
122
+ xml = '<EngineConfigRequest session-id="' + connection.session_id + '"'
123
+ xml << %( engine-id="#{id}")
124
+ xml << ' />'
125
+ r = connection.execute(xml, '1.2')
126
+
127
+ if r.success
128
+ r.res.elements.each('EngineConfigResponse/EngineConfig') do |config|
129
+ engine = Engine.new(config.attributes['address'],
130
+ config.attributes['name'],
131
+ config.attributes['port'])
132
+ engine.id = config.attributes['id']
133
+ engine.scope = config.attributes['scope'] if config.attributes['scope']
134
+ engine.priority = config.attributes['priority'] if config.attributes['priority']
135
+ config.elements.each('Site') do |site|
136
+ engine.sites << SiteSummary.new(site.attributes['id'], site.attributes['name'])
137
+ end
138
+ return engine
139
+ end
140
+ end
141
+ nil
142
+ end
143
+
144
+ # Assign a site to this scan engine.
145
+ #
146
+ # @param [Fixnum] site_id Unique numerical ID of the site.
147
+ #
148
+ def add_site(site_id)
149
+ sites << SiteSummary.new(site_id, nil)
150
+ end
151
+
152
+ def to_xml
153
+ xml = '<EngineConfig'
154
+ xml << %( id="#{id}")
155
+ xml << %( address="#{address}")
156
+ xml << %( name="#{name}")
157
+ xml << %( port="#{port}")
158
+ xml << %( scope="#{scope}") if scope
159
+ xml << %( priority="#{priority}") if priority
160
+ xml << '>'
161
+ sites.each do |site|
162
+ xml << %(<Site id="#{site.id}" />)
163
+ end
164
+ xml << '</EngineConfig>'
165
+ xml
166
+ end
167
+
168
+ # Save this engine configuration to the security console.
169
+ #
170
+ # @param [Connection] connection Connection to console where site exists.
171
+ # @return [Fixnum] ID assigned to the scan engine.
172
+ #
173
+ def save(connection)
174
+ xml = '<EngineSaveRequest session-id="' + connection.session_id + '">'
175
+ xml << to_xml
176
+ xml << '</EngineSaveRequest>'
177
+
178
+ r = connection.execute(xml, '1.2')
179
+ if r.success
180
+ r.res.elements.each('EngineSaveResponse/EngineConfig') do |v|
181
+ return @id = v.attributes['id']
182
+ end
183
+ end
184
+ end
185
+
186
+ # Delete this scan engine configuration from the security console.
187
+ #
188
+ # @param [Connection] connection Connection to console where site exists.
189
+ #
190
+ def delete(connection)
191
+ connection.delete_engine(@id, @scope)
192
+ end
193
+ end
194
+ end
@@ -0,0 +1,341 @@
1
+ module Nexpose
2
+
3
+ module NexposeAPI
4
+
5
+ # Perform an asset filter search that will locate assets matching the
6
+ # provided conditions.
7
+ #
8
+ # For example, the following call will return assets with Java installed:
9
+ # nsc.filter(Search::Field::SOFTWARE, Search::Operator::CONTAINS, 'java')
10
+ #
11
+ # @param [String] field Constant from Search::Field
12
+ # @param [String] operator Constant from Search::Operator
13
+ # @param [String] value Search term or constant from Search::Value
14
+ # @return [Array[Asset]] List of matching assets.
15
+ #
16
+ def filter(field, operator, value = '')
17
+ criterion = Criterion.new(field, operator, value)
18
+ criteria = Criteria.new(criterion)
19
+ search(criteria)
20
+ end
21
+
22
+ # Perform a search that will match the criteria provided.
23
+ #
24
+ # For example, the following call will return assets with Java and .NET:
25
+ # java_criterion = Criterion.new(Search::Field::SOFTWARE,
26
+ # Search::Operator::CONTAINS,
27
+ # 'java')
28
+ # dot_net_criterion = Criterion.new(Search::Field::SOFTWARE,
29
+ # Search::Operator::CONTAINS,
30
+ # '.net')
31
+ # criteria = Criteria.new([java_criterion, dot_net_criterion])
32
+ # results = nsc.search(criteria)
33
+ #
34
+ # @param [Criteria] criteria Criteria search object.
35
+ # @return [Array[Asset]] List of matching assets.
36
+ #
37
+ def search(criteria)
38
+ results = DataTable._get_json_table(self,
39
+ '/data/asset/filterAssets',
40
+ criteria._to_payload)
41
+ results.map { |a| Asset.new(a) }
42
+ end
43
+ end
44
+
45
+ # Constans for performing Asset Filter searches and generating Dynamic Asset
46
+ # Groups.
47
+ #
48
+ module Search
49
+ module_function
50
+
51
+ # Search constants
52
+
53
+ # Only these values are accepted for a field value.
54
+ #
55
+ module Field
56
+
57
+ # Search for an Asset by name.
58
+ # Valid Operators: IS, IS_NOT, STARTS_WITH, ENDS_WITH, CONTAINS, NOT_CONTAINS
59
+ ASSET = 'ASSET'
60
+
61
+ # Valid Operators: IS, IS_NOT
62
+ # Valid Values (See Value::AccessComplexity): LOW, MEDIUM, HIGH
63
+ CVSS_ACCESS_COMPLEXITY = 'CVSS_ACCESS_COMPLEXITY'
64
+
65
+ # Valid Operators: IS, IS_NOT
66
+ # Valid Values (See Value::AccessVector): LOCAL, ADJACENT, NETWORK
67
+ CVSS_ACCESS_VECTOR = 'CVSS_ACCESS_VECTOR'
68
+
69
+ # Valid Operators: IS, IS_NOT
70
+ # Valid Values (See Value::AuthenticationRequired): NONE, SINGLE, MULTIPLE
71
+ CVSS_AUTHENTICATION_REQUIRED = 'CVSS_AUTHENTICATION_REQUIRED'
72
+
73
+ # Valid Operators: IS, IS_NOT
74
+ # Valid Values (See Value::CVSSImpact): NONE, PARTIAL, COMPLETE
75
+ CVSS_AVAILABILITY_IMPACT = 'CVSS_AVAILABILITY_IMPACT'
76
+
77
+ # Valid Operators: IS, IS_NOT
78
+ # Valid Values (See Value::CVSSImpact): NONE, PARTIAL, COMPLETE
79
+ CVSS_CONFIDENTIALITY_IMPACT = 'CVSS_CONFIDENTIALITY_IMPACT'
80
+
81
+ # Valid Operators: IS, IS_NOT
82
+ # Valid Values (See Value::CVSSImpact): NONE, PARTIAL, COMPLETE
83
+ CVSS_INTEGRITY_IMPACT = 'CVSS_INTEGRITY_IMPACT'
84
+
85
+ # Valid Operators: IS, IS_NOT, IN_RANGE, GREATER_THAN, LESS_THAN
86
+ # Valid Values: Floats from 0.0 to 10.0
87
+ CVSS_SCORE = 'CVSS_SCORE'
88
+
89
+ # Valid Operators: IN, NOT_IN
90
+ # Valid Values (See Value::HostType): UNKNOWN, VIRTUAL, HYPERVISOR, BARE_METAL
91
+ HOST_TYPE = 'HOST_TYPE'
92
+
93
+ # Valid Operators: IN, NOT_IN
94
+ # Valid Values (See Value::IPType): IPv4, IPv6
95
+ IP_ADDRESS_TYPE = 'IP_ADDRESS_TYPE'
96
+
97
+ # Valid Operators: IN
98
+ # Valid Values (See Value::IPType): IPv4, IPv6
99
+ IP_ALT_ADDRESS_TYPE = 'IP_ALT_ADDRESS_TYPE'
100
+
101
+ # Valid Operators: IN, NOT_IN
102
+ IP_RANGE = 'IP_RANGE'
103
+
104
+ # Valid Operators: CONTAINS, NOT_CONTAINS, IS_EMPTY, IS_NOT_EMPTY
105
+ OS = 'OS'
106
+
107
+ # Valid Operators: IS
108
+ # Valid Values (See Value::PCICompliance): PASS, FAIL
109
+ PCI_COMPLIANCE_STATUS = 'PCI_COMPLIANCE_STATUS'
110
+
111
+ # Valid Operators: IS, IS_NOT, IN_RANGE, GREATER_THAN, LESS_THAN
112
+ RISK_SCORE = 'RISK_SCORE'
113
+
114
+ # Search based on the last scan date of an asset.
115
+ # Valid Operators: ON_OR_BEFORE, ON_OR_AFTER, BETWEEN, EARLIER_THAN,
116
+ # WITHIN_THE_LAST
117
+ # Valid Values: Use Value::ScanDate::FORMAT for date arguments.
118
+ # Use FixNum for day arguments.
119
+ SCAN_DATE = 'SCAN_DATE'
120
+
121
+ # Valid Operators: CONTAINS, NOT_CONTAINS
122
+ SERVICE = 'SERVICE'
123
+
124
+ # Search based on the Site ID of an asset.
125
+ # (Note that underlying search used Site ID, despite 'site name' value.)
126
+ # Valid Operators: IN, NOT_IN
127
+ # Valid Values: FixNum Site ID of the site.
128
+ SITE_ID = 'SITE_NAME'
129
+
130
+ # Valid Operators: CONTAINS, NOT_CONTAINS
131
+ SOFTWARE = 'SOFTWARE'
132
+
133
+ # Search against vulnerability titles that an asset contains.
134
+ # Valid Operators: CONTAINS, NOT_CONTAINS
135
+ VULNERABILITY = 'VULNERABILITY'
136
+
137
+ # Valid Operators: INCLUDE, DO_NOT_INCLUDE
138
+ # Valid Values (See Value::VulnerabilityExposure): MALWARE, METASPLOIT, DATABASE
139
+ VULNERABILITY_EXPOSURES = 'VULNERABILITY_EXPOSURES'
140
+ end
141
+
142
+ # List of acceptable operators. Not all fields accept all operators.
143
+ #
144
+ module Operator
145
+ CONTAINS = 'CONTAINS'
146
+ NOT_CONTAINS = 'NOT_CONTAINS'
147
+ IS = 'IS'
148
+ IS_NOT = 'IS_NOT'
149
+ IN = 'IN'
150
+ NOT_IN = 'NOT_IN'
151
+ IN_RANGE = 'IN_RANGE'
152
+ STARTS_WITH = 'STARTS_WITH'
153
+ ENDS_WITH = 'ENDS_WITH'
154
+ ON_OR_BEFORE = 'ON_OR_BEFORE'
155
+ ON_OR_AFTER = 'ON_OR_AFTER'
156
+ WITHIN_THE_LAST = 'WITHIN_THE_LAST'
157
+ GREATER_THAN = 'GREATER_THAN'
158
+ LESS_THAN = 'LESS_THAN'
159
+ IS_EMPTY = 'IS_EMPTY'
160
+ IS_NOT_EMPTY = 'IS_NOT_EMPTY'
161
+ INCLUDE = 'INCLUDE'
162
+ DO_NOT_INCLUDE = 'DO_NOT_INCLUDE'
163
+ end
164
+
165
+ # Specialized values used by certain search fields
166
+ #
167
+ module Value
168
+
169
+ module AccessComplexity
170
+ LOW = 'L'
171
+ MEDIUM = 'M'
172
+ HIGH = 'H'
173
+ end
174
+
175
+ module AccessVector
176
+ LOCAL = 'L'
177
+ ADJACENT = 'A'
178
+ NETWORK = 'N'
179
+ end
180
+
181
+ module AuthenticationRequired
182
+ NONE = 'N'
183
+ SINGLE = 'S'
184
+ MULTIPLE = 'M'
185
+ end
186
+
187
+ module CVSSImpact
188
+ NONE = 'N'
189
+ PARTIAL = 'P'
190
+ COMPLETE = 'C'
191
+ end
192
+
193
+ module HostType
194
+ UNKNOWN = '0'
195
+ VIRTUAL = '1'
196
+ HYPERVISOR = '2'
197
+ BARE_METAL = '3'
198
+ end
199
+
200
+ module IPType
201
+ IPv4 = '0'
202
+ IPv6 = '1'
203
+ end
204
+
205
+ module PCICompliance
206
+ PASS = '1'
207
+ FAIL = '0'
208
+ end
209
+
210
+ module ScanDate
211
+ # Pass this format to #strftime() to get expected format for requests.
212
+ FORMAT = '%m/%d/%Y'
213
+ end
214
+
215
+ module VulnerabilityExposure
216
+ MALWARE = 'type:"malware_type", name:"malwarekit"'
217
+ # TODO: A problem in Nexpose causes these values to not be constant.
218
+ METASPLOIT = 'type:"exploit_source_type", name:"2"'
219
+ DATABASE = 'type:"exploit_source_type", name:"1"'
220
+ end
221
+ end
222
+ end
223
+
224
+ # Individual search criterion.
225
+ #
226
+ class Criterion
227
+
228
+ # Search field. One of Nexpose::Search::Field
229
+ # @see Nexpose::Search::Field for any restrictions on the other attibutes.
230
+ attr_accessor :field
231
+ # Search operator. One of Nexpose::Search::Operator
232
+ attr_accessor :operator
233
+ # Search value. A search string or one of Nexpose::Search::Value
234
+ attr_accessor :value
235
+
236
+ def initialize(field, operator, value = '')
237
+ @field, @operator = field.upcase, operator.upcase
238
+ if value.kind_of? Array
239
+ @value = value.map { |v| v.to_s }
240
+ else
241
+ @value = value.to_s
242
+ end
243
+ end
244
+
245
+ # Convert this object into the map format expected by Nexpose.
246
+ #
247
+ def to_map
248
+ { 'metadata' => { 'fieldName' => field },
249
+ 'operator' => operator,
250
+ 'values' => value.kind_of?(Array) ? value : [value] }
251
+ end
252
+
253
+ def self.parse(json)
254
+ Criterion.new(json['metadata']['fieldName'],
255
+ json['operator'],
256
+ json['values'])
257
+ end
258
+ end
259
+
260
+ # Join search criteria for an asset filter search or dynamic asset group.
261
+ #
262
+ class Criteria
263
+
264
+ # Whether to match any or all filters. One of 'OR' or 'AND'.
265
+ attr_accessor :match
266
+ # Array of criteria to match against.
267
+ attr_accessor :criteria
268
+
269
+ def initialize(criteria = [], match = 'AND')
270
+ if criteria.kind_of?(Array)
271
+ @criteria = criteria
272
+ else
273
+ @criteria = [criteria]
274
+ end
275
+ @match = match.upcase
276
+ end
277
+
278
+ def to_map
279
+ { 'operator' => @match,
280
+ 'criteria' => @criteria.map { |c| c.to_map } }
281
+ end
282
+
283
+ # Convert this object into the format expected by Nexpose.
284
+ #
285
+ def to_json
286
+ JSON.generate(to_map)
287
+ end
288
+
289
+ # Generate the payload needed for a POST request for Asset Filter.
290
+ #
291
+ def _to_payload
292
+ { 'dir' => -1,
293
+ 'results' => -1,
294
+ 'sort' => 'assetIP',
295
+ 'startIndex' => -1,
296
+ 'table-id' => 'assetfilter',
297
+ 'searchCriteria' => to_json }
298
+ end
299
+
300
+ def self.parse(json)
301
+ ret = Criteria.new([], json['operator'])
302
+ json['criteria'].each do |c|
303
+ ret.criteria << Criterion.parse(c)
304
+ end
305
+ ret
306
+ end
307
+ end
308
+
309
+ # Asset data as returned by an Asset Filter search.
310
+ #
311
+ class Asset
312
+
313
+ # Unique identifier of this asset. Also known as device ID.
314
+ attr_reader :id
315
+
316
+ attr_reader :ip
317
+ attr_reader :name
318
+ attr_reader :os
319
+
320
+ attr_reader :exploit_count
321
+ attr_reader :malware_count
322
+ attr_reader :vuln_count
323
+ attr_reader :risk_score
324
+
325
+ attr_reader :site_id
326
+ attr_reader :last_scan
327
+
328
+ def initialize(json)
329
+ @id = json['assetID']['ID'].to_i
330
+ @ip = json['assetIP']
331
+ @name = json['assetName']
332
+ @os = json['assetOSName']
333
+ @exploit_count = json['exploitCount'].to_i
334
+ @malware_count = json['malwareCount'].to_i
335
+ @vuln_count = json['vulnCount'].to_i
336
+ @risk_score = json['riskScore'].to_f
337
+ @site_id = json['siteID']
338
+ @last_scan = Time.at(json['lastScanDate'] / 1000)
339
+ end
340
+ end
341
+ end