diff --git a/lib/final_destination/ssrf_detector.rb b/lib/final_destination/ssrf_detector.rb index d9953044ded..7a3bf1f0369 100644 --- a/lib/final_destination/ssrf_detector.rb +++ b/lib/final_destination/ssrf_detector.rb @@ -7,18 +7,47 @@ class FinalDestination class LookupFailedError < SocketError end - def self.standard_private_ranges - @private_ranges ||= [ - IPAddr.new("0.0.0.0/8"), - IPAddr.new("127.0.0.1"), - IPAddr.new("172.16.0.0/12"), - IPAddr.new("192.168.0.0/16"), - IPAddr.new("10.0.0.0/8"), - IPAddr.new("::1"), - IPAddr.new("fc00::/7"), - IPAddr.new("fe80::/10"), - ] - end + # This is a list of private IPv4 IP ranges that are not allowed to be globally reachable as given by + # https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml. + PRIVATE_IPV4_RANGES = [ + IPAddr.new("0.0.0.0/8"), + IPAddr.new("10.0.0.0/8"), + IPAddr.new("100.64.0.0/10"), + IPAddr.new("127.0.0.0/8"), + IPAddr.new("169.254.0.0/16"), + IPAddr.new("172.16.0.0/12"), + IPAddr.new("192.0.0.0/24"), + IPAddr.new("192.0.0.0/29"), + IPAddr.new("192.0.0.8/32"), + IPAddr.new("192.0.0.170/32"), + IPAddr.new("192.0.0.171/32"), + IPAddr.new("192.0.2.0/24"), + IPAddr.new("192.168.0.0/16"), + IPAddr.new("192.175.48.0/24"), + IPAddr.new("198.18.0.0/15"), + IPAddr.new("198.51.100.0/24"), + IPAddr.new("203.0.113.0/24"), + IPAddr.new("240.0.0.0/4"), + IPAddr.new("255.255.255.255/32"), + ] + + # This is a list of private IPv6 IP ranges that are not allowed to be globally reachable as given by + # https://www.iana.org/assignments/iana-ipv6-special-registry/iana-ipv6-special-registry.xhtml. + # + # ::ffff:0:0/96 is excluded from the list because it is used for IPv4-mapped IPv6 addresses which is something we want to allow. + PRIVATE_IPV6_RANGES = [ + IPAddr.new("::1/128"), + IPAddr.new("::/128"), + IPAddr.new("64:ff9b:1::/48"), + IPAddr.new("100::/64"), + IPAddr.new("2001::/23"), + IPAddr.new("2001:2::/48"), + IPAddr.new("2001:db8::/32"), + IPAddr.new("fc00::/7"), + IPAddr.new("fe80::/10"), + ] + + PRIVATE_IP_RANGES = PRIVATE_IPV4_RANGES + PRIVATE_IPV6_RANGES def self.blocked_ip_blocks SiteSetting @@ -54,10 +83,9 @@ class FinalDestination def self.ip_allowed?(ip) ip = ip.is_a?(IPAddr) ? ip : IPAddr.new(ip) + ip = ip.native - if ip_in_ranges?(ip, blocked_ip_blocks) || ip_in_ranges?(ip, standard_private_ranges) - return false - end + return false if ip_in_ranges?(ip, blocked_ip_blocks) || ip_in_ranges?(ip, PRIVATE_IP_RANGES) true end diff --git a/spec/lib/final_destination/ssrf_detector_spec.rb b/spec/lib/final_destination/ssrf_detector_spec.rb index 6d24a9ca3af..8479f8bf159 100644 --- a/spec/lib/final_destination/ssrf_detector_spec.rb +++ b/spec/lib/final_destination/ssrf_detector_spec.rb @@ -43,9 +43,19 @@ describe FinalDestination::SSRFDetector do expect(subject.ip_allowed?("9001:82f3:8873::3")).to eq(false) end - it "returns false for standard internal IPs" do - expect(subject.ip_allowed?("172.31.100.31")).to eq(false) - expect(subject.ip_allowed?("fd02:77fa:ffea::f")).to eq(false) + %w[0.0.0.0 10.0.0.0 127.0.0.0 172.31.100.31 255.255.255.255 ::1 ::].each do |internal_ip| + it "returns false for '#{internal_ip}'" do + expect(subject.ip_allowed?(internal_ip)).to eq(false) + end + end + + it "returns false for private IPv4-mapped IPv6 addresses" do + expect(subject.ip_allowed?("::ffff:172.31.100.31")).to eq(false) + expect(subject.ip_allowed?("::ffff:0.0.0.0")).to eq(false) + end + + it "returns true for public IPv4-mapped IPv6 addresses" do + expect(subject.ip_allowed?("::ffff:52.52.167.244")).to eq(true) end end