nexpose 0.2.8 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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