Class: Contrast::Agent::RequestContext

Inherits:
Object
  • Object
show all
Includes:
Components::Interface
Defined in:
lib/contrast/agent/request_context.rb

Overview

This class acts to encapsulate information about the currently executed request, making it available to the Agent for the duration of the request in a standardized and normalized format which the Agent understands.

Constant Summary collapse

EMPTY_INPUT_ANALYSIS_PB =
Contrast::Api::Settings::InputAnalysis.new

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Components::Interface

included

Constructor Details

#initialize(rack_request, app_loaded = true) ⇒ RequestContext

Returns a new instance of RequestContext.



31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/contrast/agent/request_context.rb', line 31

def initialize rack_request, app_loaded = true
  with_contrast_scope do
    # all requests get a timer and hash
    @timer = Contrast::Utils::Timer.new
    @logging_hash = {
        request_id: __id__
    }

    # instantiate helper for request and response
    @request = Contrast::Agent::Request.new(rack_request)

    @activity = Contrast::Api::Dtm::Activity.new
    @activity.http_request = request.dtm

    @server_activity = Contrast::Api::Dtm::ServerActivity.new

    @observed_route = Contrast::Api::Dtm::ObservedRoute.new

    # build analyzer
    @do_not_track = false
    @speedracer_input_analysis = EMPTY_INPUT_ANALYSIS_PB
    speedracer_input_analysis.request = request

    # flag to indicate whether the app is fully loaded
    @app_loaded = !!app_loaded

    # generic holder for properties that can be set throughout this request
    @_properties = {}

    @sample = true

    @sample_request, @sample_response = Contrast::Utils::Assess::SamplingUtil.instance.sample?(@request) if ASSESS.enabled?

    @sample_response &&= ASSESS.scan_response?

    append_route_coverage(Contrast::Agent.framework_manager.get_route_dtm(@request))
  end
end

Instance Attribute Details

#activityObject (readonly)

Returns the value of attribute activity.



21
22
23
# File 'lib/contrast/agent/request_context.rb', line 21

def activity
  @activity
end

#logging_hashObject (readonly)

Returns the value of attribute logging_hash.



21
22
23
# File 'lib/contrast/agent/request_context.rb', line 21

def logging_hash
  @logging_hash
end

#observed_routeObject (readonly)

Returns the value of attribute observed_route.



21
22
23
# File 'lib/contrast/agent/request_context.rb', line 21

def observed_route
  @observed_route
end

#requestObject (readonly)

Returns the value of attribute request.



21
22
23
# File 'lib/contrast/agent/request_context.rb', line 21

def request
  @request
end

#responseObject (readonly)

Returns the value of attribute response.



21
22
23
# File 'lib/contrast/agent/request_context.rb', line 21

def response
  @response
end

#routeObject (readonly)

Returns the value of attribute route.



21
22
23
# File 'lib/contrast/agent/request_context.rb', line 21

def route
  @route
end

#server_activityObject (readonly)

Returns the value of attribute server_activity.



21
22
23
# File 'lib/contrast/agent/request_context.rb', line 21

def server_activity
  @server_activity
end

#speedracer_input_analysisObject (readonly)

Returns the value of attribute speedracer_input_analysis.



21
22
23
# File 'lib/contrast/agent/request_context.rb', line 21

def speedracer_input_analysis
  @speedracer_input_analysis
end

#timerObject (readonly)

Returns the value of attribute timer.



21
22
23
# File 'lib/contrast/agent/request_context.rb', line 21

def timer
  @timer
end

Instance Method Details

#add_property(key, value) ⇒ Object



170
171
172
# File 'lib/contrast/agent/request_context.rb', line 170

def add_property key, value
  @_properties[key] = value
end

#analyze_request?Boolean

Returns:

  • (Boolean)


74
75
76
# File 'lib/contrast/agent/request_context.rb', line 74

def analyze_request?
  @sample_request
end

#analyze_response?Boolean

Returns:

  • (Boolean)


78
79
80
# File 'lib/contrast/agent/request_context.rb', line 78

def analyze_response?
  @sample_response
end

#app_loaded?Boolean

Returns:

  • (Boolean)


70
71
72
# File 'lib/contrast/agent/request_context.rb', line 70

def app_loaded?
  @app_loaded
end

#append_route_coverage(route) ⇒ Object

Convert the discovered route for this request to appropriate forms and disseminate it to those locations where it is necessary for our route coverage and finding vulnerability discovery features to function.



85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/contrast/agent/request_context.rb', line 85

def append_route_coverage route
  return unless route

  # For our findings
  @route = route

  # For SR findings
  @activity.routes << route

  # For TS routes
  @observed_route.signature  = route.route
  @observed_route.verb       = route.verb
  @observed_route.url        = route.url if route.url
end

#extract_after(rack_response) ⇒ Object

append anything we’ve learned to the request seen message this is the sum-total of all inventory information that has been accumulated since the last request



163
164
165
166
167
168
# File 'lib/contrast/agent/request_context.rb', line 163

def extract_after rack_response
  @response = Contrast::Agent::Response.new(rack_response)
  activity.http_response = @response.dtm if @sample_response
rescue StandardError => e
  logger.error('Unable to extract information after request', e)
end

#get_property(key) ⇒ Object



174
175
176
# File 'lib/contrast/agent/request_context.rb', line 174

def get_property key
  @_properties[key]
end

#handle_protect_state(agent_settings) ⇒ Object

NOTE: this method is only used as a backstop if Speedracer sends Input Evaluations when the protect state indicates a security exception should be thrown. This method ensures that the attack reports are generated. Normally these should be generated on Speedracer for any attacks detected during prefilter.



146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/contrast/agent/request_context.rb', line 146

def handle_protect_state agent_settings
  return unless agent_settings&.protect_state

  state = agent_settings.protect_state
  @uuid = state.uuid
  @do_not_track = true unless state.track_request
  return unless state.security_exception

  # If Contrast Service has NOT handled the input analysis, handle them here
  build_attack_results(agent_settings)
  logger.debug('Contrast Service said to block this request')
  raise Contrast::SecurityException.new(nil, (state.security_message || 'Blocking suspicious behavior'))
end

#reset_activityObject



178
179
180
181
182
# File 'lib/contrast/agent/request_context.rb', line 178

def reset_activity
  @activity = Contrast::Api::Dtm::Activity.new(http_request: request.dtm)
  @server_activity = Contrast::Api::Dtm::ServerActivity.new # it doesn't look like this is ever actually used?
  @observed_route = Contrast::Api::Dtm::ObservedRoute.new
end

#results_for(rule, response_type = nil) ⇒ Array<Contrast::Api::Dtm::AttackResult>

Collect the results for the given rule with the given action

Parameters:

  • rule (String)

    the id of the rule to which the results apply

  • response_type (Symbol) (defaults to: nil)

    the result of the response, matching a value of Contrast::Api::Dtm::AttackResult::ResponseType

Returns:



106
107
108
109
110
111
112
# File 'lib/contrast/agent/request_context.rb', line 106

def results_for rule, response_type = nil
  if response_type.nil?
    activity.results.select { |r| r.rule_id == rule }
  else
    activity.results.select { |r| r.rule_id == rule && r.response == response_type }
  end
end

#service_extract_requestObject



114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# File 'lib/contrast/agent/request_context.rb', line 114

def service_extract_request
  return false unless AGENT.enabled?
  return false unless PROTECT.enabled?
  return false if @do_not_track

  service_response = Contrast::Agent.messaging_queue.send_event_immediately(@activity.http_request)
  return false unless service_response

  handle_protect_state(service_response)
  ia = service_response.input_analysis
  if ia
    logger.trace("Analysis from Contrast Service: evaluations=#{ ia.results.length }")
    logger.trace('Results', input_analysis: ia.inspect)
    @speedracer_input_analysis = ia
    speedracer_input_analysis.request = request
  else
    logger.trace('Analysis from Contrast Service was empty.')
    false
  end
rescue Contrast::SecurityException => e
  raise e
rescue StandardError => e
  logger.warn('Unable to extract Contrast Service information from request', e)
  false
end