contrast-agent 5.1.0 → 5.2.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 (119) hide show
  1. checksums.yaml +4 -4
  2. data/ext/cs__assess_kernel/cs__assess_kernel.c +7 -4
  3. data/ext/cs__assess_module/cs__assess_module.c +7 -7
  4. data/ext/cs__common/cs__common.c +4 -0
  5. data/ext/cs__common/cs__common.h +1 -0
  6. data/ext/cs__contrast_patch/cs__contrast_patch.c +52 -27
  7. data/ext/cs__contrast_patch/cs__contrast_patch.h +2 -0
  8. data/ext/cs__scope/cs__scope.c +747 -0
  9. data/ext/cs__scope/cs__scope.h +88 -0
  10. data/ext/cs__scope/extconf.rb +5 -0
  11. data/lib/contrast/agent/assess/contrast_event.rb +20 -13
  12. data/lib/contrast/agent/assess/contrast_object.rb +4 -1
  13. data/lib/contrast/agent/assess/policy/propagation_node.rb +2 -5
  14. data/lib/contrast/agent/assess/policy/propagator/match_data.rb +2 -0
  15. data/lib/contrast/agent/assess/policy/trigger_method.rb +4 -1
  16. data/lib/contrast/agent/assess/rule/response/{autocomplete_rule.rb → auto_complete_rule.rb} +4 -3
  17. data/lib/contrast/agent/assess/rule/response/base_rule.rb +12 -79
  18. data/lib/contrast/agent/assess/rule/response/body_rule.rb +109 -0
  19. data/lib/contrast/agent/assess/rule/response/cache_control_header_rule.rb +157 -0
  20. data/lib/contrast/agent/assess/rule/response/click_jacking_header_rule.rb +26 -0
  21. data/lib/contrast/agent/assess/rule/response/csp_header_insecure_rule.rb +14 -15
  22. data/lib/contrast/agent/assess/rule/response/csp_header_missing_rule.rb +5 -25
  23. data/lib/contrast/agent/assess/rule/response/framework/rails_support.rb +29 -0
  24. data/lib/contrast/agent/assess/rule/response/header_rule.rb +70 -0
  25. data/lib/contrast/agent/assess/rule/response/hsts_header_rule.rb +12 -36
  26. data/lib/contrast/agent/assess/rule/response/parameters_pollution_rule.rb +2 -1
  27. data/lib/contrast/agent/assess/rule/response/x_content_type_header_rule.rb +26 -0
  28. data/lib/contrast/agent/assess/rule/response/x_xss_protection_header_rule.rb +36 -0
  29. data/lib/contrast/agent/middleware.rb +1 -0
  30. data/lib/contrast/agent/patching/policy/after_load_patcher.rb +1 -3
  31. data/lib/contrast/agent/patching/policy/patch.rb +2 -6
  32. data/lib/contrast/agent/patching/policy/patcher.rb +1 -1
  33. data/lib/contrast/agent/protect/input_analyzer/input_analyzer.rb +94 -0
  34. data/lib/contrast/agent/protect/rule/base.rb +28 -1
  35. data/lib/contrast/agent/protect/rule/base_service.rb +10 -1
  36. data/lib/contrast/agent/protect/rule/cmd_injection.rb +2 -0
  37. data/lib/contrast/agent/protect/rule/deserialization.rb +6 -0
  38. data/lib/contrast/agent/protect/rule/http_method_tampering.rb +5 -1
  39. data/lib/contrast/agent/protect/rule/no_sqli.rb +1 -0
  40. data/lib/contrast/agent/protect/rule/path_traversal.rb +1 -0
  41. data/lib/contrast/agent/protect/rule/sqli/sqli_input_classification.rb +124 -0
  42. data/lib/contrast/agent/protect/rule/sqli/sqli_worth_watching.rb +121 -0
  43. data/lib/contrast/agent/protect/rule/sqli.rb +33 -0
  44. data/lib/contrast/agent/protect/rule/xxe.rb +4 -0
  45. data/lib/contrast/agent/reporting/input_analysis/input_analysis.rb +44 -0
  46. data/lib/contrast/agent/reporting/input_analysis/input_analysis_result.rb +115 -0
  47. data/lib/contrast/agent/reporting/input_analysis/input_type.rb +44 -0
  48. data/lib/contrast/agent/reporting/input_analysis/score_level.rb +21 -0
  49. data/lib/contrast/agent/reporting/report.rb +1 -0
  50. data/lib/contrast/agent/reporting/reporter.rb +8 -1
  51. data/lib/contrast/agent/reporting/reporting_events/finding.rb +69 -36
  52. data/lib/contrast/agent/reporting/reporting_events/finding_event.rb +88 -59
  53. data/lib/contrast/agent/reporting/reporting_events/{finding_object.rb → finding_event_object.rb} +24 -20
  54. data/lib/contrast/agent/reporting/reporting_events/finding_event_parent_object.rb +39 -0
  55. data/lib/contrast/agent/reporting/reporting_events/finding_event_property.rb +40 -0
  56. data/lib/contrast/agent/reporting/reporting_events/{finding_signature.rb → finding_event_signature.rb} +29 -24
  57. data/lib/contrast/agent/reporting/reporting_events/finding_event_source.rb +12 -8
  58. data/lib/contrast/agent/reporting/reporting_events/{finding_stack.rb → finding_event_stack.rb} +23 -19
  59. data/lib/contrast/agent/reporting/reporting_events/{finding_taint_range.rb → finding_event_taint_range.rb} +17 -15
  60. data/lib/contrast/agent/reporting/reporting_events/finding_request.rb +26 -53
  61. data/lib/contrast/agent/reporting/reporting_events/poll.rb +29 -0
  62. data/lib/contrast/agent/reporting/reporting_events/reporting_event.rb +5 -4
  63. data/lib/contrast/agent/reporting/reporting_events/route_discovery.rb +1 -0
  64. data/lib/contrast/agent/reporting/reporting_events/server_activity.rb +1 -1
  65. data/lib/contrast/agent/reporting/reporting_utilities/audit.rb +10 -3
  66. data/lib/contrast/agent/reporting/reporting_utilities/endpoints.rb +0 -1
  67. data/lib/contrast/agent/reporting/reporting_utilities/reporter_client.rb +1 -0
  68. data/lib/contrast/agent/reporting/reporting_utilities/reporter_client_utils.rb +28 -20
  69. data/lib/contrast/agent/reporting/reporting_utilities/response_handler.rb +1 -1
  70. data/lib/contrast/agent/reporting/reporting_utilities/response_handler_utils.rb +13 -1
  71. data/lib/contrast/agent/request_context.rb +6 -1
  72. data/lib/contrast/agent/request_context_extend.rb +85 -21
  73. data/lib/contrast/agent/scope.rb +102 -107
  74. data/lib/contrast/agent/service_heartbeat.rb +45 -2
  75. data/lib/contrast/agent/version.rb +1 -1
  76. data/lib/contrast/api/decorators/bot_blocker.rb +37 -0
  77. data/lib/contrast/api/decorators/ip_denylist.rb +37 -0
  78. data/lib/contrast/api/decorators/rasp_rule_sample.rb +29 -0
  79. data/lib/contrast/api/decorators/user_input.rb +11 -1
  80. data/lib/contrast/api/decorators/virtual_patch.rb +34 -0
  81. data/lib/contrast/components/logger.rb +5 -0
  82. data/lib/contrast/components/protect.rb +4 -2
  83. data/lib/contrast/components/scope.rb +98 -91
  84. data/lib/contrast/config/agent_configuration.rb +58 -12
  85. data/lib/contrast/config/api_configuration.rb +100 -12
  86. data/lib/contrast/config/api_proxy_configuration.rb +55 -3
  87. data/lib/contrast/config/application_configuration.rb +114 -15
  88. data/lib/contrast/config/assess_configuration.rb +106 -12
  89. data/lib/contrast/config/assess_rules_configuration.rb +44 -3
  90. data/lib/contrast/config/base_configuration.rb +1 -0
  91. data/lib/contrast/config/certification_configuration.rb +74 -3
  92. data/lib/contrast/config/exception_configuration.rb +61 -3
  93. data/lib/contrast/config/heap_dump_configuration.rb +101 -17
  94. data/lib/contrast/config/inventory_configuration.rb +64 -3
  95. data/lib/contrast/config/logger_configuration.rb +46 -3
  96. data/lib/contrast/config/protect_rule_configuration.rb +36 -9
  97. data/lib/contrast/config/protect_rules_configuration.rb +120 -17
  98. data/lib/contrast/config/request_audit_configuration.rb +68 -3
  99. data/lib/contrast/config/ruby_configuration.rb +96 -22
  100. data/lib/contrast/config/sampling_configuration.rb +76 -10
  101. data/lib/contrast/config/server_configuration.rb +56 -11
  102. data/lib/contrast/configuration.rb +6 -3
  103. data/lib/contrast/logger/cef_log.rb +151 -0
  104. data/lib/contrast/utils/hash_digest.rb +14 -6
  105. data/lib/contrast/utils/log_utils.rb +114 -0
  106. data/lib/contrast/utils/middleware_utils.rb +6 -7
  107. data/lib/contrast/utils/net_http_base.rb +12 -9
  108. data/lib/contrast/utils/patching/policy/patch_utils.rb +0 -4
  109. data/lib/contrast.rb +4 -3
  110. data/ruby-agent.gemspec +1 -1
  111. data/service_executables/VERSION +1 -1
  112. data/service_executables/linux/contrast-service +0 -0
  113. data/service_executables/mac/contrast-service +0 -0
  114. metadata +41 -21
  115. data/lib/contrast/agent/assess/rule/response/cachecontrol_rule.rb +0 -184
  116. data/lib/contrast/agent/assess/rule/response/clickjacking_rule.rb +0 -66
  117. data/lib/contrast/agent/assess/rule/response/x_content_type_rule.rb +0 -52
  118. data/lib/contrast/agent/assess/rule/response/x_xss_protection_rule.rb +0 -53
  119. data/lib/contrast/extension/kernel.rb +0 -54
@@ -16,28 +16,42 @@ module Contrast
16
16
  # relay this information in the Finding/Trace messages. These findings are used by TeamServer to construct the
17
17
  # vulnerability information for the assess feature. They represent those parts of the application, either through
18
18
  # configuration, method invocation, or dataflow, which are determined to be insecure.
19
- #
20
- # @attr_accessor events [Array<Contrast::Agent::Assess::ContrastEvent>] if a dataflow based finding, the
21
- # representation of those method calls which constitute a dangerous code path.
22
- # @attr_accessor properties [Hash<String,String>] a set of values that TeamServer can use to provide more context
23
- # to the user when rendering the finding. For some findings, a specific set of keys and values are required.
24
- # @attr_accessor request [Contrast::Agent::Request, nil] the request, if any, in which this finding occurred
25
- # @attr_accessor hash_code [String] the unique identifier of this finding.
26
- # @attr_reader rule_id [String] the name of the rule violated; must match those in TeamServer.
27
19
  class Finding < Contrast::Agent::Reporting::ReportingEvent
28
- attr_reader :rule_id, :routes, :events, :properties, :request
20
+ include Contrast::Components::Logger::InstanceMethods
21
+
22
+ # @return [Integer] the time, in ms, that this object was initialized
23
+ attr_reader :created
24
+ # @return [String] the ID of the rule associated with this finding
25
+ attr_reader :rule_id
26
+ # @return [Array<Contrast::Agent::Reporting::RouteDiscovery>] the routes associated with this finding, if the
27
+ # finding is request based.
28
+ attr_reader :routes
29
+ # @return [Array<Contrast::Agent::Reporting::FindingEvent>] the events associated with this finding, if the
30
+ # finding is event (dataflow) based.
31
+ attr_reader :events
32
+ # @return [String] the evidence associated with this finding, if the finding is event based. deprecated in
33
+ # favor of properties
34
+ # attr_reader :evidence
35
+ # @return [Hash<String,String>] properties that prove the violation of the rule for this finding
36
+ attr_reader :properties
37
+ # @return [Contrast::Agent::Reporting::FindingRequest] the request associated with this finding, if the finding
38
+ # is request based
39
+ attr_reader :request
40
+ # @return [String] the uniquely identifying hash of this finding
29
41
  attr_accessor :hash_code
30
42
 
43
+ CONFIGURATION_RULES = %w[rails-http-only-disabled secure-flag-missing session-timeout].cs__freeze
44
+ HARDCODED_RULES = %w[hardcoded-key hardcoded-password].cs__freeze
31
45
  PROPERTIES_RULES = %w[
32
46
  autocomplete-missing
33
47
  cache-controls-missing
34
48
  clickjacking-control-missing
35
- xcontenttype-header-missing
36
- hsts-header-missing
37
- xxssprotection-header-disabled
38
49
  csp-header-missing
39
50
  csp-header-insecure
51
+ hsts-header-missing
40
52
  parameter-pollution
53
+ xcontenttype-header-missing
54
+ xxssprotection-header-disabled
41
55
  ].cs__freeze
42
56
 
43
57
  class << self
@@ -57,6 +71,7 @@ module Contrast
57
71
  @routes = []
58
72
  @rule_id = Contrast::Utils::StringUtils.truncate(rule_id)
59
73
  @properties = {}
74
+ @created = Contrast::Utils::Timer.now_ms
60
75
  super()
61
76
  end
62
77
 
@@ -76,15 +91,16 @@ module Contrast
76
91
  # @param request [Contrast::Agent::Request]
77
92
  # @param args [Array<Object>] the Arguments with which the method was invoked
78
93
  def attach_data trigger_node, source, object, ret, request, *args
94
+ event_messages = Contrast::Agent::Reporting::FindingEvent.from_source(source)
95
+ events.concat(event_messages) if event_messages&.any?
79
96
  event_data = Contrast::Agent::Assess::Events::EventData.new(trigger_node, source, object, ret, args)
80
- event_msgs = Contrast::Agent::Reporting::FindingEvent.from_source(source)
81
- events.concat(event_msgs) if event_msgs
82
- if request
83
- @request = Contrast::Agent::Reporting::FindingRequest.convert(request)
84
- @routes << Contrast::Agent::Reporting::RouteDiscovery.convert(request&.route)
85
- end
86
- events << Contrast::Agent::Assess::ContrastEvent.new(event_data)
97
+ contrast_event = Contrast::Agent::Assess::ContrastEvent.new(event_data)
98
+ events << Contrast::Agent::Reporting::FindingEvent.convert(contrast_event)
87
99
  attach_properties
100
+ return unless request
101
+
102
+ @request = Contrast::Agent::Reporting::FindingRequest.convert(request)
103
+ @routes << Contrast::Agent::Reporting::RouteDiscovery.convert(request.route)
88
104
  end
89
105
 
90
106
  # Attach the data from a Contrast::Api::Dtm::Finding required for property based findings generated during
@@ -92,7 +108,6 @@ module Contrast
92
108
  #
93
109
  # @param finding_dtm [Contrast::Api::Dtm::Finding]
94
110
  def attach_property_data finding_dtm
95
- @created = Contrast::Utils::Timer.now_ms
96
111
  @hash_code = finding_dtm.hash_code
97
112
  @rule_id = finding_dtm.rule_id
98
113
  finding_dtm.properties.each_pair do |key, value|
@@ -101,7 +116,7 @@ module Contrast
101
116
  finding_dtm.routes.each do |route|
102
117
  @routes << Contrast::Agent::Reporting::RouteDiscovery.convert(route)
103
118
  end
104
- request = Contrast::Agent::REQUEST_TRACKER.current&.activity&.http_request
119
+ request = Contrast::Agent::REQUEST_TRACKER.current&.request
105
120
  @request = Contrast::Agent::Reporting::FindingRequest.convert(request) if request
106
121
  end
107
122
 
@@ -113,20 +128,20 @@ module Contrast
113
128
  def to_controlled_hash
114
129
  validate
115
130
  hsh = {
116
- created: @created,
117
- hash: hash_code,
118
- ruleId: @rule_id,
119
- session_id: @agent_session_id_value,
131
+ created: created,
132
+ hash: hash_code.to_s,
133
+ ruleId: rule_id,
134
+ session_id: @agent_session_id_value.to_s,
120
135
  version: 4
121
136
  }
122
- hsh[:events] = events if event_based?
123
- hsh[:evidence] = @evidence unless event_based? || property_based?
137
+ hsh[:events] = events.map(&:to_controlled_hash) if event_based?
138
+ # hsh[:evidence] = evidence unless event_based? || property_based?
124
139
  hsh[:properties] = properties if property_based?
125
140
  hsh[:tags] = Contrast::ASSESS.tags if Contrast::ASSESS.tags
126
- if request_based?
127
- hsh[:request] = request
128
- hsh[:routes] = routes
129
- end
141
+ return hsh unless request_based?
142
+
143
+ hsh[:request] = request.to_controlled_hash
144
+ hsh[:routes] = routes.map(&:to_controlled_hash)
130
145
  hsh
131
146
  end
132
147
 
@@ -135,14 +150,14 @@ module Contrast
135
150
  raise(ArgumentError, "#{ self } did not have a proper rule. Unable to continue.") unless @rule_id
136
151
 
137
152
  if event_based? && events.empty?
138
- raise(ArgumentError, "#{ self } did not have proper events. Unable to continue.")
153
+ raise(ArgumentError, "#{ self } did not have proper events for #{ @rule_id }. Unable to continue.")
139
154
  end
140
155
  if property_based? && properties.empty?
141
- raise(ArgumentError, "#{ self } did not have proper properties. Unable to continue.")
156
+ raise(ArgumentError, "#{ self } did not have proper properties for #{ @rule_id }. Unable to continue.")
142
157
  end
143
158
  return unless request_based? && request.nil?
144
159
 
145
- raise(ArgumentError, "#{ self } did not have a proper request. Unable to continue.")
160
+ raise(ArgumentError, "#{ self } did not have a proper request for #{ @rule_id }. Unable to continue.")
146
161
  end
147
162
 
148
163
  private
@@ -169,7 +184,7 @@ module Contrast
169
184
  #
170
185
  # @return [Boolean]
171
186
  def event_based?
172
- !property_based?
187
+ !property_based? && !config_based?
173
188
  end
174
189
 
175
190
  # Rules which are property based must have a property to be sent to TeamServer. Eventually, each rule may own
@@ -181,13 +196,31 @@ module Contrast
181
196
  PROPERTIES_RULES.include?(@rule_id)
182
197
  end
183
198
 
199
+ # Rules which are config based must have a configuration to be sent to TeamServer. Eventually, each rule may own
200
+ # its own validation, as the properties each needs are different; however, that's a refactor for after we've
201
+ # translated all rules from the Service and have had time to build proper child structure.
202
+ #
203
+ # @return [Boolean]
204
+ def config_based?
205
+ CONFIGURATION_RULES.include?(@rule_id)
206
+ end
207
+
208
+ # Rules which are hardcode based send properties to TeamServer. Eventually, each rule may own its own
209
+ # validation, as the properties each needs are different; however, that's a refactor for after we've
210
+ # translated all rules from the Service and have had time to build proper child structure.
211
+ #
212
+ # @return [Boolean]
213
+ def hardcoded?
214
+ HARDCODED_RULES.include?(@rule_id)
215
+ end
216
+
184
217
  # Rules which are request based must have a request to be sent to TeamServer. Most rules fit this category, so
185
218
  # we'll default to true for now. Eventually, this will be split out for those rules, like Hardcoded, which do
186
219
  # not need requests.
187
220
  #
188
221
  # @return [Boolean]
189
222
  def request_based?
190
- true
223
+ !config_based? && !hardcoded?
191
224
  end
192
225
  end
193
226
  end
@@ -1,44 +1,63 @@
1
1
  # Copyright (c) 2022 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
2
2
  # frozen_string_literal: true
3
3
 
4
+ require 'contrast/agent/reporting/reporting_events/finding_event_object'
5
+ require 'contrast/agent/reporting/reporting_events/finding_event_parent_object'
6
+ require 'contrast/agent/reporting/reporting_events/finding_event_property'
7
+ require 'contrast/agent/reporting/reporting_events/finding_event_signature'
4
8
  require 'contrast/agent/reporting/reporting_events/finding_event_source'
5
- require 'contrast/agent/reporting/reporting_events/finding_object'
6
- require 'contrast/agent/reporting/reporting_events/finding_stack'
7
- require 'contrast/agent/reporting/reporting_events/finding_taint_range'
9
+ require 'contrast/agent/reporting/reporting_events/finding_event_stack'
10
+ require 'contrast/agent/reporting/reporting_events/finding_event_taint_range'
8
11
 
9
12
  module Contrast
10
13
  module Agent
11
14
  module Reporting
12
- # This is the new Finding class which will include all the needed information for the new reporting system to
13
- # relay this information in the Finding/Trace messages. These findings are used by TeamServer to construct the
14
- # vulnerability information for the assess feature. They represent those parts of the application, either through
15
- # configuration, method invocation, or dataflow, which are determined to be insecure.
16
- #
17
- # @attr_reader action [String] what the event did; CREATION, A2O, A2P, A2A, A2R, O2A, O2O, O2P, O2R, P2A, P2O,
18
- # P2P, P2R, TAG, TRIGGER.
19
- # @attr_reader args [Array<Contrast::Agent::Reporting::FindingObject>] the arguments passed to the method.
20
- # @attr_reader code [nil] unused.
21
- # @attr_reader event_id [Integer] the id of this event.
22
- # @attr_reader event_sources [Array<Contrast::Agent::Reporting::FindingEventSource>] the source of taint
23
- # @attr_reader field_name [nil] unused.
24
- # @attr_reader object [Contrast::Agent::Reporting::FindingEventSource] the object this method was invoked on.
25
- # @attr_reader parent_object_ids [Array<Integer>]
26
- # @attr_reader properties [Hash<String,String]
27
- # @attr_reader ret [Contrast::Agent::Reporting::FindingObject] the return of the method.
28
- # @attr_reader signature [Contrast::Agent::Reporting::FindingSignature] the signature of the method.
29
- # @attr_reader source [String] the source of the taint from the method; ^(O|R|P\d+)$
30
- # @attr_reader stack [Array<Contrast::Agent::Reporting::FindingStack>]
31
- # @attr_reader tags [Array<String>] description of what's happened to the data
32
- # @attr_reader taint_ranges [Array<Contrast::Agent::Reporting::FindingTaintRange>] the tags and spans of the
33
- # source that are tracked
34
- # @attr_reader target [String] the target of the taint from the method; ^(O|R|P\d+)$
35
- # @attr_reader thread [String] the id of the thread on which the method was invoked
36
- # @attr_reader time [Integer] the time, in ms, when the event was generated
37
- # @attr_reader type [String] the type of event; METHOD, PROPAGATION, TAG
15
+ # This is the new FindingEvent class which will include all the needed information for the new reporting system
16
+ # to relay this information in the Finding/Trace messages. These FindingEvents are used by TeamServer to
17
+ # construct the vulnerability information for the assess feature. They represent the operation the application
18
+ # underwent that transformed data during the dataflow.
38
19
  class FindingEvent
39
- attr_reader :action, :args, :code, :event_id, :event_sources, :field_name, :object, :parent_object_ids,
40
- :properties, :ret, :signature, :source, :stack, :tags, :taint_ranges, :target, :thread, :time,
41
- :type
20
+ # @return [Symbol] what the event did; CREATION, A2O, A2P, A2A, A2R, O2A, O2O, O2P, O2R, P2A, P2O, P2P, P2R,
21
+ # TAG, TRIGGER.
22
+ attr_reader :action
23
+ # @return [Array<Contrast::Agent::Reporting::FindingEventObject>] the arguments passed to the method.
24
+ attr_reader :args
25
+ # @return [nil] unused.
26
+ attr_reader :code
27
+ # @return [Integer] the id of this event.
28
+ attr_reader :event_id
29
+ # @return [Array<Contrast::Agent::Reporting::FindingEventSource>] the source of taint
30
+ attr_reader :event_sources
31
+ # @return [nil] unused.
32
+ attr_reader :field_name
33
+ # @return [Contrast::Agent::Reporting::FindingEventObject] the object this method was invoked on.
34
+ attr_reader :object
35
+ # @@return [Array<Contrast::Agent::Reporting::FindingEventParentObject>] the ids of all the events directly
36
+ # preceding this
37
+ attr_reader :parent_object_ids
38
+ # @return [Array<Contrast::Agent::Reporting::FindingEventProperty>]
39
+ attr_reader :properties
40
+ # @return [Contrast::Agent::Reporting::FindingEventObject] the return of the method.
41
+ attr_reader :ret
42
+ # @return [Contrast::Agent::Reporting::FindingEventSignature] the signature of the method.
43
+ attr_reader :signature
44
+ # @return [String] the source of the taint from the method; ^(O|R|P\d+)$
45
+ attr_reader :source
46
+ # @return [Array<Contrast::Agent::Reporting::FindingEventStack>]
47
+ attr_reader :stack
48
+ # @return [String] comma separated list of descriptions of what's happened to the data
49
+ attr_reader :tags
50
+ # @return [Array<Contrast::Agent::Reporting::FindingEventTaintRange>] the tags and spans of the source that are
51
+ # tracked
52
+ attr_reader :taint_ranges
53
+ # @return [String] the target of the taint from the method; ^(O|R|P\d+)$
54
+ attr_reader :target
55
+ # @return [String] the id of the thread on which the method was invoked
56
+ attr_reader :thread
57
+ # @return [Integer] the time, in ms, when the event was generated
58
+ attr_reader :time
59
+ # @return [String] the type of event; METHOD, PROPAGATION, TAG
60
+ attr_reader :type
42
61
 
43
62
  class << self
44
63
  # Find all the events leading up to the given source and return an array of FindingEvents
@@ -48,7 +67,7 @@ module Contrast
48
67
  def from_source source
49
68
  return unless source && (props = Contrast::Agent::Assess::Tracker.properties(source))
50
69
 
51
- build_events([], props.event) if props&.event
70
+ build_events([], props.event) if props.event
52
71
  end
53
72
 
54
73
  # @param event [Contrast::Agent::Assess::ContrastEvent]
@@ -71,7 +90,7 @@ module Contrast
71
90
  return unless event
72
91
 
73
92
  event.parent_events&.each do |parent_event|
74
- build_events(finding, parent_event)
93
+ build_events(events, parent_event)
75
94
  end
76
95
  events << convert(event)
77
96
  events
@@ -85,11 +104,11 @@ module Contrast
85
104
  def attach_data event
86
105
  @event_id = event.event_id
87
106
  @time = event.time.to_i
88
- @thread = event.thread
107
+ @thread = event.thread.to_s
89
108
  display_params!(event)
90
109
  dataflow!(event)
91
110
  event_sources!(event)
92
- @signature = Contrast::Agent::Reporting::FindingSignature.convert(event)
111
+ @signature = Contrast::Agent::Reporting::FindingEventSignature.convert(event)
93
112
  stack!(event)
94
113
  parent_ids!(event)
95
114
  properties!(event)
@@ -100,25 +119,25 @@ module Contrast
100
119
  #
101
120
  # @return [Hash]
102
121
  # @raise [ArgumentError]
103
- def to_controlled_hash
122
+ def to_controlled_hash # rubocop:disable Metrics/AbcSize
104
123
  validate
105
124
  {
106
125
  action: action,
107
- args: args,
126
+ args: args.map(&:to_controlled_hash),
108
127
  # code: code, # Unused by our agent
109
- eventId: event_id,
110
- eventSources: event_sources,
128
+ objectId: event_id,
129
+ eventSources: event_sources.map(&:to_controlled_hash),
111
130
  # fieldName: field_name, # Unused by our agent
112
- object: object,
113
- parentObjectIds: parent_object_ids,
114
- properties: properties,
115
- ret: ret,
116
- signature: signature,
117
- source: source,
118
- stack: stack,
119
- tags: tags,
120
- taintRanges: taint_ranges,
121
- target: target,
131
+ object: object.to_controlled_hash,
132
+ parentObjectIds: parent_object_ids.map(&:to_controlled_hash),
133
+ properties: properties.map(&:to_controlled_hash),
134
+ ret: ret&.to_controlled_hash,
135
+ signature: signature.to_controlled_hash,
136
+ source: source || '',
137
+ stack: stack.map(&:to_controlled_hash),
138
+ tags: tags.join(','),
139
+ taintRanges: taint_ranges.map(&:to_controlled_hash),
140
+ target: target || '',
122
141
  thread: thread,
123
142
  time: time,
124
143
  type: type
@@ -139,7 +158,14 @@ module Contrast
139
158
  # @param event [Contrast::Agent::Assess::ContrastEvent]
140
159
  def display_params! event
141
160
  @action = event.policy_node.build_action
142
- @type = event.policy_node.node_type
161
+ @type = case event.policy_node.node_type
162
+ when :TYPE_TAG
163
+ 'TAG'
164
+ when :TYPE_PROPAGATION
165
+ 'PROPAGATION'
166
+ else # :TYPE_METHOD
167
+ 'METHOD'
168
+ end
143
169
  end
144
170
 
145
171
  # Build the dataflow components of this FindingEvent.
@@ -148,9 +174,9 @@ module Contrast
148
174
  def dataflow! event
149
175
  taint_target = taint_target!(event)
150
176
  truncate_obj = Contrast::Utils::ObjectShare::OBJECT_KEY != taint_target
151
- @object = Contrast::Agent::Reporting::FindingObject.convert(event.object, truncate_obj)
177
+ @object = Contrast::Agent::Reporting::FindingEventObject.convert(event.object, truncate_obj)
152
178
  truncate_ret = Contrast::Utils::ObjectShare::RETURN_KEY != taint_target
153
- @ret = Contrast::Agent::Reporting::FindingObject.convert(event.ret, truncate_ret)
179
+ @ret = Contrast::Agent::Reporting::FindingEventObject.convert(event.ret, truncate_ret)
154
180
  event_args!(event, taint_target)
155
181
  taint_ranges!(event)
156
182
  end
@@ -164,7 +190,7 @@ module Contrast
164
190
  @args = []
165
191
  idx = 0
166
192
  while idx < event.args.length
167
- @args << Contrast::Agent::Reporting::FindingObject.convert(event.args[idx], taint_target != idx)
193
+ @args << Contrast::Agent::Reporting::FindingEventObject.convert(event.args[idx], taint_target != idx)
168
194
  idx += 1
169
195
  end
170
196
  end
@@ -176,7 +202,8 @@ module Contrast
176
202
  @event_sources = []
177
203
  return unless event.cs__is_a?(Contrast::Agent::Assess::Events::SourceEvent)
178
204
 
179
- event_sources << Contrast::Agent::Reporting::FindingEventSource.convert(event)
205
+ source = Contrast::Agent::Reporting::FindingEventSource.convert(event)
206
+ event_sources << source if source
180
207
  end
181
208
 
182
209
  # Convert the parent id's of the given ContrastEvent to the reportable form for this FindingEvent.
@@ -185,7 +212,7 @@ module Contrast
185
212
  def parent_ids! event
186
213
  @parent_object_ids = []
187
214
  event.parent_events&.each do |parent_event|
188
- parent_object_ids << parent_event.event_id.to_i
215
+ parent_object_ids << Contrast::Agent::Reporting::FindingEventParentObject.new(parent_event.event_id.to_i)
189
216
  end
190
217
  end
191
218
 
@@ -194,7 +221,7 @@ module Contrast
194
221
  #
195
222
  # @param _event [Contrast::Agent::Assess::ContrastEvent]
196
223
  def properties! _event
197
- @properties = {}
224
+ @properties = []
198
225
  end
199
226
 
200
227
  # Convert the stack of the given ContrastEvent to the reportable form for this FindingEvent.
@@ -203,7 +230,7 @@ module Contrast
203
230
  def stack! event
204
231
  @stack = []
205
232
  event.stack_trace.each do |stack_event|
206
- if (report = Contrast::Agent::Reporting::FindingStack.convert(stack_event))
233
+ if (report = Contrast::Agent::Reporting::FindingEventStack.convert(stack_event))
207
234
  stack << report
208
235
  end
209
236
  end
@@ -217,7 +244,9 @@ module Contrast
217
244
  @taint_ranges = []
218
245
  event&.tags&.each_pair do |tag_key, tag_ranges|
219
246
  tags << tag_key
220
- tag_ranges.each { |range| taint_ranges << Contrast::Agent::Reporting::FindingTaintRange.convert(range) }
247
+ tag_ranges.each do |range|
248
+ taint_ranges << Contrast::Agent::Reporting::FindingEventTaintRange.convert(range)
249
+ end
221
250
  end
222
251
  end
223
252
 
@@ -2,20 +2,22 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require 'base64'
5
+ require 'contrast/agent/assess/contrast_object'
5
6
 
6
7
  module Contrast
7
8
  module Agent
8
9
  module Reporting
9
- # This is the new FindingObject class which will include all the needed information for the new reporting system
10
- # to relay this information in the Finding/Trace messages. These FindingObjects are used by TeamServer to
11
- # construct the vulnerability information for the assess feature. They represent those parts of the objects that
12
- # were acted on in a Dataflow Finding.
13
- #
14
- # @attr_reader hash [String] the id of the Object this represents.
15
- # @attr_reader tracked [Boolean] if the Object this represents is tracked or not.
16
- # @attr_reader value [String] the base64 of the human readable representation of the Object this represents.
17
- class FindingObject
18
- attr_reader :hash, :tracked, :value
10
+ # This is the new FindingEventObject class which will include all the needed information for the new reporting
11
+ # system to relay this information in the Finding/Trace messages. These FindingEventObjects are used by
12
+ # TeamServer to construct the vulnerability information for the assess feature. They represent those parts of the
13
+ # objects that were acted on in a Dataflow Finding.
14
+ class FindingEventObject
15
+ # @return [Integer] the id of the Object this represents.
16
+ attr_reader :hash
17
+ # @return [Boolean] if the Object is tracked or not
18
+ attr_reader :tracked
19
+ # @return [String] the base64 of the human readable representation of the Object this represents.
20
+ attr_reader :value
19
21
 
20
22
  # We'll truncate any object that isn't important to the taint ranges of this event, so that we don't murder
21
23
  # TeamServer by, for instance, hypothetically sending the entire rendered HTML page >_> <_< >_>
@@ -25,8 +27,8 @@ module Contrast
25
27
 
26
28
  class << self
27
29
  # @param object [Contrast::Agent::Assess::ContrastObject] the object to translate
28
- # @param truncate [Boolean] if the value of this FindingObject should be truncated or not
29
- # @return [Contrast::Agent::Reporting::FindingObject]
30
+ # @param truncate [Boolean] if the value of this FindingEventObject should be truncated or not
31
+ # @return [Contrast::Agent::Reporting::FindingEventObject]
30
32
  def convert object, truncate
31
33
  report = new
32
34
  report.attach_data(object, truncate)
@@ -35,13 +37,13 @@ module Contrast
35
37
  end
36
38
 
37
39
  # Parse the data from a Contrast::Agent::Assess::ContrastObject to attach what is required for reporting to
38
- # TeamServer to this Contrast::Agent::Reporting::FindingObject
40
+ # TeamServer to this Contrast::Agent::Reporting::FindingEventObject
39
41
  #
40
- # @param object [Contrast::Agent::Assess::ContrastObject]
42
+ # @param object [Contrast::Agent::Assess::ContrastObject, nil]
41
43
  def attach_data object, truncate
42
- @hash = object.__id__
43
- @tracked = object.tracked?
44
- @value = reportable_value(object.object, truncate)
44
+ @hash = object&.object.__id__
45
+ @tracked = !!object&.tracked?
46
+ @value = reportable_value(object&.object, truncate)
45
47
  end
46
48
 
47
49
  # Convert the instance variables on the class, and other information, into the identifiers required for
@@ -59,9 +61,9 @@ module Contrast
59
61
  end
60
62
 
61
63
  def validate
62
- raise(ArgumentError, "#{ self } did not have a proper hash. Unable to continue.") unless hash && !hash.empty?
64
+ raise(ArgumentError, "#{ self } did not have a proper hash. Unable to continue.") unless hash
63
65
  raise(ArgumentError, "#{ self } did not have a proper tracked. Unable to continue.") if tracked.nil?
64
- return unless value && !value.empty?
66
+ return if value
65
67
 
66
68
  raise(ArgumentError, "#{ self } did not have a proper value. Unable to continue.")
67
69
  end
@@ -71,10 +73,12 @@ module Contrast
71
73
  # Parse, truncate, and translate the given value to be reported to TeamServer. The field is expected to be
72
74
  # base64 encoded.
73
75
  #
74
- # @param value [String] the contrast_string of the object this represents.
76
+ # @param value [String, nil] the contrast_string of the object this represents.
75
77
  # @param truncate [Boolean] if the string should be truncated or not.
76
78
  # @return [String]
77
79
  def reportable_value value, truncate
80
+ return Contrast::Utils::ObjectShare::NIL_STRING unless value
81
+
78
82
  if truncate && value.length > TRUNCATION_LENGTH
79
83
  tmp = []
80
84
  tmp << value[0, UNTRUNCATED_PORTION_LENGTH]
@@ -0,0 +1,39 @@
1
+ # Copyright (c) 2022 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
2
+ # frozen_string_literal: true
3
+
4
+ require 'base64'
5
+
6
+ module Contrast
7
+ module Agent
8
+ module Reporting
9
+ # This is the new FindingEventParentObject class which will include all the needed information for the new
10
+ # reporting system to relay this information in the Finding/Trace messages. These FindingEventParentObject are
11
+ # used by TeamServer to relate this event to those that came previously. They represent the events that directly
12
+ # preceding the FindingEvent generated.
13
+ class FindingEventParentObject
14
+ # @return [Integer] the Id of the parent event
15
+ attr_reader :id
16
+
17
+ def initialize id
18
+ @id = id
19
+ end
20
+
21
+ # Convert the instance variables on the class, and other information, into the identifiers required for
22
+ # TeamServer to process the JSON form of this message.
23
+ #
24
+ # @return [Hash]
25
+ # @raise [ArgumentError]
26
+ def to_controlled_hash
27
+ validate
28
+ {
29
+ id: id
30
+ }
31
+ end
32
+
33
+ def validate
34
+ raise(ArgumentError, "#{ self } did not have a proper id. Unable to continue.") unless id
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,40 @@
1
+ # Copyright (c) 2022 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
2
+ # frozen_string_literal: true
3
+
4
+ module Contrast
5
+ module Agent
6
+ module Reporting
7
+ # This is the new FindingEventProperty class which will include all the needed information for the new reporting
8
+ # system to relay this information in the Finding/Trace messages. Events have properties on them which are held
9
+ # as an array of key-value pairs.
10
+ class FindingEventProperty
11
+ # @return [String] the key of the property
12
+ attr_reader :key
13
+ # @return [String] the value of the source
14
+ attr_reader :value
15
+
16
+ def initialize key, value
17
+ @key = key
18
+ @value = value
19
+ end
20
+
21
+ # Convert the instance variables on the class, and other information, into the identifiers required for
22
+ # TeamServer to process the JSON form of this message.
23
+ #
24
+ # @return [Hash]
25
+ # @raise [ArgumentError]
26
+ def to_controlled_hash
27
+ validate
28
+ {
29
+ key: key,
30
+ value: value
31
+ }
32
+ end
33
+
34
+ def validate
35
+ raise(ArgumentError, "#{ self } did not have a proper key. Unable to continue.") unless key && !key.empty?
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end