contrast-agent 5.0.0 → 5.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|