diff --git a/lib/aikido/zen/sinks/curb.rb b/lib/aikido/zen/sinks/curb.rb index e6f85c7e..9d839205 100644 --- a/lib/aikido/zen/sinks/curb.rb +++ b/lib/aikido/zen/sinks/curb.rb @@ -1,11 +1,13 @@ # frozen_string_literal: true +require_relative "../scanners/stored_ssrf_scanner" require_relative "../scanners/ssrf_scanner" module Aikido::Zen module Sinks module Curl SINK = Sinks.add("curb", "outgoing_http_op", scanners: [ + Scanners::StoredSSRFScanner, Scanners::SSRFScanner ]) @@ -34,11 +36,20 @@ def self.wrap_response(curl) Scanners::SSRFScanner::Response.new(status: status, headers: headers) end - def self.scan(request, connection, operation) + def self.resolve_hostname(hostname) + require "resolv" + Resolv.getaddresses(hostname) + rescue => _error + [] + end + + def self.scan(request, connection, operation, hostname: nil, addresses: nil) SINK.scan( request: request, connection: connection, - operation: operation + operation: operation, + hostname: hostname, + addresses: addresses ) end end @@ -72,7 +83,12 @@ def self.load_sinks! end end - Helpers.scan(wrapped_request, connection, "request") + # Resolve the hostname to get IP addresses for StoredSSRFScanner + uri = URI(url) + hostname = uri.hostname + addresses = hostname ? Helpers.resolve_hostname(hostname) : [] + + Helpers.scan(wrapped_request, connection, "request", hostname: hostname, addresses: addresses) response = original_call.call @@ -104,7 +120,12 @@ def self.load_sinks! connection = OutboundConnection.from_uri(URI(last_effective_url)) - Helpers.scan(last_effective_request, connection, "request") + # Resolve the redirect target hostname for StoredSSRFScanner + redirect_uri = URI(last_effective_url) + redirect_hostname = redirect_uri.hostname + redirect_addresses = redirect_hostname ? Helpers.resolve_hostname(redirect_hostname) : [] + + Helpers.scan(last_effective_request, connection, "request", hostname: redirect_hostname, addresses: redirect_addresses) end response diff --git a/lib/aikido/zen/sinks/patron.rb b/lib/aikido/zen/sinks/patron.rb index df6e0bbb..4d5d9bde 100644 --- a/lib/aikido/zen/sinks/patron.rb +++ b/lib/aikido/zen/sinks/patron.rb @@ -1,11 +1,13 @@ # frozen_string_literal: true +require_relative "../scanners/stored_ssrf_scanner" require_relative "../scanners/ssrf_scanner" module Aikido::Zen module Sinks module Patron SINK = Sinks.add("patron", "outgoing_http_op", scanners: [ + Scanners::StoredSSRFScanner, Scanners::SSRFScanner ]) @@ -25,11 +27,20 @@ def self.wrap_response(request, response) end end - def self.scan(request, connection, operation) + def self.resolve_hostname(hostname) + require "resolv" + Resolv.getaddresses(hostname) + rescue => _error + [] + end + + def self.scan(request, connection, operation, hostname: nil, addresses: nil) SINK.scan( request: request, connection: connection, - operation: operation + operation: operation, + hostname: hostname, + addresses: addresses ) end end @@ -67,7 +78,12 @@ def self.load_sinks! end end - Helpers.scan(wrapped_request, connection, "request") + # Resolve the hostname to get IP addresses for StoredSSRFScanner + uri = URI(request.url) + hostname = uri.hostname + addresses = hostname ? Helpers.resolve_hostname(hostname) : [] + + Helpers.scan(wrapped_request, connection, "request", hostname: hostname, addresses: addresses) response = original_call.call @@ -94,7 +110,12 @@ def self.load_sinks! connection = OutboundConnection.from_uri(URI(response.url)) - Helpers.scan(last_effective_request, connection, "request") + # Resolve the redirect target hostname for StoredSSRFScanner + redirect_uri = URI(response.url) + redirect_hostname = redirect_uri.hostname + redirect_addresses = redirect_hostname ? Helpers.resolve_hostname(redirect_hostname) : [] + + Helpers.scan(last_effective_request, connection, "request", hostname: redirect_hostname, addresses: redirect_addresses) end response diff --git a/lib/aikido/zen/sinks/typhoeus.rb b/lib/aikido/zen/sinks/typhoeus.rb index 205d3467..9f0f4c1a 100644 --- a/lib/aikido/zen/sinks/typhoeus.rb +++ b/lib/aikido/zen/sinks/typhoeus.rb @@ -1,14 +1,25 @@ # frozen_string_literal: true require_relative "../sink" +require_relative "../scanners/stored_ssrf_scanner" module Aikido::Zen module Sinks module Typhoeus SINK = Sinks.add("typhoeus", "outgoing_http_op", scanners: [ + Aikido::Zen::Scanners::StoredSSRFScanner, Aikido::Zen::Scanners::SSRFScanner ]) + module Helpers + def self.resolve_hostname(hostname) + require "resolv" + Resolv.getaddresses(hostname) + rescue => _error + [] + end + end + before_callback = ->(request) { wrapped_request = Aikido::Zen::Scanners::SSRFScanner::Request.new( verb: request.options[:method], @@ -32,10 +43,17 @@ module Typhoeus end end + # Resolve the hostname to get IP addresses for StoredSSRFScanner + uri = URI(request.url) + hostname = uri.hostname + addresses = hostname ? Helpers.resolve_hostname(hostname) : [] + SINK.scan( connection: connection, request: wrapped_request, - operation: "request" + operation: "request", + hostname: hostname, + addresses: addresses ) request.on_headers do |response| @@ -66,6 +84,11 @@ module Typhoeus connection = Aikido::Zen::OutboundConnection.from_uri(URI(response.effective_url)) + # Resolve the redirect target hostname for StoredSSRFScanner + redirect_uri = URI(response.effective_url) + redirect_hostname = redirect_uri.hostname + redirect_addresses = redirect_hostname ? Helpers.resolve_hostname(redirect_hostname) : [] + # In this case, we can't actually stop the request from happening, but # we can scan again (now that we know another request happened), to # stop the response from being exposed to the user. This downgrades @@ -73,7 +96,9 @@ module Typhoeus SINK.scan( connection: connection, request: last_effective_request, - operation: "request" + operation: "request", + hostname: redirect_hostname, + addresses: redirect_addresses ) ensure context["ssrf.request"] = nil if context