2010-03-03 19:22:30 -05:00
module ActionDispatch
2013-01-02 18:21:13 -05:00
# This middleware calculates the IP address of the remote client that is
# making the request. It does this by checking various headers that could
# contain the address, and then picking the last-set address that is not
2013-02-11 11:32:12 -05:00
# on the list of trusted IPs. This follows the precedent set by e.g.
2013-01-02 18:21:13 -05:00
# {the Tomcat server}[https://issues.apache.org/bugzilla/show_bug.cgi?id=50453],
# with {reasoning explained at length}[http://blog.gingerlime.com/2012/rails-ip-spoofing-vulnerabilities-and-protection]
# by @gingerlime. A more detailed explanation of the algorithm is given
# at GetIp#calculate_ip.
#
# Some Rack servers concatenate repeated headers, like {HTTP RFC 2616}[http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2]
2013-02-21 09:37:45 -05:00
# requires. Some Rack servers simply drop preceding headers, and only report
2013-01-02 18:21:13 -05:00
# the value that was {given in the last header}[http://andre.arko.net/2011/12/26/repeated-headers-and-ruby-web-servers].
# If you are behind multiple proxy servers (like Nginx to HAProxy to Unicorn)
# then you should test your Rack server to make sure your data is good.
#
# IF YOU DON'T USE A PROXY, THIS MAKES YOU VULNERABLE TO IP SPOOFING.
# This middleware assumes that there is at least one proxy sitting around
# and setting headers with the client's remote IP address. If you don't use
2013-01-02 18:34:27 -05:00
# a proxy, because you are hosted on e.g. Heroku without SSL, any client can
# claim to have any IP address by setting the X-Forwarded-For header. If you
# care about that, then you need to explicitly drop or ignore those headers
# sometime before this middleware runs.
2010-03-03 19:22:30 -05:00
class RemoteIp
2013-01-02 18:21:13 -05:00
class IpSpoofAttackError < StandardError ; end
2010-03-03 19:22:30 -05:00
2013-01-02 18:21:13 -05:00
# The default trusted IPs list simply includes IP addresses that are
# guaranteed by the IP specification to be private addresses. Those will
# not be the ultimate client IP in production, and so are discarded. See
# http://en.wikipedia.org/wiki/Private_network for details.
2011-11-12 02:22:49 -05:00
TRUSTED_PROXIES = %r{
2013-01-02 18:21:13 -05:00
^ 127 \ . 0 \ . 0 \ . 1 $ | # localhost IPv4
^ :: 1 $ | # localhost IPv6
2013-10-25 17:16:42 -04:00
^ [ fF ] [ cCdD ] | # private IPv6 range fc00::/7
2013-01-02 18:21:13 -05:00
^ 10 \ . | # private IPv4 range 10.x.x.x
^ 172 \ . ( 1 [ 6 - 9 ] | 2 [ 0 - 9 ] | 3 [ 0 - 1 ] ) \ . | # private IPv4 range 172.16.0.0 .. 172.31.255.255
^ 192 \ . 168 \ . # private IPv4 range 192.168.x.x
2011-11-12 02:22:49 -05:00
} x
2011-11-14 16:20:20 -05:00
attr_reader :check_ip , :proxies
2011-11-12 05:45:31 -05:00
2013-01-02 18:21:13 -05:00
# Create a new +RemoteIp+ middleware instance.
#
# The +check_ip_spoofing+ option is on by default. When on, an exception
# is raised if it looks like the client is trying to lie about its own IP
# address. It makes sense to turn off this check on sites aimed at non-IP
# clients (like WAP devices), or behind proxies that set headers in an
# incorrect or confusing way (like AWS ELB).
#
2014-02-18 09:52:56 -05:00
# The +custom_proxies+ argument can take a regex, which will be used
2013-01-02 18:21:13 -05:00
# instead of +TRUSTED_PROXIES+, or a string, which will be used in addition
# to +TRUSTED_PROXIES+. Any proxy setup will put the value you want in the
# middle (or at the beginning) of the X-Forwarded-For list, with your proxy
# servers after it. If your proxies aren't removed, pass them in via the
2014-02-18 09:52:56 -05:00
# +custom_proxies+ parameter. That way, the middleware will ignore those
2013-01-02 18:21:13 -05:00
# IP addresses, and return the one that you want.
2011-11-12 02:22:49 -05:00
def initialize ( app , check_ip_spoofing = true , custom_proxies = nil )
@app = app
2011-11-14 16:20:20 -05:00
@check_ip = check_ip_spoofing
2011-08-22 10:20:35 -04:00
@proxies = case custom_proxies
2012-04-25 06:31:00 -04:00
when Regexp
custom_proxies
when nil
TRUSTED_PROXIES
else
Regexp . union ( TRUSTED_PROXIES , custom_proxies )
end
2011-11-12 02:22:49 -05:00
end
2010-03-03 19:22:30 -05:00
2013-01-02 18:21:13 -05:00
# Since the IP address may not be needed, we store the object here
# without calculating the IP to keep from slowing down the majority of
# requests. For those requests that do need to know the IP, the
# GetIp#calculate_ip method will calculate the memoized client IP address.
2011-11-12 02:22:49 -05:00
def call ( env )
2011-11-12 05:45:31 -05:00
env [ " action_dispatch.remote_ip " ] = GetIp . new ( env , self )
2011-11-12 02:22:49 -05:00
@app . call ( env )
2010-03-03 19:22:30 -05:00
end
2013-01-02 18:21:13 -05:00
# The GetIp class exists as a way to defer processing of the request data
# into an actual IP address. If the ActionDispatch::Request#remote_ip method
# is called, this class will calculate the value and then memoize it.
2011-11-12 05:45:31 -05:00
class GetIp
2012-04-25 06:31:00 -04:00
2013-01-02 18:21:13 -05:00
# This constant contains a regular expression that validates every known
# form of IP v4 and v6 address, with or without abbreviations, adapted
2013-02-18 09:57:37 -05:00
# from {this gist}[https://gist.github.com/gazay/1289635].
2012-04-25 06:31:00 -04:00
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 - 9 A - Fa - f ] { 1 , 4 } : ) { 7 } [ 0 - 9 A - Fa - f ] { 1 , 4 } ) | # ip v6 not abbreviated
( ( [ 0 - 9 A - Fa - f ] { 1 , 4 } : ) { 6 } : [ 0 - 9 A - Fa - f ] { 1 , 4 } ) | # ip v6 with double colon in the end
( ( [ 0 - 9 A - Fa - f ] { 1 , 4 } : ) { 5 } : ( [ 0 - 9 A - Fa - f ] { 1 , 4 } : ) ? [ 0 - 9 A - Fa - f ] { 1 , 4 } ) | # - ip addresses v6
( ( [ 0 - 9 A - Fa - f ] { 1 , 4 } : ) { 4 } : ( [ 0 - 9 A - Fa - f ] { 1 , 4 } : ) { 0 , 2 } [ 0 - 9 A - Fa - f ] { 1 , 4 } ) | # - with
( ( [ 0 - 9 A - Fa - f ] { 1 , 4 } : ) { 3 } : ( [ 0 - 9 A - Fa - f ] { 1 , 4 } : ) { 0 , 3 } [ 0 - 9 A - Fa - f ] { 1 , 4 } ) | # - double colon
( ( [ 0 - 9 A - Fa - f ] { 1 , 4 } : ) { 2 } : ( [ 0 - 9 A - Fa - f ] { 1 , 4 } : ) { 0 , 4 } [ 0 - 9 A - Fa - f ] { 1 , 4 } ) | # - in the middle
( ( [ 0 - 9 A - 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 - 9 A - 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 - 9 A - Fa - f ] { 1 , 4 } : ) { 1 } : ( [ 0 - 9 A - 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 - 9 A - Fa - f ] { 1 , 4 } : ) { 0 , 2 } : ( [ 0 - 9 A - 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 - 9 A - Fa - f ] { 1 , 4 } : ) { 0 , 3 } : ( [ 0 - 9 A - 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 - 9 A - Fa - f ] { 1 , 4 } : ) { 0 , 4 } : ( [ 0 - 9 A - 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 - 9 A - 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 - 9 A - Fa - f ] { 1 , 4 } :: ( [ 0 - 9 A - Fa - f ] { 1 , 4 } : ) { 0 , 5 } [ 0 - 9 A - Fa - f ] { 1 , 4 } ) | # ip v6 with compatible to v4
2013-03-24 09:35:41 -04:00
( :: ( [ 0 - 9 A - Fa - f ] { 1 , 4 } : ) { 0 , 6 } [ 0 - 9 A - Fa - f ] { 1 , 4 } ) | # ip v6 with double colon at the beginning
2012-04-25 06:31:00 -04:00
( ( [ 0 - 9 A - Fa - f ] { 1 , 4 } : ) { 1 , 7 } : ) # ip v6 without ending
) $ )
} x
2011-11-12 05:45:31 -05:00
def initialize ( env , middleware )
2013-01-02 18:21:13 -05:00
@env = env
@check_ip = middleware . check_ip
@proxies = middleware . proxies
2011-11-12 05:45:31 -05:00
end
2013-01-02 18:21:13 -05:00
# Sort through the various IP address headers, looking for the IP most
# likely to be the address of the actual remote client making this
# request.
#
# REMOTE_ADDR will be correct if the request is made directly against the
# Ruby process, on e.g. Heroku. When the request is proxied by another
# server like HAProxy or Nginx, the IP address that made the original
# request will be put in an X-Forwarded-For header. If there are multiple
# proxies, that header may contain a list of IPs. Other proxy services
# set the Client-Ip header instead, so we check that too.
#
# As discussed in {this post about Rails IP Spoofing}[http://blog.gingerlime.com/2012/rails-ip-spoofing-vulnerabilities-and-protection/],
# while the first IP in the list is likely to be the "originating" IP,
# it could also have been set by the client maliciously.
#
# In order to find the first address that is (probably) accurate, we
# take the list of IPs, remove known and trusted proxies, and then take
# the last address left, which was presumably set by one of those proxies.
2011-11-14 16:20:57 -05:00
def calculate_ip
2013-01-02 18:21:13 -05:00
# Set by the Rack web server, this is a single value.
remote_addr = ips_from ( 'REMOTE_ADDR' ) . last
# Could be a CSV list and/or repeated headers that were concatenated.
client_ips = ips_from ( 'HTTP_CLIENT_IP' ) . reverse
forwarded_ips = ips_from ( 'HTTP_X_FORWARDED_FOR' ) . reverse
# +Client-Ip+ and +X-Forwarded-For+ should not, generally, both be set.
# If they are both set, it means that this request passed through two
# proxies with incompatible IP header conventions, and there is no way
# for us to determine which header is the right one after the fact.
# Since we have no idea, we give up and explode.
2013-06-04 18:01:08 -04:00
should_check_ip = @check_ip && client_ips . last && forwarded_ips . last
2013-01-02 18:21:13 -05:00
if should_check_ip && ! forwarded_ips . include? ( client_ips . last )
2011-11-12 05:45:31 -05:00
# We don't know which came from the proxy, and which from the user
2013-01-02 18:21:13 -05:00
raise IpSpoofAttackError , " IP spoofing attack?! " +
" HTTP_CLIENT_IP= #{ @env [ 'HTTP_CLIENT_IP' ] . inspect } " +
2011-11-13 15:20:29 -05:00
" HTTP_X_FORWARDED_FOR= #{ @env [ 'HTTP_X_FORWARDED_FOR' ] . inspect } "
2011-11-12 05:45:31 -05:00
end
2013-01-02 18:21:13 -05:00
# We assume these things about the IP headers:
#
# - X-Forwarded-For will be a list of IPs, one per proxy, or blank
# - Client-Ip is propagated from the outermost proxy, or is blank
# - REMOTE_ADDR will be the IP that made the request to Rack
ips = [ forwarded_ips , client_ips , remote_addr ] . flatten . compact
# If every single IP option is in the trusted list, just return REMOTE_ADDR
filter_proxies ( ips ) . first || remote_addr
2011-11-12 05:45:31 -05:00
end
2013-01-02 18:21:13 -05:00
# Memoizes the value returned by #calculate_ip and returns it for
# ActionDispatch::Request to use.
2011-11-14 16:20:57 -05:00
def to_s
2012-10-24 07:32:32 -04:00
@ip || = calculate_ip
2011-11-14 16:20:57 -05:00
end
2013-01-02 18:21:13 -05:00
protected
2011-11-12 05:45:31 -05:00
2012-04-25 06:31:00 -04:00
def ips_from ( header )
2013-01-02 18:21:13 -05:00
# Split the comma-separated list into an array of strings
ips = @env [ header ] ? @env [ header ] . strip . split ( / [, \ s]+ / ) : [ ]
# Only return IPs that are valid according to the regex
ips . select { | ip | ip =~ VALID_IP }
2012-04-25 06:31:00 -04:00
end
2013-01-02 18:21:13 -05:00
def filter_proxies ( ips )
ips . reject { | ip | ip =~ @proxies }
2012-04-25 06:31:00 -04:00
end
2010-03-03 19:22:30 -05:00
end
2011-11-12 02:22:49 -05:00
2010-03-03 19:22:30 -05:00
end
2011-11-12 05:45:31 -05:00
end