module Geocoder::Request
Constants
- GEOCODER_CANDIDATE_HEADERS
There’s a whole zoo of nonstandard headers added by various
proxy softwares to indicate original client IP.
ANY of these can be trivially spoofed!
(except REMOTE_ADDR, which should by set by your server, and is included at the end as a fallback.
Order does matter: we’re following the convention established in
ActionDispatch::RemoteIp::GetIp::calculate_ip() https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/middleware/remote_ip.rb where the forwarded_for headers, possibly containing lists, are arbitrarily preferred over headers expected to contain a single address.
Public Instance Methods
geocoder_spoofable_ip()
click to toggle source
# File lib/geocoder/request.rb, line 46 def geocoder_spoofable_ip # We could use a more sophisticated IP-guessing algorithm here, # in which we'd try to resolve the use of different headers by # different proxies. The idea is that by comparing IPs repeated # in different headers, you can sometimes decide which header # was used by a proxy further along in the chain, and thus # prefer the headers used earlier. However, the gains might not # be worth the performance tradeoff, since this method is likely # to be called on every request in a lot of applications. GEOCODER_CANDIDATE_HEADERS.each do |header| if @env.has_key? header addrs = geocoder_split_ip_addresses(@env[header]) addrs = geocoder_remove_port_from_addresses(addrs) addrs = geocoder_reject_non_ipv4_addresses(addrs) addrs = geocoder_reject_trusted_ip_addresses(addrs) return addrs.first if addrs.any? end end @env['REMOTE_ADDR'] end
location()
click to toggle source
The location() method is vulnerable to trivial IP spoofing.
Don't use it in authorization/authentication code, or any other security-sensitive application. Use safe_location instead.
# File lib/geocoder/request.rb, line 10 def location @location ||= Geocoder.search(geocoder_spoofable_ip, ip_address: true).first end
safe_location()
click to toggle source
This safe_location
() protects you from trivial IP spoofing.
For requests that go through a proxy that you haven't whitelisted as trusted in your Rack config, you will get the location for the IP of the last untrusted proxy in the chain, not the original client IP. You WILL NOT get the location corresponding to the original client IP for any request sent through a non-whitelisted proxy.
# File lib/geocoder/request.rb, line 21 def safe_location @safe_location ||= Geocoder.search(ip, ip_address: true).first end
Private Instance Methods
geocoder_reject_non_ipv4_addresses(ip_addresses)
click to toggle source
# File lib/geocoder/request.rb, line 98 def geocoder_reject_non_ipv4_addresses(ip_addresses) ips = [] for ip in ip_addresses begin valid_ip = IPAddr.new(ip) rescue valid_ip = false end ips << valid_ip.to_s if valid_ip end return ips.any? ? ips : ip_addresses end
geocoder_reject_trusted_ip_addresses(ip_addresses)
click to toggle source
use Rack’s trusted_proxy?() method to filter out IPs that have
been configured as trusted; includes private ranges by default. (we don't want every lookup to return the location of our own proxy/load balancer)
# File lib/geocoder/request.rb, line 79 def geocoder_reject_trusted_ip_addresses(ip_addresses) ip_addresses.reject { |ip| trusted_proxy?(ip) } end
geocoder_remove_port_from_addresses(ip_addresses)
click to toggle source
# File lib/geocoder/request.rb, line 83 def geocoder_remove_port_from_addresses(ip_addresses) ip_addresses.map do |ip| # IPv4 if ip.count('.') > 0 ip.split(':').first # IPv6 bracket notation elsif match = ip.match(/\[(\S+)\]/) match.captures.first # IPv6 bare notation else ip end end end
geocoder_split_ip_addresses(ip_addresses)
click to toggle source
# File lib/geocoder/request.rb, line 71 def geocoder_split_ip_addresses(ip_addresses) ip_addresses ? ip_addresses.strip.split(/[,\s]+/) : [] end