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.
- checksums.yaml +4 -4
- data/ext/cs__assess_module/cs__assess_module.c +48 -0
- data/ext/cs__assess_module/cs__assess_module.h +7 -0
- data/ext/cs__common/cs__common.c +5 -0
- data/ext/cs__common/cs__common.h +8 -0
- data/ext/cs__contrast_patch/cs__contrast_patch.c +16 -1
- data/ext/cs__os_information/cs__os_information.c +31 -0
- data/ext/cs__os_information/cs__os_information.h +7 -0
- data/ext/cs__os_information/extconf.rb +5 -0
- data/lib/contrast/agent/assess/policy/propagation_method.rb +2 -116
- data/lib/contrast/agent/assess/policy/propagation_node.rb +4 -4
- data/lib/contrast/agent/assess/policy/source_method.rb +2 -71
- data/lib/contrast/agent/assess/policy/trigger_method.rb +4 -106
- data/lib/contrast/agent/assess/property/tagged.rb +2 -128
- data/lib/contrast/agent/deadzone/policy/policy.rb +1 -1
- data/lib/contrast/agent/inventory/dependency_usage_analysis.rb +1 -0
- data/lib/contrast/agent/metric_telemetry_event.rb +26 -0
- data/lib/contrast/agent/middleware.rb +22 -0
- data/lib/contrast/agent/patching/policy/patch.rb +28 -235
- data/lib/contrast/agent/patching/policy/patcher.rb +2 -41
- data/lib/contrast/agent/request_handler.rb +7 -3
- data/lib/contrast/agent/startup_metrics_telemetry_event.rb +71 -0
- data/lib/contrast/agent/static_analysis.rb +4 -2
- data/lib/contrast/agent/telemetry.rb +129 -0
- data/lib/contrast/agent/telemetry_event.rb +34 -0
- data/lib/contrast/agent/thread_watcher.rb +43 -14
- data/lib/contrast/agent/version.rb +1 -1
- data/lib/contrast/agent.rb +6 -0
- data/lib/contrast/components/api.rb +34 -0
- data/lib/contrast/components/app_context.rb +24 -0
- data/lib/contrast/components/config.rb +90 -11
- data/lib/contrast/components/contrast_service.rb +6 -0
- data/lib/contrast/config/api_configuration.rb +22 -0
- data/lib/contrast/config/env_variables.rb +25 -0
- data/lib/contrast/config/root_configuration.rb +1 -0
- data/lib/contrast/config/service_configuration.rb +2 -1
- data/lib/contrast/config.rb +1 -0
- data/lib/contrast/configuration.rb +3 -0
- data/lib/contrast/framework/manager.rb +14 -12
- data/lib/contrast/framework/rails/patch/action_controller_live_buffer.rb +9 -6
- data/lib/contrast/framework/rails/patch/support.rb +31 -29
- data/lib/contrast/logger/application.rb +4 -0
- data/lib/contrast/utils/assess/propagation_method_utils.rb +129 -0
- data/lib/contrast/utils/assess/property/tagged_utils.rb +142 -0
- data/lib/contrast/utils/assess/source_method_utils.rb +83 -0
- data/lib/contrast/utils/assess/trigger_method_utils.rb +138 -0
- data/lib/contrast/utils/exclude_key.rb +20 -0
- data/lib/contrast/utils/metrics_hash.rb +59 -0
- data/lib/contrast/utils/os.rb +23 -0
- data/lib/contrast/utils/patching/policy/patch_utils.rb +232 -0
- data/lib/contrast/utils/patching/policy/patcher_utils.rb +54 -0
- data/lib/contrast/utils/requests_client.rb +150 -0
- data/lib/contrast/utils/telemetry.rb +78 -0
- data/lib/contrast/utils/telemetry_identifier.rb +137 -0
- data/lib/contrast.rb +18 -0
- data/ruby-agent.gemspec +2 -1
- data/service_executables/VERSION +1 -1
- data/service_executables/linux/contrast-service +0 -0
- data/service_executables/mac/contrast-service +0 -0
- metadata +32 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 469e09f0e64dcdb68643154ca601b20b8f61d634789dec642a374d6a51671fec
|
4
|
+
data.tar.gz: e4053b76ef09eaf13ee1cf09418d5ae50cd5e518b5e69f611c0a22d15a9bcd4d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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);
|
data/ext/cs__common/cs__common.c
CHANGED
@@ -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"));
|
data/ext/cs__common/cs__common.h
CHANGED
@@ -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
|
+
}
|
@@ -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::
|
99
|
-
action == Contrast::
|
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::
|
109
|
-
action == Contrast::
|
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,
|