remote_ip_extensions.rb 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172
  1. # frozen_string_literal: true
  2. # Mastodon is not made to be directly accessed without a reverse proxy.
  3. # This monkey-patch prevents remote IP address spoofing when being accessed
  4. # directly.
  5. #
  6. # See PR: https://github.com/rails/rails/pull/51610
  7. # In addition to the PR above, it also raises an error if a request with
  8. # `X-Forwarded-For` or `Client-Ip` comes directly from a client without
  9. # going through a trusted proxy.
  10. # rubocop:disable all -- This is a mostly vendored file
  11. module ActionDispatch
  12. class RemoteIp
  13. module GetIpExtensions
  14. def calculate_ip
  15. # Set by the Rack web server, this is a single value.
  16. remote_addr = ips_from(@req.remote_addr).last
  17. # Could be a CSV list and/or repeated headers that were concatenated.
  18. client_ips = ips_from(@req.client_ip).reverse!
  19. forwarded_ips = ips_from(@req.x_forwarded_for).reverse!
  20. # `Client-Ip` and `X-Forwarded-For` should not, generally, both be set. If they
  21. # are both set, it means that either:
  22. #
  23. # 1) This request passed through two proxies with incompatible IP header
  24. # conventions.
  25. #
  26. # 2) The client passed one of `Client-Ip` or `X-Forwarded-For`
  27. # (whichever the proxy servers weren't using) themselves.
  28. #
  29. # Either way, there is no way for us to determine which header is the right one
  30. # after the fact. Since we have no idea, if we are concerned about IP spoofing
  31. # we need to give up and explode. (If you're not concerned about IP spoofing you
  32. # can turn the `ip_spoofing_check` option off.)
  33. should_check_ip = @check_ip && client_ips.last && forwarded_ips.last
  34. if should_check_ip && !forwarded_ips.include?(client_ips.last)
  35. # We don't know which came from the proxy, and which from the user
  36. raise IpSpoofAttackError, "IP spoofing attack?! " \
  37. "HTTP_CLIENT_IP=#{@req.client_ip.inspect} " \
  38. "HTTP_X_FORWARDED_FOR=#{@req.x_forwarded_for.inspect}"
  39. end
  40. # NOTE: Mastodon addition to make sure we don't get requests from a non-trusted client
  41. if @check_ip && (forwarded_ips.last || client_ips.last) && !@proxies.any? { |proxy| proxy === remote_addr }
  42. raise IpSpoofAttackError, "IP spoofing attack?! client #{remote_addr} is not a trusted proxy " \
  43. "HTTP_CLIENT_IP=#{@req.client_ip.inspect} " \
  44. "HTTP_X_FORWARDED_FOR=#{@req.x_forwarded_for.inspect}"
  45. end
  46. # We assume these things about the IP headers:
  47. #
  48. # - X-Forwarded-For will be a list of IPs, one per proxy, or blank
  49. # - Client-Ip is propagated from the outermost proxy, or is blank
  50. # - REMOTE_ADDR will be the IP that made the request to Rack
  51. ips = forwarded_ips + client_ips
  52. ips.compact!
  53. # If every single IP option is in the trusted list, return the IP that's
  54. # furthest away
  55. filter_proxies([remote_addr] + ips).first || ips.last || remote_addr
  56. end
  57. end
  58. end
  59. end
  60. ActionDispatch::RemoteIp::GetIp.prepend(ActionDispatch::RemoteIp::GetIpExtensions)
  61. # rubocop:enable all