contrast-agent 4.7.0 → 4.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (145) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +6 -1
  3. data/.rspec +0 -1
  4. data/.rspec_parallel +6 -0
  5. data/.simplecov +1 -0
  6. data/ext/cs__contrast_patch/cs__contrast_patch.c +0 -1
  7. data/ext/cs__contrast_patch/cs__contrast_patch.h +0 -2
  8. data/lib/contrast/agent/assess/contrast_event.rb +1 -5
  9. data/lib/contrast/agent/assess/finalizers/hash.rb +2 -5
  10. data/lib/contrast/agent/assess/policy/patcher.rb +5 -4
  11. data/lib/contrast/agent/assess/policy/policy.rb +1 -1
  12. data/lib/contrast/agent/assess/policy/policy_scanner.rb +2 -6
  13. data/lib/contrast/agent/assess/policy/preshift.rb +11 -8
  14. data/lib/contrast/agent/assess/policy/propagation_method.rb +102 -59
  15. data/lib/contrast/agent/assess/policy/propagator/database_write.rb +2 -7
  16. data/lib/contrast/agent/assess/policy/propagator/match_data.rb +31 -11
  17. data/lib/contrast/agent/assess/policy/propagator/rack_protection.rb +73 -0
  18. data/lib/contrast/agent/assess/policy/propagator/split.rb +10 -6
  19. data/lib/contrast/agent/assess/policy/propagator/substitution.rb +3 -3
  20. data/lib/contrast/agent/assess/policy/propagator.rb +1 -0
  21. data/lib/contrast/agent/assess/policy/rewriter_patch.rb +6 -7
  22. data/lib/contrast/agent/assess/policy/source_method.rb +18 -22
  23. data/lib/contrast/agent/assess/policy/trigger/xpath.rb +0 -4
  24. data/lib/contrast/agent/assess/policy/trigger_method.rb +61 -86
  25. data/lib/contrast/agent/assess/policy/trigger_node.rb +1 -1
  26. data/lib/contrast/agent/assess/property/evented.rb +2 -1
  27. data/lib/contrast/agent/assess/rule/provider/hardcoded_value_rule.rb +3 -4
  28. data/lib/contrast/agent/at_exit_hook.rb +3 -3
  29. data/lib/contrast/agent/class_reopener.rb +6 -5
  30. data/lib/contrast/agent/disable_reaction.rb +4 -5
  31. data/lib/contrast/agent/exclusion_matcher.rb +2 -7
  32. data/lib/contrast/agent/inventory/database_config.rb +117 -0
  33. data/lib/contrast/agent/inventory/dependency_analysis.rb +2 -6
  34. data/lib/contrast/agent/inventory/dependency_usage_analysis.rb +8 -9
  35. data/lib/contrast/agent/inventory/policy/datastores.rb +5 -6
  36. data/lib/contrast/agent/inventory/policy/policy.rb +1 -1
  37. data/lib/contrast/agent/middleware.rb +15 -13
  38. data/lib/contrast/agent/patching/policy/after_load_patch.rb +6 -3
  39. data/lib/contrast/agent/patching/policy/after_load_patcher.rb +21 -16
  40. data/lib/contrast/agent/patching/policy/module_policy.rb +2 -4
  41. data/lib/contrast/agent/patching/policy/patch.rb +13 -8
  42. data/lib/contrast/agent/patching/policy/patch_status.rb +3 -7
  43. data/lib/contrast/agent/patching/policy/patcher.rb +14 -14
  44. data/lib/contrast/agent/patching/policy/policy.rb +2 -4
  45. data/lib/contrast/agent/patching/policy/policy_node.rb +2 -3
  46. data/lib/contrast/agent/protect/policy/applies_no_sqli_rule.rb +1 -1
  47. data/lib/contrast/agent/protect/policy/policy.rb +1 -1
  48. data/lib/contrast/agent/protect/policy/rule_applicator.rb +3 -5
  49. data/lib/contrast/agent/protect/rule/base.rb +10 -10
  50. data/lib/contrast/agent/protect/rule/cmd_injection.rb +4 -5
  51. data/lib/contrast/agent/protect/rule/no_sqli.rb +7 -53
  52. data/lib/contrast/agent/protect/rule/path_traversal.rb +1 -5
  53. data/lib/contrast/agent/protect/rule/sql_sample_builder.rb +137 -0
  54. data/lib/contrast/agent/protect/rule/sqli.rb +7 -70
  55. data/lib/contrast/agent/reaction_processor.rb +3 -4
  56. data/lib/contrast/agent/request.rb +9 -5
  57. data/lib/contrast/agent/request_context.rb +28 -31
  58. data/lib/contrast/agent/request_handler.rb +5 -3
  59. data/lib/contrast/agent/response.rb +2 -3
  60. data/lib/contrast/agent/rewriter.rb +4 -3
  61. data/lib/contrast/agent/rule_set.rb +5 -4
  62. data/lib/contrast/agent/service_heartbeat.rb +2 -3
  63. data/lib/contrast/agent/static_analysis.rb +7 -6
  64. data/lib/contrast/agent/thread.rb +2 -4
  65. data/lib/contrast/agent/thread_watcher.rb +3 -4
  66. data/lib/contrast/agent/tracepoint_hook.rb +10 -5
  67. data/lib/contrast/agent/version.rb +1 -1
  68. data/lib/contrast/api/communication/messaging_queue.rb +16 -11
  69. data/lib/contrast/api/communication/response_processor.rb +11 -11
  70. data/lib/contrast/api/communication/service_lifecycle.rb +9 -5
  71. data/lib/contrast/api/communication/socket_client.rb +18 -14
  72. data/lib/contrast/api/communication/speedracer.rb +5 -6
  73. data/lib/contrast/api/decorators/address.rb +2 -3
  74. data/lib/contrast/api/decorators/agent_startup.rb +7 -9
  75. data/lib/contrast/api/decorators/application_startup.rb +9 -10
  76. data/lib/contrast/api/decorators/application_update.rb +0 -4
  77. data/lib/contrast/api/decorators/http_request.rb +3 -7
  78. data/lib/contrast/api/decorators/instrumentation_mode.rb +3 -5
  79. data/lib/contrast/api/decorators/message.rb +7 -7
  80. data/lib/contrast/api/decorators/route_coverage.rb +24 -1
  81. data/lib/contrast/api/decorators/trace_event_object.rb +2 -3
  82. data/lib/contrast/components/agent.rb +13 -15
  83. data/lib/contrast/components/app_context.rb +7 -11
  84. data/lib/contrast/components/assess.rb +19 -16
  85. data/lib/contrast/components/base.rb +40 -0
  86. data/lib/contrast/components/config.rb +1 -2
  87. data/lib/contrast/components/contrast_service.rb +8 -11
  88. data/lib/contrast/components/heap_dump.rb +5 -4
  89. data/lib/contrast/components/inventory.rb +2 -7
  90. data/lib/contrast/components/logger.rb +14 -10
  91. data/lib/contrast/components/protect.rb +10 -13
  92. data/lib/contrast/components/sampling.rb +5 -5
  93. data/lib/contrast/components/scope.rb +9 -32
  94. data/lib/contrast/components/settings.rb +1 -5
  95. data/lib/contrast/config/base_configuration.rb +14 -6
  96. data/lib/contrast/configuration.rb +22 -19
  97. data/lib/contrast/extension/assess/array.rb +3 -15
  98. data/lib/contrast/extension/assess/eval_trigger.rb +2 -23
  99. data/lib/contrast/extension/assess/fiber.rb +6 -16
  100. data/lib/contrast/extension/assess/hash.rb +3 -13
  101. data/lib/contrast/extension/assess/kernel.rb +3 -14
  102. data/lib/contrast/extension/assess/marshal.rb +6 -14
  103. data/lib/contrast/extension/assess/regexp.rb +5 -15
  104. data/lib/contrast/extension/assess/string.rb +6 -31
  105. data/lib/contrast/extension/extension.rb +61 -0
  106. data/lib/contrast/extension/kernel.rb +2 -4
  107. data/lib/contrast/extension/protect/kernel.rb +0 -15
  108. data/lib/contrast/framework/grape/support.rb +174 -0
  109. data/lib/contrast/framework/manager.rb +44 -9
  110. data/lib/contrast/framework/rack/patch/session_cookie.rb +6 -6
  111. data/lib/contrast/framework/rack/support.rb +1 -1
  112. data/lib/contrast/framework/rails/patch/assess_configuration.rb +5 -8
  113. data/lib/contrast/framework/rails/patch/support.rb +44 -37
  114. data/lib/contrast/framework/rails/railtie.rb +34 -0
  115. data/lib/contrast/framework/rails/rewrite/active_record_named.rb +4 -4
  116. data/lib/contrast/framework/rails/support.rb +60 -13
  117. data/lib/contrast/framework/sinatra/support.rb +1 -1
  118. data/lib/contrast/funchook/funchook.rb +4 -3
  119. data/lib/contrast/logger/application.rb +1 -6
  120. data/lib/contrast/logger/log.rb +103 -13
  121. data/lib/contrast/logger/request.rb +0 -4
  122. data/lib/contrast/tasks/config.rb +0 -1
  123. data/lib/contrast/tasks/service.rb +1 -6
  124. data/lib/contrast/utils/assess/sampling_util.rb +2 -3
  125. data/lib/contrast/utils/assess/tracking_util.rb +2 -4
  126. data/lib/contrast/utils/heap_dump_util.rb +5 -3
  127. data/lib/contrast/utils/invalid_configuration_util.rb +4 -3
  128. data/lib/contrast/utils/io_util.rb +3 -5
  129. data/lib/contrast/utils/job_servers_running.rb +4 -3
  130. data/lib/contrast/utils/os.rb +2 -3
  131. data/lib/contrast/utils/ruby_ast_rewriter.rb +16 -13
  132. data/lib/contrast/utils/string_utils.rb +2 -3
  133. data/lib/contrast/utils/tag_util.rb +26 -19
  134. data/lib/contrast.rb +24 -14
  135. data/resources/assess/policy.json +252 -2
  136. data/resources/deadzone/policy.json +10 -0
  137. data/ruby-agent.gemspec +14 -3
  138. data/service_executables/VERSION +1 -1
  139. data/service_executables/linux/contrast-service +0 -0
  140. data/service_executables/mac/contrast-service +0 -0
  141. metadata +104 -24
  142. data/lib/contrast/agent/railtie.rb +0 -31
  143. data/lib/contrast/components/interface.rb +0 -196
  144. data/lib/contrast/delegators/input_analysis.rb +0 -12
  145. data/lib/contrast/utils/inventory_util.rb +0 -114
@@ -2,6 +2,7 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require 'contrast/agent/protect/rule/base_service'
5
+ require 'contrast/agent/protect/rule/sql_sample_builder'
5
6
 
6
7
  module Contrast
7
8
  module Agent
@@ -9,6 +10,12 @@ module Contrast
9
10
  module Rule
10
11
  # The Ruby implementation of the Protect NoSQL Injection rule.
11
12
  class NoSqli < Contrast::Agent::Protect::Rule::BaseService
13
+ # Generate a sample for the No-SQL injection detection rule, allowing for reporting to and rendering
14
+ # by TeamServer
15
+ include SqlSampleBuilder::NoSqliSample
16
+ # Defining build_attack_with_match method
17
+ include SqlSampleBuilder::AttackBuilder
18
+
12
19
  NAME = 'nosql-injection'
13
20
  BLOCK_MESSAGE = 'NoSQLi rule triggered. Response blocked.'
14
21
 
@@ -31,40 +38,6 @@ module Contrast
31
38
  raise Contrast::SecurityException.new(self, BLOCK_MESSAGE) if blocked?
32
39
  end
33
40
 
34
- def build_attack_with_match context, input_analysis_result, result, query_string, **kwargs
35
- if mode == Contrast::Api::Settings::ProtectionRule::Mode::NO_ACTION ||
36
- mode == Contrast::Api::Settings::ProtectionRule::Mode::PERMIT
37
-
38
- return result
39
- end
40
-
41
- attack_string = input_analysis_result.value
42
- regexp = Regexp.new(Regexp.escape(attack_string), Regexp::IGNORECASE)
43
- return unless query_string.match?(regexp)
44
-
45
- scanner = select_scanner
46
- ss = StringScanner.new(query_string)
47
- length = attack_string.length
48
- while ss.scan_until(regexp)
49
- # the pos of StringScanner is at the end of the regexp (input string),
50
- # we need the beginning
51
- idx = ss.pos - attack_string.length
52
- last_boundary, boundary = scanner.crosses_boundary(query_string, idx, input_analysis_result.value)
53
- next unless last_boundary && boundary
54
-
55
- kwargs[:start_idx] = idx
56
- kwargs[:end_idx] = idx + length
57
- kwargs[:boundary_overrun_idx] = boundary
58
- kwargs[:input_boundary_idx] = last_boundary
59
-
60
- result ||= build_attack_result(context)
61
- update_successful_attack_response(context, input_analysis_result, result, query_string)
62
- append_sample(context, input_analysis_result, result, query_string, **kwargs)
63
- end
64
-
65
- result
66
- end
67
-
68
41
  protected
69
42
 
70
43
  def find_attacker context, potential_attack_string, **kwargs
@@ -79,25 +52,6 @@ module Contrast
79
52
  end
80
53
  super(context, potential_attack_string, **kwargs)
81
54
  end
82
-
83
- def build_sample context, input_analysis_result, candidate_string, **kwargs
84
- input = input_analysis_result.value
85
-
86
- sample = build_base_sample(context, input_analysis_result)
87
- sample.no_sqli = Contrast::Api::Dtm::NoSqlInjectionDetails.new
88
- sample.no_sqli.query = Contrast::Utils::StringUtils.protobuf_safe_string(candidate_string)
89
- sample.no_sqli.start_idx = sample.no_sqli.query.index(input).to_i
90
- sample.no_sqli.boundary_overrun_idx = sample.no_sqli.start_idx + input.length
91
- sample.no_sqli.input_boundary_idx = kwargs[:boundary].to_i
92
- sample.no_sqli.input_boundary_idx = kwargs[:last_boundary].to_i
93
- sample
94
- end
95
-
96
- private
97
-
98
- def select_scanner
99
- @_select_scanner ||= Contrast::Agent::Protect::Rule::NoSqli::MongoNoSqlScanner.new
100
- end
101
55
  end
102
56
  end
103
57
  end
@@ -2,7 +2,6 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require 'contrast/agent/protect/rule/base_service'
5
- require 'contrast/components/interface'
6
5
  require 'contrast/utils/stack_trace_utils'
7
6
 
8
7
  module Contrast
@@ -12,9 +11,6 @@ module Contrast
12
11
  # This class handles our implementation of the Path Traversal
13
12
  # Protect rule.
14
13
  class PathTraversal < Contrast::Agent::Protect::Rule::BaseService
15
- include Contrast::Components::Interface
16
- access_component :agent, :analysis
17
-
18
14
  NAME = 'path-traversal'
19
15
  SYSTEM_PATHS = %w[
20
16
  /proc/self
@@ -96,7 +92,7 @@ module Contrast
96
92
  end
97
93
 
98
94
  def custom_code_access_sysfile_enabled?
99
- PROTECT.report_custom_code_sysfile_access?
95
+ ::Contrast::PROTECT.report_custom_code_sysfile_access?
100
96
  end
101
97
 
102
98
  def custom_code_accessing_system_file? input
@@ -0,0 +1,137 @@
1
+ # Copyright (c) 2021 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
2
+ # frozen_string_literal: true
3
+
4
+ require 'contrast/agent/protect/rule/base'
5
+ require 'contrast/agent/protect/rule/base_service'
6
+
7
+ module Contrast
8
+ module Agent
9
+ module Protect
10
+ module Rule
11
+ module SqlSampleBuilder
12
+ # Generate a sample for the SQL injection detection rule, allowing for reporting to and rendering
13
+ # by TeamServer
14
+ #
15
+ # @param context [Contrast::Agent::RequestContext] the context for the current request
16
+ # @param input_analysis_result [Contrast::Api::Dtm::AttackResult, nil] previous attack result for this rule,
17
+ # if one exists, in the case of multiple inputs being found to violate the protection criteria
18
+ # @candidate_string [String] the value of the input which may be an attack
19
+ # @kwargs [Hash] key - value pairs of context individual rules need to build out details
20
+ # to send to the Service to tell the story of the attack
21
+ # @return [Contrast::Api::Dtm::RaspRuleSample] the sample from this attack
22
+ module SqliSample
23
+ def build_sample context, input_analysis_result, candidate_string, **kwargs
24
+ sqli_sample = build_base_sample(context, input_analysis_result)
25
+ sqli_sample.sqli = Contrast::Api::Dtm::SqlInjectionDetails.new
26
+ sqli_sample.sqli.query = Contrast::Utils::StringUtils.protobuf_safe_string(candidate_string)
27
+ sqli_sample.sqli.start_idx = kwargs[:start_idx]
28
+ sqli_sample.sqli.end_idx = kwargs[:end_idx]
29
+ sqli_sample.sqli.boundary_overrun_idx = kwargs[:boundary_overrun_idx].to_i
30
+ sqli_sample.sqli.input_boundary_idx = kwargs[:input_boundary_idx].to_i
31
+ sqli_sample
32
+ end
33
+ end
34
+
35
+ # Generate a sample for the No-SQL injection detection rule, allowing for reporting to and rendering
36
+ # by TeamServer
37
+ #
38
+ # @param context [Contrast::Agent::RequestContext] the context for the current request
39
+ # @param input_analysis_result [Contrast::Api::Dtm::AttackResult, nil] previous attack result for this rule,
40
+ # if one exists, in the case of multiple inputs being found to violate the protection criteria
41
+ # @candidate_string [String] the value of the input which may be an attack
42
+ # @kwargs [Hash] key - value pairs of context individual rules need to build out details
43
+ # to send to the Service to tell the story of the attack
44
+ # @return [Contrast::Api::Dtm::RaspRuleSample] the sample from this attack
45
+ module NoSqliSample
46
+ def build_sample context, input_analysis_result, candidate_string, **kwargs
47
+ no_sqli_sample = build_base_sample(context, input_analysis_result)
48
+ no_sqli_sample.no_sqli = Contrast::Api::Dtm::NoSqlInjectionDetails.new
49
+ no_sqli_sample.no_sqli.query = Contrast::Utils::StringUtils.protobuf_safe_string(candidate_string)
50
+ no_sqli_sample.no_sqli.start_idx = kwargs[:start_idx].to_i
51
+ no_sqli_sample.no_sqli.end_idx = kwargs[:end_idx].to_i
52
+ no_sqli_sample.no_sqli.boundary_overrun_idx = kwargs[:boundary_overrun_idx].to_i
53
+ no_sqli_sample.no_sqli.input_boundary_idx = kwargs[:input_boundary_idx].to_i
54
+ no_sqli_sample
55
+ end
56
+ end
57
+
58
+ # This Module is how we apply the attack fo NoSQL and SQL Injection rule.
59
+ # It includes methods for building attack with match and database scanners
60
+ module AttackBuilder
61
+ # Set up an attack result and assigns Database scanner for the No-SQL and SQLI injection detection rules
62
+ #
63
+ # @param context [Contrast::Agent::RequestContext] the context for the current request
64
+ # @param input_analysis_result [Contrast::Api::Dtm::AttackResult, nil] previous attack result for this rule,
65
+ # if one exists, in the case of multiple inputs being found to violate the protection criteria
66
+ # @param result [Contrast::Api::Dtm::AttackResult, nil] previous attack result for this rule, if one exists,
67
+ # in the case of multiple inputs being found to violate the protection criteria
68
+ # @query_string [string] he value of the input which may be an attack
69
+ # @kwargs [Hash] key - value pairs of context individual rules need to build out details to send
70
+ # to the Service to tell the story of the attack
71
+ # @return [Contrast::Api::Dtm::AttackResult] the result from this attack
72
+ def build_attack_with_match context, input_analysis_result, result, query_string, **kwargs
73
+ if mode == Contrast::Api::Settings::ProtectionRule::Mode::NO_ACTION ||
74
+ mode == Contrast::Api::Settings::ProtectionRule::Mode::PERMIT
75
+
76
+ return result
77
+ end
78
+
79
+ attack_string = input_analysis_result.value
80
+ regexp = Regexp.new(Regexp.escape(attack_string), Regexp::IGNORECASE)
81
+
82
+ return unless query_string.match?(regexp)
83
+
84
+ database = kwargs[:database]
85
+ scanner = select_scanner(database)
86
+ ss = StringScanner.new(query_string)
87
+ length = attack_string.length
88
+ while ss.scan_until(regexp)
89
+ # the pos of StringScanner is at the end of the regexp (input string),
90
+ # we need the beginning
91
+ idx = ss.pos - attack_string.length
92
+ last_boundary, boundary = scanner.crosses_boundary(query_string, idx, input_analysis_result.value)
93
+ next unless last_boundary && boundary
94
+
95
+ result ||= build_attack_result(context)
96
+
97
+ record_match(idx, length, boundary, last_boundary, kwargs)
98
+ append_match(context, input_analysis_result, result, query_string, **kwargs)
99
+ end
100
+
101
+ result
102
+ end
103
+
104
+ def select_scanner database
105
+ @scanners ||= {
106
+ Contrast::Agent::Protect::Policy::AppliesSqliRule::DATABASE_MYSQL =>
107
+ Contrast::Agent::Protect::Rule::Sqli::MysqlSqlScanner.new,
108
+ Contrast::Agent::Protect::Policy::AppliesSqliRule::DATABASE_PG =>
109
+ Contrast::Agent::Protect::Rule::Sqli::PostgresSqlScanner.new,
110
+ Contrast::Agent::Protect::Policy::AppliesSqliRule::DATABASE_SQLITE =>
111
+ Contrast::Agent::Protect::Rule::Sqli::SqliteSqlScanner.new,
112
+ Contrast::Agent::Protect::Policy::AppliesNoSqliRule::DATABASE_NOSQL =>
113
+ Contrast::Agent::Protect::Rule::NoSqli::MongoNoSqlScanner.new
114
+ }.cs__freeze
115
+
116
+ @default_scanner ||= Contrast::Agent::Protect::Rule::Sqli::DefaultSqlScanner.new
117
+ @scanners[database.to_s] || @default_scanner
118
+ end
119
+
120
+ def record_match idx, length, boundary, last_boundary, kwargs
121
+ kwargs[:start_idx] = idx
122
+ kwargs[:end_idx] = idx + length
123
+ kwargs[:boundary_overrun_idx] = boundary
124
+ kwargs[:input_boundary_idx] = last_boundary
125
+ end
126
+
127
+ def append_match context, input_analysis_result, result, query_string, **kwargs
128
+ input_analysis_result.attack_count = input_analysis_result.attack_count + 1
129
+ update_successful_attack_response(context, input_analysis_result, result, query_string)
130
+ append_sample(context, input_analysis_result, result, query_string, **kwargs)
131
+ end
132
+ end
133
+ end
134
+ end
135
+ end
136
+ end
137
+ end
@@ -3,6 +3,7 @@
3
3
 
4
4
  require 'contrast/agent/protect/rule/base_service'
5
5
  require 'contrast/agent/protect/policy/applies_sqli_rule'
6
+ require 'contrast/agent/protect/rule/sql_sample_builder'
6
7
 
7
8
  module Contrast
8
9
  module Agent
@@ -10,6 +11,12 @@ module Contrast
10
11
  module Rule
11
12
  # The Ruby implementation of the Protect SQL Injection rule.
12
13
  class Sqli < Contrast::Agent::Protect::Rule::BaseService
14
+ # Generate a sample for the SQLI injection detection rule, allowing for reporting to and rendering
15
+ # by TeamServer
16
+ include SqlSampleBuilder::SqliSample
17
+ # Defining build_attack_with_match method
18
+ include SqlSampleBuilder::AttackBuilder
19
+
13
20
  NAME = 'sql-injection'
14
21
  BLOCK_MESSAGE = 'SQLi rule triggered. Response blocked.'
15
22
 
@@ -31,76 +38,6 @@ module Contrast
31
38
 
32
39
  raise Contrast::SecurityException.new(self, BLOCK_MESSAGE) if blocked?
33
40
  end
34
-
35
- def build_attack_with_match context, input_analysis_result, result, query_string, **kwargs
36
- attack_string = input_analysis_result.value
37
- regexp = Regexp.new(Regexp.escape(attack_string), Regexp::IGNORECASE)
38
-
39
- return unless query_string.match?(regexp)
40
-
41
- database = kwargs[:database]
42
- scanner = select_scanner(database)
43
-
44
- ss = StringScanner.new(query_string)
45
- length = attack_string.length
46
- while ss.scan_until(regexp)
47
- # the pos of StringScanner is at the end of the regexp (input string),
48
- # we need the beginning
49
- idx = ss.pos - attack_string.length
50
- last_boundary, boundary = scanner.crosses_boundary(query_string, idx, input_analysis_result.value)
51
- next unless last_boundary && boundary
52
-
53
- result ||= build_attack_result(context)
54
- record_match(idx, length, boundary, last_boundary, kwargs)
55
- append_match(context, input_analysis_result, result, query_string, **kwargs)
56
- end
57
-
58
- result
59
- end
60
-
61
- protected
62
-
63
- def build_sample context, input_analysis_result, candidate_string, **kwargs
64
- input = input_analysis_result.value
65
-
66
- sample = build_base_sample(context, input_analysis_result)
67
- sample.sqli = Contrast::Api::Dtm::SqlInjectionDetails.new
68
- sample.sqli.query = Contrast::Utils::StringUtils.protobuf_safe_string(candidate_string)
69
- sample.sqli.start_idx = sample.sqli.query.index(input).to_i
70
- sample.sqli.end_idx = sample.sqli.start_idx + input.length
71
- sample.sqli.boundary_overrun_idx = kwargs[:boundary_overrun_idx].to_i
72
- sample.sqli.input_boundary_idx = kwargs[:input_boundary_idx].to_i
73
- sample
74
- end
75
-
76
- private
77
-
78
- def record_match idx, length, boundary, last_boundary, kwargs
79
- kwargs[:start_idx] = idx
80
- kwargs[:end_idx] = idx + length
81
- kwargs[:boundary_overrun_idx] = boundary
82
- kwargs[:input_boundary_idx] = last_boundary
83
- end
84
-
85
- def append_match context, input_analysis_result, result, query_string, **kwargs
86
- input_analysis_result.attack_count = input_analysis_result.attack_count + 1
87
- update_successful_attack_response(context, input_analysis_result, result, query_string)
88
- append_sample(context, input_analysis_result, result, query_string, **kwargs)
89
- end
90
-
91
- def select_scanner database
92
- @sql_scanners ||= {
93
- Contrast::Agent::Protect::Policy::AppliesSqliRule::DATABASE_MYSQL =>
94
- Contrast::Agent::Protect::Rule::Sqli::MysqlSqlScanner.new,
95
- Contrast::Agent::Protect::Policy::AppliesSqliRule::DATABASE_PG =>
96
- Contrast::Agent::Protect::Rule::Sqli::PostgresSqlScanner.new,
97
- Contrast::Agent::Protect::Policy::AppliesSqliRule::DATABASE_SQLITE =>
98
- Contrast::Agent::Protect::Rule::Sqli::SqliteSqlScanner.new
99
- }.cs__freeze
100
-
101
- @default_sql_scanner ||= Contrast::Agent::Protect::Rule::Sqli::DefaultSqlScanner.new
102
- @sql_scanners[database.to_s] || @default_sql_scanner
103
- end
104
41
  end
105
42
  end
106
43
  end
@@ -2,16 +2,15 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require 'contrast/agent/disable_reaction'
5
- require 'contrast/components/interface'
5
+ require 'contrast/components/logger'
6
6
 
7
7
  module Contrast
8
8
  module Agent
9
9
  # Because communication between the Agent/Service and TeamServer can only be initiated by outbound connections
10
10
  # from the Agent/Service, we must provide a mechanism for the TeamServer to direct the Agent to take a specific
11
11
  # action. This action is referred to as a Reaction. This class is how we handle those Reaction messages.
12
- class ReactionProcessor
13
- include Contrast::Components::Interface
14
- access_component :logging
12
+ module ReactionProcessor
13
+ extend Contrast::Components::Logger::InstanceMethods
15
14
 
16
15
  # Process the given Reactions from the application settings based on what
17
16
  # TeamServer has indicated. Each Reaction will result in a log message
@@ -7,7 +7,8 @@ require 'timeout'
7
7
  require 'contrast/utils/object_share'
8
8
  require 'contrast/utils/string_utils'
9
9
  require 'contrast/utils/hash_digest'
10
- require 'contrast/components/interface'
10
+ require 'contrast/components/logger'
11
+ require 'contrast/components/scope'
11
12
 
12
13
  module Contrast
13
14
  module Agent
@@ -16,8 +17,8 @@ module Contrast
16
17
  # data in a format that the Agent expects, caching those transformations in
17
18
  # order to avoid repeatedly creating Strings & thrashing GC.
18
19
  class Request
19
- include Contrast::Components::Interface
20
- access_component :agent, :logging, :scope
20
+ include Contrast::Components::Logger::InstanceMethods
21
+ include Contrast::Components::Scope::InstanceMethods
21
22
 
22
23
  extend Forwardable
23
24
 
@@ -182,8 +183,11 @@ module Contrast
182
183
  res[prefix] = Contrast::Utils::ObjectShare::EMPTY_STRING if prefix
183
184
  res
184
185
  when Enumerable
185
- res = val.each_with_index.each_with_object({}) do |(v, i), hash|
186
- hash.merge! normalize_params(v, prefix: "#{ prefix }[#{ i }]")
186
+ idx = 0
187
+ res = {}
188
+ while idx < val.size
189
+ res.merge! normalize_params(val[idx], prefix: "#{ prefix }[#{ idx }]")
190
+ idx += 1
187
191
  end
188
192
  res[prefix] = Contrast::Utils::ObjectShare::EMPTY_STRING if prefix
189
193
  res
@@ -4,15 +4,14 @@
4
4
  require 'contrast/utils/timer'
5
5
  require 'contrast/agent/request'
6
6
  require 'contrast/agent/response'
7
- require 'contrast/utils/inventory_util'
8
- require 'contrast/components/interface'
9
- require 'contrast/delegators/input_analysis'
7
+ require 'contrast/agent/inventory/database_config'
8
+ require 'contrast/components/logger'
9
+ require 'contrast/components/scope'
10
10
 
11
11
  module Contrast
12
12
  module Agent
13
- # This class acts to encapsulate information about the currently executed
14
- # request, making it available to the Agent for the duration of the request
15
- # in a standardized and normalized format which the Agent understands.
13
+ # This class acts to encapsulate information about the currently executed request, making it available to the Agent
14
+ # for the duration of the request in a standardized and normalized format which the Agent understands.
16
15
  #
17
16
  # @attr_reader timer [Contrast::Utils::Timer] when the context was created
18
17
  # @attr_reader logging_hash [Hash] context used to log the request
@@ -26,8 +25,8 @@ module Contrast
26
25
  # @attr_reader route [Contrast::Api::Dtm::RouteCoverage] the route, used for findings, of this request
27
26
  # @attr_reader observed_route [Contrast::Api::Dtm::ObservedRoute] the route, used for coverage, of this request
28
27
  class RequestContext
29
- include Contrast::Components::Interface
30
- access_component :agent, :analysis, :logging, :scope
28
+ include Contrast::Components::Logger::InstanceMethods
29
+ include Contrast::Components::Scope::InstanceMethods
31
30
 
32
31
  EMPTY_INPUT_ANALYSIS_PB = Contrast::Api::Settings::InputAnalysis.new
33
32
 
@@ -63,11 +62,11 @@ module Contrast
63
62
 
64
63
  @sample = true
65
64
 
66
- if ASSESS.enabled?
65
+ if ::Contrast::ASSESS.enabled?
67
66
  @sample_request, @sample_response = Contrast::Utils::Assess::SamplingUtil.instance.sample?(@request)
68
67
  end
69
68
 
70
- @sample_response &&= ASSESS.scan_response?
69
+ @sample_response &&= ::Contrast::ASSESS.scan_response?
71
70
 
72
71
  append_route_coverage(Contrast::Agent.framework_manager.get_route_dtm(@request))
73
72
  end
@@ -85,9 +84,11 @@ module Contrast
85
84
  @sample_response
86
85
  end
87
86
 
88
- # Convert the discovered route for this request to appropriate forms and
89
- # disseminate it to those locations where it is necessary for our route
90
- # coverage and finding vulnerability discovery features to function.
87
+ # Convert the discovered route for this request to appropriate forms and disseminate it to those locations
88
+ # where it is necessary for our route coverage and finding vulnerability discovery features to function.
89
+ #
90
+ # @param route [Contrast::Api::Dtm::RouteCoverage, nil] the route of the current request, as determined from the
91
+ # framework
91
92
  def append_route_coverage route
92
93
  return unless route
93
94
 
@@ -108,8 +109,8 @@ module Contrast
108
109
  # Collect the results for the given rule with the given action
109
110
  #
110
111
  # @param rule [String] the id of the rule to which the results apply
111
- # @param response_type [Symbol] the result of the response, matching a
112
- # value of Contrast::Api::Dtm::AttackResult::ResponseType
112
+ # @param response_type [Symbol] the result of the response, matching a value of
113
+ # Contrast::Api::Dtm::AttackResult::ResponseType
113
114
  # @return [Array<Contrast::Api::Dtm::AttackResult>]
114
115
  def results_for rule, response_type = nil
115
116
  if response_type.nil?
@@ -120,8 +121,8 @@ module Contrast
120
121
  end
121
122
 
122
123
  def service_extract_request
123
- return false unless AGENT.enabled?
124
- return false unless PROTECT.enabled?
124
+ return false unless ::Contrast::AGENT.enabled?
125
+ return false unless ::Contrast::PROTECT.enabled?
125
126
  return false if @do_not_track
126
127
 
127
128
  service_response = Contrast::Agent.messaging_queue.send_event_immediately(@activity.http_request)
@@ -145,10 +146,9 @@ module Contrast
145
146
  false
146
147
  end
147
148
 
148
- # NOTE: this method is only used as a backstop if Speedracer sends Input Evaluations
149
- # when the protect state indicates a security exception should be thrown. This method
150
- # ensures that the attack reports are generated. Normally these should be generated on
151
- # Speedracer for any attacks detected during prefilter.
149
+ # NOTE: this method is only used as a backstop if Speedracer sends Input Evaluations when the protect state
150
+ # indicates a security exception should be thrown. This method ensures that the attack reports are generated.
151
+ # Normally these should be generated on Speedracer for any attacks detected during prefilter.
152
152
  #
153
153
  # @param agent_settings [Contrast::Api::Settings::AgentSettings]
154
154
  def handle_protect_state agent_settings
@@ -165,9 +165,8 @@ module Contrast
165
165
  raise Contrast::SecurityException.new(nil, (state.security_message || 'Blocking suspicious behavior'))
166
166
  end
167
167
 
168
- # append anything we've learned to the request seen message
169
- # this is the sum-total of all inventory information that has
170
- # been accumulated since the last request
168
+ # append anything we've learned to the request seen message this is the sum-total of all inventory information
169
+ # that has been accumulated since the last request
171
170
  def extract_after rack_response
172
171
  @response = Contrast::Agent::Response.new(rack_response)
173
172
  activity.http_response = @response.dtm if @sample_response
@@ -185,14 +184,13 @@ module Contrast
185
184
 
186
185
  def reset_activity
187
186
  @activity = Contrast::Api::Dtm::Activity.new(http_request: request.dtm)
188
- @server_activity = Contrast::Api::Dtm::ServerActivity.new # it doesn't look like this is ever actually used?
187
+ @server_activity = Contrast::Api::Dtm::ServerActivity.new
189
188
  @observed_route = Contrast::Api::Dtm::ObservedRoute.new
190
189
  end
191
190
 
192
191
  private
193
192
 
194
- # Generate attack results directly from any evaluations on the
195
- # agent settings object.
193
+ # Generate attack results directly from any evaluations on the agent settings object.
196
194
  #
197
195
  # @param agent_settings [Contrast::Api::Settings::AgentSettings]
198
196
  def build_attack_results agent_settings
@@ -201,15 +199,14 @@ module Contrast
201
199
  attack_results_by_rule = {}
202
200
  agent_settings.input_analysis.results.each do |ia_result|
203
201
  rule_id = ia_result.rule_id
204
- rule = PROTECT.rule(rule_id)
202
+ rule = ::Contrast::PROTECT.rule(rule_id)
205
203
  next unless rule
206
204
 
207
205
  logger.debug('Building attack result from Contrast Service input analysis result', result: ia_result.inspect)
208
206
 
209
207
  attack_result = if rule.mode == :BLOCK
210
- # special case for rules (like reflected xss)
211
- # that used to have an infilter / block
212
- # mode but now are just block at perimeter
208
+ # special case for rules (like reflected xss) that used to have an infilter / block mode
209
+ # but now are just block at perimeter
213
210
  rule.build_attack_with_match(self, ia_result, attack_results_by_rule[rule_id],
214
211
  ia_result.value)
215
212
  else