blob: 79f9ddcd049b6d8cb58d01b62813f9170e622223 (
plain) (
tree)
|
|
module ActionDispatch
class RemoteIp
class IpSpoofAttackError < StandardError ; end
# IP addresses that are "trusted proxies" that can be stripped from
# the comma-delimited list in the X-Forwarded-For header. See also:
# http://en.wikipedia.org/wiki/Private_network#Private_IPv4_address_spaces
TRUSTED_PROXIES = %r{
^127\.0\.0\.1$ | # localhost
^(10 | # private IP 10.x.x.x
172\.(1[6-9]|2[0-9]|3[0-1]) | # private IP in the range 172.16.0.0 .. 172.31.255.255
192\.168 # private IP 192.168.x.x
)\.
}x
def initialize(app, check_ip_spoofing = true, custom_proxies = nil)
@app = app
@check_ip_spoofing = check_ip_spoofing
if custom_proxies
custom_regexp = Regexp.new(custom_proxies, "i")
@trusted_proxies = Regexp.union(TRUSTED_PROXIES, custom_regexp)
else
@trusted_proxies = TRUSTED_PROXIES
end
end
# Determines originating IP address. REMOTE_ADDR is the standard
# but will be wrong if the user is behind a proxy. Proxies will set
# HTTP_CLIENT_IP and/or HTTP_X_FORWARDED_FOR, so we prioritize those.
# HTTP_X_FORWARDED_FOR may be a comma-delimited list in the case of
# multiple chained proxies. The last address which is not a known proxy
# will be the originating IP.
def call(env)
client_ip = env['HTTP_CLIENT_IP']
forwarded_ips = ips_from(env, 'HTTP_X_FORWARDED_FOR')
remote_addrs = ips_from(env, 'REMOTE_ADDR')
if client_ip && @check_ip_spoofing && !forwarded_ips.include?(client_ip)
# We don't know which came from the proxy, and which from the user
raise IpSpoofAttackError, "IP spoofing attack?!" \
"HTTP_CLIENT_IP=#{env['HTTP_CLIENT_IP'].inspect}" \
"HTTP_X_FORWARDED_FOR=#{env['HTTP_X_FORWARDED_FOR'].inspect}"
end
remote_ip = client_ip || forwarded_ips.last || remote_addrs.last
env["action_dispatch.remote_ip"] = remote_ip
@app.call(env)
end
protected
def ips_from(env, header)
ips = env[header] ? env[header].strip.split(/[,\s]+/) : []
ips.reject{|ip| ip =~ @trusted_proxies }
end
end
end
|