diff --git a/app/models/ssrf_protection.rb b/app/models/ssrf_protection.rb index a73b6b3be..40be7d157 100644 --- a/app/models/ssrf_protection.rb +++ b/app/models/ssrf_protection.rb @@ -16,13 +16,19 @@ module SsrfProtection def resolve_public_ip(hostname) ip_addresses = resolve_dns(hostname) - public_ips = ip_addresses.reject { |ip| private_address?(ip) } + public_ips = ip_addresses.reject { |ip| blocked_address?(ip) } public_ips.sort_by { |ipaddr| ipaddr.ipv4? ? 0 : 1 }.first&.to_s end - def private_address?(ip) + def blocked_address?(ip) ip = IPAddr.new(ip.to_s) unless ip.is_a?(IPAddr) - ip.private? || ip.loopback? || ip.link_local? || ip.ipv4_mapped? || in_disallowed_range?(ip) + + ip.private? || + ip.loopback? || + ip.link_local? || + ip.ipv4_mapped? || + ip.ipv4_compat? || + in_disallowed_range?(ip) end private diff --git a/test/models/ssrf_protection_test.rb b/test/models/ssrf_protection_test.rb index d56f7390a..550909f89 100644 --- a/test/models/ssrf_protection_test.rb +++ b/test/models/ssrf_protection_test.rb @@ -51,6 +51,38 @@ class SsrfProtectionTest < ActiveSupport::TestCase assert_equal "93.184.216.34", SsrfProtection.resolve_public_ip("multi.example.com") end + # IPv6 address format tests (SSRF bypass prevention) + + test "blocks IPv4-mapped IPv6 addresses with private IPs" do + stub_dns_resolution("::ffff:192.168.1.1") + assert_nil SsrfProtection.resolve_public_ip("mapped-private.example.com") + end + + test "blocks IPv4-mapped IPv6 addresses with link-local IPs" do + stub_dns_resolution("::ffff:169.254.169.254") + assert_nil SsrfProtection.resolve_public_ip("mapped-metadata.example.com") + end + + test "blocks IPv4-mapped IPv6 addresses even with public IPs" do + stub_dns_resolution("::ffff:93.184.216.34") + assert_nil SsrfProtection.resolve_public_ip("mapped-public.example.com") + end + + test "blocks IPv4-compatible IPv6 addresses with private IPs" do + stub_dns_resolution("::192.168.1.1") + assert_nil SsrfProtection.resolve_public_ip("compat-private.example.com") + end + + test "blocks IPv4-compatible IPv6 addresses with link-local IPs" do + stub_dns_resolution("::169.254.169.254") + assert_nil SsrfProtection.resolve_public_ip("compat-metadata.example.com") + end + + test "blocks IPv4-compatible IPv6 addresses even with public IPs" do + stub_dns_resolution("::93.184.216.34") + assert_nil SsrfProtection.resolve_public_ip("compat-public.example.com") + end + private def stub_dns_resolution(*ips) dns_mock = mock("dns")