contrast-agent 4.2.0 → 4.3.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 (106) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +1 -0
  3. data/ext/cs__assess_marshal_module/cs__assess_marshal_module.c +22 -10
  4. data/ext/cs__assess_marshal_module/cs__assess_marshal_module.h +4 -3
  5. data/lib/contrast/agent/assess/contrast_event.rb +49 -130
  6. data/lib/contrast/agent/assess/contrast_object.rb +51 -0
  7. data/lib/contrast/agent/assess/events/source_event.rb +4 -9
  8. data/lib/contrast/agent/assess/policy/patcher.rb +4 -3
  9. data/lib/contrast/agent/assess/policy/policy_node.rb +31 -59
  10. data/lib/contrast/agent/assess/policy/preshift.rb +3 -3
  11. data/lib/contrast/agent/assess/policy/propagation_method.rb +13 -19
  12. data/lib/contrast/agent/assess/policy/propagation_node.rb +12 -24
  13. data/lib/contrast/agent/assess/policy/propagator/append.rb +1 -2
  14. data/lib/contrast/agent/assess/policy/propagator/center.rb +1 -2
  15. data/lib/contrast/agent/assess/policy/propagator/custom.rb +1 -1
  16. data/lib/contrast/agent/assess/policy/propagator/database_write.rb +1 -3
  17. data/lib/contrast/agent/assess/policy/propagator/insert.rb +1 -2
  18. data/lib/contrast/agent/assess/policy/propagator/keep.rb +1 -2
  19. data/lib/contrast/agent/assess/policy/propagator/match_data.rb +3 -2
  20. data/lib/contrast/agent/assess/policy/propagator/next.rb +1 -2
  21. data/lib/contrast/agent/assess/policy/propagator/prepend.rb +1 -2
  22. data/lib/contrast/agent/assess/policy/propagator/remove.rb +2 -4
  23. data/lib/contrast/agent/assess/policy/propagator/replace.rb +1 -2
  24. data/lib/contrast/agent/assess/policy/propagator/reverse.rb +1 -2
  25. data/lib/contrast/agent/assess/policy/propagator/select.rb +3 -4
  26. data/lib/contrast/agent/assess/policy/propagator/splat.rb +2 -4
  27. data/lib/contrast/agent/assess/policy/propagator/split.rb +73 -117
  28. data/lib/contrast/agent/assess/policy/propagator/substitution.rb +11 -11
  29. data/lib/contrast/agent/assess/policy/propagator/trim.rb +3 -7
  30. data/lib/contrast/agent/assess/policy/source_method.rb +2 -14
  31. data/lib/contrast/agent/assess/policy/trigger/reflected_xss.rb +5 -8
  32. data/lib/contrast/agent/assess/policy/trigger/xpath.rb +1 -1
  33. data/lib/contrast/agent/assess/policy/trigger_validation/ssrf_validator.rb +1 -1
  34. data/lib/contrast/agent/assess/property/tagged.rb +21 -15
  35. data/lib/contrast/agent/assess/rule/redos.rb +1 -1
  36. data/lib/contrast/agent/assess/tracker.rb +16 -18
  37. data/lib/contrast/agent/deadzone/policy/deadzone_node.rb +7 -0
  38. data/lib/contrast/agent/middleware.rb +50 -1
  39. data/lib/contrast/agent/patching/policy/method_policy.rb +1 -1
  40. data/lib/contrast/agent/patching/policy/patch.rb +4 -4
  41. data/lib/contrast/agent/protect/policy/applies_deserialization_rule.rb +47 -1
  42. data/lib/contrast/agent/protect/policy/rule_applicator.rb +53 -0
  43. data/lib/contrast/agent/protect/rule/base.rb +63 -14
  44. data/lib/contrast/agent/protect/rule/cmd_injection.rb +3 -3
  45. data/lib/contrast/agent/protect/rule/default_scanner.rb +1 -4
  46. data/lib/contrast/agent/protect/rule/deserialization.rb +4 -1
  47. data/lib/contrast/agent/protect/rule/no_sqli.rb +3 -3
  48. data/lib/contrast/agent/protect/rule/sqli.rb +3 -3
  49. data/lib/contrast/agent/protect/rule/xxe.rb +32 -11
  50. data/lib/contrast/agent/protect/rule/xxe/entity_wrapper.rb +10 -6
  51. data/lib/contrast/agent/reaction_processor.rb +1 -1
  52. data/lib/contrast/agent/response.rb +5 -5
  53. data/lib/contrast/agent/rewriter.rb +3 -3
  54. data/lib/contrast/agent/scope.rb +33 -13
  55. data/lib/contrast/agent/static_analysis.rb +13 -7
  56. data/lib/contrast/agent/version.rb +1 -1
  57. data/lib/contrast/api/decorators/library.rb +1 -0
  58. data/lib/contrast/api/decorators/library_usage_update.rb +1 -0
  59. data/lib/contrast/api/decorators/trace_event.rb +19 -31
  60. data/lib/contrast/api/decorators/trace_event_object.rb +11 -3
  61. data/lib/contrast/api/decorators/trace_event_signature.rb +27 -5
  62. data/lib/contrast/api/decorators/user_input.rb +2 -1
  63. data/lib/contrast/common_agent_configuration.rb +1 -1
  64. data/lib/contrast/components/assess.rb +36 -0
  65. data/lib/contrast/components/interface.rb +5 -3
  66. data/lib/contrast/components/scope.rb +23 -0
  67. data/lib/contrast/components/settings.rb +3 -3
  68. data/lib/contrast/config/assess_configuration.rb +2 -1
  69. data/lib/contrast/extension/assess/array.rb +1 -2
  70. data/lib/contrast/extension/assess/erb.rb +1 -3
  71. data/lib/contrast/extension/assess/exec_trigger.rb +1 -1
  72. data/lib/contrast/extension/assess/fiber.rb +2 -3
  73. data/lib/contrast/extension/assess/hash.rb +4 -2
  74. data/lib/contrast/extension/assess/kernel.rb +1 -2
  75. data/lib/contrast/extension/assess/marshal.rb +34 -26
  76. data/lib/contrast/extension/assess/regexp.rb +3 -8
  77. data/lib/contrast/extension/assess/string.rb +1 -2
  78. data/lib/contrast/framework/base_support.rb +51 -53
  79. data/lib/contrast/framework/manager.rb +3 -2
  80. data/lib/contrast/framework/rack/patch/session_cookie.rb +1 -1
  81. data/lib/contrast/framework/rack/support.rb +2 -1
  82. data/lib/contrast/framework/rails/patch/action_controller_live_buffer.rb +1 -1
  83. data/lib/contrast/framework/rails/patch/rails_application_configuration.rb +1 -1
  84. data/lib/contrast/framework/rails/rewrite/action_controller_railties_helper_inherited.rb +1 -1
  85. data/lib/contrast/framework/rails/rewrite/active_record_attribute_methods_read.rb +1 -1
  86. data/lib/contrast/framework/rails/rewrite/active_record_time_zone_inherited.rb +1 -1
  87. data/lib/contrast/framework/rails/support.rb +2 -1
  88. data/lib/contrast/framework/sinatra/support.rb +3 -2
  89. data/lib/contrast/logger/application.rb +0 -3
  90. data/lib/contrast/utils/duck_utils.rb +1 -1
  91. data/lib/contrast/utils/heap_dump_util.rb +1 -1
  92. data/lib/contrast/utils/object_share.rb +3 -3
  93. data/lib/contrast/utils/preflight_util.rb +1 -1
  94. data/lib/contrast/utils/prevent_serialization.rb +1 -1
  95. data/lib/contrast/utils/resource_loader.rb +1 -1
  96. data/lib/contrast/utils/sha256_builder.rb +2 -2
  97. data/lib/contrast/utils/string_utils.rb +1 -1
  98. data/lib/contrast/utils/tag_util.rb +9 -13
  99. data/resources/assess/policy.json +9 -9
  100. data/resources/deadzone/policy.json +156 -0
  101. data/resources/protect/policy.json +12 -0
  102. data/ruby-agent.gemspec +9 -6
  103. data/service_executables/VERSION +1 -1
  104. data/service_executables/linux/contrast-service +0 -0
  105. data/service_executables/mac/contrast-service +0 -0
  106. metadata +68 -25
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 713e0f9cd16184d4b5da094f0d05b006870f557edfa0fc0ac199bf1035acfe45
4
- data.tar.gz: f37a1ab7901a7c42bf5dce897c6dd0218f4df40d057c8719e3027f802ecd3c52
3
+ metadata.gz: 772706612c29bc99cb9861c736056218e830881daba5ea2f9a4a2e86ecaf83ab
4
+ data.tar.gz: 85d07cd9104e2b6f0c1e04e5ee4d1562ad8de62a7155dd761cdcaa554f4e68fd
5
5
  SHA512:
6
- metadata.gz: f1bcf5495957815fbe1acc801e9cdc9567a1a25f657b425a335cfa1412054ecdf4f92662d0eef0b19bdc2a1c5295b758d19ed4adf4cb7f37ef67bfaf4b67262f
7
- data.tar.gz: bd3e6f02d68c7eaa9385f6626e52b3fe16cecbac10d53f8c82dd01b733c9f39666183afd21df601318ed95c482338b786e13cb8f087c8076872cd5fb3f4cfad8
6
+ metadata.gz: 2e80a24fd38c3f88377abc59e146c755ec6e0272a64fd3af6d840888e33672513fc62539d9d9eae6de49d5c46bcce34ce656a8f8afe3fa71c0e80a3215b58753
7
+ data.tar.gz: 8bf6282052071f42dba4e40b0cd3abf9f15531a4e0fdff208d047d5a679c5268a903be5d93ec178d2ace254b5784548753c795088fdd6edac56bcd38ac1d2997
data/Rakefile CHANGED
@@ -18,6 +18,7 @@ Dir['ext/cs__*'].each do |extension|
18
18
  end
19
19
  end
20
20
 
21
+ desc 'compile the protobuf files for the agent, translating them to .rb classes'
21
22
  task :contrast_pb_compile do
22
23
  # do some stuff before compile
23
24
 
@@ -5,28 +5,39 @@
5
5
  #include "../cs__common/cs__common.h"
6
6
  #include <ruby.h>
7
7
 
8
- static VALUE contrast_assess_marshal_module_load(const int argc,
8
+ static VALUE contrast_marshal_module_load(const int argc,
9
9
  const VALUE *argv) {
10
10
  VALUE result;
11
11
  VALUE source_string;
12
- result = rb_call_super(argc, argv);
13
12
 
13
+ // Our patches only need only apply in the case where there was valid input.
14
14
  if (argc >= 1) {
15
15
  source_string = argv[0];
16
+ } else {
17
+ source_string = Qnil;
18
+ }
19
+
20
+ // Run our protect code ahead of the original method
21
+ if (source_string != Qnil) {
22
+ rb_funcall(marshal_propagator, rb_sym_protect_marshal_load, 1, source_string);
23
+ }
16
24
 
25
+ // Invoke the original method
26
+ result = rb_call_super(argc, argv);
27
+
28
+ // Run our assess code after the original method
29
+ if (source_string != Qnil) {
17
30
  VALUE tracked =
18
31
  rb_funcall(properties_hash, rb_sym_hash_tracked, 1, source_string);
19
32
 
33
+ // Assuming the source is tracked and needs assess checks
20
34
  if (tracked == Qtrue) {
21
35
  VALUE skip =
22
36
  rb_funcall(contrast_patcher(), rb_sym_skip_assess_analysis, 0);
23
-
37
+ // And Assess is enabled and applies to this request
24
38
  if (skip == Qfalse) {
25
- VALUE scope =
26
- rb_funcall(contrast_patcher(), rb_sym_enter_scope, 0);
27
- rb_funcall(marshal_module, rb_sym_assess_load_trigger_check, 2,
39
+ rb_funcall(marshal_propagator, rb_sym_assess_marshal_load, 2,
28
40
  source_string, result);
29
- rb_funcall(contrast_patcher(), rb_sym_exit_scope, 0);
30
41
  }
31
42
  }
32
43
  }
@@ -37,10 +48,11 @@ void Init_cs__assess_marshal_module(void) {
37
48
  // Contrast::Agent::Assess::Tracker::PROPERTIES_HASH
38
49
  VALUE tracker = rb_define_class_under(assess, "Tracker", rb_cObject);
39
50
  properties_hash = rb_const_get(tracker, rb_intern("PROPERTIES_HASH"));
40
- marshal_module =
51
+ marshal_propagator =
41
52
  rb_define_class_under(core_assess, "MarshalPropagator", rb_cObject);
42
- rb_sym_assess_load_trigger_check = rb_intern("cs__load_trigger_check");
53
+ rb_sym_assess_marshal_load = rb_intern("cs__load_assess");
54
+ rb_sym_protect_marshal_load = rb_intern("cs__load_protect");
43
55
 
44
56
  contrast_register_singleton_prepend_patch(
45
- "Marshal", "load", &contrast_assess_marshal_module_load);
57
+ "Marshal", "load", &contrast_marshal_module_load);
46
58
  }
@@ -1,8 +1,9 @@
1
1
  #include <ruby.h>
2
2
 
3
- static VALUE marshal_module;
3
+ static VALUE marshal_propagator;
4
4
 
5
- static VALUE rb_sym_assess_load_trigger_check;
5
+ static VALUE rb_sym_assess_marshal_load;
6
+ static VALUE rb_sym_protect_marshal_load;
6
7
  static VALUE properties_hash;
7
8
 
8
9
  /*
@@ -13,7 +14,7 @@ static VALUE properties_hash;
13
14
  * special case this for now.
14
15
  * -HM (shamelessly commenting on DP's work)
15
16
  */
16
- static VALUE contrast_assess_marshal_module_load(const int argc,
17
+ static VALUE contrast_marshal_module_load(const int argc,
17
18
  const VALUE *argv);
18
19
 
19
20
  void Init_cs__assess_marshal_module(void);
@@ -9,6 +9,8 @@ require 'contrast/utils/prevent_serialization'
9
9
  require 'contrast/utils/stack_trace_utils'
10
10
  require 'contrast/utils/string_utils'
11
11
  require 'contrast/utils/timer'
12
+ require 'contrast/components/interface'
13
+ require 'contrast/agent/assess/contrast_object'
12
14
 
13
15
  module Contrast
14
16
  module Agent
@@ -26,63 +28,22 @@ module Contrast
26
28
  # created
27
29
  # @attr_reader thread [Integer] the object id of the thread on which this
28
30
  # event was generated
29
- # @attr_reader object [String] the safe representation of the Object on
30
- # which the method was invoked
31
- # @attr_reader ret [String] the safe representation of the Return of the
32
- # invoked method
33
- # @attr_reader args [Array<Object>] the safe representation of the
34
- # Arguments with which the method was invoked
31
+ # @attr_reader object [Contrast::Agent::Assess::ContrastObject] the safe
32
+ # representation of the Object on which the method was invoked
33
+ # @attr_reader ret [Contrast::Agent::Assess::ContrastObject] the safe
34
+ # representation of the Return of the invoked method
35
+ # @attr_reader args [Array<Contrast::Agent::Assess::ContrastObject>] the
36
+ # safe representation of the Arguments with which the method was invoked
35
37
  class ContrastEvent
36
38
  include Contrast::Utils::PreventSerialization
39
+ include Contrast::Components::Interface
40
+ access_component :analysis
37
41
 
38
- class << self
39
- # Given an array of arguments, copy them into a safe, meaning String,
40
- # format that we can use to send to SR and TS for rendering.
41
- #
42
- # @param args [Array<Object>] the arguments to translate
43
- # @return [Array<String>] the String forms of those Objects, as
44
- # determined by Contrast::Utils::ClassUtil.to_contrast_string
45
- def safe_args_representation args
46
- return nil unless args
47
- return Contrast::Utils::ObjectShare::EMPTY_ARRAY if args.empty?
48
-
49
- rep = []
50
- args.each do |arg|
51
- # We have to handle named args
52
- rep << if arg.is_a?(Hash)
53
- safe_arg_hash_representation(arg)
54
- else
55
- Contrast::Utils::ClassUtil.to_contrast_string(arg)
56
- end
57
- end
58
- rep
59
- end
60
-
61
- # if given an object that can be duped, duplicate it. otherwise just
62
- # return the original object. swallow all exceptions from
63
- # non-duplicable things.
64
- #
65
- # we can't just check respond_to? though b/c dup exists on the
66
- # base Object class
67
- #
68
- # @param original [Object, nil] the thing to duplicate
69
- # @return [Object, nil] a copy of that thing
70
- def safe_dup original
71
- return nil unless original
72
-
73
- Contrast::Agent::Assess::Tracker.duplicate(original)
74
- end
75
-
76
- private
77
-
78
- def safe_arg_hash_representation hash
79
- # since this is the named hash for arguments, only the value is
80
- # suspect here
81
- hash.transform_values { |v| Contrast::Utils::ClassUtil.to_contrast_string(v) }
82
- end
83
- end
84
-
85
- attr_reader :event_id, :policy_node, :stack_trace, :time, :thread, :object, :ret, :args
42
+ attr_reader :event_id, :policy_node, :stack_trace, :time, :thread,
43
+ :object,
44
+ :ret,
45
+ :args,
46
+ :tags
86
47
 
87
48
  # We need this to track the parent id's of events to build up a flow
88
49
  # chart of the finding
@@ -106,16 +67,25 @@ module Contrast
106
67
  # was invoked
107
68
  def initialize policy_node, tagged, object, ret, args
108
69
  @policy_node = policy_node
109
- # so long as this event is built in a factory, we know Contrast Code
70
+
71
+ # Capture stacktraces only as configured.
72
+ #
73
+ # So long as this event is built in a factory, we know Contrast Code
110
74
  # will be the first three events
111
- @stack_trace = caller(3, 20)
75
+ @stack_trace = if ASSESS.capture_stacktrace?(policy_node)
76
+ caller(3, 20)
77
+ else
78
+ Contrast::Utils::ObjectShare::EMPTY_ARRAY
79
+ end
80
+
112
81
  @time = Contrast::Utils::Timer.now_ms
113
82
  @thread = Thread.current.object_id
114
83
 
115
84
  # These methods rely on the above being set. Don't move them!
116
85
  @event_id = Contrast::Agent::Assess::ContrastEvent.next_atomic_id
86
+ @tags = Contrast::Agent::Assess::Tracker.properties(tagged)&.tags
117
87
  find_parent_events!(policy_node, object, ret, args)
118
- snapshot!(tagged, object, ret, args)
88
+ snapshot!(object, ret, args)
119
89
  end
120
90
 
121
91
  def parent_events
@@ -128,22 +98,17 @@ module Contrast
128
98
  # Per TS law, each policy_node must have at least a source or a target.
129
99
  # The only type of policy_node w/o targets is a Trigger, but that may
130
100
  # change.
131
- # 2) If I have a highlight, it means that I have a P target that is
132
- # not in integer form (it was a named / keyword type for which I had
133
- # to find the index). I need to address this so that TS can process
134
- # it.
135
- # 3) I'll set the event's source and target to TS values.
136
- # 4) Return the highlight or the first source/target as the taint
137
- # target.
101
+ # 2) I'll set the event's source and target to TS values.
102
+ # 3) Return the first source/target as the taint target.
138
103
  def determine_taint_target event_dtm
139
104
  if @policy_node&.targets&.any?
140
105
  event_dtm.source = @policy_node.source_string if @policy_node.source_string
141
- event_dtm.target = @highlight ? "P#{ @highlight }" : @policy_node.target_string
142
- @highlight || @policy_node.targets[0]
106
+ event_dtm.target = @policy_node.target_string
107
+ @policy_node.targets[0]
143
108
  elsif policy_node&.sources&.any?
144
- event_dtm.source = @highlight ? "P#{ @highlight }" : @policy_node.source_string
109
+ event_dtm.source = @policy_node.source_string
145
110
  event_dtm.target = @policy_node.target_string if @policy_node.target_string
146
- @highlight || @policy_node.sources[0]
111
+ @policy_node.sources[0]
147
112
  end
148
113
  end
149
114
 
@@ -193,16 +158,7 @@ module Contrast
193
158
  when Contrast::Utils::ObjectShare::RETURN_KEY
194
159
  ret
195
160
  else
196
- if source.is_a?(Integer)
197
- args[source]
198
- else
199
- args.each do |search|
200
- next unless search.is_a?(Hash)
201
-
202
- s = search[source]
203
- return s if s
204
- end
205
- end
161
+ args[source]
206
162
  end
207
163
  end
208
164
 
@@ -212,65 +168,28 @@ module Contrast
212
168
  # them for our later use. We set those safe values to this event's
213
169
  # instance variables.
214
170
  #
215
- # @param tagged [Object] the Target to which this event pertains.
216
171
  # @param object [Object] the Object on which the method was invoked
217
172
  # @param ret [Object] the Return of the invoked method
218
173
  # @param args [Array<Object>] the Arguments with which the method
219
174
  # was invoked
220
- def snapshot! tagged, object, ret, args
221
- target = @policy_node.target
222
- case target
223
- # If the target is nil, this rule was violated simply by a method
224
- # being called. We'll save all the information, but nothing will be
225
- # marked up, as nothing need be tracked
226
- when nil
227
- @object = Contrast::Utils::ClassUtil.to_contrast_string(object)
228
- @args = cs__class.safe_args_representation(args)
229
- @ret = Contrast::Utils::ClassUtil.to_contrast_string(ret)
230
- # If the target is O, then we dup the O and safely represent the rest
231
- when Contrast::Utils::ObjectShare::OBJECT_KEY
232
- @object = cs__class.safe_dup(tagged)
233
- @args = cs__class.safe_args_representation(args)
234
- @ret = Contrast::Utils::ClassUtil.to_contrast_string(ret)
235
- # If the target is R, then we dup the R and safely represent the rest
236
- when Contrast::Utils::ObjectShare::RETURN_KEY
237
- @object = Contrast::Utils::ClassUtil.to_contrast_string(object)
238
- @args = cs__class.safe_args_representation(args)
239
- @ret = cs__class.safe_dup(tagged)
240
- # If the target is P*, then we need to dup things a differently. We
241
- # need to find the true target inside so that we can mark it up
242
- # later, but the other args should be represented as their safe form.
243
- else
244
- @object = Contrast::Utils::ClassUtil.to_contrast_string(object)
245
- @args = cs__class.safe_args_representation(args)
246
- @ret = Contrast::Utils::ClassUtil.to_contrast_string(ret)
247
- save_target_arg(target, tagged)
248
- end
175
+ def snapshot! object, ret, args
176
+ @object = Contrast::Agent::Assess::ContrastObject.new(object) if object
177
+ @ret = Contrast::Agent::Assess::ContrastObject.new(ret) if ret
178
+ @args = safe_args_representation(args)
179
+ self
249
180
  end
250
181
 
251
- # I know we're creating an extra string here since we replace the safe
252
- # one w/ a dup, but good enough for now. Trying not to make this too
253
- # complicated. - HM 8/8/19
182
+ # Given an array of arguments, copy them into a safe, meaning String,
183
+ # format that we can use to send to SR and TS for rendering.
254
184
  #
255
- # @param target [String,Integer] the marker for the target index
256
- # @param tagged [Object] the actual Object that we're acting on which
257
- # has tags
258
- def save_target_arg target, tagged
259
- return if @args.cs__frozen?
260
-
261
- if target.is_a?(Integer)
262
- @args[target] = cs__class.safe_dup(tagged)
263
- return
264
- end
265
-
266
- @args.each_with_index do |search, index|
267
- next unless search.is_a?(Hash)
268
- next unless search[target]
269
-
270
- search[target] = cs__class.safe_dup(tagged)
271
- @highlight = index
272
- break
273
- end
185
+ # @param args [Array<Object>] the arguments to translate
186
+ # @return [Array<Contrast::Agent::Assess::ContrastObject>] the String forms of those Objects, as
187
+ # determined by Contrast::Utils::ClassUtil.to_contrast_string
188
+ def safe_args_representation args
189
+ return unless args
190
+ return Contrast::Utils::ObjectShare::EMPTY_ARRAY if args.empty?
191
+
192
+ args.map { |arg| arg ? Contrast::Agent::Assess::ContrastObject.new(arg) : nil }
274
193
  end
275
194
  end
276
195
  end
@@ -0,0 +1,51 @@
1
+ # Copyright (c) 2020 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
2
+ # frozen_string_literal: true
3
+
4
+ require 'contrast/utils/class_util'
5
+
6
+ module Contrast
7
+ module Agent
8
+ module Assess
9
+ # This class is a convenient holder of our version of an Object. It
10
+ # creates a String version of the Object from the original provided
11
+ # and keeps reference to the original's Tags, letting us determine if it
12
+ # was tracked when we try to report to TeamServer.
13
+ #
14
+ # @attr_reader object [String, nil] the Contrast string representing the
15
+ # object.
16
+ # @attr_reader object_type [String] the name of the object's module.
17
+ # @attr_reader tags [Hash{String => Contrast::Agent::Assess::Tag}, nil]
18
+ # the tags on the object before it was captured.
19
+ #
20
+ # TODO: RUBY-1083 determine if this is expensive and/or worth not storing
21
+ # these values directly on ContrastEvent and passing them around. Args
22
+ # probably make the argument for wrapping them b/c otherwise we'll have
23
+ # to keep two arrays in synch or make an array of arrays, at which
24
+ # point, we may as well make this.
25
+ class ContrastObject
26
+ attr_reader :object, :object_type, :tags
27
+
28
+ # Capture the details about the object which we need to render it in
29
+ # TeamServer.
30
+ #
31
+ # @param object [Object] the thing to keep a Contrast String of
32
+ def initialize object
33
+ if object
34
+ @object = Contrast::Utils::ClassUtil.to_contrast_string(object)
35
+ @object_type = object.cs__class.name
36
+ # TODO: RUBY-1084 determine if we need to copy these tags to
37
+ # restore immutability. For instance, if these tags were on a
38
+ # String that was then #reverse!'d, would our tags be wrong?
39
+ @tags = Contrast::Agent::Assess::Tracker.properties(object)&.tags
40
+ else
41
+ @object_type = Contrast::Utils::ObjectShare::NIL_STRING
42
+ end
43
+ end
44
+
45
+ def tracked?
46
+ tags&.any?
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -58,19 +58,14 @@ module Contrast
58
58
 
59
59
  # We have to do a little work to figure out what our TS appropriate
60
60
  # target is. To break this down, the logic is as follows:
61
- # 1) If I have a highlight, it means that I have a P target that is
62
- # not in integer form (it was a named / keyword type for which I had
63
- # to find the index). I need to address this so that TS can process
64
- # it.
65
- # 2) I'll set the event's source and target to TS values.
66
- # 3) Return the highlight or the first source/target as the taint
67
- # target.
61
+ # 1) I'll set the event's source and target to TS values.
62
+ # 2) Return the first source/target as the taint target.
68
63
  def determine_taint_target event_dtm
69
64
  return unless @policy_node&.targets&.any?
70
65
 
71
66
  event_dtm.source = @policy_node.source_string if @policy_node.source_string
72
- event_dtm.target = @highlight ? "P#{ @highlight }" : @policy_node.target_string
73
- @highlight || @policy_node.targets[0]
67
+ event_dtm.target = @policy_node.target_string
68
+ @policy_node.targets[0]
74
69
  end
75
70
  end
76
71
  end
@@ -52,12 +52,13 @@ module Contrast
52
52
  Contrast::Utils::ObjectShare::CLASS,
53
53
  Contrast::Utils::ObjectShare::MODULE
54
54
  ].cs__freeze
55
- def patch_assess_method clazz, method_name
55
+ def patch_assess_method mod, method_name
56
56
  # Module.define_method is called a lot in Class and Module. We
57
57
  # currently do not expect these define_methods to result in methods
58
58
  # that require patching, so for the sake of performance, we're going
59
59
  # to skip evaluating them
60
- class_name = clazz.cs__name
60
+ mod = mod.cs__class unless mod.cs__is_a?(Module)
61
+ class_name = mod.cs__class
61
62
  return if CLASS_TYPES.include?(class_name)
62
63
  return unless ASSESS.enabled?
63
64
 
@@ -73,7 +74,7 @@ module Contrast
73
74
  method_name: source_node.method_name,
74
75
  method_visibility: source_node.method_visibility,
75
76
  instance_method: true)
76
- patcher.patch_method(clazz, method_array, method_policy)
77
+ patcher.patch_method(mod, method_array, method_policy)
77
78
  end
78
79
  rescue StandardError => e
79
80
  logger.warn(
@@ -19,8 +19,8 @@ module Contrast
19
19
  @source_string = policy_hash[JSON_SOURCE]
20
20
  @target_string = policy_hash[JSON_TARGET]
21
21
  @tags = Set.new(policy_hash[JSON_TAGS])
22
- generate_sources
23
- generate_targets
22
+ @sources = convert_policy_markers(source_string)
23
+ @targets = convert_policy_markers(target_string)
24
24
  end
25
25
 
26
26
  def feature
@@ -47,7 +47,7 @@ module Contrast
47
47
 
48
48
  def target_string= value
49
49
  @target_string = value
50
- generate_targets
50
+ @targets = convert_policy_markers(value)
51
51
  end
52
52
 
53
53
  # Sometimes we need to tie information to an event. We'll add a
@@ -66,62 +66,6 @@ module Contrast
66
66
  @properties[name]
67
67
  end
68
68
 
69
- # Given a source in the format A,B,C, populate the sources of this node
70
- # 1) Split on ','
71
- # 2) If 'O', add the source, else it's P (we don't have R sources) and
72
- # needs to be converted. P type will either be P:name or P# where #
73
- # is the index of the parameter. Drop the P and store the int as int
74
- # or name as symbol
75
- def generate_sources
76
- if source_string
77
- @sources = []
78
- source_string.split(Contrast::Utils::ObjectShare::COMMA).each do |s|
79
- is_object = (s == Contrast::Utils::ObjectShare::OBJECT_KEY)
80
- if is_object
81
- @sources << s
82
- else
83
- parameter_source = s[1..-1]
84
- @sources << if parameter_source.start_with?(Contrast::Utils::ObjectShare::COLON)
85
- parameter_source[1..-1].to_sym
86
- else
87
- parameter_source.to_i
88
- end
89
- end
90
- end
91
- else
92
- @sources = Contrast::Utils::ObjectShare::EMPTY_ARRAY
93
- end
94
- end
95
-
96
- # Given a target in the format A,B,C, populate the targets of this node
97
- # 1) Split on ','
98
- # 2) If 'O' or 'R', add the target, else it's P and needs to be
99
- # converted. P type will either be P:name or P# where # is the index
100
- # of the paramter. Drop the P and store the int as int or name as
101
- # symbol
102
- def generate_targets
103
- if target_string
104
- @targets = []
105
- target_string.split(Contrast::Utils::ObjectShare::COMMA).each do |t|
106
- case t
107
- when Contrast::Utils::ObjectShare::OBJECT_KEY
108
- @targets << t
109
- when Contrast::Utils::ObjectShare::RETURN_KEY
110
- @targets << t
111
- else
112
- parameter_target = t[1..-1]
113
- @targets << if parameter_target.start_with?(Contrast::Utils::ObjectShare::COLON)
114
- parameter_target[1..-1].to_sym
115
- else
116
- parameter_target.to_i
117
- end
118
- end
119
- end
120
- else
121
- @targets = Contrast::Utils::ObjectShare::EMPTY_ARRAY
122
- end
123
- end
124
-
125
69
  # Don't let nodes be created that will be missing things we need
126
70
  # later on. Really, if they don't have these things, they couldn't have
127
71
  # done their jobs anyway.
@@ -186,6 +130,34 @@ module Contrast
186
130
  JSON_TARGET = 'target'
187
131
  JSON_TAGS = 'tags'
188
132
  JSON_DATAFLOW = 'dataflow'
133
+
134
+ private
135
+
136
+ # Given a policy string in the format A,B,C, populate the given array
137
+ # 1) Split on ','
138
+ # 2) If 'O' or 'R', add the array, else it's P and needs to be
139
+ # converted. P type will either be P# where # is the index
140
+ # of the parameter. Drop the P and store the # as an int.
141
+ #
142
+ # @param markers [String] the String from the policy to parse
143
+ # @return [Array] the array generated by converting the marker string
144
+ def convert_policy_markers markers
145
+ return Contrast::Utils::ObjectShare::EMPTY_ARRAY unless markers
146
+ return Contrast::Utils::ObjectShare::EMPTY_ARRAY if markers.empty?
147
+
148
+ converted = []
149
+ markers.split(Contrast::Utils::ObjectShare::COMMA).each do |t|
150
+ case t
151
+ when Contrast::Utils::ObjectShare::OBJECT_KEY,
152
+ Contrast::Utils::ObjectShare::RETURN_KEY
153
+
154
+ converted << t
155
+ else
156
+ converted << Integer(t[1..-1])
157
+ end
158
+ end
159
+ converted
160
+ end
189
161
  end
190
162
  end
191
163
  end