contrast-agent 7.4.1 → 7.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2b62e21b5f8d2c3d786aaaac33005487eab07fd07438e9fe20532742f926bcb0
4
- data.tar.gz: 37ff12d328d1a58bddb75e4f0e9c799b44df881daaa78bea62f565f95ae715ff
3
+ metadata.gz: f6b34ada48cf9e91e05d85ea0f003575119b751bf135ef49709fb1501c475b3e
4
+ data.tar.gz: 2915fb037c832fec213dfc7835b8d732e1c22005987ef2c4287dfb55264d41a2
5
5
  SHA512:
6
- metadata.gz: 46f9f576d3a28befa4681e73a250af98602c100d50ace13a1e3fcaa5a22ca2a0d0695b4b19492677d57183ff56203d2f57a5470b7c6fa0fc9c1b15bbbd49dc16
7
- data.tar.gz: 427a3dafa3d8108a4aa2130ff02c8c4d52539f7d1c7b265ba78d60dc10f86f142a2b4f1132ae0be02c0c9a41c043ab1459c0a0e3a836dbced2f0db4b550aa970
6
+ metadata.gz: 627d1959c5993100f6bc08624d1a68627b02af18f6aa48c074f0cf374925877761b09077ce21366b6d5ddafb3185907472564753b3fbab65f6f7d74d2b74dc10
7
+ data.tar.gz: 224228b9e9c276c39e6d78a330a456b37cf550b4155c4b96f5ff1d29e7bf7846521c102f510061f8d6ec5e9245b99fe567cce7a5202dd2acfd62c6e7bb9ca795
@@ -27,7 +27,8 @@ module Contrast
27
27
  pp_id: @ppid,
28
28
  process_pid: Process.pid,
29
29
  process_pp_id: Process.ppid)
30
-
30
+ $stdout.puts('[Contrast Agent] Graceful shutdown...')
31
+ report_traces
31
32
  context = Contrast::Agent::REQUEST_TRACKER.current
32
33
  return unless context
33
34
 
@@ -39,6 +40,20 @@ module Contrast
39
40
  end
40
41
  Contrast::Agent.reporter&.send_event_immediately(context.activity)
41
42
  end
43
+
44
+ def self.report_traces
45
+ return unless Contrast::ASSESS.enabled?
46
+
47
+ collection = Contrast::Agent::Reporting::ReportingStorage.collection
48
+
49
+ # report gathered traces:
50
+ return if collection.empty?
51
+
52
+ collection.each do |_id, finding|
53
+ preflight = Contrast::Agent::Reporting::BuildPreflight.generate(finding)
54
+ Contrast::Agent.reporter&.send_event_immediately(preflight)
55
+ end
56
+ end
42
57
  end
43
58
  end
44
59
  end
@@ -20,6 +20,7 @@ require 'contrast/agent/protect/rule/xss/reflected_xss_input_classification'
20
20
  require 'contrast/agent/protect/rule/xss/xss'
21
21
  require 'contrast/components/logger'
22
22
  require 'contrast/utils/object_share'
23
+ require 'contrast/utils/duck_utils'
23
24
  require 'contrast/agent/protect/rule/input_classification/base64_statistic'
24
25
  require 'json'
25
26
 
@@ -105,12 +106,10 @@ module Contrast
105
106
  return unless (protect_rule = Contrast::PROTECT.rule(rule_id)) && protect_rule.enabled?
106
107
 
107
108
  input_analysis.inputs.each do |input_type, value|
108
- # TODO: RUBY-2110 Update the HEADER handling if possible.
109
- # Analyze only Header values:
110
- # This may break bot blocker rule:
111
- # value = value.values if input_type == HEADER
112
- next if value.nil? || value.empty?
109
+ value = handle_header(input_type, value)
110
+ next if Contrast::Utils::DuckUtils.empty_duck?(value)
113
111
 
112
+ # Traverse only the Header values:
114
113
  Timeout.timeout(interval) do
115
114
  protect_rule.classification.classify(rule_id, input_type, value, input_analysis)
116
115
  end
@@ -156,6 +155,16 @@ module Contrast
156
155
 
157
156
  private
158
157
 
158
+ # Extracts header values, and skips keys if input_type is Header.
159
+ # @param input_type [Symbol]
160
+ # @param value [Hash, nil]
161
+ # @return value [Hash, nil]
162
+ def handle_header input_type, value
163
+ return value&.values || [] if input_type == Contrast::Agent::Reporting::InputType::HEADER
164
+
165
+ value
166
+ end
167
+
159
168
  # Extract the filename and name of the Content Disposition Header.
160
169
  #
161
170
  # @param inputs [Hash<Contrast::Agent::Protect::InputType => user_inputs>]
@@ -22,32 +22,6 @@ module Contrast
22
22
  class << self
23
23
  include Contrast::Agent::Protect::Rule::InputClassification::Base
24
24
 
25
- # Input Classification stage is done to determine if an user input is
26
- # DEFINITEATTACK or to be ignored.
27
- #
28
- # @param rule_id [String] Name of the protect rule.
29
- # @param input_type [Contrast::Agent::Reporting::InputType] The type of the user input.
30
- # @param value [Hash<String>] the value of the input.
31
- # @param input_analysis [Contrast::Agent::Reporting::InputAnalysis] Holds all the results from the
32
- # agent analysis from the current
33
- # Request.
34
- # @return ia [Contrast::Agent::Reporting::InputAnalysis, nil] with updated results.
35
- def classify rule_id, input_type, value, input_analysis
36
- return unless (rule = Contrast::PROTECT.rule(rule_id))
37
- return unless rule.applicable_user_inputs.include?(input_type)
38
- return unless input_analysis.request
39
-
40
- value.each_value do |val|
41
- result = create_new_input_result(input_analysis.request, rule.rule_name, input_type, val)
42
- append_result(input_analysis, result)
43
- end
44
-
45
- input_analysis
46
- rescue StandardError => e
47
- logger.debug("An Error was recorded in the input classification of the #{ rule_id }", error: e)
48
- nil
49
- end
50
-
51
25
  private
52
26
 
53
27
  # This methods checks if input is tagged WORTHWATCHING or IGNORE matches value with it's
@@ -21,6 +21,11 @@ module Contrast
21
21
  include Contrast::Components::Logger::InstanceMethods
22
22
  include Contrast::Agent::Reporting::InputType
23
23
  NAME = 'cmd-injection'
24
+ APPLICABLE_USER_INPUTS = [
25
+ BODY, COOKIE_VALUE, HEADER, PARAMETER_NAME,
26
+ PARAMETER_VALUE, JSON_VALUE, MULTIPART_VALUE,
27
+ MULTIPART_FIELD_NAME, XML_VALUE, DWR_VALUE
28
+ ].cs__freeze
24
29
 
25
30
  def rule_name
26
31
  NAME
@@ -24,7 +24,7 @@ module Contrast
24
24
  COOKIE_VALUE, PARAMETER_VALUE, HEADER, JSON_VALUE, MULTIPART_VALUE, XML_VALUE, DWR_VALUE
25
25
  ].cs__freeze
26
26
 
27
- BASE64_INPUT_TYPES = [BODY, COOKIE_VALUE, PARAMETER_VALUE, MULTIPART_VALUE, XML_VALUE].cs__freeze
27
+ BASE64_INPUT_TYPES = [BODY, HEADER, COOKIE_VALUE, PARAMETER_VALUE, MULTIPART_VALUE, XML_VALUE].cs__freeze
28
28
 
29
29
  class << self
30
30
  include Contrast::Components::Logger::InstanceMethods
@@ -183,9 +183,6 @@ module Contrast
183
183
  return value unless Contrast::PROTECT.normalize_base64?
184
184
  return value unless BASE64_INPUT_TYPES.include?(input_type)
185
185
 
186
- # TODO: RUBY-2110 Update the HEADER handling if possible.
187
- # We need only the Header values.
188
-
189
186
  cs__decode64(value, input_type)
190
187
  end
191
188
  end
@@ -37,7 +37,7 @@ module Contrast
37
37
  # The Base64 method will return printable ascii characters, so we can use this to determine if the value is
38
38
  # encoded or not.
39
39
  #
40
- # The solution in this case is encodind the value, and then decoding it. If the value is already encoded
40
+ # The solution in this case is encoding the value, and then decoding it. If the value is already encoded
41
41
  # it will not be eq to the original value. If the value is not encoded, it will be eq to the original value.
42
42
  #
43
43
  # @param value [String] input to check for encoding status.
@@ -96,11 +96,43 @@ module Contrast
96
96
  def cs__decode64 value, input_type
97
97
  return value unless cs__base64?(value, input_type)
98
98
 
99
- Base64.decode64(value)
99
+ new_value = try_base64_decode(value)
100
+ new_value, success = normalize_encoding(new_value)
101
+ return new_value if success
102
+
103
+ value
100
104
  rescue StandardError => e
101
105
  logger.error('Error while decoding base64', error: e, message: e.message, backtrace: e.backtrace)
102
106
  value
103
107
  end
108
+
109
+ private
110
+
111
+ # Try and decode the value, do not use decoding if the value have zero bytes.
112
+ def try_base64_decode value
113
+ new_value = Base64.decode64(value)
114
+ # check for null bytes:
115
+ return new_value unless new_value.bytes.select(&:zero?).any?
116
+
117
+ value
118
+ end
119
+
120
+ # Detecting encoded Base64 is not perfect. In some cases it will detect certain inputs as
121
+ # encoded and will try to decode them. Even if the decoding is successful, the value may be
122
+ # encoded back to ASCII format. AgentLib will raise UTF-8 error in this case.
123
+ # This method will try to normalize the encoding to UTF-8. If the encoding fails, this means
124
+ # that the decoding was not successful and the value will be returned as is. Otherwise a
125
+ # base64 decoded string with ASCII-8BIT encoding will be parsed to UTF-8 without errors.
126
+ #
127
+ # @param value [String] input to normalize.
128
+ # @return [Array<String, Boolean>] normalized value and success flag.
129
+ def normalize_encoding value
130
+ new_value = value.dup.encode!('Windows-1252').force_encoding('UTF-8')
131
+ [new_value, true]
132
+ rescue StandardError
133
+ # encoding failed, or the decoding from base64 failed.
134
+ [nil, false]
135
+ end
104
136
  end
105
137
  end
106
138
  end
@@ -33,42 +33,12 @@ module Contrast
33
33
  # @return
34
34
  def to_a
35
35
  @_to_a ||= [
36
- UNDEFINED_TYPE, BODY, COOKIE_NAME, COOKIE_VALUE, HEADER, PARAMETER_NAME, PARAMETER_VALUE,
37
- QUERYSTRING, URI, SOCKET, JSON_VALUE, JSON_ARRAYED_VALUE, MULTIPART_CONTENT_TYPE, MULTIPART_VALUE,
38
- MULTIPART_FIELD_NAME, MULTIPART_NAME, XML_VALUE, DWR_VALUE, METHOD, REQUEST, URL_PARAMETER, UNKNOWN
36
+ UNDEFINED_TYPE, BODY, COOKIE_NAME, COOKIE_VALUE, HEADER, PARAMETER_NAME,
37
+ PARAMETER_VALUE, QUERYSTRING, URI, SOCKET, JSON_VALUE, JSON_ARRAYED_VALUE, MULTIPART_CONTENT_TYPE,
38
+ MULTIPART_VALUE, MULTIPART_FIELD_NAME, MULTIPART_NAME, XML_VALUE, DWR_VALUE, METHOD, REQUEST,
39
+ URL_PARAMETER, UNKNOWN
39
40
  ]
40
41
  end
41
-
42
- # This is a hash of the input types and their corresponding values.
43
- #
44
- # @return [Hash]
45
-
46
- def to_hash
47
- {
48
- UNDEFINED_TYPE: '1',
49
- BODY: '2',
50
- COOKIE_NAME: '3',
51
- COOKIE_VALUE: '4',
52
- HEADER: '5',
53
- PARAMETER_NAME: '6',
54
- PARAMETER_VALUE: '7',
55
- QUERYSTRING: '8',
56
- URI: '9',
57
- SOCKET: '10',
58
- JSON_VALUE: '11',
59
- JSON_ARRAYED_VALUE: '12',
60
- MULTIPART_CONTENT_TYPE: '13',
61
- MULTIPART_VALUE: '14',
62
- MULTIPART_FIELD_NAME: '15',
63
- MULTIPART_NAME: '16',
64
- XML_VALUE: '17',
65
- DWR_VALUE: '18',
66
- METHOD: '19',
67
- REQUEST: '20',
68
- URL_PARAMETER: '21',
69
- UNKNOWN: '22'
70
- }
71
- end
72
42
  end
73
43
  end
74
44
  end
@@ -43,7 +43,7 @@ module Contrast
43
43
  appPath: ::Contrast::APP_CONTEXT.name, # rubocop:disable Security/Module/Name
44
44
  appVersion: ::Contrast::APP_CONTEXT.version,
45
45
  code: CODE,
46
- data: '',
46
+ data: @data || '',
47
47
  key: 0,
48
48
  session_id: ::Contrast::ASSESS.session_id,
49
49
  routes: @routes.map(&:to_controlled_hash)
@@ -17,14 +17,14 @@ module Contrast
17
17
  # @param finding [Contrast::Agent::Reporting::Finding]
18
18
  # @return [Contrast::Agent::Reporting::Preflight, nil]
19
19
  def generate finding
20
- return unless finding
20
+ return unless finding&.cs__is_a?(Contrast::Agent::Reporting::Finding)
21
21
 
22
22
  new_preflight = Contrast::Agent::Reporting::Preflight.new
23
23
  new_preflight_message = Contrast::Agent::Reporting::PreflightMessage.new
24
- finding.routes.each do |route|
25
- new_preflight_message.routes << route
24
+ routes = finding.routes
25
+ unless Contrast::Utils::DuckUtils.empty_duck?(routes)
26
+ routes.each { |route| new_preflight_message.routes << route }
26
27
  end
27
- new_preflight_message.hash_code = finding.hash_code
28
28
  new_preflight_message.data = "#{ finding.rule_id },#{ finding.hash_code }"
29
29
  new_preflight.messages << new_preflight_message
30
30
  return new_preflight unless Contrast::Utils::DuckUtils.empty_duck?(new_preflight.messages)
@@ -118,7 +118,11 @@ module Contrast
118
118
  mode.resend.reset_rescue_attempts
119
119
  findings_to_return.each do |index|
120
120
  preflight_message = event.messages[index.to_i]
121
- corresponding_finding = Contrast::Agent::Reporting::ReportingStorage.delete(preflight_message&.data)
121
+ preflight_data = preflight_message&.data
122
+ corresponding_finding = Contrast::Agent::Reporting::ReportingStorage.delete(preflight_data)
123
+ if Contrast::Agent::REQUEST_TRACKER.current
124
+ Contrast::Agent::REQUEST_TRACKER.current.reported_findings << preflight_data
125
+ end
122
126
  next unless corresponding_finding
123
127
 
124
128
  send_event(corresponding_finding, connection)
@@ -263,7 +263,7 @@ module Contrast
263
263
  extract_response_last_modified(response, event)
264
264
  populate_response(response_data, event)
265
265
  rescue StandardError => e
266
- logger.error('Unable to convert response', e)
266
+ logger.error('Unable to convert response', error: e)
267
267
  nil
268
268
  end
269
269
 
@@ -3,6 +3,6 @@
3
3
 
4
4
  module Contrast
5
5
  module Agent
6
- VERSION = '7.4.1'
6
+ VERSION = '7.5.0'
7
7
  end
8
8
  end
@@ -14,7 +14,7 @@ module Contrast
14
14
 
15
15
  # Add any known cases where parsing error might arise from older json parser:
16
16
  # @return [Array<String>]
17
- SPECIAL_CASES = ["\"\""].cs__freeze # rubocop:disable Style/StringLiterals
17
+ SPECIAL_CASES = ["\"\"", "\"0\""].cs__freeze # rubocop:disable Style/StringLiterals
18
18
 
19
19
  # Parses a string using JSON.parser. This method is used instead of standard JSON.parse to
20
20
  # support older versions of json gem => not supporting key-value second parameter, which is
data/ruby-agent.gemspec CHANGED
@@ -116,9 +116,10 @@ end
116
116
  # dependencies.csv in this directory to indicate that and create a
117
117
  # corresponding update to the fake gem server data in TeamServer.
118
118
  def self.add_dependencies spec
119
- spec.add_dependency 'ffi', '~> 1.0'
119
+ # TODO: RUBY-99999 investigate init_with_options segmentation fault
120
+ spec.add_dependency 'ffi'
120
121
  spec.add_dependency 'ougai', '>= 1.8', '< 3.0.0'
121
- spec.add_dependency 'rack', '~> 2.0'
122
+ spec.add_dependency 'rack', '>= 2.0', '< 4.0.0'
122
123
 
123
124
  # bind this directly as we've had issues w/ build changes on bug release
124
125
  spec.add_dependency 'contrast-agent-lib', '1.1.1'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: contrast-agent
3
3
  version: !ruby/object:Gem::Version
4
- version: 7.4.1
4
+ version: 7.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - galen.palmer@contrastsecurity.com
@@ -10,10 +10,10 @@ authors:
10
10
  - alex.macdonald@contrastsecurity.com
11
11
  - mark.petersen@contrastsecurity.com
12
12
  - joshua.reed@contrastsecurity.com
13
- autorequire:
13
+ autorequire:
14
14
  bindir: exe
15
15
  cert_chain: []
16
- date: 2023-09-21 00:00:00.000000000 Z
16
+ date: 2023-10-06 00:00:00.000000000 Z
17
17
  dependencies:
18
18
  - !ruby/object:Gem::Dependency
19
19
  name: bundler
@@ -619,16 +619,16 @@ dependencies:
619
619
  name: ffi
620
620
  requirement: !ruby/object:Gem::Requirement
621
621
  requirements:
622
- - - "~>"
622
+ - - ">="
623
623
  - !ruby/object:Gem::Version
624
- version: '1.0'
624
+ version: '0'
625
625
  type: :runtime
626
626
  prerelease: false
627
627
  version_requirements: !ruby/object:Gem::Requirement
628
628
  requirements:
629
- - - "~>"
629
+ - - ">="
630
630
  - !ruby/object:Gem::Version
631
- version: '1.0'
631
+ version: '0'
632
632
  - !ruby/object:Gem::Dependency
633
633
  name: ougai
634
634
  requirement: !ruby/object:Gem::Requirement
@@ -653,16 +653,22 @@ dependencies:
653
653
  name: rack
654
654
  requirement: !ruby/object:Gem::Requirement
655
655
  requirements:
656
- - - "~>"
656
+ - - ">="
657
657
  - !ruby/object:Gem::Version
658
658
  version: '2.0'
659
+ - - "<"
660
+ - !ruby/object:Gem::Version
661
+ version: 4.0.0
659
662
  type: :runtime
660
663
  prerelease: false
661
664
  version_requirements: !ruby/object:Gem::Requirement
662
665
  requirements:
663
- - - "~>"
666
+ - - ">="
664
667
  - !ruby/object:Gem::Version
665
668
  version: '2.0'
669
+ - - "<"
670
+ - !ruby/object:Gem::Version
671
+ version: 4.0.0
666
672
  - !ruby/object:Gem::Dependency
667
673
  name: contrast-agent-lib
668
674
  requirement: !ruby/object:Gem::Requirement
@@ -1370,7 +1376,7 @@ metadata:
1370
1376
  support_uri: https://support.contrastsecurity.com
1371
1377
  trouble_shooting_uri: https://support.contrastsecurity.com/hc/en-us/search?utf8=%E2%9C%93&query=Ruby
1372
1378
  wiki_uri: https://docs.contrastsecurity.com/
1373
- post_install_message:
1379
+ post_install_message:
1374
1380
  rdoc_options: []
1375
1381
  require_paths:
1376
1382
  - lib
@@ -1388,8 +1394,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
1388
1394
  - !ruby/object:Gem::Version
1389
1395
  version: '0'
1390
1396
  requirements: []
1391
- rubygems_version: 3.2.33
1392
- signing_key:
1397
+ rubygems_version: 3.3.26
1398
+ signing_key:
1393
1399
  specification_version: 4
1394
1400
  summary: Contrast Security's agent for rack-based applications.
1395
1401
  test_files: []