contrast-agent 7.3.1 → 7.4.0

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