contrast-agent 7.3.0 → 7.3.2

Sign up to get free protection for your applications and to get access to all the features.
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