Skip to content
Permalink
Browse files Browse the repository at this point in the history
SECURITY: SSRF protection bypass with IPv4-mapped IPv6 addresses
  • Loading branch information
tgxworld authored and oblakeerickson committed Mar 16, 2023
1 parent 52ef44f commit fd16ead
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 18 deletions.
58 changes: 43 additions & 15 deletions lib/final_destination/ssrf_detector.rb
Expand Up @@ -7,18 +7,47 @@ class DisallowedIpError < SocketError
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
Expand Down Expand Up @@ -54,10 +83,9 @@ def self.host_bypasses_checks?(hostname)

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
Expand Down
16 changes: 13 additions & 3 deletions spec/lib/final_destination/ssrf_detector_spec.rb
Expand Up @@ -43,9 +43,19 @@
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

Expand Down

0 comments on commit fd16ead

Please sign in to comment.