Пользователь
20,8
рейтинг
18 апреля 2013 в 10:47

Разработка → Гормональный holywar Админа и Разраба PHP или REMOTE_ADDR vs HTTP_X_FORWARDED_FOR recovery mode

Давеча был свидетелем одного интересного спора о том как же действительно нужно определять IP адрес конечного пользователя из скриптов PHP.
Собственно, каждое слово сабжа отображает действительную ситуацию. Это был религиозный спор, обострённый весенней замечательной погодой, в котором, я считаю, не оказалось правых и не правых, но который побудил меня к мини-исследованию и, к моему счастью, поставил точку в понимании этого конфессионального но по факту очень простого вопроса.
Для тех, кто как и я сомневался был уверен, что во всём разобрался, но боялся спросить лень было разбираться в мелочах — под кат.


Предыстория


Занимаясь разработкой VOD сервиса для Samsung SmartTV платформы нам непременно нужно знать страну пользователя, чтобы вдруг нечаянно не показать счастливому пользователю фильм там, где запрещает правообладатель… А ведь за нарушение данного условия договора идут не детские штрафы в тысячах долларов (при чем за каждый факт такой оплошности).
[Вопрос, как заметили в комментариях, Юридический, и мошенничество возможно, но статья даже не о том как постараться предотвратить такие мошенничества, а о том как правильно подружить php и nginx]

На сервере имеем следующее: php-fpm+nginx

Как определить страну? Ну естественно через IP пользователя и GEO IP базу maxmind
«Пффф....» — подумалось нам всем мне — да проще простого. И дабы не писать свой велосипед, нагуглил на stackoverflow, даже вник в каждую строчку, прикрутил и оставил как там и росло код:

    public function getUserHostAddress(){
        if (!empty($_SERVER['HTTP_X_REAL_IP']))   //check ip from share internet
        {
            $ip=$_SERVER['HTTP_X_REAL_IP'];
        }
        elseif (!empty($_SERVER['HTTP_CLIENT_IP']))   //check ip from share internet
        {
            $ip=$_SERVER['HTTP_CLIENT_IP'];
        }
        elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR']))   //to check ip is pass from proxy
        {
            $ip=$_SERVER['HTTP_X_FORWARDED_FOR'];
        }
        else
        {
            $ip=$_SERVER['REMOTE_ADDR'];
        }
        return $ip;
    }


И всё работало! Почти год… пока не случилось кое-что неожиданное. Естественно неожиданное для этого кода…

Как запутать php или цепочка прокси(всё ещё часть предыстории)


Всё сломалось! А случилось это когда нам пришлось прикручивать одну из платёжных систем и весь этот код рухнул от того, что в HTTP_X_FORWARDED_FOR пришёл не один адрес, а список адресов через запятую (что строго говоря законно, допустимо, и даже не регламентировано в доке по php)
И никто бы ничего не заметил, если бы HTTP_X_REAL_IP или HTTP_CLIENT_IP(которые тоже не регламентирован докой) содержали искомый IP, но увы они были пусты :(

«Ну ладно» — подумали мы(теперь я был уже не один) перепишем всё и попросим админов запихивать пользовательский IP в переменную REMOTE_ADDR:

    public function getUserHostAddress(){
        $ip=$_SERVER['REMOTE_ADDR'];
        return $ip;
    }


И всё работало! Почти месяц… пока не случилось кое-что неожиданное. Естественно неожиданное для этого кода…

Весенний спор крутых мужиков(это не ирония — они крутые)


Всё сломалось! А случилось это потому, что нам нужно было обновить nginx. И мы обратились к профессионалам в этом деле — к нашим админам.
А те в свою очередь решили обновить и конфиг избавившись от нашего «костыля/не костыля» (пока мы этого не поняли) с пробросом в REMOTE_ADDR.

REMOTE_ADDR оставили без изменения т.е. там теперь светилось что-то типа «127.0.0.1»
в HTTP_X_FORWARDED_FOR прокинули IP пользователя (который между делом с лёгкостью удалось переопределить отправкой из браузера заголовка `x-forwarded-for: 999.999.999.999`)
И тут понеслось — Р=Разраб, А=Админ:

А: у вас всё сломалось, и поскольку мы имеем nginx-прокси то нужный вам адрес лежит в HTTP_X_FORWARDED_FOR а в REMOTE_ADDR будет лежать реальный IP сдресс клиента к php-fpm (т.е. 127.0.0.1)
Р: но мы не можем верить HTTP_X_FORWARDED_FOR, ведь это переменная, которую с лёгкостью можно переопределить через заголовок к серверу, ссылаясь на давольно интересную статью
А: нет, мы сделаем так что в ней будет лежать реальный IP конечного пользователя, а в REMOTE_ADDR реальный адрес клиента к php
Р: тогда мы не проследим последовательность проксей, и всё равно для универсализации на другом сервере (скажем без прокси) эти конфиги могут быть не правдивыми пихайте всё в REMOTE_ADDR который в любом случае будет работать.

… это кратко и без матов…

По итогу то конечно всё завелось… и остановились на прозрачном проксировании, когда php думает, что к нему подключаются напрямую клиенты безо всяких проксей и все переменные(точнее одна на которую мы обращаем внимание) в нужном нам состоянии.
Однако не хватает фэншуя в этом деле и по факту у нас ведь есть прокся а может и не одна.

Кто виноват из них кто прав


Судить не нам, но никто!

Если мы имеем действительно кучу клиентов напрямую к php, или прозрачное проксирование то всё просто — юзай REMOTE_ADDR на здоровье и наслаждайся.

Но как быть с фэншуем и где что должно лежать, если мы используем нормальное проксирование и хотим чтобы об этом знал PHP?

Рецепт… но не панацея:


  • REMOTE_ADDR — содержит IP адрес непосредственно обращающегося к нему nginx, в нашем случае 127.0.0.1
  • HTTP_X_FORWARDED_FOR — содержит цепочку прокси адресов и последним идёт IP непосредственного клиента обратившегося к прокси серверу. И тут рассмотрим два частных случая:

    • Не каскадное проксирование. В HTTP_X_FORWARDED_FOR последним или единственным IP адресом (в зависимости от того что прислал/не прислал пользователь в заголовке x-forwarded-for) будет реальный, искомый, тот самый адрес пользователя.

      Казалось бы ну в чем проблема парсить эту переменную и доставать оттуда последний элемент. Но в нашем случае настройки не были до конца корректными и весь HTTP_X_FORWARDED_FOR заменялся заголовком от браузера x-forwarded-for, а должен был приклеивать к нему реальный IP непосредственного пользователя.

      Для примера проверил на промышленном vps хостинге:

      Доверять таким данным тоже страшновато, но если всё правильно сделано в настройках то последним IP будет адресс пользователя, вне зависимости от того что придёт в заголовках.

    • Каскадное проксирование. В этом случае действительно HTTP_X_FORWARDED_FOR — содержит цепочку прокси адресов и последним идёт IP непосредственного клиента обратившегося к прокси серверу. Но это не реальный IP пользователя, а всего лишь IP предыдущей прокси в списке.

      Казалось бы ну в чем проблема парсить эту переменную и доставать оттуда первый элемент. Но как было показано выше на рисунке, это уж точно не корректные данные и пользователь может нас ввести в заблуждение в два счёта, прислав в x-forwarded-for первым элементом какой захочет IP

  • HTTP_X_REAL_IP (или любая другая переменная на которую договорятся Админ и Разраб) — содержит IP обращающегося к php пользователя или первой от сервера недоверенной прокси (что для нас равно адресу клиента)

    Для удобства можно использовать специальный модуль для nginx который нивелирует проблемы определения каскадного и не каскадного проксирования, но он по умолчанию «в стандартных сборках центоса, дебика и федоры nginx идет, почему-то без параметра --with-http_realip_module»(с)Админ, а так же для него должен быть корректно сформированна цепочка в HTTP_X_FORWARDED_FOR и настроены адреса доверенных прокси серверов от которых мы можем брать последний элемент из HTTP_X_FORWARDED_FOR

    Однако опять же HTTP_X_REAL_IP это не реальный в общем случае IP конечного пользователя, а лишь первый IP в списке проксей при каскадном проксировании.
    Хотя если проксирование не каскадное, то там может лежать и адрес конечного пользователя.
    А если проксирование каскадное и корректно настроен модуль http_realip то там должен лежать либо IP конечного пользователя либо корректный IP первой недоверенной прокси если считать от php-сервера, что для нас тоже сгодится
  • HTTP_CLIENT_IP (или любая другая переменная на которую договорятся Админ и Разраб) — содержит при любом типе проксирования первый IP из HTTP_X_FORWARDED_FOR, а при отсутствии проксирования содержимое http заголовка client-ip. Который можно использовать только для справки. И ни в коем случае не для определения реального IP пользователя.

В заключении


Есть несколько вариантов проксирования для php+nginx
  • Прозрачное — характерно неизменное содержание переменных в _SERVER (в том числе и REMOTE_ADDR) как если бы мы работали напрямую с php
  • Не прозрачное не каскадное — характерно то, что Админу и Разрабу нужно договориться, где будет храниться реальные IP адрес пользователя :)
  • Не прозрачное каскадное — характерно то же самое что и для не прозрачного не каскадного + правильно настроенный модуль для nginx А также необходимо помнить о возможности каскадного проксирования и о том, что пользователь злой и может присылать в _SERVER[«HTTP_xxxx»] очень неправдивые данные


PS
Позже мы наведём фэншуй в настройках и избавимся от прозрачного проксирования, а так же напишем универсальную функцию определения IP для обоих случаев проксирования.

PPS
Ради фана кому интересно: если кто-то в комментариях напишет эту функцию и конфиг nginx за нас и мы её будем использовать, то под честное слово, тот получит 100р на телефон.
Но эта функция и конфиг должны быть во истину православными и учитывать всё :) все зацепки есть в статье.
Главное — дзен: не торопитесь — вдруг первые напишут с ошибками и вы их учтёте, торопитесь — вдруг первый правильный ответ будет до Вас.

Всем спасибо. Хорошей весны! Договаривайтесь с коллегами и любите их! :)

UDP:
Своя реализация:
 /**
     * @param null|string $ip_param_name - ключ элемента _SERVER, в котором нужно искать IP адрес
     *          если не задано ищем по индексу REMOTE_ADDR и считаем что проксирование отсутствует или прозрачное,
     *          если задано считаем что IP пробрасывается по заданному индексу, 
     *              например по индексу HTTP_X_REAL_IP или любому другому
     * @param bool $allow_non_trusted - защита, при заданном $ip_param_name но 
     *              отсутствующем или не валидном значении _SERVER[$ip_param_name]
     *          если задано будем искать в _SERVER по ключам из аргумента $non_trusted_param_names
     * @param array $non_trusted_param_names - массив ключей, по которым будем искать IP в массиве _SERVER
     * @throws Exception
     * @return string
     */
    public function getUserHostAddress(
        $ip_param_name = null,
        $allow_non_trusted = false,
        array $non_trusted_param_names = array('HTTP_X_REAL_IP','HTTP_CLIENT_IP','HTTP_X_FORWARDED_FOR','REMOTE_ADDR')
    ){

    	if(empty($ip_param_name) || !is_string($ip_param_name)){ 
    	// если не задан или не корректен
            $ip = $_SERVER['REMOTE_ADDR'];
        }else{ 
        //иначе используем нужную переменную
            if(!empty($_SERVER[$ip_param_name]) && filter_var($_SERVER[$ip_param_name], FILTER_VALIDATE_IP)){ 
            // если переменная подошла как надо
                $ip = $_SERVER[$ip_param_name];
            }else if($allow_non_trusted){ 
            // мы решили пойти на крайний шаг и использовать сырые данные
                foreach($non_trusted_param_names as $ip_param_name_nt){
                    if($ip_param_name === $ip_param_name_nt) 
                    // мы уже проверяли эту переменную
                        continue;
                    if(!empty($_SERVER[$ip_param_name_nt]) && filter_var($_SERVER[$ip_param_name_nt], FILTER_VALIDATE_IP)){ 
                    // если переменная подошла как надо
                        $ip = $_SERVER[$ip_param_name_nt];
                        break;
                    }
                }
            }
        }
        if(empty($ip)) 
        // так и не нашли подходящих ip, хотя по умолчанию в $_SERVER['REMOTE_ADDR'] что-то должно лежать
            throw new Exception("Can't detect IP");

        return $ip;

    }
Dmitriy Paunin @ZmeeeD
карма
17,0
рейтинг 20,8
Пользователь
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (48)

  • +7
    Вот что случается, когда технари берутся решать юридические вопросы.
    По порядку:
    1) любой способ установить страну пользователя в протоколе http по встроенным в него механизмам — это неправильно. Используется это только как предположение возможной страны для удобства в вебинтерфейсах (предложить пользователю сразу нужный язык сайта).
    2) использовать полученную таким образом региональную принадлежность для ограничения доступа — неправильно. Все можно подделать и на практике подделывают.
    3) с самого начала нужно было разобраться вашему юристу в вопросе штрафов за факты показа контента в не тех регионах. Единственный способ работать по такой схеме — это указать в договоре что техническими средствами в сети интернет однозначно и безошибочно определить страну клиента невозможно, и все технические ограничения не являются 100% превентивными мерами. И нести финансовую ответственность за случаи нерегулированного показа контента разработчик не может.
    • 0
      Ваш посыл не в ту сторону, понятно что можно обмануть… и обманывают… и анонимные прокси используют в интернетах… но мыдолжны решать вопросы последней мили. вот об этом и идёт речь.
      • +4
        Мой посыл как раз в том что вы решаете этот вопрос кардинально неправильно, или вообще решать не должны, раз нет такой технической возможности.
        • +1
          Ага… объясните это правообладателям :)
          Вероятно 100% в договорах есть сноска о мошенничестве и прочем и как вы сказали «и все технические ограничения не являются 100% превентивными мерами» НО они обязаны быть!

          PS
          Информационная безопасность не гарантирует неавторизованный доступ, а лишь усложняет его.
          • НЛО прилетело и опубликовало эту надпись здесь
            • 0
              эммм… обычные пользователи даже не знают что такое «прокси»?)
              как же мы это запретим?
              • 0
                первый знак вопроса лишний.
              • НЛО прилетело и опубликовало эту надпись здесь
            • 0
              Не правда. А как же офисные работники, которых анально ограничивают «продвинутые» шефы, пуская интернет через локальный прокси? Или в общаге, или даже дома, если компов много, а интернет монополизирован верховным жильцом? По-моему, в мохнатых годах, это практиковалось даже у провайдеров! А может быть и по сей день практикуется в каких-нибудь деревнях, где интернет раздается на 3 компа.
  • +9
    Хочу держать всех в курсе и заявить: я всей душой ненавижу слово «разраб».
    • 0
      Субъективно. Я являясь таковым и не боюсь его. Просто отношусь к нему как к сокращению (админ, ОС, и тд)
    • 0
      Хорошая профессия, что вы так?
      • +3
        Не профессия. :) Именно слово «разраб». Не знаю почему, но коробит всякий раз, как слышу или вижу.

        Есть в слове какое-то пренебрежение, что ли.

        Как со словом «человечек». Вроде бы слово, как слово. А фигня какая-то. :)
        • 0
          У нас в конторе бытует еще прекрасное слово «прилага» (в смысле — приложение).
          Тоже вздрагиваю на каждом совещании.
  • 0
    тыц phpfaq.ru/ip
    Админ связку nginx+apache настроить не может нормально.
    Собственно вот готовое решение
    www.linux.org.ru/forum/admin/7441791

    Должны совпадать IP в RPAFproxy_ips и в интерфейсах, которые слушает апач — тут лучше везде ставить строго 127...1
    Если вам еще нужен X-Forwarded-For то просто в конфигах юзаются X-Real-IP или любой другой хедер

    блин, у вас fpm
    fastcgi_param SERVER_ADDR $server_addr;
    и все, извращенци
    • 0
      Вы вообще прочитали статью? ссылка на «тыц» есть по тексту

      Из решения:
      > proxy_set_header X-Real-IP $remote_addr;
      > proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

      Не подходит для каскадного проксирования — посмотрите в модуль для nginx nginx.org/ru/docs/http/ngx_http_realip_module.html
      • +1
        Разумеется я не заметил что там nginx+fpm потому что в данной конфигурации вообще проблем с IP не возникает при стандартной настройке. И конечно nginx айпишник php — значит проксирование, значит наверное на апач, а потом уже как запостил посмотрел и понял что вы вообще там упоролись!

        У вас nginx+php-fpm. Тогда извиняюсь — админ не может настроить nginx в дефолтной конфигурации, когда все работает.
        Да, я там еще ошибся — не ту строку скопировал
        fastcgi_param REMOTE_ADDR $remote_addr;
        это вообще стандартная конфигурация nginx при настройке fast-cgi
        лежит в /etc/nginx/fastcgi_params

        То-есть вместо того чтобы прозрачно передать заголовок X-Forwarded-For, раз он им нужен чтобы прокси отсекать
        fastcgi_param X-Forwarded-For $proxy_add_x_forwarded_for;

        (http://nginx.org/ru/docs/http/ngx_http_proxy_module.html)
        они тут городят фиг знает что.
        Хотя вроде прокси разбирать не нужно — поэтому вопрос — почему не устроила самая стандартная конфигурация
        fastcgi_pass unix:/tmp/php-fpm.socket; (или порт)
        include fastcgi_params;

        Блин, армянский комсомол!

        Тут вообще нету каскадного проксирования — каскадное это если было бы два нгинкса или nginx + апач а тут обычный fastcgi которому хедеры не выставили.
        • –1
          > и понял что вы вообще там упоролись
          Это да! :)

          Но и вы не уловили суть… либо я не понял что вы пытаетесь донести в этом наборе буквослов.
  • 0
    Можно проверять ВСЕ ip адреса на принадлежность к региону за исключением локальных. Если хотя бы один из них из неугодного региона, то фильтровать.
    И не будет никакой заморочки первый, последний ip в цепочке или какую там переменную использовать…
    • 0
      Откуда проверять? абстрактно написали :) тут же говорю религиозный бой и дьявол в мелочах!
      • 0
        Ну есть у вас куча разных переменных HTTP_X_REAL_IP, HTTP_CLIENT_IP, REMOTE_ADDR и HTTP_X_FORWARDED_FOR. Все эти адреса слить в один массив. Что в нём может быть? 1 — адрес самого сервера, 2 — адреса прокси серверов, 3 — фактический адрес клиента, 4 — необнаружаемая подделка адреса клиента.
        Если задействован пункт 4, то тут эти средства бессильны и в принципе нужны другие средства защиты.
        Адреса из пункта 1 вы знаете (по крайней мере админ), так что ставим на них условие при проходе через массив и игнорируем.
        Остаются адреса проксей и реальный адрес. Ставим условие, что все они должны быть в заданном регионе.
        PROFIT

        Разве нет?
        • 0
          да если кто-то хочет обмануть — обманит.
          Решение забавное но не элегантное.
          Рассматриваем позитивный случай когда у нас там всё корректно.
          Как быть со списком корректных доверенных каскадных прокси-серверов? среди них может быть прокся из «левого» региона… или несколько… или все.
          • 0
            Это какие например доверенные прокси?
            Если их так много, то да, мой подход не подойдёт. Если же их на самом деле не много, то тоже можно составить список и поставить в игнор.

            ЗЫ. И часто ли вы вместе употребляете слова PHP и «элегантно»?...
            • 0
              Можно, но вот появилась еще одна прокся… или у какой-то сменился адрес, и что? переписывать код? тут всё же важную роль играет правильно настроенные nginx и врядли хочется в коде держать переменные IP адреса.

              ЗЫ. Дело не в PHP. На его месте в общем случае мог бы быть и другой язык. Хоть брейнфак :) Это не важно
              • 0
                ok ok
                • 0
                  весна :) просто я скину конечно что получилось, но позже.
  • 0
    Напишите плиз окончательную рабочую функцию?
    • 0
      > весна :) просто я скину конечно что получилось, но позже.
      функция будет не хитрой, как я сказал, вся суть в симбиозе с nginx конфигой
      • 0
        Напишите, а то мы тут тоже боремся… :) Желательно не на пыхе, а на псевдокоде.
        • 0
          Добавил :)
    • 0
      собственно в теле статьи.
  • +3
    Вот почему хорошо пользоваться готовыми решениями и знать, что и как они умеют.
    Symfony Request
         const HEADER_CLIENT_IP = 'client_ip';
         protected static $trustedHeaders = array(
            self::HEADER_CLIENT_IP    => 'X_FORWARDED_FOR',
            self::HEADER_CLIENT_HOST  => 'X_FORWARDED_HOST',
            self::HEADER_CLIENT_PROTO => 'X_FORWARDED_PROTO',
            self::HEADER_CLIENT_PORT  => 'X_FORWARDED_PORT',
        );
        public function getClientIp()
        {
            $ip = $this->server->get('REMOTE_ADDR');
    
            if (!self::$trustProxy) {
                return $ip;
            }
    
            if (!self::$trustedHeaders[self::HEADER_CLIENT_IP] || !$this->headers->has(self::$trustedHeaders[self::HEADER_CLIENT_IP])) {
                return $ip;
            }
    
            $clientIps = array_map('trim', explode(',', $this->headers->get(self::$trustedHeaders[self::HEADER_CLIENT_IP])));
            $clientIps[] = $ip;
    
            $trustedProxies = self::$trustProxy && !self::$trustedProxies ? array($ip) : self::$trustedProxies;
            $clientIps = array_diff($clientIps, $trustedProxies);
    
            return array_pop($clientIps);
        }
    
    • 0
      вот… это уже хорошо, по моему первый проникшийся :) не знал о таком — спасибо.
      свою реализацию дописываю!
    • 0
      PS: и симфони молодцы! не то что Yii из-за чего мы, как написал товарищ iborzenkov, «упоролись»:

      public function getUserHostAddress()
      {
      return isset($_SERVER['REMOTE_ADDR'])?$_SERVER['REMOTE_ADDR']:'127.0.0.1';
      }
      • 0
        Симфони то молодцы потому что им нужно работать на всех конфигурациях — а серваки админы могут и похлеще настраивать на шаред хостингах.
        Но у вас то есть доступ к конфигам сервера и меня интересует как раз почему вы не использовали именно стандартную конфигурацию, которая вам подходит, и причем та-же симфони юзает
        !in_array(@$_SERVER['REMOTE_ADDR'], array('127.0.0.1', 'fe80::1', '::1'))
        и кстати замечу работает стандартно

        location ~ ^/(app|app_dev)\.php(/|$) {
        fastcgi_pass unix:/home/ivan/php-fpm.socket;
        fastcgi_split_path_info ^(.+\.php)(/.*)$;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        }

        Зачем криво настроенный nginx чинить на стороне php если есть доступ к конфигам nginx и можно нормально передавать заголовок.
        Вот именно поэтому я и написал выше то что написал.
        • 0
          В Symfony2 такое продуманное решение вовсе не из-за упоротых админов шаред хостингов, а из-за уймы ситуаций с проксями, балансерами и т.д.
          Ваша конфигурация сломается об любую из них.

          !in_array(@$_SERVER['REMOTE_ADDR'], array('127.0.0.1', 'fe80::1', '::1'))
          используется исключительно только в app_dev.php, которому не место на продакшне.
          • 0
            Разумеется проверка на 127.0.0.1 используется только в dev окружении.
            Но это не повод настраивать сервак криво, а потом решать это на стороне php.
            Сломается оно только на кривую конфигурацию, а не на любую, и правильно сделает что сломается.
            Собственно тут все правильно уже сказали habrahabr.ru/post/177113/#comment_6152675
            • 0
              сломается всё — если постараться!
              Но представьте две ситуации будто вы попадаете в аварию на Оке и на Феррари.
              Чувствую что товарищи из феррари не смотря на кривизну дорог в россии делают машины так, чтобы вы выжили при любом раскладе,
              а вот товарищи выпускающие Оку рассуждают как Вы — «плохие дороги? — неее… это не повод делать подвеску которая останется вне салона при наезде на кочку»

              Каждому своё — я же не настаиваю на использовании этого рецепта, он может и отравить вас :)
            • 0
              Глупый спор.
              Haproxy, Varnish, ELB, Cloudfront. Все это может и быть и не быть на 127.0.0.1, может быть и одно и не одно. етц.
  • +5
    Эта статья неграмотна от первого слова до последнего. Вместо того, чтобы гадать на кофейной гуще, и определять назначение HTTP-заголовков через анализатор трафика, надо читать спецификации и руководства. Вместо того, чтобы разобраться в проблеме, автор скопипастил код какого-то индуса, он у него сломался, и теперь автор предлагает всем так же бездумно скопипастить его код аналогичного качества.

    > и весь этот код рухнул от того, что в HTTP_X_FORWARDED_FOR пришёл не один адрес, а список адресов через запятую (что строго говоря законно, допустимо, и даже не регламентировано в доке по php

    Искать описание HTTP-заголовков в мануале по PHP? Автор, вы про HTTP и про то, как работает сервер, хотя бы в википедии почитайте что ли.

    Ваши администраторы, впрочем, не намного лучше разработчиков, если они не могут нгинкс настроить или модуль к нему скомпилировать.

    Насчет определения страны — естественно, заголовкам, пришедним снаружи, доверять нельзя, в них может быть что угодно. например, расширение для браузера може подставлять любой X-Http-Forwarded-For. Потому стоит их игнорировать. Однако, если наша цель — банить по IP, то брать IP из этих заголовков вполне можно.

    Логично было бы собирать все IP (а не один) из всех заголовков и если хоть один относится к запрещенной стране — банить запрос. Таким образом, люди использующие неанонимные прокси, останутся без видео.

    • –2
      Рассмешили… да все мы индусы, всё ведь относительно и качество в том числе :)

      > Однако, если наша цель — банить по IP, то брать IP из этих заголовков вполне можно.
      Цель вы не поняли. Наша цель максимально точно определить IP без возможности фэйка в заголовках… а уж там банить или не банить — вопрос номер два.

      > Логично было бы собирать все IP (а не один) из всех заголовков и если хоть один относится к запрещенной стране — банить запрос
      Этот вариант как бы отсекли вот здесь habrahabr.ru/post/177113/#comment_6150229

      > Искать описание HTTP-заголовков в мануале по PHP? Автор, вы про HTTP и про то, как работает сервер, хотя бы в википедии почитайте что ли.
      Но часть из них то там описана… разве нет? однако сложно не согласиться — это стезя не php)

      PS
      Мне кажется критика подразумевает первичное принятия позиции а потом её разбор, а вы как то с места в карьер…
  • 0
    Спрашивается, почему не использовали стандартный geoip модуль nginx-а, зачем было на php всё городить вообще?
    • 0
      По той же причине, что и realip модуль — большинство пакетов в репозиториях не собраны с его поддержкой
      • 0
        Зачем использовать пакеты из дистрибутива, которые, как правило, устарели, содержат больше известных багов и уязвимостей, и, к тому же, не имеют, как вы написали, нужных модулей? Есть же на официальном сайте репозитории для Ubuntu, Debian, CentOS, RHEL, и даже для Debian ARM.

        Я уж не упоминаю о том, что чтобы сделать свой кастомный пакет с нужным набором модулей, нужно потратить максимум минут 15, если раньше никогда не делал. Автор топика не захотел собрать нужный модуль и в сумме потратил, вероятно, на два порядка больше времени.

        Проблема с пакетами — высосана из пальца.
    • 0
      ну он и используется… а как вы думаете он IP получает для представления в страну?
      Дело не в пакетах и даже не в сборках… изучите www.php.net/geoip
    • 0
      а вы… про nginx nginx.org/ru/docs/http/ngx_http_geoip_module.html так это без разницы, но решили делать на php, поскольку там много нюансов переопределения IP (аля hosts) и прочего.
      Но сути не меняет… там тоже вычисляется ip настройками nginx и x-forwaeded-for
  • 0
    И дабы не писать свой велосипед, нагуглил на stackoverflow
    Примерно такой же код использовал когда-то PhpMyAdmin (пруф). Но как по мне, этот код просто ужасен. Поэтому я в свое время все-таки написал свой велосипед https://gist.github.com/AgelxNash/4943199.
    • 0
      Здесь ужасен не код, а подход.
      Все эти кульбиты с НТТР заголовками имеют смысла чуть менее, чем нисколько.
      Прочтите статью до конца.
      Прочтите ссылку на phpfaq.ru, что дана выше.
      Прочтите, наконец, статью в стиле «Скандалы, интриги, расследования», если простые объяснения не помогают: habrahabr.ru/post/158417/

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.