⬆️ ⬇️

Hormonal holywar Admin and Development PHP or REMOTE_ADDR vs HTTP_X_FORWARDED_FOR

Recently I witnessed one interesting dispute about how really you need to determine the IP address of the end user from PHP scripts.

Actually, each word sabzh displays the actual situation. It was a religious debate, aggravated by spring wonderful weather, in which, I believe, there were no right and wrong, but which led me to a mini-study and, to my luck, put an end to understanding this confessional but in fact a very simple question.

For those who, like me, I was sure that I understood everything, but I was afraid to ask if I was too lazy to understand the little things - under the cat.





Prehistory



Being engaged in the development of VOD service for Samsung SmartTV platform, we certainly need to know the user's country, so that inadvertently not showing a happy user a movie where the copyright holder prohibits ... missteps).

[The question, as noted in the comments, Legal, and fraud is possible, but the article is not even about how to try to prevent such fraud, but about how to make friends with php and nginx]



On the server we have the following: php-fpm + nginx

')

How to determine the country? Well, of course through the user's IP and GEO IP database maxmind

"Pfff ...." - it seemed to all of us - yes, simpler than simple. And in order not to write my bike, google stackoverflow , even penetrated into each line, screwed it and left it there as the code grew:



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; } 




And everything worked! Almost a year ... until something unexpected happened. Naturally unexpected for this code ...



How to confuse php or proxy chain (still part of history)



It broke! And this happened when we had to screw one of the payment systems and all this code collapsed because in HTTP_X_FORWARDED_FOR not one address came, but a comma-separated list of addresses (which is strictly speaking legal, acceptable, and not even regulated in a php dock )

And no one would have noticed if HTTP_X_REAL_IP or HTTP_CLIENT_IP (which is also not regulated by the dock) contained the IP you were looking for, but alas, they were empty :(



“Well, okay” - we thought (now I was no longer alone), we would rewrite everything and ask the admins to push the user IP into the variable REMOTE_ADDR:



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




And everything worked! Almost a month ... until something unexpected happened. Naturally unexpected for this code ...



Spring dispute tough men (this is not irony - they are cool)



It broke! This happened because we had to update nginx. And we turned to the professionals in this business - to our admins.

And those, in turn, decided to update the config and get rid of our “crutch / not crutch” (until we understood this) with a forwarding to REMOTE_ADDR.



REMOTE_ADDR left unchanged i. there now shone something like "127.0.0.1"

in HTTP_X_FORWARDED_FOR, the user's IP was skipped (which, in the meantime, was easily overridden by sending the header `x-forwarded-for: 999.999.999.999` from the browser)

And then it started - P = Developed, A = Admin:



A: you have broken everything, and since we have a nginx proxy, then the address you need is in HTTP_X_FORWARDED_FOR and in REMOTE_ADDR there will be a real client IP address to php-fpm (ie, 127.0.0.1)

R: but we cannot believe HTTP_X_FORWARDED_FOR, because this is a variable that can be easily redefined via the header to the server, referring to a very interesting article

A: No, we will do so that it will contain the real IP of the end user, and in REMOTE_ADDR the real client address to php

R: then we don’t follow the sequence of proxies, and still for universalization on another server (say, no proxy) these configs may not be true, push everything into REMOTE_ADDR which will work in any case.



... it is brief and without mats ...



As a result, of course, everything got started ... and we stopped at transparent proxying, when php thinks that clients connect to it directly without any proxies and all variables (or rather, the one to which we pay attention) are in the state we need.

However, there is not enough feng shui in this matter, and in fact we have a proxy or maybe not one.



Who is to blame of them who is right



Judge us, but no one!



If we really have a lot of clients directly to php, or transparent proxying, then everything is simple - use REMOTE_ADDR for health and enjoy.



But what about the Feng Shui and where should it be if we use normal proxying and want PHP to know about it?



The recipe ... but not a panacea:





In custody



There are several proxying options for php + nginx





PS

Later we will set up Feng Shui in the settings and get rid of transparent proxying, as well as write the universal function of determining IP for both cases of proxying.



Pps

For fun, who cares: if someone in the comments writes this function and the nginx config for us and we use it, then at fair word, he will get 100r on the phone.

But this function and the config should be truly Orthodox and take into account everything :) all the clues are in the article.

The main thing is Zen: take your time - suddenly the first ones will write with errors and you will take them into account, take your time - suddenly the first correct answer will be up to you.



Thanks to all. Have a nice spring! Negotiate with colleagues and love them! :)



UDP:

Own implementation:

  /** * @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; } 

Source: https://habr.com/ru/post/177113/



All Articles