contrast-agent 7.5.0 → 7.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/ext/cs__common/cs__common.c +5 -5
- data/ext/cs__contrast_patch/cs__contrast_patch.c +2 -1
- data/ext/cs__scope/cs__scope.c +6 -5
- data/lib/contrast/agent/assess/events/event_data.rb +11 -2
- data/lib/contrast/agent/assess/finalizers/freeze.rb +1 -0
- data/lib/contrast/agent/assess/finalizers/hash.rb +7 -0
- data/lib/contrast/agent/assess/policy/patcher.rb +2 -0
- data/lib/contrast/agent/assess/policy/policy.rb +4 -0
- data/lib/contrast/agent/assess/policy/policy_node.rb +29 -7
- data/lib/contrast/agent/assess/policy/preshift.rb +34 -1
- data/lib/contrast/agent/assess/policy/propagation_method.rb +16 -1
- data/lib/contrast/agent/assess/policy/propagation_node.rb +40 -1
- data/lib/contrast/agent/assess/policy/propagator/append.rb +5 -0
- data/lib/contrast/agent/assess/policy/propagator/base.rb +10 -0
- data/lib/contrast/agent/assess/policy/propagator/buffer.rb +6 -0
- data/lib/contrast/agent/assess/policy/propagator/center.rb +14 -0
- data/lib/contrast/agent/assess/policy/propagator/custom.rb +6 -0
- data/lib/contrast/agent/assess/policy/propagator/database_write.rb +14 -0
- data/lib/contrast/agent/assess/policy/propagator/insert.rb +6 -0
- data/lib/contrast/agent/assess/policy/propagator/match_data.rb +38 -0
- data/lib/contrast/agent/assess/policy/propagator/next.rb +6 -0
- data/lib/contrast/agent/assess/policy/propagator/prepend.rb +5 -0
- data/lib/contrast/agent/assess/policy/propagator/remove.rb +4 -0
- data/lib/contrast/agent/assess/policy/propagator/replace.rb +5 -0
- data/lib/contrast/agent/assess/policy/propagator/reverse.rb +5 -0
- data/lib/contrast/agent/assess/policy/propagator/select.rb +30 -0
- data/lib/contrast/agent/assess/policy/propagator/splat.rb +10 -0
- data/lib/contrast/agent/assess/policy/source_node.rb +5 -1
- data/lib/contrast/agent/assess/policy/source_validation/cross_site_validator.rb +4 -0
- data/lib/contrast/agent/assess/policy/trigger/reflected_xss.rb +16 -0
- data/lib/contrast/agent/assess/policy/trigger/xpath.rb +19 -0
- data/lib/contrast/agent/assess/policy/trigger_method.rb +8 -1
- data/lib/contrast/agent/assess/policy/trigger_node.rb +11 -1
- data/lib/contrast/agent/assess/policy/trigger_validation/redos_validator.rb +4 -0
- data/lib/contrast/agent/assess/policy/trigger_validation/ssrf_validator.rb +6 -0
- data/lib/contrast/agent/assess/policy/trigger_validation/xss_validator.rb +6 -0
- data/lib/contrast/agent/assess/rule/response/base_rule.rb +2 -1
- data/lib/contrast/agent/hooks/at_exit_hook.rb +1 -0
- data/lib/contrast/agent/reporting/reporting_utilities/audit.rb +1 -1
- data/lib/contrast/agent/reporting/reporting_utilities/reporter_client.rb +35 -7
- data/lib/contrast/agent/reporting/reporting_utilities/reporter_client_utils.rb +8 -2
- data/lib/contrast/agent/reporting/reporting_utilities/reporting_storage.rb +1 -6
- data/lib/contrast/agent/reporting/reporting_utilities/response_handler.rb +4 -3
- data/lib/contrast/agent/reporting/reporting_utilities/response_handler_utils.rb +22 -12
- data/lib/contrast/agent/request/request.rb +1 -1
- data/lib/contrast/agent/request/request_handler.rb +1 -0
- data/lib/contrast/agent/version.rb +1 -1
- data/lib/contrast/configuration.rb +1 -1
- data/lib/contrast/utils/hash_digest.rb +0 -14
- data/lib/contrast/utils/hash_digest_extend.rb +16 -5
- data/lib/contrast/utils/json.rb +1 -1
- data/lib/contrast/utils/middleware_utils.rb +9 -0
- data/lib/contrast/utils/routes_sent.rb +3 -2
- data/lib/contrast.rb +2 -2
- data/resources/assess/policy.json +50 -1
- data/ruby-agent.gemspec +13 -13
- metadata +24 -23
@@ -15,6 +15,13 @@ module Contrast
|
|
15
15
|
# MatchData#match methods, since the last was introduced after
|
16
16
|
# Ruby 3.1.0, but shares similar functionality, except it does not
|
17
17
|
# support ranges.
|
18
|
+
#
|
19
|
+
# @param propagation_node [Contrast::Agent::Assess::Policy::PropagationNode] the node responsible for the
|
20
|
+
# propagation action required by this method.
|
21
|
+
# @param preshift [Object] pre call state of the things.
|
22
|
+
# @param ret [Object] the return value of the method.
|
23
|
+
# @param _block [Proc] the block passed to the method.
|
24
|
+
# @return [Object] the return value of the method.
|
18
25
|
def square_bracket_tagger propagation_node, preshift, ret, _block
|
19
26
|
case ret
|
20
27
|
when Array
|
@@ -36,6 +43,14 @@ module Contrast
|
|
36
43
|
ret
|
37
44
|
end
|
38
45
|
|
46
|
+
# Captures is a method that returns an array of MatchData objects.
|
47
|
+
#
|
48
|
+
# @param propagation_node [Contrast::Agent::Assess::Policy::PropagationNode] the node responsible for the
|
49
|
+
# propagation action required by this method.
|
50
|
+
# @param preshift [Object] pre call state of the things.
|
51
|
+
# @param ret [Object] the return value of the method.
|
52
|
+
# @param _block [Proc] the block passed to the method.
|
53
|
+
# @return [Object] the return value of the method.
|
39
54
|
def captures_tagger propagation_node, preshift, ret, _block
|
40
55
|
return unless ret
|
41
56
|
|
@@ -52,6 +67,12 @@ module Contrast
|
|
52
67
|
ret
|
53
68
|
end
|
54
69
|
|
70
|
+
# @param propagation_node [Contrast::Agent::Assess::Policy::PropagationNode] the node responsible for the
|
71
|
+
# propagation action required by this method.
|
72
|
+
# @param preshift [Object] pre call state of the things.
|
73
|
+
# @param ret [Object] the return value of the method.
|
74
|
+
# @param _block [Proc] the block passed to the method.
|
75
|
+
# @return [Object] the return value of the method.
|
55
76
|
def to_a_tagger propagation_node, preshift, ret, _block
|
56
77
|
idx = 0
|
57
78
|
while idx < ret.length
|
@@ -65,6 +86,12 @@ module Contrast
|
|
65
86
|
ret
|
66
87
|
end
|
67
88
|
|
89
|
+
# @param propagation_node [Contrast::Agent::Assess::Policy::PropagationNode] the node responsible for the
|
90
|
+
# propagation action required by this method.
|
91
|
+
# @param preshift [Object] pre call state of the things.
|
92
|
+
# @param ret [Object] the return value of the method.
|
93
|
+
# @param _block [Proc] the block passed to the method.
|
94
|
+
# @return [Object] the return value of the method.
|
68
95
|
def values_at_tagger propagation_node, preshift, ret, _block
|
69
96
|
idx = 0
|
70
97
|
while idx < ret.length
|
@@ -81,6 +108,9 @@ module Contrast
|
|
81
108
|
|
82
109
|
private
|
83
110
|
|
111
|
+
# @param preshift [Object] pre call state of the things.
|
112
|
+
# @param index [Integer] the index of the argument to retrieve.
|
113
|
+
# @return [Integer] the index of the argument to retrieve.
|
84
114
|
def target_matchdata_index preshift, index
|
85
115
|
if preshift.args[0].is_a?(Range)
|
86
116
|
arg_range = preshift.args[0]
|
@@ -90,6 +120,14 @@ module Contrast
|
|
90
120
|
end
|
91
121
|
end
|
92
122
|
|
123
|
+
# Handles the propagation of a single argument.
|
124
|
+
#
|
125
|
+
# @param argument_index [Integer] the index of the argument to retrieve.
|
126
|
+
# @param preshift [Object] pre call state of the things.
|
127
|
+
# @param return_value [Object] the return value of the method.
|
128
|
+
# @param propagation_node [Contrast::Agent::Assess::Policy::PropagationNode] the node responsible for the
|
129
|
+
# propagation action required by this method.
|
130
|
+
# @return [Object] the return value of the method.
|
93
131
|
def square_bracket_single argument_index, preshift, return_value, propagation_node
|
94
132
|
original_start_index = preshift.object.begin(argument_index)
|
95
133
|
original_end_index = preshift.object.end(argument_index)
|
@@ -14,6 +14,12 @@ module Contrast
|
|
14
14
|
class << self
|
15
15
|
# String has some silly methods like next. Basically, this flips a
|
16
16
|
# character in a predictable manner
|
17
|
+
#
|
18
|
+
# @param propagation_node [Contrast::Agent::Assess::Policy::PropagationNode] the node responsible for the
|
19
|
+
# propagation action required by this method.
|
20
|
+
# @param preshift [Object] pre call state of the things.
|
21
|
+
# @param target [Object] the object to which the source is being appended
|
22
|
+
# @return [Object] the target with the tags applied
|
17
23
|
def propagate propagation_node, preshift, target
|
18
24
|
return unless (properties = Contrast::Agent::Assess::Tracker.properties!(target))
|
19
25
|
|
@@ -13,6 +13,11 @@ module Contrast
|
|
13
13
|
class << self
|
14
14
|
# For the source, prepend its tags to the target. It's basically the
|
15
15
|
# opposite of append. :-P
|
16
|
+
#
|
17
|
+
# @param propagation_node [Contrast::Agent::Assess::Policy::PropagationNode] the node responsible for the
|
18
|
+
# propagation action required by this method.
|
19
|
+
# @param preshift [Object] pre call state of the things.
|
20
|
+
# @param target [Object] the object to which the source is being appended
|
16
21
|
def propagate propagation_node, preshift, target
|
17
22
|
return unless (properties = Contrast::Agent::Assess::Tracker.properties!(target))
|
18
23
|
|
@@ -37,6 +37,10 @@ module Contrast
|
|
37
37
|
handle_removal(propagation_node, source, target)
|
38
38
|
end
|
39
39
|
|
40
|
+
# @param propagation_node [Contrast::Agent::Assess::Policy::PropagationNode] the node responsible for the
|
41
|
+
# propagation action required by this method.
|
42
|
+
# @param source [Object] the object to which the source is being appended
|
43
|
+
# @param target [Object] the object to which the source is being appended
|
40
44
|
def handle_removal propagation_node, source, target
|
41
45
|
return unless source
|
42
46
|
return unless (properties = Contrast::Agent::Assess::Tracker.properties!(target))
|
@@ -13,6 +13,11 @@ module Contrast
|
|
13
13
|
class << self
|
14
14
|
# Replace means we're replacing the target w/ the source. Anything
|
15
15
|
# on the source should be passed to the target.
|
16
|
+
#
|
17
|
+
# @param propagation_node [Contrast::Agent::Assess::Policy::PropagationNode] the node responsible for the
|
18
|
+
# propagation action required by this method.
|
19
|
+
# @param preshift [Object] pre call state of the things.
|
20
|
+
# @param target [Object] the object to which the source is being appended
|
16
21
|
def propagate propagation_node, preshift, target
|
17
22
|
return unless (properties = Contrast::Agent::Assess::Tracker.properties!(target))
|
18
23
|
|
@@ -12,6 +12,11 @@ module Contrast
|
|
12
12
|
# overlapping tags.
|
13
13
|
class Reverse < Contrast::Agent::Assess::Policy::Propagator::Base
|
14
14
|
class << self
|
15
|
+
# @param propagation_node [Contrast::Agent::Assess::Policy::PropagationNode] the node responsible for the
|
16
|
+
# propagation action required by this method.
|
17
|
+
# @param preshift [Object] pre call state of the things.
|
18
|
+
# @param target [Object] the object to which the source is being appended
|
19
|
+
# @return [Object] the target with the tags applied
|
15
20
|
def propagate propagation_node, preshift, target
|
16
21
|
return unless (properties = Contrast::Agent::Assess::Tracker.properties!(target))
|
17
22
|
|
@@ -13,6 +13,10 @@ module Contrast
|
|
13
13
|
# a 'get it right' state soon.
|
14
14
|
class Select
|
15
15
|
class << self
|
16
|
+
# @param patcher [Contrast::Agent::Assess::Patcher] the patcher
|
17
|
+
# @param preshift [Object] pre call state of the things.
|
18
|
+
# @param ret [Object] the return value of the method.
|
19
|
+
# @param _block [Proc] the block passed to the method.
|
16
20
|
def select_tagger patcher, preshift, ret, _block
|
17
21
|
source = preshift.object
|
18
22
|
args = preshift.args
|
@@ -42,6 +46,12 @@ module Contrast
|
|
42
46
|
|
43
47
|
private
|
44
48
|
|
49
|
+
# Handles the integer case for select.
|
50
|
+
#
|
51
|
+
# @param args [Array] the arguments passed to the method
|
52
|
+
# @param arg [Object] the first argument passed to the method
|
53
|
+
# @param source [String] the source string
|
54
|
+
# @return [Range, nil] the range to select from the source string
|
45
55
|
def handle_integer args, arg, source
|
46
56
|
length = args[1] || 1
|
47
57
|
# (void) negative range
|
@@ -49,11 +59,22 @@ module Contrast
|
|
49
59
|
arg...(arg + length)
|
50
60
|
end
|
51
61
|
|
62
|
+
# Handles the string case for select.
|
63
|
+
#
|
64
|
+
# @param arg [String] the first argument passed to the method
|
65
|
+
# @param source [String] the source string
|
66
|
+
# @return [Range, nil] the range to select from the source string
|
52
67
|
def handle_string arg, source
|
53
68
|
idx = source.index(arg)
|
54
69
|
idx...(idx + arg.length)
|
55
70
|
end
|
56
71
|
|
72
|
+
# Handles the regexp case for select.
|
73
|
+
#
|
74
|
+
# @param args [Array] the arguments passed to the method
|
75
|
+
# @param arg [Regexp] the first argument passed to the method
|
76
|
+
# @param source [String] the source string
|
77
|
+
# @return [Range, nil] the range to select from the source string
|
57
78
|
def handle_regexp args, arg, source
|
58
79
|
match_data = arg.match(source)
|
59
80
|
# nil has the same meaning as 0. use full match
|
@@ -61,6 +82,11 @@ module Contrast
|
|
61
82
|
match_data.begin(group)...match_data.end(group)
|
62
83
|
end
|
63
84
|
|
85
|
+
# Handles the range case for select.
|
86
|
+
#
|
87
|
+
# @param arg [Range] the first argument passed to the method
|
88
|
+
# @param source [String] the source string
|
89
|
+
# @return [Range, nil] the range to select from the source string
|
64
90
|
def handle_range arg, source
|
65
91
|
start = arg.begin
|
66
92
|
finish = arg.end
|
@@ -73,6 +99,10 @@ module Contrast
|
|
73
99
|
start...finish
|
74
100
|
end
|
75
101
|
|
102
|
+
# Determines the range to select from the source string.
|
103
|
+
#
|
104
|
+
# @param source [Object] the source string
|
105
|
+
# @param args [Array] the arguments passed to the method
|
76
106
|
def determine_select_range source, args
|
77
107
|
arg = args[0]
|
78
108
|
case arg
|
@@ -11,6 +11,11 @@ module Contrast
|
|
11
11
|
# tags are unaffected beyond any merging of overlapping tags.
|
12
12
|
class Splat < Contrast::Agent::Assess::Policy::Propagator::Base
|
13
13
|
class << self
|
14
|
+
# @param propagation_node [Contrast::Agent::Assess::Policy::PropagationNode] the node responsible for the
|
15
|
+
# propagation action required by this method.
|
16
|
+
# @param preshift [Object] pre call state of the things.
|
17
|
+
# @param target [Object] the object to which the source is being appended
|
18
|
+
# @return [Object] the target with the tags applied
|
14
19
|
def propagate propagation_node, preshift, target
|
15
20
|
tracked_inputs = []
|
16
21
|
|
@@ -28,6 +33,11 @@ module Contrast
|
|
28
33
|
Contrast::Agent::Assess::Tracker.properties(target)&.cleanup_tags
|
29
34
|
end
|
30
35
|
|
36
|
+
# Handles the splatting of tags from the tracked inputs to the target.
|
37
|
+
#
|
38
|
+
# @param tracked_inputs [Array<String>] storage for the inputs to act on later
|
39
|
+
# @param target [Object] the object to which the source is being appended
|
40
|
+
# @return [Object] the target with the tags applied
|
31
41
|
def splat_tags tracked_inputs, target
|
32
42
|
return if tracked_inputs.empty?
|
33
43
|
return unless (properties = Contrast::Agent::Assess::Tracker.properties!(target))
|
@@ -22,19 +22,23 @@ module Contrast
|
|
22
22
|
@tags << SOURCE_TAG
|
23
23
|
end
|
24
24
|
|
25
|
+
# @return [String]
|
25
26
|
def node_class
|
26
27
|
SOURCE
|
27
28
|
end
|
28
29
|
|
29
30
|
# This is confusing. Sources are Creation action but
|
30
31
|
# Propagation type. Oh and also Type refers to input type,
|
31
|
-
# like parameter, so we have to call this node_type.
|
32
|
+
# like parameter, so we have to call this node_type. :-/\
|
33
|
+
#
|
34
|
+
# @return [Symbol]
|
32
35
|
def node_type
|
33
36
|
:TYPE_PROPAGATION
|
34
37
|
end
|
35
38
|
|
36
39
|
# Standard validation + TS trace version two rules:
|
37
40
|
# Must have source and type
|
41
|
+
#
|
38
42
|
# @raise[ArgumentError] raises if any of the required fields is missing or invalid
|
39
43
|
def validate
|
40
44
|
super
|
@@ -16,6 +16,10 @@ module Contrast
|
|
16
16
|
# ActionDispatch::Http::Headers to convert keys like `referer` to `HTTP_REFERER` before they get to the
|
17
17
|
# Rack::Request#get_header method
|
18
18
|
# https://bitbucket.org/contrastsecurity/assess-specifications/src/master/rules/dataflow/reflected_xss.md
|
19
|
+
#
|
20
|
+
# @param tag [String] the tag to be applied.
|
21
|
+
# @param source_type [String] the type of the source.
|
22
|
+
# @param source_name [String] the name of the source.
|
19
23
|
def self.valid? tag, source_type, source_name
|
20
24
|
return true unless tag == 'CROSS_SITE'
|
21
25
|
return false if source_type == Contrast::Agent::Assess::Policy::SourceMethod::HEADER_KEY_TYPE
|
@@ -24,6 +24,12 @@ module Contrast
|
|
24
24
|
}.cs__freeze
|
25
25
|
TEMPLATE_PROPAGATION_NODE = Contrast::Agent::Assess::Policy::PropagationNode.new(NODE_HASH)
|
26
26
|
|
27
|
+
# @param trigger_node [Contrast::Agent::Assess::Policy::MethodPolicy] the node that governs this
|
28
|
+
# propagation event.
|
29
|
+
# @param _source [Object] the source of the propagation
|
30
|
+
# @param object [Object] the object to which the source is being appended
|
31
|
+
# @param args [Array] the arguments to the method
|
32
|
+
# @param ret [Object] the return value of the method
|
27
33
|
def xss_tilt_trigger trigger_node, _source, object, ret, *args
|
28
34
|
return unless (properties = Contrast::Agent::Assess::Tracker.properties!(ret))
|
29
35
|
|
@@ -63,6 +69,11 @@ module Contrast
|
|
63
69
|
|
64
70
|
private
|
65
71
|
|
72
|
+
# @param scope [Object] the scope of the template
|
73
|
+
# @param erb_template_prerender [String] the template string
|
74
|
+
# @param properties [Contrast::Agent::Assess::Policy::Properties] the properties of the return value
|
75
|
+
# @param interpolated_inputs [Array<String>] the interpolated inputs
|
76
|
+
# @param ret [String] the return value of the method
|
66
77
|
def handle_binding_variables scope, erb_template_prerender, ret, properties, interpolated_inputs
|
67
78
|
binding_variables = scope.instance_variables
|
68
79
|
|
@@ -81,6 +92,11 @@ module Contrast
|
|
81
92
|
end
|
82
93
|
end
|
83
94
|
|
95
|
+
# @param args [Array<String>] the arguments to the method
|
96
|
+
# @param erb_template_prerender [String] the template string
|
97
|
+
# @param ret [String] the return value of the method
|
98
|
+
# @param properties [Contrast::Agent::Assess::Policy::Properties] the properties of the return value
|
99
|
+
# @param interpolated_inputs [Array<String>] the interpolated inputs
|
84
100
|
def handle_local_variables args, erb_template_prerender, ret, properties, interpolated_inputs
|
85
101
|
locals = args[1]
|
86
102
|
locals.each do |local_name, local_value|
|
@@ -15,12 +15,26 @@ module Contrast
|
|
15
15
|
# a finding if so.
|
16
16
|
class Xpath
|
17
17
|
class << self
|
18
|
+
# @param trigger_node [Contrast::Agent::Assess::Policy::MethodPolicy] the node that governs this
|
19
|
+
# propagation event.
|
20
|
+
# @param _source [Object] the source of the propagation
|
21
|
+
# @param object [Object] the object to which the source is being appended
|
22
|
+
# @param args [Array<Object>] the arguments to the method
|
23
|
+
# @param ret [Object] the return value of the method
|
24
|
+
# @return [Object] the return value of the method
|
18
25
|
def xpath_expression_trigger trigger_node, _source, object, ret, *args
|
19
26
|
return ret unless args
|
20
27
|
|
21
28
|
process(trigger_node, object, ret, *args)
|
22
29
|
end
|
23
30
|
|
31
|
+
# @param trigger_node [Contrast::Agent::Assess::Policy::MethodPolicy] the node that governs this
|
32
|
+
# propagation event.
|
33
|
+
# @param _source [Object] the source of the propagation
|
34
|
+
# @param object [Object] the object to which the source is being appended
|
35
|
+
# @param args [Array<Object>] the arguments to the method
|
36
|
+
# @param ret [Object] the return value of the method
|
37
|
+
# @return [Object] the return value of the method
|
24
38
|
def xpath_oga_trigger trigger_node, _source, object, ret, *args
|
25
39
|
return ret unless args
|
26
40
|
|
@@ -32,6 +46,11 @@ module Contrast
|
|
32
46
|
|
33
47
|
private
|
34
48
|
|
49
|
+
# @param trigger_node [Contrast::Agent::Assess::Policy::MethodPolicy] the node that governs this
|
50
|
+
# propagation event.
|
51
|
+
# @param object [Object] the object to which the source is being appended
|
52
|
+
# @param ret [Object] the return value of the method
|
53
|
+
# @param args [Array<Object>] the arguments to the method
|
35
54
|
def process trigger_node, object, ret, *args
|
36
55
|
args.each do |arg|
|
37
56
|
next unless arg.cs__is_a?(String) || arg.cs__is_a?(Symbol)
|
@@ -178,6 +178,10 @@ module Contrast
|
|
178
178
|
Contrast::SETTINGS.excluder.assess_excluded_by_url_and_rule?(rule_id)
|
179
179
|
end
|
180
180
|
|
181
|
+
# Check if the finding should be excluded due to the assess exclusion rules.
|
182
|
+
#
|
183
|
+
# @param finding [Contrast::Agent::Reporting::Finding]
|
184
|
+
# @param rule_id [String]
|
181
185
|
def excluded_by_input_and_rule? finding, rule_id
|
182
186
|
return false unless Contrast::SETTINGS.excluder.exclusions.any?
|
183
187
|
return unless Contrast::Agent::REQUEST_TRACKER.current
|
@@ -185,7 +189,9 @@ module Contrast
|
|
185
189
|
Contrast::SETTINGS.excluder.assess_excluded_by_input_and_rule?(finding, rule_id)
|
186
190
|
end
|
187
191
|
|
188
|
-
# Handles the Stored Xss rule. If a vector is stored in the database
|
192
|
+
# Handles the Stored Xss rule. If a vector is stored in the database.
|
193
|
+
#
|
194
|
+
# @param finding [Contrast::Agent::Reporting::Finding]
|
189
195
|
def check_for_stored_xss finding
|
190
196
|
return unless finding && finding.rule_id == 'reflected-xss'
|
191
197
|
|
@@ -200,6 +206,7 @@ module Contrast
|
|
200
206
|
end
|
201
207
|
end
|
202
208
|
|
209
|
+
# @param finding [Contrast::Agent::Reporting::Finding]
|
203
210
|
def extract_dynamic_source_info finding
|
204
211
|
return unless finding
|
205
212
|
|
@@ -29,7 +29,16 @@ module Contrast
|
|
29
29
|
# from the application. Some rules rely on Content-Type validation.
|
30
30
|
COLLECTABLE_RULES = %w[reflected-xss].cs__freeze
|
31
31
|
|
32
|
-
|
32
|
+
# @return [String]
|
33
|
+
attr_reader :rule_id
|
34
|
+
# @return [Array<String>]
|
35
|
+
attr_reader :required_tags
|
36
|
+
# @return [Array<String>]
|
37
|
+
attr_reader :disallowed_tags
|
38
|
+
# @return [Array<String>]
|
39
|
+
attr_reader :good_value
|
40
|
+
# @return [Array<String>]
|
41
|
+
attr_reader :bad_value
|
33
42
|
|
34
43
|
ENCODER_START = 'CUSTOM_ENCODED_'
|
35
44
|
# By default, any rule will be triggered if the source
|
@@ -46,6 +55,7 @@ module Contrast
|
|
46
55
|
LIMITED_CHARS = 'LIMITED_CHARS'
|
47
56
|
CUSTOM_ENCODED = 'CUSTOM_ENCODED'
|
48
57
|
CUSTOM_VALIDATED = 'CUSTOM_VALIDATED'
|
58
|
+
|
49
59
|
def initialize trigger_hash = {}, rule_hash = {}
|
50
60
|
super(trigger_hash)
|
51
61
|
good_value = trigger_hash[JSON_GOOD_VALUE]
|
@@ -16,6 +16,10 @@ module Contrast
|
|
16
16
|
NEGATIVE_INFINITY = -POSITIVE_INFINITY
|
17
17
|
|
18
18
|
class << self
|
19
|
+
# @param _patcher [Contrast::Agent::Patcher] the patcher instance
|
20
|
+
# @param object [Object] the object that was called
|
21
|
+
# @param _ret [Object] the return value of the method
|
22
|
+
# @param args [Array<Object>] the arguments passed to the method
|
19
23
|
def valid? _patcher, object, _ret, args
|
20
24
|
# Can arrive here from either:
|
21
25
|
# regexp =~ string
|
@@ -21,6 +21,12 @@ module Contrast
|
|
21
21
|
# a valid URL in which the User controls a section prior to the
|
22
22
|
# querystring
|
23
23
|
# https://bitbucket.org/contrastsecurity/assess-specifications/src/master/rules/dataflow/server_side_request_forgery.md
|
24
|
+
#
|
25
|
+
# @param patcher [Contrast::Agent::Patcher] the patcher instance
|
26
|
+
# @param _object [Object] the object that was called
|
27
|
+
# @param _ret [Object] the return value of the method
|
28
|
+
# @param args [Array<Object>] the arguments passed to the method
|
29
|
+
# @return [Boolean] true if the finding is valid, false otherwise
|
24
30
|
def self.valid? patcher, _object, _ret, args
|
25
31
|
return true if patcher.id.to_s.start_with?(PATH_ONLY_PATCH_MARKER)
|
26
32
|
|
@@ -16,6 +16,12 @@ module Contrast
|
|
16
16
|
# A finding is valid for XSS if the response type is not one of
|
17
17
|
# those assumed to be safe
|
18
18
|
# https://bitbucket.org/contrastsecurity/assess-specifications/src/master/rules/dataflow/reflected_xss.md
|
19
|
+
#
|
20
|
+
# @param patcher [Contrast::Agent::Patcher] the patcher instance
|
21
|
+
# @param _object [Object] the object that was called
|
22
|
+
# @param _ret [Object] the return value of the method
|
23
|
+
# @param args [Array<Object>] the arguments passed to the method
|
24
|
+
# @return [Boolean] true if the finding is valid, false otherwise
|
19
25
|
def self.valid? _patcher, _object, _ret, _args
|
20
26
|
content_type = Contrast::Agent::REQUEST_TRACKER.current&.response&.content_type
|
21
27
|
return false unless content_type
|
@@ -73,7 +73,8 @@ module Contrast
|
|
73
73
|
finding.routes << context.discovered_route if context&.discovered_route
|
74
74
|
build_evidence(evidence, finding)
|
75
75
|
finding.request = Contrast::Agent::Reporting::FindingRequest.convert(context.request) if context&.request
|
76
|
-
|
76
|
+
# Hash must be built last so that finding has full context.
|
77
|
+
hash = Contrast::Utils::HashDigest.generate_response_hash(finding, context&.request)
|
77
78
|
finding.hash_code = Contrast::Utils::StringUtils.force_utf8(hash)
|
78
79
|
finding
|
79
80
|
end
|
@@ -24,7 +24,7 @@ module Contrast
|
|
24
24
|
#
|
25
25
|
# @param event [Contrast::Agent::Reporting::ReportingEvent] One of the DTMs valid for the
|
26
26
|
# event field of Contrast::Agent::Reporting::ReportingEvent
|
27
|
-
# @param response_data [Net::
|
27
|
+
# @param response_data [Net::HTTPResponse]
|
28
28
|
def audit_event event, response_data = nil
|
29
29
|
return unless ::Contrast::API.request_audit_requests || ::Contrast::API.request_audit_responses
|
30
30
|
|
@@ -69,21 +69,46 @@ module Contrast
|
|
69
69
|
# @param event [Contrast::Agent::Reporting::ReportingEvent] The event to send to TeamServer. Really a
|
70
70
|
# child of the ReportingEvent rather than a literal one.
|
71
71
|
# @param connection [Net::HTTP] open connection
|
72
|
-
# @return response [Net::HTTP::Response, nil] response from TS if no response
|
73
72
|
def send_event event, connection
|
74
73
|
return unless connection
|
75
74
|
return unless event.valid?
|
76
75
|
|
77
76
|
logger.debug('[Reporter] Preparing to send reporting event', event_class: event.cs__class)
|
78
77
|
request = build_request(event)
|
79
|
-
|
78
|
+
begin
|
79
|
+
response = connection.request(request)
|
80
|
+
rescue StandardError => e
|
81
|
+
handle_response_error(event, connection, e)
|
82
|
+
return
|
83
|
+
end
|
80
84
|
audit.audit_event(event, response) if ::Contrast::API.request_audit_enable
|
81
|
-
|
82
|
-
process_preflight_response(event, response, connection)
|
83
|
-
report_configuration(response, event)
|
85
|
+
process_event_response(event, response, connection)
|
84
86
|
response
|
85
87
|
rescue StandardError => e
|
86
|
-
|
88
|
+
logger.error('[Reporter] Error while processing event sent to TeamServer',
|
89
|
+
error: e,
|
90
|
+
event_type: event&.cs__class)
|
91
|
+
end
|
92
|
+
|
93
|
+
# Only two types of events require processing when returned from TeamServer; preflight and settings.
|
94
|
+
# For Settings, we want to update internally and then report the resulting configuration.
|
95
|
+
# For preflight, we want to process the response and send findings if requested.
|
96
|
+
#
|
97
|
+
# @param event [Contrast::Agent::Reporting::ReportingEvent] The event sent to TeamServer
|
98
|
+
# @param response [Net::HTTPResponse] the response from TeamServer
|
99
|
+
# @param connection [Net::HTTP] open connection
|
100
|
+
def process_event_response event, response, connection
|
101
|
+
case event
|
102
|
+
when Contrast::Agent::Reporting::Preflight
|
103
|
+
process_preflight_response(event, response, connection)
|
104
|
+
when Contrast::Agent::Reporting::AgentStartup,
|
105
|
+
Contrast::Agent::Reporting::ApplicationStartup,
|
106
|
+
Contrast::Agent::Reporting::ApplicationSettings,
|
107
|
+
Contrast::Agent::Reporting::ServerSettings
|
108
|
+
|
109
|
+
process_settings_response(response, event)
|
110
|
+
report_configuration(response, event)
|
111
|
+
end
|
87
112
|
end
|
88
113
|
|
89
114
|
# Write effective config to file:
|
@@ -91,7 +116,7 @@ module Contrast
|
|
91
116
|
# 200 or 304. In case of 304 there will be no new settings and we can write current ones.
|
92
117
|
# This is done on every settings request.
|
93
118
|
#
|
94
|
-
# @param response [
|
119
|
+
# @param response [Net::HTTPResponse, nil]
|
95
120
|
# @param event [Contrast::Agent::Reporting::ReportingEvent]
|
96
121
|
def report_configuration response, event
|
97
122
|
return unless response
|
@@ -109,14 +134,17 @@ module Contrast
|
|
109
134
|
logger.error('[Reporter] Error while reporting configuration', error: e, event_type: event&.cs__class)
|
110
135
|
end
|
111
136
|
|
137
|
+
# @return [Contrast::Agent::Reporting::ConnectionStatus]
|
112
138
|
def status
|
113
139
|
@_status ||= Contrast::Agent::Reporting::ConnectionStatus.new
|
114
140
|
end
|
115
141
|
|
142
|
+
# @return [Contrast::Agent::Reporting::ResponseHandler]
|
116
143
|
def response_handler
|
117
144
|
@_response_handler ||= Contrast::Agent::Reporting::ResponseHandler.new
|
118
145
|
end
|
119
146
|
|
147
|
+
# @return [Contrast::Config::Diagnostics::Monitor]
|
120
148
|
def diagnostics
|
121
149
|
@_diagnostics ||= Contrast::Config::Diagnostics::Monitor.new(Contrast::LOGGER.path)
|
122
150
|
end
|
@@ -90,7 +90,8 @@ module Contrast
|
|
90
90
|
# Handles response processing and sets status
|
91
91
|
#
|
92
92
|
# @param event [Contrast::Agent::Reporting::ReportingEvent] The event sent to TeamServer.
|
93
|
-
# @param response [Net::
|
93
|
+
# @param response [Net::HTTPResponse]
|
94
|
+
# @return [Contrast::Agent::Reporting::Response]
|
94
95
|
def process_settings_response response, event
|
95
96
|
res = response_handler.process(response, event)
|
96
97
|
if res
|
@@ -115,7 +116,6 @@ module Contrast
|
|
115
116
|
return unless response&.body && connection
|
116
117
|
|
117
118
|
findings_to_return = response.body.split(',').delete_if { |el| el.include?('*') }
|
118
|
-
mode.resend.reset_rescue_attempts
|
119
119
|
findings_to_return.each do |index|
|
120
120
|
preflight_message = event.messages[index.to_i]
|
121
121
|
preflight_data = preflight_message&.data
|
@@ -127,6 +127,12 @@ module Contrast
|
|
127
127
|
|
128
128
|
send_event(corresponding_finding, connection)
|
129
129
|
end
|
130
|
+
|
131
|
+
# Event rejected traces should be removed from the set.
|
132
|
+
# We could clean this up to know if the message was already deleted by the loop above.
|
133
|
+
event.messages&.each do |preflight_message|
|
134
|
+
Contrast::Agent::Reporting::ReportingStorage.delete(preflight_message&.data)
|
135
|
+
end
|
130
136
|
rescue StandardError => e
|
131
137
|
logger.error('[Reporter] Unable to handle preflight response', e)
|
132
138
|
end
|
@@ -44,12 +44,7 @@ module Contrast
|
|
44
44
|
def delete key
|
45
45
|
return unless key
|
46
46
|
|
47
|
-
|
48
|
-
|
49
|
-
deleted_value = collection.delete(key)
|
50
|
-
logger.debug('Key wasn\'t found') unless deleted_value
|
51
|
-
|
52
|
-
deleted_value
|
47
|
+
collection.delete(key)
|
53
48
|
end
|
54
49
|
|
55
50
|
# @param rule_id [String] the rule_id
|
@@ -20,11 +20,12 @@ module Contrast
|
|
20
20
|
|
21
21
|
# Process the response from TS
|
22
22
|
#
|
23
|
-
# @param response [Net::
|
23
|
+
# @param response [Net::HTTPResponse, nil]
|
24
24
|
# @param event [Contrast::Agent::Reporting::ReportingEvent] The event sent to TeamServer.
|
25
|
-
# @return response [Net::
|
25
|
+
# @return response [Net::HTTPResponse, nil]
|
26
26
|
def process response, event
|
27
27
|
logger.debug('[Reporter] Received a response')
|
28
|
+
return if event&.cs__is_a?(Contrast::Agent::Reporting::Finding)
|
28
29
|
return unless analyze_response?(response)
|
29
30
|
|
30
31
|
# Handle the response body and obtain server_features or app_settings
|
@@ -107,7 +108,7 @@ module Contrast
|
|
107
108
|
# Handles the errors code received from TS and takes appropriate action.
|
108
109
|
# If we are here the response.code is an error that needs handling [4XX]
|
109
110
|
#
|
110
|
-
# @param response [Net::
|
111
|
+
# @param response [Net::HTTPResponse]
|
111
112
|
def handle_error response
|
112
113
|
case response&.code
|
113
114
|
when ERROR_CODES[:message_not_sent]
|