Is It Secure to Rely on "X-Forwarded-For" to Restrict Access by Ip in Apache While Using Cloudflare

Is it secure to rely on X-Forwarded-For to restrict access by IP in Apache while using Cloudflare?

According to the IETF RFC 2616, Section 4.2, a header can hold a comma separated list of values, and this is the case of X-Forwarded-For as Cloudflare uses it.

If an X-Forwarded-For header was already present in the request to
Cloudflare, Cloudflare appends the IP address of the HTTP proxy to the
header:

Example: X-Forwarded-For: 203.0.113.1,198.51.100.101,198.51.100.102 

In the examples above, 203.0.113.1 is the original visitor IP address and
198.51.100.101 and 198.51.100.102 are proxy server IP addresses provided to Cloudflare via the X-Forwarded-For header.

It is customary to take the leftmost IP as the real one, but this isn't always the case.

If you go this way, you should check for a regex that matches your IP as

SetEnvIf X-Forwarded-For ^1\.2\.3\.4 allowed

(leftmost IP, escaping the dots)

A better way (IMHO)

Cloudflare also sends the header cf-connecting-ip (which is meant to be the last IP to hit cloudflare before being sent to your machine) and I'd rather use that one.

Is this a safe approach or is there a better/more secure way to limit
access by client IP in Apache when Cloudflare is being used?

There's a catch. In this scenario, you're telling Apache:

"we have cloudflare in the middle so instead of your native way to tell the visitor's IP let's look at this custom header".

That custom header can be forged. Absolutely. Therefore, you should also say:

"this custom header should be taken as reliable if and only the request comes from a Cloudflare IP".

Cloudflare does explicitly list their IP ranges

Finally, you should use mod_remoteip instead of manually building a SetEnvIf rule.

Basically:

# /etc/apache2/conf-enabled/remoteip.conf
RemoteIPHeader CF-Connecting-IP
RemoteIPTrustedProxy 173.245.48.0/20
RemoteIPTrustedProxy 103.21.244.0/22
...
RemoteIPTrustedProxy 2606:4700::/32
RemoteIPTrustedProxy 2803:f800::/32

Alternatively, put the IP list in a separate file:

# /etc/apache2/conf-enabled/remoteip.conf
RemoteIPHeader CF-Connecting-IP
RemoteIPTrustedProxyList conf/trusted-proxies.lst

and

# conf/trusted-proxies.lst
173.245.48.0/20
103.21.244.0/22
...
...
2803:f800::/32
2606:4700::/32

If said header doesn't come in the request, Apache falls back ro REMOTE_ADDR. Same goes for requests coming from untrusted IPs. If the header is present and comes from Cloudflare, you can simply do:

Require ip 1.2.3.4

This approach replaces the IP wherever you need to use it (logs, auth, etc) and gracefully falls back to original REMOTE_ADDR in edge cases.

Apache Server Timing Out taking long time

i think you have to ask your webhost or ask cloudflare support

and also raise s ticket on Sucuri. Their team closely works with the respective developers in fixing the security issues. Once fixed, Sucuri patches those vulnerabilities at the firewall level

During the attacks, website with heavy traffic like yours would slow down significantly due to the high server load. Sometimes it would even cause the server to restart causing downtime.

When you enable Sucuri, all your site traffic goes through their cloudproxy firewall before coming to your hosting server. This allows them to block all the attacks and only send you legitimate visitors.

Sucuri’s firewall blocks all the attacks before it even touches our server. Since they’re one of the leading security companies, Sucuri proactively research and report potential security issues to WordPress core team as well as third-party plugins.

If you still not resolve the problem then then it may be a different type of attack

  • TCP Connection Attacks

These attempt to use up all the available connections to infrastructure devices such as load-balancers, firewalls and application servers. Even devices capable of maintaining state on millions of connections can be taken down by these attacks.

  • Volumetric Attacks

These attempt to consume the bandwidth either within the target network/service, or between the target network/service and the rest of the Internet. These attacks are simply about causing congestion

  • Fragmentation Attacks

These send a flood of TCP or UDP fragments to a victim, overwhelming the victim's ability to re-assemble the streams and severely reducing performance.

  • Application Attacks

These attempt to overwhelm a specific aspect of an application or service and can be effective even with very few attacking machines generating a low traffic rate (making them difficult to detect and mitigate).

Varnish automagically adding load balancer IP to X-Forwarded-For header

I also bumped into this problem today.

The default.vcl in varnish 4.0 was renamed in builtin.vcl and does not contain the set req.http.X-Forwarded-For part that you mentioned - link. Nevertheless he clearly appends to a comma separated list the intermediate proxy IP address as per the protocol specs - Wikipedia link.

One solution would be to use the X-Real-IP header instead, overwriting this header all the time in HAProxy with the real client ip and using this one for vcl ACL.

Another solution as (wrongly) mentioned in the varnish forum, would be regsub(req.http.X-Forwarded-For, "[, ].*$", "") that takes the leftmost IP address. However, this method is NOT SECURE, since this header can be easily spoofed.

My suggestion would be to extract the known part, varnish IP from the header like this:

if (!std.ip(regsub(req.http.X-Forwarded-For, ", 192\.168\.1\.101$", ""), "0.0.0.0") ~ purge_acl) {
return(synth(403, "Not allowed."));
}

The only problem with this is if there are more than 2 hops in the connection, eg. you also use a proxy to connect to internet. A good solution for this is provided by nginx since you can define trusted hops, and they are ignored recursively until the real client ip.

set_real_ip_from 192.168.1.101;
real_ip_header X-Forwarded-For;
real_ip_recursive on;

You can see more details about this in this serverfault thread answer

You might also want to check why in your VCL_call RECV you do a ReqUnset X-Forwarded-For BEFORE the ACL match.

What is the most accurate way to retrieve a user's correct IP address in PHP?

Here is a shorter, cleaner way to get the IP address:

function get_ip_address(){
foreach (array('HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_FORWARDED', 'HTTP_X_CLUSTER_CLIENT_IP', 'HTTP_FORWARDED_FOR', 'HTTP_FORWARDED', 'REMOTE_ADDR') as $key){
if (array_key_exists($key, $_SERVER) === true){
foreach (explode(',', $_SERVER[$key]) as $ip){
$ip = trim($ip); // just to be safe

if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !== false){
return $ip;
}
}
}
}
}

Your code seems to be pretty complete already, I cannot see any possible bugs in it (aside from the usual IP caveats), I would change the validate_ip() function to rely on the filter extension though:

public function validate_ip($ip)
{
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) === false)
{
return false;
}

self::$ip = sprintf('%u', ip2long($ip)); // you seem to want this

return true;
}

Also your HTTP_X_FORWARDED_FOR snippet can be simplified from this:

// check for IPs passing through proxies
if (!empty($_SERVER['HTTP_X_FORWARDED_FOR']))
{
// check if multiple ips exist in var
if (strpos($_SERVER['HTTP_X_FORWARDED_FOR'], ',') !== false)
{
$iplist = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);

foreach ($iplist as $ip)
{
if ($this->validate_ip($ip))
return $ip;
}
}

else
{
if ($this->validate_ip($_SERVER['HTTP_X_FORWARDED_FOR']))
return $_SERVER['HTTP_X_FORWARDED_FOR'];
}
}

To this:

// check for IPs passing through proxies
if (!empty($_SERVER['HTTP_X_FORWARDED_FOR']))
{
$iplist = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);

foreach ($iplist as $ip)
{
if ($this->validate_ip($ip))
return $ip;
}
}

You may also want to validate IPv6 addresses.



Related Topics



Leave a reply



Submit