contrast-agent 5.0.0 → 5.1.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 -2
- data/ext/cs__assess_module/cs__assess_module.c +41 -31
- data/lib/contrast/agent/assess/policy/propagator/match_data.rb +4 -0
- data/lib/contrast/agent/assess/rule/response/autocomplete_rule.rb +0 -62
- data/lib/contrast/agent/assess/rule/response/base_rule.rb +83 -3
- data/lib/contrast/agent/assess/rule/response/cachecontrol_rule.rb +184 -0
- data/lib/contrast/agent/assess/rule/response/clickjacking_rule.rb +66 -0
- data/lib/contrast/agent/assess/rule/response/csp_header_insecure_rule.rb +101 -0
- data/lib/contrast/agent/assess/rule/response/csp_header_missing_rule.rb +46 -0
- data/lib/contrast/agent/assess/rule/response/hsts_header_rule.rb +60 -0
- data/lib/contrast/agent/assess/rule/response/parameters_pollution_rule.rb +60 -0
- data/lib/contrast/agent/assess/rule/response/x_content_type_rule.rb +52 -0
- data/lib/contrast/agent/assess/rule/response/x_xss_protection_rule.rb +53 -0
- data/lib/contrast/agent/reporting/reporting_events/finding.rb +11 -1
- data/lib/contrast/agent/request_context_extend.rb +13 -0
- data/lib/contrast/agent/version.rb +1 -1
- data/lib/contrast/extension/assess/erb.rb +17 -0
- data/resources/assess/policy.json +10 -0
- data/ruby-agent.gemspec +1 -1
- metadata +19 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c756fb0e7fe0433c7507c2cdb7082e635adabea8847c545333d65800225b1e3d
|
4
|
+
data.tar.gz: c71e1a8d020e3dfe98ea58d844924b0b0a9e1576b5f6899d186abc28e4efb152
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 82a0a252fa7696b2590f8be43fba09e03c6b13371a58a678b31e088e01a244311e0ac9d5b077de5d7970c16bc9a2144504d01a0e037c5e0f0b06d06e8530c57a
|
7
|
+
data.tar.gz: d9fd4708ab8238a22a2e3187e880a898635739b00f0d2f4f224904325f3b88b28f3470cf37733d5875e97018a02a5bfe10bd7f041219fd842b596e9697bfdbb6
|
data/.simplecov
CHANGED
@@ -1,9 +1,8 @@
|
|
1
1
|
# Copyright (c) 2022 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
SimpleCov.minimum_coverage line:
|
4
|
+
SimpleCov.minimum_coverage line: 95
|
5
5
|
SimpleCov.start do
|
6
6
|
add_filter '/spec/'
|
7
|
-
add_filter '/lib/contrast/extension/assess/erb.rb'
|
8
7
|
enable_coverage :branch
|
9
8
|
end
|
@@ -61,39 +61,39 @@ VALUE
|
|
61
61
|
contrast_assess_module_prepend(const int argc, const VALUE *argv,
|
62
62
|
const VALUE self) {
|
63
63
|
|
64
|
-
rb_prepend_module(self, argv[0]);
|
64
|
+
// rb_prepend_module(self, argv[0]);
|
65
65
|
|
66
66
|
VALUE module_at;
|
67
67
|
VALUE rb_incl_in_mod_ary = rb_funcall(self, rb_intern("included_in"), 0);
|
68
68
|
|
69
69
|
if (RB_TYPE_P(rb_incl_in_mod_ary, T_ARRAY)) {
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
70
|
+
int i = 0;
|
71
|
+
int size = rb_funcall(rb_incl_in_mod_ary, rb_intern("length"), 0);
|
72
|
+
for (i = 0; i < size; ++i) {
|
73
|
+
module_at = rb_ary_entry(rb_incl_in_mod_ary, i);
|
74
|
+
if (RB_TYPE_P(module_at, T_MODULE)) {
|
75
|
+
rb_include_module(module_at, argv[0]);
|
76
|
+
}
|
77
|
+
}
|
78
78
|
}
|
79
79
|
return self;
|
80
80
|
}
|
81
81
|
|
82
82
|
VALUE
|
83
83
|
contrast_assess_module_included(const int argc, const VALUE *argv,
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
84
|
+
const VALUE self) {
|
85
|
+
VALUE frozen;
|
86
|
+
if (RB_TYPE_P(self, T_MODULE)) {
|
87
|
+
// check if frozen
|
88
|
+
frozen = rb_funcall(self, rb_intern("cs__frozen?"), 0);
|
89
|
+
if (frozen == Qfalse) {
|
90
|
+
VALUE ary = rb_funcall(self, rb_intern("included_in"), 0);
|
91
|
+
if (RB_TYPE_P(ary, T_ARRAY)) {
|
92
|
+
rb_ary_push(ary, argv[0]);
|
93
|
+
}
|
94
|
+
}
|
95
|
+
}
|
96
|
+
return self;
|
97
97
|
}
|
98
98
|
|
99
99
|
void Init_cs__assess_module(void) {
|
@@ -115,13 +115,23 @@ void Init_cs__assess_module(void) {
|
|
115
115
|
|
116
116
|
contrast_register_patch("Module", "module_eval",
|
117
117
|
contrast_assess_module_module_eval);
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
118
|
+
/*
|
119
|
+
* We patch these for better ancestors handling, and only for older ruby
|
120
|
+
* versions.
|
121
|
+
*/
|
122
|
+
// if (rb_ver_below_three()) {
|
123
|
+
/*
|
124
|
+
* `included` is a private method. We should make it public, patch it,
|
125
|
+
* and make our new method public
|
126
|
+
*/
|
127
|
+
// contrast_register_patch("Module", "included",
|
128
|
+
// contrast_assess_module_included);
|
129
|
+
/*
|
130
|
+
* The `prepend` patch may actually be the issue, if we're not properly
|
131
|
+
* passing along the call/context. It could be that my attempt to fix
|
132
|
+
* `included` left this section unreachable.
|
133
|
+
*/
|
134
|
+
// contrast_register_patch("Module", "prepend",
|
135
|
+
// contrast_assess_module_prepend);
|
136
|
+
// }
|
127
137
|
}
|
@@ -11,6 +11,10 @@ module Contrast
|
|
11
11
|
# are unaffected beyond any merging of overlapping tags.
|
12
12
|
class MatchData < Contrast::Agent::Assess::Policy::Propagator::Base
|
13
13
|
class << self
|
14
|
+
# This patch method is used to track through the MatchData#[] and
|
15
|
+
# MatchData#match methods, since the last was introduced after
|
16
|
+
# Ruby 3.1.0, but shares similar functionality, except it does not
|
17
|
+
# support ranges.
|
14
18
|
def square_bracket_tagger propagation_node, preshift, ret, _block
|
15
19
|
case ret
|
16
20
|
when Array
|
@@ -18,10 +18,6 @@ module Contrast
|
|
18
18
|
|
19
19
|
protected
|
20
20
|
|
21
|
-
HTML_PROP = 'html'.cs__freeze
|
22
|
-
START_PROP = 'start'.cs__freeze
|
23
|
-
END_PROP = 'end'.cs__freeze
|
24
|
-
|
25
21
|
# Rules discern which responses they can/should analyze.
|
26
22
|
#
|
27
23
|
# @param response [Contrast::Agent::Response] the response of the application
|
@@ -44,42 +40,6 @@ module Contrast
|
|
44
40
|
nil
|
45
41
|
end
|
46
42
|
|
47
|
-
# Find the forms in this body, if any, so as to determine if they violate this rule.
|
48
|
-
#
|
49
|
-
# @param body [String]
|
50
|
-
# @return [Array<Hash>] the forms of this body, as well as their start and end indexes.
|
51
|
-
def forms body
|
52
|
-
forms = []
|
53
|
-
body_start = 0
|
54
|
-
# The instance of "<form" in the body may be a form. Turn them into chunks to check.
|
55
|
-
potential_forms = body.split(form_start)
|
56
|
-
potential_forms.each do |potential_form|
|
57
|
-
# We can consider this a form if the next character is one of whitespace of form tag closing
|
58
|
-
# characters.
|
59
|
-
next unless potential_form
|
60
|
-
next unless form_openings.any? { |opening| potential_form.start_with?(opening) }
|
61
|
-
|
62
|
-
body_start = body.index(form_start, body_start)
|
63
|
-
next unless body_start
|
64
|
-
|
65
|
-
form_stop = potential_form.index('>').to_i
|
66
|
-
next unless form_stop
|
67
|
-
|
68
|
-
body_close = body_start + 6 + form_stop
|
69
|
-
forms << capture(body, body_start, body_close, form_stop)
|
70
|
-
body_start = body_close
|
71
|
-
end
|
72
|
-
forms
|
73
|
-
end
|
74
|
-
|
75
|
-
def form_start
|
76
|
-
/<form/i
|
77
|
-
end
|
78
|
-
|
79
|
-
def form_openings
|
80
|
-
[' ', "\n", "\r", "\t", '>']
|
81
|
-
end
|
82
|
-
|
83
43
|
def off_values
|
84
44
|
[/"off"/, /'off'/, /off /, /off>/]
|
85
45
|
end
|
@@ -100,28 +60,6 @@ module Contrast
|
|
100
60
|
end
|
101
61
|
true
|
102
62
|
end
|
103
|
-
|
104
|
-
# Capture the information needed to build the properties of this finding by parsing out from the body
|
105
|
-
#
|
106
|
-
# @param body [String] the entire HTTP Response body
|
107
|
-
# @param body_start [Integer] the start of the range to take from the body
|
108
|
-
# @param body_close [Integer] the end of the range to take from the body
|
109
|
-
# @param form_stop [Integer] the index of the end of the form from its start
|
110
|
-
# @return [Hash]
|
111
|
-
def capture body, body_start, body_close, form_stop
|
112
|
-
form = {}
|
113
|
-
# Capture the 50 characters in front of the form, or up to the start if the form starts before 50.
|
114
|
-
capture_start = body_start < 50 ? 0 : body_start - 50
|
115
|
-
# Start is where the '<form' is in the body
|
116
|
-
# 6 accounts for the characters in the form and the opening char
|
117
|
-
# potential_form.index('>') accounts for finding the rest of the form
|
118
|
-
# 50 accounts for the context to capture beyond
|
119
|
-
capture_close = body_close + 50
|
120
|
-
form[HTML_PROP] = body[capture_start...capture_close]
|
121
|
-
form[START_PROP] = body_start < 50 ? body_start : 50
|
122
|
-
form[END_PROP] = form[START_PROP] + 6 + form_stop
|
123
|
-
form
|
124
|
-
end
|
125
63
|
end
|
126
64
|
end
|
127
65
|
end
|
@@ -40,6 +40,11 @@ module Contrast
|
|
40
40
|
|
41
41
|
protected
|
42
42
|
|
43
|
+
DATA = 'data'.cs__freeze
|
44
|
+
HTML_PROP = 'html'.cs__freeze
|
45
|
+
START_PROP = 'start'.cs__freeze
|
46
|
+
END_PROP = 'end'.cs__freeze
|
47
|
+
|
43
48
|
# Rules discern which responses they can/should analyze.
|
44
49
|
#
|
45
50
|
# @param response [Contrast::Agent::Response] the response of the application
|
@@ -68,15 +73,24 @@ module Contrast
|
|
68
73
|
finding.rule_id = rule_id
|
69
74
|
context = Contrast::Agent::REQUEST_TRACKER.current
|
70
75
|
finding.routes << context.route if context&.route
|
71
|
-
evidence
|
72
|
-
finding.properties[key] = Contrast::Utils::StringUtils.force_utf8(value)
|
73
|
-
end
|
76
|
+
build_evidence evidence, finding
|
74
77
|
hash = Contrast::Utils::HashDigest.generate_config_hash(finding)
|
75
78
|
finding.hash_code = Contrast::Utils::StringUtils.force_utf8(hash)
|
76
79
|
finding.preflight = Contrast::Utils::PreflightUtil.create_preflight(finding)
|
77
80
|
finding
|
78
81
|
end
|
79
82
|
|
83
|
+
# This method allows to change the evidence we attach and the way we attach it
|
84
|
+
# Change it accordingly the rule you work on
|
85
|
+
#
|
86
|
+
# @param evidence [Hash] the properties required to build this finding.
|
87
|
+
# @param finding [Contrast::Api::Dtm::Finding] finding to attach the evidence to
|
88
|
+
def build_evidence evidence, finding
|
89
|
+
evidence.each_pair do |key, value|
|
90
|
+
finding.properties[key] = Contrast::Utils::StringUtils.force_utf8(value)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
80
94
|
# A rule is disabled if assess is off or it is turned off by TeamServer or by configuration.
|
81
95
|
#
|
82
96
|
# @return [Boolean]
|
@@ -109,6 +123,72 @@ module Contrast
|
|
109
123
|
def body? response
|
110
124
|
Contrast::Utils::StringUtils.present?(response.body)
|
111
125
|
end
|
126
|
+
|
127
|
+
# Capture the information needed to build the properties of this finding by parsing out from the body
|
128
|
+
#
|
129
|
+
# @param body [String] the entire HTTP Response body
|
130
|
+
# @param body_start [Integer] the start of the range to take from the body
|
131
|
+
# @param body_close [Integer] the end of the range to take from the body
|
132
|
+
# @param tag_stop [Integer] the index of the end of the html tag from its start
|
133
|
+
# @return [Hash]
|
134
|
+
def capture body, body_start, body_close, tag_stop
|
135
|
+
tag = {}
|
136
|
+
# Capture the 50 characters in front of the form, or up to the start if the form starts before 50.
|
137
|
+
capture_start = body_start < 50 ? 0 : body_start - 50
|
138
|
+
# Start is where the '<form' is in the body
|
139
|
+
# 6 accounts for the characters in the form and the opening char
|
140
|
+
# potential_form.index('>') accounts for finding the rest of the form
|
141
|
+
# 50 accounts for the context to capture beyond
|
142
|
+
capture_close = body_close + 50
|
143
|
+
tag[HTML_PROP] = body[capture_start...capture_close]
|
144
|
+
tag[START_PROP] = body_start < 50 ? body_start : 50
|
145
|
+
tag[END_PROP] = tag[START_PROP] + 6 + tag_stop
|
146
|
+
tag
|
147
|
+
end
|
148
|
+
|
149
|
+
# Find the forms in this body, if any, so as to determine if they violate this rule.
|
150
|
+
#
|
151
|
+
# @param body [String]
|
152
|
+
# @return [Array<Hash>] the forms of this body, as well as their start and end indexes.
|
153
|
+
def forms body
|
154
|
+
forms = []
|
155
|
+
body_start = 0
|
156
|
+
# The instance of "<form" in the body may be a form. Turn them into chunks to check.
|
157
|
+
potential_forms = body.split(form_start)
|
158
|
+
potential_forms.each do |potential_form|
|
159
|
+
# We can consider this a form if the next character is one of whitespace of form tag closing
|
160
|
+
# characters.
|
161
|
+
next unless potential_form
|
162
|
+
next unless form_openings.any? { |opening| potential_form.start_with?(opening) }
|
163
|
+
|
164
|
+
body_start = body.index(form_start, body_start)
|
165
|
+
next unless body_start
|
166
|
+
|
167
|
+
form_stop = potential_form.index('>').to_i
|
168
|
+
next unless form_stop
|
169
|
+
|
170
|
+
body_close = body_start + 6 + form_stop
|
171
|
+
forms << capture(body, body_start, body_close, form_stop)
|
172
|
+
body_start = body_close
|
173
|
+
end
|
174
|
+
forms
|
175
|
+
end
|
176
|
+
|
177
|
+
def form_start
|
178
|
+
/<form/i
|
179
|
+
end
|
180
|
+
|
181
|
+
def form_openings
|
182
|
+
[' ', "\n", "\r", "\t", '>']
|
183
|
+
end
|
184
|
+
|
185
|
+
# Determine if a response has headers.
|
186
|
+
#
|
187
|
+
# @param response [Contrast::Agent::Response] the response of the application
|
188
|
+
# @return [Boolean]
|
189
|
+
def headers? response
|
190
|
+
response.headers.cs__is_a?(Hash)
|
191
|
+
end
|
112
192
|
end
|
113
193
|
end
|
114
194
|
end
|
@@ -0,0 +1,184 @@
|
|
1
|
+
# Copyright (c) 2022 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'contrast/agent/assess/rule/response/base_rule'
|
5
|
+
require 'contrast/utils/string_utils'
|
6
|
+
require 'json'
|
7
|
+
|
8
|
+
module Contrast
|
9
|
+
module Agent
|
10
|
+
module Assess
|
11
|
+
module Rule
|
12
|
+
module Response
|
13
|
+
# These rules check the content of the HTTP Response to determine if the body or the headers include and/or
|
14
|
+
# set incorrectly the cache-control header
|
15
|
+
class Cachecontrol < BaseRule
|
16
|
+
def rule_id
|
17
|
+
'cache-controls-missing'
|
18
|
+
end
|
19
|
+
|
20
|
+
protected
|
21
|
+
|
22
|
+
HEADER_KEY = 'Cache-Control'.cs__freeze
|
23
|
+
ACCEPTED_VALUES = %w[no-store no-cache].cs__freeze
|
24
|
+
|
25
|
+
# Rules discern which responses they can/should analyze.
|
26
|
+
#
|
27
|
+
# @param response [Contrast::Agent::Response] the response of the application
|
28
|
+
def analyze_response? response
|
29
|
+
super && body?(response)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Determine if the Response violates the Rule or not. If it does, return the evidence that proves it so.
|
33
|
+
#
|
34
|
+
# @param response [Contrast::Agent::Response] the response of the application
|
35
|
+
# @return [Hash, nil] the evidence required to prove the violation of the rule
|
36
|
+
def violated? response
|
37
|
+
# This rule is violated if the header is not there
|
38
|
+
# or if it's there, but the value is not 'no-store' or 'no-cache'
|
39
|
+
headers = response.headers
|
40
|
+
header_key_sym = HEADER_KEY.to_sym
|
41
|
+
cache_control = headers[HEADER_KEY] || headers[header_key_sym]
|
42
|
+
if cache_control && !valid_header?(cache_control)
|
43
|
+
return to_cachecontrol_rule('header', 'cache-control', cache_control)
|
44
|
+
end
|
45
|
+
|
46
|
+
body = response.body
|
47
|
+
# check if the meta tag is include it
|
48
|
+
tags = meta_tags(body)
|
49
|
+
|
50
|
+
tags.each do |tag|
|
51
|
+
return to_cachecontrol_rule('meta', 'pragma', tag[HTML_PROP]) if meta_cache_tag? tag[HTML_PROP]
|
52
|
+
end
|
53
|
+
# we should return if header not presented and no tags are detected
|
54
|
+
return {} if !(headers.key?(HEADER_KEY) || headers.key?(header_key_sym)) && tags.empty?
|
55
|
+
|
56
|
+
nil
|
57
|
+
end
|
58
|
+
|
59
|
+
def valid_header? header
|
60
|
+
ACCEPTED_VALUES.any? { |val| header.include?(val) || header == val }
|
61
|
+
end
|
62
|
+
|
63
|
+
# Find the tags in this body, if any, so as to determine if they violate this rule.
|
64
|
+
#
|
65
|
+
# @param body [String,nil]
|
66
|
+
# @return [Array<Hash>] the tags of this body, as well as their start and end indexes.
|
67
|
+
def meta_tags body
|
68
|
+
tags = []
|
69
|
+
body_start = 0
|
70
|
+
|
71
|
+
# meta tags are stored in the <head></head> section
|
72
|
+
head_section = body&.split(head_tag)
|
73
|
+
return [] unless head_section
|
74
|
+
|
75
|
+
potential_tags = head_section.map { |el| el.split(meta_start) }
|
76
|
+
potential_tags.flatten.each do |potential_tag|
|
77
|
+
next unless potential_tag
|
78
|
+
next unless tag_openings.any? { |opening| potential_tag.starts_with?(opening) }
|
79
|
+
|
80
|
+
body_start = body.index(meta_start, body_start)
|
81
|
+
next unless body_start
|
82
|
+
|
83
|
+
tag_stop = potential_tag.index('>').to_i
|
84
|
+
next unless tag_stop
|
85
|
+
|
86
|
+
body_close = body_start + 6 + tag_stop
|
87
|
+
tags << capture(body, body_start, body_close, tag_stop)
|
88
|
+
body_start = body_close
|
89
|
+
end
|
90
|
+
tags
|
91
|
+
end
|
92
|
+
|
93
|
+
def meta_start
|
94
|
+
/<meta/i
|
95
|
+
end
|
96
|
+
|
97
|
+
def head_tag
|
98
|
+
/<head>/i
|
99
|
+
end
|
100
|
+
|
101
|
+
def tag_openings
|
102
|
+
[' ', "\n", "\r", "\t"]
|
103
|
+
end
|
104
|
+
|
105
|
+
def accepted_http_values
|
106
|
+
[/'cache-control'/i, /"cache-control"/i]
|
107
|
+
end
|
108
|
+
|
109
|
+
def accepted_values
|
110
|
+
[/'no-cache'/i, /"no-cache"/i, /"no-store"/i, /'no-store'/i]
|
111
|
+
end
|
112
|
+
|
113
|
+
# Determine if the given metatag does not have a valid cache-control tag.
|
114
|
+
# Meta tags has the option to set http-equiv and content to set the http response header
|
115
|
+
# to define for the document
|
116
|
+
#
|
117
|
+
# @param tag [String] the meta tag
|
118
|
+
# @return [Boolean, nil]
|
119
|
+
def meta_cache_tag? tag
|
120
|
+
# Here we should determine the index of the needed keys
|
121
|
+
# http-equiv and content
|
122
|
+
http_equiv_idx = tag =~ /http-equiv=/i
|
123
|
+
return false unless http_equiv_idx
|
124
|
+
|
125
|
+
content_idx = tag =~ /content=/i
|
126
|
+
return false unless content_idx
|
127
|
+
|
128
|
+
# determine the value of the http-equiv if it's cache-control
|
129
|
+
http_equiv_idx += 11
|
130
|
+
is_valid = accepted_http_values.any? { |el| (tag =~ el) == http_equiv_idx }
|
131
|
+
return false unless is_valid
|
132
|
+
|
133
|
+
content_idx += 8
|
134
|
+
return false if accepted_values.any? { |value| (tag =~ value) == content_idx }
|
135
|
+
|
136
|
+
true
|
137
|
+
end
|
138
|
+
|
139
|
+
# This method allows to change the evidence we attach and the way we attach it
|
140
|
+
# Change it accordingly the rule you work on
|
141
|
+
#
|
142
|
+
# @param evidence [Hash] the properties required to build this finding.
|
143
|
+
# @param finding [Contrast::Api::Dtm::Finding] finding to attach the evidence to
|
144
|
+
def build_evidence evidence, finding
|
145
|
+
evidence.each_pair do |key, value|
|
146
|
+
finding.properties[key] = Contrast::Utils::StringUtils.protobuf_format(value)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
# This method accepts the violation and transforms it to the proper hash
|
151
|
+
# before return in as violation
|
152
|
+
#
|
153
|
+
# @param type [String] String of Header or META of the type
|
154
|
+
# @param name [String] String of either cache-control or pragma
|
155
|
+
# @param value [String] String of the violated value
|
156
|
+
def to_cachecontrol_rule type, name, value
|
157
|
+
{ data: { type: type, name: name, value: value }.to_s }
|
158
|
+
end
|
159
|
+
|
160
|
+
# Capture the information needed to build the properties of this finding by parsing out from the body
|
161
|
+
#
|
162
|
+
# @param body [String] the entire HTTP Response body
|
163
|
+
# @param body_start [Integer] the start of the range to take from the body
|
164
|
+
# @param body_close [Integer] the end of the range to take from the body
|
165
|
+
# @param tag_stop [Integer] the index of the end of the html tag from its start
|
166
|
+
# @return [Hash]
|
167
|
+
def capture body, body_start, body_close, tag_stop
|
168
|
+
# In this situation we don't need to capture before and after the meta tag, as this may produce an error
|
169
|
+
# So if we capture 30-50 chars before and after the tag, we may capture part of the tag, we want to
|
170
|
+
# inspect and eventually this wil return wrong string. Because of that - we split the <head> and take
|
171
|
+
# each meta tag and examine it
|
172
|
+
tag = {}
|
173
|
+
# we dont need to capture here before or after the meta tag
|
174
|
+
tag[HTML_PROP] = body[body_start...body_close]
|
175
|
+
tag[START_PROP] = body_start
|
176
|
+
tag[END_PROP] = tag[START_PROP] + 6 + tag_stop
|
177
|
+
tag
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# Copyright (c) 2022 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'contrast/agent/assess/rule/response/base_rule'
|
5
|
+
require 'contrast/utils/string_utils'
|
6
|
+
|
7
|
+
module Contrast
|
8
|
+
module Agent
|
9
|
+
module Assess
|
10
|
+
module Rule
|
11
|
+
module Response
|
12
|
+
# These rules check the content of the HTTP Response to determine if the headers contains the required header
|
13
|
+
class Clickjacking < BaseRule
|
14
|
+
def rule_id
|
15
|
+
'clickjacking-control-missing'
|
16
|
+
end
|
17
|
+
|
18
|
+
protected
|
19
|
+
|
20
|
+
HEADER_KEY = 'X-Frame-Options'.cs__freeze
|
21
|
+
HEADER_KEY_SYM = HEADER_KEY.to_sym
|
22
|
+
ACCEPTED_VALUES = [/^deny/i, /^sameorigin/i].cs__freeze
|
23
|
+
|
24
|
+
# Rules discern which responses they can/should analyze.
|
25
|
+
#
|
26
|
+
# @param response [Contrast::Agent::Response] the response of the application
|
27
|
+
def analyze_response? response
|
28
|
+
super && headers?(response)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Determine if the Response violates the Rule or not. If it does, return the evidence that proves it so.
|
32
|
+
#
|
33
|
+
# @param response [Contrast::Agent::Response] the response of the application
|
34
|
+
# @return [Hash, nil] the evidence required to prove the violation of the rule
|
35
|
+
def violated? response
|
36
|
+
headers = response.headers
|
37
|
+
cache_control = headers[HEADER_KEY] || headers[HEADER_KEY_SYM]
|
38
|
+
return unsafe_response unless cache_control
|
39
|
+
return unsafe_response(cache_control) unless valid_header?(cache_control)
|
40
|
+
|
41
|
+
nil
|
42
|
+
end
|
43
|
+
|
44
|
+
def valid_header? header
|
45
|
+
ACCEPTED_VALUES.any? { |val| header =~ val }
|
46
|
+
end
|
47
|
+
|
48
|
+
def unsafe_response value = ''
|
49
|
+
{ data: value }
|
50
|
+
end
|
51
|
+
|
52
|
+
# Change it accordingly the rule you work on
|
53
|
+
#
|
54
|
+
# @param evidence [Hash] the properties required to build this finding.
|
55
|
+
# @param finding [Contrast::Api::Dtm::Finding] finding to attach the evidence to
|
56
|
+
def build_evidence evidence, finding
|
57
|
+
evidence.each_pair do |key, value|
|
58
|
+
finding.properties[key] = Contrast::Utils::StringUtils.protobuf_format(value)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# Copyright (c) 2022 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
|
2
|
+
# Copyright (c) 2021 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
|
3
|
+
# frozen_string_literal: true
|
4
|
+
|
5
|
+
require 'contrast/agent/assess/rule/response/base_rule'
|
6
|
+
require 'contrast/utils/string_utils'
|
7
|
+
|
8
|
+
module Contrast
|
9
|
+
module Agent
|
10
|
+
module Assess
|
11
|
+
module Rule
|
12
|
+
module Response
|
13
|
+
# These rules check that the HTTP Headers include CSP header types
|
14
|
+
class CspHeaderInsecure < BaseRule
|
15
|
+
def rule_id
|
16
|
+
'csp-header-insecure'
|
17
|
+
end
|
18
|
+
|
19
|
+
protected
|
20
|
+
|
21
|
+
CSP_HEADERS = %w[CONTENT_SECURITY_POLICY X_CONTENT_SECURITY_POLICY X_WEBKIT_CSP].cs__freeze
|
22
|
+
SETTINGS = %w[
|
23
|
+
base-uri child-src default-src connect-src frame-src media-src object-src script-src
|
24
|
+
style-src form-action frame-ancestors plugin-types reflected-xss referer
|
25
|
+
].cs__freeze
|
26
|
+
UNSAFE_VALUE_REGEXP = /^unsafe-(?:inline|eval)$/.cs__freeze
|
27
|
+
ASTERISK_REGEXP = /[*]/.cs__freeze
|
28
|
+
|
29
|
+
# Rules discern which responses they can/should analyze.
|
30
|
+
#
|
31
|
+
# @param response [Contrast::Agent::Response] the response of the application
|
32
|
+
def analyze_response? response
|
33
|
+
super && headers?(response)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Determine if the Response violates the Rule or not. If it does, return the evidence that proves it so.
|
37
|
+
#
|
38
|
+
# @param response [Contrast::Agent::Response] the response of the application
|
39
|
+
# @return [Contrast::Utils::ObjectShare::EMPTY_STRING, nil] if CSP Header is not found
|
40
|
+
def violated? response
|
41
|
+
settings = {}
|
42
|
+
csp_hash = get_csp_header_values(response.headers)
|
43
|
+
return if csp_hash.nil?
|
44
|
+
|
45
|
+
SETTINGS.each do |setting_attr|
|
46
|
+
value = csp_hash[setting_attr]
|
47
|
+
key = convert_key(setting_attr)
|
48
|
+
settings["#{ key }Secure"] = !value.nil? && value_secure?(value) && value_safe?(value)
|
49
|
+
settings["#{ key }Value"] = value.nil? ? Contrast::Utils::ObjectShare::EMPTY_STRING : value
|
50
|
+
end
|
51
|
+
{ DATA => settings }
|
52
|
+
end
|
53
|
+
|
54
|
+
# Get the CSP values from and transforms them to key value hash
|
55
|
+
#
|
56
|
+
# ex default-src 'self' *.test.com; img-src * becomes:
|
57
|
+
# { default-src: "'self' *.test.com", img-src: "*" }
|
58
|
+
#
|
59
|
+
# @param headers [Hash] the response of the application
|
60
|
+
# @return [Array, nil] array of CSP header values
|
61
|
+
def get_csp_header_values headers
|
62
|
+
csp_hash = {}
|
63
|
+
CSP_HEADERS.each do |header_key|
|
64
|
+
next unless headers[header_key]&.length&.positive?
|
65
|
+
|
66
|
+
values = headers[header_key].split(Contrast::Utils::ObjectShare::SEMICOLON)
|
67
|
+
values.each do |value|
|
68
|
+
normalized = value.downcase.strip
|
69
|
+
kv = normalized.split(Contrast::Utils::ObjectShare::SPACE, 2)
|
70
|
+
csp_hash[kv[0]] = kv[1]
|
71
|
+
end
|
72
|
+
end
|
73
|
+
csp_hash
|
74
|
+
end
|
75
|
+
|
76
|
+
def value_secure? value
|
77
|
+
ASTERISK_REGEXP.match(value).nil?
|
78
|
+
end
|
79
|
+
|
80
|
+
def value_safe? value
|
81
|
+
UNSAFE_VALUE_REGEXP.match(value).nil?
|
82
|
+
end
|
83
|
+
|
84
|
+
# Converts the CSP key to camelcase to be used as key for evidence object
|
85
|
+
#
|
86
|
+
# base-uri -> baseUri
|
87
|
+
#
|
88
|
+
# @param key [String] key as found in header
|
89
|
+
# @return [String] camelcase key
|
90
|
+
def convert_key key
|
91
|
+
return key unless key.include?('-')
|
92
|
+
|
93
|
+
str = key.split('-')
|
94
|
+
"#{ str[0] }#{ str[1].capitalize }"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# Copyright (c) 2022 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'contrast/agent/assess/rule/response/base_rule'
|
5
|
+
require 'contrast/utils/string_utils'
|
6
|
+
|
7
|
+
module Contrast
|
8
|
+
module Agent
|
9
|
+
module Assess
|
10
|
+
module Rule
|
11
|
+
module Response
|
12
|
+
# These rules check that the HTTP Headers include CSP header types
|
13
|
+
class CspHeaderMissing < BaseRule
|
14
|
+
def rule_id
|
15
|
+
'csp-header-missing'
|
16
|
+
end
|
17
|
+
|
18
|
+
protected
|
19
|
+
|
20
|
+
CSP_HEADERS = %w[CONTENT_SECURITY_POLICY X_CONTENT_SECURITY_POLICY X_WEBKIT_CSP].cs__freeze
|
21
|
+
|
22
|
+
DATA = 'data'.cs__freeze
|
23
|
+
|
24
|
+
# Rules discern which responses they can/should analyze.
|
25
|
+
#
|
26
|
+
# @param response [Contrast::Agent::Response] the response of the application
|
27
|
+
def analyze_response? response
|
28
|
+
super && headers?(response)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Determine if the Response violates the Rule or not. If it does, return the evidence that proves it so.
|
32
|
+
#
|
33
|
+
# @param response [Contrast::Agent::Response] the response of the application
|
34
|
+
# @return [Contrast::Utils::ObjectShare::EMPTY_STRING, nil] if CSP Header is not found
|
35
|
+
def violated? response
|
36
|
+
response_headers = response.headers
|
37
|
+
return if CSP_HEADERS.any? { |header_key| response_headers[header_key]&.length&.positive? }
|
38
|
+
|
39
|
+
{ DATA => Contrast::Utils::ObjectShare::EMPTY_STRING }
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# Copyright (c) 2022 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Contrast
|
5
|
+
module Agent
|
6
|
+
module Assess
|
7
|
+
module Rule
|
8
|
+
module Response
|
9
|
+
# This rule checks if the HTTP Headers include HSTS header and ensures that the max-age value
|
10
|
+
# is set to a value greater than 0.
|
11
|
+
class HSTSHeader < BaseRule
|
12
|
+
def rule_id
|
13
|
+
'hsts-header-missing'
|
14
|
+
end
|
15
|
+
|
16
|
+
protected
|
17
|
+
|
18
|
+
HEADER_KEY = 'Strict-Transport-Security'
|
19
|
+
HEADER_KEY_SYM = HEADER_KEY.to_sym
|
20
|
+
MAX_AGE = 'max-age'
|
21
|
+
MAX_AGE_SYM = MAX_AGE.to_sym
|
22
|
+
# Rules discern which responses they can/should analyze.
|
23
|
+
#
|
24
|
+
# @param response [Contrast::Agent::Response] the response of the application
|
25
|
+
def analyze_response? response
|
26
|
+
super && response.headers.cs__is_a?(Hash)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Determine if the Response violates the Rule or not. If it does, return the evidence that proves it so.
|
30
|
+
#
|
31
|
+
# @param response [Contrast::Agent::Response] the response of the application
|
32
|
+
# @return [Hash<data: Contrast::Utils::ObjectShare::EMPTY_STRING, String>, nil] return string
|
33
|
+
# representation of the max_age
|
34
|
+
def violated? response
|
35
|
+
headers = response.headers
|
36
|
+
target = headers[HEADER_KEY] || headers[HEADER_KEY_SYM]
|
37
|
+
# this rule is safe by default if no target => no evidence
|
38
|
+
# if the property max_age is not positive or absent then the rule is violated
|
39
|
+
return unless target
|
40
|
+
|
41
|
+
max_age = target[MAX_AGE] || target[MAX_AGE_SYM]
|
42
|
+
return if max_age.to_i.positive?
|
43
|
+
|
44
|
+
evidence max_age
|
45
|
+
end
|
46
|
+
|
47
|
+
# returns evidence that the max_age is negative or absent
|
48
|
+
#
|
49
|
+
# @param max_age [String] String representation of the max-age value to which the header is set
|
50
|
+
# @return [Hash<data: Contrast::Utils::ObjectShare::EMPTY_STRING, String>] return string representation of
|
51
|
+
# the max_age
|
52
|
+
def evidence max_age
|
53
|
+
{ data: max_age.to_s }
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# Copyright (c) 2022 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'contrast/agent/assess/rule/response/base_rule'
|
5
|
+
require 'contrast/utils/string_utils'
|
6
|
+
|
7
|
+
module Contrast
|
8
|
+
module Agent
|
9
|
+
module Assess
|
10
|
+
module Rule
|
11
|
+
module Response
|
12
|
+
# These rules check the content of the HTTP Response to determine if the body contains a form which
|
13
|
+
# incorrectly sets the action attribute.
|
14
|
+
class ParametersPollution < BaseRule
|
15
|
+
def rule_id
|
16
|
+
'parameter-pollution'
|
17
|
+
end
|
18
|
+
|
19
|
+
protected
|
20
|
+
|
21
|
+
# Rules discern which responses they can/should analyze.
|
22
|
+
#
|
23
|
+
# @param response [Contrast::Agent::Response] the response of the application
|
24
|
+
def analyze_response? response
|
25
|
+
super && body?(response)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Determine if the Response violates the Rule or not. If it does, return the evidence that proves it so.
|
29
|
+
#
|
30
|
+
# @param response [Contrast::Agent::Response] the response of the application
|
31
|
+
# @return [Hash, nil] the evidence required to prove the violation of the rule
|
32
|
+
def violated? response
|
33
|
+
body = response.body
|
34
|
+
forms = forms(body)
|
35
|
+
forms.each do |form|
|
36
|
+
# Because TeamServer will reject any subsequent form on the same page due to deduplication, we can
|
37
|
+
# skip out on the first violation.
|
38
|
+
return form if action?(form[HTML_PROP])
|
39
|
+
end
|
40
|
+
nil
|
41
|
+
end
|
42
|
+
|
43
|
+
def accepted_values
|
44
|
+
[/action="[^"]/i, /action='[^']/i, /action=[^\\'"]/i, /action=[^\s<>'"]/i].cs__freeze
|
45
|
+
end
|
46
|
+
|
47
|
+
def action? form
|
48
|
+
return true unless /action=/i.match?(form)
|
49
|
+
|
50
|
+
accepted_values.each do |off_value|
|
51
|
+
return false if form.match?(off_value)
|
52
|
+
end
|
53
|
+
true
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# Copyright (c) 2022 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'contrast/agent/assess/rule/response/base_rule'
|
5
|
+
require 'contrast/utils/string_utils'
|
6
|
+
|
7
|
+
module Contrast
|
8
|
+
module Agent
|
9
|
+
module Assess
|
10
|
+
module Rule
|
11
|
+
module Response
|
12
|
+
# These rules check the content of the HTTP Response to determine if the response contains the needed header
|
13
|
+
class XContentType < BaseRule
|
14
|
+
def rule_id
|
15
|
+
'xcontenttype-header-missing'
|
16
|
+
end
|
17
|
+
|
18
|
+
protected
|
19
|
+
|
20
|
+
HEADER_KEY = 'X-Content-Type-Options'.cs__freeze
|
21
|
+
HEADER_KEY_SYM = HEADER_KEY.to_sym
|
22
|
+
ACCEPTED_VALUE = /^nosniff/i.cs__freeze
|
23
|
+
|
24
|
+
# Rules discern which responses they can/should analyze.
|
25
|
+
#
|
26
|
+
# @param response [Contrast::Agent::Response] the response of the application
|
27
|
+
def analyze_response? response
|
28
|
+
super && headers?(response)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Determine if the Response violates the Rule or not. If it does, return the evidence that proves it so.
|
32
|
+
#
|
33
|
+
# @param response [Contrast::Agent::Response] the response of the application
|
34
|
+
# @return [Hash, nil] the evidence required to prove the violation of the rule
|
35
|
+
def violated? response
|
36
|
+
headers = response.headers
|
37
|
+
x_content_type = headers[HEADER_KEY] || headers[HEADER_KEY_SYM]
|
38
|
+
return unsafe_response unless x_content_type
|
39
|
+
return unsafe_response x_content_type unless ACCEPTED_VALUE.match?(x_content_type)
|
40
|
+
|
41
|
+
nil
|
42
|
+
end
|
43
|
+
|
44
|
+
def unsafe_response value = ''
|
45
|
+
{ data: value }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# Copyright (c) 2022 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'contrast/agent/assess/rule/response/base_rule'
|
5
|
+
require 'contrast/utils/string_utils'
|
6
|
+
|
7
|
+
module Contrast
|
8
|
+
module Agent
|
9
|
+
module Assess
|
10
|
+
module Rule
|
11
|
+
module Response
|
12
|
+
# These rules check the content of the HTTP Response to determine if the response contains the needed header
|
13
|
+
class XXssProtection < BaseRule
|
14
|
+
def rule_id
|
15
|
+
'xxssprotection-header-disabled'
|
16
|
+
end
|
17
|
+
|
18
|
+
protected
|
19
|
+
|
20
|
+
HEADER_KEY = 'X-XSS-Protection'.cs__freeze
|
21
|
+
HEADER_KEY_SYM = HEADER_KEY.to_sym
|
22
|
+
ACCEPTED_VALUE = /^1/.cs__freeze
|
23
|
+
|
24
|
+
# Rules discern which responses they can/should analyze.
|
25
|
+
#
|
26
|
+
# @param response [Contrast::Agent::Response] the response of the application
|
27
|
+
def analyze_response? response
|
28
|
+
super && headers?(response)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Determine if the Response violates the Rule or not. If it does, return the evidence that proves it so.
|
32
|
+
#
|
33
|
+
# @param response [Contrast::Agent::Response] the response of the application
|
34
|
+
# @return [Hash, nil] the evidence required to prove the violation of the rule
|
35
|
+
def violated? response
|
36
|
+
headers = response.headers
|
37
|
+
x_xss_protection = headers[HEADER_KEY] || headers[HEADER_KEY_SYM]
|
38
|
+
# header is safe by default so only need to return finding on failed value match
|
39
|
+
return unless x_xss_protection
|
40
|
+
return unsafe_response x_xss_protection unless ACCEPTED_VALUE.match?(x_xss_protection)
|
41
|
+
|
42
|
+
nil
|
43
|
+
end
|
44
|
+
|
45
|
+
def unsafe_response value = ''
|
46
|
+
{ data: value }
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -28,7 +28,17 @@ module Contrast
|
|
28
28
|
attr_reader :rule_id, :routes, :events, :properties, :request
|
29
29
|
attr_accessor :hash_code
|
30
30
|
|
31
|
-
PROPERTIES_RULES = [
|
31
|
+
PROPERTIES_RULES = %w[
|
32
|
+
autocomplete-missing
|
33
|
+
cache-controls-missing
|
34
|
+
clickjacking-control-missing
|
35
|
+
xcontenttype-header-missing
|
36
|
+
hsts-header-missing
|
37
|
+
xxssprotection-header-disabled
|
38
|
+
csp-header-missing
|
39
|
+
csp-header-insecure
|
40
|
+
parameter-pollution
|
41
|
+
].cs__freeze
|
32
42
|
|
33
43
|
class << self
|
34
44
|
# @param finding_dtm [Contrast::Api::Dtm::Finding]
|
@@ -2,6 +2,11 @@
|
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require 'contrast/agent/assess/rule/response/autocomplete_rule'
|
5
|
+
require 'contrast/agent/assess/rule/response/hsts_header_rule'
|
6
|
+
require 'contrast/agent/assess/rule/response/cachecontrol_rule'
|
7
|
+
require 'contrast/agent/assess/rule/response/clickjacking_rule'
|
8
|
+
require 'contrast/agent/assess/rule/response/x_content_type_rule'
|
9
|
+
require 'contrast/agent/assess/rule/response/parameters_pollution_rule'
|
5
10
|
|
6
11
|
module Contrast
|
7
12
|
module Agent
|
@@ -113,6 +118,14 @@ module Contrast
|
|
113
118
|
return unless Contrast::Agent::Reporter.enabled?
|
114
119
|
|
115
120
|
Contrast::Agent::Assess::Rule::Response::Autocomplete.new.analyze(@response)
|
121
|
+
Contrast::Agent::Assess::Rule::Response::HSTSHeader.new.analyze(@response)
|
122
|
+
Contrast::Agent::Assess::Rule::Response::Cachecontrol.new.analyze(@response)
|
123
|
+
Contrast::Agent::Assess::Rule::Response::XXssProtection.new.analyze(@response)
|
124
|
+
Contrast::Agent::Assess::Rule::Response::CspHeaderMissing.new.analyze(@response)
|
125
|
+
Contrast::Agent::Assess::Rule::Response::CspHeaderInsecure.new.analyze(@response)
|
126
|
+
Contrast::Agent::Assess::Rule::Response::Clickjacking.new.analyze(@response)
|
127
|
+
Contrast::Agent::Assess::Rule::Response::XContentType.new.analyze(@response)
|
128
|
+
Contrast::Agent::Assess::Rule::Response::ParametersPollution.new.analyze(@response)
|
116
129
|
rescue StandardError => e
|
117
130
|
logger.error('Unable to extract information after request', e)
|
118
131
|
end
|
@@ -4,6 +4,13 @@
|
|
4
4
|
# This module is used to track propagation through ERB template rendering
|
5
5
|
module ERBPropagator
|
6
6
|
class << self
|
7
|
+
# After ERB#result method is called we need to take the tags to the target and keep the
|
8
|
+
# propagation.
|
9
|
+
#
|
10
|
+
# @param patcher [Contrast::Agent::Assess::Policy::PolicyNode] the node that governs this event
|
11
|
+
# @param preshift [Contrast::Agent::Assess::Preshift] Saved state before the propagation
|
12
|
+
# @param ret [the Return of the invoked method]
|
13
|
+
# @param _block [&block, nil] block passed
|
7
14
|
def result_tagger patcher, preshift, ret, _block
|
8
15
|
return unless preshift.args.length >= 1
|
9
16
|
return unless (properties = Contrast::Agent::Assess::Tracker.properties!(ret))
|
@@ -26,6 +33,16 @@ module ERBPropagator
|
|
26
33
|
|
27
34
|
private
|
28
35
|
|
36
|
+
# Checks if binded variables set includes the object we track and proceed to update tags in the returned value
|
37
|
+
#
|
38
|
+
# @param binding_variable_set [Array<Symbol>] list of local variables used in the binding of params
|
39
|
+
# @param used_binding [Binding] the binding in of the current event, saved as preshift argument
|
40
|
+
# @param erb_pre_result [String] the source saved in the preshift
|
41
|
+
# @param properties [Contrast::Agent::Assess::Properties] properties of the target if none create new
|
42
|
+
# @param parent_events [Array<Contrast::Agent::Assess::ContrastEvent>] parents event extracted from the source
|
43
|
+
# properties
|
44
|
+
# @param ret [String] the Return of the invoked method
|
45
|
+
# @return [Array<Symbol>]
|
29
46
|
def binding_variable_set binding_variable_set, used_binding, erb_pre_result, properties, parent_events, ret
|
30
47
|
binding_variable_set.each do |bound_var_symbol|
|
31
48
|
bound_variable_value = used_binding.local_variable_get(bound_var_symbol)
|
@@ -634,6 +634,16 @@
|
|
634
634
|
"source":"O",
|
635
635
|
"target":"R",
|
636
636
|
"action":"REMOVE"
|
637
|
+
}, {
|
638
|
+
"class_name":"MatchData",
|
639
|
+
"instance_method": true,
|
640
|
+
"method_visibility": "public",
|
641
|
+
"method_name":"match",
|
642
|
+
"source":"O",
|
643
|
+
"target":"R",
|
644
|
+
"action":"CUSTOM",
|
645
|
+
"patch_class": "Contrast::Agent::Assess::Policy::Propagator::MatchData",
|
646
|
+
"patch_method": "square_bracket_tagger"
|
637
647
|
}, {
|
638
648
|
"class_name":"MatchData",
|
639
649
|
"instance_method": true,
|
data/ruby-agent.gemspec
CHANGED
@@ -59,7 +59,7 @@ def self.add_linters spec
|
|
59
59
|
spec.add_development_dependency 'debride', '1.8.2'
|
60
60
|
spec.add_development_dependency 'fasterer', '0.9.0'
|
61
61
|
spec.add_development_dependency 'flay', '2.12.1'
|
62
|
-
spec.add_development_dependency 'steep', '0.
|
62
|
+
spec.add_development_dependency 'steep', '0.47.0'
|
63
63
|
add_rubocop(spec)
|
64
64
|
end
|
65
65
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: contrast-agent
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 5.
|
4
|
+
version: 5.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- galen.palmer@contrastsecurity.com
|
@@ -13,7 +13,7 @@ authors:
|
|
13
13
|
autorequire:
|
14
14
|
bindir: exe
|
15
15
|
cert_chain: []
|
16
|
-
date: 2022-01-
|
16
|
+
date: 2022-01-24 00:00:00.000000000 Z
|
17
17
|
dependencies:
|
18
18
|
- !ruby/object:Gem::Dependency
|
19
19
|
name: bundler
|
@@ -147,14 +147,14 @@ dependencies:
|
|
147
147
|
requirements:
|
148
148
|
- - '='
|
149
149
|
- !ruby/object:Gem::Version
|
150
|
-
version: 0.
|
150
|
+
version: 0.47.0
|
151
151
|
type: :development
|
152
152
|
prerelease: false
|
153
153
|
version_requirements: !ruby/object:Gem::Requirement
|
154
154
|
requirements:
|
155
155
|
- - '='
|
156
156
|
- !ruby/object:Gem::Version
|
157
|
-
version: 0.
|
157
|
+
version: 0.47.0
|
158
158
|
- !ruby/object:Gem::Dependency
|
159
159
|
name: rubocop
|
160
160
|
requirement: !ruby/object:Gem::Requirement
|
@@ -617,18 +617,18 @@ executables:
|
|
617
617
|
- contrast_service
|
618
618
|
extensions:
|
619
619
|
- ext/cs__common/extconf.rb
|
620
|
+
- ext/cs__contrast_patch/extconf.rb
|
621
|
+
- ext/cs__assess_yield_track/extconf.rb
|
622
|
+
- ext/cs__assess_hash/extconf.rb
|
623
|
+
- ext/cs__assess_marshal_module/extconf.rb
|
620
624
|
- ext/cs__assess_fiber_track/extconf.rb
|
625
|
+
- ext/cs__assess_string_interpolation26/extconf.rb
|
626
|
+
- ext/cs__assess_basic_object/extconf.rb
|
621
627
|
- ext/cs__assess_array/extconf.rb
|
622
|
-
- ext/cs__assess_string/extconf.rb
|
623
628
|
- ext/cs__assess_regexp/extconf.rb
|
624
|
-
- ext/cs__assess_yield_track/extconf.rb
|
625
|
-
- ext/cs__contrast_patch/extconf.rb
|
626
629
|
- ext/cs__assess_kernel/extconf.rb
|
627
|
-
- ext/cs__assess_hash/extconf.rb
|
628
|
-
- ext/cs__assess_string_interpolation26/extconf.rb
|
629
630
|
- ext/cs__os_information/extconf.rb
|
630
|
-
- ext/
|
631
|
-
- ext/cs__assess_basic_object/extconf.rb
|
631
|
+
- ext/cs__assess_string/extconf.rb
|
632
632
|
- ext/cs__assess_module/extconf.rb
|
633
633
|
extra_rdoc_files: []
|
634
634
|
files:
|
@@ -881,6 +881,14 @@ files:
|
|
881
881
|
- lib/contrast/agent/assess/rule/provider/hardcoded_value_rule.rb
|
882
882
|
- lib/contrast/agent/assess/rule/response/autocomplete_rule.rb
|
883
883
|
- lib/contrast/agent/assess/rule/response/base_rule.rb
|
884
|
+
- lib/contrast/agent/assess/rule/response/cachecontrol_rule.rb
|
885
|
+
- lib/contrast/agent/assess/rule/response/clickjacking_rule.rb
|
886
|
+
- lib/contrast/agent/assess/rule/response/csp_header_insecure_rule.rb
|
887
|
+
- lib/contrast/agent/assess/rule/response/csp_header_missing_rule.rb
|
888
|
+
- lib/contrast/agent/assess/rule/response/hsts_header_rule.rb
|
889
|
+
- lib/contrast/agent/assess/rule/response/parameters_pollution_rule.rb
|
890
|
+
- lib/contrast/agent/assess/rule/response/x_content_type_rule.rb
|
891
|
+
- lib/contrast/agent/assess/rule/response/x_xss_protection_rule.rb
|
884
892
|
- lib/contrast/agent/assess/tag.rb
|
885
893
|
- lib/contrast/agent/assess/tracker.rb
|
886
894
|
- lib/contrast/agent/at_exit_hook.rb
|