[HN Gopher] What is X-Forwarded-For and when can you trust it? (...
       ___________________________________________________________________
        
       What is X-Forwarded-For and when can you trust it? (2024)
        
       Author : ayoisaiah
       Score  : 62 points
       Date   : 2025-07-23 10:18 UTC (3 days ago)
        
 (HTM) web link (httptoolkit.com)
 (TXT) w3m dump (httptoolkit.com)
        
       | westurner wrote:
       | 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
        
       | vlod wrote:
       | 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 wrote:
         | 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.
        
         | miyuru wrote:
         | 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.
        
         | supriyo-biswas wrote:
         | > 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?
        
       | nodesocket wrote:
       | 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...
        
       | SSchick wrote:
       | See https://expressjs.com/en/guide/behind-proxies.html for a fun
       | read.
        
       | francislavoie wrote:
       | 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.
        
         | danhite wrote:
         | The article you cited was very informative.
         | 
         | For example it reminds that standards & intermediaries you
         | trust might still allow multiple unconcatenated XFF headers
         | though and the XFF unpeel by right to left heuristic fails if
         | you only look at the e.g. the first such header.
         | 
         | This reminded me that there always seems to be a past or future
         | pitfall case lurking, such as using old code/library honoring a
         | X-HTTP-Method-Override header (e.g. historically used to
         | ~convert POST to PUT bypassing client CORS restrictions).
         | 
         | Many holes I've noticed over the years seem to spring from
         | legacy preservation of well intended endruns of restrictions,
         | for example I can presently in Safari exploit a bug to get a
         | crypto digest calculated for me within an insecure context ...
         | I find this quite usefully locally (e.g. lan http: , data: ,
         | ~bookmarklets that I control) but nonetheless I reported the
         | bug and am trying not to rely on it.
        
         | OutOfHere wrote:
         | This was the only article that opened my eyes; it's a lot
         | better than the Wikipedia article on the topic. It covers the
         | XFF header, also related headers, clearly from both defense and
         | offense perspectives.
         | 
         | Two things it failed to advise for defense are:
         | 
         | (1) I can simply just reject requests that provide multiple
         | keys of this header. In Go, I will use
         | `http.Header.Values(headerName)` to check the count. There is
         | no good reason for having multiple keys of it. Any
         | misconfiguration in setting them is the client's problem.
         | 
         | (2) I can and I must reject large requests that have too many
         | header bytes. In Go, when initializing `http.Server`, I can
         | give it the `MaxHeaderBytes` argument. Sending megabytes of
         | headers stops here.
         | 
         | If I understood correctly, when wanting the rightmost-ish XFF
         | value, I can use the rightmost value that is not in a list of
         | trusted subnets, assuming there is at least one remaining value
         | left of it.
        
       | pityJuke wrote:
       | 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 wrote:
       | X-Forwarded-For lets us bypass geoblocking ;-)
        
         | wutwutwat wrote:
         | A properly configured load balancer is going to drop this
         | header if the client sends it, and then set it itself, with the
         | request connection's ip being first, then the proxy ip being
         | second. Every proxy after that should append its own ip to that
         | header, then finally when the request reaches your app server,
         | you should filter out your known proxy ips to be left hopefully
         | with just the ip address of the connection the request was
         | forwarded for, which was not set via any client header, and not
         | able to be spoofed.
         | 
         | I'm sure plenty of lbs/reverse proxies and app servers don't
         | set things, establish trust, or filter the header properly
         | though, because, people, but it is easy to lock down.
        
           | tetha wrote:
           | Yeah we got dinged by our pentesters a few years ago because
           | the LB didn't clear X-Forwarded-For headers. So you could
           | just set some trusted IP into the X-Forwarded-For header and
           | various ip whitelists went "Well, it came from there, so we
           | gonna let it though".
           | 
           | Oops :)
           | 
           | It is one of these trust-based headers that need to be
           | cleared at the edge of your network / trust zone.
        
           | OutOfHere wrote:
           | I do not agree that the XFF header must be dropped and re-
           | set. Doing so can in fact be harmful. There is a reason for
           | preserving the chain of IPs, which is that it allows the app
           | to use the rightmost-ish IP after skipping the known proxy
           | IPs.
        
       | knorker wrote:
       | 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.
        
         | danhite wrote:
         | thank you for your comment :
         | 
         | > The article seems to forget the step of checking the real TCP
         | connection remote address (from my skimming)
         | 
         | as this alerted me when reading the article to see their very
         | important, but not highlighted, caveat emptor that covers this
         | dangerous case :                 Note that this logic assumes
         | that your server is not directly accessible.        If it is,
         | you need to check the actual request source IP address is one
         | of yours first -        effectively treating that as an extra
         | right-most address.
        
       | nfriedly wrote:
       | 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.
        
       | MajesticHobo2 wrote:
       | XFF handling is the bug that keeps on giving. I'd estimate I've
       | seen incorrect parsing of it in at least half of the web
       | applications I've audited professionally.
       | 
       | The funniest is when the app renders user IP addresses somewhere
       | and you can get XSS through it.
        
       | Avamander wrote:
       | Side note, don't use XFF if you have any option not to. The
       | "Forwarded" header is much nicer.
        
         | OutOfHere wrote:
         | The XFF header is set a lot more commonly, and this gives the
         | app the freedom to be implicitly compatible with a lot more
         | reverse proxy servers than the Forwarded header without needing
         | special configuration.
         | 
         | Moreover, the Forwarded header has all the security pitfalls of
         | the XFF header.
        
       ___________________________________________________________________
       (page generated 2025-07-26 23:02 UTC)