Skip to content(if available)orjump to list(if available)

What is X-Forwarded-For and when can you trust it? (2024)

nfriedly

I have a rate limiting library, and for a long time, some of the most frequent issues related to misconfiguration around X-Forwarded-For headers: either ignoring them when it shouldn't and limiting the load balancer's IP instead of the end user, or blindly trusting any XFF header and allowing limits to be trivially bypassed.

Eventually I added some runtime checks that log a warning and linked to documentation for both of those issues and a few other common ones.

My support burden has decreased dramatically since then.

francislavoie

A very in-depth article on the topic: https://adam-p.ca/blog/2022/03/x-forwarded-for/

I implemented those recommendations in Caddy to enable a "trusted proxies" system which informs the proxy, logging, request matching etc to safely use the client IP taken from proxy headers.

pityJuke

Brings back memories of changing the header to watch South Park episodes for free (the official site was very vulnerable to just changing the header to a NA IP).

0points

X-Forwarded-For lets us bypass geoblocking ;-)

nodesocket

I know that the Python module proxy_fix[1] requires you to configure how many X-Forwarded-For ip entries it should trust with the default being 1.

[1] https://werkzeug.palletsprojects.com/en/stable/middleware/pr...

vlod

If I remember correctly, it's sometimes used by nginx (used as a reverse proxy) to inject values into the request of the real ip address. e.g. nginx in front of several node processes.

I've had problems with this though. I want to get the ip4 address from cloudflare, instead of ip6 and it's next to impossible AFAICT. (for the free plan anyway)

remram

A request doesn't come in with both an IPv4 and an IPv6. If the user connected over IPv6, the connection only has an IPv6 address. You can't get the IPv4 address, there is none.

supriyo-biswas

> I've had problems with this though. I want to get the ip4 address from cloudflare, instead of ip6 and it's next to impossible AFAICT. (for the free plan anyway)

I have multiple websites on Cloudflare and can receive IPv4 addresses just fine, though for Cloudflare fronted websites it's usually better to use the CF-Connecting-IP as people can send any value in the X-Forwarded-For header.

Maybe some intermediate layer turns the IPv4 into a IPv6-mapped IPv4 address?

miyuru

what the usecase and what is the problem using the IPv6 address?

In fact if the user has IPv6, IP blocks/Rate limits wont affect the other users on the CGNAT legacy address.

knorker

1. Have (and maintain!) a list of addresses you trust to not lie (e.g. your own proxy layers, cloudflare's proxy IP list, akamai, GCP LB, AWS LB, etc…)

2. If the connecting party (real TCP connection remote end) is in the trusted list, then take the rightmost address in XFF and set as remote end.

3. Repeat 2 until you get an address not in the trusted list.

4. That is now the real client IP. Discard anything to the left of it in XFF. (though maybe log it, if you want)

The article seems to forget the step of checking the real TCP connection remote address (from my skimming), which means that if the web server can be accessed directly, and not just through a load balancer that always sets the header, then the article is a security hole.

westurner

From the article: https://httptoolkit.com/blog/what-is-x-forwarded-for/ :

> Dropping all external values like this is the safest approach when you're not sure how secure and reliable the rest of your call chain is going to be. If other proxies and backend apps are likely to blindly trust the incoming information, or generally make insecure choices (which we'll get into more later) then it's probably safest to completely replace the X-Forwarded-For header at that outside-world facing reverse proxy, and ditch any untrustworthy data in the process.

X-Forwarded-For: https://en.wikipedia.org/wiki/X-Forwarded-For :

> Just logging the X-Forwarded-For field is not always enough as the last proxy IP address in a chain is not contained within the X-Forwarded-For field, it is in the actual IP header. A web server should log both the request's source IP address and the X-Forwarded-For field information for completeness

HTTP header injection: https://en.wikipedia.org/wiki/HTTP_header_injection

This OWASP page has a list of X-Forwarded-For and X-FORWARDED-foR and similar headers; "Headers for IP Spoofing" https://owasp.org/www-community/pages/attacks/ip_spoofing_vi...

A sufficient WAF should detect all such attempts.

The X-Forwarded-For Wikipedia article mentions that RFC 7239 actually standardizes the header and parsing:

  Forwarded: for=192.0.2.60;proto=http;by=203.0.113.43
  Forwarded: for="[2001:db8::1234]"
RFC 7239: "Forwarded HTTP Extension" (2014): https://www.rfc-editor.org/rfc/rfc7239