contrast-agent 4.3.2 → 4.4.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.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/lib/contrast/agent.rb +5 -1
  3. data/lib/contrast/agent/assess.rb +0 -9
  4. data/lib/contrast/agent/assess/contrast_event.rb +0 -2
  5. data/lib/contrast/agent/assess/contrast_object.rb +5 -2
  6. data/lib/contrast/agent/assess/finalizers/hash.rb +7 -0
  7. data/lib/contrast/agent/assess/policy/dynamic_source_factory.rb +17 -3
  8. data/lib/contrast/agent/assess/policy/propagation_method.rb +28 -13
  9. data/lib/contrast/agent/assess/policy/propagator/append.rb +28 -13
  10. data/lib/contrast/agent/assess/policy/propagator/database_write.rb +21 -16
  11. data/lib/contrast/agent/assess/policy/propagator/splat.rb +23 -13
  12. data/lib/contrast/agent/assess/policy/propagator/split.rb +14 -7
  13. data/lib/contrast/agent/assess/policy/propagator/substitution.rb +30 -14
  14. data/lib/contrast/agent/assess/policy/trigger_method.rb +13 -8
  15. data/lib/contrast/agent/assess/policy/trigger_node.rb +28 -7
  16. data/lib/contrast/agent/assess/policy/trigger_validation/redos_validator.rb +59 -0
  17. data/lib/contrast/agent/assess/policy/trigger_validation/ssrf_validator.rb +1 -2
  18. data/lib/contrast/agent/assess/policy/trigger_validation/trigger_validation.rb +6 -4
  19. data/lib/contrast/agent/assess/policy/trigger_validation/xss_validator.rb +2 -4
  20. data/lib/contrast/agent/assess/properties.rb +0 -2
  21. data/lib/contrast/agent/assess/property/tagged.rb +37 -19
  22. data/lib/contrast/agent/assess/tracker.rb +1 -1
  23. data/lib/contrast/agent/middleware.rb +85 -55
  24. data/lib/contrast/agent/patching/policy/patch_status.rb +1 -1
  25. data/lib/contrast/agent/patching/policy/patcher.rb +51 -44
  26. data/lib/contrast/agent/patching/policy/trigger_node.rb +5 -2
  27. data/lib/contrast/agent/protect/rule/sqli.rb +17 -11
  28. data/lib/contrast/agent/request_context.rb +12 -0
  29. data/lib/contrast/agent/thread.rb +1 -1
  30. data/lib/contrast/agent/thread_watcher.rb +20 -5
  31. data/lib/contrast/agent/version.rb +1 -1
  32. data/lib/contrast/api/communication/messaging_queue.rb +18 -21
  33. data/lib/contrast/api/communication/response_processor.rb +8 -1
  34. data/lib/contrast/api/communication/socket_client.rb +22 -14
  35. data/lib/contrast/api/decorators.rb +2 -0
  36. data/lib/contrast/api/decorators/agent_startup.rb +58 -0
  37. data/lib/contrast/api/decorators/application_startup.rb +51 -0
  38. data/lib/contrast/api/decorators/route_coverage.rb +15 -5
  39. data/lib/contrast/api/decorators/trace_event.rb +42 -14
  40. data/lib/contrast/components/agent.rb +2 -0
  41. data/lib/contrast/components/app_context.rb +4 -22
  42. data/lib/contrast/components/sampling.rb +48 -6
  43. data/lib/contrast/components/settings.rb +5 -4
  44. data/lib/contrast/framework/manager.rb +13 -12
  45. data/lib/contrast/framework/rails/support.rb +42 -43
  46. data/lib/contrast/framework/sinatra/support.rb +100 -41
  47. data/lib/contrast/logger/log.rb +31 -15
  48. data/lib/contrast/utils/class_util.rb +3 -1
  49. data/lib/contrast/utils/heap_dump_util.rb +103 -87
  50. data/lib/contrast/utils/invalid_configuration_util.rb +21 -12
  51. data/resources/assess/policy.json +3 -9
  52. data/resources/deadzone/policy.json +6 -0
  53. data/ruby-agent.gemspec +54 -16
  54. metadata +105 -136
  55. data/lib/contrast/agent/assess/rule.rb +0 -18
  56. data/lib/contrast/agent/assess/rule/base.rb +0 -52
  57. data/lib/contrast/agent/assess/rule/redos.rb +0 -67
  58. data/lib/contrast/framework/sinatra/patch/base.rb +0 -83
  59. data/lib/contrast/framework/sinatra/patch/support.rb +0 -27
  60. data/lib/contrast/utils/prevent_serialization.rb +0 -52
@@ -1,18 +0,0 @@
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 Agent
6
- module Assess
7
- # This is the base module for our assess rule classes. It is intended to
8
- # facilitate the patching of the application for Assess functionality. Any
9
- # class under this namespace should be required here, providing a single
10
- # point of require for this functionality.
11
- module Rule
12
- end
13
- end
14
- end
15
- end
16
-
17
- require 'contrast/agent/assess/rule/base'
18
- require 'contrast/agent/assess/rule/provider'
@@ -1,52 +0,0 @@
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
- require 'digest'
5
- require 'zlib'
6
- require 'contrast/components/interface'
7
-
8
- module Contrast
9
- module Agent
10
- module Assess
11
- module Rule
12
- # The base class for each of our Assess Rules
13
- class Base
14
- include Contrast::Components::Interface
15
- access_component :agent, :analysis, :logging, :settings
16
-
17
- def initialize
18
- SETTINGS.assess_rules[name] = self
19
- end
20
-
21
- # Should return the name as it is known to Teamserver; defaults to
22
- # class
23
- def name
24
- cs__class.name
25
- end
26
-
27
- def enabled?
28
- ASSESS.enabled? && !ASSESS.rule_disabled?(name)
29
- end
30
-
31
- def prefilter _context; end
32
-
33
- # If a rule needs to inspect the response body it is not stream safe
34
- # The rule should override this and return false
35
- def stream_safe?
36
- true
37
- end
38
-
39
- def postfilter _context; end
40
-
41
- # this rule is excluded if any of the given exclusions have a
42
- # protection rule that matches this rule name
43
- def excluded? exclusions
44
- Array(exclusions).any? do |ex|
45
- ex.assess_rule?(name)
46
- end
47
- end
48
- end
49
- end
50
- end
51
- end
52
- end
@@ -1,67 +0,0 @@
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 Agent
6
- module Assess
7
- module Rule
8
- # A regexp is only vulnerable to REDOS if it's going to run
9
- # with pathologically bad performance.
10
- # We report a vulnerability if the regexp is liable to run
11
- # with quadratic time for some input.
12
- # This vastly errs on the side of false positives.
13
- class Redos < Contrast::Agent::Assess::Rule::Base
14
- class << self
15
- NAME = 'redos'
16
- def name
17
- NAME
18
- end
19
-
20
- def regexp_complexity_check context, trigger_node, source, object, ret, *args
21
- # we can arrive here either from:
22
- # regexp =~ string
23
- # string =~ regexp
24
- # regexp.match string
25
- #
26
- # so object/args[0] can be string/regexp or regexp/string.
27
- regexp = object.is_a?(Regexp) ? object : args[0]
28
- string = object.is_a?(String) ? object : args[0]
29
-
30
- # (1) regexp must be exploitable
31
- return unless regexp_vulnerable?(regexp)
32
-
33
- # (2) regexp must evaluate against user input
34
- return unless trigger_node.violated?(string)
35
-
36
- Contrast::Agent::Assess::Policy::TriggerMethod.build_finding(context, trigger_node, source, object, ret, *args)
37
- end
38
-
39
- protected
40
-
41
- VULNERABLE_PATTERN = /[\[(].*?[\[(].*?[\])][*+?].*?[\])][*+?]/.cs__freeze
42
-
43
- # Does the regexp
44
- # https://bitbucket.org/contrastsecurity/assess-specifications/src/master/rules/dataflow/redos.md
45
- def regexp_vulnerable? regexp
46
- # A pattern is considered vulnerable if it has 2 or more levels of nested multi-matching.
47
- # A level is defined as any set of opening and closing control characters immediately followed by a multi match control character.
48
- # A control character is defined as one of the OPENING_CHARS, CLOSING_CHARS,
49
- # or MULTI_MATCH_CHARS that is not immediately preceded by an escaping \ character.
50
- # OPENING_CHARS are ( and [ CLOSING_CHARS are ) and ] MULTI_MATCH_CHARS are +, *, and ?
51
-
52
- # Nota bene about Regexp#to_s: it doesn't necessarily give you the original Regexp back
53
- # (in the sense of `my_str == Regexp.new(my_str).to_s`), it gives you a Regexp that
54
- # will have the same functional characteristics as the original.
55
- # Regexp#inspect gives you a "more nicely formatted" version than #to_s.
56
- # Regexp#source will give you the original source.
57
-
58
- # Use #match? because it doesn't fill out global variables
59
- # in the way match or =~ do.
60
- VULNERABLE_PATTERN.match? regexp.source
61
- end
62
- end
63
- end
64
- end
65
- end
66
- end
67
- end
@@ -1,83 +0,0 @@
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
- require 'contrast/framework/sinatra/support'
5
-
6
- module Contrast
7
- module Framework
8
- module Sinatra
9
- module Patch
10
- # Our patch into the Sinatra::Base Class, allowing for the inventory of the
11
- # routes called by the application
12
- module Base
13
- class << self
14
- # Use logic copied from Sinatra::Base#process_route to determine which
15
- # pattern matches the request being invoked by Sinatra::Base#call!
16
- #
17
- # @param clazz [Class] the Class that extends Sinatra::Base in
18
- # which this route is defined.
19
- # @param settings [Object] the Sinatra::Base @settings object.
20
- # @param args [Array<Object>] we rely on the @settings object in
21
- # Sinatra::Base and the env variable passed in as args[0].
22
- def map_route clazz, settings, *args
23
- context = Contrast::Agent::REQUEST_TRACKER.current
24
- return unless context
25
-
26
- env = args[0]
27
- return unless env
28
- return unless settings
29
-
30
- convert_routes(context, clazz, settings, env)
31
- rescue StandardError
32
- # Being careful here since we're directly patching something
33
- end
34
-
35
- def instrument
36
- @_instrument ||= begin
37
- ::Sinatra::Base.class_eval do
38
- alias_method :cs__patched_sinatra_base_call!, :call!
39
- # publicly available method for Sinatra::Base things -- unfortunately,
40
- # getting the routes appear to require a lookup every time
41
- def call! *args
42
- Contrast::Framework::Sinatra::Patch::Base.map_route(cs__class, settings, *args)
43
- cs__patched_sinatra_base_call!(*args)
44
- end
45
- end
46
- true
47
- end
48
- end
49
-
50
- private
51
-
52
- # Find the route that matches this request. Since we're using
53
- # settings, we should resolve in the same precedence as Sinatra
54
- def convert_routes context, clazz, settings, env
55
- # There isn't a Request object in the Sinatra::Base yet - it's made
56
- # during the #call! method, which we're patching - so we need to
57
- # access the env
58
- method = env[::Rack::REQUEST_METHOD]
59
- # get all potential routes for this request type
60
- routes = settings.routes[method]
61
- return unless routes
62
-
63
- route = env[::Rack::PATH_INFO]
64
- route = route.to_s
65
-
66
- # Do some cleanup that matches Sinatra::Base#process_route
67
- route = '/' if route.empty? && !settings.empty_path_info?
68
- route = route[0..-2] if !settings.strict_paths? && route != '/' && route.end_with?('/')
69
-
70
- routes.each do |pattern, _, _| # Mustermann::Sinatra
71
- next unless pattern.params(route)
72
-
73
- dtm = Contrast::Api::Dtm::RouteCoverage.from_sinatra_route(clazz, method, pattern)
74
- context.append_route_coverage(dtm)
75
- break
76
- end
77
- end
78
- end
79
- end
80
- end
81
- end
82
- end
83
- end
@@ -1,27 +0,0 @@
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
- require 'contrast/agent/patching/policy/after_load_patch'
5
-
6
- module Contrast
7
- module Framework
8
- module Sinatra
9
- module Patch
10
- # Extension point allowing for the registration of Patches required to
11
- # support the Sinatra framework.
12
- module Support
13
- # (See BaseSupport#after_load_patches)
14
- def after_load_patches
15
- Set.new([
16
- Contrast::Agent::Patching::Policy::AfterLoadPatch.new(
17
- 'Sinatra::Base',
18
- 'contrast/framework/sinatra/patch/base',
19
- method_to_instrument: nil,
20
- instrumenting_module: 'Contrast::Framework::Sinatra::Patch::Base')
21
- ])
22
- end
23
- end
24
- end
25
- end
26
- end
27
- end
@@ -1,52 +0,0 @@
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 Utils
6
- # DO NOT REMOVE THIS!
7
- #
8
- # Marshal is pretty cool. It does a lot of things well. What it doesn't
9
- # mess around with though is StringIO. And what we don't want to do is
10
- # serialize ourselves out with Marshal.dump.
11
- #
12
- # Unfortunately, we have to mess around w/ that. To isolate our things from
13
- # user dumped Strings (and so that we can marshal findings), we have
14
- # decided to make this class not marshalled.
15
- module PreventMarshalSerialization
16
- def marshal_dump
17
- nil
18
- end
19
-
20
- def marshal_load *_args
21
- nil
22
- end
23
- end
24
-
25
- # DO NOT REMOVE THIS!
26
- #
27
- # Psych/YAML is also pretty cool. But it doesn't mess with anonymous
28
- # classes. In order to make things we extend serializable, we need to make
29
- # sure we play nice.
30
- module PreventPsychSerialization
31
- def encode_with *_args
32
- nil
33
- end
34
-
35
- def init_with *_args
36
- nil
37
- end
38
- end
39
-
40
- # DO NOT REMOVE THIS!
41
- #
42
- # This module is used to prevent deserialization of our classes, not b/c
43
- # we're trying to be sneaky, but b/c there is a high probability that the
44
- # events we're capturing have non-serializable data in them and b/c we
45
- # can't be sure the serialized data will be used in an application running
46
- # Contrast.
47
- module PreventSerialization
48
- include PreventMarshalSerialization
49
- include PreventPsychSerialization
50
- end
51
- end
52
- end