Apr 15, 2026·7 min read·11 visits
go-httpbin versions <= 2.17.1 fail to sanitize user-controlled output when arbitrary Content-Type headers are requested. Attackers can execute reflected XSS attacks against the /response-headers and /base64 endpoints by forcing the response to render as HTML.
A reflected cross-site scripting (XSS) vulnerability exists in the mccutchen/go-httpbin package up to version 2.17.1. The flaw allows unauthenticated remote attackers to execute arbitrary JavaScript in a user's browser by injecting malicious payloads via query parameters alongside a manipulated Content-Type header.
The mccutchen/go-httpbin library, also distributed as the httpbingo binary, serves as an HTTP request and response service for testing purposes. It exposes numerous endpoints designed to echo client data, manipulate response formats, and simulate various HTTP behaviors. Versions up to and including 2.17.1 contain a flaw in how specific endpoints handle user-defined response headers, specifically the Content-Type header.
The vulnerability is classified under CWE-79 (Improper Neutralization of Input During Web Page Generation) and CWE-80 (Improper Neutralization of Script-Related HTML Tags in a Web Page). The primary attack surface involves the /response-headers and /base64 endpoints. These endpoints process user-supplied parameters to construct the HTTP response, including both the response headers and the body content, without enforcing strict encoding or validation on the output format.
Security impact is restricted to the client side, where the application reflects unsanitized input back to the user's browser. When an attacker dictates a text/html content type, the browser parses the subsequent JSON or decoded base64 response body as HTML. If the response body contains executable script tags, the browser executes them within the origin of the go-httpbin instance.
Exploitation requires user interaction. An attacker must construct a malicious URL and distribute it to a target user. When the user navigates to the URL, the underlying JavaScript executes, allowing the attacker to access sensitive session data, modify page content, or perform actions on behalf of the user within the affected domain.
The root cause of CVE-2025-45286 is the application's explicit trust of user input when determining the HTTP response Content-Type header, coupled with a lack of contextual output encoding. The application design intends to allow clients to test various response types, but fails to restrict the combination of dangerous content types with arbitrary reflected data.
Within the /response-headers endpoint logic, the application iterates over all provided query parameters. It uses these parameters to populate the HTTP response headers and concurrently serializes them into a JSON object that forms the response body. If a client supplies Content-Type=text/html as a query parameter, the application applies this header directly to the HTTP response.
Similarly, the /base64 endpoint decodes a base64-encoded string supplied in the URL path. It returns the raw decoded bytes as the response body. The endpoint accepts a content-type query parameter, which dictates the HTTP header sent back to the client. This allows an attacker to supply a base64-encoded HTML payload and explicitly instruct the browser to render it as HTML.
The following sequence diagram illustrates the vulnerable data flow:
Prior to commit 0decfd1a2e88d85ca6bfb8a92421653f647cbc04, the ResponseHeaders function in httpbin/handlers.go blindly applied query parameters to the response writer. The code iterated through r.URL.Query() and called w.Header().Add(k, v) for every key-value pair. Following header assignment, the function called mustMarshalJSON(w, args), which serialized the input into the response body.
func (h *HTTPBin) ResponseHeaders(w http.ResponseWriter, r *http.Request) {
args := r.URL.Query()
for k, vs := range args {
for _, v := range vs {
w.Header().Add(k, v)
}
}
mustMarshalJSON(w, args)
}The vulnerability exists because mustMarshalJSON does not HTML-escape the JSON strings by default. When combined with the attacker-controlled Content-Type header, the browser ignores the JSON structure and parses the literal HTML tags contained within the values.
The patch introduced in version 2.18.0 remediates this by implementing a safe content type whitelist and an escaping mechanism. A new helper function, mustEscapeResponse, checks if the requested content type matches text/plain, application/json, or application/octet-string. If the content type falls outside this whitelist, the application applies html.EscapeString() to the response body.
The maintainer also implemented a configuration toggle, UnsafeAllowDangerousResponses, to support legacy testing scenarios. When this boolean flag is false (the default), the application strictly enforces the HTML escaping logic, neutralizing any injected script tags before they reach the client browser.
Exploitation of the /response-headers endpoint requires crafting a specific GET request. The attacker appends the Content-Type=text/html parameter to dictate the response format. They then append an additional parameter containing the XSS payload. A functional proof-of-concept URL takes the form: http://target/response-headers?Content-Type=text/html&xss=<img/src/onerror=alert(1)>.
Upon processing this request, the go-httpbin server sets the Content-Type: text/html HTTP header. The response body contains the JSON representation of the query parameters: { "Content-Type": [ "text/html" ], "xss": [ "<img/src/onerror=alert(1)>" ] }. The target's browser processes this text as HTML, encounters the unescaped image tag, fails to load the source, and executes the onerror event handler.
Exploiting the /base64 endpoint utilizes a slightly different vector. The attacker constructs an HTML payload, such as <img src=x onerror=alert(1)>, and encodes it using base64, resulting in PGltZyBzcmM9eCBvbmVycm9yPWFsZXJ0KDEpPg==. They append this encoded string to the endpoint path.
The attacker then appends the content-type=text/html query parameter to the URL. The final exploit string is http://target/base64/PGltZyBzcmM9eCBvbmVycm9yPWFsZXJ0KDEpPg==?content-type=text/html. The server decodes the payload, returns the raw HTML bytes, and assigns the text/html header, leading to immediate script execution in the victim's browser.
Successful exploitation of CVE-2025-45286 yields arbitrary JavaScript execution within the context of the vulnerable application's origin. An attacker can read or modify DOM content, exfiltrate sensitive data such as session cookies or local storage items, and perform actions on behalf of the authenticated user. The impact scope is restricted to the client side, presenting no direct threat to the server infrastructure hosting the go-httpbin instance.
The vulnerability carries a CVSS v3.1 Base Score of 6.1 (Medium), reflected by the vector CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N. The network attack vector and lack of required privileges elevate the risk, while the necessity of user interaction reduces the immediate severity. The scope metric evaluates to "Changed" because the vulnerable server acts as a conduit to attack the user's browser environment.
The Exploit Prediction Scoring System (EPSS) assigns this vulnerability a score of 0.00013, placing it in the 0.02226 percentile. This exceptionally low score indicates a minimal probability of widespread, automated exploitation. The vulnerability resides in a specialized testing utility rather than a ubiquitous consumer application, limiting the targetable attack surface.
The severity of this flaw heavily depends on the deployment architecture. If the go-httpbin service is hosted on a dedicated, isolated domain (e.g., httpbingo.example.com), the risk is minimal due to the Same-Origin Policy. However, if deployed on a shared domain or sub-directory (e.g., api.example.com/httpbin), the injected script can access authentication tokens or interact with adjacent production services.
The primary remediation strategy requires updating the mccutchen/go-httpbin dependency to version 2.18.0 or later. Organizations deploying the standalone httpbingo binary must pull the latest release and redeploy their infrastructure. This patch implements strict content-type whitelisting and context-aware output escaping, resolving the underlying vulnerability.
Administrators must audit their deployment configurations to ensure the UNSAFE_ALLOW_DANGEROUS_RESPONSES environment variable remains undefined or explicitly set to false. Enabling this flag disables the security mechanisms introduced in version 2.18.0 and reinstates the vulnerable behavior. This flag exists solely to support legacy internal testing environments and has no place in production or publicly accessible deployments.
Organizations unable to patch immediately should deploy Web Application Firewall (WAF) rules to inspect inbound requests to the /response-headers and /base64 endpoints. The WAF should drop any request where query parameters contain strings matching common HTML tags, script fragments, or event handlers. Additionally, strict egress filtering on the affected domain can limit data exfiltration via malicious scripts.
As a defense-in-depth measure, security teams should mandate that all diagnostic and testing utilities operate on isolated, untrusted domains. Implementing strict Content Security Policy (CSP) headers on the reverse proxy terminating the go-httpbin traffic provides an additional layer of protection against execution of inline scripts and unauthorized data transmission.
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
go-httpbin mccutchen | <= 2.17.1 | 2.18.0 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-79, CWE-80 |
| Attack Vector | Network |
| CVSS Score | 6.1 |
| EPSS Score | 0.00013 |
| Impact | Reflected Cross-Site Scripting (XSS) |
| Exploit Status | Proof of Concept |
| KEV Status | Not Listed |
The application does not adequately neutralize user-controlled input before placing it in the output used as a web page.