Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 130 lines (111 sloc) 6.783 kb
93422af Move remote_ip to a middleware:
Carlhuda authored
1 module ActionDispatch
2 class RemoteIp
3 class IpSpoofAttackError < StandardError ; end
4
9432163 André Arko refactor RemoteIp middleware
indirect authored
5 # IP addresses that are "trusted proxies" that can be stripped from
6 # the comma-delimited list in the X-Forwarded-For header. See also:
7 # http://en.wikipedia.org/wiki/Private_network#Private_IPv4_address_spaces
6da2bc5 Alexey Gaziev Valid ips v4 and v6. Right logic for working with X-FORWARDED-FOR header...
gazay authored
8 # http://en.wikipedia.org/wiki/Private_network#Private_IPv6_addresses.
9432163 André Arko refactor RemoteIp middleware
indirect authored
9 TRUSTED_PROXIES = %r{
10 ^127\.0\.0\.1$ | # localhost
6da2bc5 Alexey Gaziev Valid ips v4 and v6. Right logic for working with X-FORWARDED-FOR header...
gazay authored
11 ^::1$ |
9432163 André Arko refactor RemoteIp middleware
indirect authored
12 ^(10 | # private IP 10.x.x.x
13 172\.(1[6-9]|2[0-9]|3[0-1]) | # private IP in the range 172.16.0.0 .. 172.31.255.255
6da2bc5 Alexey Gaziev Valid ips v4 and v6. Right logic for working with X-FORWARDED-FOR header...
gazay authored
14 192\.168 | # private IP 192.168.x.x
15 fc00:: # private IP fc00
9432163 André Arko refactor RemoteIp middleware
indirect authored
16 )\.
17 }x
18
00a0a4d André Arko cleaner names
indirect authored
19 attr_reader :check_ip, :proxies
317f4e2 André Arko defer calculating the remote IP until requested
indirect authored
20
9432163 André Arko refactor RemoteIp middleware
indirect authored
21 def initialize(app, check_ip_spoofing = true, custom_proxies = nil)
22 @app = app
00a0a4d André Arko cleaner names
indirect authored
23 @check_ip = check_ip_spoofing
dd09811 Greg Sterndale Trusted proxies is replaced with a Regexp or appended to with a String
gsterndale authored
24 @proxies = case custom_proxies
6da2bc5 Alexey Gaziev Valid ips v4 and v6. Right logic for working with X-FORWARDED-FOR header...
gazay authored
25 when Regexp
26 custom_proxies
27 when nil
28 TRUSTED_PROXIES
29 else
30 Regexp.union(TRUSTED_PROXIES, custom_proxies)
31 end
9432163 André Arko refactor RemoteIp middleware
indirect authored
32 end
93422af Move remote_ip to a middleware:
Carlhuda authored
33
9432163 André Arko refactor RemoteIp middleware
indirect authored
34 def call(env)
317f4e2 André Arko defer calculating the remote IP until requested
indirect authored
35 env["action_dispatch.remote_ip"] = GetIp.new(env, self)
9432163 André Arko refactor RemoteIp middleware
indirect authored
36 @app.call(env)
93422af Move remote_ip to a middleware:
Carlhuda authored
37 end
38
317f4e2 André Arko defer calculating the remote IP until requested
indirect authored
39 class GetIp
6da2bc5 Alexey Gaziev Valid ips v4 and v6. Right logic for working with X-FORWARDED-FOR header...
gazay authored
40
41 # IP v4 and v6 (with compression) validation regexp
42 # https://gist.github.com/1289635
43 VALID_IP = %r{
44 (^(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
45 (^(
46 (([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4}) | # ip v6 not abbreviated
47 (([0-9A-Fa-f]{1,4}:){6}:[0-9A-Fa-f]{1,4}) | # ip v6 with double colon in the end
48 (([0-9A-Fa-f]{1,4}:){5}:([0-9A-Fa-f]{1,4}:)?[0-9A-Fa-f]{1,4}) | # - ip addresses v6
49 (([0-9A-Fa-f]{1,4}:){4}:([0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4}) | # - with
50 (([0-9A-Fa-f]{1,4}:){3}:([0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4}) | # - double colon
51 (([0-9A-Fa-f]{1,4}:){2}:([0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4}) | # - in the middle
52 (([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
53 (([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
54 (([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
55 (([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
56 (([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
57 (([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
58 (::([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
59 ([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
60 (::([0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4}) | # ip v6 with double colon at the begining
61 (([0-9A-Fa-f]{1,4}:){1,7}:) # ip v6 without ending
62 )$)
63 }x
64
317f4e2 André Arko defer calculating the remote IP until requested
indirect authored
65 def initialize(env, middleware)
93df3b2 Carlos Antonio da Silva Fix routing test to use assert_equal
carlosantoniodasilva authored
66 @env = env
67 @middleware = middleware
c3ae1d2 Arun Agrawal It should be @calculated_ip not @calculate_ip
arunagw authored
68 @calculated_ip = false
317f4e2 André Arko defer calculating the remote IP until requested
indirect authored
69 end
70
71 # Determines originating IP address. REMOTE_ADDR is the standard
72 # but will be wrong if the user is behind a proxy. Proxies will set
73 # HTTP_CLIENT_IP and/or HTTP_X_FORWARDED_FOR, so we prioritize those.
74 # HTTP_X_FORWARDED_FOR may be a comma-delimited list in the case of
6da2bc5 Alexey Gaziev Valid ips v4 and v6. Right logic for working with X-FORWARDED-FOR header...
gazay authored
75 # multiple chained proxies. The first address which is in this list
76 # if it's not a known proxy will be the originating IP.
77 # Format of HTTP_X_FORWARDED_FOR:
78 # client_ip, proxy_ip1, proxy_ip2...
79 # http://en.wikipedia.org/wiki/X-Forwarded-For
cda1a5d André Arko memoize the relatively expensive remote IP code
indirect authored
80 def calculate_ip
6da2bc5 Alexey Gaziev Valid ips v4 and v6. Right logic for working with X-FORWARDED-FOR header...
gazay authored
81 client_ip = @env['HTTP_CLIENT_IP']
82 forwarded_ip = ips_from('HTTP_X_FORWARDED_FOR').first
83 remote_addrs = ips_from('REMOTE_ADDR')
93422af Move remote_ip to a middleware:
Carlhuda authored
84
00a0a4d André Arko cleaner names
indirect authored
85 check_ip = client_ip && @middleware.check_ip
6da2bc5 Alexey Gaziev Valid ips v4 and v6. Right logic for working with X-FORWARDED-FOR header...
gazay authored
86 if check_ip && forwarded_ip != client_ip
317f4e2 André Arko defer calculating the remote IP until requested
indirect authored
87 # We don't know which came from the proxy, and which from the user
88 raise IpSpoofAttackError, "IP spoofing attack?!" \
2189bff André Arko correctly raise IpSpoofAttackError message
indirect authored
89 "HTTP_CLIENT_IP=#{@env['HTTP_CLIENT_IP'].inspect}" \
90 "HTTP_X_FORWARDED_FOR=#{@env['HTTP_X_FORWARDED_FOR'].inspect}"
317f4e2 André Arko defer calculating the remote IP until requested
indirect authored
91 end
92
6da2bc5 Alexey Gaziev Valid ips v4 and v6. Right logic for working with X-FORWARDED-FOR header...
gazay authored
93 client_ips = remove_proxies [client_ip, forwarded_ip, remote_addrs].flatten
94 if client_ips.present?
95 client_ips.first
96 else
97 # If there is no client ip we can return first valid proxy ip from REMOTE_ADDR
98 remote_addrs.find { |ip| valid_ip? ip }
99 end
317f4e2 André Arko defer calculating the remote IP until requested
indirect authored
100 end
101
cda1a5d André Arko memoize the relatively expensive remote IP code
indirect authored
102 def to_s
103 return @ip if @calculated_ip
104 @calculated_ip = true
105 @ip = calculate_ip
106 end
107
6da2bc5 Alexey Gaziev Valid ips v4 and v6. Right logic for working with X-FORWARDED-FOR header...
gazay authored
108 private
317f4e2 André Arko defer calculating the remote IP until requested
indirect authored
109
6da2bc5 Alexey Gaziev Valid ips v4 and v6. Right logic for working with X-FORWARDED-FOR header...
gazay authored
110 def ips_from(header)
111 @env[header] ? @env[header].strip.split(/[,\s]+/) : []
317f4e2 André Arko defer calculating the remote IP until requested
indirect authored
112 end
6da2bc5 Alexey Gaziev Valid ips v4 and v6. Right logic for working with X-FORWARDED-FOR header...
gazay authored
113
114 def valid_ip?(ip)
115 ip =~ VALID_IP
116 end
117
118 def not_a_proxy?(ip)
119 ip !~ @middleware.proxies
120 end
121
122 def remove_proxies(ips)
123 ips.select { |ip| valid_ip?(ip) && not_a_proxy?(ip) }
124 end
125
93422af Move remote_ip to a middleware:
Carlhuda authored
126 end
9432163 André Arko refactor RemoteIp middleware
indirect authored
127
93422af Move remote_ip to a middleware:
Carlhuda authored
128 end
317f4e2 André Arko defer calculating the remote IP until requested
indirect authored
129 end
Something went wrong with that request. Please try again.