mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
275c3a1cb3
The first IP address in the X-Forwarded-For header is the originating IP
83 lines
2.8 KiB
Ruby
83 lines
2.8 KiB
Ruby
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
|
|
|
|
attr_reader :check_ip, :proxies
|
|
|
|
def initialize(app, check_ip_spoofing = true, custom_proxies = nil)
|
|
@app = app
|
|
@check_ip = check_ip_spoofing
|
|
@proxies = case custom_proxies
|
|
when Regexp
|
|
custom_proxies
|
|
when nil
|
|
TRUSTED_PROXIES
|
|
else
|
|
Regexp.union(TRUSTED_PROXIES, custom_proxies)
|
|
end
|
|
end
|
|
|
|
def call(env)
|
|
env["action_dispatch.remote_ip"] = GetIp.new(env, self)
|
|
@app.call(env)
|
|
end
|
|
|
|
class GetIp
|
|
def initialize(env, middleware)
|
|
@env = env
|
|
@middleware = middleware
|
|
@calculated_ip = false
|
|
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 calculate_ip
|
|
client_ip = @env['HTTP_CLIENT_IP']
|
|
forwarded_ips = ips_from('HTTP_X_FORWARDED_FOR')
|
|
remote_addrs = ips_from('REMOTE_ADDR')
|
|
|
|
check_ip = client_ip && @middleware.check_ip
|
|
if check_ip && !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
|
|
|
|
not_proxy = client_ip || forwarded_ips.first || remote_addrs.first
|
|
|
|
# Return first REMOTE_ADDR if there are no other options
|
|
not_proxy || ips_from('REMOTE_ADDR', :allow_proxies).first
|
|
end
|
|
|
|
def to_s
|
|
return @ip if @calculated_ip
|
|
@calculated_ip = true
|
|
@ip = calculate_ip
|
|
end
|
|
|
|
protected
|
|
|
|
def ips_from(header, allow_proxies = false)
|
|
ips = @env[header] ? @env[header].strip.split(/[,\s]+/) : []
|
|
allow_proxies ? ips : ips.reject{|ip| ip =~ @middleware.proxies }
|
|
end
|
|
end
|
|
|
|
end
|
|
end
|