aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/action_dispatch/middleware/remote_ip.rb
diff options
context:
space:
mode:
authorAlexey Gaziev <alex.gaziev@gmail.com>2012-04-25 14:31:00 +0400
committerAlexey Gaziev <alex.gaziev@gmail.com>2012-04-25 14:50:59 +0400
commit6da2bc5fcc9aa35707250c8b348a045685ef62c7 (patch)
treef36050d7eac5706fb6e5c0cf890343cfbe80e3ad /actionpack/lib/action_dispatch/middleware/remote_ip.rb
parent82c3aca17e78d25f217702e530586673f2a219d7 (diff)
downloadrails-6da2bc5fcc9aa35707250c8b348a045685ef62c7.tar.gz
rails-6da2bc5fcc9aa35707250c8b348a045685ef62c7.tar.bz2
rails-6da2bc5fcc9aa35707250c8b348a045685ef62c7.zip
Valid ips v4 and v6. Right logic for working with X-FORWARDED-FOR header and tests.
Diffstat (limited to 'actionpack/lib/action_dispatch/middleware/remote_ip.rb')
-rw-r--r--actionpack/lib/action_dispatch/middleware/remote_ip.rb90
1 files changed, 68 insertions, 22 deletions
diff --git a/actionpack/lib/action_dispatch/middleware/remote_ip.rb b/actionpack/lib/action_dispatch/middleware/remote_ip.rb
index d924f21fad..ec15a2a715 100644
--- a/actionpack/lib/action_dispatch/middleware/remote_ip.rb
+++ b/actionpack/lib/action_dispatch/middleware/remote_ip.rb
@@ -5,11 +5,14 @@ module ActionDispatch
# 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
+ # http://en.wikipedia.org/wiki/Private_network#Private_IPv6_addresses.
TRUSTED_PROXIES = %r{
^127\.0\.0\.1$ | # localhost
+ ^::1$ |
^(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
+ 192\.168 | # private IP 192.168.x.x
+ fc00:: # private IP fc00
)\.
}x
@@ -19,13 +22,13 @@ module ActionDispatch
@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
+ when Regexp
+ custom_proxies
+ when nil
+ TRUSTED_PROXIES
+ else
+ Regexp.union(TRUSTED_PROXIES, custom_proxies)
+ end
end
def call(env)
@@ -34,6 +37,31 @@ module ActionDispatch
end
class GetIp
+
+ # IP v4 and v6 (with compression) validation regexp
+ # https://gist.github.com/1289635
+ VALID_IP = %r{
+ (^(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[0-9]{1,2})(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[0-9]{1,2})){3}$) | # ip v4
+ (^(
+ (([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4}) | # ip v6 not abbreviated
+ (([0-9A-Fa-f]{1,4}:){6}:[0-9A-Fa-f]{1,4}) | # ip v6 with double colon in the end
+ (([0-9A-Fa-f]{1,4}:){5}:([0-9A-Fa-f]{1,4}:)?[0-9A-Fa-f]{1,4}) | # - ip addresses v6
+ (([0-9A-Fa-f]{1,4}:){4}:([0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4}) | # - with
+ (([0-9A-Fa-f]{1,4}:){3}:([0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4}) | # - double colon
+ (([0-9A-Fa-f]{1,4}:){2}:([0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4}) | # - in the middle
+ (([0-9A-Fa-f]{1,4}:){6} ((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3} (\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
+ (([0-9A-Fa-f]{1,4}:){1,5}:((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
+ (([0-9A-Fa-f]{1,4}:){1}:([0-9A-Fa-f]{1,4}:){0,4}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
+ (([0-9A-Fa-f]{1,4}:){0,2}:([0-9A-Fa-f]{1,4}:){0,3}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
+ (([0-9A-Fa-f]{1,4}:){0,3}:([0-9A-Fa-f]{1,4}:){0,2}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
+ (([0-9A-Fa-f]{1,4}:){0,4}:([0-9A-Fa-f]{1,4}:){1}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
+ (::([0-9A-Fa-f]{1,4}:){0,5}((\b((25[0-5])|(1\d{2})|(2[0-4]\d) |(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
+ ([0-9A-Fa-f]{1,4}::([0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4}) | # ip v6 with compatible to v4
+ (::([0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4}) | # ip v6 with double colon at the begining
+ (([0-9A-Fa-f]{1,4}:){1,7}:) # ip v6 without ending
+ )$)
+ }x
+
def initialize(env, middleware)
@env = env
@middleware = middleware
@@ -44,25 +72,31 @@ module ActionDispatch
# 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.
+ # multiple chained proxies. The first address which is in this list
+ # if it's not a known proxy will be the originating IP.
+ # Format of HTTP_X_FORWARDED_FOR:
+ # client_ip, proxy_ip1, proxy_ip2...
+ # http://en.wikipedia.org/wiki/X-Forwarded-For
def calculate_ip
- client_ip = @env['HTTP_CLIENT_IP']
- forwarded_ips = ips_from('HTTP_X_FORWARDED_FOR')
- remote_addrs = ips_from('REMOTE_ADDR')
+ client_ip = @env['HTTP_CLIENT_IP']
+ forwarded_ip = ips_from('HTTP_X_FORWARDED_FOR').first
+ remote_addrs = ips_from('REMOTE_ADDR')
check_ip = client_ip && @middleware.check_ip
- if check_ip && !forwarded_ips.include?(client_ip)
+ if check_ip && forwarded_ip != 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
+ client_ips = remove_proxies [client_ip, forwarded_ip, remote_addrs].flatten
+ if client_ips.present?
+ client_ips.first
+ else
+ # If there is no client ip we can return first valid proxy ip from REMOTE_ADDR
+ remote_addrs.find { |ip| valid_ip? ip }
+ end
end
def to_s
@@ -71,12 +105,24 @@ module ActionDispatch
@ip = calculate_ip
end
- protected
+ private
- def ips_from(header, allow_proxies = false)
- ips = @env[header] ? @env[header].strip.split(/[,\s]+/) : []
- allow_proxies ? ips : ips.reject{|ip| ip =~ @middleware.proxies }
+ def ips_from(header)
+ @env[header] ? @env[header].strip.split(/[,\s]+/) : []
end
+
+ def valid_ip?(ip)
+ ip =~ VALID_IP
+ end
+
+ def not_a_proxy?(ip)
+ ip !~ @middleware.proxies
+ end
+
+ def remove_proxies(ips)
+ ips.select { |ip| valid_ip?(ip) && not_a_proxy?(ip) }
+ end
+
end
end