contrast-agent 4.1.0 → 4.2.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/lib/contrast/agent/patching/policy/patch.rb +6 -0
- data/lib/contrast/agent/protect/rule/cmd_injection.rb +9 -25
- data/lib/contrast/agent/scope.rb +59 -53
- data/lib/contrast/agent/version.rb +1 -1
- data/lib/contrast/common_agent_configuration.rb +2 -1
- data/lib/contrast/components/scope.rb +49 -6
- data/lib/contrast/components/settings.rb +6 -3
- data/lib/contrast/extension/assess/array.rb +1 -1
- data/lib/contrast/extension/assess/exec_trigger.rb +0 -3
- data/ruby-agent.gemspec +1 -0
- data/service_executables/VERSION +1 -1
- data/service_executables/linux/contrast-service +0 -0
- data/service_executables/mac/contrast-service +0 -0
- metadata +13 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 713e0f9cd16184d4b5da094f0d05b006870f557edfa0fc0ac199bf1035acfe45
|
4
|
+
data.tar.gz: f37a1ab7901a7c42bf5dce897c6dd0218f4df40d057c8719e3027f802ecd3c52
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f1bcf5495957815fbe1acc801e9cdc9567a1a25f657b425a335cfa1412054ecdf4f92662d0eef0b19bdc2a1c5295b758d19ed4adf4cb7f37ef67bfaf4b67262f
|
7
|
+
data.tar.gz: bd3e6f02d68c7eaa9385f6626e52b3fe16cecbac10d53f8c82dd01b733c9f39666183afd21df601318ed95c482338b786e13cb8f087c8076872cd5fb3f4cfad8
|
@@ -365,12 +365,18 @@ module Contrast
|
|
365
365
|
unless target_module.instance_methods(false).include? underlying_method_name
|
366
366
|
# alias_method may be private
|
367
367
|
target_module.send(:alias_method, underlying_method_name, method_name)
|
368
|
+
# TODO: RUBY-1052
|
369
|
+
# rubocop:disable Kernel/DefineMethod
|
368
370
|
target_module.send(:define_method, method_name, unbound_method.bind(target_module))
|
371
|
+
# rubocop:enable Kernel/DefineMethod
|
369
372
|
end
|
370
373
|
target_module.send(visibility, method_name) # e.g., module.private(:my_method)
|
371
374
|
when :prepend
|
372
375
|
prepending_module = Module.new
|
376
|
+
# TODO: RUBY-1052
|
377
|
+
# rubocop:disable Kernel/DefineMethod
|
373
378
|
prepending_module.send(:define_method, method_name, unbound_method.bind(target_module))
|
379
|
+
# rubocop:enable Kernel/DefineMethod
|
374
380
|
prepending_module.send(visibility, method_name)
|
375
381
|
# This prepends to the singleton class (it patches a class method)
|
376
382
|
target_module.prepend prepending_module
|
@@ -1,7 +1,6 @@
|
|
1
1
|
# Copyright (c) 2020 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require 'contrast/agent/at_exit_hook'
|
5
4
|
require 'contrast/agent/protect/rule/base_service'
|
6
5
|
require 'contrast/utils/stack_trace_utils'
|
7
6
|
require 'contrast/utils/object_share'
|
@@ -45,11 +44,6 @@ module Contrast
|
|
45
44
|
raise Contrast::SecurityException.new(
|
46
45
|
self,
|
47
46
|
"Command Injection rule triggered. Call to #{ classname }.#{ method } blocked.")
|
48
|
-
ensure
|
49
|
-
# Kernel#exec replaces the current process and does not go through
|
50
|
-
# at_exit hooks. Kernel#` runs as a subshell - messages appended
|
51
|
-
# here do not seem to be present in the original process.
|
52
|
-
Contrast::Agent::AtExitHook.on_exit if %i[exec `].include?(method.to_sym)
|
53
47
|
end
|
54
48
|
|
55
49
|
def build_attack_with_match context, input_analysis_result, result, candidate_string, **kwargs
|
@@ -104,37 +98,27 @@ module Contrast
|
|
104
98
|
|
105
99
|
def report_command_execution context, command, **kwargs
|
106
100
|
return unless report_any_command_execution?
|
107
|
-
return
|
101
|
+
return if protect_excluded_by_code?
|
108
102
|
|
109
103
|
build_attack_with_match(context, nil, nil, command, **kwargs)
|
110
104
|
end
|
111
105
|
|
112
106
|
def find_probable_attacker context, potential_attack_string, ia_results, **kwargs
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
next unless chained_command?(input_analysis_result.value)
|
118
|
-
|
119
|
-
most_likely = input_analysis_result
|
120
|
-
break
|
121
|
-
end
|
122
|
-
end
|
123
|
-
return result unless most_likely
|
107
|
+
return unless chained_command?(potential_attack_string)
|
108
|
+
|
109
|
+
likely_attacker = ia_results.find { |input_analysis_result| chained_command?(input_analysis_result.value) }
|
110
|
+
return unless likely_attacker
|
124
111
|
|
125
|
-
|
112
|
+
build_attack_with_match(
|
126
113
|
context,
|
127
|
-
|
128
|
-
|
114
|
+
likely_attacker,
|
115
|
+
nil,
|
129
116
|
potential_attack_string,
|
130
117
|
**kwargs)
|
131
|
-
result
|
132
118
|
end
|
133
119
|
|
134
120
|
def chained_command? command
|
135
|
-
|
136
|
-
|
137
|
-
false
|
121
|
+
CHAINED_COMMAND_CHARS.match(command)
|
138
122
|
end
|
139
123
|
|
140
124
|
# Part of the Hardening for Command Injection detection is the
|
data/lib/contrast/agent/scope.rb
CHANGED
@@ -15,67 +15,73 @@ module Contrast
|
|
15
15
|
# Instead, we should say "If I'm already doing Contrast things, don't track
|
16
16
|
# this"
|
17
17
|
class Scope
|
18
|
-
# The following %i[] list is the authoritative list
|
19
|
-
# of scopes. If you define a new symbol here, you'll
|
20
|
-
# get scope methods:
|
21
|
-
# %i[monkey] ->
|
22
|
-
# enter_monkey_scope!
|
23
|
-
# exit_monkey_scope!
|
24
|
-
# in_monkey_scope?
|
25
|
-
# with_monkey_scope { special_monkey_function }
|
26
18
|
SCOPE_LIST = %i[contrast deserialization].cs__freeze
|
27
19
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
instance_variable_set(iv_sym, 0)
|
32
|
-
end
|
20
|
+
def initialize
|
21
|
+
instance_variable_set(:@contrast_scope, 0)
|
22
|
+
instance_variable_set(:@deserialization_scope, 0)
|
33
23
|
end
|
34
24
|
|
35
|
-
|
36
|
-
|
25
|
+
def in_contrast_scope?
|
26
|
+
instance_variable_get(:@contrast_scope).positive?
|
27
|
+
end
|
37
28
|
|
38
|
-
|
39
|
-
|
40
|
-
|
29
|
+
def in_deserialization_scope?
|
30
|
+
instance_variable_get(:@deserialization_scope).positive?
|
31
|
+
end
|
41
32
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
end
|
33
|
+
def enter_contrast_scope!
|
34
|
+
level = instance_variable_get(:@contrast_scope)
|
35
|
+
instance_variable_set(:@contrast_scope, level + 1)
|
36
|
+
end
|
47
37
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
# should cancel each other out.
|
53
|
-
#
|
54
|
-
# so we prefer this sequence:
|
55
|
-
# scope = 0
|
56
|
-
# exit = -1
|
57
|
-
# enter = 0
|
58
|
-
# enter = 1
|
59
|
-
# exit = 0
|
60
|
-
# scope = 0
|
61
|
-
#
|
62
|
-
# over this sequence:
|
63
|
-
# scope = 0
|
64
|
-
# exit = 0
|
65
|
-
# enter = 1
|
66
|
-
# enter = 2
|
67
|
-
# exit = 1
|
68
|
-
# scope = 1
|
69
|
-
level = instance_variable_get(iv_sym)
|
70
|
-
instance_variable_set(iv_sym, level - 1)
|
71
|
-
end
|
38
|
+
def enter_deserialization_scope!
|
39
|
+
level = instance_variable_get(:@deserialization_scope)
|
40
|
+
instance_variable_set(:@deserialization_scope, level + 1)
|
41
|
+
end
|
72
42
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
43
|
+
# Scope Exits...
|
44
|
+
# by design, can go below zero.
|
45
|
+
# every exit/enter pair (regardless of series)
|
46
|
+
# should cancel each other out.
|
47
|
+
#
|
48
|
+
# so we prefer this sequence:
|
49
|
+
# scope = 0
|
50
|
+
# exit = -1
|
51
|
+
# enter = 0
|
52
|
+
# enter = 1
|
53
|
+
# exit = 0
|
54
|
+
# scope = 0
|
55
|
+
#
|
56
|
+
# over this sequence:
|
57
|
+
# scope = 0
|
58
|
+
# exit = 0
|
59
|
+
# enter = 1
|
60
|
+
# enter = 2
|
61
|
+
# exit = 1
|
62
|
+
# scope = 1
|
63
|
+
def exit_contrast_scope!
|
64
|
+
level = instance_variable_get(:@contrast_scope)
|
65
|
+
instance_variable_set(:@contrast_scope, level - 1)
|
66
|
+
end
|
67
|
+
|
68
|
+
def exit_deserialization_scope!
|
69
|
+
level = instance_variable_get(:@deserialization_scope)
|
70
|
+
instance_variable_set(:@deserialization_scope, level - 1)
|
71
|
+
end
|
72
|
+
|
73
|
+
def with_contrast_scope
|
74
|
+
enter_contrast_scope!
|
75
|
+
yield
|
76
|
+
ensure
|
77
|
+
exit_contrast_scope!
|
78
|
+
end
|
79
|
+
|
80
|
+
def with_deserialization_scope
|
81
|
+
enter_deserialization_scope!
|
82
|
+
yield
|
83
|
+
ensure
|
84
|
+
exit_deserialization_scope!
|
79
85
|
end
|
80
86
|
|
81
87
|
# Dynamic versions of the above.
|
@@ -65,7 +65,8 @@ module Contrast
|
|
65
65
|
end
|
66
66
|
|
67
67
|
%w[name default description required_languages display].each do |field|
|
68
|
-
|
68
|
+
# TODO: RUBY-1052
|
69
|
+
define_method(field) { hsh[field].dup } # rubocop:disable Kernel/DefineMethod
|
69
70
|
end
|
70
71
|
end
|
71
72
|
|
@@ -42,18 +42,61 @@ module Contrast
|
|
42
42
|
# For each instance method on a scope, define a forwarder
|
43
43
|
# to the scope on the current execution context's scope.
|
44
44
|
|
45
|
-
Contrast::Agent::Scope.public_instance_methods(false).each do |method_sym|
|
46
|
-
define_method(method_sym) do |*args, &block|
|
47
|
-
scope_for_current_ec.send(method_sym, *args, &block)
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
45
|
def scope_for_current_ec
|
52
46
|
MONITOR.synchronize do
|
53
47
|
return EXECUTION_CONTEXT[Fiber.current] ||= Contrast::Agent::Scope.new
|
54
48
|
end
|
55
49
|
end
|
56
50
|
|
51
|
+
def enter_contrast_scope!
|
52
|
+
scope_for_current_ec.enter_contrast_scope!
|
53
|
+
end
|
54
|
+
|
55
|
+
def enter_deserialization_scope!
|
56
|
+
scope_for_current_ec.enter_deserialization_scope!
|
57
|
+
end
|
58
|
+
|
59
|
+
def enter_scope! name
|
60
|
+
scope_for_current_ec.enter_scope! name
|
61
|
+
end
|
62
|
+
|
63
|
+
def exit_contrast_scope!
|
64
|
+
scope_for_current_ec.exit_contrast_scope!
|
65
|
+
end
|
66
|
+
|
67
|
+
def exit_deserialization_scope!
|
68
|
+
scope_for_current_ec.exit_deserialization_scope!
|
69
|
+
end
|
70
|
+
|
71
|
+
def exit_scope! name
|
72
|
+
scope_for_current_ec.exit_scope! name
|
73
|
+
end
|
74
|
+
|
75
|
+
def in_contrast_scope?
|
76
|
+
scope_for_current_ec.in_contrast_scope?
|
77
|
+
end
|
78
|
+
|
79
|
+
def in_deserialization_scope?
|
80
|
+
scope_for_current_ec.in_deserialization_scope?
|
81
|
+
end
|
82
|
+
|
83
|
+
def in_scope? name
|
84
|
+
scope_for_current_ec.in_scope? name
|
85
|
+
end
|
86
|
+
|
87
|
+
def with_contrast_scope
|
88
|
+
scope_for_current_ec.enter_contrast_scope!
|
89
|
+
yield
|
90
|
+
ensure
|
91
|
+
scope_for_current_ec.exit_contrast_scope!
|
92
|
+
end
|
93
|
+
|
94
|
+
def with_deserialization_scope
|
95
|
+
scope_for_current_ec.enter_deserialization_scope!
|
96
|
+
ensure
|
97
|
+
scope_for_current_ec.exit_deserialization_scope!
|
98
|
+
end
|
99
|
+
|
57
100
|
# TODO: RUBY-572
|
58
101
|
#
|
59
102
|
# Current behavior is to no-op if we're not "in a request context".
|
@@ -57,19 +57,22 @@ module Contrast
|
|
57
57
|
# Meta-define an accessor for each state attribute.
|
58
58
|
|
59
59
|
PROTECT_STATE_ATTRS.each do |attr|
|
60
|
-
|
60
|
+
# TODO: RUBY-1052
|
61
|
+
define_method(attr) do # rubocop:disable Kernel/DefineMethod
|
61
62
|
protect_state[attr]
|
62
63
|
end
|
63
64
|
end
|
64
65
|
|
65
66
|
ASSESS_STATE_ATTRS.each do |attr|
|
66
|
-
|
67
|
+
# TODO: RUBY-1052
|
68
|
+
define_method(attr) do # rubocop:disable Kernel/DefineMethod
|
67
69
|
assess_state[attr]
|
68
70
|
end
|
69
71
|
end
|
70
72
|
|
71
73
|
APPLICATION_STATE_ATTRS.each do |attr|
|
72
|
-
|
74
|
+
# TODO: RUBY-1052
|
75
|
+
define_method(attr) do # rubocop:disable Kernel/DefineMethod
|
73
76
|
application_state[attr]
|
74
77
|
end
|
75
78
|
end
|
@@ -35,7 +35,7 @@ module Contrast
|
|
35
35
|
# operation happens in C, we have to do it here rather than rely on the
|
36
36
|
# patch of our String append or concat methods.
|
37
37
|
def cs__track_join ary, separator, ret
|
38
|
-
return ret unless ary
|
38
|
+
return ret unless ary&.any? { |element| Contrast::Agent::Assess::Tracker.tracked?(element) }
|
39
39
|
return ret if Contrast::Agent::Patching::Policy::Patch.skip_assess_analysis?
|
40
40
|
|
41
41
|
with_contrast_scope do
|
data/ruby-agent.gemspec
CHANGED
data/service_executables/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
2.
|
1
|
+
2.16.0
|
Binary file
|
Binary file
|
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: 4.
|
4
|
+
version: 4.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- galen.palmer@contrastsecurity.com
|
@@ -9,10 +9,11 @@ authors:
|
|
9
9
|
- donald.propst@contrastsecurity.com
|
10
10
|
- alex.macdonald@contrastsecurity.com
|
11
11
|
- mark.petersen@contrastsecurity.com
|
12
|
+
- joshua.reed@contrastsecurity.com
|
12
13
|
autorequire:
|
13
14
|
bindir: exe
|
14
15
|
cert_chain: []
|
15
|
-
date: 2020-
|
16
|
+
date: 2020-12-18 00:00:00.000000000 Z
|
16
17
|
dependencies:
|
17
18
|
- !ruby/object:Gem::Dependency
|
18
19
|
name: amazing_print
|
@@ -498,20 +499,20 @@ executables:
|
|
498
499
|
- contrast_service
|
499
500
|
extensions:
|
500
501
|
- ext/cs__common/extconf.rb
|
501
|
-
- ext/cs__contrast_patch/extconf.rb
|
502
502
|
- ext/cs__assess_active_record_named/extconf.rb
|
503
|
-
- ext/cs__assess_module/extconf.rb
|
504
|
-
- ext/cs__assess_marshal_module/extconf.rb
|
505
|
-
- ext/cs__assess_hash/extconf.rb
|
506
|
-
- ext/cs__assess_regexp/extconf.rb
|
507
|
-
- ext/cs__assess_string/extconf.rb
|
508
|
-
- ext/cs__protect_kernel/extconf.rb
|
509
|
-
- ext/cs__assess_string_interpolation26/extconf.rb
|
510
|
-
- ext/cs__assess_kernel/extconf.rb
|
511
503
|
- ext/cs__assess_fiber_track/extconf.rb
|
504
|
+
- ext/cs__assess_basic_object/extconf.rb
|
505
|
+
- ext/cs__contrast_patch/extconf.rb
|
512
506
|
- ext/cs__assess_array/extconf.rb
|
507
|
+
- ext/cs__protect_kernel/extconf.rb
|
508
|
+
- ext/cs__assess_kernel/extconf.rb
|
509
|
+
- ext/cs__assess_regexp/extconf.rb
|
510
|
+
- ext/cs__assess_hash/extconf.rb
|
511
|
+
- ext/cs__assess_module/extconf.rb
|
512
|
+
- ext/cs__assess_string_interpolation26/extconf.rb
|
513
|
+
- ext/cs__assess_marshal_module/extconf.rb
|
513
514
|
- ext/cs__assess_yield_track/extconf.rb
|
514
|
-
- ext/
|
515
|
+
- ext/cs__assess_string/extconf.rb
|
515
516
|
extra_rdoc_files: []
|
516
517
|
files:
|
517
518
|
- ".clang-format"
|