mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Merge pull request #3617 from indirect/remote_ip
refactor RemoteIp middleware
This commit is contained in:
commit
2591cc6692
2 changed files with 57 additions and 55 deletions
|
@ -155,24 +155,7 @@ module ActionDispatch
|
|||
@ip ||= super
|
||||
end
|
||||
|
||||
# Which IP addresses are "trusted proxies" that can be stripped from
|
||||
# the right-hand-side of X-Forwarded-For.
|
||||
#
|
||||
# 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
|
||||
|
||||
# Determines originating IP address. REMOTE_ADDR is the standard
|
||||
# but will fail if the user is behind a proxy. HTTP_CLIENT_IP and/or
|
||||
# HTTP_X_FORWARDED_FOR are set by proxies so check for these if
|
||||
# REMOTE_ADDR is a proxy. HTTP_X_FORWARDED_FOR may be a comma-
|
||||
# delimited list in the case of multiple chained proxies; the last
|
||||
# address which is not trusted is the originating IP.
|
||||
# Originating IP address, usually set by the RemoteIp middleware.
|
||||
def remote_ip
|
||||
@remote_ip ||= (@env["action_dispatch.remote_ip"] || ip).to_s
|
||||
end
|
||||
|
|
|
@ -2,50 +2,69 @@ module ActionDispatch
|
|||
class RemoteIp
|
||||
class IpSpoofAttackError < StandardError ; end
|
||||
|
||||
class RemoteIpGetter
|
||||
def initialize(env, check_ip_spoofing, trusted_proxies)
|
||||
@env = env
|
||||
@check_ip_spoofing = check_ip_spoofing
|
||||
@trusted_proxies = trusted_proxies
|
||||
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 remote_addrs
|
||||
@remote_addrs ||= begin
|
||||
list = @env['REMOTE_ADDR'] ? @env['REMOTE_ADDR'].split(/[,\s]+/) : []
|
||||
list.reject { |addr| addr =~ @trusted_proxies }
|
||||
end
|
||||
end
|
||||
attr_reader :check_ip_spoofing, :trusted_proxies
|
||||
|
||||
def to_s
|
||||
return remote_addrs.first if remote_addrs.any?
|
||||
|
||||
forwarded_ips = @env['HTTP_X_FORWARDED_FOR'] ? @env['HTTP_X_FORWARDED_FOR'].strip.split(/[,\s]+/) : []
|
||||
|
||||
if client_ip = @env['HTTP_CLIENT_IP']
|
||||
if @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
|
||||
return client_ip
|
||||
end
|
||||
|
||||
return forwarded_ips.reject { |ip| ip =~ @trusted_proxies }.last || @env["REMOTE_ADDR"]
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(app, check_ip_spoofing = true, trusted_proxies = nil)
|
||||
def initialize(app, check_ip_spoofing = true, custom_proxies = nil)
|
||||
@app = app
|
||||
@check_ip_spoofing = check_ip_spoofing
|
||||
regex = '(^127\.0\.0\.1$|^(10|172\.(1[6-9]|2[0-9]|30|31)|192\.168)\.)'
|
||||
regex << "|(#{trusted_proxies})" if trusted_proxies
|
||||
@trusted_proxies = Regexp.new(regex, "i")
|
||||
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
|
||||
|
||||
def call(env)
|
||||
env["action_dispatch.remote_ip"] = RemoteIpGetter.new(env, @check_ip_spoofing, @trusted_proxies)
|
||||
env["action_dispatch.remote_ip"] = GetIp.new(env, self)
|
||||
@app.call(env)
|
||||
end
|
||||
|
||||
class GetIp
|
||||
def initialize(env, middleware)
|
||||
@env, @middleware = env, middleware
|
||||
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 to_s
|
||||
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_spoofing
|
||||
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
|
||||
|
||||
client_ip || forwarded_ips.last || remote_addrs.last
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def ips_from(header)
|
||||
ips = @env[header] ? @env[header].strip.split(/[,\s]+/) : []
|
||||
ips.reject{|ip| ip =~ @middleware.trusted_proxies }
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue