Skip to content

Commit 2c96f50

Browse files
committed
Merge [9124] from trunk: Avoid remote_ip spoofing.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/2-0-stable@9125 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
1 parent 1c207df commit 2c96f50

File tree

3 files changed

+53
-15
lines changed

3 files changed

+53
-15
lines changed

actionpack/CHANGELOG

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
*SVN*
22

3+
* Avoid remote_ip spoofing. [Brian Candler]
4+
35
* Correct inconsistencies in RequestForgeryProtection docs. #11032 [mislav]
46

57
* Make assert_routing aware of the HTTP method used. #8039 [mpalmer]

actionpack/lib/action_controller/request.rb

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -122,26 +122,41 @@ def xml_http_request?
122122
end
123123
alias xhr? :xml_http_request?
124124

125+
# Which IP addresses are "trusted proxies" that can be stripped from
126+
# the right-hand-side of X-Forwarded-For
127+
TRUSTED_PROXIES = /^127\.0\.0\.1$|^(10|172\.(1[6-9]|2[0-9]|30|31)|192\.168)\./i
128+
125129
# Determine originating IP address. REMOTE_ADDR is the standard
126130
# but will fail if the user is behind a proxy. HTTP_CLIENT_IP and/or
127-
# HTTP_X_FORWARDED_FOR are set by proxies so check for these before
128-
# falling back to REMOTE_ADDR. HTTP_X_FORWARDED_FOR may be a comma-
129-
# delimited list in the case of multiple chained proxies; the first is
130-
# the originating IP.
131-
#
132-
# Security note: do not use if IP spoofing is a concern for your
133-
# application. Since remote_ip checks HTTP headers for addresses forwarded
134-
# by proxies, the client may send any IP. remote_addr can't be spoofed but
135-
# also doesn't work behind a proxy, since it's always the proxy's IP.
131+
# HTTP_X_FORWARDED_FOR are set by proxies so check for these if
132+
# REMOTE_ADDR is a proxy. HTTP_X_FORWARDED_FOR may be a comma-
133+
# delimited list in the case of multiple chained proxies; the last
134+
# address which is not trusted is the originating IP.
135+
136136
def remote_ip
137-
return @env['HTTP_CLIENT_IP'] if @env.include? 'HTTP_CLIENT_IP'
137+
if TRUSTED_PROXIES !~ @env['REMOTE_ADDR']
138+
return @env['REMOTE_ADDR']
139+
end
140+
141+
if @env.include? 'HTTP_CLIENT_IP'
142+
if @env.include? 'HTTP_X_FORWARDED_FOR'
143+
# We don't know which came from the proxy, and which from the user
144+
raise ActionControllerError.new(<<EOM)
145+
IP spoofing attack?!
146+
HTTP_CLIENT_IP=#{@env['HTTP_CLIENT_IP'].inspect}
147+
HTTP_X_FORWARDED_FOR=#{@env['HTTP_X_FORWARDED_FOR'].inspect}
148+
EOM
149+
end
150+
return @env['HTTP_CLIENT_IP']
151+
end
138152

139153
if @env.include? 'HTTP_X_FORWARDED_FOR' then
140-
remote_ips = @env['HTTP_X_FORWARDED_FOR'].split(',').reject do |ip|
141-
ip.strip =~ /^unknown$|^(10|172\.(1[6-9]|2[0-9]|30|31)|192\.168)\./i
154+
remote_ips = @env['HTTP_X_FORWARDED_FOR'].split(',')
155+
while remote_ips.size > 1 && TRUSTED_PROXIES =~ remote_ips.last.strip
156+
remote_ips.pop
142157
end
143158

144-
return remote_ips.first.strip unless remote_ips.empty?
159+
return remote_ips.last.strip
145160
end
146161

147162
@env['REMOTE_ADDR']

actionpack/test/controller/request_test.rb

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,17 @@ def test_remote_ip
1313
assert_equal '1.2.3.4', @request.remote_ip
1414

1515
@request.env['HTTP_CLIENT_IP'] = '2.3.4.5'
16+
assert_equal '1.2.3.4', @request.remote_ip
17+
18+
@request.remote_addr = '192.168.0.1'
1619
assert_equal '2.3.4.5', @request.remote_ip
1720
@request.env.delete 'HTTP_CLIENT_IP'
1821

22+
@request.remote_addr = '1.2.3.4'
23+
@request.env['HTTP_X_FORWARDED_FOR'] = '3.4.5.6'
24+
assert_equal '1.2.3.4', @request.remote_ip
25+
26+
@request.remote_addr = '127.0.0.1'
1927
@request.env['HTTP_X_FORWARDED_FOR'] = '3.4.5.6'
2028
assert_equal '3.4.5.6', @request.remote_ip
2129

@@ -35,10 +43,23 @@ def test_remote_ip
3543
assert_equal '3.4.5.6', @request.remote_ip
3644

3745
@request.env['HTTP_X_FORWARDED_FOR'] = '127.0.0.1,3.4.5.6'
38-
assert_equal '127.0.0.1', @request.remote_ip
46+
assert_equal '3.4.5.6', @request.remote_ip
3947

4048
@request.env['HTTP_X_FORWARDED_FOR'] = 'unknown,192.168.0.1'
41-
assert_equal '1.2.3.4', @request.remote_ip
49+
assert_equal 'unknown', @request.remote_ip
50+
51+
@request.env['HTTP_X_FORWARDED_FOR'] = '9.9.9.9, 3.4.5.6, 10.0.0.1, 172.31.4.4'
52+
assert_equal '3.4.5.6', @request.remote_ip
53+
54+
@request.env['HTTP_CLIENT_IP'] = '8.8.8.8'
55+
e = assert_raises(ActionController::ActionControllerError) {
56+
@request.remote_ip
57+
}
58+
assert_match /IP spoofing attack/, e.message
59+
assert_match /HTTP_X_FORWARDED_FOR="9.9.9.9, 3.4.5.6, 10.0.0.1, 172.31.4.4"/, e.message
60+
assert_match /HTTP_CLIENT_IP="8.8.8.8"/, e.message
61+
62+
@request.env.delete 'HTTP_CLIENT_IP'
4263
@request.env.delete 'HTTP_X_FORWARDED_FOR'
4364
end
4465

0 commit comments

Comments
 (0)