Получить реальный IP-адрес клиента на Heroku

На Heroku Cedar я хотел получить IP клиента. Первая попытка:

ENV['REMOTE_ADDR']

Конечно, это не работает, потому что все запросы передаются через прокси. Таким образом, альтернативой было использовать:

ENV['HTTP_X_FORWARDED_FOR']

Но это не совсем безопасно, не так ли?

Если он содержит только одно значение, я беру это. Если он содержит более одного значения (через запятую), я мог бы взять первое.

Но что, если кто-то манипулирует этим значением? Я не могу доверять ENV['HTTP_X_FORWARDED_FOR'] как я мог с ENV['REMOTE_ADDR'], И нет списка доверенных прокси, которые я мог бы использовать.

Но всегда должен быть надежный способ получить IP-адрес клиента. Вы знаете один?

В своих документах Heroku описывает, что X-Forwarded-For это "исходный IP-адрес клиента, подключающегося к маршрутизатору Heroku".

Это звучит так, как будто Heroku может переписать X-Forwarded-For с исходящим удаленным IP. Это предотвратит подделку, верно? Кто-нибудь может это проверить?

4 ответа

Решение

От Джейкоба, директора по безопасности Heroku в то время:

Роутер не перезаписывает X-Forwarded-For, но это гарантирует, что реальное происхождение всегда будет последним элементом в списке.

Это означает, что, если вы получите доступ к приложению Heroku обычным способом, вы просто увидите свой IP-адрес в X-Forwarded-For заголовок:

$ curl http://httpbin.org/ip
{
  "origin": "123.124.125.126",
}

Если вы попытаетесь подделать IP-адрес, ваше предполагаемое происхождение будет отражено, но, что особенно важно, ваш реальный IP-адрес тоже. Очевидно, это все, что нам нужно, поэтому есть ясное и безопасное решение для получения IP-адреса клиента на Heroku:

$ curl -H"X-Forwarded-For: 8.8.8.8" http://httpbin.org/ip
{
  "origin": "8.8.8.8, 123.124.125.126"
}

Кстати, это полная противоположность тому, что описано в Википедии.

Реализация PHP:

function getIpAddress() {
    if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
        $ipAddresses = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
        return trim(end($ipAddresses));
    }
    else {
        return $_SERVER['REMOTE_ADDR'];
    }
}

Я работаю в отделе поддержки Heroku и провел некоторое время, обсуждая это с нашими инженерами по маршрутизации. Я хотел опубликовать дополнительную информацию, чтобы уточнить некоторые вещи о том, что здесь происходит.

В примере, приведенном в ответе выше, только что IP-адрес клиента отображался в последний раз по совпадению, и это не гарантировано. Причина, по которой он не был первым, заключается в том, что исходящий запрос утверждал, что он переадресовал IP, указанный в X-Forwarded-For заголовок. Когда маршрутизатор Heroku получил запрос, он просто добавил IP-адрес, напрямую подключенный к X-Forwarded-For список после того, который был введен в запрос. Наш маршрутизатор всегда добавляет IP-адрес, подключенный к AWS ELB перед нашей платформой, как последний IP-адрес в списке. Этот IP-адрес может быть исходным (и в случае, когда есть только один IP-адрес, он почти наверняка есть), но в тот момент, когда несколько IP-адресов соединены в цепочку, все ставки отключены. Общепринятым всегда является добавление последнего IP-адреса в цепочке в конец списка (что мы и делаем), но в любой точке цепочки эта цепочка может быть изменена и могут быть добавлены разные IP-адреса. Таким образом, единственный надежный IP-адрес (с точки зрения нашей платформы) - последний IP-адрес в списке.

Для иллюстрации, скажем, кто-то инициирует запрос и произвольно добавляет 3 дополнительных IP-адреса в заголовок X-Forwarded-For:

curl -H "X-Forwarded-For: 12.12.12.12,15.15.15.15,4.4.4.4" http://www.google.com

Представьте, что IP-адрес этой машины был 9.9.9.9, и что он должен был проходить через прокси (например, прокси-сервер университета). Допустим, у прокси был IP 2.2.2.2. Предполагая, что он не настроен на раздевание X-Forwarded-For Заголовки (которые, скорее всего, не будут), он просто прикрепит IP 9.9.9.9 к концу списка и передаст запрос в Google. На этом этапе заголовок будет выглядеть так:

X-Forwarded-For: 12.12.12.12,15.15.15.15,4.4.4.4,9.9.9.9

Затем этот запрос пройдет через конечную точку Google, которая добавит IP-адрес университетского прокси-сервера 2.2.2.2, поэтому заголовок в журналах Google будет выглядеть следующим образом:

X-Forwarded-For: 12.12.12.12,15.15.15.15,4.4.4.4,9.9.9.9,2.2.2.2

Итак, какой IP-адрес клиента? Это невозможно сказать с точки зрения Google. На самом деле IP-адрес клиента - 9.9.9.9. Последний из перечисленных IP-адресов - 2.2.2.2, а первый - 12.12.12.12. Все, что Google знал бы, - это то, что IP 2.2.2.2 определенно правильный, потому что это был IP, который фактически подключался к их сервису - но они не знали бы, был ли это первоначальный клиент для запроса или нет из доступных данных. Точно так же, когда в этом заголовке есть только один IP-адрес - это IP-адрес, напрямую связанный с нашим сервисом, поэтому мы знаем, что он надежный.

С практической точки зрения этот IP, скорее всего, будет надежным большую часть времени (потому что большинство людей не будут пытаться подделать свой IP). К сожалению, такого рода спуфинг невозможно предотвратить, и к тому времени, когда запрос поступит на маршрутизатор Heroku, мы не сможем определить, находятся ли IP-адреса в X-Forwarded-For цепь была подделана или нет.

За исключением всех вопросов надежности, эти цепочки IP-адресов всегда должны читаться слева направо. IP-адрес клиента всегда должен быть крайним левым.

Вы никогда не можете доверять любой информации, поступающей от клиента. Это больше вопрос того, кому ты доверяешь и как ты это проверяешь. Даже Героку может оказать влияние, чтобы обеспечить плохой HTTP_X_FORWARDED_FOR значение, если в их коде есть ошибка, или они каким-то образом взломаны. Другим вариантом будет другой компьютер Heroku, который будет подключаться к вашему серверу и вообще обходить свой прокси при фальсификации. REMOTE_ADDR и / или HTTP_X_FORWARDED_FOR,

Лучший ответ здесь будет зависеть от того, что вы пытаетесь сделать. Если вы пытаетесь проверить своих клиентов, сертификат на стороне клиента может быть более подходящим решением. Если все, что вам нужно для IP-адреса, это географическое местоположение, то доверие к данным может быть достаточно хорошим. В худшем случае, кто-то подделает местоположение и получит неправильный контент... Если у вас другой вариант использования, между этими двумя крайностями может быть много других решений.

Если я делаю запрос с несколькими заголовками:curl -s -v -H "X-Forwarded-For: 1.1.1.1, 1.1.1.2, 1.1.1.3" -H "X-Forwarded-For: 2.2.2.2" -H "X-Forwarded-For: 3.3.3.3" https://foo.herokuapp.com/

      > X-Forwarded-For: 1.1.1.1, 1.1.1.2, 1.1.1.3
> X-Forwarded-For: 2.2.2.2
> X-Forwarded-For: 3.3.3.3

The X-Forwarded-Forзаголовок, переданный приложению, будет:

      1.1.1.1, 1.1.1.2, 1.1.1.3, <real client IP>, 2.2.2.2, 3.3.3.3

поэтому выбор последнего из этого списка не выдерживает :/

Другие вопросы по тегам