contrast-agent 3.15.0 → 4.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (158) 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.rb +4 -12
  6. data/lib/contrast/agent/assess/contrast_event.rb +121 -130
  7. data/lib/contrast/agent/assess/contrast_object.rb +51 -0
  8. data/lib/contrast/agent/assess/events/source_event.rb +5 -10
  9. data/lib/contrast/agent/assess/policy/dynamic_source_factory.rb +10 -3
  10. data/lib/contrast/agent/assess/policy/patcher.rb +4 -3
  11. data/lib/contrast/agent/assess/policy/policy_node.rb +46 -69
  12. data/lib/contrast/agent/assess/policy/policy_scanner.rb +19 -2
  13. data/lib/contrast/agent/assess/policy/preshift.rb +3 -3
  14. data/lib/contrast/agent/assess/policy/propagation_method.rb +13 -19
  15. data/lib/contrast/agent/assess/policy/propagation_node.rb +12 -24
  16. data/lib/contrast/agent/assess/policy/propagator/append.rb +1 -2
  17. data/lib/contrast/agent/assess/policy/propagator/center.rb +1 -2
  18. data/lib/contrast/agent/assess/policy/propagator/custom.rb +1 -1
  19. data/lib/contrast/agent/assess/policy/propagator/database_write.rb +1 -3
  20. data/lib/contrast/agent/assess/policy/propagator/insert.rb +2 -3
  21. data/lib/contrast/agent/assess/policy/propagator/keep.rb +1 -2
  22. data/lib/contrast/agent/assess/policy/propagator/match_data.rb +3 -5
  23. data/lib/contrast/agent/assess/policy/propagator/next.rb +1 -2
  24. data/lib/contrast/agent/assess/policy/propagator/prepend.rb +1 -2
  25. data/lib/contrast/agent/assess/policy/propagator/remove.rb +2 -4
  26. data/lib/contrast/agent/assess/policy/propagator/replace.rb +1 -2
  27. data/lib/contrast/agent/assess/policy/propagator/reverse.rb +1 -2
  28. data/lib/contrast/agent/assess/policy/propagator/select.rb +4 -7
  29. data/lib/contrast/agent/assess/policy/propagator/splat.rb +2 -9
  30. data/lib/contrast/agent/assess/policy/propagator/split.rb +77 -122
  31. data/lib/contrast/agent/assess/policy/propagator/substitution.rb +32 -25
  32. data/lib/contrast/agent/assess/policy/propagator/trim.rb +3 -7
  33. data/lib/contrast/agent/assess/policy/source_method.rb +2 -14
  34. data/lib/contrast/agent/assess/policy/trigger/reflected_xss.rb +9 -13
  35. data/lib/contrast/agent/assess/policy/trigger/xpath.rb +1 -1
  36. data/lib/contrast/agent/assess/policy/trigger_method.rb +39 -14
  37. data/lib/contrast/agent/assess/policy/trigger_node.rb +31 -37
  38. data/lib/contrast/agent/assess/policy/trigger_validation/ssrf_validator.rb +1 -1
  39. data/lib/contrast/agent/assess/property/evented.rb +5 -18
  40. data/lib/contrast/agent/assess/property/tagged.rb +28 -16
  41. data/lib/contrast/agent/assess/property/updated.rb +0 -5
  42. data/lib/contrast/agent/assess/rule/provider/hardcoded_key.rb +58 -5
  43. data/lib/contrast/agent/assess/rule/provider/hardcoded_password.rb +23 -8
  44. data/lib/contrast/agent/assess/rule/provider/hardcoded_value_rule.rb +83 -14
  45. data/lib/contrast/agent/assess/rule/redos.rb +1 -1
  46. data/lib/contrast/agent/assess/tag.rb +1 -1
  47. data/lib/contrast/agent/assess/tracker.rb +16 -18
  48. data/lib/contrast/agent/at_exit_hook.rb +5 -5
  49. data/lib/contrast/agent/deadzone/policy/deadzone_node.rb +7 -0
  50. data/lib/contrast/agent/inventory.rb +15 -0
  51. data/lib/contrast/agent/inventory/dependencies.rb +50 -0
  52. data/lib/contrast/agent/inventory/dependency_analysis.rb +37 -0
  53. data/lib/contrast/agent/inventory/dependency_usage_analysis.rb +104 -0
  54. data/lib/contrast/agent/inventory/gemfile_digest_cache.rb +38 -0
  55. data/lib/contrast/agent/middleware.rb +51 -3
  56. data/lib/contrast/agent/patching/policy/after_load_patch.rb +5 -5
  57. data/lib/contrast/agent/patching/policy/after_load_patcher.rb +20 -20
  58. data/lib/contrast/agent/patching/policy/method_policy.rb +1 -1
  59. data/lib/contrast/agent/patching/policy/module_policy.rb +10 -10
  60. data/lib/contrast/agent/patching/policy/patch.rb +6 -0
  61. data/lib/contrast/agent/patching/policy/policy.rb +16 -2
  62. data/lib/contrast/agent/protect/policy/applies_command_injection_rule.rb +3 -5
  63. data/lib/contrast/agent/protect/policy/applies_deserialization_rule.rb +47 -1
  64. data/lib/contrast/agent/protect/policy/applies_path_traversal_rule.rb +4 -3
  65. data/lib/contrast/agent/protect/policy/applies_xxe_rule.rb +1 -1
  66. data/lib/contrast/agent/protect/policy/rule_applicator.rb +53 -0
  67. data/lib/contrast/agent/protect/rule/base.rb +63 -14
  68. data/lib/contrast/agent/protect/rule/cmd_injection.rb +12 -28
  69. data/lib/contrast/agent/protect/rule/default_scanner.rb +1 -4
  70. data/lib/contrast/agent/protect/rule/deserialization.rb +4 -1
  71. data/lib/contrast/agent/protect/rule/no_sqli.rb +3 -3
  72. data/lib/contrast/agent/protect/rule/no_sqli/mongo_no_sql_scanner.rb +1 -0
  73. data/lib/contrast/agent/protect/rule/sqli.rb +3 -3
  74. data/lib/contrast/agent/protect/rule/xxe.rb +32 -11
  75. data/lib/contrast/agent/protect/rule/xxe/entity_wrapper.rb +10 -6
  76. data/lib/contrast/agent/reaction_processor.rb +1 -1
  77. data/lib/contrast/agent/request.rb +34 -34
  78. data/lib/contrast/agent/request_handler.rb +1 -1
  79. data/lib/contrast/agent/response.rb +5 -5
  80. data/lib/contrast/agent/rewriter.rb +3 -3
  81. data/lib/contrast/agent/scope.rb +81 -55
  82. data/lib/contrast/agent/static_analysis.rb +15 -9
  83. data/lib/contrast/agent/tracepoint_hook.rb +1 -1
  84. data/lib/contrast/agent/version.rb +1 -1
  85. data/lib/contrast/api/communication/socket_client.rb +36 -1
  86. data/lib/contrast/api/decorators.rb +3 -0
  87. data/lib/contrast/api/decorators/address.rb +13 -14
  88. data/lib/contrast/api/decorators/application_update.rb +1 -1
  89. data/lib/contrast/api/decorators/library.rb +54 -0
  90. data/lib/contrast/api/decorators/library_usage_update.rb +31 -0
  91. data/lib/contrast/api/decorators/message.rb +1 -0
  92. data/lib/contrast/api/decorators/trace_event.rb +31 -41
  93. data/lib/contrast/api/decorators/trace_event_object.rb +11 -3
  94. data/lib/contrast/api/decorators/trace_event_signature.rb +27 -5
  95. data/lib/contrast/api/decorators/user_input.rb +2 -1
  96. data/lib/contrast/common_agent_configuration.rb +2 -1
  97. data/lib/contrast/components/agent.rb +6 -5
  98. data/lib/contrast/components/app_context.rb +39 -30
  99. data/lib/contrast/components/assess.rb +36 -0
  100. data/lib/contrast/components/config.rb +29 -37
  101. data/lib/contrast/components/contrast_service.rb +9 -9
  102. data/lib/contrast/components/interface.rb +30 -6
  103. data/lib/contrast/components/inventory.rb +6 -1
  104. data/lib/contrast/components/scope.rb +72 -6
  105. data/lib/contrast/components/settings.rb +23 -23
  106. data/lib/contrast/config/assess_configuration.rb +2 -1
  107. data/lib/contrast/config/inventory_configuration.rb +2 -2
  108. data/lib/contrast/config/service_configuration.rb +4 -2
  109. data/lib/contrast/configuration.rb +1 -1
  110. data/lib/contrast/extension/assess/array.rb +9 -6
  111. data/lib/contrast/extension/assess/erb.rb +6 -3
  112. data/lib/contrast/extension/assess/eval_trigger.rb +6 -6
  113. data/lib/contrast/extension/assess/exec_trigger.rb +0 -3
  114. data/lib/contrast/extension/assess/fiber.rb +5 -6
  115. data/lib/contrast/extension/assess/hash.rb +7 -5
  116. data/lib/contrast/extension/assess/kernel.rb +19 -22
  117. data/lib/contrast/extension/assess/marshal.rb +40 -28
  118. data/lib/contrast/extension/assess/regexp.rb +6 -11
  119. data/lib/contrast/extension/assess/string.rb +14 -13
  120. data/lib/contrast/extension/protect/kernel.rb +3 -3
  121. data/lib/contrast/framework/base_support.rb +51 -53
  122. data/lib/contrast/framework/manager.rb +6 -5
  123. data/lib/contrast/framework/rack/patch/session_cookie.rb +10 -10
  124. data/lib/contrast/framework/rack/support.rb +2 -1
  125. data/lib/contrast/framework/rails/patch/action_controller_live_buffer.rb +14 -14
  126. data/lib/contrast/framework/rails/patch/assess_configuration.rb +1 -1
  127. data/lib/contrast/framework/rails/patch/rails_application_configuration.rb +11 -11
  128. data/lib/contrast/framework/rails/patch/support.rb +1 -1
  129. data/lib/contrast/framework/rails/rewrite/action_controller_railties_helper_inherited.rb +12 -12
  130. data/lib/contrast/framework/rails/rewrite/active_record_attribute_methods_read.rb +13 -13
  131. data/lib/contrast/framework/rails/rewrite/active_record_named.rb +3 -3
  132. data/lib/contrast/framework/rails/rewrite/active_record_time_zone_inherited.rb +13 -13
  133. data/lib/contrast/framework/rails/support.rb +5 -1
  134. data/lib/contrast/framework/sinatra/patch/base.rb +11 -11
  135. data/lib/contrast/framework/sinatra/support.rb +7 -6
  136. data/lib/contrast/logger/application.rb +1 -4
  137. data/lib/contrast/logger/log.rb +7 -2
  138. data/lib/contrast/utils/duck_utils.rb +1 -1
  139. data/lib/contrast/utils/heap_dump_util.rb +1 -1
  140. data/lib/contrast/utils/invalid_configuration_util.rb +2 -5
  141. data/lib/contrast/utils/inventory_util.rb +0 -7
  142. data/lib/contrast/utils/object_share.rb +3 -3
  143. data/lib/contrast/utils/preflight_util.rb +1 -1
  144. data/lib/contrast/utils/prevent_serialization.rb +1 -1
  145. data/lib/contrast/utils/resource_loader.rb +1 -1
  146. data/lib/contrast/utils/sha256_builder.rb +2 -14
  147. data/lib/contrast/utils/string_utils.rb +1 -1
  148. data/lib/contrast/utils/tag_util.rb +9 -13
  149. data/resources/assess/policy.json +31 -12
  150. data/resources/deadzone/policy.json +156 -0
  151. data/resources/protect/policy.json +12 -0
  152. data/ruby-agent.gemspec +11 -6
  153. data/service_executables/VERSION +1 -1
  154. data/service_executables/linux/contrast-service +0 -0
  155. data/service_executables/mac/contrast-service +0 -0
  156. metadata +91 -28
  157. data/lib/contrast/utils/boolean_util.rb +0 -30
  158. data/lib/contrast/utils/gemfile_reader.rb +0 -193
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ac8fc7d0e9c127859cf7bdb149c7ac519286c4329bd81ad28db4963128e0cd63
4
- data.tar.gz: 784afc67ef269df8dfbaed392e8ad28e2e3b679113ed8679625f95033e324929
3
+ metadata.gz: 772706612c29bc99cb9861c736056218e830881daba5ea2f9a4a2e86ecaf83ab
4
+ data.tar.gz: 85d07cd9104e2b6f0c1e04e5ee4d1562ad8de62a7155dd761cdcaa554f4e68fd
5
5
  SHA512:
6
- metadata.gz: c1a5563b34a4eba33fdf8094986362f70c88bda53102899bb01ad0096588d26883b5e7ad475e41afa15b95674b8c2810cfe6546c6779b8e3b2030bf7bb1e33d6
7
- data.tar.gz: 1a1eb220719c1caf3f7153108bff4ac5132130d6879dc2b425e6dd31720e4c76bda9c27c0ac65fa00ccb846a31cb173e04dc7d3320ba3079ea9b785a62991f00
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);
@@ -23,7 +23,6 @@ require 'contrast/extension/protect'
23
23
  require 'contrast/extension/protect/kernel'
24
24
 
25
25
  require 'contrast/utils/object_share'
26
- require 'contrast/utils/boolean_util'
27
26
  require 'contrast/utils/string_utils'
28
27
  require 'contrast/utils/io_util'
29
28
  require 'contrast/utils/os'
@@ -88,18 +87,11 @@ require 'contrast/agent/assess'
88
87
  # protect rules
89
88
  require 'contrast/agent/protect/rule'
90
89
 
91
- # application libraries
92
- require 'contrast/utils/gemfile_reader'
90
+ # application libraries and technologies
91
+ require 'contrast/agent/inventory'
93
92
 
94
93
  # rack event monitoring
95
94
  require 'contrast/agent/middleware'
96
95
 
97
- # TODO: RUBY-919
98
- # Refactor to use Contrast::Framework::Manager
99
- # Contrast::Framework::Manager.before_load_patches!
100
- if defined?(::Rails)
101
- require 'contrast/framework/rails/patch/support'
102
- require 'contrast/framework/rails/patch/rails_application_configuration'
103
- Contrast::Framework::Rails::Patch::RailsApplicationConfiguration.instrument
104
- require 'contrast/agent/railtie' if ::Rails::VERSION::MAJOR.to_i >= 3
105
- end
96
+ # Install the patches we need before the application has a chance to initialize
97
+ Contrast::Agent.framework_manager.before_load_patches!
@@ -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
@@ -16,46 +18,32 @@ module Contrast
16
18
  # This class holds the data about an event in the application
17
19
  # We'll use it to build an event that TeamServer can consume if
18
20
  # the object to which this event belongs ends in a trigger.
21
+ #
22
+ # @attr_reader event_id [Integer] the atomic id of this event
23
+ # @attr_reader policy_node [Contrast::Agent::Assess::Policy::PolicyNode]
24
+ # the node that governs this event.
25
+ # @attr_reader stack_trace [Array<String>] the execution stack at the
26
+ # time the method for this event was invoked
27
+ # @attr_reader time [Integer] the time, in epoch ms, when this event was
28
+ # created
29
+ # @attr_reader thread [Integer] the object id of the thread on which this
30
+ # event was generated
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
19
37
  class ContrastEvent
20
38
  include Contrast::Utils::PreventSerialization
39
+ include Contrast::Components::Interface
40
+ access_component :analysis
21
41
 
22
- class << self
23
- def safe_args_representation args
24
- return nil unless args
25
- return Contrast::Utils::ObjectShare::EMPTY_ARRAY if args.empty?
26
-
27
- rep = []
28
- args.each do |arg|
29
- # We have to handle named args
30
- rep << if arg.is_a?(Hash)
31
- safe_arg_hash_representation(arg)
32
- else
33
- Contrast::Utils::ClassUtil.to_contrast_string(arg)
34
- end
35
- end
36
- rep
37
- end
38
-
39
- def safe_arg_hash_representation hash
40
- # since this is the named hash for arguments, only the value is
41
- # suspect here
42
- hash.transform_values { |v| Contrast::Utils::ClassUtil.to_contrast_string(v) }
43
- end
44
-
45
- # if given an object that can be duped, duplicate it. otherwise just
46
- # return the original object. swallow all exceptions from
47
- # non-duplicable things.
48
- #
49
- # we can't just check respond_to? though b/c dup exists on the
50
- # base Object class
51
- def safe_dup original
52
- return nil unless original
53
-
54
- Contrast::Agent::Assess::Tracker.duplicate(original)
55
- end
56
- end
57
-
58
- attr_reader :event_id, :parent_ids, :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
59
47
 
60
48
  # We need this to track the parent id's of events to build up a flow
61
49
  # chart of the finding
@@ -70,85 +58,38 @@ module Contrast
70
58
  end
71
59
  end
72
60
 
61
+ # @param policy_node [Contrast::Agent::Assess::Policy::PolicyNode]
62
+ # the node that governs this event.
63
+ # @param tagged [Object] the Target to which this event pertains.
64
+ # @param object [Object] the Object on which the method was invoked
65
+ # @param ret [Object] the Return of the invoked method
66
+ # @param args [Array<Object>] the Arguments with which the method
67
+ # was invoked
73
68
  def initialize policy_node, tagged, object, ret, args
74
69
  @policy_node = policy_node
75
- # 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
76
74
  # will be the first three events
77
- @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
+
78
81
  @time = Contrast::Utils::Timer.now_ms
79
82
  @thread = Thread.current.object_id
80
83
 
81
84
  # These methods rely on the above being set. Don't move them!
82
85
  @event_id = Contrast::Agent::Assess::ContrastEvent.next_atomic_id
83
- @parent_ids = find_parent_ids(policy_node, object, ret, args)
84
- snapshot(tagged, object, ret, args)
85
- end
86
-
87
- # Parent IDs are the event ids of all the sources of this event which
88
- # were tracked prior to this event occurring
89
- def find_parent_ids policy_node, object, ret, args
90
- mapped = policy_node.sources.map do |source|
91
- value_of_source(source, object, ret, args)
92
- end
93
- selected = mapped.select do |source|
94
- source && Contrast::Agent::Assess::Tracker.properties(source)&.events&.last
95
- end
96
- selected.map do |source|
97
- properties = Contrast::Agent::Assess::Tracker.properties(source)
98
- properties.events.last.event_id
99
- end
86
+ @tags = Contrast::Agent::Assess::Tracker.properties(tagged)&.tags
87
+ find_parent_events!(policy_node, object, ret, args)
88
+ snapshot!(object, ret, args)
100
89
  end
101
90
 
102
- def snapshot tagged, object, ret, args
103
- target = @policy_node.target
104
- case target
105
- # If the target is nil, this rule was violated simply by a method
106
- # being called. We'll save all the information, but nothing will be
107
- # marked up, as nothing need be tracked
108
- when nil
109
- @object = Contrast::Utils::ClassUtil.to_contrast_string(object)
110
- @args = cs__class.safe_args_representation(args)
111
- @ret = Contrast::Utils::ClassUtil.to_contrast_string(ret)
112
- # If the target is O, then we dup the O and safely represent the rest
113
- when Contrast::Utils::ObjectShare::OBJECT_KEY
114
- @object = cs__class.safe_dup(tagged)
115
- @args = cs__class.safe_args_representation(args)
116
- @ret = Contrast::Utils::ClassUtil.to_contrast_string(ret)
117
- # If the target is R, then we dup the R and safely represent the rest
118
- when Contrast::Utils::ObjectShare::RETURN_KEY
119
- @object = Contrast::Utils::ClassUtil.to_contrast_string(object)
120
- @args = cs__class.safe_args_representation(args)
121
- @ret = cs__class.safe_dup(tagged)
122
- # If the target is P*, then we need to dup things a differently. We
123
- # need to find the true target inside so that we can mark it up
124
- # later, but the other args should be represented as their safe form.
125
- else
126
- @object = Contrast::Utils::ClassUtil.to_contrast_string(object)
127
- @args = cs__class.safe_args_representation(args)
128
- @ret = Contrast::Utils::ClassUtil.to_contrast_string(ret)
129
- save_target_arg(target, tagged)
130
- end
131
- end
132
-
133
- # I know we're creating an extra string here since we replace the safe
134
- # one w/ a dup, but good enough for now. Trying not to make this too
135
- # complicated. - HM 8/8/19
136
- def save_target_arg target, tagged
137
- return if @args.cs__frozen?
138
-
139
- if target.is_a?(Integer)
140
- @args[target] = cs__class.safe_dup(tagged)
141
- return
142
- end
143
-
144
- @args.each_with_index do |search, index|
145
- next unless search.is_a?(Hash)
146
- next unless search[target]
147
-
148
- search[target] = cs__class.safe_dup(tagged)
149
- @highlight = index
150
- break
151
- end
91
+ def parent_events
92
+ @_parent_events ||= []
152
93
  end
153
94
 
154
95
  # We have to do a little work to figure out what our TS appropriate
@@ -157,25 +98,59 @@ module Contrast
157
98
  # Per TS law, each policy_node must have at least a source or a target.
158
99
  # The only type of policy_node w/o targets is a Trigger, but that may
159
100
  # change.
160
- # 2) If I have a highlight, it means that I have a P target that is
161
- # not in integer form (it was a named / keyword type for which I had
162
- # to find the index). I need to address this so that TS can process
163
- # it.
164
- # 3) I'll set the event's source and target to TS values.
165
- # 4) Return the highlight or the first source/target as the taint
166
- # 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.
167
103
  def determine_taint_target event_dtm
168
104
  if @policy_node&.targets&.any?
169
105
  event_dtm.source = @policy_node.source_string if @policy_node.source_string
170
- event_dtm.target = @highlight ? "P#{ @highlight }" : @policy_node.target_string
171
- @highlight || @policy_node.targets[0]
106
+ event_dtm.target = @policy_node.target_string
107
+ @policy_node.targets[0]
172
108
  elsif policy_node&.sources&.any?
173
- event_dtm.source = @highlight ? "P#{ @highlight }" : @policy_node.source_string
109
+ event_dtm.source = @policy_node.source_string
174
110
  event_dtm.target = @policy_node.target_string if @policy_node.target_string
175
- @highlight || @policy_node.sources[0]
111
+ @policy_node.sources[0]
112
+ end
113
+ end
114
+
115
+ # Convert this event into a DTM that TeamServer can consume
116
+ def to_dtm_event
117
+ Contrast::Api::Dtm::TraceEvent.build(self)
118
+ end
119
+
120
+ private
121
+
122
+ # Parent events are the events of all the sources of this event which
123
+ # were tracked prior to this event occurring. Depending on which, if
124
+ # any of the sources were tracked, there may be more than one parent.
125
+ #
126
+ # All events except for [Contrast::Agent::Assess::Events::SourceEvent]
127
+ # will have at least one parent.
128
+ #
129
+ # We set those events to this event's instance variables.
130
+ #
131
+ # @param policy_node [Contrast::Agent::Assess::Policy::PolicyNode]
132
+ # the node that governs this event.
133
+ # @param object [Object] the Object on which the method was invoked
134
+ # @param ret [Object] the Return of the invoked method
135
+ # @param args [Array<Object>] the Arguments with which the method
136
+ # was invoked
137
+ def find_parent_events! policy_node, object, ret, args
138
+ policy_node.sources.each do |source_marker|
139
+ source = value_of_source(source_marker, object, ret, args)
140
+ next unless source
141
+
142
+ event = Contrast::Agent::Assess::Tracker.properties(source)&.event
143
+ parent_events << event if event
176
144
  end
177
145
  end
178
146
 
147
+ # @param source [String] the marker for the source type
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
151
+ # was invoked
152
+ # @return [Object,nil] the literal value of the source indicated by the
153
+ # given marker
179
154
  def value_of_source source, object, ret, args
180
155
  case source
181
156
  when Contrast::Utils::ObjectShare::OBJECT_KEY
@@ -183,22 +158,38 @@ module Contrast
183
158
  when Contrast::Utils::ObjectShare::RETURN_KEY
184
159
  ret
185
160
  else
186
- if source.is_a?(Integer)
187
- args[source]
188
- else
189
- args.each do |search|
190
- next unless search.is_a?(Hash)
191
-
192
- s = search[source]
193
- return s if s
194
- end
195
- end
161
+ args[source]
196
162
  end
197
163
  end
198
164
 
199
- # Convert this event into a DTM that TeamServer can consume
200
- def to_dtm_event
201
- Contrast::Api::Dtm::TraceEvent.build(self)
165
+ # Everything* is mutable in Ruby. As such, to ensure we can accurately
166
+ # report the application state at the time of this method's invocation,
167
+ # we have to snapshot the given values, making safe representations of
168
+ # them for our later use. We set those safe values to this event's
169
+ # instance variables.
170
+ #
171
+ # @param object [Object] the Object on which the method was invoked
172
+ # @param ret [Object] the Return of the invoked method
173
+ # @param args [Array<Object>] the Arguments with which the method
174
+ # was invoked
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
180
+ end
181
+
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.
184
+ #
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 }
202
193
  end
203
194
  end
204
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