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.
- checksums.yaml +4 -4
- data/lib/contrast/agent.rb +5 -1
- data/lib/contrast/agent/assess.rb +0 -9
- data/lib/contrast/agent/assess/contrast_event.rb +0 -2
- data/lib/contrast/agent/assess/contrast_object.rb +5 -2
- data/lib/contrast/agent/assess/finalizers/hash.rb +7 -0
- data/lib/contrast/agent/assess/policy/dynamic_source_factory.rb +17 -3
- data/lib/contrast/agent/assess/policy/propagation_method.rb +28 -13
- data/lib/contrast/agent/assess/policy/propagator/append.rb +28 -13
- data/lib/contrast/agent/assess/policy/propagator/database_write.rb +21 -16
- data/lib/contrast/agent/assess/policy/propagator/splat.rb +23 -13
- data/lib/contrast/agent/assess/policy/propagator/split.rb +14 -7
- data/lib/contrast/agent/assess/policy/propagator/substitution.rb +30 -14
- data/lib/contrast/agent/assess/policy/trigger_method.rb +13 -8
- data/lib/contrast/agent/assess/policy/trigger_node.rb +28 -7
- data/lib/contrast/agent/assess/policy/trigger_validation/redos_validator.rb +59 -0
- data/lib/contrast/agent/assess/policy/trigger_validation/ssrf_validator.rb +1 -2
- data/lib/contrast/agent/assess/policy/trigger_validation/trigger_validation.rb +6 -4
- data/lib/contrast/agent/assess/policy/trigger_validation/xss_validator.rb +2 -4
- data/lib/contrast/agent/assess/properties.rb +0 -2
- data/lib/contrast/agent/assess/property/tagged.rb +37 -19
- data/lib/contrast/agent/assess/tracker.rb +1 -1
- data/lib/contrast/agent/middleware.rb +85 -55
- data/lib/contrast/agent/patching/policy/patch_status.rb +1 -1
- data/lib/contrast/agent/patching/policy/patcher.rb +51 -44
- data/lib/contrast/agent/patching/policy/trigger_node.rb +5 -2
- data/lib/contrast/agent/protect/rule/sqli.rb +17 -11
- data/lib/contrast/agent/request_context.rb +12 -0
- data/lib/contrast/agent/thread.rb +1 -1
- data/lib/contrast/agent/thread_watcher.rb +20 -5
- data/lib/contrast/agent/version.rb +1 -1
- data/lib/contrast/api/communication/messaging_queue.rb +18 -21
- data/lib/contrast/api/communication/response_processor.rb +8 -1
- data/lib/contrast/api/communication/socket_client.rb +22 -14
- data/lib/contrast/api/decorators.rb +2 -0
- data/lib/contrast/api/decorators/agent_startup.rb +58 -0
- data/lib/contrast/api/decorators/application_startup.rb +51 -0
- data/lib/contrast/api/decorators/route_coverage.rb +15 -5
- data/lib/contrast/api/decorators/trace_event.rb +42 -14
- data/lib/contrast/components/agent.rb +2 -0
- data/lib/contrast/components/app_context.rb +4 -22
- data/lib/contrast/components/sampling.rb +48 -6
- data/lib/contrast/components/settings.rb +5 -4
- data/lib/contrast/framework/manager.rb +13 -12
- data/lib/contrast/framework/rails/support.rb +42 -43
- data/lib/contrast/framework/sinatra/support.rb +100 -41
- data/lib/contrast/logger/log.rb +31 -15
- data/lib/contrast/utils/class_util.rb +3 -1
- data/lib/contrast/utils/heap_dump_util.rb +103 -87
- data/lib/contrast/utils/invalid_configuration_util.rb +21 -12
- data/resources/assess/policy.json +3 -9
- data/resources/deadzone/policy.json +6 -0
- data/ruby-agent.gemspec +54 -16
- metadata +105 -136
- data/lib/contrast/agent/assess/rule.rb +0 -18
- data/lib/contrast/agent/assess/rule/base.rb +0 -52
- data/lib/contrast/agent/assess/rule/redos.rb +0 -67
- data/lib/contrast/framework/sinatra/patch/base.rb +0 -83
- data/lib/contrast/framework/sinatra/patch/support.rb +0 -27
- 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
|