Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
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 @indirect 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 @gazay Valid ips v4 and v6. Right logic for working with X-FORWARDED-FOR hea…
gazay authored
8 # http://en.wikipedia.org/wiki/Private_network#Private_IPv6_addresses.
9432163 @indirect refactor RemoteIp middleware
indirect authored
9 TRUSTED_PROXIES = %r{
10 ^127\.0\.0\.1$ | # localhost
6da2bc5 @gazay Valid ips v4 and v6. Right logic for working with X-FORWARDED-FOR hea…
gazay authored
11 ^::1$ |
9432163 @indirect 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 @gazay Valid ips v4 and v6. Right logic for working with X-FORWARDED-FOR hea…
gazay authored
14 192\.168 | # private IP 192.168.x.x
15 fc00:: # private IP fc00
9432163 @indirect refactor RemoteIp middleware
indirect authored
16 )\.
17 }x
18
00a0a4d @indirect cleaner names
indirect authored
19 attr_reader :check_ip, :proxies
317f4e2 @indirect defer calculating the remote IP until requested
indirect authored
20
9432163 @indirect refactor RemoteIp middleware
indirect authored
21 def initialize(app, check_ip_spoofing = true, custom_proxies = nil)
22 @app = app
00a0a4d @indirect cleaner names
indirect authored
23 @check_ip = check_ip_spoofing
dd09811 @gsterndale Trusted proxies is replaced with a Regexp or appended to with a String
gsterndale authored
24 @proxies = case custom_proxies
6da2bc5 @gazay Valid ips v4 and v6. Right logic for working with X-FORWARDED-FOR hea…
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 @indirect refactor RemoteIp middleware
indirect authored
32 end
93422af Move remote_ip to a middleware:
Carlhuda authored
33
9432163 @indirect refactor RemoteIp middleware
indirect authored
34 def call(env)
317f4e2 @indirect defer calculating the remote IP until requested
indirect authored
35 env["action_dispatch.remote_ip"] = GetIp.new(env, self)
9432163 @indirect refactor RemoteIp middleware
indirect authored
36 @app.call(env)
93422af Move remote_ip to a middleware:
Carlhuda authored
37 end
38
317f4e2 @indirect defer calculating the remote IP until requested
indirect authored
39 class GetIp
6da2bc5 @gazay Valid ips v4 and v6. Right logic for working with X-FORWARDED-FOR hea…
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 @indirect defer calculating the remote IP until requested
indirect authored
65 def initialize(env, middleware)
93df3b2 @carlosantoniodasilva Fix routing test to use assert_equal
carlosantoniodasilva authored
66 @env = env
67 @middleware = middleware
c3ae1d2 @arunagw It should be @calculated_ip not @calculate_ip
arunagw authored
68 @calculated_ip = false
317f4e2 @indirect 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 @gazay Valid ips v4 and v6. Right logic for working with X-FORWARDED-FOR hea…
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 @indirect memoize the relatively expensive remote IP code
indirect authored
80 def calculate_ip
6da2bc5 @gazay Valid ips v4 and v6. Right logic for working with X-FORWARDED-FOR hea…
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 @indirect cleaner names
indirect authored
85 check_ip = client_ip && @middleware.check_ip
6da2bc5 @gazay Valid ips v4 and v6. Right logic for working with X-FORWARDED-FOR hea…
gazay authored
86 if check_ip && forwarded_ip != client_ip
317f4e2 @indirect 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 @indirect 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 @indirect defer calculating the remote IP until requested
indirect authored
91 end
92
6da2bc5 @gazay Valid ips v4 and v6. Right logic for working with X-FORWARDED-FOR hea…
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 @indirect defer calculating the remote IP until requested
indirect authored
100 end
101
cda1a5d @indirect 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 @gazay Valid ips v4 and v6. Right logic for working with X-FORWARDED-FOR hea…
gazay authored
108 private
317f4e2 @indirect defer calculating the remote IP until requested
indirect authored
109
6da2bc5 @gazay Valid ips v4 and v6. Right logic for working with X-FORWARDED-FOR hea…
gazay authored
110 def ips_from(header)
111 @env[header] ? @env[header].strip.split(/[,\s]+/) : []
317f4e2 @indirect defer calculating the remote IP until requested
indirect authored
112 end
6da2bc5 @gazay Valid ips v4 and v6. Right logic for working with X-FORWARDED-FOR hea…
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 @indirect refactor RemoteIp middleware
indirect authored
127
93422af Move remote_ip to a middleware:
Carlhuda authored
128 end
317f4e2 @indirect defer calculating the remote IP until requested
indirect authored
129 end
Something went wrong with that request. Please try again.