contrast-agent 7.5.0 → 7.6.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/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/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 +1 -1
- data/lib/contrast/agent/reporting/reporting_utilities/reporter_client_utils.rb +1 -1
- data/lib/contrast/agent/reporting/reporting_utilities/response_handler.rb +3 -3
- data/lib/contrast/agent/reporting/reporting_utilities/response_handler_utils.rb +18 -6
- 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/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 +23 -22
@@ -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
|
@@ -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,7 +69,7 @@ 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::
|
72
|
+
# @return response [Net::HTTPResponse, nil] response from TS if no response
|
73
73
|
def send_event event, connection
|
74
74
|
return unless connection
|
75
75
|
return unless event.valid?
|
@@ -90,7 +90,7 @@ 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
94
|
def process_settings_response response, event
|
95
95
|
res = response_handler.process(response, event)
|
96
96
|
if res
|
@@ -20,9 +20,9 @@ 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
28
|
return unless analyze_response?(response)
|
@@ -107,7 +107,7 @@ module Contrast
|
|
107
107
|
# Handles the errors code received from TS and takes appropriate action.
|
108
108
|
# If we are here the response.code is an error that needs handling [4XX]
|
109
109
|
#
|
110
|
-
# @param response [Net::
|
110
|
+
# @param response [Net::HTTPResponse]
|
111
111
|
def handle_error response
|
112
112
|
case response&.code
|
113
113
|
when ERROR_CODES[:message_not_sent]
|
@@ -68,7 +68,7 @@ module Contrast
|
|
68
68
|
|
69
69
|
# check if response code is valid before analyze it
|
70
70
|
#
|
71
|
-
# @param response [Net::
|
71
|
+
# @param response [Net::HTTPResponse, nil]
|
72
72
|
# @return [Boolean]
|
73
73
|
def analyze_response? response
|
74
74
|
# Code descriptions:
|
@@ -118,7 +118,7 @@ module Contrast
|
|
118
118
|
@_last_response_code = response_code
|
119
119
|
return true if ANALYZE_WHEN.include?(response_code)
|
120
120
|
|
121
|
-
handle_error(response) if ERROR_CODES.value?(response_code)
|
121
|
+
handle_error(response) if ERROR_CODES.value?(response_code) && response&.body
|
122
122
|
# There was error, so analyze the Error and nothing more.
|
123
123
|
false
|
124
124
|
end
|
@@ -126,7 +126,7 @@ module Contrast
|
|
126
126
|
# Analyze the headers of the response code. They have information about the
|
127
127
|
# retry timeout and some response bodies contains error messages.
|
128
128
|
#
|
129
|
-
# @param response [
|
129
|
+
# @param response [Net::HTTPResponse]
|
130
130
|
# Integer
|
131
131
|
# @param message [String] Message to log.
|
132
132
|
# @param mode [Symbol, nil]
|
@@ -142,6 +142,8 @@ module Contrast
|
|
142
142
|
error_message: error_message || 'none',
|
143
143
|
auth_error: auth_error || 'none')
|
144
144
|
end
|
145
|
+
return unless rejected_by_ts?(response)
|
146
|
+
|
145
147
|
suspend_reporting(message, ready_after, error_message) if mode == @_mode.resending
|
146
148
|
return unless mode == @_mode.disabled
|
147
149
|
|
@@ -152,7 +154,7 @@ module Contrast
|
|
152
154
|
|
153
155
|
# Extract what we've received.
|
154
156
|
#
|
155
|
-
# @param response [Net::
|
157
|
+
# @param response [Net::HTTPResponse, nil]
|
156
158
|
# @return [Array<String, Integer>] all collected error info.
|
157
159
|
def extract_response_info response
|
158
160
|
# Extract what we got from the response:
|
@@ -164,11 +166,21 @@ module Contrast
|
|
164
166
|
[ready_after.to_i, error_message, auth_error]
|
165
167
|
end
|
166
168
|
|
169
|
+
# We only want to shut down the agent if TeamServer actually told us to, not because of a network error
|
170
|
+
#
|
171
|
+
# @param [Net::HTTPResponse]
|
172
|
+
# @return Boolean
|
173
|
+
def rejected_by_ts? response
|
174
|
+
response_body = response&.body || Contrast::Utils::ObjectShare::EMPTY_STRING
|
175
|
+
response_data = Contrast::Utils::Json.parse(response_body, deep_symbolize: true)
|
176
|
+
response_data.key?(:success) && response_data[:success] == false
|
177
|
+
end
|
178
|
+
|
167
179
|
# Extract Last-Modified header from ServerSettings response.
|
168
180
|
# The new GET server settings endpoint have different payload.
|
169
181
|
# Extract the last modify headers with last update form TS.
|
170
182
|
#
|
171
|
-
# @param response [Net::
|
183
|
+
# @param response [Net::HTTPResponse, nil]
|
172
184
|
# @param event [Contrast::Agent::Reporting::ServerSettings,
|
173
185
|
# Contrast::Agent::Reporting::ApplicationSettings, nil]
|
174
186
|
# @return last_modified[integer, nil] Time since last server update
|
@@ -250,7 +262,7 @@ module Contrast
|
|
250
262
|
#
|
251
263
|
# This method works to extract away these differences.
|
252
264
|
#
|
253
|
-
# @param response [Net::
|
265
|
+
# @param response [Net::HTTPResponse, nil]
|
254
266
|
# @param event [Contrast::Agent::Reporting::ReportingEvent] The event sent to TeamServer.
|
255
267
|
# @return response [Contrast::Agent::Reporting::Response]
|
256
268
|
def convert_response response, event
|
@@ -26,6 +26,7 @@ module Contrast
|
|
26
26
|
#
|
27
27
|
def report_observed_route
|
28
28
|
return unless (reporter = Contrast::Agent.reporter)
|
29
|
+
return if Contrast::Agent::REQUEST_TRACKER.current&.response&.response_code == 404
|
29
30
|
|
30
31
|
reporter.send_event(context.observed_route) if Contrast::ROUTES_SENT.sendable?(context.observed_route)
|
31
32
|
end
|
@@ -124,7 +124,7 @@ module Contrast
|
|
124
124
|
|
125
125
|
# @return [Contrast::Components::Assess::Interface]
|
126
126
|
def assess
|
127
|
-
@assess ||= Contrast::Components::
|
127
|
+
@assess ||= Contrast::Components::Settings::Interface.new # rubocop:disable Naming/MemoizedInstanceVariableName
|
128
128
|
end
|
129
129
|
|
130
130
|
# @return [Contrast::Components::Inventory::Interface]
|
@@ -91,6 +91,15 @@ module Contrast
|
|
91
91
|
rescue Contrast::SecurityException => e
|
92
92
|
logger.trace('Security Exception raised during application lifecycle to prevent an attack', e)
|
93
93
|
raise(e)
|
94
|
+
rescue StandardError => e
|
95
|
+
# If there is a routing error of this type, then we cannot find a method explicitly mapped to this route.
|
96
|
+
# In this case, we should report nothing.
|
97
|
+
if Contrast::Utils::ClassUtil.truly_defined?('ActionController::RoutingError') &&
|
98
|
+
e.is_a?(ActionController::RoutingError)
|
99
|
+
|
100
|
+
Contrast::Agent::REQUEST_TRACKER.current&.observed_route = nil
|
101
|
+
end
|
102
|
+
raise(e)
|
94
103
|
end
|
95
104
|
end
|
96
105
|
end
|
@@ -25,8 +25,9 @@ module Contrast
|
|
25
25
|
# @param route [Contrast::Agent::Reporting::ObservedRoute] the route
|
26
26
|
# @return [boolean]
|
27
27
|
def sendable? route
|
28
|
-
return false
|
29
|
-
return false
|
28
|
+
return false unless route
|
29
|
+
return false unless route.signature && !route.signature.blank?
|
30
|
+
return false unless route.url && !route.url.blank?
|
30
31
|
|
31
32
|
route_hash = route.hash_id
|
32
33
|
|