contrast-agent 4.13.1 → 4.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.simplecov +1 -0
- data/lib/contrast/agent/assess/policy/policy_node.rb +6 -6
- data/lib/contrast/agent/assess/policy/policy_scanner.rb +5 -0
- data/lib/contrast/agent/assess/policy/propagator/center.rb +1 -1
- data/lib/contrast/agent/assess/policy/propagator/substitution.rb +2 -154
- data/lib/contrast/agent/assess/policy/trigger_method.rb +44 -7
- data/lib/contrast/agent/assess/policy/trigger_node.rb +14 -6
- data/lib/contrast/agent/assess/policy/trigger_validation/xss_validator.rb +1 -1
- data/lib/contrast/agent/assess/property/tagged.rb +51 -57
- data/lib/contrast/agent/assess/rule/provider/hardcoded_value_rule.rb +40 -6
- data/lib/contrast/agent/metric_telemetry_event.rb +2 -2
- data/lib/contrast/agent/middleware.rb +5 -75
- data/lib/contrast/agent/patching/policy/method_policy.rb +3 -89
- data/lib/contrast/agent/patching/policy/method_policy_extend.rb +111 -0
- data/lib/contrast/agent/patching/policy/patcher.rb +12 -8
- data/lib/contrast/agent/reporting/report.rb +21 -0
- data/lib/contrast/agent/reporting/reporter.rb +142 -0
- data/lib/contrast/agent/reporting/reporting_events/finding.rb +90 -0
- data/lib/contrast/agent/reporting/reporting_events/preflight.rb +25 -0
- data/lib/contrast/agent/reporting/reporting_events/preflight_message.rb +56 -0
- data/lib/contrast/agent/reporting/reporting_events/reporting_event.rb +37 -0
- data/lib/contrast/agent/reporting/reporting_utilities/audit.rb +127 -0
- data/lib/contrast/agent/reporting/reporting_utilities/reporter_client.rb +168 -0
- data/lib/contrast/agent/reporting/reporting_utilities/reporting_storage.rb +66 -0
- data/lib/contrast/agent/request.rb +2 -81
- data/lib/contrast/agent/request_context.rb +4 -128
- data/lib/contrast/agent/request_context_extend.rb +138 -0
- data/lib/contrast/agent/response.rb +2 -73
- data/lib/contrast/agent/startup_metrics_telemetry_event.rb +39 -16
- data/lib/contrast/agent/static_analysis.rb +1 -1
- data/lib/contrast/agent/telemetry.rb +15 -7
- data/lib/contrast/agent/telemetry_event.rb +8 -9
- data/lib/contrast/agent/thread_watcher.rb +31 -5
- data/lib/contrast/agent/version.rb +1 -1
- data/lib/contrast/agent.rb +15 -0
- data/lib/contrast/api/communication/connection_status.rb +10 -7
- data/lib/contrast/api/communication/messaging_queue.rb +37 -3
- data/lib/contrast/api/communication/response_processor.rb +15 -8
- data/lib/contrast/api/communication/service_lifecycle.rb +13 -3
- data/lib/contrast/api/communication/socket.rb +6 -8
- data/lib/contrast/api/communication/socket_client.rb +29 -12
- data/lib/contrast/api/communication/speedracer.rb +37 -1
- data/lib/contrast/api/communication/tcp_socket.rb +4 -3
- data/lib/contrast/api/communication/unix_socket.rb +1 -0
- data/lib/contrast/api/decorators/finding.rb +45 -0
- data/lib/contrast/components/api.rb +56 -0
- data/lib/contrast/components/app_context.rb +10 -65
- data/lib/contrast/components/app_context_extend.rb +78 -0
- data/lib/contrast/components/base.rb +23 -0
- data/lib/contrast/components/config.rb +8 -8
- data/lib/contrast/components/contrast_service.rb +5 -0
- data/lib/contrast/components/sampling.rb +2 -2
- data/lib/contrast/config/agent_configuration.rb +1 -1
- data/lib/contrast/config/api_configuration.rb +9 -4
- data/lib/contrast/config/api_proxy_configuration.rb +14 -0
- data/lib/contrast/config/application_configuration.rb +2 -3
- data/lib/contrast/config/assess_configuration.rb +3 -3
- data/lib/contrast/config/base_configuration.rb +17 -28
- data/lib/contrast/config/certification_configuration.rb +15 -0
- data/lib/contrast/config/env_variables.rb +2 -9
- data/lib/contrast/config/heap_dump_configuration.rb +6 -6
- data/lib/contrast/config/inventory_configuration.rb +1 -5
- data/lib/contrast/config/protect_rule_configuration.rb +1 -1
- data/lib/contrast/config/request_audit_configuration.rb +18 -0
- data/lib/contrast/config/ruby_configuration.rb +6 -6
- data/lib/contrast/config/service_configuration.rb +1 -2
- data/lib/contrast/config.rb +0 -1
- data/lib/contrast/configuration.rb +1 -2
- data/lib/contrast/extension/assess/array.rb +5 -7
- data/lib/contrast/framework/manager.rb +8 -32
- data/lib/contrast/framework/manager_extend.rb +50 -0
- data/lib/contrast/framework/rails/railtie.rb +1 -1
- data/lib/contrast/framework/sinatra/support.rb +2 -1
- data/lib/contrast/logger/log.rb +8 -103
- data/lib/contrast/utils/assess/property/tagged_utils.rb +23 -0
- data/lib/contrast/utils/assess/tracking_util.rb +20 -15
- data/lib/contrast/utils/assess/trigger_method_utils.rb +1 -1
- data/lib/contrast/utils/class_util.rb +18 -14
- data/lib/contrast/utils/findings.rb +62 -0
- data/lib/contrast/utils/hash_digest.rb +10 -73
- data/lib/contrast/utils/hash_digest_extend.rb +86 -0
- data/lib/contrast/utils/head_dump_utils_extend.rb +74 -0
- data/lib/contrast/utils/heap_dump_util.rb +2 -65
- data/lib/contrast/utils/invalid_configuration_util.rb +29 -0
- data/lib/contrast/utils/io_util.rb +1 -1
- data/lib/contrast/utils/log_utils.rb +108 -0
- data/lib/contrast/utils/middleware_utils.rb +87 -0
- data/lib/contrast/utils/net_http_base.rb +158 -0
- data/lib/contrast/utils/object_share.rb +1 -0
- data/lib/contrast/utils/request_utils.rb +88 -0
- data/lib/contrast/utils/response_utils.rb +97 -0
- data/lib/contrast/utils/substitution_utils.rb +167 -0
- data/lib/contrast/utils/tag_util.rb +9 -9
- data/lib/contrast/utils/telemetry.rb +4 -2
- data/lib/contrast/utils/telemetry_client.rb +90 -0
- data/lib/contrast/utils/telemetry_identifier.rb +17 -24
- data/ruby-agent.gemspec +5 -5
- metadata +48 -23
- data/lib/contrast/config/default_value.rb +0 -17
- data/lib/contrast/utils/requests_client.rb +0 -150
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2fd48ab0d63bb36fd51e032c360b2ffebe0c387a57e2b70a5e025c1af9667bfb
|
|
4
|
+
data.tar.gz: 7aaf091b2e3fd2ced8c8b11708db4a8e216bf55b7499fc235940e93ebff2ef1a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 56fbdd193cc27bcd7e21fe5c4f237276c616e4633344db18b6f27e7e73c99979131cbb9922a2d0cf947c0b666dd3a0d5098b120a805d8cf7b52bc73c6becc535
|
|
7
|
+
data.tar.gz: dce071438c0f996567c9867b4ab59900d2225fc3afc59887ffbd850705981f59f228360899084711fe88c9d566e33cdb20f3d5a9127b30efaa0c689274c84159
|
data/.simplecov
CHANGED
|
@@ -135,14 +135,14 @@ module Contrast
|
|
|
135
135
|
|
|
136
136
|
converted = []
|
|
137
137
|
markers.split(Contrast::Utils::ObjectShare::COMMA).each do |t|
|
|
138
|
-
case t
|
|
139
|
-
|
|
138
|
+
converted << case t
|
|
139
|
+
when Contrast::Utils::ObjectShare::OBJECT_KEY,
|
|
140
140
|
Contrast::Utils::ObjectShare::RETURN_KEY
|
|
141
141
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
142
|
+
t
|
|
143
|
+
else
|
|
144
|
+
Integer(t[1..-1])
|
|
145
|
+
end
|
|
146
146
|
end
|
|
147
147
|
converted
|
|
148
148
|
end
|
|
@@ -32,8 +32,13 @@ module Contrast
|
|
|
32
32
|
mod = trace_point.self
|
|
33
33
|
return if mod.cs__frozen? || mod.singleton_class?
|
|
34
34
|
|
|
35
|
+
different_ruby_version trace_point, provider_values, mod
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def different_ruby_version trace_point, provider_values, mod
|
|
35
39
|
# TODO: RUBY-1014 - remove non-AST approach
|
|
36
40
|
if RUBY_VERSION >= '2.6.0'
|
|
41
|
+
# TODO: RUBY-714 EOL 2.5. That check will be removed and the code will be brought back in here.
|
|
37
42
|
ast = RubyVM::AbstractSyntaxTree.parse_file(trace_point.path)
|
|
38
43
|
provider_values.each do |provider|
|
|
39
44
|
provider.parse(trace_point, ast)
|
|
@@ -24,7 +24,7 @@ module Contrast
|
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
# find original in the target, copy tags to the new position in target
|
|
27
|
-
original_start_index = target[0..target.length / 2 + 1].rindex(source1)
|
|
27
|
+
original_start_index = target[0..(target.length / 2) + 1].rindex(source1)
|
|
28
28
|
original_start_index ||= 1
|
|
29
29
|
properties.copy_from(source1, target, original_start_index, propagation_node.untags)
|
|
30
30
|
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
require 'contrast/components/logger'
|
|
5
5
|
require 'contrast/utils/duck_utils'
|
|
6
|
+
require 'contrast/utils/substitution_utils'
|
|
6
7
|
|
|
7
8
|
module Contrast
|
|
8
9
|
module Agent
|
|
@@ -17,9 +18,7 @@ module Contrast
|
|
|
17
18
|
class Substitution
|
|
18
19
|
include Contrast::Components::Logger::InstanceMethods
|
|
19
20
|
extend Contrast::Components::Logger::InstanceMethods
|
|
20
|
-
|
|
21
|
-
CAPTURE_GROUP_REGEXP = /\\[[:digit:]]/.cs__freeze
|
|
22
|
-
CAPTURE_NAME_REGEXP = /\\k<[[:alpha:]]/.cs__freeze
|
|
21
|
+
extend Contrast::Utils::SubstitutionUtils
|
|
23
22
|
|
|
24
23
|
class << self
|
|
25
24
|
# gsub is hard. there are four versions of this method
|
|
@@ -38,157 +37,6 @@ module Contrast
|
|
|
38
37
|
def sub_tagger patcher, preshift, ret, block
|
|
39
38
|
substitution_tagger(patcher, preshift, ret, !block.nil?, false)
|
|
40
39
|
end
|
|
41
|
-
|
|
42
|
-
private
|
|
43
|
-
|
|
44
|
-
def substitution_tagger patcher, preshift, ret, block, global = true
|
|
45
|
-
return ret unless ret
|
|
46
|
-
|
|
47
|
-
begin
|
|
48
|
-
source = preshift.object
|
|
49
|
-
self_tracked = Contrast::Agent::Assess::Tracker.tracked?(source)
|
|
50
|
-
args = preshift.args[1]
|
|
51
|
-
incoming_tracked = args && determine_tracked(args)
|
|
52
|
-
return ret unless self_tracked || incoming_tracked
|
|
53
|
-
|
|
54
|
-
parent_events = []
|
|
55
|
-
if block
|
|
56
|
-
block_sub(self_tracked, source, ret)
|
|
57
|
-
elsif args.is_a?(String)
|
|
58
|
-
string_sub(parent_events, self_tracked, preshift, ret, args, incoming_tracked, global)
|
|
59
|
-
elsif args.is_a?(Hash)
|
|
60
|
-
hash_sub(self_tracked, source, ret)
|
|
61
|
-
else # Enumerator, only for gsub
|
|
62
|
-
pattern_gsub(parent_events, preshift, ret)
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
if self_tracked
|
|
66
|
-
source_properties = Contrast::Agent::Assess::Tracker.properties(source)
|
|
67
|
-
parent_event = source_properties&.event
|
|
68
|
-
parent_events.prepend(parent_event) if parent_event
|
|
69
|
-
end
|
|
70
|
-
string_build_event(parent_events, patcher, preshift, ret)
|
|
71
|
-
rescue StandardError => e
|
|
72
|
-
logger.error('Unable to apply gsub propagator', e)
|
|
73
|
-
end
|
|
74
|
-
ret
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
def determine_tracked args
|
|
78
|
-
# if there's no arg, it can't be tracked
|
|
79
|
-
return false unless args
|
|
80
|
-
|
|
81
|
-
# if it's a string, just ask if it's tracked
|
|
82
|
-
case args
|
|
83
|
-
when String
|
|
84
|
-
Contrast::Agent::Assess::Tracker.tracked?(args)
|
|
85
|
-
# if it's a hash, ask if it has a tracked string
|
|
86
|
-
when Hash
|
|
87
|
-
args.values.any? { |value| value.is_a?(String) && Contrast::Agent::Assess::Tracker.tracked?(value) }
|
|
88
|
-
# this should never happen
|
|
89
|
-
else
|
|
90
|
-
false
|
|
91
|
-
end
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
def string_sub parent_events, self_tracked, preshift, ret, incoming, incoming_tracked, global
|
|
95
|
-
return unless (properties = Contrast::Agent::Assess::Tracker.properties!(ret))
|
|
96
|
-
|
|
97
|
-
incoming_properties = Contrast::Agent::Assess::Tracker.properties(incoming)
|
|
98
|
-
parent_events << incoming_properties&.event if incoming_properties&.event
|
|
99
|
-
|
|
100
|
-
source = preshift.object
|
|
101
|
-
|
|
102
|
-
# We can't efficiently find the places that things were
|
|
103
|
-
# copied from regexp / captures. Trading accuracy for
|
|
104
|
-
# performance
|
|
105
|
-
if incoming.match?(CAPTURE_GROUP_REGEXP) || incoming.match?(CAPTURE_NAME_REGEXP)
|
|
106
|
-
properties.splat_from(source, ret) if self_tracked
|
|
107
|
-
return
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
# if it's just a straight insert, that we can do
|
|
111
|
-
# Copy the tags from us to the return
|
|
112
|
-
ranges = find_string_sub_insert(properties, preshift, incoming, ret, global)
|
|
113
|
-
|
|
114
|
-
properties.delete_tags_at_ranges(ranges)
|
|
115
|
-
properties.shift_tags(ranges)
|
|
116
|
-
return unless incoming_tracked
|
|
117
|
-
return unless incoming_properties
|
|
118
|
-
|
|
119
|
-
tags = incoming_properties.tag_keys
|
|
120
|
-
ranges.each do |range|
|
|
121
|
-
tags.each do |tag|
|
|
122
|
-
properties.add_tag(tag, range)
|
|
123
|
-
end
|
|
124
|
-
end
|
|
125
|
-
end
|
|
126
|
-
|
|
127
|
-
# Find the points at which the new String was placed into the original
|
|
128
|
-
#
|
|
129
|
-
# @param properties [Contrast::Agent::Assess::Properties] the Properties of the ret
|
|
130
|
-
# @param preshift [Contrast::Agent::Assess::PreShift] the capture of the state of the code just prior to
|
|
131
|
-
# the invocation of the patched method
|
|
132
|
-
# @param incoming [String] the new String going into the substitution
|
|
133
|
-
# @param ret [String] the result of the substitution
|
|
134
|
-
# @param global [Boolean] if this was a global or single substitution
|
|
135
|
-
# @return [Array<Range>] the Ranges where substitution occurred
|
|
136
|
-
def find_string_sub_insert properties, preshift, incoming, ret, global
|
|
137
|
-
pattern = preshift.args[0]
|
|
138
|
-
source = preshift.object
|
|
139
|
-
|
|
140
|
-
properties.copy_from(source, ret)
|
|
141
|
-
# Figure out where inserts occurred
|
|
142
|
-
last_idx = 0
|
|
143
|
-
ranges = []
|
|
144
|
-
# For each insert, move the tag ranges
|
|
145
|
-
while last_idx
|
|
146
|
-
idx = source.index(pattern, last_idx)
|
|
147
|
-
break unless idx
|
|
148
|
-
|
|
149
|
-
last_idx = idx ? idx + 1 : nil
|
|
150
|
-
start_index = idx
|
|
151
|
-
end_index = idx + incoming.length
|
|
152
|
-
ranges << (start_index...end_index)
|
|
153
|
-
break unless global
|
|
154
|
-
end
|
|
155
|
-
ranges
|
|
156
|
-
end
|
|
157
|
-
|
|
158
|
-
def block_sub self_tracked, source, ret
|
|
159
|
-
return unless self_tracked
|
|
160
|
-
|
|
161
|
-
properties = Contrast::Agent::Assess::Tracker.properties!(ret)
|
|
162
|
-
properties&.splat_from(source, ret)
|
|
163
|
-
end
|
|
164
|
-
|
|
165
|
-
def hash_sub self_tracked, source, ret
|
|
166
|
-
return unless self_tracked
|
|
167
|
-
|
|
168
|
-
properties = Contrast::Agent::Assess::Tracker.properties!(ret)
|
|
169
|
-
properties&.splat_from(source, ret)
|
|
170
|
-
end
|
|
171
|
-
|
|
172
|
-
def pattern_gsub parent_events, preshift, ret
|
|
173
|
-
source = preshift.object
|
|
174
|
-
return unless (source_properties = Contrast::Agent::Assess::Tracker.properties(source))
|
|
175
|
-
return unless (properties = Contrast::Agent::Assess::Tracker.properties!(ret))
|
|
176
|
-
|
|
177
|
-
source_properties.tag_keys.each do |key|
|
|
178
|
-
properties.add_tag(key, 0...1)
|
|
179
|
-
end
|
|
180
|
-
parent_event = source_properties.event
|
|
181
|
-
parent_events << parent_event if parent_event
|
|
182
|
-
end
|
|
183
|
-
|
|
184
|
-
def string_build_event parent_events, patcher, preshift, ret
|
|
185
|
-
return unless Contrast::Agent::Assess::Tracker.tracked?(ret)
|
|
186
|
-
|
|
187
|
-
properties = Contrast::Agent::Assess::Tracker.properties(ret)
|
|
188
|
-
args = preshift.args
|
|
189
|
-
properties.build_event(patcher, ret, preshift.object, ret, args, 2)
|
|
190
|
-
properties.event.instance_variable_set(:@_parent_events, parent_events)
|
|
191
|
-
end
|
|
192
40
|
end
|
|
193
41
|
end
|
|
194
42
|
end
|
|
@@ -7,6 +7,7 @@ require 'contrast/components/logger'
|
|
|
7
7
|
require 'contrast/utils/object_share'
|
|
8
8
|
require 'contrast/utils/sha256_builder'
|
|
9
9
|
require 'contrast/utils/assess/trigger_method_utils'
|
|
10
|
+
require 'contrast/agent/reporting/reporting_utilities/reporting_storage'
|
|
10
11
|
|
|
11
12
|
module Contrast
|
|
12
13
|
module Agent
|
|
@@ -16,7 +17,7 @@ module Contrast
|
|
|
16
17
|
# Contrast::Agent::Assess::Policy::TriggerNode class. Each such method will call to this module just after
|
|
17
18
|
# invocation in order to determine if the call was done safely. In those cases where it was not, a Finding
|
|
18
19
|
# report is issued to the Service.
|
|
19
|
-
module TriggerMethod
|
|
20
|
+
module TriggerMethod # rubocop:disable Metrics/ModuleLength
|
|
20
21
|
extend Contrast::Components::Logger::InstanceMethods
|
|
21
22
|
extend Contrast::Utils::Assess::TriggerMethodUtils
|
|
22
23
|
|
|
@@ -52,6 +53,14 @@ module Contrast
|
|
|
52
53
|
apply_trigger(trigger_node, source, object, ret, *args)
|
|
53
54
|
end
|
|
54
55
|
|
|
56
|
+
def append_to_finding finding, trigger_node, source, object, ret, request, *args
|
|
57
|
+
finding.rule_id = Contrast::Utils::StringUtils.protobuf_safe_string(trigger_node.rule_id)
|
|
58
|
+
finding.version = determine_compliance_version(finding)
|
|
59
|
+
append_events(finding, trigger_node, source, object, ret, args)
|
|
60
|
+
append_route(finding, request)
|
|
61
|
+
append_hash(finding, source, request)
|
|
62
|
+
end
|
|
63
|
+
|
|
55
64
|
# This converts the source of the finding, and the events leading up to it into a Finding
|
|
56
65
|
#
|
|
57
66
|
# @param trigger_node [Contrast::Agent::Assess::Policy::TriggerNode] the node to direct applying this
|
|
@@ -63,18 +72,21 @@ module Contrast
|
|
|
63
72
|
# @return [Contrast::Api::Dtm::Finding, nil] the Contrast::Api::Dtm::Finding to send to TeamServer or
|
|
64
73
|
# nil if conditions were not met
|
|
65
74
|
def build_finding trigger_node, source, object, ret, *args
|
|
75
|
+
content_type = Contrast::Agent::REQUEST_TRACKER.current&.response&.content_type
|
|
76
|
+
|
|
77
|
+
if content_type.nil? && trigger_node.collectable?
|
|
78
|
+
Contrast::Agent::FINDINGS.collect_finding trigger_node, source, object, ret, *args
|
|
79
|
+
return
|
|
80
|
+
end
|
|
81
|
+
|
|
66
82
|
return unless Contrast::Agent::Assess::Policy::TriggerValidation.valid?(trigger_node, object, ret, args)
|
|
67
83
|
|
|
68
84
|
request = find_request(source)
|
|
69
85
|
return unless reportable?(request&.env)
|
|
70
86
|
|
|
87
|
+
handle_new_finding trigger_node, source, object, ret, request, *args
|
|
71
88
|
finding = Contrast::Api::Dtm::Finding.new
|
|
72
|
-
finding
|
|
73
|
-
|
|
74
|
-
append_events(finding, trigger_node, source, object, ret, args)
|
|
75
|
-
append_route(finding, request)
|
|
76
|
-
append_hash(finding, source, request)
|
|
77
|
-
finding.version = determine_compliance_version(finding)
|
|
89
|
+
append_to_finding finding, trigger_node, source, object, ret, request, *args
|
|
78
90
|
logger.trace('Finding created', node_id: trigger_node.id, source_id: source.__id__,
|
|
79
91
|
rule: trigger_node.rule_id)
|
|
80
92
|
report_finding(finding, request)
|
|
@@ -110,6 +122,31 @@ module Contrast
|
|
|
110
122
|
Contrast::Agent.messaging_queue.send_event_eventually(activity)
|
|
111
123
|
end
|
|
112
124
|
|
|
125
|
+
def handle_new_finding trigger_node, source, object, ret, request, *args
|
|
126
|
+
return unless Contrast::Agent::Reporter.enabled?
|
|
127
|
+
|
|
128
|
+
new_finding_and_reporting trigger_node, source, object, ret, request, *args
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def new_finding_and_reporting trigger_node, source, object, ret, request, *args
|
|
132
|
+
# sent to reporter
|
|
133
|
+
# here we will generate new type of finding
|
|
134
|
+
ruby_finding = Contrast::Agent::Reporting::Finding.new trigger_node.rule_id
|
|
135
|
+
ruby_finding.attach_data trigger_node, source, object, ret, request, *args
|
|
136
|
+
hash_code = Contrast::Utils::HashDigest.generate_event_hash(ruby_finding, source, request)
|
|
137
|
+
ruby_finding.hash_code = hash_code
|
|
138
|
+
# save the current finding
|
|
139
|
+
Contrast::Agent::Reporting::ReportingStorage[hash_code] = ruby_finding
|
|
140
|
+
|
|
141
|
+
new_preflight = Contrast::Agent::Reporting::Preflight.new
|
|
142
|
+
new_preflight_message = Contrast::Agent::Reporting::PreflightMessage.new
|
|
143
|
+
new_preflight_message.routes << request
|
|
144
|
+
new_preflight_message.hash_code = hash_code
|
|
145
|
+
new_preflight_message.data = "#{ trigger_node.rule_id },#{ hash_code }"
|
|
146
|
+
new_preflight.messages << new_preflight_message
|
|
147
|
+
Contrast::Agent.reporter_queue.send_event_immediately(new_preflight)
|
|
148
|
+
end
|
|
149
|
+
|
|
113
150
|
private
|
|
114
151
|
|
|
115
152
|
def settings
|
|
@@ -22,6 +22,10 @@ module Contrast
|
|
|
22
22
|
JSON_RULE_NAME = 'name'
|
|
23
23
|
JSON_CUSTOM_PATCH = 'custom_patch'
|
|
24
24
|
|
|
25
|
+
# Our list with rules to be collected and reported back when we have response
|
|
26
|
+
# from the application. Some rules rely on Content-Type validation.
|
|
27
|
+
COLLECTABLE_RULES = %w[reflected-xss].cs__freeze
|
|
28
|
+
|
|
25
29
|
attr_reader :rule_id, :required_tags, :disallowed_tags, :good_value, :bad_value
|
|
26
30
|
|
|
27
31
|
def initialize trigger_hash = {}, rule_hash = {}
|
|
@@ -67,6 +71,10 @@ module Contrast
|
|
|
67
71
|
:TYPE_METHOD
|
|
68
72
|
end
|
|
69
73
|
|
|
74
|
+
def collectable?
|
|
75
|
+
COLLECTABLE_RULES.include?(rule_id)
|
|
76
|
+
end
|
|
77
|
+
|
|
70
78
|
def rule_disabled?
|
|
71
79
|
::Contrast::ASSESS.rule_disabled?(rule_id)
|
|
72
80
|
end
|
|
@@ -160,8 +168,8 @@ module Contrast
|
|
|
160
168
|
@disallowed_tags << LIMITED_CHARS
|
|
161
169
|
@disallowed_tags << CUSTOM_ENCODED
|
|
162
170
|
@disallowed_tags << CUSTOM_VALIDATED
|
|
163
|
-
@disallowed_tags << ENCODER_START + loud_name
|
|
164
|
-
@disallowed_tags << VALIDATOR_START + loud_name
|
|
171
|
+
@disallowed_tags << (ENCODER_START + loud_name)
|
|
172
|
+
@disallowed_tags << (VALIDATOR_START + loud_name)
|
|
165
173
|
end
|
|
166
174
|
|
|
167
175
|
def validate_rule_tags tags
|
|
@@ -200,14 +208,14 @@ module Contrast
|
|
|
200
208
|
satisfied = tags_at.any? && required_tags.all? { |tag| tags_at.any? { |found| found.label == tag } }
|
|
201
209
|
# if this range matches all the required tags and we're already
|
|
202
210
|
# chunking, meaning the previous range matched, do nothing
|
|
203
|
-
if satisfied && chunking
|
|
204
|
-
start_range += 1
|
|
205
|
-
next
|
|
206
|
-
end
|
|
207
211
|
|
|
208
212
|
# if we are satisfied and we were not chunking, this represents
|
|
209
213
|
# the start of the next range, so create a new entry.
|
|
210
214
|
if satisfied
|
|
215
|
+
if chunking
|
|
216
|
+
start_range += 1
|
|
217
|
+
next
|
|
218
|
+
end
|
|
211
219
|
ranges << Contrast::Agent::Assess::Tag.new('required', 0, start_range)
|
|
212
220
|
chunking = true
|
|
213
221
|
# if we are chunking and not satisfied, this represents the end
|
|
@@ -18,7 +18,7 @@ module Contrast
|
|
|
18
18
|
# https://bitbucket.org/contrastsecurity/assess-specifications/src/master/rules/dataflow/reflected_xss.md
|
|
19
19
|
def self.valid? _patcher, _object, _ret, _args
|
|
20
20
|
content_type = Contrast::Agent::REQUEST_TRACKER.current&.response&.content_type
|
|
21
|
-
return
|
|
21
|
+
return false unless content_type
|
|
22
22
|
|
|
23
23
|
content_type = content_type.downcase
|
|
24
24
|
SAFE_CONTENT_TYPES.none? { |safe_type| content_type.index(safe_type) }
|
|
@@ -24,29 +24,6 @@ module Contrast
|
|
|
24
24
|
instance_variable_defined?(:@_tags) && tags.any?
|
|
25
25
|
end
|
|
26
26
|
|
|
27
|
-
# Is the given tag present?
|
|
28
|
-
# Used in testing, so found by `be_tagged`, if you're grepping for it
|
|
29
|
-
#
|
|
30
|
-
# @param label [Symbol] the tag to check for
|
|
31
|
-
# @return [Boolean]
|
|
32
|
-
def tagged? label
|
|
33
|
-
tracked? && tags.key?(label)
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
# Similar to #tracked?, but limited to a given range.
|
|
37
|
-
#
|
|
38
|
-
# @param start [Integer] the inclusive start index to check.
|
|
39
|
-
# @param finish [Integer] the exclusive end index to check.
|
|
40
|
-
# @return [Boolean]
|
|
41
|
-
def any_tags_between? start, finish
|
|
42
|
-
return false unless tracked?
|
|
43
|
-
|
|
44
|
-
tags.each_value do |tag_array|
|
|
45
|
-
return true if tag_array.any? { |tag| tag.overlaps?(start, finish) }
|
|
46
|
-
end
|
|
47
|
-
false
|
|
48
|
-
end
|
|
49
|
-
|
|
50
27
|
# Remove all tags within the given ranges.
|
|
51
28
|
# This does not delete an entire tag if part of that tag is
|
|
52
29
|
# outside this range, meaning we may reduce sizes of tags
|
|
@@ -102,6 +79,8 @@ module Contrast
|
|
|
102
79
|
end
|
|
103
80
|
|
|
104
81
|
# Remove the tag ranges covering the given range
|
|
82
|
+
# and appends any trailing value that might
|
|
83
|
+
# exist after removal of range
|
|
105
84
|
def remove_tags range
|
|
106
85
|
return unless tracked?
|
|
107
86
|
|
|
@@ -112,19 +91,7 @@ module Contrast
|
|
|
112
91
|
value.each do |tag|
|
|
113
92
|
comparison = tag.compare_range(range.begin, range.end)
|
|
114
93
|
# ABOVE and BELOW are not affected by this check
|
|
115
|
-
|
|
116
|
-
when Contrast::Agent::Assess::Tag::LOW_SPAN
|
|
117
|
-
tag.update_end(range.begin)
|
|
118
|
-
when Contrast::Agent::Assess::Tag::WITHIN
|
|
119
|
-
remove << tag
|
|
120
|
-
when Contrast::Agent::Assess::Tag::WITHOUT
|
|
121
|
-
new_tag = tag.clone
|
|
122
|
-
new_tag.update_start(range.end)
|
|
123
|
-
add << new_tag
|
|
124
|
-
tag.update_end(range.begin)
|
|
125
|
-
when Contrast::Agent::Assess::Tag::HIGH_SPAN
|
|
126
|
-
tag.update_start(range.end)
|
|
127
|
-
end
|
|
94
|
+
tags_remove_comparison comparison, tag, remove, add, range
|
|
128
95
|
end
|
|
129
96
|
value.delete_if { |tag| remove.include?(tag) }
|
|
130
97
|
Contrast::Utils::TagUtil.ordered_merge(value, add)
|
|
@@ -133,6 +100,29 @@ module Contrast
|
|
|
133
100
|
full_delete.each { |key| tags.delete(key) }
|
|
134
101
|
end
|
|
135
102
|
|
|
103
|
+
# This method is for the tags comparison
|
|
104
|
+
# the idea is to move the whole case here
|
|
105
|
+
# @param comparison [String] indicates type of removal is to occur
|
|
106
|
+
# @param tag Contrast::Agent::Assess::Tag
|
|
107
|
+
# @param remove [String] holds removed Tag if exists
|
|
108
|
+
# @param add [String] holds trailing Tag if exists
|
|
109
|
+
# @param range [Range] start and stop for idx for removal
|
|
110
|
+
def tags_remove_comparison comparison, tag, remove, add, range
|
|
111
|
+
case comparison
|
|
112
|
+
when Contrast::Agent::Assess::Tag::LOW_SPAN
|
|
113
|
+
tag.update_end(range.begin)
|
|
114
|
+
when Contrast::Agent::Assess::Tag::WITHIN
|
|
115
|
+
remove << tag
|
|
116
|
+
when Contrast::Agent::Assess::Tag::WITHOUT
|
|
117
|
+
new_tag = tag.clone
|
|
118
|
+
new_tag.update_start(range.end)
|
|
119
|
+
add << new_tag
|
|
120
|
+
tag.update_end(range.begin)
|
|
121
|
+
when Contrast::Agent::Assess::Tag::HIGH_SPAN
|
|
122
|
+
tag.update_start(range.end)
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
136
126
|
# Shift the tag ranges covering the given range
|
|
137
127
|
# We assume this is for a deletion, meaning we
|
|
138
128
|
# have to move tags to the left
|
|
@@ -176,32 +166,36 @@ module Contrast
|
|
|
176
166
|
comparison = tag.compare_range(range.begin, range.end)
|
|
177
167
|
length = range.end - range.begin
|
|
178
168
|
# BELOW is not affected by this check
|
|
179
|
-
|
|
180
|
-
# part of the tag is being inserted on
|
|
181
|
-
when Contrast::Agent::Assess::Tag::LOW_SPAN
|
|
182
|
-
new_tag = tag.clone
|
|
183
|
-
new_tag.update_start(range.begin)
|
|
184
|
-
new_tag.shift(length)
|
|
185
|
-
add << new_tag
|
|
186
|
-
tag.update_end(range.begin)
|
|
187
|
-
# the tag exists in the inserted range. it is partially shifted
|
|
188
|
-
when Contrast::Agent::Assess::Tag::WITHIN
|
|
189
|
-
tag.shift(length)
|
|
190
|
-
# the tag spans the range. leave the part below alone
|
|
191
|
-
when Contrast::Agent::Assess::Tag::WITHOUT # rubocop:disable Lint/DuplicateBranch
|
|
192
|
-
new_tag = tag.clone
|
|
193
|
-
new_tag.update_start(range.begin)
|
|
194
|
-
new_tag.shift(length)
|
|
195
|
-
add << new_tag
|
|
196
|
-
tag.update_end(range.begin)
|
|
197
|
-
when Contrast::Agent::Assess::Tag::HIGH_SPAN, Contrast::Agent::Assess::Tag::ABOVE # rubocop:disable Lint/DuplicateBranch
|
|
198
|
-
tag.shift(length)
|
|
199
|
-
end
|
|
169
|
+
shift_tags_comparison comparison, add, tag, length, range
|
|
200
170
|
end
|
|
201
171
|
Contrast::Utils::TagUtil.ordered_merge(value, add)
|
|
202
172
|
end
|
|
203
173
|
end
|
|
204
174
|
|
|
175
|
+
def shift_tags_comparison comparison, add, tag, length, range
|
|
176
|
+
case comparison
|
|
177
|
+
# part of the tag is being inserted on
|
|
178
|
+
when Contrast::Agent::Assess::Tag::LOW_SPAN
|
|
179
|
+
new_tag = tag.clone
|
|
180
|
+
new_tag.update_start(range.begin)
|
|
181
|
+
new_tag.shift(length)
|
|
182
|
+
add << new_tag
|
|
183
|
+
tag.update_end(range.begin)
|
|
184
|
+
# the tag exists in the inserted range. it is partially shifted
|
|
185
|
+
when Contrast::Agent::Assess::Tag::WITHIN
|
|
186
|
+
tag.shift(length)
|
|
187
|
+
# the tag spans the range. leave the part below alone
|
|
188
|
+
when Contrast::Agent::Assess::Tag::WITHOUT # rubocop:disable Lint/DuplicateBranch
|
|
189
|
+
new_tag = tag.clone
|
|
190
|
+
new_tag.update_start(range.begin)
|
|
191
|
+
new_tag.shift(length)
|
|
192
|
+
add << new_tag
|
|
193
|
+
tag.update_end(range.begin)
|
|
194
|
+
when Contrast::Agent::Assess::Tag::HIGH_SPAN, Contrast::Agent::Assess::Tag::ABOVE # rubocop:disable Lint/DuplicateBranch
|
|
195
|
+
tag.shift(length)
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
205
199
|
private
|
|
206
200
|
|
|
207
201
|
# Because of the auto-fill thing, we should not allow direct access to
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
require 'contrast/agent/assess/policy/trigger_method'
|
|
5
5
|
require 'contrast/components/logger'
|
|
6
6
|
require 'contrast/extension/module'
|
|
7
|
+
require 'contrast/agent/reporting/report'
|
|
7
8
|
|
|
8
9
|
module Contrast
|
|
9
10
|
module Agent
|
|
@@ -66,7 +67,8 @@ module Contrast
|
|
|
66
67
|
# if it looks like a placeholder / pointer to a config, skip it
|
|
67
68
|
next unless value_passes?(value)
|
|
68
69
|
|
|
69
|
-
|
|
70
|
+
new_finding_and_reporting clazz, constant_string
|
|
71
|
+
build_finding clazz, constant_string
|
|
70
72
|
end
|
|
71
73
|
end
|
|
72
74
|
|
|
@@ -138,7 +140,8 @@ module Contrast
|
|
|
138
140
|
# sort. We leave it to each rule to properly handle these nodes.
|
|
139
141
|
return unless value_node_passes?(value)
|
|
140
142
|
|
|
141
|
-
|
|
143
|
+
new_finding_and_reporting mod, name
|
|
144
|
+
build_finding mod, name
|
|
142
145
|
end
|
|
143
146
|
|
|
144
147
|
# Constants can be set as frozen directly. We need to account for
|
|
@@ -161,6 +164,14 @@ module Contrast
|
|
|
161
164
|
def build_finding clazz, constant_string
|
|
162
165
|
class_name = clazz.cs__name
|
|
163
166
|
|
|
167
|
+
finding = assign_finding class_name, constant_string
|
|
168
|
+
Contrast::Agent::Assess::Policy::TriggerMethod.report_finding(finding)
|
|
169
|
+
rescue StandardError => e
|
|
170
|
+
logger.error('Unable to build a finding for Hardcoded Rule', e)
|
|
171
|
+
nil
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def assign_finding class_name, constant_string
|
|
164
175
|
finding = Contrast::Api::Dtm::Finding.new
|
|
165
176
|
finding.rule_id = Contrast::Utils::StringUtils.protobuf_safe_string(rule_id)
|
|
166
177
|
finding.version = Contrast::Agent::Assess::Policy::TriggerMethod::CURRENT_FINDING_VERSION
|
|
@@ -173,10 +184,33 @@ module Contrast
|
|
|
173
184
|
hash = Contrast::Utils::HashDigest.generate_class_scanning_hash(finding)
|
|
174
185
|
finding.hash_code = Contrast::Utils::StringUtils.protobuf_safe_string(hash)
|
|
175
186
|
finding.preflight = Contrast::Utils::PreflightUtil.create_preflight(finding)
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
187
|
+
finding
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def new_finding_and_reporting clazz, constant_string
|
|
191
|
+
return unless Contrast::Agent::Reporter.enabled?
|
|
192
|
+
|
|
193
|
+
# sent to reporter
|
|
194
|
+
# and add logger message for the report of the preflight
|
|
195
|
+
new_preflight = Contrast::Agent::Reporting::Preflight.new
|
|
196
|
+
new_preflight_message = Contrast::Agent::Reporting::PreflightMessage.new
|
|
197
|
+
new_preflight_message.hash_code = hash
|
|
198
|
+
new_preflight_message.data = "#{ rule_id },#{ hash }"
|
|
199
|
+
new_preflight.messages << new_preflight_message
|
|
200
|
+
|
|
201
|
+
# extract to new method
|
|
202
|
+
# here we will generate new type of finding
|
|
203
|
+
ruby_finding = Contrast::Agent::Reporting::Finding.new rule_id
|
|
204
|
+
ruby_finding.hash_code = hash
|
|
205
|
+
ruby_finding.properties[SOURCE_KEY] = clazz.cs__name
|
|
206
|
+
ruby_finding.properties[CONSTANT_NAME_KEY] = constant_string
|
|
207
|
+
ruby_finding.properties[CODE_SOURCE_KEY] = constant_string + redacted_marker
|
|
208
|
+
save_and_report_finding ruby_finding, new_preflight
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def save_and_report_finding ruby_finding, new_preflight
|
|
212
|
+
Contrast::Agent::Reporting::ReportingStorage[hash] = ruby_finding
|
|
213
|
+
Contrast::Agent.reporter_queue.send_event_immediately(new_preflight)
|
|
180
214
|
end
|
|
181
215
|
end
|
|
182
216
|
end
|