contrast-agent 4.12.0 → 4.13.0

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