Jul 14, 2026·5 min read·5 visits
TsDProxy failed to sanitize client-provided 'X-Forwarded-For' and 'X-Real-IP' headers, enabling authenticated attackers to spoof their identity and bypass downstream IP-based administrative restrictions.
A high-severity input sanitization and header injection vulnerability in TsDProxy allows authenticated Tailscale users to inject arbitrary values into the X-Forwarded-For and X-Real-IP HTTP headers. Because downstream backend services frequently trust these headers to resolve client identities, attackers can exploit this flaw to bypass IP-based access control lists, audit logs, and geo-blocking restrictions.
TsDProxy is a Go-based helper proxy designed to route traffic from a Tailscale network (tailnet) to internal backend services. It acts as an authenticated reverse proxy that terminates TLS and routes HTTP requests to non-exposed internal containers. In typical deployments, downstream services reside behind TsDProxy and implicitly trust its authentication header metadata.
The vulnerability is classified under CWE-74 (Improper Neutralization of Special Elements in Output Used by a Downstream Component). It manifests as an IP spoofing flaw where the proxy fails to scrub or validate incoming 'X-Forwarded-For' and 'X-Real-IP' headers before forwarding them to downstream backend components.
Because the backend applications often rely on these headers to enforce security-critical access policies, an attacker can manipulate them to execute unauthorized actions. This flaw allows any authenticated Tailscale user to bypass network segregation and assume administrative roles on the backend.
The root cause of this vulnerability lies in the 'newPortProxy' function within 'internal/proxymanager/port.go'. The proxy uses Go's 'net/http/httputil' reverse proxy standard library package to manage incoming connections. It registers a 'Rewrite' closure to execute custom headers management before sending the request onward.
While the proxy correctly strips custom application headers like 'HeaderID' or 'HeaderRemoteUser', it originally failed to sanitize standard proxy headers like 'X-Forwarded-For' and 'X-Real-IP'. When 'r.SetXForwarded()' is called, it does not overwrite existing 'X-Forwarded-For' values if they are already present in the incoming client request.
Instead, according to Go's standard library documentation, 'SetXForwarded' appends the actual remote address of the client to the pre-existing list. Consequently, if an attacker sends an initial header of 'X-Forwarded-For: 127.0.0.1', the outgoing request becomes 'X-Forwarded-For: 127.0.0.1, <attacker_ip>'. Downstream engines that parse the first element of this CSV string are deceived into identifying the client as '127.0.0.1'.
To understand the vulnerability, examine the 'newPortProxy' function before the fix was implemented. The proxy stripped key identification headers but left the standard proxy routing headers untouched before execution of 'r.SetXForwarded()':
// Vulnerable Code Path
Rewrite: func(r *httputil.ProxyRequest) {
r.SetURL(pconfig.GetFirstTarget())
r.Out.Host = r.In.Host
// Delete custom authentication headers
r.Out.Header.Del(consts.HeaderID)
r.Out.Header.Del(consts.HeaderRemoteUser)
// CRITICAL OMISSION: X-Forwarded-For is not deleted
r.SetXForwarded()
}The patch introduced in version '3.0.0-alpha.3' fixes this behavior by declaring 'HeaderXForwardedFor' in 'TrustedProxyHeaders' to ensure automatic scrubbing before rewriting. The revised code strips incoming headers and enforces authoritative IP overwriting:
// Patched Code Path in internal/proxymanager/port.go
r.SetXForwarded() // SetXForwarded now works on clean headers
if peerIP := resolvePeerIP(r.In); peerIP != "" {
r.Out.Header.Set(consts.HeaderRealIP, peerIP)
r.Out.Header.Set(consts.HeaderXForwardedFor, peerIP) // Overrides with authoritative IP
}This remediation completely removes reliance on client-supplied values. By overriding both headers with the authoritative IP extracted from the connection socket state via 'resolvePeerIP', the proxy eliminates downstream parsing ambiguity.
Exploitation of this vulnerability requires network access to the TsDProxy endpoint, which implies the attacker must be authenticated to the target Tailscale network (tailnet). The attacker does not require administrative privileges on either the proxy or the target backend system.
The attack is executed by appending custom HTTP headers to the request. The following bash command demonstrates how an attacker can craft a request targeting a backend server configured with local administrative restrictions:
curl -H "X-Forwarded-For: 127.0.0.1" \
-H "X-Real-IP: 127.0.0.1" \
https://backend.tailscale-proxy.ts.net/adminUpon receiving the request, the proxy appends the legitimate tailnet IP to the existing spoofed header list. When Nginx or another reverse proxy upstream on the backend server processes the headers, it extracts '127.0.0.1' as the original sender, granting access to restricted endpoints.
The impact of this header injection vulnerability is rated High, with a CVSS v3.1 score of 8.5. The primary consequence is the total bypass of IP-based authentication controls on downstream systems. This is particularly critical because many administrators run backends behind TsDProxy under the assumption that only valid, non-spoofed Tailscale client IPs can reach them.
The attack vector is network-based (AV:N), requiring low complexity (AC:L) and low privileges (PR:L). Critically, the scope metric is changed (S:C) because the security boundary of the proxy is violated, directly compromising the authorization layer of downstream components.
While there is no direct risk of Denial of Service (A:N), confidentiality (C:L) and integrity (I:H) are heavily impacted. Attackers can execute administrative state-changing operations if backend management interfaces rely on source IP validation.
The recommended remediation is to upgrade TsDProxy to version '3.0.0-alpha.3' or higher immediately. This version integrates 'HeaderXForwardedFor' into the 'TrustedProxyHeaders' block, enforcing proper stripping of client-supplied routing parameters before they are processed.
If upgrading is not immediately possible, administrators must implement workarounds at the downstream application tier. For example, if Nginx is deployed as the backend server, avoid trusting the leftmost entry of the header unless recursive verification is enabled. Ensure configuration uses 'real_ip_recursive on' and restrict the trusted proxy addresses exclusively to the TsDProxy IP range.
Furthermore, developers should avoid relying solely on the 'X-Forwarded-For' or 'X-Real-IP' headers for authentication and authorization. Critical admin endpoints should enforce robust cryptographic sessions or application-level authentication (e.g., tokens or client certs) rather than relying exclusively on source-network layer identification.
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:H/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
tsdproxy almeidapaulopt | < 3.0.0-alpha.3 | 3.0.0-alpha.3 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-74 |
| Attack Vector | Network |
| CVSS v3.1 | 8.5 (High) |
| Impact | Security Bypass / IP Spoofing |
| Exploit Status | poc |
| KEV Status | Not Listed |
Improper Neutralization of Special Elements in Output Used by a Downstream Component ('Injection')
CVE-2026-54448 is a critical denial of service vulnerability in Trivy's Infrastructure-as-Code (IaC) misconfiguration scanning engine. Prior to version 0.71.0, Trivy utilized a custom archive parser to unpack Helm chart tarballs (.tgz) during automated scans. This custom implementation iterated through compressed files and loaded their entire raw contents into system memory using the io.ReadAll function without implementing size limits or threshold checks, enabling an attacker to trigger an immediate heap-allocation crash or system Out-of-Memory (OOM) termination using a decompression bomb.
A critical remote code execution vulnerability exists in TidGi Desktop up to version 0.13.0. The flaw allows an attacker to execute arbitrary code with Node.js privileges when a user imports or clones a malicious TiddlyWiki repository. This occurs due to the automatic execution of 'startup' modules defined in user-imported tiddler files.
A critical Denial of Service (DoS) vulnerability in the Ech0 publishing platform allows unauthenticated remote attackers to exhaust CPU resources via a crafted Accept-Language header. By utilizing underscore separators instead of hyphens, the attack bypasses the CVE-2022-32149 guard within the Go language tag parser, triggering a quadratic-time complexity operation.
FacturaScripts is an open-source PHP-based enterprise resource planning (ERP) and billing software. A critical path traversal vulnerability in the file-handling logic allows authenticated attackers with file upload permissions to write arbitrary files to any location on the system writable by the web server user. By writing custom server configuration files (.htaccess) to directories excluded from default rewrite rules, attackers can map allowed file types (like .png) to the PHP interpreter, leading to full remote code execution.
Nebula-mesh allows non-admin operators to disable webhook SSRF (Server-Side Request Forgery) protection via the allow_private parameter. Low-privilege operators can configure webhook endpoints targeting internal endpoints and trigger lifecycle events on resources they own, bypassing network access controls.
An information exposure vulnerability exists in Umbraco.AI package versions up to 1.13.0, where an authenticated backoffice user with elevated privileges can resolve and retrieve arbitrary configuration values from the global ASP.NET Core IConfiguration hierarchy.