nexpose 0.9.8 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,178 @@
1
+ module Nexpose
2
+
3
+ # Object that represents administrative credentials to be used
4
+ # during a scan. When retrieved from an existing site configuration
5
+ # the credentials will be returned as a security blob and can only
6
+ # be passed back as is during a Site Save operation. This object
7
+ # can only be used to create a new set of credentials.
8
+ #
9
+ class SiteCredentials < Credential
10
+
11
+ # Unique identifier of the credential on the Nexpose console.
12
+ attr_accessor :id
13
+ # The service for these credentials.
14
+ attr_accessor :service
15
+ # The host for these credentials.
16
+ attr_accessor :host_restriction
17
+ # The port on which to use these credentials.
18
+ attr_accessor :port_restriction
19
+ # The password
20
+ attr_accessor :password
21
+ # The name
22
+ attr_accessor :name
23
+ # is this credential enable on site or not.
24
+ attr_accessor :enabled
25
+ # the description of credential
26
+ attr_accessor :description
27
+ # domain of the service
28
+ attr_accessor :domain
29
+ # database of the service
30
+ attr_accessor :database
31
+ # The type of privilege escalation to use (sudo/su)
32
+ # Permission elevation type. See Nexpose::Credential::ElevationType.
33
+ attr_accessor :permission_elevation_type
34
+ # The userid to use when escalating privileges (optional)
35
+ attr_accessor :permission_elevation_user
36
+ # The password to use when escalating privileges (optional)
37
+ attr_accessor :permission_elevation_password
38
+ # The authentication type to use with SNMP v3 credentials
39
+ attr_accessor :authentication_type
40
+ # The privacy/encryption type to use with SNMP v3 credentials
41
+ attr_accessor :privacy_type
42
+ # The privacy/encryption pass phrase to use with SNMP v3 credentials
43
+ attr_accessor :privacy_password
44
+ # the user name to be used in service
45
+ attr_accessor :user_name
46
+ # the notes password
47
+ attr_accessor :notes_id_password
48
+ # use windows auth
49
+ attr_accessor :use_windows_auth
50
+ # sid for oracle
51
+ attr_accessor :sid
52
+ #for ssh public key require pem format private key
53
+ attr_accessor :pem_format_private_key
54
+ # for snmp v1/v2
55
+ attr_accessor :community_name
56
+ # scope of credential
57
+ attr_accessor :scope
58
+
59
+ #Create a credential object using name, id, description, host and port
60
+ def self.for_service(name, id = -1, desc = nil, host = nil, port = nil, service = Credential::Service::CIFS)
61
+ cred = new
62
+ cred.name = name
63
+ cred.id = id.to_i
64
+ cred.enabled = true
65
+ cred.description = desc
66
+ cred.host_restriction = host
67
+ cred.port_restriction = port
68
+ cred.service = service
69
+ cred.scope = Credential::Scope::SITE_SPECIFIC
70
+ cred
71
+ end
72
+
73
+ # Load an credential from the provided console.
74
+ #
75
+ # @param [Connection] nsc Active connection to a Nexpose console.
76
+ # @param [String] id Unique identifier of an site.
77
+ # @param [String] id Unique identifier of an credential.
78
+ # @return [SiteCredential] The requested credential of site, if found.
79
+ #
80
+ def self.load(nsc, site_id, credential_id)
81
+ uri = "/api/2.1/sites/#{site_id}/credentials/#{credential_id}"
82
+ resp = AJAX.get(nsc, uri, AJAX::CONTENT_TYPE::JSON)
83
+ hash = JSON.parse(resp, symbolize_names: true)
84
+ new.object_from_hash(nsc, hash)
85
+ end
86
+
87
+ # Copy an existing configuration from a Nexpose instance.
88
+ # Returned object will reset the credential ID and append "Copy" to the existing
89
+ # name.
90
+ #
91
+ # @param [Connection] connection Connection to the security console.
92
+ # @param [String] id Unique identifier of an site.
93
+ # @param [String] id Unique identifier of an credential.
94
+ # @return [SiteCredentials] Site credential loaded from a Nexpose console.
95
+ #
96
+ def self.copy(connection, site_id, credential_id)
97
+ siteCredential = self.load(connection, site_id, credential_id)
98
+ siteCredential.id = -1
99
+ siteCredential.name = "#{siteCredential.name} Copy"
100
+ siteCredential
101
+ end
102
+
103
+ # Copy an existing configuration from a site credential.
104
+ # Returned object will reset the credential ID and append "Copy" to the existing
105
+ # name.
106
+ #
107
+ # @param [siteCredential] site credential to be copied.
108
+ # @return [SiteCredentials] modified.
109
+ #
110
+ def self.copy(siteCredential)
111
+ siteCredential.id = -1
112
+ siteCredential.name = "#{siteCredential.name} Copy"
113
+ siteCredential
114
+ end
115
+
116
+ def to_json
117
+ JSON.generate(to_h)
118
+ end
119
+
120
+ def to_h
121
+ { id: id,
122
+ service: service,
123
+ host_restriction: host_restriction,
124
+ port_restriction: port_restriction,
125
+ password: password,
126
+ name: name,
127
+ enabled: enabled,
128
+ description: description,
129
+ domain: domain,
130
+ database: database,
131
+ permission_elevation_type: permission_elevation_type,
132
+ permission_elevation_user: permission_elevation_user,
133
+ permission_elevation_password: permission_elevation_password,
134
+ authentication_type: authentication_type,
135
+ privacy_type: privacy_type,
136
+ privacy_password: privacy_password,
137
+ user_name: user_name,
138
+ notes_id_password: notes_id_password,
139
+ use_windows_auth: use_windows_auth,
140
+ sid: sid,
141
+ pem_format_private_key: pem_format_private_key,
142
+ community_name: community_name,
143
+ scope: scope
144
+ }
145
+ end
146
+
147
+ def ==(other)
148
+ eql?(other)
149
+ end
150
+
151
+ def eql?(other)
152
+ id.eql?(other.id) &&
153
+ service.eql?(other.service) &&
154
+ host_restriction.eql?(other.host_restriction) &&
155
+ port_restriction.eql?(other.port_restriction) &&
156
+ password.eql?(other.password) &&
157
+ name.eql?(other.name) &&
158
+ enabled.eql?(other.enabled) &&
159
+ description.eql?(other.description) &&
160
+ domain.eql?(other.domain) &&
161
+ database.eql?(other.database) &&
162
+ permission_elevation_type.eql?(other.permission_elevation_type) &&
163
+ permission_elevation_user.eql?(other.permission_elevation_user) &&
164
+ permission_elevation_password.eql?(other.permission_elevation_password) &&
165
+ authentication_type.eql?(other.authentication_type) &&
166
+ privacy_type.eql?(other.privacy_type) &&
167
+ privacy_password.eql?(other.privacy_password) &&
168
+ user_name.eql?(other.user_name) &&
169
+ notes_id_password.eql?(other.notes_id_password) &&
170
+ use_windows_auth.eql?(other.use_windows_auth) &&
171
+ sid.eql?(other.sid) &&
172
+ pem_format_private_key.eql?(other.pem_format_private_key) &&
173
+ community_name.eql?(other.community_name) &&
174
+ scope.eql?(other.scope)
175
+ end
176
+
177
+ end
178
+ end
data/lib/nexpose/tag.rb CHANGED
@@ -218,7 +218,48 @@ module Nexpose
218
218
  end
219
219
 
220
220
  @color = hex
221
- end
221
+ end
222
+
223
+ # Create list of tag objects from hash
224
+ def self.load_tags(tags)
225
+ unless tags.nil?
226
+ tags = tags.map do |hash|
227
+ self.create(hash)
228
+ end
229
+ end
230
+ tags
231
+ end
232
+
233
+ # Create tag object from hash
234
+ def self.create(hash)
235
+ attributes = hash[:attributes]
236
+ color = attributes.find { |attr| attr[:tag_attribute_name] == 'COLOR' }
237
+ color = color[:tag_attribute_value] if color
238
+ source = attributes.find { |attr| attr[:tag_attribute_name] == 'SOURCE' }
239
+ source = source[:tag_attribute_value] if source
240
+ tag = Tag.new(hash[:tag_name], hash[:tag_type], hash[:tag_id])
241
+ tag.color = color
242
+ tag.source = source
243
+ tag
244
+ end
245
+
246
+ def to_h
247
+ {
248
+ tag_id: id,
249
+ tag_name: name,
250
+ tag_type: type,
251
+ attributes:[
252
+ {
253
+ tag_attribute_name: "COLOR",
254
+ tag_attribute_value: color
255
+ },
256
+ {
257
+ tag_attribute_name: "SOURCE",
258
+ tag_attribute_value: source
259
+ }
260
+ ]
261
+ }
262
+ end
222
263
 
223
264
  # Creates and saves a tag to Nexpose console
224
265
  #
data/lib/nexpose/util.rb CHANGED
@@ -1,5 +1,4 @@
1
1
  module Nexpose
2
-
3
2
  module Sanitize
4
3
  def replace_entities(str)
5
4
  str.to_s.gsub(/&/, '&amp;').gsub(/'/, '&apos;').gsub(/"/, '&quot;').gsub(/</, '&lt;').gsub(/>/, '&gt;')
@@ -7,21 +6,18 @@ module Nexpose
7
6
  end
8
7
 
9
8
  module XMLUtils
10
-
11
9
  def parse_xml(xml)
12
10
  ::REXML::Document.new(xml.to_s)
13
11
  end
14
12
 
15
13
  def make_xml(name, opts = {}, data = '', append_session_id = true)
16
14
  xml = REXML::Element.new(name)
17
- if @session_id and append_session_id
15
+ if @session_id && append_session_id
18
16
  xml.attributes['session-id'] = @session_id
19
17
  end
20
18
 
21
19
  opts.keys.each do |k|
22
- if opts[k] != nil
23
- xml.attributes[k] = "#{opts[k]}"
24
- end
20
+ xml.attributes[k] = "#{opts[k]}" unless opts[k].nil?
25
21
  end
26
22
 
27
23
  xml.text = data
@@ -54,16 +50,15 @@ module Nexpose
54
50
  # @return [IPRange|HostName] Valid class, if it can be converted.
55
51
  #
56
52
  def convert(asset)
57
- begin
58
- # Use IPAddr construtor validation to see if it's an IP.
59
- IPAddr.new(asset)
60
- IPRange.new(asset)
61
- rescue ArgumentError => e
62
- if e.message == 'invalid address'
63
- HostName.new(asset)
64
- else
65
- raise "Unable to parse asset: '#{asset}'. #{e.message}"
66
- end
53
+ ips = asset.split(' - ')
54
+ IPAddr.new(ips[0])
55
+ IPAddr.new(ips[1]) if ips[1]
56
+ IPRange.new(ips[0], ips[1])
57
+ rescue ArgumentError => e
58
+ if e.message == 'invalid address'
59
+ HostName.new(asset)
60
+ else
61
+ raise "Unable to parse asset: '#{asset}'. #{e.message}"
67
62
  end
68
63
  end
69
64
 
@@ -1,4 +1,4 @@
1
1
  module Nexpose
2
2
  # The latest version of the Nexpose gem
3
- VERSION = '0.9.8'
3
+ VERSION = '1.0.0'
4
4
  end
@@ -0,0 +1,103 @@
1
+ module Nexpose
2
+ class Wait
3
+ attr_reader :error_message, :ready, :retry_count, :timeout, :polling_interval
4
+
5
+ def initialize(retry_count: nil, timeout: nil, polling_interval: nil)
6
+ @error_message = 'Default General Failure in Nexpose::Wait'
7
+ @ready = false
8
+ @retry_count = retry_count.to_i
9
+ @timeout = timeout
10
+ @polling_interval = polling_interval
11
+ end
12
+
13
+ def ready?
14
+ @ready
15
+ end
16
+
17
+ def for_report(nexpose_connection:, report_id:)
18
+ poller = Nexpose::Poller.new(timeout: @timeout, polling_interval: @polling_interval)
19
+ poller.wait(report_status_proc(nexpose_connection: nexpose_connection, report_id: report_id))
20
+ @ready = true
21
+ rescue TimeoutError
22
+ retry if timeout_retry?
23
+ @error_message = "Timeout Waiting for Report to Generate - Report Config ID: #{report_id}"
24
+ rescue NoMethodError => error
25
+ @error_message = "Error Report Config ID: #{report_id} :: Report Probably Does Not Exist :: #{error}"
26
+ rescue => error
27
+ @error_message = "Error Report Config ID: #{report_id} :: #{error}"
28
+ end
29
+
30
+ def for_integration(nexpose_connection:, scan_id:, status: 'finished')
31
+ poller = Nexpose::Poller.new(timeout: @timeout, polling_interval: @polling_interval)
32
+ poller.wait(integration_status_proc(nexpose_connection: nexpose_connection, scan_id: scan_id, status: status))
33
+ @ready = true
34
+ rescue TimeoutError
35
+ retry if timeout_retry?
36
+ @error_message = "Timeout Waiting for Integration Status of '#{status}' - Scan ID: #{scan_id}"
37
+ rescue Nexpose::APIError => error
38
+ @error_message = "API Error Waiting for Integration Scan ID: #{scan_id} :: #{error.req.error}"
39
+ end
40
+
41
+ def for_judgment(proc:, desc:)
42
+ poller = Nexpose::Poller.new(timeout: @timeout, polling_interval: @polling_interval)
43
+ poller.wait(proc)
44
+ @ready = true
45
+ rescue TimeoutError
46
+ retry if timeout_retry?
47
+ @error_message = "Timeout Waiting for Judgment to Judge. #{desc}"
48
+ end
49
+
50
+ private
51
+
52
+ def report_status_proc(nexpose_connection:, report_id:)
53
+ Proc.new { nexpose_connection.last_report(report_id).status == 'Generated' }
54
+ end
55
+
56
+ def integration_status_proc(nexpose_connection:, scan_id:, status:)
57
+ Proc.new { nexpose_connection.scan_status(scan_id).downcase == status.downcase }
58
+ end
59
+
60
+ def timeout_retry?
61
+ if @retry_count > 0
62
+ @retry_count -= 1
63
+ true
64
+ else
65
+ false
66
+ end
67
+ end
68
+ end
69
+
70
+ class Poller
71
+ ## Stand alone object to handle waiting logic.
72
+ attr_reader :timeout, :polling_interval, :poll_begin
73
+
74
+ def initialize(timeout: nil, polling_interval: nil)
75
+ global_timeout = set_global_timeout
76
+ @timeout = timeout.nil? ? global_timeout : timeout
77
+
78
+ global_polling = set_polling_interval
79
+ @polling_interval = polling_interval.nil? ? global_polling : polling_interval
80
+ end
81
+
82
+ def wait(condition)
83
+ @poll_begin = Time.now
84
+ loop do
85
+ break if condition.call
86
+ raise TimeoutError if @poll_begin + @timeout < Time.now
87
+ sleep @polling_interval
88
+ end
89
+ end
90
+
91
+ private
92
+
93
+ def set_global_timeout
94
+ default_timeout = 120
95
+ ENV['GLOBAL_TIMEOUT'].nil? ? default_timeout : ENV['GLOBAL_TIMEOUT']
96
+ end
97
+
98
+ def set_polling_interval
99
+ default_polling = 1
100
+ ENV['GLOBAL_POLLING_INTERVAL'].nil? ? default_polling : ENV['GLOBAL_POLLING_INTERVAL']
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,252 @@
1
+ module Nexpose
2
+
3
+ # Object that represents web credential defined in site configuration.
4
+ module WebCredentials
5
+
6
+ module WebAppAuthType
7
+ HTML_FORM = 'htmlform' # Represent HTML form credentials.
8
+ HTTP_HEADER = 'httpheaders' # Represent HTTP header credentials.
9
+ end
10
+
11
+ # Object that represents Header name-value pairs, associated with Web Session Authentication.
12
+ #
13
+ class Header
14
+
15
+ # Name, one per Header
16
+ attr_reader :name
17
+ # Value, one per Header
18
+ attr_reader :value
19
+
20
+ # Construct with name value pair
21
+ def initialize(name, value)
22
+ @name = name
23
+ @value = value
24
+ end
25
+
26
+ def to_json
27
+ JSON.generate(to_h)
28
+ end
29
+
30
+ def to_h
31
+ header = Hash.new
32
+ header[@name] = @value
33
+ header
34
+ end
35
+ end
36
+
37
+ # Object that represents Headers, associated with Web Session Authentication.
38
+ #
39
+ class Headers < APIObject
40
+
41
+ # A regular expression used to match against the response to identify authentication failures.
42
+ attr_reader :soft403Pattern
43
+ # Base URL of the application for which the form authentication applies.
44
+ attr_reader :baseURL
45
+ # When using HTTP headers, this represents the set of headers to pass with the authentication request.
46
+ attr_reader :headers
47
+ # name of the html header
48
+ attr_reader :name
49
+ # is this enable for the site configuration
50
+ attr_accessor :enabled
51
+ #service type of header
52
+ attr_reader :service
53
+ # id of the header
54
+ attr_reader :id
55
+
56
+
57
+ def initialize(name, baseURL, soft403Pattern, id = -1, enabled = true)
58
+ @headers = {}
59
+ @name = name
60
+ @baseURL = baseURL
61
+ @soft403Pattern = soft403Pattern
62
+ @service = WebAppAuthType::HTTP_HEADER
63
+ @enabled = enabled
64
+ @id = id
65
+ end
66
+
67
+ def add_header(header)
68
+ @headers = @headers.merge(header.to_h)
69
+ end
70
+
71
+ def to_json
72
+ JSON.generate(to_h)
73
+ end
74
+
75
+ def to_h
76
+ { id: id,
77
+ service: service,
78
+ enabled: enabled,
79
+ name: name,
80
+ headers: headers,
81
+ baseURL: baseURL,
82
+ soft403Pattern: soft403Pattern
83
+ }
84
+ end
85
+
86
+ def ==(other)
87
+ eql?(other)
88
+ end
89
+
90
+ def eql?(other)
91
+ id.eql?(other.id) &&
92
+ service.eql?(other.service) &&
93
+ enabled.eql?(other.enabled) &&
94
+ name.eql?(other.name) &&
95
+ headers.eql?(other.headers) &&
96
+ baseURL.eql?(other.baseURL) &&
97
+ soft403Pattern.eql?(other.soft403Pattern)
98
+ end
99
+ end
100
+
101
+ # When using HTML form, this represents the login form information.
102
+ #
103
+ class Field
104
+ # The name of the HTML field (form parameter).
105
+ attr_reader :name
106
+ # The value of the HTML field (form parameter).
107
+ attr_reader :value
108
+ # The type of the HTML field (form parameter).
109
+ attr_reader :type
110
+ # Is the HTML field (form parameter) dynamically generated? If so,
111
+ # the login page is requested and the value of the field is extracted
112
+ # from the response.
113
+ attr_reader :dynamic
114
+ # If the HTML field (form parameter) is a radio button, checkbox or select
115
+ # field, this flag determines if the field should be checked (selected).
116
+ attr_reader :checked
117
+
118
+ def initialize(name, value, type, dynamic, checked)
119
+ @name = name
120
+ @value = value
121
+ @type = type
122
+ @dynamic = dynamic
123
+ @checked = checked
124
+ end
125
+
126
+ def to_json
127
+ JSON.generate(to_h)
128
+ end
129
+
130
+ def to_h
131
+ {
132
+ value: value,
133
+ type: type,
134
+ name: name,
135
+ dynamic: dynamic,
136
+ checked: checked
137
+ }
138
+ end
139
+ end
140
+
141
+ # When using HTML form, this represents the login form information.
142
+ #
143
+ class HTMLForm
144
+
145
+ # The name of the form being submitted.
146
+ attr_reader :name
147
+ # The HTTP action (URL) through which to submit the login form.
148
+ attr_reader :action
149
+ # The HTTP request method with which to submit the form.
150
+ attr_reader :method
151
+ # The HTTP encoding type with which to submit the form.
152
+ attr_reader :encType
153
+ # The fields in the HTML Form
154
+ attr_reader :fields
155
+
156
+ def initialize(name, action, method, encType)
157
+ @name = name
158
+ @action = action
159
+ @method = method
160
+ @encType = encType
161
+ @fields = []
162
+ end
163
+
164
+ def add_field(field)
165
+ @fields << field.to_h
166
+ end
167
+
168
+ def to_json
169
+ JSON.generate(to_h)
170
+ end
171
+
172
+ def to_h
173
+ { name: name,
174
+ action: action,
175
+ method: method,
176
+ encType: encType,
177
+ fields: fields,
178
+ parentPage: action
179
+ }
180
+ end
181
+ end
182
+
183
+ # When using HTML form, this represents the login form information.
184
+ #
185
+ class HTMLForms < APIObject
186
+
187
+ # A regular expression used to match against the response to identify authentication failures.
188
+ attr_reader :soft403Pattern
189
+ # Base URL of the application for which the form authentication applies.
190
+ attr_reader :baseURL
191
+ # The URL of the login page containing the login form.
192
+ attr_reader :loginURL
193
+ # name of the html header
194
+ attr_reader :name
195
+ # is this enable for the site configuration
196
+ attr_accessor :enabled
197
+ #service type of header
198
+ attr_reader :service
199
+ # id of the header
200
+ attr_reader :id
201
+ # The forms to authenticate with
202
+ attr_reader :form
203
+
204
+ def initialize(name, baseURL, loginURL, soft403Pattern, id = -1, enabled = true)
205
+ @name = name
206
+ @baseURL = baseURL
207
+ @loginURL = loginURL
208
+ @soft403Pattern = soft403Pattern
209
+ @service = WebAppAuthType::HTML_FORM
210
+ @enabled = enabled
211
+ @id = id
212
+ end
213
+
214
+ def add_html_form(html_form)
215
+ @form = html_form
216
+ end
217
+
218
+ def to_json
219
+ JSON.generate(to_h)
220
+ end
221
+
222
+ def to_h
223
+ { id: id,
224
+ service: service,
225
+ enabled: enabled,
226
+ name: name,
227
+ form: form.to_h,
228
+ baseURL: baseURL,
229
+ loginURL: loginURL,
230
+ soft403Pattern: soft403Pattern
231
+ }
232
+ end
233
+
234
+ def ==(other)
235
+ eql?(other)
236
+ end
237
+
238
+ def eql?(other)
239
+ id.eql?(other.id) &&
240
+ service.eql?(other.service) &&
241
+ enabled.eql?(other.enabled) &&
242
+ name.eql?(other.name) &&
243
+ form.eql?(other.form) &&
244
+ baseURL.eql?(other.baseURL) &&
245
+ loginURL.eql?(other.loginURL) &&
246
+ soft403Pattern.eql?(other.soft403Pattern)
247
+ end
248
+
249
+ end
250
+ end
251
+ end
252
+