contrast-agent 7.3.0 → 7.3.2

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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/ext/cs__scope/cs__scope.c +76 -7
  3. data/ext/cs__scope/cs__scope.h +4 -0
  4. data/lib/contrast/agent/assess/policy/policy_node.rb +25 -6
  5. data/lib/contrast/agent/assess/policy/propagator/response.rb +64 -0
  6. data/lib/contrast/agent/assess/policy/propagator.rb +1 -0
  7. data/lib/contrast/agent/assess/policy/source_method.rb +5 -0
  8. data/lib/contrast/agent/assess/rule/response/body_rule.rb +22 -7
  9. data/lib/contrast/agent/assess/rule/response/cache_control_header_rule.rb +4 -1
  10. data/lib/contrast/agent/inventory/policy/datastores.rb +0 -3
  11. data/lib/contrast/agent/reporting/reporting_events/application_activity.rb +4 -10
  12. data/lib/contrast/agent/reporting/reporting_events/application_defend_activity.rb +11 -12
  13. data/lib/contrast/agent/reporting/reporting_events/application_defend_attack_sample_activity.rb +4 -29
  14. data/lib/contrast/agent/reporting/reporting_events/application_defend_attacker_activity.rb +1 -2
  15. data/lib/contrast/agent/reporting/reporting_events/application_inventory_activity.rb +2 -2
  16. data/lib/contrast/agent/reporting/reporting_events/finding_request.rb +2 -2
  17. data/lib/contrast/agent/reporting/reporting_utilities/ng_response_extractor.rb +15 -2
  18. data/lib/contrast/agent/reporting/reporting_utilities/response.rb +0 -2
  19. data/lib/contrast/agent/reporting/reporting_utilities/response_handler_utils.rb +5 -2
  20. data/lib/contrast/agent/reporting/settings/protect.rb +61 -18
  21. data/lib/contrast/agent/reporting/settings/server_features.rb +2 -0
  22. data/lib/contrast/agent/telemetry/exception/obfuscate.rb +4 -3
  23. data/lib/contrast/agent/telemetry/identifier.rb +13 -26
  24. data/lib/contrast/agent/version.rb +1 -1
  25. data/lib/contrast/components/assess.rb +33 -6
  26. data/lib/contrast/components/base.rb +4 -2
  27. data/lib/contrast/components/config.rb +2 -2
  28. data/lib/contrast/components/protect.rb +14 -1
  29. data/lib/contrast/components/settings.rb +11 -1
  30. data/lib/contrast/config/diagnostics/command_line.rb +2 -2
  31. data/lib/contrast/config/diagnostics/environment_variables.rb +2 -1
  32. data/lib/contrast/config/diagnostics/tools.rb +15 -5
  33. data/lib/contrast/configuration.rb +61 -29
  34. data/lib/contrast/logger/application.rb +3 -3
  35. data/lib/contrast/utils/assess/propagation_method_utils.rb +2 -0
  36. data/lib/contrast/utils/os.rb +1 -9
  37. data/lib/contrast/utils/reporting/application_activity_batch_utils.rb +0 -3
  38. data/lib/contrast.rb +1 -1
  39. data/resources/assess/policy.json +80 -3
  40. metadata +3 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e64852411fae5dd5c52973361e7f54cdf60813105423ed81cd6455c6ec212330
4
- data.tar.gz: d7d6a1ef01242d97b36f1b8fc4767829d300ec2f3ce1dcb21df7fe05aebc22b1
3
+ metadata.gz: a4ed370d3625c9e07c5966fb4e091e96cc37a6a2fce8376d63a547e655b27173
4
+ data.tar.gz: 601a4767e4317af72e2c610df6ba19bb68df2c42f128bb873539204c93801019
5
5
  SHA512:
6
- metadata.gz: 86626808971cfc1fcd74febe8cdf2d41be668a78cc02d41d4bf7f6a488e550fef9f2a8fe3230eea7ea9ed63824d09e61956878d34d99375dd3595b77d3a9755f
7
- data.tar.gz: 9fb15e1733095b1f7c2a9d5d4bfce96a3a2e9d5f956d77f0cd6c1fb810c44166a7bf0e2b89966a63ca80aa34514dd82e6f87e619a782d5024854d84c1ed93183
6
+ metadata.gz: 597307a8f3521cd9783c07ccc8b2f54d6bf2cf1631db84bd29fba683fbd93fc4f5eff47bbbc2ed50c37c02fd3c59d5df987526c839c5e731299cdb6e02e30c6f
7
+ data.tar.gz: 95898f87cbabbcc24d9ce3f0d5ba1397b4399f9ca35f4c4a1290c7e3d9c7cd26d6bf13c99affc013dea2456260aeb918349a97905863ba790ba869a272faccfc
@@ -5,6 +5,7 @@
5
5
  #include "../cs__common/cs__common.h"
6
6
  #include <ruby.h>
7
7
  #include <stdlib.h>
8
+ #include <pthread.h>
8
9
 
9
10
  /*-----------------------------------------
10
11
  | Calls to Contrast modules and classes
@@ -23,6 +24,9 @@ static char truthy_arr[3][5] = {"true", "True", "TRUE"};
23
24
  | Helpers
24
25
  -----------*/
25
26
 
27
+ /* Declare new c mutex for scope locking: */
28
+ pthread_mutex_t c_mutex;
29
+
26
30
  /**
27
31
  * @brief get scope for ec or create new
28
32
  *
@@ -153,22 +157,40 @@ VALUE contrast_scope_application_update() {
153
157
  * [ Do not touch, do not free. We don't control it! ]
154
158
  */
155
159
  char *eVar = getenv(c_const_ctr_agent_app_scope);
156
- VALUE app_scope = eVar;
160
+ /* check to see if the ENV variable is set */
161
+ return INT2FIX(env_var_set(eVar));
162
+ }
157
163
 
164
+ /**
165
+ * @brief Determines if the Contrast Scope should be set with the Trap Context.
166
+ * @return 1 if set, 0 if not set.
167
+ */
168
+ int contrast_scope_with_trap_context() {
169
+ char *eVar = getenv(c_const_ctr_agent_scope_trap_context);
170
+ return env_var_set(eVar);
171
+ }
172
+
173
+ /**
174
+ * @brief Checks to see if the ENV variable is set.
175
+ * @param args VALUE [String] ENV variable name.
176
+ * @return 1 if set, 0 if not set.
177
+ */
178
+ int env_var_set(char *eVar) {
179
+ VALUE env_var = eVar;
158
180
  /* check to see if the ENV variable is set */
159
- if (RTEST(app_scope)) {
181
+ if (RTEST(env_var)) {
160
182
  /* Application Scope is set*/
161
183
  int i;
162
184
  for (i = 0; i < sizeof(truthy_arr) / sizeof(truthy_arr[0]); i++) {
163
185
  if (strcmp(eVar, truthy_arr[i]) == 0) {
164
- return INT2FIX(1);
186
+ return 1;
165
187
  }
166
188
  }
167
189
  /* this covers all of the false values */
168
- return INT2FIX(0);
190
+ return 0;
169
191
  } else {
170
192
  /* Application Scope is not set ( NULL )*/
171
- return INT2FIX(0);
193
+ return 0;
172
194
  }
173
195
  }
174
196
 
@@ -505,8 +527,48 @@ VALUE contrast_scope_interface_init(VALUE self, VALUE args) {
505
527
  VALUE contrast_scope_for_current_ec(VALUE self, VALUE args) {
506
528
  /* synchronize */
507
529
  VALUE mutex = rb_const_get(scope_mod, rb_intern(rb_const_mon));
508
-
509
- return rb_mutex_synchronize(mutex, get_ec, 0);
530
+ if (contrast_scope_with_trap_context()) {
531
+ /**
532
+ * trap context safety:
533
+ *
534
+ * If mutex synchonize is called inside a trap context,
535
+ * it will raise a thread error. To avoid that, there are
536
+ * different approaches as to trap all signal in a loop,
537
+ * but that is not a good idea, since the scope is checked
538
+ * once, and only one signal could be trapped. Another way
539
+ * is to make new thread around the mutex synchronization
540
+ * call, but this will create a new execution context and
541
+ * the returned scope will be different.
542
+ *
543
+ * Most of the thread error occur randomly whenever GC is
544
+ * started. We cannot detect, When the GC is started, it
545
+ * will stop all current Ruby code execution while running.
546
+ *
547
+ * Instead we call the `get_ec()` directly since Ruby have
548
+ * GVL (Global VM Lock) when calling it's C API therefore
549
+ * This should be safe called from Ruby land. Just in case
550
+ * this is a feature flag, so that only be used when thread
551
+ * safety could be traded for signal safety.
552
+ *
553
+ * This is still experimental as relies ong the GLV and the
554
+ * Ruby VM is not thread safe by default. Various problems
555
+ * might occur, when the mutex is in Ruby and called from
556
+ * different thread, at once, but since the mutex stays in
557
+ * the C API context, this might work fine. Still use only
558
+ * when unusual conditions are met.
559
+ *
560
+ * In addition we could use C mutex lock just in case. Ruby
561
+ * Threads under the hood are C threads (pthread), so they
562
+ * should be treated as such. In theory only one thread can
563
+ * access this code at a time.
564
+ */
565
+ pthread_mutex_lock(&c_mutex);
566
+ VALUE res = get_ec();
567
+ pthread_mutex_unlock(&c_mutex);
568
+ return res;
569
+ } else {
570
+ return rb_mutex_synchronize(mutex, get_ec, 0);
571
+ }
510
572
  }
511
573
 
512
574
  /*--------------------------------------------------------
@@ -819,6 +881,9 @@ VALUE scope_mod_sweep_dead_ecs(VALUE self, VALUE args) {
819
881
  }
820
882
 
821
883
  void Init_cs__scope() {
884
+ /* Init new mutex handle */
885
+ pthread_mutex_init(&c_mutex, NULL);
886
+
822
887
  /* ivs */
823
888
  rb_iv_cntr_scope = "@contrast_scope";
824
889
  rb_iv_dslr_scope = "@deserialization_scope";
@@ -829,6 +894,7 @@ void Init_cs__scope() {
829
894
  rb_const_ec = "EXECUTION_CONTEXT";
830
895
  rb_const_ec_keys = "EC_KEYS";
831
896
  c_const_ctr_agent_app_scope = "CONTRAST__AGENT__RUBY__APPLICATION_SCOPE";
897
+ c_const_ctr_agent_scope_trap_context = "CONTRAST__AGENT__SCOPE__WITH_TRAP_CONTEXT";
832
898
 
833
899
  /* Symbols */
834
900
  rb_sym_scope_mod = rb_intern("Scope");
@@ -977,4 +1043,7 @@ void Init_cs__scope() {
977
1043
  inst_methods_enter_method_scope, 1);
978
1044
  rb_define_method(scope_inst_methods, "contrast_exit_method_scopes!",
979
1045
  inst_methods_exit_method_scope, 1);
1046
+
1047
+ /* free the c_mutex */
1048
+ pthread_mutex_destroy(&c_mutex);
980
1049
  }
@@ -1,4 +1,5 @@
1
1
  #include <ruby.h>
2
+ #include <ruby/thread.h>
2
3
 
3
4
  /* Calls to Contrast modules and classes */
4
5
  VALUE scope_interface;
@@ -14,6 +15,7 @@ static VALUE rb_const_ec;
14
15
  static VALUE rb_const_mon;
15
16
  static VALUE rb_const_ec_keys;
16
17
  static VALUE c_const_ctr_agent_app_scope;
18
+ static VALUE c_const_ctr_agent_scope_trap_context;
17
19
 
18
20
  /* Symbols */
19
21
  static VALUE rb_sym_scope_mod;
@@ -58,6 +60,7 @@ VALUE scope_klass_exit_scope(VALUE self, VALUE method_scope_sym);
58
60
  VALUE contrast_scope_interface_init(VALUE self, VALUE args);
59
61
  VALUE contrast_scope_for_current_ec(VALUE self, VALUE args);
60
62
  VALUE contrast_scope_application_update();
63
+ int contrast_scope_with_trap_context();
61
64
 
62
65
  /* Scope instance methods */
63
66
  VALUE inst_methods_in_cntr_scope(VALUE self, VALUE args);
@@ -83,6 +86,7 @@ VALUE inst_methods_exit_method_scope(VALUE self, VALUE scopes_to_exit);
83
86
  VALUE is_in_scope(int scope);
84
87
  VALUE get_ec();
85
88
  VALUE rb_new_c_scope();
89
+ int env_var_set(char *eVar);
86
90
  int scope_increase(int scope);
87
91
  int scope_decrease(int scope);
88
92
  void rb_raise_scope_no_method_err(const VALUE method_scope_sym);
@@ -16,6 +16,7 @@ module Contrast
16
16
  class PolicyNode < Contrast::Agent::Patching::Policy::PolicyNode
17
17
  include Contrast::Components::Logger::InstanceMethods
18
18
  include PolicyNodeUtils
19
+
19
20
  JSON_TAGS = 'tags'
20
21
  JSON_DATAFLOW = 'dataflow'
21
22
  # The keys used to read from policy.json to create the individual
@@ -48,6 +49,9 @@ module Contrast
48
49
  ].cs__freeze
49
50
  TO_S = %w[to_s to_str].cs__freeze
50
51
 
52
+ # Here are all Responses that will be tracked as sources, or methods they use, like body.
53
+ RESPONSE_SOURCES = %w[Net::HTTPResponse Rack::Response Sinatra::Response].cs__freeze
54
+
51
55
  def initialize policy_hash = {}
52
56
  super(policy_hash)
53
57
  @source_string = policy_hash[JSON_SOURCE]
@@ -57,13 +61,14 @@ module Contrast
57
61
  @targets = convert_policy_markers(target_string)
58
62
  @_use_original_object = ORIGINAL_OBJECT_METHODS.include?(@method_name)
59
63
  @_use_original_on_bang_method = assign_on_bang_check(policy_hash)
64
+ @_use_response_as_source = RESPONSE_SOURCES.include?(@class_name)
60
65
  end
61
66
 
67
+ # If we have KEEP action on String, and the method is to_s, that method would return self:
68
+ # String#to_s => self or string. This method is included here to cover the situations such as
69
+ # String.to_s.html_safe, where normally the dynamic sources properties get lost. To solve this
70
+ # we will simply return the original object here.
62
71
  def assign_on_bang_check policy_hash
63
- # If we have KEEP action on String, and the method is to_s, that method would return self:
64
- # String#to_s => self or string. This method is included here to cover the situations such as
65
- # String.to_s.html_safe, where normally the dynamic sources properties get lost. To solve this
66
- # we will simply return the original object here.
67
72
  return true if @_use_original_object && TO_S.include?(policy_hash[JSON_METHOD_NAME])
68
73
 
69
74
  @_use_original_object &&
@@ -166,7 +171,7 @@ module Contrast
166
171
  # that the method is without bang - it does not change the source, but rather
167
172
  # creates a copy of it.
168
173
  #
169
- # @return true | false
174
+ # @return [Boolean]
170
175
  def use_original_object?
171
176
  @_use_original_object && Contrast::ASSESS.track_original_object?
172
177
  end
@@ -175,10 +180,24 @@ module Contrast
175
180
  # that the target return is the same as object - a bang method modifying the
176
181
  # source.
177
182
  #
178
- # @return true | false
183
+ # @return [Boolean]
179
184
  def use_original_on_bang_method?
180
185
  @_use_original_on_bang_method && Contrast::ASSESS.track_original_object?
181
186
  end
187
+
188
+ # This method will check if policy is fit to use response as source.
189
+ #
190
+ # @return [Boolean]
191
+ def use_response_as_source?
192
+ Contrast::ASSESS.track_response_as_source?
193
+ end
194
+
195
+ # This method will check if the policy node is for response method.
196
+ #
197
+ # @return [Boolean]
198
+ def response_source_node?
199
+ @_use_response_as_source
200
+ end
182
201
  end
183
202
  end
184
203
  end
@@ -0,0 +1,64 @@
1
+ # Copyright (c) 2023 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
2
+ # frozen_string_literal: true
3
+
4
+ require 'contrast/agent/assess/policy/propagator/select'
5
+ require 'contrast/utils/duck_utils'
6
+
7
+ module Contrast
8
+ module Agent
9
+ module Assess
10
+ module Policy
11
+ module Propagator
12
+ # Propagation that results in all the tags of the source being
13
+ # applied to the target at the point of insertion. The target's
14
+ # preexisting tags are shifted to account for this insertion.
15
+ class Response < Contrast::Agent::Assess::Policy::Propagator::Base
16
+ class << self
17
+ # This will path the Net::HTTP.request method. It takes two parameters:
18
+ # - req: Net::HTTPGenericRequest
19
+ # - body: String
20
+ # As body may be optional, we need to check if it's nil or not.
21
+ #
22
+ # @param propagation_node [Contrast::Agent::Assess::Policy::PropagationNode]
23
+ # @param preshift [Contrast::Agent::Assess::Preshift]
24
+ # @param ret [Object] Return targer from method invocation.
25
+ # @param _block [nil, {}] block passed.
26
+ def net_response_keep propagation_node, preshift, ret, _block
27
+ return unless Contrast::ASSESS.track_response_as_source?
28
+
29
+ # Check to see if the argument is of correct type, and whether the body is tracked or not.
30
+ # if it's tracked and the body is not nil, then copy the properties from the source's body
31
+ # to the target's body.
32
+ source_body = if preshift.args.length == 2
33
+ preshift.args[1]
34
+ else
35
+ preshift.args[0]&.body
36
+ end
37
+ copy_body_tags(propagation_node, source_body, ret)
38
+ end
39
+
40
+ private
41
+
42
+ # Copy the properties form source body to the response body, if one is present.
43
+ #
44
+ # @param propagation_node [Contrast::Agent::Assess::Policy::PropagationNode]
45
+ # @param source_body [String] the tracked body to copy from.
46
+ # @param ret [String] the return target from method invocation.
47
+ # @return [String, nil]
48
+ def copy_body_tags propagation_node, source_body, ret
49
+ return if Contrast::Utils::DuckUtils.empty_duck?(source_body)
50
+ return unless ret&.body&.cs__is_a?(String)
51
+ return unless source_body&.cs__is_a?(String)
52
+ return unless (properties = Contrast::Agent::Assess::Tracker.properties!(ret.body))
53
+
54
+ # KEEP
55
+ properties.copy_from(source_body, ret.body, 0, propagation_node.untags)
56
+ ret
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -31,6 +31,7 @@ module Contrast
31
31
  require 'contrast/agent/assess/policy/propagator/substitution'
32
32
  require 'contrast/agent/assess/policy/propagator/trim'
33
33
  require 'contrast/agent/assess/policy/propagator/buffer'
34
+ require 'contrast/agent/assess/policy/propagator/response'
34
35
  end
35
36
  end
36
37
  end
@@ -46,6 +46,11 @@ module Contrast
46
46
  # Exclusions makes method slow:
47
47
  return if excluded_by_url?
48
48
 
49
+ # Check to see if the source node is to be used for response as source.
50
+ if method_policy.source_node.response_source_node? && !method_policy.source_node.use_response_as_source?
51
+ return
52
+ end
53
+
49
54
  # used to hold the object and ret
50
55
  source_data = Contrast::Agent::Assess::Events::EventData.new(nil, nil, object, ret, nil)
51
56
 
@@ -3,6 +3,7 @@
3
3
 
4
4
  require 'rack'
5
5
  require 'contrast/utils/hash_digest'
6
+ require 'contrast/utils/duck_utils'
6
7
  require 'contrast/utils/string_utils'
7
8
  require 'contrast/agent/assess/rule/response/base_rule'
8
9
 
@@ -44,21 +45,35 @@ module Contrast
44
45
  # @param element_start_str [String] element to find in html section
45
46
  # @return [Array<Hash>] the found elements of this section, as well as their start and end indexes.
46
47
  def html_elements section, element_start_str = '', capture_overflow: false
48
+ return [] unless section
49
+ return [] unless (potentials = potential_elements(section, element_start_str).flatten).any?
50
+
47
51
  elements = []
48
52
  section_start = 0
49
- return [] unless section
50
53
 
51
- potential_elements(section, element_start_str).flatten.each do |potential_element|
54
+ potentials.each do |potential_element|
52
55
  next unless potential_element
53
56
  next unless element_openings.any? { |opening| potential_element.start_with?(opening) }
54
57
 
55
- section_start = section.index(element_start_str, section_start)
56
- next unless section_start
58
+ start = section&.index(element_start_str, section_start)
59
+ next if Contrast::Utils::DuckUtils.empty_duck?(start)
60
+
61
+ stop = potential_element.index('>').to_i
62
+ next if Contrast::Utils::DuckUtils.empty_duck?(stop)
57
63
 
58
- element_stop = potential_element.index('>').to_i
59
- next unless element_stop
64
+ section_close = start + 6 + stop
65
+ # Now we have valid tag section with start and stop.
66
+ # Save new boundaries. This is to make sure that If
67
+ # on previous iteration there were non valid section,
68
+ # the start_section will be assigned to nil, thus making
69
+ # the detection of new section not possible, and throwing
70
+ # an error. To that end old values are kept safe.
71
+ #
72
+ # Assign new start index.
73
+ section_start = start
74
+ # Assign new end index.
75
+ element_stop = stop
60
76
 
61
- section_close = section_start + 6 + element_stop
62
77
  elements << capture(section, section_start, section_close, element_stop, overflow: capture_overflow)
63
78
  section_start = section_close
64
79
  end
@@ -70,7 +70,10 @@ module Contrast
70
70
  # @param response [Contrast::Agent::Response] the response of the application
71
71
  # @return [Array<Hash<String,String>]
72
72
  def cache_meta_tags response
73
- html_elements(response.body&.split(HEAD_TAG)&.last, META_START_STR).
73
+ head_tag = response.body&.split(HEAD_TAG)&.last
74
+ return [] unless head_tag
75
+
76
+ html_elements(head_tag, META_START_STR, capture_overflow: false).
74
77
  select { |tag| cache_control_tag?(tag[HTML_PROP]) }
75
78
  end
76
79
 
@@ -38,9 +38,6 @@ module Contrast
38
38
  context = Contrast::Agent::REQUEST_TRACKER.current
39
39
  return unless context&.activity
40
40
 
41
- context.activity.query_count += 1
42
- return unless context.activity.query_count == 1
43
-
44
41
  Contrast::Agent::Inventory::DatabaseConfig.append_db_config(context.activity)
45
42
  end
46
43
  end
@@ -16,16 +16,11 @@ module Contrast
16
16
  include Contrast::Agent::Reporting::ResponseType
17
17
  include Contrast::Components::Logger::InstanceMethods
18
18
 
19
- # @return [Integer]
20
- attr_accessor :query_count
21
- # @return [Array]
22
- attr_accessor :routes
23
19
  # @return [Contrast::Agent::Response]
24
20
  attr_accessor :response
25
21
 
22
+ # @param ia_request [Contrast::Agent::Request]
26
23
  def initialize ia_request: nil
27
- @routes = []
28
- @query_count = 0
29
24
  @event_method = :PUT
30
25
  @event_type = :application_activity
31
26
  @event_endpoint = Contrast::Agent::Reporting::Endpoints.application_activity
@@ -66,7 +61,7 @@ module Contrast
66
61
  # searching with rule_id and response_type
67
62
  #
68
63
  # @param rule_id [String] name of the protect rule
69
- # @param response_type[Symbol<Contrast::Agent::Reporting::ResponseType]
64
+ # @param response_type[Symbol<Contrast::Agent::Reporting::ResponseType>]
70
65
  # filter by response type
71
66
  # @return [Array<Contrast::Agent::Reporting::ApplicationDefendAttackSampleActivity>, nil]
72
67
  # return any matches.
@@ -99,9 +94,8 @@ module Contrast
99
94
  defend.attackers.map { |a| a.protection_rules.values }
100
95
  end
101
96
 
102
- # This is primary used for attaching new data and merging existing
103
- # samples per rule entry in attackers, and to make sure the attack
104
- # time_map is updated correctly.
97
+ # This is primary used for attaching new data and merging existing samples and counts per rule entry in
98
+ # attackers.
105
99
  #
106
100
  # @param attack_result [Contrast::Agent::Reporting::AttackResult]
107
101
  def attach_defend attack_result
@@ -55,6 +55,7 @@ module Contrast
55
55
 
56
56
  # Find an existing attacker if it matches on source details
57
57
  # @param new_attacker_activity [Contrast::Agent::Reporting::ApplicationDefendAttackerActivity]
58
+ # @return [Contrast::Agent::Reporting::ApplicationDefendAttackerActivity, nil]
58
59
  def find_existing_attacker_activity new_attacker_activity
59
60
  attackers.find do |existing|
60
61
  existing.source_forwarded_for == new_attacker_activity.source_forwarded_for &&
@@ -67,30 +68,28 @@ module Contrast
67
68
  # @param rule [String]
68
69
  def attach_existing existing_attacker_activity, attacker_activity, rule
69
70
  new_violation = attacker_activity.protection_rules[rule]
71
+ return unless new_violation
72
+
70
73
  sample_activity = Contrast::Agent::Reporting::ApplicationDefendAttackSampleActivity
71
74
  if (previously_violated = existing_attacker_activity.protection_rules[rule])
72
- if (new_blocked = new_violation.blocked)
75
+ if (new_blocked_samples = new_violation.blocked&.samples)&.any?
73
76
  previously_violated.blocked ||= sample_activity.new
74
- previously_violated.blocked.samples.concat(new_blocked.samples) if new_blocked.samples
75
- previously_violated.blocked.merge_time_maps(new_blocked.time_map)
77
+ previously_violated.blocked.samples.concat(new_blocked_samples)
76
78
  end
77
79
 
78
- if (new_exploited = new_violation.exploited)
80
+ if (new_exploited_samples = new_violation.exploited&.samples)&.any?
79
81
  previously_violated.exploited ||= sample_activity.new
80
- previously_violated.exploited.samples.concat(new_exploited.samples) if new_exploited.samples
81
- previously_violated.exploited.merge_time_maps(new_exploited.time_map)
82
+ previously_violated.exploited.samples.concat(new_exploited_samples)
82
83
  end
83
84
 
84
- if (new_ineffective = new_violation.ineffective)
85
+ if (new_ineffective_samples = new_violation.ineffective&.samples)&.any?
85
86
  previously_violated.ineffective ||= sample_activity.new
86
- previously_violated.ineffective.samples.concat(new_ineffective.samples) if new_ineffective.samples
87
- previously_violated.ineffective.merge_time_maps(new_ineffective.time_map)
87
+ previously_violated.ineffective.samples.concat(new_ineffective_samples)
88
88
  end
89
89
 
90
- if (new_suspicious = new_violation.suspicious)
90
+ if (new_suspicious_samples = new_violation.suspicious&.samples)&.any?
91
91
  previously_violated.suspicious ||= sample_activity.new
92
- previously_violated.suspicious.samples.concat(new_suspicious.samples) if new_suspicious.samples
93
- previously_violated.suspicious.merge_time_maps(new_suspicious.time_map)
92
+ previously_violated.suspicious.samples.concat(new_suspicious_samples)
94
93
  end
95
94
  else
96
95
  existing_attacker_activity.protection_rules[rule] = new_violation
@@ -13,21 +13,17 @@ module Contrast
13
13
  class ApplicationDefendAttackSampleActivity < Contrast::Agent::Reporting::ReportableHash
14
14
  # @return [Array<Contrast::Agent::Reporting::ApplicationDefendAttackSample>]
15
15
  attr_reader :samples
16
- # @return [Hash<Integer,Integer>] map of time from start in seconds to number of attacks in that second
17
- attr_reader :time_map
18
16
 
19
17
  def initialize
20
18
  @samples = []
21
- @start_time = Contrast::Agent::REQUEST_TRACKER.current&.timer&.start_ms || 0 # in ms
19
+ @start_time = Contrast::Agent::REQUEST_TRACKER.current&.timer&.start_ms || Contrast::Utils::Timer.now_ms
22
20
  @event_type = :application_defend_attack_sample_activity
23
- @time_map = Hash.new { |h, k| h[k] = 0 }
24
21
  super()
25
22
  end
26
23
 
27
24
  def to_controlled_hash
28
25
  validate
29
26
  {
30
- attackTimeMap: time_map,
31
27
  samples: samples.map(&:to_controlled_hash),
32
28
  startTime: @start_time, # Start time in ms.
33
29
  total: 1 # there will only ever be 1 attack sample, until batching is done
@@ -41,32 +37,11 @@ module Contrast
41
37
  # @param attack_result [Contrast::Agent::Reporting::AttackResult]
42
38
  def attach_data attack_result
43
39
  attack_result.samples.each do |attack_sample|
44
- base_time = Contrast::Agent::REQUEST_TRACKER.current&.timer&.start_ms || 0
45
- sample_time = attack_sample.time_stamp.to_i
46
40
  samples << Contrast::Agent::Reporting::ApplicationDefendAttackSample.convert(attack_result, attack_sample)
47
- @start_time = if sample_time.zero?
48
- @start_time
49
- else
50
- sample_time
51
- end
52
- attack_second = (@start_time - base_time) / 1000 # in seconds
53
- time_map[attack_second] += 1
54
- end
55
- end
56
-
57
- # This method will merge time_maps of attack samples with same
58
- # type.
59
- #
60
- # @param map [Hash<Integer,Integer>] TimeMap to append to previously_violated rule
61
- # samples.
62
- # @return time_map [Hash<Integer,Integer>] merged time map with updated occurrences.
63
- def merge_time_maps map
64
- # If the second is the same (key) if we just merge there won't be a new entry,
65
- # so just increase the attack count.
66
- map.each_key do |key|
67
- @time_map[key] = @time_map.fetch(key, 0) + map[key]
41
+ # If somehow this sample was found before this container was created, change the time to reflect that
42
+ sample_time = attack_sample.time_stamp.to_i
43
+ @start_time = sample_time if sample_time < @start_time
68
44
  end
69
- @time_map
70
45
  end
71
46
  end
72
47
  end
@@ -28,8 +28,7 @@ module Contrast
28
28
  # saved request.
29
29
  def initialize ia_request: nil
30
30
  @protection_rules = {}
31
- req = ia_request || Contrast::Agent::REQUEST_TRACKER.current&.request
32
- if req
31
+ if (req = ia_request || Contrast::Agent::REQUEST_TRACKER.current&.request)
33
32
  @source_ip = req.ip || Contrast::Utils::ObjectShare::EMPTY_STRING
34
33
  @source_forwarded_for = req.headers['X-Forwarded-For']
35
34
  end
@@ -15,7 +15,7 @@ module Contrast
15
15
  class ApplicationInventoryActivity < Contrast::Agent::Reporting::ApplicationReportingEvent
16
16
  # return [Array<Contrast::Agent::Reporting::ArchitectureComponent>]
17
17
  attr_reader :components
18
- # @ return [Array<String>, nil] - User-Agent Header value
18
+ # @ return [Array<String>] - User-Agent Header value
19
19
  attr_reader :browsers
20
20
 
21
21
  def initialize
@@ -34,7 +34,7 @@ module Contrast
34
34
  end
35
35
 
36
36
  # @param architectures [Array<Contrast::Agent::Reporting::ArchitectureComponent>,
37
- # Contrast::Agent::Reporting::ArchitectureComponent]
37
+ # Contrast::Agent::Reporting::ArchitectureComponent]
38
38
  def attach_data architectures
39
39
  Array(architectures).each do |architecture|
40
40
  @components << architecture
@@ -31,7 +31,7 @@ module Contrast
31
31
  attr_reader :uri
32
32
  # @return [String] the HTTP version of this request
33
33
  attr_reader :version
34
- # @return [Integer]
34
+ # @return [String]
35
35
  attr_reader :ip
36
36
  # @return [String] Byte representation of the body
37
37
  attr_accessor :body_binary
@@ -40,7 +40,7 @@ module Contrast
40
40
 
41
41
  class << self
42
42
  # @param request [Contrast::Agent::Request]
43
- # @return [Contrast::Agent::Reporting::FindingRequest]
43
+ # @return [Contrast::Agent::Reporting::FindingRequest, nil]
44
44
  def convert request
45
45
  return unless request
46
46
 
@@ -126,10 +126,23 @@ module Contrast
126
126
  # @param response_data [Hash]
127
127
  # @param res [Contrast::Agent::Reporting::Response]
128
128
  def ng_extract_log_settings response_data, res
129
- return unless (log_level = response_data[:logLevel])
129
+ # agent_startup event defines the log level under features.
130
+ log_level = if response_data[:features]
131
+ response_data[:features][:logLevel]
132
+ else
133
+ response_data[:logLevel]
134
+ end
135
+ return unless log_level
130
136
 
131
137
  res.server_features.log_level = log_level
132
- res.server_features.log_file = response_data[:logFile] if response_data[:logFile]
138
+ log_file = if response_data[:features]
139
+ response_data[:features][:logFile]
140
+ else
141
+ response_data[:logFile]
142
+ end
143
+ return unless log_file
144
+
145
+ res.server_features.log_file = log_file
133
146
  end
134
147
  end
135
148
  end
@@ -87,8 +87,6 @@ module Contrast
87
87
  messages: messages,
88
88
  features: server_features.nil? ? nil : server_features.to_controlled_hash,
89
89
  settings: application_settings.nil? ? nil : application_settings.to_controlled_hash,
90
- logLevel: server_features&.log_level,
91
- logFile: server_features&.log_file,
92
90
  reactions: server_features.nil? ? nil : reactions.map(&:to_controlled_hash)
93
91
  }.compact
94
92
  end