Jul 14, 2026·5 min read·15 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')
An input validation and encoding desynchronization vulnerability exists in MariaDB Connector/R2DBC versions prior to 1.4.1. The driver assumes all communication utilizes the UTF-8 character set, but fails to account for server-driven mid-session changes to the character_set_client variable. When a change to a multi-byte character set such as GBK or Big5 is induced, the server interprets client-escaped single quotes as part of a multi-byte character. This state desynchronization bypasses standard escaping mechanisms and allows remote unauthenticated attackers to execute arbitrary SQL commands.
A security vulnerability in the MariaDB Connector/R2DBC client driver allows credential theft during the database authentication phase. The client driver does not gate clear-text password authentication plugins on transport encryption, making it possible for on-path attackers or hostile database servers to intercept passwords.
A critical security flaw was identified in RestrictedPython prior to version 8.3 where positional-only arguments introduced in Python 3.8 were not properly validated. This allowed an attacker executing code within the sandbox to shadow critical security guards like `_write_` and `_getattr_`, leading to a complete sandbox escape and arbitrary code execution on the underlying server.
CVE-2026-55855 is a client-side SQL injection vulnerability in the MariaDB Connector/Node.js library that occurs when using legacy multi-byte character sets. The flaw arises from naive, byte-wise client-side parameter escaping. Attackers can leverage specific multi-byte lead bytes to absorb backslash escape characters on the server side, allowing them to terminate string literals and execute arbitrary SQL commands.
An improper authentication vulnerability (CWE-287) in Portainer Community Edition (CE) allows unauthenticated remote attackers to achieve full administrative takeover. During the initial five-minute uninitialized setup window, sensitive endpoints responsible for creating the initial administrator user and restoring database state are publicly accessible without authentication. Attackers can exploit this to create administrative credentials or overwrite the system state with a malicious database configuration.
CVE-2026-55678 defines a critical security vulnerability in the Enterprise clustering implementation of Arc, an open-source SQL-native time-series database. When clustering is enabled but a shared secret is not defined, the cluster coordinator fails to enforce authentication on cluster join requests and node status updates. Remote, unauthenticated attackers can exploit this behavior to register a rogue node, hijack telemetry routing, and harvest sensitive client authentication headers.