contrast-agent 7.1.0 → 7.2.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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/ext/extconf_common.rb +88 -14
  3. data/lib/contrast/agent/assess/policy/source_method.rb +13 -4
  4. data/lib/contrast/agent/assess/policy/trigger_method.rb +12 -18
  5. data/lib/contrast/agent/excluder/excluder.rb +64 -31
  6. data/lib/contrast/agent/protect/rule/base.rb +4 -6
  7. data/lib/contrast/agent/protect/rule/bot_blocker/bot_blocker.rb +1 -1
  8. data/lib/contrast/agent/protect/rule/bot_blocker/bot_blocker_input_classification.rb +2 -2
  9. data/lib/contrast/agent/protect/rule/cmdi/cmdi_backdoors.rb +1 -1
  10. data/lib/contrast/agent/protect/rule/cmdi/cmdi_base_rule.rb +1 -1
  11. data/lib/contrast/agent/protect/rule/deserialization/deserialization.rb +2 -2
  12. data/lib/contrast/agent/protect/rule/path_traversal/path_traversal_semantic_security_bypass.rb +1 -1
  13. data/lib/contrast/agent/protect/rule/sqli/sqli_semantic/sqli_dangerous_functions.rb +1 -1
  14. data/lib/contrast/agent/protect/rule/utils/filters.rb +6 -6
  15. data/lib/contrast/agent/protect/rule/xxe/xxe.rb +1 -1
  16. data/lib/contrast/agent/reporting/client/interface.rb +132 -0
  17. data/lib/contrast/agent/reporting/client/interface_base.rb +27 -0
  18. data/lib/contrast/agent/reporting/connection_status.rb +0 -1
  19. data/lib/contrast/agent/reporting/input_analysis/input_analysis_result.rb +6 -4
  20. data/lib/contrast/agent/reporting/reporter.rb +11 -26
  21. data/lib/contrast/agent/reporting/reporting_events/discovered_route.rb +1 -1
  22. data/lib/contrast/agent/reporting/reporting_utilities/audit.rb +10 -3
  23. data/lib/contrast/agent/reporting/reporting_utilities/reporter_client.rb +47 -6
  24. data/lib/contrast/agent/reporting/reporting_utilities/reporter_client_utils.rb +40 -31
  25. data/lib/contrast/agent/reporting/reporting_utilities/resend.rb +144 -0
  26. data/lib/contrast/agent/reporting/reporting_utilities/response_handler.rb +35 -13
  27. data/lib/contrast/agent/reporting/reporting_utilities/response_handler_mode.rb +14 -1
  28. data/lib/contrast/agent/reporting/reporting_utilities/response_handler_utils.rb +11 -11
  29. data/lib/contrast/agent/request/request.rb +27 -12
  30. data/lib/contrast/agent/telemetry/base.rb +18 -19
  31. data/lib/contrast/agent/telemetry/exception/obfuscate.rb +97 -0
  32. data/lib/contrast/agent/telemetry/exception.rb +1 -0
  33. data/lib/contrast/agent/version.rb +1 -1
  34. data/lib/contrast/components/config/sources.rb +6 -5
  35. data/lib/contrast/components/settings.rb +9 -0
  36. data/lib/contrast/config/diagnostics/source_config_value.rb +5 -1
  37. data/lib/contrast/config/diagnostics/tools.rb +4 -4
  38. data/lib/contrast/config/validate.rb +2 -2
  39. data/lib/contrast/configuration.rb +11 -19
  40. data/lib/contrast/framework/grape/support.rb +1 -2
  41. data/lib/contrast/framework/manager.rb +17 -8
  42. data/lib/contrast/framework/rack/support.rb +99 -1
  43. data/lib/contrast/framework/rails/support.rb +1 -2
  44. data/lib/contrast/framework/sinatra/support.rb +1 -2
  45. data/lib/contrast/logger/aliased_logging.rb +18 -9
  46. data/lib/contrast/utils/hash_utils.rb +21 -2
  47. data/lib/contrast/utils/request_utils.rb +14 -0
  48. data/resources/assess/policy.json +11 -0
  49. metadata +6 -3
  50. data/lib/contrast/agent/reporting/input_analysis/details/bot_blocker_details.rb +0 -27
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 81222798666699f86b31b925d531d2ee2229eb7934582d2a502cc61de3ca4e0b
4
- data.tar.gz: 7dd4d41a58600b7d57b5f57cf95c42bd2f6d198f5f1906e93751e33c09efa3e0
3
+ metadata.gz: 1a9469e29e067a8c67e64a771b3d60522c4999ecd1133fdc96c3f6c9ff528f71
4
+ data.tar.gz: 8cbed6cbdf60a9017c80f0edbd5bbe11ea104620ae606f2eac5db4d2c930667f
5
5
  SHA512:
6
- metadata.gz: 02e5d3aa6b342e8c4277ad6cefde65819aed6f6b4a1079cfe7528fcce236ec702aa8a63fc756c403c0df6e3ef38d5d25dc9f1c6e5c450d272122d699b6fd9872
7
- data.tar.gz: 9cc83b5f69edeea949784ae766be7e02c1d589e58b2af5b5c1bd7975dcee1450d294bc7de647f60a9a3f18a1ea05a43bfe452a52d4760ed1b1cfcb0a7339a6ab
6
+ metadata.gz: 3ba2a3f4887a593a5b61544ce6622df70cfe54d1a6b7fe6c4e57927714ab5b6c16ccf13b8c7cd703acb0f55e405c6bcb8e55c781e6f7171d087dc48885942a33
7
+ data.tar.gz: 7d791ee31ffe0857ac51a6d26f7489baf986f3b4445cc697f202df3ab47dd77c2e484a2516ca2ed442afbfe9be02e8139dab2a866dcc0358210774a6c1e33ca9
@@ -5,6 +5,31 @@ require 'mkmf'
5
5
  require 'rbconfig'
6
6
  require_relative '../lib/contrast/agent/version'
7
7
 
8
+ # Create explicit symbol list, that will be set as promise to be loaded dynamic on run time.
9
+ SYMS = %w[
10
+ _assess
11
+ _policy
12
+ _assess_policy
13
+ _assess_propagator
14
+ _core_assess
15
+ _contrast_patcher
16
+ _contrast_check_prepended
17
+ _contrast_check_and_register_instance_patch
18
+ _contrast_register_singleton_prepend_patch
19
+ _contrast_register_patch
20
+ _contrast_register_singleton_patch
21
+ _inst_methods_enter_cntr_scope
22
+ _inst_methods_enter_method_scope
23
+ _inst_methods_exit_cntr_scope
24
+ _inst_methods_exit_method_scope
25
+ _inst_methods_in_cntr_scope
26
+ _rb_sym_method
27
+ _rb_sym_hash_tracked
28
+ _rb_sym_skip_assess_analysis
29
+ _rb_sym_skip_contrast_analysis
30
+ _patch_via_funchook
31
+ ].freeze # rubocop:disable Security/Object/Freeze
32
+
8
33
  # The mkmf.rb file uses all passed flags from Ruby configuration (RbConfig::CONFIG) on
9
34
  # Ruby build time. Problem with Clang and GCC is that it do not keep up with c89 and finds
10
35
  # error on including <ryby.h> as not allowing inline variables.
@@ -32,8 +57,7 @@ require_relative '../lib/contrast/agent/version'
32
57
  STANDARD_FLAGS = '-std=gnu89'
33
58
  CLANG = 'clang'
34
59
 
35
- # TODO: RUBY-999999 Add -pedantic flag, remove all warning flags and see to it that as many as possible become obsolete.
36
- # Note: Adding -pedantic could raise <ruby.h> warnings, and we are not in control of that code.
60
+ # Adding -pedantic could raise <ruby.h> warnings, and we are not in control of that code.
37
61
  # e.g. error: '_Bool' is a C99 extension [-Werror,-Wc99-extensions] ; empty macros and etc.
38
62
  #
39
63
  # -Wno-int-conversion => Passing VALUEs as function args but required as unsigned long parameters.
@@ -55,8 +79,19 @@ WARNING_FLAGS = %w[
55
79
  # Flags that are only recognized by gcc:
56
80
  GCC_FLAGS = %w[-Wno-maybe-uninitialized].freeze # rubocop:disable Security/Object/Freeze
57
81
 
82
+ def darwin?
83
+ RbConfig::CONFIG['target_os'].include?('darwin')
84
+ end
85
+
58
86
  # Extend $CFLAGS passed directly to compiler in ruby mkmf
87
+ #
88
+ # Extended flags are mainly tested with clang and gcc. Experience with other compilers may vary.
89
+ # To that end if something brakes on client side we must have a mechanism to go back to previous
90
+ # non strict gnu89 standard and be able to maintain the build.
91
+ # We can disable newly added changes with this setting CONTRAST_USE_C89=false.
59
92
  def extend_cflags
93
+ return if ENV['CONTRAST__USE_GNU89'] == 'false'
94
+
60
95
  $CFLAGS += " #{ [STANDARD_FLAGS, WARNING_FLAGS].flatten.join(' ') }"
61
96
  # Extend with GCC specific flags:
62
97
  unless RbConfig::MAKEFILE_CONFIG['CC'].downcase.include?(CLANG) ||
@@ -67,6 +102,52 @@ def extend_cflags
67
102
  end
68
103
  end
69
104
 
105
+ # use C compiler if set.
106
+ def enable_env_cc
107
+ RbConfig::CONFIG['CC'] = RbConfig::MAKEFILE_CONFIG['CC'] = ENV['CC'] if ENV['CC']
108
+ end
109
+
110
+ # Since we cannot link directly the bundles created after the extensions being build,
111
+ # we can pass flags to the linker to resolve symbols not being found during compilation,
112
+ # since they are going to be available on load time. Ruby first is loaded then the extensions.
113
+ # MacOS introduced new fixups with Xcode 14. This causes the issue of symbols not being linked during
114
+ # compilation for the Agent, and other ruby related issues with objective C objects being used,
115
+ # bigdecimal being different and so on.
116
+ #
117
+ # Ruby itself is build on MacOS with '--enabled-shared' flag, also Ruby interpreters compiled
118
+ # under Xcode14 no longer specify by default in DLDFLAGS this flag:
119
+ #
120
+ # '-undefined dynamic_lookup'
121
+ # ( As of Xcode14.3 behavior of dynamic_lookup is reverted back.)
122
+ #
123
+ # This simply is telling the linker that any unknown symbols will be resolved dynamic on extension load.
124
+ # However with the new fixup chain introduced an warning may be produced:
125
+ # ld: warning: -undefined dynamic_lookup may not work with chained fixups.
126
+ # The fixups are responsible for the dynamic linking of the dylibs.
127
+ def mac_symbol_resolve
128
+ # If this is braking the build, it can be disabled.
129
+ return if ENV['CONTRAST__NO_MAC_LD_FIX'] == 'true'
130
+ return unless darwin?
131
+
132
+ # Disabled as of the unknown changes to newly ruby interpreter builds:
133
+ # RbConfig::CONFIG['EXTDLDFLAGS'] = "-bundle_loader #{ RbConfig::CONFIG['EXTDLDFLAGS'] }"
134
+
135
+ # Avoid using this, as not desirable:
136
+ #
137
+ # Adding -no_fixup_chains will brake older compilers but will work on newer.
138
+ # This flag solves the problem but on the long run might not be the solution needed, now not being default,
139
+ # any new feature changes on Mac or Ruby side might brake things again.
140
+ # Use this only if explicitly set as ENV var,as a backup on client's end.
141
+ #
142
+ # $LDFLAGS << " " << flag
143
+ append_ldflags('-Wl,-no_fixup_chains -undefined dynamic_lookup') if ENV['CONTRAST__MAC_LD_USE_ALT'] == 'true'
144
+
145
+ # Another alternative is to use -Wl,-U with explicit listed depending symbols.
146
+ # Append the symfile:
147
+ SYMS.each { |sym| $DLDFLAGS << " -Wl,-U,#{ sym }" } unless ENV['CONTRAST__MAC_LD_USE_ALT'] == 'true'
148
+ end
149
+
150
+ # Generate Makefile.
70
151
  def make!
71
152
  create_makefile("#{ $TO_MAKE }/#{ $TO_MAKE }")
72
153
  end
@@ -75,9 +156,9 @@ end
75
156
  # | MOVING CODE BELLOW THIS SECTION MAY BRAKE MAKEFILE. ORDER MATTERS! |
76
157
  # ----------------------------------------------------------------------
77
158
 
159
+ # __dir__ is relative to the file you're reading.
160
+ # this file you're reading is presently within $APP_ROOT/ext/.
78
161
  def ext_path
79
- # __dir__ is relative to the file you're reading.
80
- # this file you're reading is presently within $APP_ROOT/ext/.
81
162
  __dir__
82
163
  end
83
164
 
@@ -85,14 +166,7 @@ end
85
166
  # funchook.h file. Then we can pass CFLAGS and extend makefile flags and invoke make!
86
167
  require_relative './build_funchook'
87
168
 
88
- # Extended flags are mainly tested with clang and gcc. Experience with other compilers may vary.
89
- # To that end if something brakes on client side we must have a mechanism to go back to previous
90
- # non strict gnu89 standard and be able to maintain the build.
91
- # We can disable newly added changes with this setting CONTRAST_USE_C89=false.
92
- extend_cflags unless ENV['CONTRAST__USE_GNU89'] == 'false'
93
-
94
- # use same C compiler if set.
95
- RbConfig::CONFIG['CC'] = RbConfig::MAKEFILE_CONFIG['CC'] = ENV['CC'] if ENV['CC']
96
-
97
- # Generate Makefile.
169
+ enable_env_cc
170
+ mac_symbol_resolve
171
+ extend_cflags
98
172
  make!
@@ -43,6 +43,7 @@ module Contrast
43
43
  return unless analyze?(method_policy, object, ret, args)
44
44
  return if event_limit?(method_policy)
45
45
  return unless (source_node = method_policy.source_node)
46
+ # Exclusions makes method slow:
46
47
  return if excluded_by_url?
47
48
 
48
49
  # used to hold the object and ret
@@ -76,6 +77,7 @@ module Contrast
76
77
 
77
78
  source_name ||= determine_source_name(source_node, source_data.object, source_data.ret, *args)
78
79
 
80
+ # Exclusions makes method slow:
79
81
  return if excluded_by_input?(source_type, source_name)
80
82
 
81
83
  # We know we only work on certain things.
@@ -188,16 +190,23 @@ module Contrast
188
190
  #
189
191
  # @return [Boolean]
190
192
  def excluded_by_input? source_type, source_name
191
- context = Contrast::Agent::REQUEST_TRACKER.current
192
- Contrast::SETTINGS.excluder.assess_excluded_by_input?(context.request, source_type, source_name)
193
+ return false unless Contrast::SETTINGS.excluder.exclusions.any?
194
+ return false unless Contrast::Agent::REQUEST_TRACKER.current
195
+ # skip if the source is collection, it will be evaluated by it's elements. Collections are not
196
+ # trackable.
197
+ return false if source_name.cs__is_a?(Hash)
198
+
199
+ Contrast::SETTINGS.excluder.assess_excluded_by_input?(source_type, source_name)
193
200
  end
194
201
 
195
202
  # Should a source be excluded because it matches one of the url exclusion rules?
196
203
  #
197
204
  # @return [Boolean]
198
205
  def excluded_by_url?
199
- context = Contrast::Agent::REQUEST_TRACKER.current
200
- Contrast::SETTINGS.excluder.assess_excluded_by_url?(context.request)
206
+ return false unless Contrast::SETTINGS.excluder.exclusions.any?
207
+ return false unless Contrast::Agent::REQUEST_TRACKER.current
208
+
209
+ Contrast::SETTINGS.excluder.assess_excluded_by_url?
201
210
  end
202
211
  end
203
212
  end
@@ -93,11 +93,11 @@ module Contrast
93
93
 
94
94
  request = find_request(source)
95
95
  return unless reportable?(request&.env)
96
- return if excluded_by_url_and_rule?(request, trigger_node.rule_id)
96
+ return if excluded_by_url_and_rule?(trigger_node.rule_id)
97
97
 
98
98
  finding = Contrast::Agent::Reporting::Finding.new(trigger_node.rule_id)
99
99
  finding.attach_data(trigger_node, source, object, ret, request, *args)
100
- return if excluded_by_input_and_rule?(request, finding, trigger_node.rule_id)
100
+ return if excluded_by_input_and_rule?(finding, trigger_node.rule_id)
101
101
 
102
102
  finding.hash_code = Contrast::Utils::HashDigest.generate_event_hash(finding, source, request)
103
103
  check_for_stored_xss(finding)
@@ -167,28 +167,22 @@ module Contrast
167
167
  end
168
168
  end
169
169
 
170
- def build_events finding, event
171
- return unless event
172
-
173
- event.parent_events&.each do |parent_event|
174
- build_events(finding, parent_event)
175
- end
176
- # events could technically be nil, but we would have failed the rule check before getting here. not
177
- # worth the nil check
178
- finding.events << event.to_dtm_event
179
- end
180
-
181
170
  # Check if the finding should be excluded due to the assess exclusion rules.
182
171
  #
183
- # @param request [Contrast::Agent::Request] a wrapper around the Rack::Request for the current request
184
172
  # @param rule_id [String]
185
173
  # return [Boolean]
186
- def excluded_by_url_and_rule? request, rule_id
187
- Contrast::SETTINGS.excluder.assess_excluded_by_url_and_rule?(request, rule_id)
174
+ def excluded_by_url_and_rule? rule_id
175
+ return false unless Contrast::SETTINGS.excluder.exclusions.any?
176
+ return unless Contrast::Agent::REQUEST_TRACKER.current
177
+
178
+ Contrast::SETTINGS.excluder.assess_excluded_by_url_and_rule?(rule_id)
188
179
  end
189
180
 
190
- def excluded_by_input_and_rule? request, finding, rule_id
191
- Contrast::SETTINGS.excluder.assess_excluded_by_input_and_rule?(request, finding, rule_id)
181
+ def excluded_by_input_and_rule? finding, rule_id
182
+ return false unless Contrast::SETTINGS.excluder.exclusions.any?
183
+ return unless Contrast::Agent::REQUEST_TRACKER.current
184
+
185
+ Contrast::SETTINGS.excluder.assess_excluded_by_input_and_rule?(finding, rule_id)
192
186
  end
193
187
 
194
188
  # Handles the Stored Xss rule. If a vector is stored in the database
@@ -3,6 +3,8 @@
3
3
 
4
4
  require 'contrast/agent/reporting/settings/url_exclusion'
5
5
  require 'contrast/agent/reporting/input_analysis/input_type'
6
+ require 'contrast/utils/object_share'
7
+ require 'contrast/utils/assess/object_store'
6
8
 
7
9
  module Contrast
8
10
  module Agent
@@ -19,11 +21,14 @@ module Contrast
19
21
  @exclusions = exclusions
20
22
  end
21
23
 
24
+ def cached_paths
25
+ @_cached_paths ||= Contrast::Utils::Assess::ObjectStore.new(10)
26
+ end
27
+
22
28
  # Determine if an input is excluded for protect rule.
23
29
  #
24
30
  # @param results [Array<Contrast::Agent::Reporting::InputAnalysisResult>]
25
- # @param request_path [String] Current request path
26
- def protect_excluded_by_input? results, request_path
31
+ def protect_excluded_by_input? results
27
32
  return false unless results.any?
28
33
 
29
34
  exclusion_matched = 0
@@ -35,8 +40,7 @@ module Contrast
35
40
 
36
41
  # Based on strategy:
37
42
  match = input_match_strategy(exclusion_match,
38
- input_match?(exclusion_match, rule_result.input_type, rule_result.key),
39
- request_path)
43
+ input_match?(exclusion_match, rule_result.input_type, rule_result.key))
40
44
  exclusion_matched += 1 if match
41
45
  end
42
46
  end
@@ -48,25 +52,21 @@ module Contrast
48
52
  # If an assess URL exclusion rule applies to the current url, *and* is defined as "All Rules"
49
53
  # then we can avoid any tracking for the request.
50
54
  #
51
- # @param request [Contrast::Agent::Request] a wrapper around the Rack::Request for the current request
52
55
  # @return [Boolean]
53
- def assess_excluded_by_url? request
56
+ def assess_excluded_by_url?
54
57
  assess_url_exclusions_for_all_rules.any? do |exclusion_matcher|
55
- path_match?(exclusion_matcher, request.path)
58
+ path_match?(exclusion_matcher)
56
59
  end
57
60
  end
58
61
 
59
62
  # If an assess URL exclusion rule applies to the current url, *and* also covers the
60
63
  # provided rule_id, then we can avoid tracking this entry.
61
64
  #
62
- # @param request [Contrast::Agent::Request] a wrapper around the Rack::Request for the current request
63
65
  # @param rule_id [String]
64
66
  # return [Boolean]
65
- def assess_excluded_by_url_and_rule? request, rule_id
66
- request_path = request.path
67
-
67
+ def assess_excluded_by_url_and_rule?rule_id
68
68
  assess_url_exclusions.any? do |exclusion_matcher|
69
- path_match?(exclusion_matcher, request_path) &&
69
+ path_match?(exclusion_matcher) &&
70
70
  (exclusion_matcher.assess_rules.empty? || exclusion_matcher.assess_rules.include?(rule_id))
71
71
  end
72
72
  end
@@ -74,35 +74,30 @@ module Contrast
74
74
  # If an assess INPUT exclusion rule applies to the current url, *and* also covers all
75
75
  # rules, then we can avoid tracking this entry.
76
76
  #
77
- # @param request [Contrast::Agent::Request] a wrapper around the Rack::Request for the current request
78
77
  # @param source_type [String]
79
78
  # @param source_name [String]
80
79
  # return [Boolean]
81
- def assess_excluded_by_input? request, source_type, source_name
82
- request_path = request.path
83
-
80
+ def assess_excluded_by_input?source_type, source_name
84
81
  assess_input_exclusions_for_all_rules.any? do |exclusion_matcher|
85
- input_match?(exclusion_matcher, source_type, source_name) && path_match?(exclusion_matcher, request_path)
82
+ input_match?(exclusion_matcher, source_type, source_name) && path_match?(exclusion_matcher)
86
83
  end
87
84
  end
88
85
 
89
86
  # If an assess INPUT exclusion rule covers the provided rule_id *for all finding event sources*, then we
90
87
  # can avoid tracking this entry. If any event source *isn't excluded* then we don't exclude the finding.
91
88
  #
92
- # @param request [Contrast::Agent::Request] a wrapper around the Rack::Request for the current request
93
89
  # @param finding [Contrast::Agent::Reporting::Finding]
94
90
  # @param rule [String]
95
91
  # return [Boolean]
96
- def assess_excluded_by_input_and_rule? request, finding, rule
92
+ def assess_excluded_by_input_and_rule?finding, rule
97
93
  return false if finding.events.empty?
98
94
 
99
95
  # We need to check for url exclusions here for the input rules as the url exclusions
100
96
  # that have already been checked didn't include the INPUT exclusions. So we look for
101
97
  # any INPUT exclusions that apply to the current url and the supplied rule.
102
- path = request.path
103
98
  rule_input_exclusions = assess_input_exclusions.select do |exclusion_matcher|
104
99
  (exclusion_matcher.protect_rules.empty? || exclusion_matcher.protect_rules.include?(rule)) &&
105
- path_match?(exclusion_matcher, path)
100
+ path_match?(exclusion_matcher)
106
101
  end
107
102
  return false if rule_input_exclusions.empty?
108
103
 
@@ -122,13 +117,12 @@ module Contrast
122
117
  # then we can avoid using the rule for the request.
123
118
  #
124
119
  # @param rule_id [String]
125
- # @param path [String]
126
120
  # return [Boolean]
127
- def protect_excluded_by_url? rule_id, path
121
+ def protect_excluded_by_url? rule_id
128
122
  protect_url_exclusions.any? do |exclusion_matcher|
129
123
  next unless exclusion_matcher.protection_rule?(rule_id)
130
124
 
131
- return true if path_match?(exclusion_matcher, path)
125
+ return true if path_match?(exclusion_matcher)
132
126
  end
133
127
  end
134
128
 
@@ -140,14 +134,13 @@ module Contrast
140
134
  #
141
135
  # @param exclusion_match [Contrast::Agent::ExclusionMatcher]
142
136
  # @param input_match [Boolean] does the input match the exclusion
143
- # @param request_path [String] Current request path
144
137
  # @return [Boolean]
145
- def input_match_strategy exclusion_match, input_match, request_path
138
+ def input_match_strategy exclusion_match, input_match
146
139
  # for ALL urls
147
140
  return input_match if exclusion_match.match_all?
148
141
 
149
142
  # for ONLY match we need to check if there is an input and url match.
150
- input_match && path_match?(exclusion_match, request_path)
143
+ input_match && path_match?(exclusion_match)
151
144
  end
152
145
 
153
146
  # @return [Array<Contrast::Agent::ExclusionMatcher>]
@@ -202,12 +195,25 @@ module Contrast
202
195
  end
203
196
  end
204
197
 
198
+ # Returns true if context.request.path matches any url exclusion.
199
+ #
205
200
  # @return [Boolean]
206
- def path_match? exclusion_matcher, path
207
- return false unless path
201
+ def path_match? exclusion_matcher
202
+ return false unless Contrast::Agent::REQUEST_TRACKER.current&.request&.path
203
+
204
+ return_cached_result(exclusion_matcher)
205
+ matches = 0
206
+ matches += 1 if exclusion_matcher.wildcard_url
207
+ exclusion_matcher.urls.any? do |url|
208
+ if url.match?(Contrast::Agent::REQUEST_TRACKER.current.request.path) ||
209
+ regexp_match?(url, Contrast::Agent::REQUEST_TRACKER.current.request.path)
210
+
211
+ matches += 1
212
+ end
213
+ end
208
214
 
209
- exclusion_matcher.wildcard_url ||
210
- exclusion_matcher.urls.any? { |url| url.match?(path) || regexp_match?(url, path) }
215
+ add_cached_path(exclusion_matcher, matches)
216
+ matches.positive?
211
217
  end
212
218
 
213
219
  # @param exclusion [Contrast::Agent::ExclusionMatcher]
@@ -301,6 +307,33 @@ module Contrast
301
307
  def cookie_types
302
308
  @_cookie_types ||= [COOKIE_NAME, COOKIE_VALUE].cs__freeze
303
309
  end
310
+
311
+ # Adds new cached result unless it already exists
312
+ #
313
+ # @param exclusion_matcher [Contrast::Agent::ExclusionMatcher]
314
+ # @param matches [Boolean] the result of last iteration
315
+ # @return [Hash, nil]
316
+ def add_cached_path exclusion_matcher, matches
317
+ return if cached_paths[Contrast::Agent::REQUEST_TRACKER.current.request.path.__id__]
318
+
319
+ cached_paths[Contrast::Agent::REQUEST_TRACKER.
320
+ current.request.path.__id__] = { matcher: exclusion_matcher.__id__, result: matches.positive? }
321
+ rescue StandardError
322
+ nil
323
+ end
324
+
325
+ # returns a cached result if current path and matcher are the same.
326
+ # @param exclusion_matcher [Contrast::Agent::ExclusionMatcher]
327
+ # @return [Boolean, nil]
328
+ def return_cached_result exclusion_matcher
329
+ return unless !cached_paths[Contrast::Agent::REQUEST_TRACKER.current.request.path.__id__].nil? &&
330
+ (cached_paths[Contrast::Agent::REQUEST_TRACKER.current.
331
+ request.path.__id__][:matcher] == exclusion_matcher.__id__)
332
+
333
+ cached_paths[Contrast::Agent::REQUEST_TRACKER.current.request.path.__id__][:result]
334
+ rescue StandardError
335
+ nil
336
+ end
304
337
  end
305
338
  end
306
339
  end
@@ -166,17 +166,15 @@ module Contrast
166
166
  # Check if the protect rules is excluded by url from the exclusion rules for this application.
167
167
  #
168
168
  # @param rule_id [String]
169
- # @param request_path [String] Current request path
170
- def protect_excluded_by_url? rule_id, request_path
171
- Contrast::SETTINGS.excluder.protect_excluded_by_url?(rule_id, request_path)
169
+ def protect_excluded_by_url? rule_id
170
+ Contrast::SETTINGS.excluder.protect_excluded_by_url?(rule_id)
172
171
  end
173
172
 
174
173
  # Check if the protect rules is excluded by input from the exclusion rules for this application.
175
174
  #
176
175
  # @param results [Array<Contrast::Agent::Reporting::InputAnalysis>]
177
- # @param request_path [String] Current request path
178
- def protect_excluded_by_input? results, request_path
179
- Contrast::SETTINGS.excluder.protect_excluded_by_input?(results, request_path)
176
+ def protect_excluded_by_input? results
177
+ Contrast::SETTINGS.excluder.protect_excluded_by_input?(results)
180
178
  end
181
179
 
182
180
  # Allows for the InputAnalysis from Agent Library to be extracted early
@@ -77,7 +77,7 @@ module Contrast
77
77
  # @return [Contrast::Agent::Reporting::RaspRuleSample]
78
78
  def build_sample context, ia_result, _candidate_string, **_kwargs
79
79
  sample = build_base_sample(context, ia_result)
80
- sample.details = Contrast::Agent::Reporting::BotBlockerDetails.new
80
+ sample.details = Contrast::Agent::Reporting::Details::BotBlockerDetails.new
81
81
  sample.details.bot = ia_result.value
82
82
  sample.details.user_agent = context&.request&.user_agent
83
83
  sample
@@ -3,7 +3,7 @@
3
3
 
4
4
  require 'contrast/agent/reporting/input_analysis/input_type'
5
5
  require 'contrast/agent/reporting/input_analysis/score_level'
6
- require 'contrast/agent/reporting/input_analysis/details/bot_blocker_details'
6
+ require 'contrast/agent/reporting/details/bot_blocker_details'
7
7
  require 'contrast/utils/input_classification_base'
8
8
  require 'contrast/utils/object_share'
9
9
 
@@ -73,7 +73,7 @@ module Contrast
73
73
  if score >= THRESHOLD
74
74
  ia_result.score_level = DEFINITEATTACK
75
75
  ia_result.ids << BOT_BLOCKER_MATCH
76
- ia_result.details = Contrast::Agent::Reporting::BotBlockerDetails.new
76
+ ia_result.details = Contrast::Agent::Reporting::Details::BotBlockerDetails.new
77
77
  # details:
78
78
  add_details(ia_result, value)
79
79
  else
@@ -37,7 +37,7 @@ module Contrast
37
37
  # @raise [Contrast::SecurityException] if the rule mode ise set
38
38
  # to BLOCK and valid cdmi is detected.
39
39
  def infilter context, classname, method, command
40
- return if protect_excluded_by_url?(rule_name, context.request.path)
40
+ return if protect_excluded_by_url?(rule_name)
41
41
  return unless backdoors_match?(command)
42
42
  return unless (result = build_attack_with_match(context, nil, nil, command,
43
43
  **{ classname: classname, method: method }))
@@ -43,7 +43,7 @@ module Contrast
43
43
  # to BLOCK and valid cdmi is detected.
44
44
  def infilter context, classname, method, command
45
45
  return unless infilter?(command)
46
- return if protect_excluded_by_url?(rule_name, context.request.path)
46
+ return if protect_excluded_by_url?(rule_name)
47
47
  return unless (result = build_violation(context, command))
48
48
 
49
49
  append_to_activity(context, result)
@@ -58,9 +58,9 @@ module Contrast
58
58
  # Per the spec, this rule applies regardless of input. Only the mode
59
59
  # of the rule and code exclusions apply at this point.
60
60
  # @return [Boolean] should the rule apply to this call.
61
- def infilter? context
61
+ def infilter?_context
62
62
  return false unless enabled?
63
- return false if protect_excluded_by_url?(rule_name, context.request.path)
63
+ return false if protect_excluded_by_url?(rule_name)
64
64
 
65
65
  true
66
66
  end
@@ -46,7 +46,7 @@ module Contrast
46
46
  # @raise [Contrast::SecurityException] if the rule mode ise set
47
47
  # to BLOCK and valid cdmi is detected.
48
48
  def infilter context, method, path
49
- return if protect_excluded_by_url?(rule_name, context.request.path)
49
+ return if protect_excluded_by_url?(rule_name)
50
50
  return unless rule_violated?(path)
51
51
 
52
52
  result = build_violation(context, path)
@@ -19,7 +19,7 @@ module Contrast
19
19
  end
20
20
 
21
21
  def infilter context, sql_query
22
- return false if protect_excluded_by_url?(rule_name, context.request.path)
22
+ return false if protect_excluded_by_url?(rule_name)
23
23
  return unless violated?(sql_query)
24
24
 
25
25
  result = build_violation(context, sql_query)
@@ -42,8 +42,8 @@ module Contrast
42
42
  return false unless context
43
43
  return false unless enabled?
44
44
  return false unless (results = gather_ia_results(context)) && results.any?
45
- return false if protect_excluded_by_url?(rule_name, context.request.path)
46
- return false if protect_excluded_by_input?(results, context.request.path)
45
+ return false if protect_excluded_by_url?(rule_name)
46
+ return false if protect_excluded_by_input?(results)
47
47
 
48
48
  true
49
49
  end
@@ -68,8 +68,8 @@ module Contrast
68
68
  def infilter? context
69
69
  return false unless enabled?
70
70
  return false unless (results = gather_ia_results(context)) && results.any?
71
- return false if protect_excluded_by_url?(rule_name, context.request.path)
72
- return false if protect_excluded_by_input?(results, context.request.path)
71
+ return false if protect_excluded_by_url?(rule_name)
72
+ return false if protect_excluded_by_input?(results)
73
73
 
74
74
  true
75
75
  end
@@ -89,8 +89,8 @@ module Contrast
89
89
  # @raise [Contrast::SecurityException]
90
90
  def postfilter context
91
91
  return unless enabled? && POSTFILTER_MODES.include?(mode)
92
- return false if protect_excluded_by_url?(rule_name, context.request.path)
93
- return if protect_excluded_by_input?(gather_ia_results(context), context.request.path)
92
+ return false if protect_excluded_by_url?(rule_name)
93
+ return if protect_excluded_by_input?(gather_ia_results(context))
94
94
 
95
95
  return if mode == :NO_ACTION || mode == :PERMIT
96
96
 
@@ -37,7 +37,7 @@ module Contrast
37
37
  # @raise [Contrast::SecurityException] Security exception if an XXE
38
38
  # attack is found and the rule is in block mode.
39
39
  def infilter context, framework, xml
40
- return if protect_excluded_by_url?(rule_name, context.request.path)
40
+ return if protect_excluded_by_url?(rule_name)
41
41
 
42
42
  result = find_attacker(context, xml, framework: framework)
43
43
  return unless result