What's the difference between request.remote_ip and request.ip in Rails?
Asked Answered
D

3

92

As the title goes, you can get the client's ip with both methods. I wonder if there is any differences. Thank you.

in the source code there goes

"/usr/local/rvm/gems/ruby-1.9.3-p194/gems/actionpack-3.2.3/lib/action _dispatch/http/request.rb" 257L, 8741C

def ip
  @ip ||= super
end

# Originating IP address, usually set by the RemoteIp middleware.
def remote_ip
  @remote_ip ||= (@env["action_dispatch.remote_ip"] || ip).to_s
end

but I really don't know the implications.

Douche answered 12/6, 2012 at 12:49 Comment(0)
I
46

From source:

module ActionDispatch
  class Request < Rack::Request

    # ...

    def ip
      @ip ||= super
    end

    def remote_ip
      @remote_ip ||= (@env["action_dispatch.remote_ip"] || ip).to_s
    end

    # ...

  end
end

where Rack::Request looks like this

module Rack
  class Request
     def ip
      remote_addrs = split_ip_addresses(@env['REMOTE_ADDR'])
      remote_addrs = reject_trusted_ip_addresses(remote_addrs)

      return remote_addrs.first if remote_addrs.any?

      forwarded_ips = split_ip_addresses(@env['HTTP_X_FORWARDED_FOR'])

      if client_ip = @env['HTTP_CLIENT_IP']
        # If forwarded_ips doesn't include the client_ip, it might be an
        # ip spoofing attempt, so we ignore HTTP_CLIENT_IP
        return client_ip if forwarded_ips.include?(client_ip)
      end

      return reject_trusted_ip_addresses(forwarded_ips).last || @env["REMOTE_ADDR"]
    end
  end
end 

So remote_ip gives precedence to action_dispatch.remote_ip. That is being set by ActionDispatch::RemoteIp middleware. You can see in that middleware's source that it's checking for spoofing attacks when being called, since it's calling GetIp.new to set that env variable. That's needed since remote_ip reads the ip address even through the local proxies, as Clowerweb explains.

Incommode answered 12/6, 2012 at 13:6 Comment(1)
In my experience, even though request.remote_ip looks through the HTTP_X_FORWARDED_FOR chain, it can still be spoofed if you have Rails behind nginx or haproxy. In nginx, use proxy_set_header X-Forwarded-For $remote_addr; instead of proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;. In the latter case, curl -H "X-Forwarded-For: 6.66.6.66" http://example.com will produce a request.remote_ip of 6.66.6.66, which will spoof any ip checking you have in Rails.Biometry
A
98

request.ip returns the client ip even if that client is a proxy.

request.remote_ip is smarter and gets the actual client ip. This can only be done if the all the proxies along the way set the X-Forwarded-For header.

Asphyxia answered 12/6, 2012 at 12:56 Comment(0)
T
82

request.ip

request.ip is the basic ip detection provided by Rack::Request out of the box. Its current definition can be found at https://github.com/rack/rack/blob/master/lib/rack/request.rb.

The algorithm it follows is to first check the REMOTE_ADDR header for any untrusted IP addresses, and if it finds any, it chooses the first one listed. "Trusted" IP addresses in this case are IP addresses from the reserved private subnet ranges, but note that it matches by regex which is probably not the best way to do it. If there is no untrusted REMOTE_ADDR then it looks at the HTTP_X_FORWARDED_FOR header, and picks the last untrusted one listed. If neither of those reveals anyone it falls back to the raw REMOTE_ADDR which is probably 127.0.0.1.

request.remote_ip

request.remote_ip is enhanced IP detection provided by ActionDispatch::Request (which inherits from Rack::Request). This is the code shown in the question. As you can see, it falls back to request.ip unless action_dispatch.remote_ip is set on the @env. That is done by the RemoteIp middleware, which is included in the default Rails stack. You can see its source at https://github.com/rails/rails/blob/4-2-stable/actionpack/lib/action_dispatch/middleware/remote_ip.rb.

The RemoteIp middleware if enabled provides these additional features:

  • Provides optional but default IP spoofing detection.
  • Allows configuration proxy addresses to be filtered instead of relying only on defaults.
  • Uses the IPAddr class to actually test IP ranges properly instead of relying on a brittle regex.
  • Uses HTTP_CLIENT_IP as a source of potential IPs.

The algorithm is similar to request.ip but slightly different. It uses HTTP_X_FORWARDED_FOR from last to first, then HTTP_CLIENT_IP from last to first, then finally the last entry of REMOTE_ADDR. It puts those all in a list and filters proxies, picking the first remaining one.

IP Spoofing Detection

The IP spoofing detection provided by RemoteIp is not particularly powerful, all it does is raise an exception if the last HTTP_CLIENT_IP is not in HTTP_X_FORWARDED_FOR. This isn't necessarily a symptom of an attack, but it is probably a symptom of a misconfiguration or a mix of proxies using different conventions which are not producing a coherent result.

Which to Use

In a simple setup where your proxies are all local or on private subnets, you can probably get away with request.ip, but request.remote_ip should be considered the superior choice in general. If you are using proxies with public internet routing (such as many CDNs) then RemoteIp can be configured to give you correct client IPs out of the box, whereas request.ip will only be correct if you can get your upstream proxy to set REMOTE_ADDR correctly.

Secure Configuration

Now to address Tim Coulter's comment about spoofing. He's definitely right you should be concerned, but he's wrong that you can be spoofed if you're behind nginx or haproxy by default. RemoteIp is designed to prevent spoofing by choosing the last IP in the chain. The X-Forwarded-For spec specifies that each proxy append the requester's IP to the end of the chain. By filtering out whitelisted proxies, the last entry is guaranteed to be the client IP written by your first whitelisted proxy. There is one caveat of course, which is that you must actually be running a proxy that always sets/appends X-Forwarded-For, so Tim's advice should actually be opposite: only use request.remote_ip when you are running a proxy.

How To Configure for Public IP Proxies

That's all fine and good, but ActionDispatch::RemoteIp is already in the default middleware stack. How do reconfigure it to add my proxy CIDRs?!

Add this to your application.rb:

check_spoofing = true
proxies = ["23.235.32.0/20", "203.57.145.0/24"]
proxies += ActionDispatch::RemoteIp::TRUSTED_PROXIES
config.middleware.swap ActionDispatch::RemoteIp,
                       ActionDispatch::RemoteIp,
                       true,
                       proxies
Thermosphere answered 25/3, 2017 at 8:37 Comment(0)
I
46

From source:

module ActionDispatch
  class Request < Rack::Request

    # ...

    def ip
      @ip ||= super
    end

    def remote_ip
      @remote_ip ||= (@env["action_dispatch.remote_ip"] || ip).to_s
    end

    # ...

  end
end

where Rack::Request looks like this

module Rack
  class Request
     def ip
      remote_addrs = split_ip_addresses(@env['REMOTE_ADDR'])
      remote_addrs = reject_trusted_ip_addresses(remote_addrs)

      return remote_addrs.first if remote_addrs.any?

      forwarded_ips = split_ip_addresses(@env['HTTP_X_FORWARDED_FOR'])

      if client_ip = @env['HTTP_CLIENT_IP']
        # If forwarded_ips doesn't include the client_ip, it might be an
        # ip spoofing attempt, so we ignore HTTP_CLIENT_IP
        return client_ip if forwarded_ips.include?(client_ip)
      end

      return reject_trusted_ip_addresses(forwarded_ips).last || @env["REMOTE_ADDR"]
    end
  end
end 

So remote_ip gives precedence to action_dispatch.remote_ip. That is being set by ActionDispatch::RemoteIp middleware. You can see in that middleware's source that it's checking for spoofing attacks when being called, since it's calling GetIp.new to set that env variable. That's needed since remote_ip reads the ip address even through the local proxies, as Clowerweb explains.

Incommode answered 12/6, 2012 at 13:6 Comment(1)
In my experience, even though request.remote_ip looks through the HTTP_X_FORWARDED_FOR chain, it can still be spoofed if you have Rails behind nginx or haproxy. In nginx, use proxy_set_header X-Forwarded-For $remote_addr; instead of proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;. In the latter case, curl -H "X-Forwarded-For: 6.66.6.66" http://example.com will produce a request.remote_ip of 6.66.6.66, which will spoof any ip checking you have in Rails.Biometry

© 2022 - 2024 — McMap. All rights reserved.