contrast-agent 7.3.2 → 7.4.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. checksums.yaml +4 -4
  2. data/lib/contrast/agent/middleware/middleware.rb +1 -1
  3. data/lib/contrast/agent/protect/input_analyzer/input_analyzer.rb +9 -11
  4. data/lib/contrast/agent/protect/input_analyzer/worth_watching_analyzer.rb +55 -20
  5. data/lib/contrast/agent/protect/policy/rule_applicator.rb +1 -4
  6. data/lib/contrast/agent/protect/rule/base.rb +61 -26
  7. data/lib/contrast/agent/protect/rule/bot_blocker/bot_blocker.rb +12 -4
  8. data/lib/contrast/agent/protect/rule/cmdi/cmd_injection.rb +19 -15
  9. data/lib/contrast/agent/protect/rule/cmdi/cmdi_backdoors.rb +2 -4
  10. data/lib/contrast/agent/protect/rule/cmdi/cmdi_base_rule.rb +2 -1
  11. data/lib/contrast/agent/protect/rule/deserialization/deserialization.rb +4 -4
  12. data/lib/contrast/agent/protect/rule/input_classification/base.rb +7 -2
  13. data/lib/contrast/agent/protect/rule/input_classification/encoding.rb +1 -1
  14. data/lib/contrast/agent/protect/rule/no_sqli/no_sqli.rb +5 -2
  15. data/lib/contrast/agent/protect/rule/path_traversal/path_traversal.rb +20 -8
  16. data/lib/contrast/agent/protect/rule/path_traversal/path_traversal_semantic_security_bypass.rb +2 -2
  17. data/lib/contrast/agent/protect/rule/sqli/sqli.rb +8 -1
  18. data/lib/contrast/agent/protect/rule/sqli/sqli_base_rule.rb +2 -3
  19. data/lib/contrast/agent/protect/rule/sqli/sqli_semantic/sqli_dangerous_functions.rb +3 -4
  20. data/lib/contrast/agent/protect/rule/unsafe_file_upload/unsafe_file_upload.rb +3 -0
  21. data/lib/contrast/agent/protect/rule/utils/builders.rb +3 -4
  22. data/lib/contrast/agent/protect/rule/utils/filters.rb +32 -16
  23. data/lib/contrast/agent/protect/rule/xss/xss.rb +80 -0
  24. data/lib/contrast/agent/protect/rule/xxe/xxe.rb +9 -2
  25. data/lib/contrast/agent/protect/state.rb +110 -0
  26. data/lib/contrast/agent/reporting/details/xss_match.rb +17 -0
  27. data/lib/contrast/agent/reporting/input_analysis/input_analysis.rb +32 -0
  28. data/lib/contrast/agent/reporting/reporting_events/application_defend_attack_sample_activity.rb +2 -0
  29. data/lib/contrast/agent/reporting/reporting_events/architecture_component.rb +2 -0
  30. data/lib/contrast/agent/reporting/reporting_events/finding.rb +1 -4
  31. data/lib/contrast/agent/reporting/reporting_events/finding_event.rb +4 -0
  32. data/lib/contrast/agent/reporting/reporting_events/finding_request.rb +2 -0
  33. data/lib/contrast/agent/reporting/reporting_events/observed_library_usage.rb +2 -0
  34. data/lib/contrast/agent/reporting/reporting_events/preflight_message.rb +9 -8
  35. data/lib/contrast/agent/reporting/reporting_events/reportable_hash.rb +30 -6
  36. data/lib/contrast/agent/reporting/reporting_utilities/reporter_client.rb +1 -1
  37. data/lib/contrast/agent/reporting/reporting_utilities/resend.rb +1 -1
  38. data/lib/contrast/agent/reporting/reporting_utilities/response_handler_utils.rb +1 -5
  39. data/lib/contrast/agent/reporting/settings/protect.rb +3 -3
  40. data/lib/contrast/agent/reporting/settings/sampling.rb +5 -4
  41. data/lib/contrast/agent/request/request_context_extend.rb +0 -2
  42. data/lib/contrast/agent/version.rb +1 -1
  43. data/lib/contrast/components/agent.rb +3 -5
  44. data/lib/contrast/components/api.rb +3 -3
  45. data/lib/contrast/components/assess.rb +4 -0
  46. data/lib/contrast/components/assess_rules.rb +1 -2
  47. data/lib/contrast/components/base.rb +1 -2
  48. data/lib/contrast/components/config/sources.rb +23 -0
  49. data/lib/contrast/components/logger.rb +19 -0
  50. data/lib/contrast/components/protect.rb +55 -14
  51. data/lib/contrast/components/sampling.rb +5 -12
  52. data/lib/contrast/components/security_logger.rb +17 -0
  53. data/lib/contrast/components/settings.rb +110 -76
  54. data/lib/contrast/config/certification_configuration.rb +1 -1
  55. data/lib/contrast/config/configuration_files.rb +0 -2
  56. data/lib/contrast/config/diagnostics/config.rb +3 -3
  57. data/lib/contrast/config/diagnostics/effective_config.rb +1 -1
  58. data/lib/contrast/config/diagnostics/environment_variables.rb +21 -11
  59. data/lib/contrast/config/diagnostics/monitor.rb +1 -1
  60. data/lib/contrast/config/diagnostics/singleton_tools.rb +170 -0
  61. data/lib/contrast/config/diagnostics/source_config_value.rb +14 -9
  62. data/lib/contrast/config/diagnostics/tools.rb +23 -84
  63. data/lib/contrast/config/request_audit_configuration.rb +1 -1
  64. data/lib/contrast/config/server_configuration.rb +3 -15
  65. data/lib/contrast/configuration.rb +5 -2
  66. data/lib/contrast/framework/manager.rb +4 -3
  67. data/lib/contrast/framework/manager_extend.rb +3 -1
  68. data/lib/contrast/framework/rack/support.rb +11 -2
  69. data/lib/contrast/framework/rails/support.rb +2 -2
  70. data/lib/contrast/logger/cef_log.rb +30 -4
  71. data/lib/contrast/utils/io_util.rb +3 -0
  72. data/lib/contrast/utils/log_utils.rb +22 -11
  73. data/lib/contrast/utils/request_utils.rb +1 -1
  74. data/lib/contrast/utils/timer.rb +1 -1
  75. metadata +4 -2
@@ -32,6 +32,9 @@ module Contrast
32
32
  NAME
33
33
  end
34
34
 
35
+ # Return the specific blocking message for this rule.
36
+ #
37
+ # @return [String] the reason for the raised security exception.
35
38
  def block_message
36
39
  BLOCK_MESSAGE
37
40
  end
@@ -56,9 +59,9 @@ module Contrast
56
59
  return unless result
57
60
 
58
61
  append_to_activity(context, result)
59
-
62
+ record_triggered(context)
60
63
  cef_logging(result, :successful_attack)
61
- raise(Contrast::SecurityException.new(self, BLOCK_MESSAGE)) if blocked?
64
+ raise(Contrast::SecurityException.new(self, BLOCK_MESSAGE)) if blocked_violation?(result)
62
65
  end
63
66
 
64
67
  def build_attack_with_match context, input_analysis_result, result, candidate_string, **kwargs
@@ -26,21 +26,37 @@ module Contrast
26
26
  JSON_VALUE, MULTIPART_VALUE, MULTIPART_FIELD_NAME, XML_VALUE, DWR_VALUE, URI
27
27
  ].cs__freeze
28
28
 
29
+ BLOCK_MESSAGE = 'Path Traversal rule triggered. Request blocked.'
30
+
29
31
  def rule_name
30
32
  NAME
31
33
  end
32
34
 
35
+ # Sub-rules forwarders:
36
+
37
+ # @return [Contrast::Agent::Protect::Rule::PathTraversalSemanticBypass]
38
+ def semantic_file_security_bypass
39
+ @_semantic_file_security_bypass ||= Contrast::Agent::Protect::Rule::PathTraversalSemanticBypass.new
40
+ end
41
+
33
42
  # Array of sub_rules
34
43
  #
35
44
  # @return [Array]
36
45
  def sub_rules
37
- @_sub_rules ||= [Contrast::Agent::Protect::Rule::PathTraversalSemanticBypass.new].cs__freeze
46
+ @_sub_rules ||= [semantic_file_security_bypass].cs__freeze
38
47
  end
39
48
 
40
49
  def applicable_user_inputs
41
50
  APPLICABLE_USER_INPUTS
42
51
  end
43
52
 
53
+ # Return the specific blocking message for this rule.
54
+ #
55
+ # @return [String] the reason for the raised security exception.
56
+ def block_message
57
+ BLOCK_MESSAGE
58
+ end
59
+
44
60
  # Path Traversal input classification
45
61
  #
46
62
  # @return [module<Contrast::Agent::Protect::Rule::PathTraversalInputClassification>]
@@ -48,19 +64,15 @@ module Contrast
48
64
  @_classification ||= Contrast::Agent::Protect::Rule::PathTraversalInputClassification.cs__freeze
49
65
  end
50
66
 
51
- def infilter context, method, path
67
+ def infilter context, _method, path
52
68
  return unless infilter?(context)
53
69
 
54
70
  result = find_attacker(context, path)
55
71
  return unless result
56
72
 
57
73
  append_to_activity(context, result)
58
- return unless blocked?
59
-
60
- result_rule_name = Contrast::Utils::StringUtils.transform_string(result.rule_id)
61
- cef_logging(result, :successful_attack, value: path)
62
- exception_messasge = "#{ result_rule_name } rule triggered. Call to File.#{ method } blocked."
63
- raise(Contrast::SecurityException.new(self, exception_messasge))
74
+ record_triggered(context)
75
+ raise(Contrast::SecurityException.new(self, block_message)) if blocked_violation?(result)
64
76
  end
65
77
 
66
78
  protected
@@ -53,10 +53,10 @@ module Contrast
53
53
  return unless result
54
54
 
55
55
  append_to_activity(context, result)
56
- return unless blocked?
56
+ record_triggered(context)
57
+ return unless blocked_violation?(result)
57
58
 
58
59
  result_rule_name = Contrast::Utils::StringUtils.transform_string(result.rule_id)
59
- cef_logging(result, :successful_attack, value: path)
60
60
  exception_messasge = "#{ result_rule_name } rule triggered. Call to File.#{ method } blocked."
61
61
  raise(Contrast::SecurityException.new(self, exception_messasge))
62
62
  end
@@ -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
@@ -26,9 +26,8 @@ module Contrast
26
26
  return unless result
27
27
 
28
28
  append_to_activity(context, result)
29
-
30
- cef_logging(result, :successful_attack)
31
- raise(Contrast::SecurityException.new(self, BLOCK_MESSAGE)) if blocked?
29
+ record_triggered(context)
30
+ raise(Contrast::SecurityException.new(self, BLOCK_MESSAGE)) if blocked_violation?(result)
32
31
  end
33
32
  end
34
33
  end
@@ -26,9 +26,8 @@ module Contrast
26
26
  return unless result
27
27
 
28
28
  append_to_activity(context, result)
29
-
30
- cef_logging(result, :successful_attack)
31
- raise(Contrast::SecurityException.new(self, BLOCK_MESSAGE)) if blocked?
29
+ record_triggered(context)
30
+ raise(Contrast::SecurityException.new(self, BLOCK_MESSAGE)) if blocked_violation?(result)
32
31
  end
33
32
 
34
33
  protected
@@ -39,8 +38,8 @@ module Contrast
39
38
 
40
39
  def build_violation context, potential_attack_string
41
40
  result = build_attack_result(context)
42
- update_successful_attack_response(context, nil, result, potential_attack_string)
43
41
  append_sample(context, nil, result, potential_attack_string)
42
+ update_successful_attack_response(context, nil, result, potential_attack_string)
44
43
  result
45
44
  end
46
45
 
@@ -28,6 +28,9 @@ module Contrast
28
28
  APPLICABLE_USER_INPUTS
29
29
  end
30
30
 
31
+ # Return the specific blocking message for this rule.
32
+ #
33
+ # @return [String] the reason for the raised security exception.
31
34
  def block_message
32
35
  BLOCK_MESSAGE
33
36
  end
@@ -29,8 +29,8 @@ module Contrast
29
29
  # this input
30
30
  def build_attack_with_match context, ia_result, result, candidate_string, **kwargs
31
31
  result ||= build_attack_result(context)
32
- update_successful_attack_response(context, ia_result, result, candidate_string)
33
32
  append_sample(context, ia_result, result, candidate_string, **kwargs)
33
+ update_successful_attack_response(context, ia_result, result, candidate_string)
34
34
 
35
35
  result
36
36
  end
@@ -53,8 +53,8 @@ module Contrast
53
53
  # this input
54
54
  def build_attack_without_match context, ia_result, result, **kwargs
55
55
  result ||= build_attack_result(context)
56
- update_perimeter_attack_response(context, ia_result, result)
57
56
  append_sample(context, ia_result, result, nil, **kwargs)
57
+ update_perimeter_attack_response(context, ia_result, result)
58
58
 
59
59
  result
60
60
  end
@@ -97,11 +97,10 @@ module Contrast
97
97
  # @param potential_attack_string [String]
98
98
  def build_violation context, potential_attack_string
99
99
  result = build_attack_result(context)
100
+ append_sample(context, nil, result, potential_attack_string)
100
101
  update_successful_attack_response(context, nil, result, potential_attack_string)
101
102
  return unless result
102
103
 
103
- append_sample(context, nil, result, potential_attack_string)
104
- cef_logging(result, :successful_attack)
105
104
  result
106
105
  end
107
106
  end
@@ -25,11 +25,12 @@ module Contrast
25
25
 
26
26
  ia_results.each do |ia_result|
27
27
  result = build_attack_result(context)
28
- build_attack_without_match(context, ia_result, result)
29
- append_to_activity(context, result)
28
+ result = build_attack_without_match(context, ia_result, result)
29
+ next unless result
30
30
 
31
- cef_logging(result, :successful_attack)
32
- raise(Contrast::SecurityException.new(self, block_message)) if blocked?
31
+ append_to_activity(context, result)
32
+ record_triggered(context)
33
+ raise(Contrast::SecurityException.new(self, block_message)) if blocked_violation?(result)
33
34
  end
34
35
  end
35
36
 
@@ -39,10 +40,10 @@ module Contrast
39
40
  # @param context [Contrast::Agent::RequestContext]
40
41
  # @return [Boolean]
41
42
  def prefilter? context
42
- return false unless context
43
43
  return false unless enabled?
44
- return false unless (results = gather_ia_results(context)) && results.any?
45
44
  return false if protect_excluded_by_url?(rule_name)
45
+ return false unless context
46
+ return false unless (results = gather_ia_results(context)) && results.any?
46
47
  return false if protect_excluded_by_input?(results)
47
48
 
48
49
  true
@@ -52,13 +53,20 @@ module Contrast
52
53
  # have a different implementation based on the rule. As such, there
53
54
  # is not parent implementation.
54
55
  #
55
- # @param _context [Contrast::Agent::RequestContext] the context for
56
+ # @param context [Contrast::Agent::RequestContext] the context for
56
57
  # the current request
57
- # @param _match_string [String] the input that violated the rule and
58
+ # @param match_string [String] the input that violated the rule and
58
59
  # matched the attack detection logic
59
60
  # @param _kwargs [Hash] key-value pairs used by the rule to build a
60
61
  # report.
61
- def infilter _context, _match_string, **_kwargs; end
62
+ def infilter context, match_string, _kwargs
63
+ return unless infilter?(context)
64
+ return unless (result = build_violation(context, match_string))
65
+
66
+ append_to_activity(context, result)
67
+ record_triggered(context)
68
+ raise(Contrast::SecurityException.new(self, block_message)) if blocked?
69
+ end
62
70
 
63
71
  # Infilter check always called before infilter to check if the rule is infilter
64
72
  # capable, not disabled or in other way excluded by url or input exclusions.
@@ -74,6 +82,18 @@ module Contrast
74
82
  true
75
83
  end
76
84
 
85
+ # Check befor commiting infilter
86
+ #
87
+ # @param context [Contrast::Agent::RequestContext]
88
+ def postfilter? context
89
+ return false unless enabled? && POSTFILTER_MODES.include?(mode)
90
+ return false if protect_excluded_by_url?(rule_name)
91
+ return false if protect_excluded_by_input?(gather_ia_results(context))
92
+ return false if mode == :NO_ACTION || mode == :PERMIT
93
+
94
+ true
95
+ end
96
+
77
97
  # Actions required for the rules that have to happen after the
78
98
  # application has completed its processing of the request.
79
99
  #
@@ -88,18 +108,14 @@ module Contrast
88
108
  # @param context [Contrast::Agent::RequestContext]
89
109
  # @raise [Contrast::SecurityException]
90
110
  def postfilter context
91
- return unless enabled? && POSTFILTER_MODES.include?(mode)
92
- return false if protect_excluded_by_url?(rule_name)
93
- return if protect_excluded_by_input?(gather_ia_results(context))
94
-
95
- return if mode == :NO_ACTION || mode == :PERMIT
111
+ return unless postfilter?(context)
96
112
 
97
113
  result = find_postfilter_attacker(context, nil)
98
114
  return unless result&.samples&.any?
99
115
 
100
- cef_logging(result)
101
116
  append_to_activity(context, result)
102
- return unless result.response == :BLOCKED
117
+ record_triggered(context)
118
+ return unless blocked_violation?(result)
103
119
 
104
120
  raise(Contrast::SecurityException.new(self, "#{ rule_name } triggered in postfilter. Response blocked."))
105
121
  end
@@ -3,6 +3,8 @@
3
3
 
4
4
  require 'contrast/agent/protect/rule/base'
5
5
  require 'contrast/agent/protect/rule/xss/reflected_xss_input_classification'
6
+ require 'contrast/agent/reporting/details/xss_details'
7
+ require 'contrast/agent/reporting/details/xss_match'
6
8
  require 'contrast/agent/reporting/input_analysis/input_type'
7
9
 
8
10
  module Contrast
@@ -25,10 +27,56 @@ module Contrast
25
27
  NAME
26
28
  end
27
29
 
30
+ # Return the specific blocking message for this rule.
31
+ #
32
+ # @return [String] the reason for the raised security exception.
28
33
  def block_message
29
34
  BLOCK_MESSAGE
30
35
  end
31
36
 
37
+ # Prefilter check always called before infilter to check if the rule is infilter
38
+ # capable, not disabled or in other way excluded by url or input exclusions.
39
+ #
40
+ # @param context [Contrast::Agent::RequestContext]
41
+ # @return [Boolean]
42
+ def prefilter? context
43
+ return false unless enabled?
44
+ return false if protect_excluded_by_url?(rule_name)
45
+ return false unless context
46
+ return false unless (results = gather_ia_results(context)) && results.any?
47
+ return false if protect_excluded_by_input?(results)
48
+
49
+ true
50
+ end
51
+
52
+ def prefilter context
53
+ return unless prefilter?(context)
54
+
55
+ ia_results = gather_ia_results(context)
56
+
57
+ ia_results.each do |ia_result|
58
+ result = build_attack_result(context)
59
+ result = build_attack_without_match(context, ia_result, result)
60
+ next unless result
61
+
62
+ append_to_activity(context, result)
63
+ # XSS is being triggered, so we need to add it to the triggered rules,
64
+ # So the IA won't be done for this rule again for the current request.
65
+ record_triggered(context)
66
+ raise(Contrast::SecurityException.new(self, block_message)) if blocked_violation?(result)
67
+ end
68
+ end
69
+
70
+ # XSS is evaluated only on prefilter
71
+ def infilter? _context
72
+ false
73
+ end
74
+
75
+ # XSS is evaluated only on prefilter
76
+ def postfilter? _context
77
+ false
78
+ end
79
+
32
80
  # XSS Upload input classification
33
81
  #
34
82
  # @return [module<Contrast::Agent::Protect::Rule::ReflectedXssInputClassification>]
@@ -43,6 +91,38 @@ module Contrast
43
91
  def applicable_user_inputs
44
92
  APPLICABLE_USER_INPUTS
45
93
  end
94
+
95
+ # Adding XSS details
96
+ #
97
+ # @param context [Contrast::Agent::RequestContext]
98
+ # @param ia_result [Contrast::Agent::Reporting::InputAnalysisResult]
99
+ # @param _xss_string
100
+ # @param **_kwargs
101
+ # @return [Contrast::Agent::Reporting::RaspRuleSample]
102
+ def build_sample context, ia_result, _xss_string, **_kwargs
103
+ sample = build_base_sample(context, ia_result)
104
+ sample.details = Contrast::Agent::Reporting::Details::XssDetails.new
105
+ sample.details.input = ia_result.value
106
+
107
+ # TODO: RUBY-99999 check the if the ReflectedXss matches are needed.
108
+ xss_match = Contrast::Agent::Reporting::Details::XssMatch.new(ia_result.value)
109
+ sample.details.matches << xss_match unless xss_match.empty?
110
+
111
+ sample
112
+ end
113
+
114
+ private
115
+
116
+ # @param context [Contrast::Agent::RequestContext]
117
+ # @param potential_attack_string [String, nil]
118
+ # @return [Contrast::Agent::Reporting::AttackResult, nil]
119
+ def find_postfilter_attacker context, potential_attack_string, **kwargs
120
+ ia_results = gather_ia_results(context)
121
+ ia_results.reject! do |ia_result|
122
+ ia_result.score_level == Contrast::Agent::Reporting::ScoreLevel::IGNORE
123
+ end
124
+ find_attacker_with_results(context, potential_attack_string, ia_results, **kwargs)
125
+ end
46
126
  end
47
127
  end
48
128
  end
@@ -26,6 +26,13 @@ module Contrast
26
26
  NAME
27
27
  end
28
28
 
29
+ # Return the specific blocking message for this rule.
30
+ #
31
+ # @return [String] the reason for the raised security exception.
32
+ def block_message
33
+ BLOCK_MESSAGE
34
+ end
35
+
29
36
  # Given an xml, evaluate it for an XXE attack. There's no return here
30
37
  # as this method handles appending the evaluation to the request
31
38
  # context, connecting it to the reporting mechanism at request end.
@@ -43,9 +50,9 @@ module Contrast
43
50
  return unless result
44
51
 
45
52
  append_to_activity(context, result)
46
- return unless blocked?
53
+ record_triggered(context)
54
+ return unless blocked_violation?(result)
47
55
 
48
- cef_logging(result, :successful_attack, value: xml)
49
56
  raise(Contrast::SecurityException.new(self, BLOCK_MESSAGE))
50
57
  end
51
58
 
@@ -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
@@ -9,6 +9,9 @@ module Contrast
9
9
  module Details
10
10
  # Matcher data for XSS rule.
11
11
  class XssMatch
12
+ EVIDENCE_START = /<script.*?>/i.cs__freeze
13
+ EVIDENCE_END = %r{</script.*?>}i.cs__freeze
14
+
12
15
  # @return [Integer] in ms
13
16
  attr_accessor :evidence_start
14
17
  # @return [String]
@@ -16,6 +19,16 @@ module Contrast
16
19
  # @return [Integer]
17
20
  attr_accessor :offset
18
21
 
22
+ # @param xss_string [String] to check for matches.
23
+ def initialize xss_string = ''
24
+ return if xss_string.empty?
25
+
26
+ @evidence_start = xss_string.index(EVIDENCE_START)
27
+ @offset = (xss_string[0...@evidence_start] || '').length
28
+ @evidence = xss_string[@evidence_start...xss_string.index(EVIDENCE_END)].gsub(EVIDENCE_START, '').
29
+ gsub(EVIDENCE_END, '')
30
+ end
31
+
19
32
  def to_controlled_hash
20
33
  {
21
34
  evidenceStart: evidence_start,
@@ -23,6 +36,10 @@ module Contrast
23
36
  offset: offset
24
37
  }
25
38
  end
39
+
40
+ def empty?
41
+ evidence_start.nil? || evidence.nil? || offset.nil?
42
+ end
26
43
  end
27
44
  end
28
45
  end
@@ -2,6 +2,7 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require 'contrast/utils/object_share'
5
+ require 'contrast/utils/duck_utils'
5
6
  require 'contrast/agent/reporting/input_analysis/input_analysis_result'
6
7
 
7
8
  module Contrast
@@ -12,10 +13,20 @@ module Contrast
12
13
  # @return [Hash] Stored request inputs for this context.
13
14
  attr_accessor :inputs
14
15
 
16
+ # Records rules triggered on sink.
17
+ #
18
+ # @return [Array<String>] array with rule names.
15
19
  def triggered_rules
16
20
  @_triggered_rules ||= []
17
21
  end
18
22
 
23
+ # Records rules with input analysis for current request.
24
+ #
25
+ # @return [Array<String>] array with rule names.
26
+ def analysed_rules
27
+ @_analysed_rules ||= []
28
+ end
29
+
19
30
  # result from input analysis
20
31
  #
21
32
  # @return @_results [Array<Contrast::Agent::Reporting::Settings::InputAnalysisResult>]
@@ -44,6 +55,27 @@ module Contrast
44
55
  def request= request
45
56
  @_request = request if request.instance_of?(Contrast::Agent::Request)
46
57
  end
58
+
59
+ # Check if the InputAnalysis is empty.
60
+ def no_inputs?
61
+ Contrast::Utils::DuckUtils.empty_duck?(inputs)
62
+ end
63
+
64
+ # We add the analysed rules to the list. The IA won't be build for this rule,
65
+ # since already it's already available.
66
+ #
67
+ # @param rule_name [String] name to record.
68
+ def record_analysed_rule rule_name
69
+ analysed_rules << rule_name unless triggered_rules.include?(rule_name)
70
+ end
71
+
72
+ # We add the triggered rules to the list. After request analysis will skip this rule
73
+ # as already triggered.
74
+ #
75
+ # @param rule_name [String] name to record.
76
+ def record_rule_triggered rule_name
77
+ triggered_rules << rule_name unless triggered_rules.include?(rule_name)
78
+ end
47
79
  end
48
80
  end
49
81
  end
@@ -32,6 +32,8 @@ module Contrast
32
32
 
33
33
  def validate
34
34
  raise(ArgumentError, 'Start Time is not presented') unless @start_time
35
+
36
+ nil
35
37
  end
36
38
 
37
39
  # @param attack_result [Contrast::Agent::Reporting::AttackResult]
@@ -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
@@ -119,15 +119,12 @@ module Contrast
119
119
  ruleId: rule_id,
120
120
  session_id: ::Contrast::ASSESS.session_id,
121
121
  version: 4
122
- }
122
+ }.compact
123
123
  end
124
124
 
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
- unless ::Contrast::ASSESS.session_id
129
- raise(ArgumentError, "#{ self } did not have a proper session id. Unable to continue.")
130
- end
131
128
  if event_based? && events.empty?
132
129
  raise(ArgumentError, "#{ self } did not have proper events for #{ @rule_id }. Unable to continue.")
133
130
  end