contrast-agent 3.8.5 → 3.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/ext/cs__assess_array/cs__assess_array.c +1 -1
- data/ext/cs__assess_module/cs__assess_module.c +0 -1
- data/ext/cs__assess_yield_track/cs__assess_yield_track.c +34 -0
- data/ext/cs__assess_yield_track/cs__assess_yield_track.h +12 -0
- data/ext/{cs__scope → cs__assess_yield_track}/extconf.rb +0 -0
- data/ext/cs__common/cs__common.c +6 -6
- data/ext/cs__common/cs__common.h +3 -1
- data/ext/cs__contrast_patch/cs__contrast_patch.c +142 -119
- data/ext/cs__contrast_patch/cs__contrast_patch.h +3 -0
- data/funchook/autom4te.cache/requests +48 -48
- data/funchook/config.log +2 -2
- data/lib/contrast/agent.rb +15 -5
- data/lib/contrast/agent/assess.rb +0 -1
- data/lib/contrast/agent/assess/contrast_event.rb +9 -8
- data/lib/contrast/agent/assess/policy/dynamic_source_factory.rb +68 -18
- data/lib/contrast/agent/assess/policy/policy.rb +0 -14
- data/lib/contrast/agent/assess/policy/policy_scanner.rb +1 -1
- data/lib/contrast/agent/assess/policy/preshift.rb +1 -1
- data/lib/contrast/agent/assess/policy/propagation_method.rb +4 -2
- data/lib/contrast/agent/assess/policy/propagator/custom.rb +1 -1
- data/lib/contrast/agent/assess/policy/propagator/database_write.rb +1 -1
- data/lib/contrast/agent/assess/policy/propagator/splat.rb +2 -2
- data/lib/contrast/agent/assess/policy/propagator/split.rb +166 -1
- data/lib/contrast/agent/assess/policy/rewriter_patch.rb +1 -0
- data/lib/contrast/agent/assess/policy/source_method.rb +199 -140
- data/lib/contrast/agent/assess/policy/source_validation/cross_site_validator.rb +30 -0
- data/lib/contrast/agent/assess/policy/source_validation/source_validation.rb +36 -0
- data/lib/contrast/agent/assess/policy/trigger_method.rb +238 -153
- data/lib/contrast/agent/assess/policy/trigger_node.rb +54 -9
- data/lib/contrast/agent/assess/policy/trigger_validation/trigger_validation.rb +13 -0
- data/lib/contrast/agent/assess/properties.rb +29 -0
- data/lib/contrast/agent/assess/rule/csrf/csrf_applicator.rb +35 -31
- data/lib/contrast/agent/assess/rule/provider/hardcoded_value_rule.rb +1 -1
- data/lib/contrast/agent/class_reopener.rb +98 -55
- data/lib/contrast/agent/feature_state.rb +1 -1
- data/lib/contrast/agent/inventory/policy/policy.rb +1 -1
- data/lib/contrast/agent/logger_manager.rb +2 -2
- data/lib/contrast/agent/middleware.rb +1 -3
- data/lib/contrast/agent/patching/policy/after_load_patch.rb +40 -4
- data/lib/contrast/agent/patching/policy/after_load_patcher.rb +33 -8
- data/lib/contrast/agent/patching/policy/method_policy.rb +20 -7
- data/lib/contrast/agent/patching/policy/patch.rb +54 -23
- data/lib/contrast/agent/patching/policy/patch_status.rb +0 -2
- data/lib/contrast/agent/patching/policy/patcher.rb +10 -11
- data/lib/contrast/agent/patching/policy/policy.rb +4 -0
- data/lib/contrast/agent/patching/policy/policy_node.rb +14 -1
- data/lib/contrast/agent/patching/policy/trigger_node.rb +2 -1
- data/lib/contrast/agent/protect/policy/policy.rb +6 -6
- data/lib/contrast/agent/protect/rule/base.rb +1 -1
- data/lib/contrast/agent/protect/rule/deserialization.rb +3 -25
- data/lib/contrast/agent/protect/rule/sqli.rb +1 -1
- data/lib/contrast/agent/railtie.rb +11 -5
- data/lib/contrast/agent/request.rb +1 -19
- data/lib/contrast/agent/request_context.rb +1 -1
- data/lib/contrast/agent/rewriter.rb +4 -3
- data/lib/contrast/agent/scope.rb +116 -19
- data/lib/contrast/agent/service_heartbeat.rb +5 -2
- data/lib/contrast/agent/settings_state.rb +12 -8
- data/lib/contrast/agent/version.rb +1 -1
- data/lib/contrast/api.rb +1 -0
- data/lib/contrast/api/speedracer.rb +2 -2
- data/lib/contrast/components/agent.rb +26 -7
- data/lib/contrast/components/app_context.rb +8 -45
- data/lib/contrast/components/contrast_service.rb +3 -4
- data/lib/contrast/components/interface.rb +1 -1
- data/lib/contrast/components/scope.rb +56 -26
- data/lib/contrast/config/ruby_configuration.rb +8 -3
- data/lib/contrast/delegators.rb +9 -0
- data/lib/contrast/delegators/application_update.rb +32 -0
- data/lib/contrast/extensions/framework/rack/cookie.rb +24 -0
- data/lib/contrast/extensions/framework/rack/request.rb +24 -0
- data/lib/contrast/extensions/framework/rack/response.rb +23 -0
- data/lib/contrast/extensions/framework/rails/action_controller_railties_helper_inherited.rb +20 -0
- data/lib/contrast/extensions/framework/rails/active_record.rb +26 -0
- data/lib/contrast/extensions/framework/rails/active_record_named.rb +53 -0
- data/lib/contrast/extensions/framework/rails/active_record_time_zone_inherited.rb +21 -0
- data/lib/contrast/extensions/framework/rails/buffer.rb +28 -0
- data/lib/contrast/extensions/framework/rails/configuration.rb +27 -0
- data/lib/contrast/extensions/framework/sinatra/base.rb +59 -0
- data/lib/contrast/{core_extensions → extensions/ruby_core}/assess.rb +12 -11
- data/lib/contrast/{core_extensions → extensions/ruby_core}/assess/array.rb +4 -3
- data/lib/contrast/{core_extensions → extensions/ruby_core}/assess/assess_extension.rb +0 -2
- data/lib/contrast/{core_extensions → extensions/ruby_core}/assess/basic_object.rb +1 -1
- data/lib/contrast/{core_extensions → extensions/ruby_core}/assess/erb.rb +0 -0
- data/lib/contrast/{core_extensions → extensions/ruby_core}/assess/exec_trigger.rb +0 -0
- data/lib/contrast/{core_extensions → extensions/ruby_core}/assess/fiber.rb +3 -4
- data/lib/contrast/{core_extensions → extensions/ruby_core}/assess/hash.rb +0 -0
- data/lib/contrast/{core_extensions → extensions/ruby_core}/assess/kernel.rb +1 -1
- data/lib/contrast/{core_extensions → extensions/ruby_core}/assess/module.rb +1 -1
- data/lib/contrast/{core_extensions → extensions/ruby_core}/assess/regexp.rb +0 -0
- data/lib/contrast/{core_extensions → extensions/ruby_core}/assess/string.rb +0 -0
- data/lib/contrast/{core_extensions → extensions/ruby_core}/assess/tilt_template_trigger.rb +0 -0
- data/lib/contrast/extensions/ruby_core/assess/xpath_library_trigger.rb +40 -0
- data/lib/contrast/{core_extensions → extensions/ruby_core}/delegator.rb +0 -0
- data/lib/contrast/{core_extensions → extensions/ruby_core}/eval_trigger.rb +1 -1
- data/lib/contrast/{core_extensions → extensions/ruby_core}/inventory.rb +0 -0
- data/lib/contrast/{core_extensions → extensions/ruby_core}/inventory/datastores.rb +1 -1
- data/lib/contrast/extensions/ruby_core/module.rb +17 -0
- data/lib/contrast/{core_extensions → extensions/ruby_core}/protect.rb +0 -0
- data/lib/contrast/{core_extensions → extensions/ruby_core}/protect/applies_command_injection_rule.rb +8 -6
- data/lib/contrast/{core_extensions → extensions/ruby_core}/protect/applies_deserialization_rule.rb +7 -5
- data/lib/contrast/{core_extensions → extensions/ruby_core}/protect/applies_no_sqli_rule.rb +5 -3
- data/lib/contrast/{core_extensions → extensions/ruby_core}/protect/applies_path_traversal_rule.rb +31 -27
- data/lib/contrast/{core_extensions → extensions/ruby_core}/protect/applies_sqli_rule.rb +5 -3
- data/lib/contrast/{core_extensions → extensions/ruby_core}/protect/applies_xxe_rule.rb +9 -7
- data/lib/contrast/{core_extensions → extensions/ruby_core}/protect/kernel.rb +0 -0
- data/lib/contrast/{core_extensions → extensions/ruby_core}/protect/psych.rb +1 -1
- data/lib/contrast/{core_extensions → extensions/ruby_core}/thread.rb +0 -0
- data/lib/contrast/framework/base_support.rb +63 -0
- data/lib/contrast/framework/manager.rb +109 -0
- data/lib/contrast/framework/platform_version.rb +21 -0
- data/lib/contrast/framework/rails_support.rb +88 -0
- data/lib/contrast/framework/sinatra_application_helper.rb +49 -0
- data/lib/contrast/framework/sinatra_support.rb +94 -0
- data/lib/contrast/framework/view_technologies_descriptor.rb +20 -0
- data/lib/contrast/utils/assess/tracking_util.rb +2 -4
- data/lib/contrast/utils/class_util.rb +92 -37
- data/lib/contrast/utils/duck_utils.rb +59 -39
- data/lib/contrast/utils/environment_util.rb +5 -75
- data/lib/contrast/utils/freeze_util.rb +3 -7
- data/lib/contrast/utils/invalid_configuration_util.rb +5 -5
- data/lib/contrast/utils/job_servers_running.rb +39 -0
- data/lib/contrast/utils/ruby_ast_rewriter.rb +2 -2
- data/lib/contrast/utils/service_response_util.rb +0 -6
- data/lib/contrast/utils/sinatra_helper.rb +6 -0
- data/lib/contrast/utils/stack_trace_utils.rb +1 -1
- data/resources/assess/policy.json +74 -23
- data/resources/inventory/policy.json +1 -1
- data/resources/protect/policy.json +11 -9
- data/resources/rubocops/object/frozen_cop.rb +1 -1
- data/ruby-agent.gemspec +2 -0
- data/service_executables/VERSION +1 -1
- data/service_executables/linux/contrast-service +0 -0
- data/service_executables/mac/contrast-service +0 -0
- metadata +94 -57
- data/ext/cs__scope/cs__scope.c +0 -96
- data/ext/cs__scope/cs__scope.h +0 -33
- data/lib/contrast/agent/assess/class_reverter.rb +0 -82
- data/lib/contrast/agent/patching/policy/policy_unpatcher.rb +0 -28
- data/lib/contrast/core_extensions/module.rb +0 -42
- data/lib/contrast/core_extensions/object.rb +0 -27
- data/lib/contrast/rails_extensions/assess/action_controller_inheritance.rb +0 -48
- data/lib/contrast/rails_extensions/assess/active_record.rb +0 -32
- data/lib/contrast/rails_extensions/assess/active_record_named.rb +0 -61
- data/lib/contrast/rails_extensions/assess/configuration.rb +0 -26
- data/lib/contrast/rails_extensions/buffer.rb +0 -30
- data/lib/contrast/rails_extensions/rack.rb +0 -45
- data/lib/contrast/sinatra_extensions/assess/cookie.rb +0 -26
- data/lib/contrast/sinatra_extensions/inventory/sinatra_base.rb +0 -59
- data/lib/contrast/utils/operating_environment.rb +0 -38
- data/lib/contrast/utils/path_util.rb +0 -151
- data/lib/contrast/utils/scope_util.rb +0 -99
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Copyright (c) 2020 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
cs__scoped_require 'contrast/utils/object_share'
|
|
5
|
+
|
|
6
|
+
# Some developers override various methods on Module, which can often involve
|
|
7
|
+
# changing expected method parity/behavior which in turn prevents us from being
|
|
8
|
+
# able to reliably use affected methods. Let's alias these method so that we
|
|
9
|
+
# always have access to them.
|
|
10
|
+
class Module
|
|
11
|
+
alias_method :cs__name, :name
|
|
12
|
+
alias_method :cs__constants, :constants
|
|
13
|
+
alias_method :cs__const_defined?, :const_defined?
|
|
14
|
+
alias_method :cs__const_get, :const_get
|
|
15
|
+
alias_method :cs__const_set, :const_set
|
|
16
|
+
alias_method :cs__autoload?, :autoload?
|
|
17
|
+
end
|
|
File without changes
|
data/lib/contrast/{core_extensions → extensions/ruby_core}/protect/applies_command_injection_rule.rb
RENAMED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
4
|
cs__scoped_require 'contrast/components/interface'
|
|
5
|
-
cs__scoped_require 'contrast/
|
|
5
|
+
cs__scoped_require 'contrast/extensions/ruby_core/protect/applies_deserialization_rule'
|
|
6
6
|
|
|
7
7
|
module Contrast
|
|
8
8
|
module CoreExtensions
|
|
@@ -13,7 +13,7 @@ module Contrast
|
|
|
13
13
|
# rule should be invoked.
|
|
14
14
|
# In addition, b/c of the nature of Deserialization's sand boxing
|
|
15
15
|
# function, this Module's apply methods call through to the
|
|
16
|
-
# {#
|
|
16
|
+
# {#apply_deserialization_command_check} method of the
|
|
17
17
|
# Deserialization applicator.
|
|
18
18
|
module AppliesCommandInjectionRule
|
|
19
19
|
CS__SEMICOLON = '; '
|
|
@@ -22,12 +22,12 @@ module Contrast
|
|
|
22
22
|
include Contrast::Components::Interface
|
|
23
23
|
access_component :logging, :analysis
|
|
24
24
|
|
|
25
|
-
def
|
|
25
|
+
def apply_command_injection_rule method, _exception, _properties, object, args
|
|
26
26
|
return unless valid_command?(args)
|
|
27
27
|
|
|
28
28
|
command = build_command(args)
|
|
29
|
-
Contrast::CoreExtensions::Protect::AppliesDeserializationRule.
|
|
30
|
-
return if
|
|
29
|
+
Contrast::CoreExtensions::Protect::AppliesDeserializationRule.apply_deserialization_command_check(command)
|
|
30
|
+
return if skip_analysis?
|
|
31
31
|
|
|
32
32
|
begin
|
|
33
33
|
clazz = object.is_a?(Module) ? object : object.cs__class
|
|
@@ -41,11 +41,13 @@ module Contrast
|
|
|
41
41
|
logger.error(e, msg)
|
|
42
42
|
end
|
|
43
43
|
|
|
44
|
+
private
|
|
45
|
+
|
|
44
46
|
def rule
|
|
45
47
|
PROTECT.rule Contrast::Agent::Protect::Rule::CmdInjection::NAME
|
|
46
48
|
end
|
|
47
49
|
|
|
48
|
-
def
|
|
50
|
+
def skip_analysis?
|
|
49
51
|
context = Contrast::Agent::REQUEST_TRACKER.current
|
|
50
52
|
return true unless context&.app_loaded?
|
|
51
53
|
return true unless rule&.enabled?
|
data/lib/contrast/{core_extensions → extensions/ruby_core}/protect/applies_deserialization_rule.rb
RENAMED
|
@@ -15,9 +15,9 @@ module Contrast
|
|
|
15
15
|
access_component :logging, :analysis
|
|
16
16
|
|
|
17
17
|
class << self
|
|
18
|
-
def
|
|
18
|
+
def apply_deserialization_rule method, _exception, _properties, object, args
|
|
19
19
|
return unless valid_input?(args)
|
|
20
|
-
return if
|
|
20
|
+
return if skip_analysis?
|
|
21
21
|
|
|
22
22
|
rule.infilter(Contrast::Agent::REQUEST_TRACKER.current, args[0])
|
|
23
23
|
rescue Contrast::SecurityException => e
|
|
@@ -26,13 +26,15 @@ module Contrast
|
|
|
26
26
|
logger.error(e, "Error running untrusted-deserialization rule in #{ object }.#{ method }")
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
-
def
|
|
29
|
+
def apply_deserialization_command_check command
|
|
30
30
|
return unless command
|
|
31
|
-
return if
|
|
31
|
+
return if skip_analysis?
|
|
32
32
|
|
|
33
33
|
rule.check_command_scope(command)
|
|
34
34
|
end
|
|
35
35
|
|
|
36
|
+
private
|
|
37
|
+
|
|
36
38
|
def rule
|
|
37
39
|
PROTECT.rule Contrast::Agent::Protect::Rule::Deserialization::NAME
|
|
38
40
|
end
|
|
@@ -44,7 +46,7 @@ module Contrast
|
|
|
44
46
|
input.is_a?(String)
|
|
45
47
|
end
|
|
46
48
|
|
|
47
|
-
def
|
|
49
|
+
def skip_analysis?
|
|
48
50
|
context = Contrast::Agent::REQUEST_TRACKER.current
|
|
49
51
|
return true unless context&.app_loaded?
|
|
50
52
|
return true unless rule&.enabled?
|
|
@@ -21,9 +21,9 @@ module Contrast
|
|
|
21
21
|
include Contrast::Components::Interface
|
|
22
22
|
access_component :logging, :analysis
|
|
23
23
|
|
|
24
|
-
def
|
|
24
|
+
def apply_nosql_rule method, _exception, properties, _object, args
|
|
25
25
|
return unless valid_input?(args)
|
|
26
|
-
return if
|
|
26
|
+
return if skip_analysis?
|
|
27
27
|
|
|
28
28
|
database = properties['database']
|
|
29
29
|
operations = args[0]
|
|
@@ -41,6 +41,8 @@ module Contrast
|
|
|
41
41
|
logger.error(e, "Error running NoSQLi rule in #{ properties['database'] }")
|
|
42
42
|
end
|
|
43
43
|
|
|
44
|
+
private
|
|
45
|
+
|
|
44
46
|
def rule
|
|
45
47
|
PROTECT.rule Contrast::Agent::Protect::Rule::NoSqli::NAME
|
|
46
48
|
end
|
|
@@ -51,7 +53,7 @@ module Contrast
|
|
|
51
53
|
args[0]
|
|
52
54
|
end
|
|
53
55
|
|
|
54
|
-
def
|
|
56
|
+
def skip_analysis?
|
|
55
57
|
context = Contrast::Agent::REQUEST_TRACKER.current
|
|
56
58
|
return true unless context&.app_loaded?
|
|
57
59
|
return true unless rule&.enabled?
|
data/lib/contrast/{core_extensions → extensions/ruby_core}/protect/applies_path_traversal_rule.rb
RENAMED
|
@@ -17,32 +17,17 @@ module Contrast
|
|
|
17
17
|
access_component :logging, :analysis
|
|
18
18
|
|
|
19
19
|
class << self
|
|
20
|
-
def
|
|
21
|
-
input.cs__respond_to?(:to_s) &&
|
|
22
|
-
input.to_s.include?(Contrast::Utils::ObjectShare::WRITE_FLAG)
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
def rule
|
|
26
|
-
PROTECT.rule Contrast::Agent::Protect::Rule::PathTraversal::NAME
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
def cs__skip_analysis?
|
|
30
|
-
context = Contrast::Agent::REQUEST_TRACKER.current
|
|
31
|
-
return true unless context&.app_loaded?
|
|
32
|
-
return true unless rule&.enabled?
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
def cs__patched_apply_path_traversal_rule method, _exception, properties, _object, args
|
|
20
|
+
def apply_path_traversal_rule method, _exception, properties, _object, args
|
|
36
21
|
return unless args&.any?
|
|
37
22
|
|
|
38
23
|
path = args[0]
|
|
39
24
|
return unless path.is_a?(String)
|
|
40
|
-
return if
|
|
25
|
+
return if skip_analysis?
|
|
41
26
|
|
|
42
27
|
action = properties['action']
|
|
43
28
|
write_marker = write?(action, *args)
|
|
44
|
-
possible_write = write_marker &&
|
|
45
|
-
|
|
29
|
+
possible_write = write_marker && possible_write(write_marker)
|
|
30
|
+
path_traversal_rule(path, possible_write, method)
|
|
46
31
|
|
|
47
32
|
# If the action was copy, we need to handle the write half of it.
|
|
48
33
|
# We handled read in line above.
|
|
@@ -52,7 +37,26 @@ module Contrast
|
|
|
52
37
|
dst = args[1]
|
|
53
38
|
return unless dst.is_a?(String)
|
|
54
39
|
|
|
55
|
-
|
|
40
|
+
path_traversal_rule(dst, true, method)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def rule
|
|
46
|
+
PROTECT.rule Contrast::Agent::Protect::Rule::PathTraversal::NAME
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def possible_write input
|
|
50
|
+
input.cs__respond_to?(:to_s) &&
|
|
51
|
+
input.to_s.include?(Contrast::Utils::ObjectShare::WRITE_FLAG)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def skip_analysis?
|
|
55
|
+
context = Contrast::Agent::REQUEST_TRACKER.current
|
|
56
|
+
return true unless context&.app_loaded?
|
|
57
|
+
return true unless rule&.enabled?
|
|
58
|
+
|
|
59
|
+
false
|
|
56
60
|
end
|
|
57
61
|
|
|
58
62
|
READ = 'read'
|
|
@@ -64,11 +68,11 @@ module Contrast
|
|
|
64
68
|
return true if action == WRITE
|
|
65
69
|
|
|
66
70
|
write_marker = args.length > 1 ? args[1] : nil
|
|
67
|
-
write_marker &&
|
|
71
|
+
write_marker && possible_write(write_marker)
|
|
68
72
|
end
|
|
69
73
|
|
|
70
|
-
def
|
|
71
|
-
return unless
|
|
74
|
+
def path_traversal_rule path, possible_write, method
|
|
75
|
+
return unless applies_to?(path, possible_write)
|
|
72
76
|
|
|
73
77
|
logger.debug(nil, "checking path traversal: write=true path=#{ path }")
|
|
74
78
|
rule.infilter(Contrast::Agent::REQUEST_TRACKER.current, method, path)
|
|
@@ -79,8 +83,8 @@ module Contrast
|
|
|
79
83
|
end
|
|
80
84
|
|
|
81
85
|
CS__SAFER_REL_PATHS = %w[public app log tmp].cs__freeze
|
|
82
|
-
def
|
|
83
|
-
@
|
|
86
|
+
def safer_abs_paths
|
|
87
|
+
@_safer_abs_paths ||= begin
|
|
84
88
|
pwd = ENV['PWD']
|
|
85
89
|
if pwd
|
|
86
90
|
tmp = CS__SAFER_REL_PATHS.map { |r| "#{ pwd }/#{ r }" }
|
|
@@ -93,7 +97,7 @@ module Contrast
|
|
|
93
97
|
end
|
|
94
98
|
end
|
|
95
99
|
|
|
96
|
-
def
|
|
100
|
+
def applies_to? path, possible_write = false
|
|
97
101
|
# any possible write is a potential risk
|
|
98
102
|
return true if possible_write
|
|
99
103
|
|
|
@@ -102,7 +106,7 @@ module Contrast
|
|
|
102
106
|
|
|
103
107
|
path = path.downcase
|
|
104
108
|
if path.start_with?(Contrast::Utils::ObjectShare::SLASH)
|
|
105
|
-
|
|
109
|
+
safer_abs_paths.each do |prefix|
|
|
106
110
|
return false if path.start_with?(prefix)
|
|
107
111
|
end
|
|
108
112
|
else
|
|
@@ -21,13 +21,13 @@ module Contrast
|
|
|
21
21
|
|
|
22
22
|
access_component :logging, :analysis
|
|
23
23
|
|
|
24
|
-
def
|
|
24
|
+
def apply_sql_rule method, _exception, properties, object, args
|
|
25
25
|
database = properties['database']
|
|
26
26
|
return unless database
|
|
27
27
|
|
|
28
28
|
index = properties[Contrast::Utils::ObjectShare::INDEX]
|
|
29
29
|
return unless valid_input?(index, args)
|
|
30
|
-
return if
|
|
30
|
+
return if skip_analysis?
|
|
31
31
|
|
|
32
32
|
sql = args[index]
|
|
33
33
|
logger.debug(nil, "applying sqli rule on #{ object } for #{ method }")
|
|
@@ -38,6 +38,8 @@ module Contrast
|
|
|
38
38
|
logger.error(e, "Error running SQLi rule in #{ object }")
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
+
private
|
|
42
|
+
|
|
41
43
|
def rule
|
|
42
44
|
PROTECT.rule Contrast::Agent::Protect::Rule::Sqli::NAME
|
|
43
45
|
end
|
|
@@ -49,7 +51,7 @@ module Contrast
|
|
|
49
51
|
sql && !sql.empty?
|
|
50
52
|
end
|
|
51
53
|
|
|
52
|
-
def
|
|
54
|
+
def skip_analysis?
|
|
53
55
|
context = Contrast::Agent::REQUEST_TRACKER.current
|
|
54
56
|
return true unless context&.app_loaded?
|
|
55
57
|
return true unless rule&.enabled?
|
|
@@ -16,9 +16,9 @@ module Contrast
|
|
|
16
16
|
access_component :logging, :analysis
|
|
17
17
|
|
|
18
18
|
class << self
|
|
19
|
-
def
|
|
19
|
+
def apply_xxe_rule _method, _exception, _properties, object, args
|
|
20
20
|
return unless valid_input?(args)
|
|
21
|
-
return if
|
|
21
|
+
return if skip_analysis?
|
|
22
22
|
|
|
23
23
|
xml = args.first
|
|
24
24
|
parser = determine_parser(object)
|
|
@@ -34,10 +34,10 @@ module Contrast
|
|
|
34
34
|
|
|
35
35
|
# IO is tricky. If we can't rewind it, we can't fix it back to the
|
|
36
36
|
# original state. To be safe, we'll skip non-rewindable IO objects.
|
|
37
|
-
def
|
|
37
|
+
def apply_xxe_rule__io _method, _exception, _properties, object, args
|
|
38
38
|
need_rewind = false
|
|
39
39
|
return unless valid_io_input?(args)
|
|
40
|
-
return if
|
|
40
|
+
return if skip_analysis?
|
|
41
41
|
|
|
42
42
|
need_rewind = true
|
|
43
43
|
xml = args.first.read
|
|
@@ -56,9 +56,9 @@ module Contrast
|
|
|
56
56
|
|
|
57
57
|
# Oga's Lexer is a special case b/c the information we need is on the
|
|
58
58
|
# object itself -- specifically in the @data instance variable
|
|
59
|
-
def
|
|
59
|
+
def apply_xxe_rule__lexer _method, _exception, _properties, object, _args
|
|
60
60
|
return unless valid_data_input?(object)
|
|
61
|
-
return if
|
|
61
|
+
return if skip_analysis?
|
|
62
62
|
|
|
63
63
|
parser = determine_parser(object)
|
|
64
64
|
return unless parser
|
|
@@ -85,6 +85,8 @@ module Contrast
|
|
|
85
85
|
logger.error(e, "Error running XXE rule in #{ parser }")
|
|
86
86
|
end
|
|
87
87
|
|
|
88
|
+
private
|
|
89
|
+
|
|
88
90
|
def rule
|
|
89
91
|
PROTECT.rule Contrast::Agent::Protect::Rule::Xxe::NAME
|
|
90
92
|
end
|
|
@@ -108,7 +110,7 @@ module Contrast
|
|
|
108
110
|
object.instance_variable_get(DATA_KEY)
|
|
109
111
|
end
|
|
110
112
|
|
|
111
|
-
def
|
|
113
|
+
def skip_analysis?
|
|
112
114
|
context = Contrast::Agent::REQUEST_TRACKER.current
|
|
113
115
|
return true unless context&.app_loaded?
|
|
114
116
|
return true unless rule&.enabled?
|
|
File without changes
|
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
4
|
if defined?(Psych)
|
|
5
|
-
cs__scoped_require 'contrast/
|
|
5
|
+
cs__scoped_require 'contrast/extensions/ruby_core/protect/applies_deserialization_rule'
|
|
6
6
|
cs__scoped_require 'cs__patched_psych/cs__patched_psych'
|
|
7
7
|
end
|
|
File without changes
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Copyright (c) 2020 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 Framework
|
|
6
|
+
# The API for all subclasses to implement to correctly support a given framework
|
|
7
|
+
class BaseSupport
|
|
8
|
+
class << self
|
|
9
|
+
# The top level module name used by the framework
|
|
10
|
+
def detection_class
|
|
11
|
+
raise NoMethodError('Subclasses of BaseSupport should implement this method')
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def version
|
|
15
|
+
raise NoMethodError('Subclasses of BaseSupport should implement this method')
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def application_name
|
|
19
|
+
raise NoMethodError, 'Subclasses of BaseSupport should implement this method'
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def server_type
|
|
23
|
+
raise NoMethodError, 'Subclasses of BaseSupport should implement this method'
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Iterate through known locations, looking for files
|
|
27
|
+
# that represent view or template files. If found, for each file in the directory
|
|
28
|
+
# append the technology and the view object to the application update instance
|
|
29
|
+
def scan_views
|
|
30
|
+
raise NoMethodError, 'Subclasses of BaseSupport should implement this method'
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Find all the predefined routes for this application and append them to the
|
|
34
|
+
# provided inventory message
|
|
35
|
+
# msg should be a Contrast::Api::Dtm::ApplicationUpdate or some other msg
|
|
36
|
+
# that has a routes array consisting of Contrast::Api::Dtm::RouteCoverage
|
|
37
|
+
def collect_routes
|
|
38
|
+
raise NoMethodError, 'Subclasses of BaseSupport should implement this method'
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def current_route
|
|
42
|
+
raise NoMethodError, 'Subclasses of BaseSupport should implement this method'
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
protected
|
|
46
|
+
|
|
47
|
+
def source_or_string obj
|
|
48
|
+
if obj.cs__is_a?(Regexp)
|
|
49
|
+
obj.source
|
|
50
|
+
elsif obj.cs__respond_to?(:safe_string)
|
|
51
|
+
obj.safe_string
|
|
52
|
+
else
|
|
53
|
+
obj.to_s
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def scan_view_directories view_technology_descriptors
|
|
58
|
+
view_technology_descriptors.reject(&:empty?)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# Copyright (c) 2020 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
cs__scoped_require 'contrast/framework/view_technologies_descriptor'
|
|
5
|
+
cs__scoped_require 'contrast/framework/platform_version'
|
|
6
|
+
cs__scoped_require 'contrast/framework/base_support'
|
|
7
|
+
cs__scoped_require 'contrast/framework/rails_support'
|
|
8
|
+
cs__scoped_require 'contrast/framework/sinatra_support'
|
|
9
|
+
cs__scoped_require 'contrast/components/interface'
|
|
10
|
+
cs__scoped_require 'contrast/utils/class_util'
|
|
11
|
+
|
|
12
|
+
module Contrast
|
|
13
|
+
module Framework
|
|
14
|
+
# Allows access to framework specific information
|
|
15
|
+
class Manager
|
|
16
|
+
include Contrast::Components::Interface
|
|
17
|
+
access_component :logging, :analysis
|
|
18
|
+
|
|
19
|
+
# Order here does matter as the first framework listed will be the first one we pull information from
|
|
20
|
+
# Rack will be a special case that may involve updating some logic to handle only applying Rack if Rails/Sinatra
|
|
21
|
+
# do not exist
|
|
22
|
+
SUPPORTED_FRAMEWORKS = [
|
|
23
|
+
Contrast::Framework::RailsSupport,
|
|
24
|
+
Contrast::Framework::SinatraSupport
|
|
25
|
+
].cs__freeze
|
|
26
|
+
|
|
27
|
+
def initialize
|
|
28
|
+
@_frameworks = SUPPORTED_FRAMEWORKS.map do |framework_klass|
|
|
29
|
+
next unless enable_framework_support?(framework_klass.detection_class)
|
|
30
|
+
|
|
31
|
+
logger.debug("Detected framework #{ framework_klass.detection_class } - enabling support")
|
|
32
|
+
framework_klass
|
|
33
|
+
end
|
|
34
|
+
@_frameworks.compact!
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def find_applicable_view_technologies
|
|
38
|
+
scan_views_for_all_frameworks
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def find_route_discovery_data
|
|
42
|
+
routes_for_all_frameworks
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def platform_version
|
|
46
|
+
framework_version = first_framework_result :version, ''
|
|
47
|
+
|
|
48
|
+
Contrast::Framework::PlatformVersion.from_string(framework_version)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def server_type
|
|
52
|
+
first_framework_result :server_type, 'rack'
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def app_name
|
|
56
|
+
first_framework_result :application_name, 'root'
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def get_route_dtm request
|
|
60
|
+
result = nil
|
|
61
|
+
@_frameworks.find do |framework_klass|
|
|
62
|
+
# TODO: RUBY-763 Sinatra::Base#call patch adds the Route report
|
|
63
|
+
next if framework_klass == Contrast::Framework::SinatraSupport
|
|
64
|
+
|
|
65
|
+
result = framework_klass.current_route(request)
|
|
66
|
+
end
|
|
67
|
+
result
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
private
|
|
71
|
+
|
|
72
|
+
def enable_framework_support? klass
|
|
73
|
+
Contrast::Utils::ClassUtil.truly_defined?(klass)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def scan_views_for_all_frameworks
|
|
77
|
+
data_for_all_frameworks :scan_views
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def routes_for_all_frameworks
|
|
81
|
+
data_for_all_frameworks :collect_routes
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# This returns an array of all data from each framework in a flat, no-nil values array
|
|
85
|
+
#
|
|
86
|
+
# @param method_name [Symbol] the method to call on each FrameworkSupport class
|
|
87
|
+
# @return [Array]
|
|
88
|
+
def data_for_all_frameworks method_name
|
|
89
|
+
data = @_frameworks.flat_map do |framework|
|
|
90
|
+
framework.send(method_name)
|
|
91
|
+
end.compact
|
|
92
|
+
data
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# This returns a single object from the first framework to successfully respond
|
|
96
|
+
#
|
|
97
|
+
# @param method_name [Symbol] the method to call on each FrameworkSupport class
|
|
98
|
+
# @return [Object] - Determined by method to be invoked
|
|
99
|
+
def first_framework_result method_name, default_value
|
|
100
|
+
result = nil
|
|
101
|
+
@_frameworks.each do |framework|
|
|
102
|
+
result = framework.send(method_name)
|
|
103
|
+
break if result
|
|
104
|
+
end
|
|
105
|
+
result || default_value
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|