Class: Aikido::Zen::Scanners::SSRFScanner

Inherits:
Object
  • Object
show all
Defined in:
lib/aikido/zen/scanners/ssrf_scanner.rb

Defined Under Namespace

Modules: Headers Classes: RedirectChains, Request, Response

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(request_uri, input, redirects) ⇒ SSRFScanner

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns a new instance of SSRFScanner.



77
78
79
80
81
# File 'lib/aikido/zen/scanners/ssrf_scanner.rb', line 77

def initialize(request_uri, input, redirects)
  @request_uri = request_uri
  @input = input
  @redirects = redirects
end

Class Method Details

.call(request:, sink:, context:, operation:) ⇒ Aikido::Zen::Attacks::SSRFAttack?

Checks if an outbound HTTP request is to a hostname supplied from user input that resolves to a “dangerous” address. This is called from two different places:

  • HTTP library sinks, before we make a request. In these cases we can detect very obvious attempts such as a request that attempts to access localhost or an internal IP.

  • DNS lookup sinks, after we resolve a hostname. For HTTP requests that are not obviously an attack, we let the DNS resolution happen, and then check again, now knowing if the domain name provided actually resolves to an internal IP or not.

NOTE: Because not all DNS resolutions might be happening in the context of a protected HTTP request, the request argument below might be nil and we can then skip this scan.

Parameters:

Returns:



34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/aikido/zen/scanners/ssrf_scanner.rb', line 34

def self.call(request:, sink:, context:, operation:, **)
  return if context.nil?
  return if request.nil? # See NOTE above.

  context["ssrf.redirects"] ||= RedirectChains.new

  context.payloads.each do |payload|
    scanner = new(request.uri, payload.value, context["ssrf.redirects"])
    next unless scanner.attack?

    attack = Attacks::SSRFAttack.new(
      sink: sink,
      request: request,
      input: payload,
      context: context,
      operation: "#{sink.operation}.#{operation}"
    )

    return attack
  end

  nil
end

.track_redirects(request:, response:, context: Aikido::Zen.current_context) ⇒ void

This method returns an undefined value.

Track the origin of a redirection so we know if an attacker is using redirect chains to mask their use of a (seemingly) safe domain.

Parameters:



66
67
68
69
70
71
72
73
74
# File 'lib/aikido/zen/scanners/ssrf_scanner.rb', line 66

def self.track_redirects(request:, response:, context: Aikido::Zen.current_context)
  return unless response.redirect?

  context["ssrf.redirects"] ||= RedirectChains.new
  context["ssrf.redirects"].add(
    source: request.uri,
    destination: response.redirect_to
  )
end

Instance Method Details

#attack?Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns:

  • (Boolean)


84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/aikido/zen/scanners/ssrf_scanner.rb', line 84

def attack?
  return false if @input.nil? || @input.to_s.empty?

  # If the request is not aimed at an internal IP, we can ignore it. (It
  # might still be an SSRF if defined strictly, but it's unlikely to be
  # exfiltrating data from the app's servers, and the risk for false
  # positives is too high.)
  return false unless private_ip?(@request_uri.hostname)

  origins_for_request
    .product(uris_from_input)
    .any? { |(conn_uri, candidate)| match?(conn_uri, candidate) }
end