contrast-agent 7.3.1 → 7.4.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 (63) 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/inventory/policy/datastores.rb +0 -3
  5. data/lib/contrast/agent/protect/rule/base.rb +5 -1
  6. data/lib/contrast/agent/protect/rule/cmdi/cmd_injection.rb +17 -5
  7. data/lib/contrast/agent/protect/rule/input_classification/base.rb +7 -2
  8. data/lib/contrast/agent/protect/rule/input_classification/encoding.rb +1 -1
  9. data/lib/contrast/agent/protect/rule/path_traversal/path_traversal.rb +8 -1
  10. data/lib/contrast/agent/protect/rule/sqli/sqli.rb +8 -1
  11. data/lib/contrast/agent/protect/state.rb +110 -0
  12. data/lib/contrast/agent/reporting/reporting_events/application_activity.rb +4 -10
  13. data/lib/contrast/agent/reporting/reporting_events/application_defend_activity.rb +11 -12
  14. data/lib/contrast/agent/reporting/reporting_events/application_defend_attack_sample_activity.rb +6 -29
  15. data/lib/contrast/agent/reporting/reporting_events/application_defend_attacker_activity.rb +1 -2
  16. data/lib/contrast/agent/reporting/reporting_events/application_inventory_activity.rb +2 -2
  17. data/lib/contrast/agent/reporting/reporting_events/architecture_component.rb +2 -0
  18. data/lib/contrast/agent/reporting/reporting_events/finding.rb +1 -0
  19. data/lib/contrast/agent/reporting/reporting_events/finding_event.rb +4 -0
  20. data/lib/contrast/agent/reporting/reporting_events/finding_request.rb +4 -2
  21. data/lib/contrast/agent/reporting/reporting_events/observed_library_usage.rb +2 -0
  22. data/lib/contrast/agent/reporting/reporting_events/preflight_message.rb +9 -5
  23. data/lib/contrast/agent/reporting/reporting_events/reportable_hash.rb +30 -6
  24. data/lib/contrast/agent/reporting/reporting_utilities/ng_response_extractor.rb +15 -2
  25. data/lib/contrast/agent/reporting/reporting_utilities/reporter_client.rb +1 -1
  26. data/lib/contrast/agent/reporting/reporting_utilities/resend.rb +1 -1
  27. data/lib/contrast/agent/reporting/reporting_utilities/response.rb +0 -2
  28. data/lib/contrast/agent/reporting/reporting_utilities/response_handler_utils.rb +4 -5
  29. data/lib/contrast/agent/reporting/settings/protect.rb +61 -18
  30. data/lib/contrast/agent/reporting/settings/sampling.rb +5 -4
  31. data/lib/contrast/agent/reporting/settings/server_features.rb +2 -0
  32. data/lib/contrast/agent/version.rb +1 -1
  33. data/lib/contrast/components/agent.rb +3 -5
  34. data/lib/contrast/components/api.rb +3 -3
  35. data/lib/contrast/components/assess_rules.rb +1 -2
  36. data/lib/contrast/components/base.rb +1 -2
  37. data/lib/contrast/components/config/sources.rb +23 -0
  38. data/lib/contrast/components/logger.rb +19 -0
  39. data/lib/contrast/components/protect.rb +69 -15
  40. data/lib/contrast/components/sampling.rb +5 -12
  41. data/lib/contrast/components/security_logger.rb +17 -0
  42. data/lib/contrast/components/settings.rb +114 -70
  43. data/lib/contrast/config/certification_configuration.rb +1 -1
  44. data/lib/contrast/config/configuration_files.rb +0 -2
  45. data/lib/contrast/config/diagnostics/config.rb +3 -3
  46. data/lib/contrast/config/diagnostics/effective_config.rb +1 -1
  47. data/lib/contrast/config/diagnostics/environment_variables.rb +21 -11
  48. data/lib/contrast/config/diagnostics/monitor.rb +1 -1
  49. data/lib/contrast/config/diagnostics/singleton_tools.rb +170 -0
  50. data/lib/contrast/config/diagnostics/source_config_value.rb +14 -9
  51. data/lib/contrast/config/diagnostics/tools.rb +23 -84
  52. data/lib/contrast/config/request_audit_configuration.rb +1 -1
  53. data/lib/contrast/config/server_configuration.rb +3 -15
  54. data/lib/contrast/configuration.rb +5 -2
  55. data/lib/contrast/framework/manager.rb +4 -3
  56. data/lib/contrast/framework/manager_extend.rb +3 -1
  57. data/lib/contrast/framework/rack/support.rb +11 -2
  58. data/lib/contrast/utils/log_utils.rb +1 -1
  59. data/lib/contrast/utils/reporting/application_activity_batch_utils.rb +0 -3
  60. data/lib/contrast/utils/request_utils.rb +1 -1
  61. data/lib/contrast/utils/timer.rb +1 -1
  62. data/lib/contrast.rb +1 -1
  63. metadata +4 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4223bb2218df4bdf98b2600c77c4fa148e71b830575e938a968d41e89138fb81
4
- data.tar.gz: cc559a6b364c5019d9c50d8ee6b8237486ac1596ec8ade76d8e6ae4e51e1b906
3
+ metadata.gz: 1266effb11fb78123e8461ce7295f9b4a67a88b7dda632bc57da5d70cb39f87d
4
+ data.tar.gz: e553aa33bd73ab712585b774578c10ff6f19ae925fdea8adb89c3b6ac253e399
5
5
  SHA512:
6
- metadata.gz: 6b88f94ad140a8dd0e8099e2d4f3a396f2221de6ba9a3cbdfaa7e9327644703b7dc275c369ffe4efa29109619bf9eb182f2a0f7b85103c67e2a83246c49c137a
7
- data.tar.gz: eaf65ba9546f37ace248a29657e690966deaf02aa26426b169f1f3e9639aa3f7b355ea858afd4ef0a5787bd9e2549d7927b3db89894eb6ef4bcd26daf01382e9
6
+ metadata.gz: 7abce257d4092010babc21cff242307343ea2fc83bdbd040b8cd95e64be9b2e640963a06ae017053989be17e98442bbc528987c2da5b8f7bc8688b776022c783
7
+ data.tar.gz: f85de50b08e0eaa5f5d8c6a571fe4d04a03cbbcaab1f9c7f2431dac6b4373b00e04f63be94b5f02d56e222248c5fe256b61fb43ba123d041e1ce585b80e63a5f
@@ -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);
@@ -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
@@ -45,7 +45,6 @@ module Contrast
45
45
  #
46
46
  # @return mode [Symbol]
47
47
  def initialize
48
- ::Contrast::PROTECT.defend_rules[rule_name] = self
49
48
  @mode = mode_from_settings
50
49
  end
51
50
 
@@ -63,6 +62,11 @@ module Contrast
63
62
  RULE_NAME
64
63
  end
65
64
 
65
+ # Update state form Settings or Configuration.
66
+ def update
67
+ @mode = mode_from_settings
68
+ end
69
+
66
70
  # Should return the short name.
67
71
  #
68
72
  # @return [String]
@@ -31,15 +31,27 @@ module Contrast
31
31
  NAME
32
32
  end
33
33
 
34
+ # Sub-rules forwarders:
35
+
36
+ # @return [Contrast::Agent::Protect::Rule::CmdiBackdoors]
37
+ def command_backdoors
38
+ @_command_backdoors ||= Contrast::Agent::Protect::Rule::CmdiBackdoors.new
39
+ end
40
+
41
+ # @return [Contrast::Agent::Protect::Rule::CmdiChainedCommand]
42
+ def semantic_chained_commands
43
+ @_semantic_chained_commands ||= Contrast::Agent::Protect::Rule::CmdiChainedCommand.new
44
+ end
45
+
46
+ def semantic_dangerous_paths
47
+ @_semantic_dangerous_paths ||= Contrast::Agent::Protect::Rule::CmdiDangerousPath.new
48
+ end
49
+
34
50
  # Array of sub_rules:
35
51
  #
36
52
  # @return [Array]
37
53
  def sub_rules
38
- @_sub_rules ||= [
39
- Contrast::Agent::Protect::Rule::CmdiBackdoors.new,
40
- Contrast::Agent::Protect::Rule::CmdiChainedCommand.new,
41
- Contrast::Agent::Protect::Rule::CmdiDangerousPath.new
42
- ].cs__freeze
54
+ @_sub_rules ||= [command_backdoors, semantic_chained_commands, semantic_dangerous_paths].cs__freeze
43
55
  end
44
56
 
45
57
  def applicable_user_inputs
@@ -24,7 +24,7 @@ module Contrast
24
24
  COOKIE_VALUE, PARAMETER_VALUE, HEADER, JSON_VALUE, MULTIPART_VALUE, XML_VALUE, DWR_VALUE
25
25
  ].cs__freeze
26
26
 
27
- BASE64_INPUT_TYPES = [BODY, COOKIE_VALUE, HEADER, PARAMETER_VALUE, MULTIPART_VALUE, XML_VALUE].cs__freeze
27
+ BASE64_INPUT_TYPES = [BODY, COOKIE_VALUE, PARAMETER_VALUE, MULTIPART_VALUE, XML_VALUE].cs__freeze
28
28
 
29
29
  class << self
30
30
  include Contrast::Components::Logger::InstanceMethods
@@ -172,7 +172,9 @@ module Contrast
172
172
  end
173
173
 
174
174
  # Decodes the value for the given input type.
175
- # Applies to BODY, COOKIE_VALUE, HEADER, PARAMETER_VALUE, MULTIPART_VALUE, XML_VALUE
175
+ #
176
+ # This applies to Values sources only:
177
+ # BODY, COOKIE_VALUE, HEADER, PARAMETER_VALUE, MULTIPART_VALUE, XML_VALUE
176
178
  #
177
179
  # @param value [String]
178
180
  # @param input_type [Symbol]
@@ -181,6 +183,9 @@ module Contrast
181
183
  return value unless Contrast::PROTECT.normalize_base64?
182
184
  return value unless BASE64_INPUT_TYPES.include?(input_type)
183
185
 
186
+ # TODO: RUBY-2110 Update the HEADER handling if possible.
187
+ # We need only the Header values.
188
+
184
189
  cs__decode64(value, input_type)
185
190
  end
186
191
  end
@@ -16,7 +16,7 @@ module Contrast
16
16
 
17
17
  # Still a list is needed for this one, as it is not possible to determine if the value is encoded or not.
18
18
  # As long as the list is short the method has a good percentage of success.
19
- KNOWN_DECODING_EXCEPTIONS = %w[cmd].cs__freeze
19
+ KNOWN_DECODING_EXCEPTIONS = %w[cmd version if_modified_since].cs__freeze
20
20
 
21
21
  # This methods is not performant, but is more safe for false positive.
22
22
  # Base64 check is no trivial task. For example if one passes a value like 'stringdw' it will return true,
@@ -30,11 +30,18 @@ module Contrast
30
30
  NAME
31
31
  end
32
32
 
33
+ # Sub-rules forwarders:
34
+
35
+ # @return [Contrast::Agent::Protect::Rule::PathTraversalSemanticBypass]
36
+ def semantic_file_security_bypass
37
+ @_semantic_file_security_bypass ||= Contrast::Agent::Protect::Rule::PathTraversalSemanticBypass.new
38
+ end
39
+
33
40
  # Array of sub_rules
34
41
  #
35
42
  # @return [Array]
36
43
  def sub_rules
37
- @_sub_rules ||= [Contrast::Agent::Protect::Rule::PathTraversalSemanticBypass.new].cs__freeze
44
+ @_sub_rules ||= [semantic_file_security_bypass].cs__freeze
38
45
  end
39
46
 
40
47
  def applicable_user_inputs
@@ -35,11 +35,18 @@ module Contrast
35
35
  BLOCK_MESSAGE
36
36
  end
37
37
 
38
+ # Sub-rules forwarders:
39
+
40
+ # @return [Contrast::Agent::Protect::Rule::SqliDangerousFunctions]
41
+ def semantic_dangerous_functions
42
+ @_semantic_dangerous_functions ||= Contrast::Agent::Protect::Rule::SqliDangerousFunctions.new
43
+ end
44
+
38
45
  # Array of sub_rules
39
46
  #
40
47
  # @return [Array]
41
48
  def sub_rules
42
- @_sub_rules ||= [Contrast::Agent::Protect::Rule::SqliDangerousFunctions.new].cs__freeze
49
+ @_sub_rules ||= [semantic_dangerous_functions].cs__freeze
43
50
  end
44
51
 
45
52
  def applicable_user_inputs
@@ -0,0 +1,110 @@
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/utils/object_share'
5
+ require 'contrast/components/logger'
6
+
7
+ module Contrast
8
+ module Agent
9
+ module Protect
10
+ # Master class for each protect rule. This class will hold all the rules references.
11
+ # Any access to the rules should be done through this class. and new rules should be
12
+ # added here. Each main rule should require and include and initialize it's sub-rules.
13
+ class State
14
+ include Contrast::Components::Logger::InstanceMethods
15
+
16
+ # @return [boolean] State dictated by local or server settings
17
+ attr_accessor :enabled
18
+ # @return [Contrast::Agent::Protect::Rule::BotBlocker] the bot blocker rule
19
+ attr_reader :bot_blocker
20
+ # @return [Contrast::Agent::Protect::Rule::CmdInjection] the command injection rule
21
+ attr_reader :cmd_injection
22
+ # @return [Contrast::Agent::Protect::Rule::CmdiBackdoors]
23
+ attr_reader :cmd_injection_command_backdoors
24
+ # @return [Contrast::Agent::Protect::Rule::CmdiChainedCommand]
25
+ attr_reader :cmd_injection_semantic_chained_commands
26
+ # @return [Contrast::Agent::Protect::Rule::CmdiDangerousPath]
27
+ attr_reader :cmd_injection_semantic_dangerous_paths
28
+ # @return [Contrast::Agent::Protect::Rule::Deserialization]
29
+ attr_reader :untrusted_deserialization
30
+ # @return [Contrast::Agent::Protect::Rule::NoSqli]
31
+ attr_reader :nosql_injection
32
+ # @return [Contrast::Agent::Protect::Rule::PathTraversal]
33
+ attr_reader :path_traversal
34
+ # @return [Contrast::Agent::Protect::Rule::PathTraversalSemanticBypass]
35
+ attr_reader :path_traversal_semantic_file_security_bypass
36
+ # @return [Contrast::Agent::Protect::Rule::Sqli]
37
+ attr_reader :sql_injection
38
+ # @return [Contrast::Agent::Protect::Rule::SqliDangerousFunctions]
39
+ attr_reader :sql_injection_semantic_dangerous_functions
40
+ # @return [Contrast::Agent::Protect::Rule::UnsafeFileUpload] the unsafe file upload rule
41
+ attr_reader :unsafe_file_upload
42
+ # @return [Contrast::Agent::Protect::Rule::Xss] the reflected xss rule
43
+ attr_reader :reflected_xss
44
+ # @return [Contrast::Agent::Protect::Rule::Xxe] the xxe rule
45
+ attr_reader :xxe
46
+
47
+ # Initialize all the protect rules. This should be the one place to access each live
48
+ # rule reference.
49
+ def initialize
50
+ @bot_blocker = Contrast::Agent::Protect::Rule::BotBlocker.new
51
+ @cmd_injection = Contrast::Agent::Protect::Rule::CmdInjection.new
52
+ @cmd_injection_command_backdoors = @cmd_injection.command_backdoors
53
+ @cmd_injection_semantic_chained_commands = @cmd_injection.semantic_chained_commands
54
+ @cmd_injection_semantic_dangerous_paths = @cmd_injection.semantic_dangerous_paths
55
+ @untrusted_deserialization = Contrast::Agent::Protect::Rule::Deserialization.new
56
+ @nosql_injection = Contrast::Agent::Protect::Rule::NoSqli.new
57
+ @path_traversal = Contrast::Agent::Protect::Rule::PathTraversal.new
58
+ @path_traversal_semantic_file_security_bypass = @path_traversal.semantic_file_security_bypass
59
+ @sql_injection = Contrast::Agent::Protect::Rule::Sqli.new
60
+ @sql_injection_semantic_dangerous_functions = @sql_injection.semantic_dangerous_functions
61
+ @unsafe_file_upload = Contrast::Agent::Protect::Rule::UnsafeFileUpload.new
62
+ @reflected_xss = Contrast::Agent::Protect::Rule::Xss.new
63
+ @xxe = Contrast::Agent::Protect::Rule::Xxe.new
64
+ end
65
+
66
+ # Return the Rules in Hash form {rule_id => rule_class }.
67
+ # This is used to traverse for each rule and update it's settings.
68
+ # Also is the way a rule is retrieved given the ID is known.
69
+ #
70
+ # @return [Hash<String, Contrast::Agent::Protect::Rule::Base>]
71
+ def rules
72
+ @_rules ||= {
73
+ @bot_blocker.rule_name => @bot_blocker,
74
+ @cmd_injection.rule_name => @cmd_injection,
75
+ @cmd_injection_command_backdoors.rule_name => @cmd_injection_command_backdoors,
76
+ @cmd_injection_semantic_chained_commands.rule_name => @cmd_injection_semantic_chained_commands,
77
+ @cmd_injection_semantic_dangerous_paths.rule_name => @cmd_injection_semantic_dangerous_paths,
78
+ @untrusted_deserialization.rule_name => @untrusted_deserialization,
79
+ @nosql_injection.rule_name => @nosql_injection,
80
+ @path_traversal.rule_name => @path_traversal,
81
+ @path_traversal_semantic_file_security_bypass.rule_name => @path_traversal_semantic_file_security_bypass,
82
+ @sql_injection.rule_name => @sql_injection,
83
+ @sql_injection_semantic_dangerous_functions.rule_name => @sql_injection_semantic_dangerous_functions,
84
+ @unsafe_file_upload.rule_name => @unsafe_file_upload,
85
+ @reflected_xss.rule_name => @reflected_xss,
86
+ @xxe.rule_name => @xxe
87
+ }
88
+ end
89
+
90
+ # Update all settings from configuration.
91
+ def update
92
+ rules.values.each(&:update)
93
+ logger.info('Current rule settings:')
94
+ rules.each { |k, v| logger.info('Protect Rule mode set', rule: k, mode: v.mode) }
95
+ end
96
+
97
+ # Check the local configurations first then the server settings.
98
+ def enabled?
99
+ Contrast::PROTECT.enable || Contrast::SETTINGS.protect_state.enabled
100
+ end
101
+
102
+ # @param [String] rule_id
103
+ # @return [Contrast::Agent::Protect::Rule::Base]
104
+ def [] rule_id
105
+ rules[rule_id]
106
+ end
107
+ end
108
+ end
109
+ end
110
+ 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
@@ -36,37 +32,18 @@ module Contrast
36
32
 
37
33
  def validate
38
34
  raise(ArgumentError, 'Start Time is not presented') unless @start_time
35
+
36
+ nil
39
37
  end
40
38
 
41
39
  # @param attack_result [Contrast::Agent::Reporting::AttackResult]
42
40
  def attach_data attack_result
43
41
  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
42
  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]
43
+ # If somehow this sample was found before this container was created, change the time to reflect that
44
+ sample_time = attack_sample.time_stamp.to_i
45
+ @start_time = sample_time if sample_time < @start_time
68
46
  end
69
- @time_map
70
47
  end
71
48
  end
72
49
  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
@@ -59,6 +59,8 @@ module Contrast
59
59
  raise(ArgumentError, "#{ self } did not have a proper type - '#{ type }'. Unable to continue.")
60
60
  end
61
61
  raise(ArgumentError, "#{ self } did not have a proper URL. Unable to continue.") unless url
62
+
63
+ nil
62
64
  end
63
65
  end
64
66
  end
@@ -125,6 +125,7 @@ module Contrast
125
125
  # @raise [ArgumentError]
126
126
  def validate
127
127
  raise(ArgumentError, "#{ self } did not have a proper rule. Unable to continue.") unless @rule_id
128
+
128
129
  unless ::Contrast::ASSESS.session_id
129
130
  raise(ArgumentError, "#{ self } did not have a proper session id. Unable to continue.")
130
131
  end
@@ -422,6 +422,8 @@ module Contrast
422
422
  raise(ArgumentError, "#{ self } did not have a proper thread. Unable to continue.") unless thread
423
423
  raise(ArgumentError, "#{ self } did not have a proper time. Unable to continue.") unless time
424
424
  raise(ArgumentError, "#{ self } did not have a proper type. Unable to continue.") unless type
425
+
426
+ nil
425
427
  end
426
428
 
427
429
  # @raise [ArgumentError]
@@ -435,6 +437,8 @@ module Contrast
435
437
  raise(ArgumentError, "#{ self } did not have a proper signature. Unable to continue.") unless signature
436
438
  raise(ArgumentError, "#{ self } did not have a proper stack. Unable to continue.") unless stack
437
439
  raise(ArgumentError, "#{ self } did not have a proper tags. Unable to continue.") unless reportable_tags
440
+
441
+ nil
438
442
  end
439
443
  end
440
444
  end