contrast-agent 4.12.0 → 4.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/ext/cs__assess_module/cs__assess_module.c +48 -0
  3. data/ext/cs__assess_module/cs__assess_module.h +7 -0
  4. data/ext/cs__common/cs__common.c +5 -0
  5. data/ext/cs__common/cs__common.h +8 -0
  6. data/ext/cs__contrast_patch/cs__contrast_patch.c +16 -1
  7. data/ext/cs__os_information/cs__os_information.c +31 -0
  8. data/ext/cs__os_information/cs__os_information.h +7 -0
  9. data/ext/cs__os_information/extconf.rb +5 -0
  10. data/lib/contrast/agent/assess/policy/propagation_method.rb +2 -116
  11. data/lib/contrast/agent/assess/policy/propagation_node.rb +4 -4
  12. data/lib/contrast/agent/assess/policy/source_method.rb +2 -71
  13. data/lib/contrast/agent/assess/policy/trigger_method.rb +4 -106
  14. data/lib/contrast/agent/assess/property/tagged.rb +2 -128
  15. data/lib/contrast/agent/deadzone/policy/policy.rb +1 -1
  16. data/lib/contrast/agent/inventory/dependency_usage_analysis.rb +1 -0
  17. data/lib/contrast/agent/metric_telemetry_event.rb +26 -0
  18. data/lib/contrast/agent/middleware.rb +22 -0
  19. data/lib/contrast/agent/patching/policy/patch.rb +28 -235
  20. data/lib/contrast/agent/patching/policy/patcher.rb +2 -41
  21. data/lib/contrast/agent/request_handler.rb +7 -3
  22. data/lib/contrast/agent/startup_metrics_telemetry_event.rb +71 -0
  23. data/lib/contrast/agent/static_analysis.rb +4 -2
  24. data/lib/contrast/agent/telemetry.rb +129 -0
  25. data/lib/contrast/agent/telemetry_event.rb +34 -0
  26. data/lib/contrast/agent/thread_watcher.rb +43 -14
  27. data/lib/contrast/agent/version.rb +1 -1
  28. data/lib/contrast/agent.rb +6 -0
  29. data/lib/contrast/components/api.rb +34 -0
  30. data/lib/contrast/components/app_context.rb +24 -0
  31. data/lib/contrast/components/config.rb +90 -11
  32. data/lib/contrast/components/contrast_service.rb +6 -0
  33. data/lib/contrast/config/api_configuration.rb +22 -0
  34. data/lib/contrast/config/env_variables.rb +25 -0
  35. data/lib/contrast/config/root_configuration.rb +1 -0
  36. data/lib/contrast/config/service_configuration.rb +2 -1
  37. data/lib/contrast/config.rb +1 -0
  38. data/lib/contrast/configuration.rb +3 -0
  39. data/lib/contrast/framework/manager.rb +14 -12
  40. data/lib/contrast/framework/rails/patch/action_controller_live_buffer.rb +9 -6
  41. data/lib/contrast/framework/rails/patch/support.rb +31 -29
  42. data/lib/contrast/logger/application.rb +4 -0
  43. data/lib/contrast/utils/assess/propagation_method_utils.rb +129 -0
  44. data/lib/contrast/utils/assess/property/tagged_utils.rb +142 -0
  45. data/lib/contrast/utils/assess/source_method_utils.rb +83 -0
  46. data/lib/contrast/utils/assess/trigger_method_utils.rb +138 -0
  47. data/lib/contrast/utils/exclude_key.rb +20 -0
  48. data/lib/contrast/utils/metrics_hash.rb +59 -0
  49. data/lib/contrast/utils/os.rb +23 -0
  50. data/lib/contrast/utils/patching/policy/patch_utils.rb +232 -0
  51. data/lib/contrast/utils/patching/policy/patcher_utils.rb +54 -0
  52. data/lib/contrast/utils/requests_client.rb +150 -0
  53. data/lib/contrast/utils/telemetry.rb +78 -0
  54. data/lib/contrast/utils/telemetry_identifier.rb +137 -0
  55. data/lib/contrast.rb +18 -0
  56. data/ruby-agent.gemspec +2 -1
  57. data/service_executables/VERSION +1 -1
  58. data/service_executables/linux/contrast-service +0 -0
  59. data/service_executables/mac/contrast-service +0 -0
  60. metadata +32 -10
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6a0a50913c5c680462f9afd79070ca1f3d46d09d66ffdb943b1dc778f84830d6
4
- data.tar.gz: bb3254c6a2cdc2b4add4e09b4bf1ed63c6a640f0dd82824db2769b911aacf96b
3
+ metadata.gz: 469e09f0e64dcdb68643154ca601b20b8f61d634789dec642a374d6a51671fec
4
+ data.tar.gz: e4053b76ef09eaf13ee1cf09418d5ae50cd5e518b5e69f611c0a22d15a9bcd4d
5
5
  SHA512:
6
- metadata.gz: 78d5e66ee3ab7408e94348f6c2998e73cd78227a8b75cffa7add261b771092d470431ce8e44357dfb7d1a178e03e5033efc9f7a4c974a979f9dea88b2c55a3ec
7
- data.tar.gz: d20f41b563778d84e63e4db7a9399e666a85fe1a2343772258f5248cd139f02e6a6bea33e51d9e5a0f954b1e2b4683ec08f3db7a52ab5a6990f7d725d2bc3dea
6
+ metadata.gz: 311a76a16c063b1d3afe09921e82efa59ea6367165d78fb26c833ab1dcf1e0daba2c3a0bf3597651cd86d146f4480004e916bb78842c6d8185f824dc768c204d
7
+ data.tar.gz: 3a4c0db4f013ccd0c7427ef01e451fa2d57b9458de7ce2c23df3444f3fcc4c824a7b841d81bf2aa25ecfd378be2afcea3ea57cded948bd350f4c6516a509477e
@@ -57,6 +57,45 @@ contrast_assess_module_module_eval(const int argc, const VALUE *argv,
57
57
  return ret;
58
58
  }
59
59
 
60
+ VALUE
61
+ contrast_assess_module_prepend(const int argc, const VALUE *argv,
62
+ const VALUE self) {
63
+
64
+ rb_prepend_module(self, argv[0]);
65
+
66
+ VALUE module_at;
67
+ VALUE rb_incl_in_mod_ary = rb_funcall(self, rb_intern("included_in"), 0);
68
+
69
+ if (RB_TYPE_P(rb_incl_in_mod_ary, T_ARRAY)) {
70
+ int i = 0;
71
+ int size = rb_funcall(rb_incl_in_mod_ary, rb_intern("length"), 0);
72
+ for (i = 0; i < size; ++i) {
73
+ module_at = rb_ary_entry(rb_incl_in_mod_ary, i);
74
+ if (RB_TYPE_P(module_at, T_MODULE)) {
75
+ rb_include_module(module_at, argv[0]);
76
+ }
77
+ }
78
+ }
79
+ return self;
80
+ }
81
+
82
+ VALUE
83
+ contrast_assess_module_included(const int argc, const VALUE *argv,
84
+ const VALUE self) {
85
+ VALUE frozen;
86
+ if (RB_TYPE_P(self, T_MODULE)) {
87
+ // check if frozen
88
+ frozen = rb_funcall(self, rb_intern("cs__frozen?"), 0);
89
+ if (frozen == Qfalse) {
90
+ VALUE ary = rb_funcall(self, rb_intern("included_in"), 0);
91
+ if (RB_TYPE_P(ary, T_ARRAY)) {
92
+ rb_ary_push(ary, argv[0]);
93
+ }
94
+ }
95
+ }
96
+ return self;
97
+ }
98
+
60
99
  void Init_cs__assess_module(void) {
61
100
  module_eval_trigger =
62
101
  rb_define_class_under(core_assess, "EvalTrigger", rb_cObject);
@@ -76,4 +115,13 @@ void Init_cs__assess_module(void) {
76
115
 
77
116
  contrast_register_patch("Module", "module_eval",
78
117
  contrast_assess_module_module_eval);
118
+ /*
119
+ * We patch these for better ancestors handling, and only for older ruby versions.
120
+ */
121
+ if (rb_ver_below_three()) {
122
+ contrast_register_patch("Module", "included",
123
+ contrast_assess_module_included);
124
+ contrast_register_patch("Module", "prepend",
125
+ contrast_assess_module_prepend);
126
+ }
79
127
  }
@@ -24,5 +24,12 @@ contrast_assess_module_class_eval(const int argc, const VALUE *argv,
24
24
  VALUE
25
25
  contrast_assess_module_module_eval(const int argc, const VALUE *argv,
26
26
  const VALUE mod);
27
+ VALUE
28
+ contrast_assess_module_prepend(const int argc, const VALUE *argv,
29
+ const VALUE self);
30
+
31
+ VALUE
32
+ contrast_assess_module_included(const int argc, const VALUE *argv,
33
+ const VALUE mod);
27
34
 
28
35
  void Init_cs__assess_module(void);
@@ -144,6 +144,11 @@ _contrast_register_patch(const char *module_name, const char *method_name,
144
144
  return SYM2ID(underlying_method_name);
145
145
  }
146
146
 
147
+ int rb_ver_below_three() {
148
+ int ruby_version = FIX2INT(rb_funcall(rb_const_get(rb_cObject, rb_intern("RUBY_VERSION")), rb_intern("to_i"), 0));
149
+ return ruby_version < 3;
150
+ }
151
+
147
152
  void Init_cs__common(void) {
148
153
  cs__send_method = rb_intern("send");
149
154
  cs__alias_method_sym = ID2SYM(rb_intern("alias_method"));
@@ -34,6 +34,14 @@ static VALUE rb_sym_alias_singleton;
34
34
  static VALUE rb_sym_prepend_instance;
35
35
  static VALUE rb_sym_prepend_singleton;
36
36
 
37
+ /*
38
+ * Check if ruby version is < 3.0.0.
39
+ * We are using this for handling ancestors of included modules.
40
+ * Since this is fixed after Ruby 3.0.0 we should remove this after
41
+ * dropping support for older versions, as no longer needed.
42
+ */
43
+ int rb_ver_below_three();
44
+
37
45
  void patch_via_funchook(void *original_function, void *hook_function);
38
46
 
39
47
  void contrast_alias_method(const VALUE target, const char *to,
@@ -43,7 +43,7 @@ VALUE contrast_patch_call_original(const VALUE *args) {
43
43
  if (rb_block_given_p()) {
44
44
  return rb_funcall_with_block_kw(object, method_id, argc, params, rb_block_proc(), RB_PASS_CALLED_KEYWORDS);
45
45
  } else {
46
- return rb_funcallv_kw(object, method_id, argc, params, RB_PASS_CALLED_KEYWORDS);
46
+ return rb_funcallv_kw(object, method_id, argc, params, RB_PASS_CALLED_KEYWORDS);
47
47
  }
48
48
  /* Ruby < 2.7 */
49
49
  #else
@@ -448,6 +448,21 @@ VALUE contrast_patch_prepend(const VALUE self, const VALUE originalModule,
448
448
  -1);
449
449
  }
450
450
  rb_prepend_module(originalModule, module);
451
+
452
+ if (rb_ver_below_three()) {
453
+ VALUE module_at;
454
+ VALUE rb_incl_in_mod_ary = rb_funcall(originalModule, rb_intern("included_in"), 0);
455
+ if (RB_TYPE_P(rb_incl_in_mod_ary, T_ARRAY)) {
456
+ int i = 0;
457
+ int size = rb_funcall(rb_incl_in_mod_ary, rb_intern("length"), 0);
458
+ for (i = 0; i < size; ++i) {
459
+ module_at = rb_ary_entry(rb_incl_in_mod_ary, i);
460
+ if (RB_TYPE_P(module_at, T_MODULE)) {
461
+ rb_include_module(module_at, module);
462
+ }
463
+ }
464
+ }
465
+ }
451
466
  return Qtrue;
452
467
  }
453
468
 
@@ -0,0 +1,31 @@
1
+ /* Copyright (c) 2021 Contrast Security, Inc. See
2
+ * https://www.contrastsecurity.com/enduser-terms-0317a for more details. */
3
+
4
+ #include "cs__os_information.h"
5
+ #include <dlfcn.h>
6
+ #include <ruby.h>
7
+ #include <sys/utsname.h>
8
+
9
+ VALUE contrast, utils, os;
10
+
11
+ VALUE contrast_get_system_information()
12
+ {
13
+ struct utsname uname_pointer;
14
+
15
+ uname (&uname_pointer);
16
+
17
+ VALUE rb_data_hash = rb_hash_new();
18
+ rb_hash_aset(rb_data_hash, rb_str_new2("os_type"), rb_str_new2(uname_pointer.sysname));
19
+ rb_hash_aset(rb_data_hash, rb_str_new2("os_version"), rb_str_new2(uname_pointer.release));
20
+ rb_hash_aset(rb_data_hash, rb_str_new2("os_complete_version"), rb_str_new2(uname_pointer.version));
21
+ rb_hash_aset(rb_data_hash, rb_str_new2("os_arch"), rb_str_new2(uname_pointer.machine));
22
+ return rb_data_hash;
23
+ }
24
+
25
+ void Init_cs__os_information(void)
26
+ {
27
+ contrast = rb_define_module("Contrast");
28
+ utils = rb_define_module_under(contrast, "Utils");
29
+ os = rb_define_module_under(utils, "OS");
30
+ rb_define_module_function(os, "get_system_information", contrast_get_system_information, 0);
31
+ }
@@ -0,0 +1,7 @@
1
+ #include <ruby.h>
2
+
3
+ extern VALUE contrast, utils, os;
4
+
5
+ VALUE contrast_get_system_information();
6
+
7
+ void Init_cs__os_information(void);
@@ -0,0 +1,5 @@
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
+ $TO_MAKE = File.basename(__dir__)
5
+ require_relative '../extconf_common'
@@ -7,6 +7,7 @@ require 'contrast/agent/assess/policy/propagator'
7
7
  require 'contrast/components/logger'
8
8
  require 'contrast/utils/object_share'
9
9
  require 'contrast/utils/sha256_builder'
10
+ require 'contrast/utils/assess/propagation_method_utils'
10
11
 
11
12
  module Contrast
12
13
  module Agent
@@ -16,35 +17,9 @@ module Contrast
16
17
  # untrusted value. In general, these methods work on the String class or a holder of Strings.
17
18
  module PropagationMethod
18
19
  extend Contrast::Components::Logger::InstanceMethods
19
-
20
- APPEND_ACTION = 'APPEND'
21
- CENTER_ACTION = 'CENTER'
22
- INSERT_ACTION = 'INSERT'
23
- KEEP_ACTION = 'KEEP'
24
- NEXT_ACTION = 'NEXT'
25
- NOOP_ACTION = 'NOOP'
26
- PREPEND_ACTION = 'PREPEND'
27
- REPLACE_ACTION = 'REPLACE'
28
- REMOVE_ACTION = 'REMOVE'
29
- REVERSE_ACTION = 'REVERSE'
30
- SPLAT_ACTION = 'SPLAT'
31
- SPLIT_ACTION = 'SPLIT'
32
- DB_WRITE_ACTION = 'DB_WRITE'
33
- CUSTOM_ACTION = 'CUSTOM'
20
+ extend Contrast::Utils::Assess::PropagationMethodUtils
34
21
 
35
22
  class << self
36
- def determine_target propagation_node, ret, object, args
37
- target = propagation_node.targets[0]
38
- case target
39
- when Contrast::Utils::ObjectShare::OBJECT_KEY
40
- object
41
- when Contrast::Utils::ObjectShare::RETURN_KEY
42
- ret
43
- else
44
- args[target]
45
- end
46
- end
47
-
48
23
  # @param method_policy [Contrast::Agent::Patching::Policy::MethodPolicy] the policy that governs the
49
24
  # patches to this method
50
25
  # @param preshift [Contrast::Agent::Assess::PreShift] The capture of the state of the code just prior to
@@ -65,21 +40,6 @@ module Contrast
65
40
  PropagationMethod.apply_propagator(propagation_node, preshift, target, object, ret, args, block)
66
41
  end
67
42
 
68
- PROPAGATION_ACTIONS = {
69
- APPEND_ACTION => Contrast::Agent::Assess::Policy::Propagator::Append,
70
- CENTER_ACTION => Contrast::Agent::Assess::Policy::Propagator::Center,
71
- INSERT_ACTION => Contrast::Agent::Assess::Policy::Propagator::Insert,
72
- KEEP_ACTION => Contrast::Agent::Assess::Policy::Propagator::Keep,
73
- NEXT_ACTION => Contrast::Agent::Assess::Policy::Propagator::Next,
74
- NOOP_ACTION => nil,
75
- PREPEND_ACTION => Contrast::Agent::Assess::Policy::Propagator::Prepend,
76
- REPLACE_ACTION => Contrast::Agent::Assess::Policy::Propagator::Replace,
77
- REMOVE_ACTION => Contrast::Agent::Assess::Policy::Propagator::Remove,
78
- REVERSE_ACTION => Contrast::Agent::Assess::Policy::Propagator::Reverse,
79
- SPLAT_ACTION => Contrast::Agent::Assess::Policy::Propagator::Splat,
80
- SPLIT_ACTION => Contrast::Agent::Assess::Policy::Propagator::Split
81
- }.cs__freeze
82
-
83
43
  # I lied above. We had to figure out what the target of the propagation was. Now that we know, we'll
84
44
  # actually do things to it. Note that the return of this method will replace the original return of the
85
45
  # patched function unless it is nil, so be sure you're returning what you intend.
@@ -116,80 +76,6 @@ module Contrast
116
76
  nil
117
77
  end
118
78
 
119
- # Custom actions tend to be the more complex of our propagations. Often, the method has to make decisions
120
- # about the target based on the context with which the method was called. As such, defer determining if the
121
- # target is valid to that method.
122
- #
123
- # In all other cases, a target is valid for propagation if it is not nil
124
- #
125
- # @param target [Object] the thing to which to propagate
126
- # @param propagation_node [Contrast::Agent::Assess::Policy::PropagationNode] the node that governs this
127
- # propagation event.
128
- # @return [Boolean]
129
- def valid_target? target, propagation_node
130
- return true if propagation_node.action == CUSTOM_ACTION
131
-
132
- !!target
133
- end
134
-
135
- ZERO_LENGTH_ACTIONS = [DB_WRITE_ACTION, CUSTOM_ACTION, KEEP_ACTION, REPLACE_ACTION, SPLAT_ACTION].cs__freeze
136
- # If the action required needs a length and the target does not have one, the length is not valid
137
- #
138
- # @param target [Object] the thing to which to propagate
139
- # @param action [String] the name of the action taken during this propagation
140
- # @return [Boolean]
141
- def valid_length? target, action
142
- return true if ZERO_LENGTH_ACTIONS.include?(action)
143
-
144
- if Contrast::Utils::DuckUtils.quacks_to?(target, :length)
145
- target.length != 0 # rubocop:disable Style/ZeroLengthPredicate
146
- else
147
- !target.to_s.empty?
148
- end
149
- end
150
-
151
- # Before we do any work, we should check if we even need to. If the source and target of this patcher are
152
- # not tracked, there's no need to do anything. A copy of nothing is still nothing.
153
- #
154
- # @param propagation_node [Contrast::Agent::Assess::Policy::PropagationNode] the node that governs this
155
- # propagation event.
156
- # @param preshift [Contrast::Agent::Assess::PreShift] The capture of the state of the code just prior to
157
- # the invocation of the patched method.
158
- # @param target [Object] the thing to which to propagate
159
- # @return [Boolean]
160
- def can_propagate? propagation_node, preshift, target
161
- return false unless appropriate_target?(propagation_node, target)
162
- return true if Contrast::Utils::Assess::TrackingUtil.tracked?(target)
163
- return false unless preshift
164
-
165
- propagation_node.sources.each do |source|
166
- case source
167
- when Contrast::Utils::ObjectShare::OBJECT_KEY
168
- return true if Contrast::Utils::Assess::TrackingUtil.tracked?(preshift.object)
169
- else
170
- # has to be P, there's no ret source type (yet? ever?)
171
- return true if preshift.args && Contrast::Utils::Assess::TrackingUtil.tracked?(preshift.args[source])
172
- end
173
- end
174
- false
175
- end
176
-
177
- # We cannot propagate to frozen things that have not been updated to work with our property tracking,
178
- # unless they're duplicable and the return. We probably shouldn't propagate to frozen things at all, as
179
- # they're supposed to be immutable, but third parties do jenky things, so allow it as long as it is safe to
180
- # do.
181
- #
182
- # @param propagation_node [Contrast::Agent::Assess::Policy::PropagationNode] the node that governs this
183
- # propagation event.
184
- # @param target [Object] the Target to which to propagate.
185
- # @return [Boolean] if the target can be propagated to
186
- def appropriate_target? propagation_node, target
187
- # special handle Returns b/c we can do unfreezing magic during propagation
188
- return true if propagation_node.targets[0] == Contrast::Utils::ObjectShare::RETURN_KEY
189
-
190
- Contrast::Agent::Assess::Tracker.trackable?(target)
191
- end
192
-
193
79
  # If this patcher has tags, apply them to the entire target
194
80
  # @param propagation_node [Contrast::Agent::Assess::Policy::PropagationNode] the node that governs this
195
81
  # propagation event.
@@ -95,8 +95,8 @@ module Contrast
95
95
 
96
96
  def needs_object?
97
97
  if @_needs_object.nil?
98
- @_needs_object = action == Contrast::Agent::Assess::Policy::PropagationMethod::CUSTOM_ACTION ||
99
- action == Contrast::Agent::Assess::Policy::PropagationMethod::DB_WRITE_ACTION ||
98
+ @_needs_object = action == Contrast::Utils::Assess::PropagationMethodUtils::CUSTOM_ACTION ||
99
+ action == Contrast::Utils::Assess::PropagationMethodUtils::DB_WRITE_ACTION ||
100
100
  sources.any?(Contrast::Utils::ObjectShare::OBJECT_KEY) ||
101
101
  targets.any?(Contrast::Utils::ObjectShare::OBJECT_KEY)
102
102
  end
@@ -105,8 +105,8 @@ module Contrast
105
105
 
106
106
  def needs_args?
107
107
  if @_needs_args.nil?
108
- @_needs_args = action == Contrast::Agent::Assess::Policy::PropagationMethod::CUSTOM_ACTION ||
109
- action == Contrast::Agent::Assess::Policy::PropagationMethod::DB_WRITE_ACTION ||
108
+ @_needs_args = action == Contrast::Utils::Assess::PropagationMethodUtils::CUSTOM_ACTION ||
109
+ action == Contrast::Utils::Assess::PropagationMethodUtils::DB_WRITE_ACTION ||
110
110
  sources.any? { |source| source.is_a?(Integer) || source.is_a?(Symbol) } ||
111
111
  targets.any? { |target| target.is_a?(Integer) || target.is_a?(Symbol) }
112
112
  end
@@ -6,6 +6,7 @@ require 'contrast/agent/assess/policy/source_validation/source_validation'
6
6
  require 'contrast/components/logger'
7
7
  require 'contrast/utils/object_share'
8
8
  require 'contrast/utils/sha256_builder'
9
+ require 'contrast/utils/assess/source_method_utils'
9
10
 
10
11
  module Contrast
11
12
  module Agent
@@ -16,6 +17,7 @@ module Contrast
16
17
  # used in Assess vulnerability detection.
17
18
  module SourceMethod
18
19
  extend Contrast::Components::Logger::InstanceMethods
20
+ extend Contrast::Utils::Assess::SourceMethodUtils
19
21
 
20
22
  PARAMETER_TYPE = 'PARAMETER'
21
23
  PARAMETER_KEY_TYPE = 'PARAMETER_KEY'
@@ -133,15 +135,6 @@ module Contrast
133
135
  !Contrast::Agent::Assess::Tracker.trackable?(key)
134
136
  end
135
137
 
136
- # Safely duplicate the target, or return nil
137
- #
138
- # @param target [Object] the thing to check for duplication
139
- def safe_dup target
140
- target.dup
141
- rescue StandardError => _e
142
- nil
143
- end
144
-
145
138
  # Hash is designed to keep one instance of the string key in it. We need to remove the existing one and
146
139
  # replace it with our new tracked one.
147
140
  def handle_hash_key target, to_replace
@@ -174,68 +167,6 @@ module Contrast
174
167
  properties.build_event(source_node, target, object, ret, args, source_type, source_name)
175
168
  end
176
169
 
177
- # Find the name of the source
178
- #
179
- # @param source_node [Contrast::Agent::Assess::Policy::SourceNode] the node to direct applying this source
180
- # event
181
- # @param object [Object] the Object on which the method was invoked
182
- # @param ret [Object] the Return of the invoked method
183
- # @param args [Array<Object>] the Arguments with which the method was invoked
184
- # @return [String, nil] the human readable name of the target to which this source event applies, or nil if
185
- # none provided by the node
186
- def determine_source_name source_node, object, ret, *args
187
- return source_node.get_property('dynamic_source_name') if source_node.type == 'UNTRUSTED_DATABASE'
188
-
189
- source_node_source = source_node.sources[0]
190
- case source_node_source
191
- when nil
192
- nil
193
- when Contrast::Utils::ObjectShare::RETURN_KEY
194
- ret
195
- when Contrast::Utils::ObjectShare::OBJECT_KEY
196
- object
197
- else
198
- args[source_node_source]
199
- end
200
- end
201
-
202
- # Determine if we should analyze this method invocation for a Source or not. We should if we have enough
203
- # information to build the context of this invocation, we're not disabled, and we can't immediately
204
- # determine the invocation was done safely.
205
- #
206
- # @param method_policy [Contrast::Agent::Patching::Policy::MethodPolicy] the policy that applies to the
207
- # method being called
208
- # @param object [Object] the Object on which the method was invoked
209
- # @param ret [Object] the Return of the invoked method
210
- # @param args [Array<Object>] the Arguments with which the method was invoked
211
- # @return [boolean] if the invocation of this method should be analyzed
212
- def analyze? method_policy, object, ret, args
213
- return false unless method_policy&.source_node
214
- return false unless ::Contrast::ASSESS.enabled?
215
- return false unless Contrast::Agent::REQUEST_TRACKER.current&.analyze_request?
216
-
217
- !safe_invocation?(method_policy.source_node, object, ret, args)
218
- end
219
-
220
- # Determine if the method was invoked safely.
221
- #
222
- # @param source_node [Contrast::Agent::Assess::Policy::SourceNode] the node to direct applying this source
223
- # event
224
- # @param _object [Object] the Object on which the method was invoked
225
- # @param _ret [Object] the Return of the invoked method
226
- # @param args [Array<Object>] the Arguments with which the method was invoked
227
- # @return [boolean] if the invocation of this method was safe
228
- def safe_invocation? source_node, _object, _ret, args
229
- # According the the Rack Specification https://github.com/rack/rack/blob/master/SPEC.rdoc, any header
230
- # from the Request will start with HTTP_. As such, only Headers with that key should be considered for
231
- # tracking, as the others have come from the Framework or Middleware stashing in the ENV. Rails, for
232
- # instance, uses action_dispatch. to store several values. Technically, you can't call
233
- # Rack::Request#get_header without a parameter, and that parameter should be a String, but trust no one.
234
- source_node.id == 'Assess:Source:Rack::Request::Env#get_header' &&
235
- args&.any? &&
236
- !args[0].to_s.start_with?('HTTP_')
237
- end
238
-
239
170
  # Find the literal target of the propagation
240
171
  #
241
172
  # @param source_node [Contrast::Agent::Assess::Policy::SourceNode] the node to direct applying this source
@@ -6,6 +6,7 @@ require 'contrast/agent/assess/policy/trigger_validation/trigger_validation'
6
6
  require 'contrast/components/logger'
7
7
  require 'contrast/utils/object_share'
8
8
  require 'contrast/utils/sha256_builder'
9
+ require 'contrast/utils/assess/trigger_method_utils'
9
10
 
10
11
  module Contrast
11
12
  module Agent
@@ -17,6 +18,7 @@ module Contrast
17
18
  # report is issued to the Service.
18
19
  module TriggerMethod
19
20
  extend Contrast::Components::Logger::InstanceMethods
21
+ extend Contrast::Utils::Assess::TriggerMethodUtils
20
22
 
21
23
  # The level of TeamServer compliance our traces meet when in the abnormal condition of being dataflow rules
22
24
  # without routes.
@@ -84,6 +86,8 @@ module Contrast
84
86
  # activity message does not exist, b/c we're invoked outside of a request context, build an activity and
85
87
  # immediately report it with the finding.
86
88
  #
89
+ # TODO: RUBY-1351
90
+ #
87
91
  # @param finding [Contrast::Api::Dtm::Finding] the Finding to report.
88
92
  # @param request [Contrast::Agent::Request] our wrapper around the Rack::Request.
89
93
  def report_finding finding, request = nil
@@ -108,64 +112,10 @@ module Contrast
108
112
 
109
113
  private
110
114
 
111
- # A request is reportable if it is not from ActionController::Live
112
- #
113
- # @param env [Hash] the env of the Request
114
- # @return [Boolean]
115
- def reportable? env
116
- !(defined?(ActionController::Live) &&
117
- env &&
118
- env['action_controller.instance'].cs__class.included_modules.include?(ActionController::Live))
119
- end
120
-
121
- # Find the request for this finding. This assumes, for now, that if there is an active request, then that
122
- # is the request to report. Otherwise, we'll use the first request found in the events of the
123
- # source_object.
124
- #
125
- # @param source [Object,nil] some Object used as the source of a trigger event
126
- # @return [Contrast::Agent::Request,nil] the request from which the dataflow on the request originated.
127
- def find_request source
128
- return Contrast::Agent::REQUEST_TRACKER.current.request if Contrast::Agent::REQUEST_TRACKER.current
129
- return unless (properties = Contrast::Agent::Assess::Tracker.properties(source))
130
-
131
- properties.events.each do |event|
132
- next unless event.cs__is_a?(Contrast::Agent::Assess::Events::SourceEvent)
133
-
134
- return event.request if event.request
135
- end
136
- nil
137
- end
138
-
139
115
  def settings
140
116
  Contrast::Agent::FeatureState.instance
141
117
  end
142
118
 
143
- # This is our method that actually checks the taint on the object our trigger_node targets.
144
- #
145
- # @param trigger_node [Contrast::Agent::Assess::Policy::TriggerNode] the node to direct applying this
146
- # trigger event
147
- # @param source [Object] the source of the Trigger Event
148
- # @param object [Object] the Object on which the method was invoked
149
- # @param ret [Object] the Return of the invoked method
150
- # @param args [Array<Object>] the Arguments with which the method was invoked
151
- def apply_trigger trigger_node, source, object, ret, *args
152
- return unless trigger_node
153
- return if trigger_node.rule_disabled?
154
- return if trigger_node.dataflow? && source.nil?
155
-
156
- if trigger_node.regexp_rule?
157
- apply_regexp_rule(trigger_node, source, object, ret, *args)
158
- elsif trigger_node.custom_trigger?
159
- trigger_node.apply_custom_trigger(trigger_node, source, object, ret, *args)
160
- elsif trigger_node.dataflow?
161
- apply_dataflow_rule(trigger_node, source, object, ret, *args)
162
- else # trigger rule - just calling the method is dangerous
163
- build_finding(trigger_node, source, object, ret, *args)
164
- end
165
- rescue StandardError => e
166
- logger.warn('Unable to apply trigger', e, node_id: trigger_node.id)
167
- end
168
-
169
119
  # Given the marker from the trigger_node (the pointer indicating the entity from which the taint
170
120
  # originated), return the entity on which this trigger needs to operate.
171
121
  #
@@ -199,58 +149,6 @@ module Contrast
199
149
  end
200
150
  end
201
151
 
202
- # This is our method that actually checks the taint on the object our trigger_node targets for our Regexp
203
- # based rules.
204
- #
205
- # @param trigger_node [Contrast::Agent::Assess::Policy::TriggerNode] the node to direct applying this
206
- # trigger event
207
- # @param source [Object] the source of the Trigger Event
208
- # @param object [Object] the Object on which the method was invoked
209
- # @param ret [Object] the Return of the invoked method
210
- # @param args [Array<Object>] the Arguments with which the method was invoked
211
- def apply_regexp_rule trigger_node, source, object, ret, *args
212
- return unless source.is_a?(String)
213
- return if trigger_node.good_value && source.match?(trigger_node.good_value)
214
- return if trigger_node.bad_value && source !~ trigger_node.bad_value
215
-
216
- build_finding(trigger_node, source, object, ret, *args)
217
- end
218
-
219
- # This is our method that actually checks the taint on the object our trigger_node targets for our Dataflow
220
- # based rules.
221
- #
222
- # @param trigger_node [Contrast::Agent::Assess::Policy::TriggerNode] the node to direct applying this
223
- # trigger event
224
- # @param source [Object] the source of the Trigger Event
225
- # @param object [Object] the Object on which the method was invoked
226
- # @param ret [Object] the Return of the invoked method
227
- # @param args [Array<Object>] the Arguments with which the method was invoked
228
- def apply_dataflow_rule trigger_node, source, object, ret, *args
229
- return unless source
230
-
231
- if Contrast::Agent::Assess::Tracker.trackable?(source)
232
- return unless Contrast::Agent::Assess::Tracker.tracked?(source)
233
- return unless trigger_node.violated?(source)
234
-
235
- build_finding(trigger_node, source, object, ret, *args)
236
- elsif Contrast::Utils::DuckUtils.iterable_hash?(source)
237
- source.each_pair do |key, value|
238
- apply_dataflow_rule(trigger_node, key, object, ret, *args)
239
- apply_dataflow_rule(trigger_node, value, object, ret, *args)
240
- end
241
- elsif Contrast::Utils::DuckUtils.iterable_enumerable?(source)
242
- source.each do |value|
243
- apply_dataflow_rule(trigger_node, value, object, ret, *args)
244
- end
245
- else
246
- logger.debug('Trigger source is untrackable. Unable to inspect.',
247
- node_id: trigger_node.id,
248
- source_id: source.__id__,
249
- source_type: source.cs__class.cs__name,
250
- frozen: source.cs__frozen?)
251
- end
252
- end
253
-
254
152
  def append_events finding, trigger_node, source, object, ret, args
255
153
  append_from_source(finding, source)
256
154
  finding.events << Contrast::Agent::Assess::Events::EventFactory.build(trigger_node, source, object, ret,