How to use HTTP_X_FORWARDED_FOR properly?

131,017

Solution 1

You can use this function to get proper client IP:

public function getClientIP(){       
     if (array_key_exists('HTTP_X_FORWARDED_FOR', $_SERVER)){
            return  $_SERVER["HTTP_X_FORWARDED_FOR"];  
     }else if (array_key_exists('REMOTE_ADDR', $_SERVER)) { 
            return $_SERVER["REMOTE_ADDR"]; 
     }else if (array_key_exists('HTTP_CLIENT_IP', $_SERVER)) {
            return $_SERVER["HTTP_CLIENT_IP"]; 
     } 

     return '';
}

Solution 2

I like Hrishikesh's answer, to which I only have this to add...because we saw a comma-delimited string coming across when multiple proxies along the way were used, we found it necessary to add an explode and grab the final value, like this:

$IParray=array_values(array_filter(explode(',',$_SERVER['HTTP_X_FORWARDED_FOR'])));
return reset($IParray);

the array_filter is in there to remove empty entries.

Solution 3

In the light of the latest httpoxy vulnerabilities, there is really a need for a full example, how to use HTTP_X_FORWARDED_FOR properly.

So here is an example written in PHP, how to detect a client IP address, if you know that client may be behind a proxy and you know this proxy can be trusted. If you don't known any trusted proxies, just use REMOTE_ADDR

<?php

function get_client_ip ()
{
    // Nothing to do without any reliable information
    if (!isset ($_SERVER['REMOTE_ADDR'])) {
        return NULL;
    }

    // Header that is used by the trusted proxy to refer to
    // the original IP
    $proxy_header = "HTTP_X_FORWARDED_FOR";

    // List of all the proxies that are known to handle 'proxy_header'
    // in known, safe manner
    $trusted_proxies = array ("2001:db8::1", "192.168.50.1");

    if (in_array ($_SERVER['REMOTE_ADDR'], $trusted_proxies)) {

        // Get the IP address of the client behind trusted proxy
        if (array_key_exists ($proxy_header, $_SERVER)) {

            // Header can contain multiple IP-s of proxies that are passed through.
            // Only the IP added by the last proxy (last IP in the list) can be trusted.
            $proxy_list = explode (",", $_SERVER[$proxy_header]);
            $client_ip = trim (end ($proxy_list));

            // Validate just in case
            if (filter_var ($client_ip, FILTER_VALIDATE_IP)) {
                return $client_ip;
            } else {
                // Validation failed - beat the guy who configured the proxy or
                // the guy who created the trusted proxy list?
                // TODO: some error handling to notify about the need of punishment
            }
        }
    }

    // In all other cases, REMOTE_ADDR is the ONLY IP we can trust.
    return $_SERVER['REMOTE_ADDR'];
}

print get_client_ip ();

?>

Solution 4

You can also solve this problem via Apache configuration using mod_remoteip, by adding the following to a conf.d file:

RemoteIPHeader X-Forwarded-For
RemoteIPInternalProxy 172.16.0.0/12
LogFormat "%a %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
Share:
131,017
kingmaple
Author by

kingmaple

I am natural born Aries, I went through military service and obtained a degree in the University of Tallinn, in Estonia. I am working as an architect in the fields of internet media, creating applications like the one you are using right now. I also consult developers and startups, so if you need help with anything, let me know.

Updated on July 09, 2022

Comments

  • kingmaple
    kingmaple almost 2 years

    Alright, I have an small authentication issue. My web service allows to connect to my API over HTTP with a username and password, but this connection can also be restricted to a specific IP address.

    This means that the $_SERVER['REMOTE_ADDR'] can be incorrect. I already know that any IP information can never truly be relied upon - I have the restriction only in an attempt to add another layer of security.

    If this is the general overview of a request to my web server:

    clientSERVER => clientPROXY => myPROXY => mySERVER

    Then this means that mySERVER shows REMOTE_ADDR of myPROXY instead of that of the client and sends the actual IP of the client as HTTP_X_FORWARDED_FOR.

    To overcome this, my web service has a list of 'trusted proxy' IP addresses and if REMOTE_ADDR is from one of those trusted IP addresses, then it tells my web service that the actual IP address is the value of HTTP_X_FORWARDED_FOR.

    Now the problem is with clientPROXY. This means that (quite often) mySERVER gets HTTP_X_FORWARDED_FOR value that has multiple IP addresses. According to HTTP_X_FORWARDED_FOR documentation, the value is a comma-separated list of IP addresses where the first IP is that of the actual true client and every other IP address is that of a proxy.

    So, if HTTP_X_FORWARDED_FOR has multiple values and my service is IP restricted, do I have to check the 'last' value of HTTP_X_FORWARDED_FOR against my allowed IP list and just ignore the actual client IP?

    I assume that in a system, where I have to set the list of allowed IP addresses, the whitelisted IP address should be that of a proxy and not an IP that is behind the proxy (since that could be some localhost IP and change frequently).

    And what of HTTP_CLIENT_IP?

  • BenMorel
    BenMorel over 10 years
    None of these headers is "more reliable" than the others. They can all be forged by the client, and your applications needs to know what's trusted and what's not: you normally know your infrastructure and the IP address of any proxy / load balancer in front of your HTTP server(s).
  • TheEnvironmentalist
    TheEnvironmentalist over 10 years
    True, but generally, if an order of preference is to be established based on assumed reliability, this is it. I realize that they can all be manipulated, but if these must be used, this is how to do it.
  • Admin
    Admin over 9 years
    This answer doesn't address the specific situation described in the question, where not all requests are passed through a proxy. As a result, requests received directly from clients may contain inaccurate IP addresses in headers.
  • Matthew Kolb
    Matthew Kolb over 9 years
    Note that it appears that using the last value in the list is still probably using a proxy's IP. According to the link below, the originating client is the FIRST IP. en.wikipedia.org/wiki/X-Forwarded-For
  • Vipul Hadiya
    Vipul Hadiya about 9 years
    Nice answer, you can find even accurate from Prestashop's Tools class :) . Find getRemoteAddr() function
  • Rob Brandt
    Rob Brandt about 9 years
    Also note that the same source says that this is easy to forge, so the last one is more reliable. So each use case may make different choices. If the use case for getting the IP is combating fraud or spam, the first IP may be meaningless and the most reliable address - the last one - is most useful. If the use case for getting the IP is less nefarious activities, the first one would be most useful.
  • transistor09
    transistor09 about 9 years
    Why is there so much disinformation on this topic? $_SERVER['REMOTE_ADDR'] is the only reliable field that's not influenced by the remote user, all others are parsed from headers.
  • TheEnvironmentalist
    TheEnvironmentalist almost 9 years
    @transistor09 Care to provide your own answer then?
  • Ben Dyer
    Ben Dyer over 6 years
    This is an excellent answer, but there is one minor problem. If strict error reporting is on, trying to trim(end(explode())) on one line will return "Only variables should be passed by reference." To get around that, set the exploded proxy header to a variable first, then trim(end()) that instead.
  • Erki Aring
    Erki Aring over 6 years
    @BenDyer Thanks, fixed it.
  • frodeborli
    frodeborli almost 6 years
    This is a possible security hole. Anybody can add the X-Forwarded-For header to their request.
  • tisc0
    tisc0 almost 6 years
    @frodeborli : You're right ; that's why one should use the reqidel+reqadd directives in haproxy.
  • Lucas Bustamante
    Lucas Bustamante over 4 years
    I believe this is incorrect. According to Mozilla, the original IP is the first, not the last, so it should be trim(array_shift()) developer.mozilla.org/en-US/docs/Web/HTTP/Headers/…
  • CragMonkey
    CragMonkey over 2 years
    On my site, $_SERVER['HTTP_CLIENT_IP'] is not set and $_SERVER['REMOTE_ADDR'] changes on every page load, while $_SERVER['HTTP_X_FORWARDED_FOR'] is constant and set to the expected value.