contrast-agent 7.4.0 → 7.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.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/lib/contrast/agent/hooks/at_exit_hook.rb +16 -1
  3. data/lib/contrast/agent/middleware/middleware.rb +1 -1
  4. data/lib/contrast/agent/protect/input_analyzer/input_analyzer.rb +19 -12
  5. data/lib/contrast/agent/protect/input_analyzer/worth_watching_analyzer.rb +55 -20
  6. data/lib/contrast/agent/protect/policy/rule_applicator.rb +1 -4
  7. data/lib/contrast/agent/protect/rule/base.rb +56 -25
  8. data/lib/contrast/agent/protect/rule/bot_blocker/bot_blocker.rb +12 -4
  9. data/lib/contrast/agent/protect/rule/bot_blocker/bot_blocker_input_classification.rb +0 -26
  10. data/lib/contrast/agent/protect/rule/cmdi/cmd_injection.rb +2 -5
  11. data/lib/contrast/agent/protect/rule/cmdi/cmdi_backdoors.rb +2 -4
  12. data/lib/contrast/agent/protect/rule/cmdi/cmdi_base_rule.rb +2 -1
  13. data/lib/contrast/agent/protect/rule/deserialization/deserialization.rb +4 -4
  14. data/lib/contrast/agent/protect/rule/input_classification/base.rb +1 -4
  15. data/lib/contrast/agent/protect/rule/input_classification/encoding.rb +34 -2
  16. data/lib/contrast/agent/protect/rule/no_sqli/no_sqli.rb +5 -2
  17. data/lib/contrast/agent/protect/rule/path_traversal/path_traversal.rb +12 -7
  18. data/lib/contrast/agent/protect/rule/path_traversal/path_traversal_semantic_security_bypass.rb +2 -2
  19. data/lib/contrast/agent/protect/rule/sqli/sqli_base_rule.rb +2 -3
  20. data/lib/contrast/agent/protect/rule/sqli/sqli_semantic/sqli_dangerous_functions.rb +3 -4
  21. data/lib/contrast/agent/protect/rule/unsafe_file_upload/unsafe_file_upload.rb +3 -0
  22. data/lib/contrast/agent/protect/rule/utils/builders.rb +3 -4
  23. data/lib/contrast/agent/protect/rule/utils/filters.rb +32 -16
  24. data/lib/contrast/agent/protect/rule/xss/xss.rb +80 -0
  25. data/lib/contrast/agent/protect/rule/xxe/xxe.rb +9 -2
  26. data/lib/contrast/agent/reporting/details/xss_match.rb +17 -0
  27. data/lib/contrast/agent/reporting/input_analysis/input_analysis.rb +32 -0
  28. data/lib/contrast/agent/reporting/input_analysis/input_type.rb +4 -34
  29. data/lib/contrast/agent/reporting/reporting_events/finding.rb +1 -5
  30. data/lib/contrast/agent/reporting/reporting_events/preflight_message.rb +2 -5
  31. data/lib/contrast/agent/reporting/reporting_utilities/build_preflight.rb +4 -4
  32. data/lib/contrast/agent/reporting/reporting_utilities/reporter_client_utils.rb +5 -1
  33. data/lib/contrast/agent/reporting/reporting_utilities/response_handler_utils.rb +1 -1
  34. data/lib/contrast/agent/request/request_context_extend.rb +0 -2
  35. data/lib/contrast/agent/version.rb +1 -1
  36. data/lib/contrast/components/assess.rb +4 -0
  37. data/lib/contrast/framework/rails/support.rb +2 -2
  38. data/lib/contrast/logger/cef_log.rb +30 -4
  39. data/lib/contrast/utils/io_util.rb +3 -0
  40. data/lib/contrast/utils/json.rb +1 -1
  41. data/lib/contrast/utils/log_utils.rb +21 -10
  42. data/ruby-agent.gemspec +3 -2
  43. metadata +18 -12
@@ -52,8 +52,6 @@ module Contrast
52
52
  if (ia = Contrast::Agent::Protect::InputAnalyzer.analyse(request))
53
53
  # Handle prefilter
54
54
  Contrast::Agent::Protect::InputAnalyzer.input_classification(ia, prefilter: true)
55
- # Reflected xss infilter
56
- Contrast::Agent::Protect::InputAnalyzer.input_classification_for('reflected-xss', ia)
57
55
  @agent_input_analysis = ia
58
56
  else
59
57
  logger.trace('Analysis from Agent was empty.')
@@ -3,6 +3,6 @@
3
3
 
4
4
  module Contrast
5
5
  module Agent
6
- VERSION = '7.4.0'
6
+ VERSION = '7.5.0'
7
7
  end
8
8
  end
@@ -237,6 +237,10 @@ module Contrast
237
237
 
238
238
  # The id for this process, based on the session metadata or id provided by the user, as indicated in
239
239
  # application startup.
240
+ #
241
+ # The ID of the current application run, as returned by the application settings endpoint or set by
242
+ # application.session_id. If there is no session associated with this run, this field should be omitted
243
+ # when reporting to TS.
240
244
  def session_id
241
245
  ::Contrast::SETTINGS.assess_state.session_id
242
246
  end
@@ -59,7 +59,7 @@ module Contrast
59
59
  # ActionDispatch::Journey::Path::Pattern::MatchData, Hash, ActionDispatch::Journey::Route, Array<String>
60
60
  match, _params, route, path = get_full_route(request.rack_request)
61
61
  unless route
62
- logger.warn("Unable to determine the current route of this request: #{ request.rack_request }")
62
+ logger.debug("Unable to determine the current route of this request: #{ request.rack_request }")
63
63
  return
64
64
  end
65
65
 
@@ -77,7 +77,7 @@ module Contrast
77
77
  new_route_coverage&.attach_rails_data(route, original_url)
78
78
  new_route_coverage
79
79
  rescue StandardError => e
80
- logger.warn('Unable to determine the current route of this request due to exception: ', e)
80
+ logger.error('Unable to determine the current route of this request due to exception: ', e)
81
81
  nil
82
82
  end
83
83
 
@@ -131,7 +131,16 @@ module Contrast
131
131
  log([message, block_entry, outcome], ::Logger::Severity::DEBUG)
132
132
  end
133
133
 
134
- def successful_attack rule_id, outcome, input_type = nil, input_value = nil
134
+ # Log successful attack attack
135
+ #
136
+ # @param rule_id [String] the rule that was triggered
137
+ # @param outcome [String] the outcome of the rule
138
+ # @param input_type [String] the type of input that was detected
139
+ # @param input_value [String] the value of the input that was detected
140
+ # @param attack_context [Contrast::Agent::RequestContext] the request context of the attack
141
+ def successful_attack rule_id, outcome, input_type = nil, input_value = nil, attack_context = nil
142
+ # We may log from the worthwatching Queue with saved attack_context
143
+ update_logger_formatter(@_cef_logger, new_context: attack_context) if attack_context
135
144
  if input_type.present? && input_value.present?
136
145
  successful_attack_with_input = "#{ input_type } had a value that successfully exploited" \
137
146
  "#{ rule_id } - #{ input_value }"
@@ -142,7 +151,16 @@ module Contrast
142
151
  end
143
152
  end
144
153
 
145
- def ineffective_attack rule_id, outcome, input_type = nil, input_value = nil
154
+ # Log ineffective attack attack
155
+ #
156
+ # @param rule_id [String] the rule that was triggered
157
+ # @param outcome [String] the outcome of the rule
158
+ # @param input_type [String] the type of input that was detected
159
+ # @param input_value [String] the value of the input that was detected
160
+ # @param attack_context [Contrast::Agent::RequestContext] the request context of the attack
161
+ def ineffective_attack rule_id, outcome, input_type = nil, input_value = nil, attack_context = nil
162
+ # We may log from the worthwatching Queue with saved attack_context
163
+ update_logger_formatter(@_cef_logger, new_context: attack_context) if attack_context
146
164
  if input_type.present? && input_value.present?
147
165
  ineffective_attack_with_input = "#{ input_type } had a value that matched a signature for, " \
148
166
  "but did not successfully exploit #{ rule_id } - #{ input_value }"
@@ -153,8 +171,16 @@ module Contrast
153
171
  end
154
172
  end
155
173
 
156
- # newer - currently not in the agent, currently is a probe for us
157
- def suspicious_attack rule_id, outcome, input_type = nil, input_value = nil
174
+ # Log suspicious attack
175
+ #
176
+ # @param rule_id [String] the rule that was triggered
177
+ # @param outcome [String] the outcome of the rule
178
+ # @param input_type [String] the type of input that was detected
179
+ # @param input_value [String] the value of the input that was detected
180
+ # @param attack_context [Contrast::Agent::RequestContext] the request context of the attack
181
+ def suspicious_attack rule_id, outcome, input_type = nil, input_value = nil, attack_context = nil
182
+ # We may log from the worthwatching Queue with saved attack_context
183
+ update_logger_formatter(@_cef_logger, new_context: attack_context) if attack_context
158
184
  if input_type.present? && input_value.present?
159
185
  suspicious_attack_with = "#{ input_type } included a potential attack value that was detected" \
160
186
  "as suspicious using #{ rule_id } - #{ input_value }"
@@ -7,6 +7,8 @@ module Contrast
7
7
  module Utils
8
8
  # Util for information about an IO
9
9
  module IOUtil
10
+ UNKNOWN_IO = 'unknown'
11
+
10
12
  extend Contrast::Components::Logger::InstanceMethods
11
13
 
12
14
  class << self
@@ -48,6 +50,7 @@ module Contrast
48
50
  return false unless status
49
51
  return false if status.pipe?
50
52
  return false if status.socket?
53
+ return false if status.ftype == UNKNOWN_IO
51
54
 
52
55
  true
53
56
  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
@@ -146,6 +146,7 @@ module Contrast
146
146
  private
147
147
 
148
148
  def build path: STDOUT_STR, level_const: DEFAULT_LEVEL
149
+ context = Contrast::Agent::REQUEST_TRACKER.current
149
150
  logger = case path
150
151
  when STDOUT_STR, STDERR_STR
151
152
  ::Logger.new(Object.cs__const_get(path))
@@ -154,15 +155,23 @@ module Contrast
154
155
  end
155
156
  logger.progname = PROGNAME
156
157
  logger.level = level_const
157
- change_logger_formatter(logger)
158
+ update_logger_formatter(logger, new_context: context)
158
159
  logger
159
160
  end
160
161
 
161
162
  def context
162
- Contrast::Agent::REQUEST_TRACKER.current
163
+ @_context ||= Contrast::Agent::REQUEST_TRACKER.current
163
164
  end
164
165
 
165
- def change_logger_formatter logger
166
+ def context_update new_context
167
+ @_context = new_context unless new_context.nil?
168
+ end
169
+
170
+ # @param logger [Logger]
171
+ # @param new_context [Contrast::Agent::RequestContext]
172
+ def update_logger_formatter logger, new_context: nil
173
+ context_update(new_context) if new_context
174
+
166
175
  ip_address = extract_ip_address
167
176
  logger.formatter = proc do |severity, datetime, progname, msg|
168
177
  date_format = datetime.strftime(DATE_TIME_FORMAT)
@@ -206,7 +215,7 @@ module Contrast
206
215
  request_method = assign_request_method(context)
207
216
  app_name = ::Contrast::APP_CONTEXT.name # rubocop:disable Security/Module/Name
208
217
  attach_request_and_sender_info(message, sender_info)
209
- message << "request=#{ context.request.url } "
218
+ message << "request=#{ context&.request&.url } "
210
219
  message << "requestMethod=#{ request_method } "
211
220
  message << "app=#{ app_name } "
212
221
  message << "outcome=#{ outcome } "
@@ -238,16 +247,18 @@ module Contrast
238
247
  end
239
248
 
240
249
  def extract_sender_ip
241
- request_headers = context.activity.request.headers&.transform_keys(&:to_s)
250
+ request_headers = context&.activity&.request&.headers&.transform_keys(&:to_s)
251
+ return unless request_headers
252
+
242
253
  request_headers['X-Forwarded-For']
243
254
  end
244
255
 
245
256
  def assign_request_method context
246
- if context.request.rack_request.env['REQUEST_METHOD'].length.positive?
247
- context.request.rack_request.env['REQUEST_METHOD']
248
- else
249
- DEFAULT_METADATA
250
- end
257
+ request_method = context&.request&.rack_request&.env
258
+ request_method = request_method['REQUEST_METHOD'] if request_method
259
+ return DEFAULT_METADATA if request_method.nil? || !request_method.length.positive?
260
+
261
+ request_method
251
262
  end
252
263
  end
253
264
  end
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.0
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-12 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: []