{"id":"TECH-202611-c2lguf","kind":"technique","aliases":[],"related":[],"title":"Spoofed 'Forwarded' header gets past reverse proxies and CDNs, letting attackers fake their IP address or the site's host name in Rails, Spring Boot, Jetty and Symfony apps","description":null,"summary":"A researcher's write-up (lkoskela.fi, 5 Nov 2026) shows that several web frameworks read the standard RFC 7239 'Forwarded' request header and trust it over X-Forwarded-*: Ruby on Rails 8.1 before 8.1.4.1 (client IP only), Jetty 12 in its default setup, Spring Boot before 4.2 when forwarded-header handling is on (FRAMEWORK strategy, or NATIVE on Jetty or Reactor Netty), Symfony when Forwarded support is enabled (host name), and the Gorilla ProxyHeaders handler. Almost no proxy or CDN strips this header by default: Envoy/Envoy Gateway, ingress-nginx, Traefik, Apache, Caddy, Kong, Cloudflare and CloudFront pass it through, and per their docs AWS ALB, Google Cloud and Azure load balancers probably do too. 'Trusted proxy' settings do not help. So a client can fake its IP address (getting around IP allowlists and rate limits, faking log entries) and the host name or protocol (poisoned password-reset links that lead to account takeover, web cache poisoning, redirect loops). The researcher found it common across bug bounty targets. Proof of concept only; no attacks in the wild reported. Rails fixed it in 8.1.4.1. Spring Boot 4.2, Spring 7.1 and Reactor Netty 1.4 changed their defaults. Jetty did not change its default. No CVE known.","severity":"medium","cvss":null,"epss":null,"exploited":false,"exploited_at":null,"products":[{"name":"Ruby on Rails","fixed":["8.1.4.1"],"affected":">=8.1.0 <8.1.4.1"},{"name":"Spring Boot","fixed":["4.2"],"affected":"<4.2"},{"name":"Jetty","fixed":[],"affected":"12.x default config (fix: configuration only)"},{"name":"Reactor Netty","fixed":[],"affected":"<1.4"},{"name":"Symfony","fixed":[],"affected":"when Forwarded trusted header enabled"},{"name":"Gorilla handlers","fixed":[],"affected":"ProxyHeaders"}],"ecosystems":[],"tags":["early-warning","technique"],"not_affected_if":[],"how_to_check":"Check whether your app runs on Rails 8.1.x before 8.1.4.1, Jetty 12 (or older), Spring Boot before 4.2 with server.forward-headers-strategy set (FRAMEWORK or NATIVE on Jetty or Reactor Netty), Symfony with the Forwarded trusted header enabled, or Gorilla handlers.ProxyHeaders, AND whether the proxy/CDN in front of it passes the 'Forwarded' header through (the default on Envoy, ingress-nginx, Traefik, Apache, Caddy, Kong, Cloudflare, CloudFront). Test: send 'Forwarded: host=example.com:invalidport'. A 400 Bad Request (often with a Jetty stack trace) means the backend parses Forwarded. Also send 'Forwarded: for=127.0.0.1' and see whether the app logs or uses that IP. On Rails, a 500 when both 'Client-IP' and 'Forwarded: for' are sent means Rails is parsing it. In logs, look for incoming requests carrying a Forwarded header from clients.","fix":"Make the first reverse proxy, CDN or load balancer drop (or overwrite) any incoming 'Forwarded' header, and X-Real-IP or any other proxy header you do not set yourself. Turn off Forwarded parsing in the app so it reads only the X-Forwarded-* headers your proxy actually sets. Upgrade Rails to 8.1.4.1+ and Spring Boot to 4.2+ (or set the header family explicitly in Spring 7.1 / Reactor Netty 1.4 and older branches). On Jetty, configure ForwardedRequestCustomizer to accept only the headers your proxy handles. Set an allowed-hosts list (Rails config.hosts, Symfony trusted_hosts, Django ALLOWED_HOSTS, ASP.NET AllowedHosts). Do not build password-reset or other absolute links from the request host.","status":"open","published_at":null,"first_seen_at":"2026-11-08T19:02:23+00:00","triaged_at":"2026-11-08T19:02:23+00:00","updated_at":"2026-11-08T19:02:32+00:00","category":"runtime","confidence":null,"triage":null,"analyst_status":"done","analyst_note":null,"search":"'-202611':31A '1.4':243B '12':88B '2026':57B '4.2':96B,237B '5':55B '7.1':239B '7239':67B '8.1':81B '8.1.4.1':83B,234B 'account':204B 'across':216B 'address':15A,179B 'alb':156B 'allowlist':183B 'almost':126B 'apach':142B 'app':29A 'around':181B 'attack':11A,225B 'aw':155B 'azur':160B 'balanc':162B 'boot':25A,37A,94B,236B 'bounti':218B 'bug':217B 'c2lguf':32A 'cach':207B 'caddi':143B 'cdn':130B 'cdns':9A 'chang':244B,250B 'client':84B,174B 'cloud':158B 'cloudflar':145B 'cloudfront':147B 'common':215B 'concept':222B 'cve':254B 'default':91B,135B,246B,252B 'doc':154B 'earli':45B 'early-warn':44B 'enabl':118B 'entri':189B 'envoy/envoy':136B 'fake':12A,176B,187B 'fix':231B 'forward':2A,68B,77B,99B,115B 'forwarded-head':98B 'found':213B 'framework':62B,104B 'gateway':137B 'get':4A,180B 'googl':157B 'gorilla':42A,123B 'handl':101B 'handler':125B 'handlers':43A 'header':3A,70B,100B,133B 'help':171B 'host':20A,119B,192B 'ingress':139B 'ingress-nginx':138B 'ip':14A,85B,178B,182B 'jetti':26A,87B,109B,247B 'jetty':38A 'known':255B 'kong':144B 'lead':202B 'let':10A 'limit':186B 'link':200B 'lkoskela.fi':54B 'load':161B 'log':188B 'loop':210B 'name':21A,120B,193B 'nativ':107B 'netti':112B,242B 'netty':40A 'nginx':140B 'nov':56B 'on':34A 'pass':148B 'password':198B 'password-reset':197B 'past':5A 'per':152B 'poison':196B,208B 'probabl':163B 'proof':220B 'protocol':195B 'proxi':7A,128B,167B 'proxyhead':124B 'rail':23A,80B,230B 'rails':35A 'rate':185B 'reactor':39A,111B,241B 'read':63B 'redirect':209B 'report':229B 'request':69B 'research':49B,212B 'reset':199B 'revers':6A 'rfc':66B 'rubi':78B 'ruby':33A 'set':168B 'setup':92B 'sever':60B 'show':58B 'site':18A 'spoof':1A 'spring':24A,36A,93B,235B,238B 'standard':65B 'strategi':105B 'strip':131B 'support':116B 'symfoni':28A,113B 'symfony':41A 'takeov':205B 'target':219B 'tech':30A 'techniqu':47B 'traefik':141B 'trust':72B,166B 'warn':46B 'web':61B,206B 'wild':228B 'write':52B 'write-up':51B 'x':76B 'x-forward':75B","nicknames":[],"mitigations":[],"who_affected":null,"tier":"C","verified":{},"analyst_revisit_at":null,"fix_status":null,"audience":null,"plain_action":null,"pkeys":["boot","gorillahandlers","handlers","jetty","netty","onrails","reactornetty","rubyonrails","springboot","symfony"],"ptokens":["boot","gorilla","handlers","jetty","netty","on","rails","reactor","ruby","spring","symfony"],"labels":["earlywarning","medium","runtime","technique"],"campaign_type":"other","search_a":"'-202611':31A 'address':15A 'app':29A 'attack':11A 'boot':25A,37A 'c2lguf':32A 'cdns':9A 'fake':12A 'forward':2A 'get':4A 'gorilla':42A 'handlers':43A 'header':3A 'host':20A 'ip':14A 'jetti':26A 'jetty':38A 'let':10A 'name':21A 'netty':40A 'on':34A 'past':5A 'proxi':7A 'rail':23A 'rails':35A 'reactor':39A 'revers':6A 'ruby':33A 'site':18A 'spoof':1A 'spring':24A,36A 'symfoni':28A 'symfony':41A 'tech':30A","search_b":"'1.4':243B '12':88B '2026':57B '4.2':96B,237B '5':55B '7.1':239B '7239':67B '8.1':81B '8.1.4.1':83B,234B 'account':204B 'across':216B 'address':179B 'alb':156B 'allowlist':183B 'almost':126B 'apach':142B 'around':181B 'attack':225B 'aw':155B 'azur':160B 'balanc':162B 'boot':94B,236B 'bounti':218B 'bug':217B 'cach':207B 'caddi':143B 'cdn':130B 'chang':244B,250B 'client':84B,174B 'cloud':158B 'cloudflar':145B 'cloudfront':147B 'common':215B 'concept':222B 'cve':254B 'default':91B,135B,246B,252B 'doc':154B 'earli':45B 'early-warn':44B 'enabl':118B 'entri':189B 'envoy/envoy':136B 'fake':176B,187B 'fix':231B 'forward':68B,77B,99B,115B 'forwarded-head':98B 'found':213B 'framework':62B,104B 'gateway':137B 'get':180B 'googl':157B 'gorilla':123B 'handl':101B 'handler':125B 'header':70B,100B,133B 'help':171B 'host':119B,192B 'ingress':139B 'ingress-nginx':138B 'ip':85B,178B,182B 'jetti':87B,109B,247B 'known':255B 'kong':144B 'lead':202B 'limit':186B 'link':200B 'lkoskela.fi':54B 'load':161B 'log':188B 'loop':210B 'name':120B,193B 'nativ':107B 'netti':112B,242B 'nginx':140B 'nov':56B 'pass':148B 'password':198B 'password-reset':197B 'per':152B 'poison':196B,208B 'probabl':163B 'proof':220B 'protocol':195B 'proxi':128B,167B 'proxyhead':124B 'rail':80B,230B 'rate':185B 'reactor':111B,241B 'read':63B 'redirect':209B 'report':229B 'request':69B 'research':49B,212B 'reset':199B 'rfc':66B 'rubi':78B 'set':168B 'setup':92B 'sever':60B 'show':58B 'spring':93B,235B,238B 'standard':65B 'strategi':105B 'strip':131B 'support':116B 'symfoni':113B 'takeov':205B 'target':219B 'techniqu':47B 'traefik':141B 'trust':72B,166B 'warn':46B 'web':61B,206B 'wild':228B 'write':52B 'write-up':51B 'x':76B 'x-forward':75B","search_c":"","cause_entry":null,"cause_path":null,"disclosures_checked_at":null,"first_public_at":"2026-11-05T14:04:44+00:00","kev":null,"cvss_vector":null,"cvss_by":null,"poc_count":0,"sources":[{"source":"analyst","external_id":"TECH-202611-c2lguf","url":"https://www.lkoskela.fi/blog/2026-11-05-forwarded-without-question/","severity":null,"published_at":null,"modified_at":null,"first_seen_at":"2026-11-08T19:02:23+00:00","fetched_at":"2026-11-08T19:02:23+00:00","note":null,"cites":["https://www.lkoskela.fi/blog/2026-11-05-forwarded-without-question/"]}],"documents":[{"id":102944,"url":"https://news.ycombinator.com/item?id=50415810","title":"Web frameworks overtrust the HTTP Forwarded: header","kind":"post","source":"hackernews","publisher":null,"text":"Web frameworks overtrust the HTTP Forwarded: header\nhttps://www.lkoskela.fi/blog/2026-11-05-forwarded-without-question/\n\n1 points, 0 comments on Hacker News","published_at":"2026-11-08T18:42:07+00:00","relation":"describes","confidence":null,"note":null},{"id":103055,"url":"https://www.lkoskela.fi/blog/2026-11-05-forwarded-without-question/","title":"Forwarded Without Question: How Web Frameworks Overtrust the Forwarded Header","kind":"blog","source":"analyst","publisher":null,"text":"Researcher write-up (lkoskela.fi, 5 Nov 2026): Rails 8.1 (<8.1.4.1), Jetty 12 (default), Spring Boot <4.2 (FRAMEWORK, Jetty/Reactor Netty NATIVE), Symfony (when Forwarded enabled), Gorilla ProxyHeaders read and prioritise RFC 7239 Forwarded header over X-Forwarded-*. Envoy, nginx ingress, Traefik, Apache, Caddy, Kong, Cloudflare, CloudFront, AWS ALB/GCP/Azure LB pass Forwarded through untouched, so clients can spoof client IP, host and proto: IP allowlist/rate-limit bypass, log spoofing, password-reset link poisoning, web cache poisoning. Fixes: Rails 8.1.4.1; Spring Boot 4.2 / Spring 7.1 / Reactor Netty 1.4 default changes; Jetty default unchanged. Detection: Forwarded: host=example.com:invalidport returns 400 on Jetty/Spring/Symfony. Mitigation: proxy drops incoming Forwarded; disable Forwarded parsing in app; allowed-hosts lists.","published_at":"2026-11-05T14:04:44+00:00","relation":"describes","confidence":null,"note":null}],"disclosures":[],"exploit_sources":[],"notice":"Free to read, with fair use of about 60 pages a minute. For higher limits or API access: sales@infrastack.ai"}