contrast-agent 7.4.0 → 7.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/contrast/agent/middleware/middleware.rb +1 -1
- data/lib/contrast/agent/protect/input_analyzer/input_analyzer.rb +9 -11
- data/lib/contrast/agent/protect/input_analyzer/worth_watching_analyzer.rb +55 -20
- data/lib/contrast/agent/protect/policy/rule_applicator.rb +1 -4
- data/lib/contrast/agent/protect/rule/base.rb +56 -25
- data/lib/contrast/agent/protect/rule/bot_blocker/bot_blocker.rb +12 -4
- data/lib/contrast/agent/protect/rule/cmdi/cmd_injection.rb +2 -10
- data/lib/contrast/agent/protect/rule/cmdi/cmdi_backdoors.rb +2 -4
- data/lib/contrast/agent/protect/rule/cmdi/cmdi_base_rule.rb +2 -1
- data/lib/contrast/agent/protect/rule/deserialization/deserialization.rb +4 -4
- data/lib/contrast/agent/protect/rule/no_sqli/no_sqli.rb +5 -2
- data/lib/contrast/agent/protect/rule/path_traversal/path_traversal.rb +12 -7
- data/lib/contrast/agent/protect/rule/path_traversal/path_traversal_semantic_security_bypass.rb +2 -2
- data/lib/contrast/agent/protect/rule/sqli/sqli_base_rule.rb +2 -3
- data/lib/contrast/agent/protect/rule/sqli/sqli_semantic/sqli_dangerous_functions.rb +3 -4
- data/lib/contrast/agent/protect/rule/unsafe_file_upload/unsafe_file_upload.rb +3 -0
- data/lib/contrast/agent/protect/rule/utils/builders.rb +3 -4
- data/lib/contrast/agent/protect/rule/utils/filters.rb +32 -16
- data/lib/contrast/agent/protect/rule/xss/xss.rb +80 -0
- data/lib/contrast/agent/protect/rule/xxe/xxe.rb +9 -2
- data/lib/contrast/agent/reporting/details/xss_match.rb +17 -0
- data/lib/contrast/agent/reporting/input_analysis/input_analysis.rb +32 -0
- data/lib/contrast/agent/reporting/reporting_events/finding.rb +1 -5
- data/lib/contrast/agent/reporting/reporting_events/preflight_message.rb +1 -4
- data/lib/contrast/agent/request/request_context_extend.rb +0 -2
- data/lib/contrast/agent/version.rb +1 -1
- data/lib/contrast/components/assess.rb +4 -0
- data/lib/contrast/framework/rails/support.rb +2 -2
- data/lib/contrast/logger/cef_log.rb +30 -4
- data/lib/contrast/utils/io_util.rb +3 -0
- data/lib/contrast/utils/log_utils.rb +21 -10
- metadata +2 -2
@@ -26,9 +26,8 @@ module Contrast
|
|
26
26
|
return unless result
|
27
27
|
|
28
28
|
append_to_activity(context, result)
|
29
|
-
|
30
|
-
|
31
|
-
raise(Contrast::SecurityException.new(self, BLOCK_MESSAGE)) if blocked?
|
29
|
+
record_triggered(context)
|
30
|
+
raise(Contrast::SecurityException.new(self, BLOCK_MESSAGE)) if blocked_violation?(result)
|
32
31
|
end
|
33
32
|
|
34
33
|
protected
|
@@ -39,8 +38,8 @@ module Contrast
|
|
39
38
|
|
40
39
|
def build_violation context, potential_attack_string
|
41
40
|
result = build_attack_result(context)
|
42
|
-
update_successful_attack_response(context, nil, result, potential_attack_string)
|
43
41
|
append_sample(context, nil, result, potential_attack_string)
|
42
|
+
update_successful_attack_response(context, nil, result, potential_attack_string)
|
44
43
|
result
|
45
44
|
end
|
46
45
|
|
@@ -29,8 +29,8 @@ module Contrast
|
|
29
29
|
# this input
|
30
30
|
def build_attack_with_match context, ia_result, result, candidate_string, **kwargs
|
31
31
|
result ||= build_attack_result(context)
|
32
|
-
update_successful_attack_response(context, ia_result, result, candidate_string)
|
33
32
|
append_sample(context, ia_result, result, candidate_string, **kwargs)
|
33
|
+
update_successful_attack_response(context, ia_result, result, candidate_string)
|
34
34
|
|
35
35
|
result
|
36
36
|
end
|
@@ -53,8 +53,8 @@ module Contrast
|
|
53
53
|
# this input
|
54
54
|
def build_attack_without_match context, ia_result, result, **kwargs
|
55
55
|
result ||= build_attack_result(context)
|
56
|
-
update_perimeter_attack_response(context, ia_result, result)
|
57
56
|
append_sample(context, ia_result, result, nil, **kwargs)
|
57
|
+
update_perimeter_attack_response(context, ia_result, result)
|
58
58
|
|
59
59
|
result
|
60
60
|
end
|
@@ -97,11 +97,10 @@ module Contrast
|
|
97
97
|
# @param potential_attack_string [String]
|
98
98
|
def build_violation context, potential_attack_string
|
99
99
|
result = build_attack_result(context)
|
100
|
+
append_sample(context, nil, result, potential_attack_string)
|
100
101
|
update_successful_attack_response(context, nil, result, potential_attack_string)
|
101
102
|
return unless result
|
102
103
|
|
103
|
-
append_sample(context, nil, result, potential_attack_string)
|
104
|
-
cef_logging(result, :successful_attack)
|
105
104
|
result
|
106
105
|
end
|
107
106
|
end
|
@@ -25,11 +25,12 @@ module Contrast
|
|
25
25
|
|
26
26
|
ia_results.each do |ia_result|
|
27
27
|
result = build_attack_result(context)
|
28
|
-
build_attack_without_match(context, ia_result, result)
|
29
|
-
|
28
|
+
result = build_attack_without_match(context, ia_result, result)
|
29
|
+
next unless result
|
30
30
|
|
31
|
-
|
32
|
-
|
31
|
+
append_to_activity(context, result)
|
32
|
+
record_triggered(context)
|
33
|
+
raise(Contrast::SecurityException.new(self, block_message)) if blocked_violation?(result)
|
33
34
|
end
|
34
35
|
end
|
35
36
|
|
@@ -39,10 +40,10 @@ module Contrast
|
|
39
40
|
# @param context [Contrast::Agent::RequestContext]
|
40
41
|
# @return [Boolean]
|
41
42
|
def prefilter? context
|
42
|
-
return false unless context
|
43
43
|
return false unless enabled?
|
44
|
-
return false unless (results = gather_ia_results(context)) && results.any?
|
45
44
|
return false if protect_excluded_by_url?(rule_name)
|
45
|
+
return false unless context
|
46
|
+
return false unless (results = gather_ia_results(context)) && results.any?
|
46
47
|
return false if protect_excluded_by_input?(results)
|
47
48
|
|
48
49
|
true
|
@@ -52,13 +53,20 @@ module Contrast
|
|
52
53
|
# have a different implementation based on the rule. As such, there
|
53
54
|
# is not parent implementation.
|
54
55
|
#
|
55
|
-
# @param
|
56
|
+
# @param context [Contrast::Agent::RequestContext] the context for
|
56
57
|
# the current request
|
57
|
-
# @param
|
58
|
+
# @param match_string [String] the input that violated the rule and
|
58
59
|
# matched the attack detection logic
|
59
60
|
# @param _kwargs [Hash] key-value pairs used by the rule to build a
|
60
61
|
# report.
|
61
|
-
def infilter
|
62
|
+
def infilter context, match_string, _kwargs
|
63
|
+
return unless infilter?(context)
|
64
|
+
return unless (result = build_violation(context, match_string))
|
65
|
+
|
66
|
+
append_to_activity(context, result)
|
67
|
+
record_triggered(context)
|
68
|
+
raise(Contrast::SecurityException.new(self, block_message)) if blocked?
|
69
|
+
end
|
62
70
|
|
63
71
|
# Infilter check always called before infilter to check if the rule is infilter
|
64
72
|
# capable, not disabled or in other way excluded by url or input exclusions.
|
@@ -74,6 +82,18 @@ module Contrast
|
|
74
82
|
true
|
75
83
|
end
|
76
84
|
|
85
|
+
# Check befor commiting infilter
|
86
|
+
#
|
87
|
+
# @param context [Contrast::Agent::RequestContext]
|
88
|
+
def postfilter? context
|
89
|
+
return false unless enabled? && POSTFILTER_MODES.include?(mode)
|
90
|
+
return false if protect_excluded_by_url?(rule_name)
|
91
|
+
return false if protect_excluded_by_input?(gather_ia_results(context))
|
92
|
+
return false if mode == :NO_ACTION || mode == :PERMIT
|
93
|
+
|
94
|
+
true
|
95
|
+
end
|
96
|
+
|
77
97
|
# Actions required for the rules that have to happen after the
|
78
98
|
# application has completed its processing of the request.
|
79
99
|
#
|
@@ -88,18 +108,14 @@ module Contrast
|
|
88
108
|
# @param context [Contrast::Agent::RequestContext]
|
89
109
|
# @raise [Contrast::SecurityException]
|
90
110
|
def postfilter context
|
91
|
-
return unless
|
92
|
-
return false if protect_excluded_by_url?(rule_name)
|
93
|
-
return if protect_excluded_by_input?(gather_ia_results(context))
|
94
|
-
|
95
|
-
return if mode == :NO_ACTION || mode == :PERMIT
|
111
|
+
return unless postfilter?(context)
|
96
112
|
|
97
113
|
result = find_postfilter_attacker(context, nil)
|
98
114
|
return unless result&.samples&.any?
|
99
115
|
|
100
|
-
cef_logging(result)
|
101
116
|
append_to_activity(context, result)
|
102
|
-
|
117
|
+
record_triggered(context)
|
118
|
+
return unless blocked_violation?(result)
|
103
119
|
|
104
120
|
raise(Contrast::SecurityException.new(self, "#{ rule_name } triggered in postfilter. Response blocked."))
|
105
121
|
end
|
@@ -3,6 +3,8 @@
|
|
3
3
|
|
4
4
|
require 'contrast/agent/protect/rule/base'
|
5
5
|
require 'contrast/agent/protect/rule/xss/reflected_xss_input_classification'
|
6
|
+
require 'contrast/agent/reporting/details/xss_details'
|
7
|
+
require 'contrast/agent/reporting/details/xss_match'
|
6
8
|
require 'contrast/agent/reporting/input_analysis/input_type'
|
7
9
|
|
8
10
|
module Contrast
|
@@ -25,10 +27,56 @@ module Contrast
|
|
25
27
|
NAME
|
26
28
|
end
|
27
29
|
|
30
|
+
# Return the specific blocking message for this rule.
|
31
|
+
#
|
32
|
+
# @return [String] the reason for the raised security exception.
|
28
33
|
def block_message
|
29
34
|
BLOCK_MESSAGE
|
30
35
|
end
|
31
36
|
|
37
|
+
# Prefilter check always called before infilter to check if the rule is infilter
|
38
|
+
# capable, not disabled or in other way excluded by url or input exclusions.
|
39
|
+
#
|
40
|
+
# @param context [Contrast::Agent::RequestContext]
|
41
|
+
# @return [Boolean]
|
42
|
+
def prefilter? context
|
43
|
+
return false unless enabled?
|
44
|
+
return false if protect_excluded_by_url?(rule_name)
|
45
|
+
return false unless context
|
46
|
+
return false unless (results = gather_ia_results(context)) && results.any?
|
47
|
+
return false if protect_excluded_by_input?(results)
|
48
|
+
|
49
|
+
true
|
50
|
+
end
|
51
|
+
|
52
|
+
def prefilter context
|
53
|
+
return unless prefilter?(context)
|
54
|
+
|
55
|
+
ia_results = gather_ia_results(context)
|
56
|
+
|
57
|
+
ia_results.each do |ia_result|
|
58
|
+
result = build_attack_result(context)
|
59
|
+
result = build_attack_without_match(context, ia_result, result)
|
60
|
+
next unless result
|
61
|
+
|
62
|
+
append_to_activity(context, result)
|
63
|
+
# XSS is being triggered, so we need to add it to the triggered rules,
|
64
|
+
# So the IA won't be done for this rule again for the current request.
|
65
|
+
record_triggered(context)
|
66
|
+
raise(Contrast::SecurityException.new(self, block_message)) if blocked_violation?(result)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# XSS is evaluated only on prefilter
|
71
|
+
def infilter? _context
|
72
|
+
false
|
73
|
+
end
|
74
|
+
|
75
|
+
# XSS is evaluated only on prefilter
|
76
|
+
def postfilter? _context
|
77
|
+
false
|
78
|
+
end
|
79
|
+
|
32
80
|
# XSS Upload input classification
|
33
81
|
#
|
34
82
|
# @return [module<Contrast::Agent::Protect::Rule::ReflectedXssInputClassification>]
|
@@ -43,6 +91,38 @@ module Contrast
|
|
43
91
|
def applicable_user_inputs
|
44
92
|
APPLICABLE_USER_INPUTS
|
45
93
|
end
|
94
|
+
|
95
|
+
# Adding XSS details
|
96
|
+
#
|
97
|
+
# @param context [Contrast::Agent::RequestContext]
|
98
|
+
# @param ia_result [Contrast::Agent::Reporting::InputAnalysisResult]
|
99
|
+
# @param _xss_string
|
100
|
+
# @param **_kwargs
|
101
|
+
# @return [Contrast::Agent::Reporting::RaspRuleSample]
|
102
|
+
def build_sample context, ia_result, _xss_string, **_kwargs
|
103
|
+
sample = build_base_sample(context, ia_result)
|
104
|
+
sample.details = Contrast::Agent::Reporting::Details::XssDetails.new
|
105
|
+
sample.details.input = ia_result.value
|
106
|
+
|
107
|
+
# TODO: RUBY-99999 check the if the ReflectedXss matches are needed.
|
108
|
+
xss_match = Contrast::Agent::Reporting::Details::XssMatch.new(ia_result.value)
|
109
|
+
sample.details.matches << xss_match unless xss_match.empty?
|
110
|
+
|
111
|
+
sample
|
112
|
+
end
|
113
|
+
|
114
|
+
private
|
115
|
+
|
116
|
+
# @param context [Contrast::Agent::RequestContext]
|
117
|
+
# @param potential_attack_string [String, nil]
|
118
|
+
# @return [Contrast::Agent::Reporting::AttackResult, nil]
|
119
|
+
def find_postfilter_attacker context, potential_attack_string, **kwargs
|
120
|
+
ia_results = gather_ia_results(context)
|
121
|
+
ia_results.reject! do |ia_result|
|
122
|
+
ia_result.score_level == Contrast::Agent::Reporting::ScoreLevel::IGNORE
|
123
|
+
end
|
124
|
+
find_attacker_with_results(context, potential_attack_string, ia_results, **kwargs)
|
125
|
+
end
|
46
126
|
end
|
47
127
|
end
|
48
128
|
end
|
@@ -26,6 +26,13 @@ module Contrast
|
|
26
26
|
NAME
|
27
27
|
end
|
28
28
|
|
29
|
+
# Return the specific blocking message for this rule.
|
30
|
+
#
|
31
|
+
# @return [String] the reason for the raised security exception.
|
32
|
+
def block_message
|
33
|
+
BLOCK_MESSAGE
|
34
|
+
end
|
35
|
+
|
29
36
|
# Given an xml, evaluate it for an XXE attack. There's no return here
|
30
37
|
# as this method handles appending the evaluation to the request
|
31
38
|
# context, connecting it to the reporting mechanism at request end.
|
@@ -43,9 +50,9 @@ module Contrast
|
|
43
50
|
return unless result
|
44
51
|
|
45
52
|
append_to_activity(context, result)
|
46
|
-
|
53
|
+
record_triggered(context)
|
54
|
+
return unless blocked_violation?(result)
|
47
55
|
|
48
|
-
cef_logging(result, :successful_attack, value: xml)
|
49
56
|
raise(Contrast::SecurityException.new(self, BLOCK_MESSAGE))
|
50
57
|
end
|
51
58
|
|
@@ -9,6 +9,9 @@ module Contrast
|
|
9
9
|
module Details
|
10
10
|
# Matcher data for XSS rule.
|
11
11
|
class XssMatch
|
12
|
+
EVIDENCE_START = /<script.*?>/i.cs__freeze
|
13
|
+
EVIDENCE_END = %r{</script.*?>}i.cs__freeze
|
14
|
+
|
12
15
|
# @return [Integer] in ms
|
13
16
|
attr_accessor :evidence_start
|
14
17
|
# @return [String]
|
@@ -16,6 +19,16 @@ module Contrast
|
|
16
19
|
# @return [Integer]
|
17
20
|
attr_accessor :offset
|
18
21
|
|
22
|
+
# @param xss_string [String] to check for matches.
|
23
|
+
def initialize xss_string = ''
|
24
|
+
return if xss_string.empty?
|
25
|
+
|
26
|
+
@evidence_start = xss_string.index(EVIDENCE_START)
|
27
|
+
@offset = (xss_string[0...@evidence_start] || '').length
|
28
|
+
@evidence = xss_string[@evidence_start...xss_string.index(EVIDENCE_END)].gsub(EVIDENCE_START, '').
|
29
|
+
gsub(EVIDENCE_END, '')
|
30
|
+
end
|
31
|
+
|
19
32
|
def to_controlled_hash
|
20
33
|
{
|
21
34
|
evidenceStart: evidence_start,
|
@@ -23,6 +36,10 @@ module Contrast
|
|
23
36
|
offset: offset
|
24
37
|
}
|
25
38
|
end
|
39
|
+
|
40
|
+
def empty?
|
41
|
+
evidence_start.nil? || evidence.nil? || offset.nil?
|
42
|
+
end
|
26
43
|
end
|
27
44
|
end
|
28
45
|
end
|
@@ -2,6 +2,7 @@
|
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require 'contrast/utils/object_share'
|
5
|
+
require 'contrast/utils/duck_utils'
|
5
6
|
require 'contrast/agent/reporting/input_analysis/input_analysis_result'
|
6
7
|
|
7
8
|
module Contrast
|
@@ -12,10 +13,20 @@ module Contrast
|
|
12
13
|
# @return [Hash] Stored request inputs for this context.
|
13
14
|
attr_accessor :inputs
|
14
15
|
|
16
|
+
# Records rules triggered on sink.
|
17
|
+
#
|
18
|
+
# @return [Array<String>] array with rule names.
|
15
19
|
def triggered_rules
|
16
20
|
@_triggered_rules ||= []
|
17
21
|
end
|
18
22
|
|
23
|
+
# Records rules with input analysis for current request.
|
24
|
+
#
|
25
|
+
# @return [Array<String>] array with rule names.
|
26
|
+
def analysed_rules
|
27
|
+
@_analysed_rules ||= []
|
28
|
+
end
|
29
|
+
|
19
30
|
# result from input analysis
|
20
31
|
#
|
21
32
|
# @return @_results [Array<Contrast::Agent::Reporting::Settings::InputAnalysisResult>]
|
@@ -44,6 +55,27 @@ module Contrast
|
|
44
55
|
def request= request
|
45
56
|
@_request = request if request.instance_of?(Contrast::Agent::Request)
|
46
57
|
end
|
58
|
+
|
59
|
+
# Check if the InputAnalysis is empty.
|
60
|
+
def no_inputs?
|
61
|
+
Contrast::Utils::DuckUtils.empty_duck?(inputs)
|
62
|
+
end
|
63
|
+
|
64
|
+
# We add the analysed rules to the list. The IA won't be build for this rule,
|
65
|
+
# since already it's already available.
|
66
|
+
#
|
67
|
+
# @param rule_name [String] name to record.
|
68
|
+
def record_analysed_rule rule_name
|
69
|
+
analysed_rules << rule_name unless triggered_rules.include?(rule_name)
|
70
|
+
end
|
71
|
+
|
72
|
+
# We add the triggered rules to the list. After request analysis will skip this rule
|
73
|
+
# as already triggered.
|
74
|
+
#
|
75
|
+
# @param rule_name [String] name to record.
|
76
|
+
def record_rule_triggered rule_name
|
77
|
+
triggered_rules << rule_name unless triggered_rules.include?(rule_name)
|
78
|
+
end
|
47
79
|
end
|
48
80
|
end
|
49
81
|
end
|
@@ -119,16 +119,12 @@ module Contrast
|
|
119
119
|
ruleId: rule_id,
|
120
120
|
session_id: ::Contrast::ASSESS.session_id,
|
121
121
|
version: 4
|
122
|
-
}
|
122
|
+
}.compact
|
123
123
|
end
|
124
124
|
|
125
125
|
# @raise [ArgumentError]
|
126
126
|
def validate
|
127
127
|
raise(ArgumentError, "#{ self } did not have a proper rule. Unable to continue.") unless @rule_id
|
128
|
-
|
129
|
-
unless ::Contrast::ASSESS.session_id
|
130
|
-
raise(ArgumentError, "#{ self } did not have a proper session id. Unable to continue.")
|
131
|
-
end
|
132
128
|
if event_based? && events.empty?
|
133
129
|
raise(ArgumentError, "#{ self } did not have proper events for #{ @rule_id }. Unable to continue.")
|
134
130
|
end
|
@@ -47,7 +47,7 @@ module Contrast
|
|
47
47
|
key: 0,
|
48
48
|
session_id: ::Contrast::ASSESS.session_id,
|
49
49
|
routes: @routes.map(&:to_controlled_hash)
|
50
|
-
}
|
50
|
+
}.compact
|
51
51
|
end
|
52
52
|
|
53
53
|
# @raise [ArgumentError]
|
@@ -55,9 +55,6 @@ module Contrast
|
|
55
55
|
unless Contrast::Utils::StringUtils.present?(data)
|
56
56
|
raise(ArgumentError, "#{ cs__class } did not have a proper data. Unable to continue.")
|
57
57
|
end
|
58
|
-
unless ::Contrast::ASSESS.session_id
|
59
|
-
raise(ArgumentError, "#{ cs__class } did not have a proper session id. Unable to continue.")
|
60
|
-
end
|
61
58
|
unless Contrast::APP_CONTEXT.name # rubocop:disable Security/Module/Name
|
62
59
|
raise(ArgumentError, "#{ cs__class } did not have a proper Application Name. Unable to continue.")
|
63
60
|
end
|
@@ -52,8 +52,6 @@ module Contrast
|
|
52
52
|
if (ia = Contrast::Agent::Protect::InputAnalyzer.analyse(request))
|
53
53
|
# Handle prefilter
|
54
54
|
Contrast::Agent::Protect::InputAnalyzer.input_classification(ia, prefilter: true)
|
55
|
-
# Reflected xss infilter
|
56
|
-
Contrast::Agent::Protect::InputAnalyzer.input_classification_for('reflected-xss', ia)
|
57
55
|
@agent_input_analysis = ia
|
58
56
|
else
|
59
57
|
logger.trace('Analysis from Agent was empty.')
|
@@ -237,6 +237,10 @@ module Contrast
|
|
237
237
|
|
238
238
|
# The id for this process, based on the session metadata or id provided by the user, as indicated in
|
239
239
|
# application startup.
|
240
|
+
#
|
241
|
+
# The ID of the current application run, as returned by the application settings endpoint or set by
|
242
|
+
# application.session_id. If there is no session associated with this run, this field should be omitted
|
243
|
+
# when reporting to TS.
|
240
244
|
def session_id
|
241
245
|
::Contrast::SETTINGS.assess_state.session_id
|
242
246
|
end
|
@@ -59,7 +59,7 @@ module Contrast
|
|
59
59
|
# ActionDispatch::Journey::Path::Pattern::MatchData, Hash, ActionDispatch::Journey::Route, Array<String>
|
60
60
|
match, _params, route, path = get_full_route(request.rack_request)
|
61
61
|
unless route
|
62
|
-
logger.
|
62
|
+
logger.debug("Unable to determine the current route of this request: #{ request.rack_request }")
|
63
63
|
return
|
64
64
|
end
|
65
65
|
|
@@ -77,7 +77,7 @@ module Contrast
|
|
77
77
|
new_route_coverage&.attach_rails_data(route, original_url)
|
78
78
|
new_route_coverage
|
79
79
|
rescue StandardError => e
|
80
|
-
logger.
|
80
|
+
logger.error('Unable to determine the current route of this request due to exception: ', e)
|
81
81
|
nil
|
82
82
|
end
|
83
83
|
|
@@ -131,7 +131,16 @@ module Contrast
|
|
131
131
|
log([message, block_entry, outcome], ::Logger::Severity::DEBUG)
|
132
132
|
end
|
133
133
|
|
134
|
-
|
134
|
+
# Log successful attack attack
|
135
|
+
#
|
136
|
+
# @param rule_id [String] the rule that was triggered
|
137
|
+
# @param outcome [String] the outcome of the rule
|
138
|
+
# @param input_type [String] the type of input that was detected
|
139
|
+
# @param input_value [String] the value of the input that was detected
|
140
|
+
# @param attack_context [Contrast::Agent::RequestContext] the request context of the attack
|
141
|
+
def successful_attack rule_id, outcome, input_type = nil, input_value = nil, attack_context = nil
|
142
|
+
# We may log from the worthwatching Queue with saved attack_context
|
143
|
+
update_logger_formatter(@_cef_logger, new_context: attack_context) if attack_context
|
135
144
|
if input_type.present? && input_value.present?
|
136
145
|
successful_attack_with_input = "#{ input_type } had a value that successfully exploited" \
|
137
146
|
"#{ rule_id } - #{ input_value }"
|
@@ -142,7 +151,16 @@ module Contrast
|
|
142
151
|
end
|
143
152
|
end
|
144
153
|
|
145
|
-
|
154
|
+
# Log ineffective attack attack
|
155
|
+
#
|
156
|
+
# @param rule_id [String] the rule that was triggered
|
157
|
+
# @param outcome [String] the outcome of the rule
|
158
|
+
# @param input_type [String] the type of input that was detected
|
159
|
+
# @param input_value [String] the value of the input that was detected
|
160
|
+
# @param attack_context [Contrast::Agent::RequestContext] the request context of the attack
|
161
|
+
def ineffective_attack rule_id, outcome, input_type = nil, input_value = nil, attack_context = nil
|
162
|
+
# We may log from the worthwatching Queue with saved attack_context
|
163
|
+
update_logger_formatter(@_cef_logger, new_context: attack_context) if attack_context
|
146
164
|
if input_type.present? && input_value.present?
|
147
165
|
ineffective_attack_with_input = "#{ input_type } had a value that matched a signature for, " \
|
148
166
|
"but did not successfully exploit #{ rule_id } - #{ input_value }"
|
@@ -153,8 +171,16 @@ module Contrast
|
|
153
171
|
end
|
154
172
|
end
|
155
173
|
|
156
|
-
#
|
157
|
-
|
174
|
+
# Log suspicious attack
|
175
|
+
#
|
176
|
+
# @param rule_id [String] the rule that was triggered
|
177
|
+
# @param outcome [String] the outcome of the rule
|
178
|
+
# @param input_type [String] the type of input that was detected
|
179
|
+
# @param input_value [String] the value of the input that was detected
|
180
|
+
# @param attack_context [Contrast::Agent::RequestContext] the request context of the attack
|
181
|
+
def suspicious_attack rule_id, outcome, input_type = nil, input_value = nil, attack_context = nil
|
182
|
+
# We may log from the worthwatching Queue with saved attack_context
|
183
|
+
update_logger_formatter(@_cef_logger, new_context: attack_context) if attack_context
|
158
184
|
if input_type.present? && input_value.present?
|
159
185
|
suspicious_attack_with = "#{ input_type } included a potential attack value that was detected" \
|
160
186
|
"as suspicious using #{ rule_id } - #{ input_value }"
|
@@ -7,6 +7,8 @@ module Contrast
|
|
7
7
|
module Utils
|
8
8
|
# Util for information about an IO
|
9
9
|
module IOUtil
|
10
|
+
UNKNOWN_IO = 'unknown'
|
11
|
+
|
10
12
|
extend Contrast::Components::Logger::InstanceMethods
|
11
13
|
|
12
14
|
class << self
|
@@ -48,6 +50,7 @@ module Contrast
|
|
48
50
|
return false unless status
|
49
51
|
return false if status.pipe?
|
50
52
|
return false if status.socket?
|
53
|
+
return false if status.ftype == UNKNOWN_IO
|
51
54
|
|
52
55
|
true
|
53
56
|
end
|
@@ -146,6 +146,7 @@ module Contrast
|
|
146
146
|
private
|
147
147
|
|
148
148
|
def build path: STDOUT_STR, level_const: DEFAULT_LEVEL
|
149
|
+
context = Contrast::Agent::REQUEST_TRACKER.current
|
149
150
|
logger = case path
|
150
151
|
when STDOUT_STR, STDERR_STR
|
151
152
|
::Logger.new(Object.cs__const_get(path))
|
@@ -154,15 +155,23 @@ module Contrast
|
|
154
155
|
end
|
155
156
|
logger.progname = PROGNAME
|
156
157
|
logger.level = level_const
|
157
|
-
|
158
|
+
update_logger_formatter(logger, new_context: context)
|
158
159
|
logger
|
159
160
|
end
|
160
161
|
|
161
162
|
def context
|
162
|
-
Contrast::Agent::REQUEST_TRACKER.current
|
163
|
+
@_context ||= Contrast::Agent::REQUEST_TRACKER.current
|
163
164
|
end
|
164
165
|
|
165
|
-
def
|
166
|
+
def context_update new_context
|
167
|
+
@_context = new_context unless new_context.nil?
|
168
|
+
end
|
169
|
+
|
170
|
+
# @param logger [Logger]
|
171
|
+
# @param new_context [Contrast::Agent::RequestContext]
|
172
|
+
def update_logger_formatter logger, new_context: nil
|
173
|
+
context_update(new_context) if new_context
|
174
|
+
|
166
175
|
ip_address = extract_ip_address
|
167
176
|
logger.formatter = proc do |severity, datetime, progname, msg|
|
168
177
|
date_format = datetime.strftime(DATE_TIME_FORMAT)
|
@@ -206,7 +215,7 @@ module Contrast
|
|
206
215
|
request_method = assign_request_method(context)
|
207
216
|
app_name = ::Contrast::APP_CONTEXT.name # rubocop:disable Security/Module/Name
|
208
217
|
attach_request_and_sender_info(message, sender_info)
|
209
|
-
message << "request=#{ context
|
218
|
+
message << "request=#{ context&.request&.url } "
|
210
219
|
message << "requestMethod=#{ request_method } "
|
211
220
|
message << "app=#{ app_name } "
|
212
221
|
message << "outcome=#{ outcome } "
|
@@ -238,16 +247,18 @@ module Contrast
|
|
238
247
|
end
|
239
248
|
|
240
249
|
def extract_sender_ip
|
241
|
-
request_headers = context
|
250
|
+
request_headers = context&.activity&.request&.headers&.transform_keys(&:to_s)
|
251
|
+
return unless request_headers
|
252
|
+
|
242
253
|
request_headers['X-Forwarded-For']
|
243
254
|
end
|
244
255
|
|
245
256
|
def assign_request_method context
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
257
|
+
request_method = context&.request&.rack_request&.env
|
258
|
+
request_method = request_method['REQUEST_METHOD'] if request_method
|
259
|
+
return DEFAULT_METADATA if request_method.nil? || !request_method.length.positive?
|
260
|
+
|
261
|
+
request_method
|
251
262
|
end
|
252
263
|
end
|
253
264
|
end
|
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: 7.4.
|
4
|
+
version: 7.4.1
|
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: 2023-09-
|
16
|
+
date: 2023-09-21 00:00:00.000000000 Z
|
17
17
|
dependencies:
|
18
18
|
- !ruby/object:Gem::Dependency
|
19
19
|
name: bundler
|