PageRenderTime 42ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/actionpack/lib/action_dispatch/middleware/remote_ip.rb

http://github.com/rails/rails
Ruby | 184 lines | 72 code | 16 blank | 96 comment | 9 complexity | 1061e3bd9e4ad9e8e15b51a7209b95cd MD5 | raw file
  1. # frozen_string_literal: true
  2. require "ipaddr"
  3. module ActionDispatch
  4. # This middleware calculates the IP address of the remote client that is
  5. # making the request. It does this by checking various headers that could
  6. # contain the address, and then picking the last-set address that is not
  7. # on the list of trusted IPs. This follows the precedent set by e.g.
  8. # {the Tomcat server}[https://issues.apache.org/bugzilla/show_bug.cgi?id=50453],
  9. # with {reasoning explained at length}[https://blog.gingerlime.com/2012/rails-ip-spoofing-vulnerabilities-and-protection]
  10. # by @gingerlime. A more detailed explanation of the algorithm is given
  11. # at GetIp#calculate_ip.
  12. #
  13. # Some Rack servers concatenate repeated headers, like {HTTP RFC 2616}[https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2]
  14. # requires. Some Rack servers simply drop preceding headers, and only report
  15. # the value that was {given in the last header}[https://andre.arko.net/2011/12/26/repeated-headers-and-ruby-web-servers].
  16. # If you are behind multiple proxy servers (like NGINX to HAProxy to Unicorn)
  17. # then you should test your Rack server to make sure your data is good.
  18. #
  19. # IF YOU DON'T USE A PROXY, THIS MAKES YOU VULNERABLE TO IP SPOOFING.
  20. # This middleware assumes that there is at least one proxy sitting around
  21. # and setting headers with the client's remote IP address. If you don't use
  22. # a proxy, because you are hosted on e.g. Heroku without SSL, any client can
  23. # claim to have any IP address by setting the X-Forwarded-For header. If you
  24. # care about that, then you need to explicitly drop or ignore those headers
  25. # sometime before this middleware runs.
  26. class RemoteIp
  27. class IpSpoofAttackError < StandardError; end
  28. # The default trusted IPs list simply includes IP addresses that are
  29. # guaranteed by the IP specification to be private addresses. Those will
  30. # not be the ultimate client IP in production, and so are discarded. See
  31. # https://en.wikipedia.org/wiki/Private_network for details.
  32. TRUSTED_PROXIES = [
  33. "127.0.0.0/8", # localhost IPv4 range, per RFC-3330
  34. "::1", # localhost IPv6
  35. "fc00::/7", # private IPv6 range fc00::/7
  36. "10.0.0.0/8", # private IPv4 range 10.x.x.x
  37. "172.16.0.0/12", # private IPv4 range 172.16.0.0 .. 172.31.255.255
  38. "192.168.0.0/16", # private IPv4 range 192.168.x.x
  39. ].map { |proxy| IPAddr.new(proxy) }
  40. attr_reader :check_ip, :proxies
  41. # Create a new +RemoteIp+ middleware instance.
  42. #
  43. # The +ip_spoofing_check+ option is on by default. When on, an exception
  44. # is raised if it looks like the client is trying to lie about its own IP
  45. # address. It makes sense to turn off this check on sites aimed at non-IP
  46. # clients (like WAP devices), or behind proxies that set headers in an
  47. # incorrect or confusing way (like AWS ELB).
  48. #
  49. # The +custom_proxies+ argument can take an Array of string, IPAddr, or
  50. # Regexp objects which will be used instead of +TRUSTED_PROXIES+. If a
  51. # single string, IPAddr, or Regexp object is provided, it will be used in
  52. # addition to +TRUSTED_PROXIES+. Any proxy setup will put the value you
  53. # want in the middle (or at the beginning) of the X-Forwarded-For list,
  54. # with your proxy servers after it. If your proxies aren't removed, pass
  55. # them in via the +custom_proxies+ parameter. That way, the middleware will
  56. # ignore those IP addresses, and return the one that you want.
  57. def initialize(app, ip_spoofing_check = true, custom_proxies = nil)
  58. @app = app
  59. @check_ip = ip_spoofing_check
  60. @proxies = if custom_proxies.blank?
  61. TRUSTED_PROXIES
  62. elsif custom_proxies.respond_to?(:any?)
  63. custom_proxies
  64. else
  65. Array(custom_proxies) + TRUSTED_PROXIES
  66. end
  67. end
  68. # Since the IP address may not be needed, we store the object here
  69. # without calculating the IP to keep from slowing down the majority of
  70. # requests. For those requests that do need to know the IP, the
  71. # GetIp#calculate_ip method will calculate the memoized client IP address.
  72. def call(env)
  73. req = ActionDispatch::Request.new env
  74. req.remote_ip = GetIp.new(req, check_ip, proxies)
  75. @app.call(req.env)
  76. end
  77. # The GetIp class exists as a way to defer processing of the request data
  78. # into an actual IP address. If the ActionDispatch::Request#remote_ip method
  79. # is called, this class will calculate the value and then memoize it.
  80. class GetIp
  81. def initialize(req, check_ip, proxies)
  82. @req = req
  83. @check_ip = check_ip
  84. @proxies = proxies
  85. end
  86. # Sort through the various IP address headers, looking for the IP most
  87. # likely to be the address of the actual remote client making this
  88. # request.
  89. #
  90. # REMOTE_ADDR will be correct if the request is made directly against the
  91. # Ruby process, on e.g. Heroku. When the request is proxied by another
  92. # server like HAProxy or NGINX, the IP address that made the original
  93. # request will be put in an X-Forwarded-For header. If there are multiple
  94. # proxies, that header may contain a list of IPs. Other proxy services
  95. # set the Client-Ip header instead, so we check that too.
  96. #
  97. # As discussed in {this post about Rails IP Spoofing}[https://blog.gingerlime.com/2012/rails-ip-spoofing-vulnerabilities-and-protection/],
  98. # while the first IP in the list is likely to be the "originating" IP,
  99. # it could also have been set by the client maliciously.
  100. #
  101. # In order to find the first address that is (probably) accurate, we
  102. # take the list of IPs, remove known and trusted proxies, and then take
  103. # the last address left, which was presumably set by one of those proxies.
  104. def calculate_ip
  105. # Set by the Rack web server, this is a single value.
  106. remote_addr = sanitize_ips(ips_from(@req.remote_addr)).last
  107. # Could be a CSV list and/or repeated headers that were concatenated.
  108. client_ips = sanitize_ips(ips_from(@req.client_ip)).reverse
  109. forwarded_ips = sanitize_ips(@req.forwarded_for || []).reverse
  110. # +Client-Ip+ and +X-Forwarded-For+ should not, generally, both be set.
  111. # If they are both set, it means that either:
  112. #
  113. # 1) This request passed through two proxies with incompatible IP header
  114. # conventions.
  115. # 2) The client passed one of +Client-Ip+ or +X-Forwarded-For+
  116. # (whichever the proxy servers weren't using) themselves.
  117. #
  118. # Either way, there is no way for us to determine which header is the
  119. # right one after the fact. Since we have no idea, if we are concerned
  120. # about IP spoofing we need to give up and explode. (If you're not
  121. # concerned about IP spoofing you can turn the +ip_spoofing_check+
  122. # option off.)
  123. should_check_ip = @check_ip && client_ips.last && forwarded_ips.last
  124. if should_check_ip && !forwarded_ips.include?(client_ips.last)
  125. # We don't know which came from the proxy, and which from the user
  126. raise IpSpoofAttackError, "IP spoofing attack?! " \
  127. "HTTP_CLIENT_IP=#{@req.client_ip.inspect} " \
  128. "HTTP_X_FORWARDED_FOR=#{@req.x_forwarded_for.inspect}"
  129. end
  130. # We assume these things about the IP headers:
  131. #
  132. # - X-Forwarded-For will be a list of IPs, one per proxy, or blank
  133. # - Client-Ip is propagated from the outermost proxy, or is blank
  134. # - REMOTE_ADDR will be the IP that made the request to Rack
  135. ips = [forwarded_ips, client_ips].flatten.compact
  136. # If every single IP option is in the trusted list, return the IP
  137. # that's furthest away
  138. filter_proxies(ips + [remote_addr]).first || ips.last || remote_addr
  139. end
  140. # Memoizes the value returned by #calculate_ip and returns it for
  141. # ActionDispatch::Request to use.
  142. def to_s
  143. @ip ||= calculate_ip
  144. end
  145. private
  146. def ips_from(header) # :doc:
  147. return [] unless header
  148. # Split the comma-separated list into an array of strings.
  149. header.strip.split(/[,\s]+/)
  150. end
  151. def sanitize_ips(ips) # :doc:
  152. ips.select do |ip|
  153. # Only return IPs that are valid according to the IPAddr#new method.
  154. range = IPAddr.new(ip).to_range
  155. # We want to make sure nobody is sneaking a netmask in.
  156. range.begin == range.end
  157. rescue ArgumentError
  158. nil
  159. end
  160. end
  161. def filter_proxies(ips) # :doc:
  162. ips.reject do |ip|
  163. @proxies.any? { |proxy| proxy === ip }
  164. end
  165. end
  166. end
  167. end
  168. end