contrast-agent 4.10.0 → 4.11.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ab65fd574ef84fbe339e4f4d4bf340623235a442ecc11d7ba28c6af3c6f04d4d
4
- data.tar.gz: 0fa9cc44fe85713ee924101139e615d361de6d906c8f5ff4fdd345a930eee9d2
3
+ metadata.gz: 2ad97d601b81e16e1d0263d86b60a3acd0e5a5ff9cb3f42598c262774a518169
4
+ data.tar.gz: 749f87aefffd1e1504834dc7f919d45280cf560a20805184757dd9f6bda97477
5
5
  SHA512:
6
- metadata.gz: cb21ebcad0e772649d16a25d2f00cc057ee10df71638b7bd7cc6a4710a1ded28ce4dca749fa040ba2fb78e992727f4cc5d5f38e1187c363a8ea33a12333fc289
7
- data.tar.gz: c3ad4c82a9e25ecb58c0ea906f1170cffdd6811952f9c1d71a6c75166358a37e0ebba40e55e9a7433156a51122b9ca1450bb5a4a2a87f9081a4a4268d9abfdb4
6
+ metadata.gz: cfa15549565b4f17c431332c7bc7c6fe84bd5bea321860db84f24a2df5e2d5241675200ed6e50ebe53fc319d94ade6ebfed26fe664fc297dde54a1fc9f645fda
7
+ data.tar.gz: 017f02883faee2d281b7b052844653ddbb744fa27f02cf5739c38e50256eb054effb966c5b35b79b05e7d3af62f35d90509352f5c4327c9474e936c7e740ef19
@@ -35,9 +35,6 @@ module Contrast
35
35
  if object
36
36
  @object = Contrast::Utils::ClassUtil.to_contrast_string(object)
37
37
  @object_type = object.cs__class.cs__name
38
- # TODO: RUBY-1084 determine if we need to copy these tags to
39
- # restore immutability. For instance, if these tags were on a
40
- # String that was then #reverse!'d, would our tags be wrong?
41
38
  @tags = Contrast::Agent::Assess::Tracker.properties(object)&.tags
42
39
  else
43
40
  @object = Contrast::Utils::ObjectShare::NIL_STRING
@@ -83,20 +83,21 @@ module Contrast
83
83
  end
84
84
 
85
85
  def append_arg_details preshift, args
86
- preshift.args = args.dup
86
+ args_length = args.length
87
+ preshift.args = Array.new(args_length)
88
+ preshift.arg_lengths = Array.new(args_length)
87
89
  idx = 0
88
- while idx < preshift.args.size
90
+ while idx < args_length
89
91
  original_arg = args[idx]
90
- p_arg = preshift.args[idx]
92
+ p_arg = can_dup?(false, original_arg) ? original_arg.dup : original_arg
93
+ preshift.args[idx] = p_arg
94
+ preshift.arg_lengths[idx] = Contrast::Utils::DuckUtils.quacks_to?(p_arg, :length) ? p_arg.length : 0
91
95
  idx += 1
92
96
  next if p_arg.__id__ == original_arg.__id__
93
97
  next unless Contrast::Agent::Assess::Tracker.tracked?(original_arg)
94
98
 
95
99
  Contrast::Agent::Assess::Tracker.copy(original_arg, p_arg)
96
100
  end
97
- preshift.arg_lengths = preshift.args.map do |preshift_arg|
98
- Contrast::Utils::DuckUtils.quacks_to?(preshift_arg, :length) ? preshift_arg.length : 0
99
- end
100
101
  end
101
102
  end
102
103
  end
@@ -15,7 +15,7 @@ module Contrast
15
15
  case ret
16
16
  when Array
17
17
  idx = 0
18
- while idx < ret.size
18
+ while idx < ret.length
19
19
  return_value = ret[idx]
20
20
  index = idx
21
21
  idx += 1
@@ -34,7 +34,7 @@ module Contrast
34
34
 
35
35
  def captures_tagger propagation_node, preshift, ret, _block
36
36
  idx = 0
37
- while idx < ret.size
37
+ while idx < ret.length
38
38
  return_value = ret[idx]
39
39
  index = idx
40
40
  idx += 1
@@ -48,7 +48,7 @@ module Contrast
48
48
 
49
49
  def to_a_tagger propagation_node, preshift, ret, _block
50
50
  idx = 0
51
- while idx < ret.size
51
+ while idx < ret.length
52
52
  return_value = ret[idx]
53
53
  index = idx
54
54
  idx += 1
@@ -61,7 +61,7 @@ module Contrast
61
61
 
62
62
  def values_at_tagger propagation_node, preshift, ret, _block
63
63
  idx = 0
64
- while idx < ret.size
64
+ while idx < ret.length
65
65
  return_value = ret[idx]
66
66
  return_index = idx
67
67
  idx += 1
@@ -26,7 +26,6 @@ module Contrast
26
26
  return unless (properties = Contrast::Agent::Assess::Tracker.properties!(target))
27
27
 
28
28
  source_string = source.is_a?(String) ? source : source.to_s
29
-
30
29
  # If the lengths are the same, we should just copy the tags because nothing was removed, but a new
31
30
  # instance could have been created. copy_from will handle the case where the source is the target.
32
31
  if source_string.length == target.length
@@ -34,10 +33,7 @@ module Contrast
34
33
  return
35
34
  end
36
35
 
37
- source_chars = source_string.chars
38
36
  source_idx = 0
39
-
40
- target_chars = target.chars
41
37
  target_idx = 0
42
38
 
43
39
  remove_ranges = []
@@ -45,10 +41,9 @@ module Contrast
45
41
 
46
42
  # loop over the target, the result of the delete every range of characters that it differs from the
47
43
  # source represents a section that was deleted. these sections need to have their tags updated
48
- target_len = target_chars.length
49
- while target_idx < target_len
50
- target_char = target_chars[target_idx]
51
- source_char = source_chars[source_idx]
44
+ while target_idx < target.length
45
+ target_char = target[target_idx]
46
+ source_char = source_string[source_idx]
52
47
  if target_char == source_char
53
48
  target_idx += 1
54
49
  if start
@@ -63,7 +58,7 @@ module Contrast
63
58
 
64
59
  # once we're done looping over the target, anything left over is extra from the source that was
65
60
  # deleted. tags applying to it need to be removed.
66
- remove_ranges << (source_idx...source_chars.length) if source_idx != source_chars.length
61
+ remove_ranges << (source_idx...source_string.length) if source_idx != source_string.length
67
62
 
68
63
  # handle deleting the removed ranges
69
64
  properties.delete_tags_at_ranges(remove_ranges)
@@ -246,9 +246,8 @@ module Contrast
246
246
  logger.debug('Trigger source is untrackable. Unable to inspect.',
247
247
  node_id: trigger_node.id,
248
248
  source_id: source.__id__,
249
- source_type: source.cs__class.to_s,
249
+ source_type: source.cs__class.cs__name,
250
250
  frozen: source.cs__frozen?)
251
- logger.trace(source.to_s[0..99])
252
251
  end
253
252
  end
254
253
 
@@ -60,7 +60,7 @@ module Contrast
60
60
 
61
61
  digest = Contrast::Utils::Sha256Builder.instance.build_from_spec(spec)
62
62
  unless digest
63
- logger.debug('Unable to resolve digest for gem spec', spec: spec.to_s)
63
+ logger.debug('Unable to resolve digest for gem spec', spec: spec.to_s) if logger.debug?
64
64
  return
65
65
  end
66
66
  report_path = adjust_path_for_reporting(path, spec)
@@ -92,10 +92,12 @@ module Contrast
92
92
  defined?(Rack::Multipart::UploadedFile) &&
93
93
  body.is_a?(Rack::Multipart::UploadedFile)
94
94
 
95
- logger.trace("not parsing uploaded file body :: #{ body.original_filename }::#{ body.content_type }")
95
+ logger.trace('not parsing uploaded file body',
96
+ file_name: body.original_filename,
97
+ content_type: body.content_type)
96
98
  @_body = nil
97
99
  else
98
- logger.trace("parsing body from request :: #{ body.cs__class.cs__name }")
100
+ logger.trace('parsing body from request', body_type: body.cs__class.cs__name)
99
101
  @_body = Contrast::Utils::StringUtils.force_utf8(read_body(body))
100
102
  end
101
103
 
@@ -185,7 +187,7 @@ module Contrast
185
187
  when Enumerable
186
188
  idx = 0
187
189
  res = {}
188
- while idx < val.size
190
+ while idx < val.length
189
191
  res.merge! normalize_params(val[idx], prefix: "#{ prefix }[#{ idx }]")
190
192
  idx += 1
191
193
  end
@@ -131,8 +131,10 @@ module Contrast
131
131
  handle_protect_state(service_response)
132
132
  ia = service_response.input_analysis
133
133
  if ia
134
- logger.trace("Analysis from Contrast Service: evaluations=#{ ia.results.length }")
135
- logger.trace('Results', input_analysis: ia.inspect)
134
+ if logger.trace?
135
+ logger.trace('Analysis from Contrast Service', evaluations: ia.results.length)
136
+ logger.trace('Results', input_analysis: ia.inspect)
137
+ end
136
138
  @speedracer_input_analysis = ia
137
139
  speedracer_input_analysis.request = request
138
140
  else
@@ -202,7 +204,10 @@ module Contrast
202
204
  rule = ::Contrast::PROTECT.rule(rule_id)
203
205
  next unless rule
204
206
 
205
- logger.debug('Building attack result from Contrast Service input analysis result', result: ia_result.inspect)
207
+ if logger.debug?
208
+ logger.debug('Building attack result from Contrast Service input analysis result',
209
+ result: ia_result.inspect)
210
+ end
206
211
 
207
212
  attack_result = if rule.mode == :BLOCK
208
213
  # special case for rules (like reflected xss) that used to have an infilter / block mode
@@ -104,39 +104,51 @@ module Contrast
104
104
  exit_split_scope!
105
105
  end
106
106
 
107
- # Dynamic versions of the above.
108
- # These are equivalent, but they're slower and riskier.
109
- # Prefer the static methods if you know what scope you need at the call site.
107
+ # Static methods to be used, the cases are defined by the usage from the above methods
108
+ # if more methods are added - please extend the case statements as they are no longed dynamic
110
109
  def in_scope? name
111
- cs__class.ensure_valid_scope! name
112
- call = with_contrast_scope { :"in_#{ name }_scope?" }
113
- send(call)
110
+ case name
111
+ when :contrast
112
+ in_contrast_scope?
113
+ when :deserialization
114
+ in_deserialization_scope?
115
+ when :split
116
+ in_split_scope?
117
+ else
118
+ raise NoMethodError, "Scope '#{ name.inspect }' is not registered as a scope."
119
+ end
114
120
  end
115
121
 
116
122
  def enter_scope! name
117
- cs__class.ensure_valid_scope! name
118
- call = with_contrast_scope { :"enter_#{ name }_scope!" }
119
- send(call)
123
+ case name
124
+ when :contrast
125
+ enter_contrast_scope!
126
+ when :deserialization
127
+ enter_deserialization_scope!
128
+ when :split
129
+ enter_split_scope!
130
+ else
131
+ raise NoMethodError, "Scope '#{ name.inspect }' is not registered as a scope."
132
+ end
120
133
  end
121
134
 
122
135
  def exit_scope! name
123
- cs__class.ensure_valid_scope! name
124
- call = with_contrast_scope { :"exit_#{ name }_scope!" }
125
- send(call)
136
+ case name
137
+ when :contrast
138
+ exit_contrast_scope!
139
+ when :deserialization
140
+ exit_deserialization_scope!
141
+ when :split
142
+ exit_split_scope!
143
+ else
144
+ raise NoMethodError, "Scope '#{ name.inspect }' is not registered as a scope."
145
+ end
126
146
  end
127
147
 
128
148
  class << self
129
149
  def valid_scope? scope_sym
130
150
  Contrast::Agent::Scope::SCOPE_LIST.include? scope_sym
131
151
  end
132
-
133
- def ensure_valid_scope! scope_sym
134
- unless valid_scope? scope_sym # rubocop:disable Style/GuardClause
135
- with_contrast_scope do
136
- raise NoMethodError, "Scope '#{ scope_sym.inspect }' is not registered as a scope."
137
- end
138
- end
139
- end
140
152
  end
141
153
  end
142
154
  end
@@ -27,14 +27,22 @@ module Contrast
27
27
 
28
28
  private
29
29
 
30
+ # Use the TracePoint from the :end event, meaning the completion of a definition of a Class or Module (or
31
+ # really the completion of that piece of a definition, as determined by an `end` statement since there could be
32
+ # definitions across multiple files) to carry out actions required on definition. This typically involves
33
+ # patching and usage analysis
34
+ #
35
+ # @param tracepoint_event [TracePoint] the TracePoint from the :end
30
36
  def process tracepoint_event
31
37
  with_contrast_scope do
32
- logger.trace('Received TracePoint end event', module: tracepoint_event.self.to_s)
33
-
38
+ # the Module or Class that was loaded during this event
34
39
  loaded_module = tracepoint_event.self
40
+ # the file being loaded that contained this definition
35
41
  path = tracepoint_event.path
36
42
  return if path&.include?('contrast')
37
43
 
44
+ logger.trace('Received TracePoint end event', module: loaded_module, path: path)
45
+
38
46
  Contrast::Agent.framework_manager.register_late_framework(loaded_module)
39
47
  Contrast::Agent::Inventory::DependencyUsageAnalysis.instance.associate_file(path) if path
40
48
  Contrast::Agent::Patching::Policy::Patcher.patch_specific_module(loaded_module)
@@ -43,7 +51,7 @@ module Contrast
43
51
  end
44
52
  Contrast::Agent::Assess::Policy::PolicyScanner.scan(tracepoint_event)
45
53
  rescue StandardError => e
46
- logger.error('Unable to complete TracePoint analysis', e, module: loaded_module)
54
+ logger.error('Unable to complete TracePoint analysis', e, module: loaded_module, path: path)
47
55
  end
48
56
  end
49
57
  end
@@ -3,6 +3,6 @@
3
3
 
4
4
  module Contrast
5
5
  module Agent
6
- VERSION = '4.10.0'
6
+ VERSION = '4.11.0'
7
7
  end
8
8
  end
@@ -3,11 +3,13 @@
3
3
 
4
4
  require 'contrast/extension/module'
5
5
  require 'contrast/utils/object_share'
6
+ require 'contrast/utils/lru_cache'
6
7
 
7
8
  module Contrast
8
9
  module Utils
9
10
  # Utility methods for exploring the complete space of Objects
10
11
  class ClassUtil
12
+ @lru_cache = LRUCache.new
11
13
  class << self
12
14
  # some classes have had things prepended to them, like Marshal in Rails
13
15
  # 5 and higher. Their ActiveSupport::MarshalWithAutoloading will break
@@ -47,27 +49,32 @@ module Contrast
47
49
  # @param object [Object, nil] the entity to convert to a String
48
50
  # @return [String] the human readable form of the String, as defined by
49
51
  # https://bitbucket.org/contrastsecurity/assess-specifications/src/master/vulnerability/capture-snapshot.md
52
+
50
53
  def to_contrast_string object
51
- # Only treat object like a string if it actually is a string
54
+ # After implementing the LRU Cache, we firstly need to check if already had that object cached
55
+ # and if we have it - we can return it directly
56
+ return @lru_cache[object.__id__] if @lru_cache.key? object.__id__
57
+
58
+ # Only treat object like a string if it actually is a string+
52
59
  # some subclasses of String override string methods we depend on
53
- if object.cs__class == String
54
- cached = to_cached_string(object)
55
- return cached if cached
56
-
57
- object.dup
58
- elsif object.nil?
59
- Contrast::Utils::ObjectShare::NIL_STRING
60
- elsif object.cs__is_a?(Symbol)
61
- ":#{ object }"
62
- elsif object.cs__is_a?(Module) || object.cs__is_a?(Class)
63
- "#{ object.cs__name }@#{ object.__id__ }"
64
- elsif object.cs__is_a?(Regexp)
65
- object.source
66
- elsif use_to_s?(object)
67
- object.to_s
68
- else
69
- "#{ object.cs__class.cs__name }@#{ object.__id__ }"
70
- end
60
+ @lru_cache[object.__id__] = if object.cs__class == String
61
+ cached = to_cached_string(object)
62
+ return cached if cached
63
+
64
+ object.dup
65
+ elsif object.nil?
66
+ Contrast::Utils::ObjectShare::NIL_STRING
67
+ elsif object.cs__is_a?(Symbol)
68
+ ":#{ object }"
69
+ elsif object.cs__is_a?(Module) || object.cs__is_a?(Class)
70
+ "#{ object.cs__name }@#{ object.__id__ }"
71
+ elsif object.cs__is_a?(Regexp)
72
+ object.source
73
+ elsif use_to_s?(object)
74
+ object.to_s
75
+ else
76
+ "#{ object.cs__class.cs__name }@#{ object.__id__ }"
77
+ end
71
78
  end
72
79
 
73
80
  # The method const_defined? can cause autoload, which is bad for us.
@@ -9,40 +9,48 @@ module Contrast
9
9
  module IOUtil
10
10
  extend Contrast::Components::Logger::InstanceMethods
11
11
 
12
- # We're only going to call rewind on things that we believe are safe to
13
- # call it on. This method white lists those cases and returns false in
14
- # all others.
15
- def self.should_rewind? potential_io
16
- return true if potential_io.is_a?(StringIO)
17
- return false unless io?(potential_io)
18
-
19
- should_rewind_io?(potential_io)
20
- rescue StandardError => e
21
- logger.debug('Encountered an issue determining if rewindable', e, module: potential_io.cs__class.cs__name)
22
- false
23
- end
24
-
25
- # IO cannot be used with streams such as pipes, ttys, and sockets.
26
- def self.should_rewind_io? io
27
- return false if io.tty?
28
-
29
- status = io.stat
30
- return false unless status
31
- return false if status.pipe?
32
- return false if status.socket?
33
-
34
- true
35
- end
36
-
37
- # A class is IO if it is a decedent or delegate of IO
38
- def self.io? object
39
- return true if object.is_a?(IO)
40
-
41
- # DelegateClass, which is a Delegator, defines __getobj__ as a way to
42
- # get the object that the class wraps around (delegates to)
43
- return false unless object.is_a?(Delegator)
44
-
45
- object.__getobj__.is_a?(IO)
12
+ class << self
13
+ # We're only going to call rewind on things that we believe are safe to
14
+ # call it on. This method white lists those cases and returns false in
15
+ # all others.
16
+ def should_rewind? potential_io
17
+ return true if potential_io.is_a?(StringIO)
18
+ return false unless io?(potential_io)
19
+
20
+ should_rewind_io?(potential_io)
21
+ rescue StandardError => e
22
+ logger.debug('Encountered an issue determining if rewindable', e, module: potential_io.cs__class.cs__name)
23
+ false
24
+ end
25
+
26
+ # A class is IO if it is a decedent or delegate of IO
27
+ def io? object
28
+ return true if object.is_a?(IO)
29
+
30
+ # DelegateClass, which is a Delegator, defines __getobj__ as a way to
31
+ # get the object that the class wraps around (delegates to)
32
+ return false unless object.is_a?(Delegator)
33
+
34
+ object.__getobj__.is_a?(IO)
35
+ end
36
+
37
+ private
38
+
39
+ # IO rewind cannot be used with streams such as pipes, ttys, and sockets or for ones which have been closed.
40
+ #
41
+ # @param io [IO] the input to check for the ability to rewind
42
+ # @return [Boolean] if the given IO can be rewound
43
+ def should_rewind_io? io
44
+ return false if io.closed?
45
+ return false if io.tty?
46
+
47
+ status = io.stat
48
+ return false unless status
49
+ return false if status.pipe?
50
+ return false if status.socket?
51
+
52
+ true
53
+ end
46
54
  end
47
55
  end
48
56
  end
@@ -0,0 +1,43 @@
1
+ # Copyright (c) 2021 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
2
+ # frozen_string_literal: true
3
+
4
+ require 'contrast/components/logger'
5
+
6
+ module Contrast
7
+ module Utils
8
+ # A LRU(Least Recently Used) Cache store.
9
+ class LRUCache
10
+ def initialize capacity = 500
11
+ raise StandardError 'Capacity must be bigger than 0' if capacity <= 0
12
+
13
+ @capacity = capacity
14
+ @cache = {}
15
+ end
16
+
17
+ def [] key
18
+ val = @cache.delete(key)
19
+ @cache[key] = val if val
20
+ val
21
+ end
22
+
23
+ def []= key, value
24
+ @cache.delete(key)
25
+ @cache[key] = value
26
+ @cache.shift if @cache.size > @capacity
27
+ value # rubocop:disable Lint/Void
28
+ end
29
+
30
+ def keys
31
+ @cache.keys
32
+ end
33
+
34
+ def key? key
35
+ @cache.key?(key)
36
+ end
37
+
38
+ def values
39
+ @cache.values
40
+ end
41
+ end
42
+ end
43
+ end
@@ -38,7 +38,7 @@ module Contrast
38
38
  return if node.children.all? { |child_node| child_node.type == :str }
39
39
  new_content = +'('
40
40
  idx = 0
41
- while idx < node.children.size
41
+ while idx < node.children.length
42
42
  #node.children.each_with_index do |child_node, index|
43
43
  child_node = node.children[idx]
44
44
  # A begin node looks like #{some_code} in ruby, we do a substring
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.10.0
4
+ version: 4.11.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: 2021-08-31 00:00:00.000000000 Z
16
+ date: 2021-09-23 00:00:00.000000000 Z
17
17
  dependencies:
18
18
  - !ruby/object:Gem::Dependency
19
19
  name: bundler
@@ -617,20 +617,20 @@ executables:
617
617
  - contrast_service
618
618
  extensions:
619
619
  - ext/cs__common/extconf.rb
620
- - ext/cs__assess_fiber_track/extconf.rb
621
- - ext/cs__assess_marshal_module/extconf.rb
622
- - ext/cs__assess_kernel/extconf.rb
623
- - ext/cs__assess_basic_object/extconf.rb
624
- - ext/cs__assess_string/extconf.rb
620
+ - ext/cs__assess_array/extconf.rb
625
621
  - ext/cs__assess_regexp/extconf.rb
626
622
  - ext/cs__protect_kernel/extconf.rb
623
+ - ext/cs__assess_marshal_module/extconf.rb
624
+ - ext/cs__assess_yield_track/extconf.rb
625
+ - ext/cs__assess_string_interpolation26/extconf.rb
626
+ - ext/cs__assess_fiber_track/extconf.rb
627
+ - ext/cs__assess_string/extconf.rb
628
+ - ext/cs__assess_hash/extconf.rb
629
+ - ext/cs__assess_kernel/extconf.rb
627
630
  - ext/cs__contrast_patch/extconf.rb
628
- - ext/cs__assess_active_record_named/extconf.rb
631
+ - ext/cs__assess_basic_object/extconf.rb
629
632
  - ext/cs__assess_module/extconf.rb
630
- - ext/cs__assess_hash/extconf.rb
631
- - ext/cs__assess_string_interpolation26/extconf.rb
632
- - ext/cs__assess_array/extconf.rb
633
- - ext/cs__assess_yield_track/extconf.rb
633
+ - ext/cs__assess_active_record_named/extconf.rb
634
634
  extra_rdoc_files: []
635
635
  files:
636
636
  - ".clang-format"
@@ -1079,6 +1079,7 @@ files:
1079
1079
  - lib/contrast/utils/invalid_configuration_util.rb
1080
1080
  - lib/contrast/utils/io_util.rb
1081
1081
  - lib/contrast/utils/job_servers_running.rb
1082
+ - lib/contrast/utils/lru_cache.rb
1082
1083
  - lib/contrast/utils/object_share.rb
1083
1084
  - lib/contrast/utils/os.rb
1084
1085
  - lib/contrast/utils/preflight_util.rb