Mar 6, 2026·5 min read·5 visits
Versions of go-httpbin before 2.18.0 fail to sanitize output when the Content-Type header is user-controlled. Attackers can craft URLs that force the server to respond with text/html, executing arbitrary JavaScript in the victim's browser. The fix enforces HTML escaping for unsafe content types.
A reflected Cross-Site Scripting (XSS) vulnerability has been identified in mccutchen/go-httpbin versions prior to 2.18.0. The vulnerability stems from improper handling of user-controlled Content-Type headers in endpoints such as /response-headers and /base64. By manipulating these parameters, an attacker can force the application to serve a response with a text/html MIME type containing malicious JavaScript, which is then executed by the victim's browser.
The mccutchen/go-httpbin library is a Go implementation of the popular httpbin service, designed to test HTTP requests and responses. It provides various endpoints that echo back request data, including headers, cookies, and body content. A core feature of httpbin is the ability for the client to dictate the behavior of the server via query parameters, including setting specific response headers.
The vulnerability, tracked as CVE-2025-45286, is a Reflected Cross-Site Scripting (XSS) flaw located in the response generation logic. Specifically, endpoints like /response-headers and /base64 allow a user to define the Content-Type of the HTTP response. In vulnerable versions, the application reflects user input (such as query parameters or decoded path segments) directly into the response body without validation or escaping, even when the user has specified a Content-Type that browsers interpret as executable (e.g., text/html).
This behavior creates a security gap where the server acts as an unwitting host for malicious scripts. Because the response is served from the go-httpbin domain, any script executed via this vector runs within the origin of that domain. This allows attackers to bypass Same-Origin Policy (SOP) protections, potentially accessing sensitive data stored in the browser for that origin.
The root cause of this vulnerability is the lack of context-aware output encoding. The application treats the Content-Type header and the response body as independent variables controlled by the user, without enforcing safety invariants between them.
In a standard XSS scenario, an application accepts input and displays it to the user. If the application fails to escape special characters (like < and >), the browser treats the data as code. go-httpbin is intended to echo data, so strict validation is often counter-productive to its utility as a debugging tool. However, the flaw arises because the application permits the Content-Type to be set to text/html while simultaneously echoing raw JSON or base64-decoded data into the body.
When a browser receives a response with Content-Type: text/html, it parses the body using its HTML parser. If the body contains a JSON object like {"key": "<script>alert(1)</script>"}, the HTML parser encounters the <script> tag and executes it immediately. The application logic did not anticipate that the JSON echo functionality could be weaponized by simply changing the MIME type.
The vulnerability was addressed in commit 0decfd1a2e88d85ca6bfb8a92421653f647cbc04 by introducing a whitelist of safe content types and enforcing HTML escaping for anything else.
Vulnerable Logic (Conceptual):
Prior to the patch, the handler for /response-headers simply iterated over the provided query parameters, set them as headers, and then wrote the map to the response body using a JSON encoder.
// Pseudo-code of vulnerable logic
func responseHeaders(w http.ResponseWriter, r *http.Request) {
// 1. Set headers based on query params
for k, v := range r.URL.Query() {
w.Header().Set(k, v[0])
}
// 2. Write body directly
json.NewEncoder(w).Encode(r.URL.Query())
}Patched Logic:
The fix introduces a check mustEscapeResponse(contentType) and a map of safeContentTypes (including application/json, text/plain, etc.). If the requested Content-Type is not in the safe list (e.g., it is text/html), the application now escapes the input before writing it to the body.
// Logic from the fix
var safeContentTypes = map[string]bool{
"text/plain": true,
"application/json": true,
"application/octet-string": true,
// ...
}
// In the handler
contentType := w.Header().Get("Content-Type")
if h.mustEscapeResponse(contentType) {
// Apply HTML escaping to keys and values
// effectively neutralizing <script> tags into <script>
encodedKey := html.EscapeString(key)
encodedVal := html.EscapeString(val)
// ... write escaped data ...
}Additionally, the developers added an opt-in flag -unsafe-allow-dangerous-responses for users who rely on the legacy behavior, prioritizing security by default.
Exploiting this vulnerability requires an attacker to trick a victim into visiting a specially crafted URL. The attack vector relies on the reflection of the Content-Type header and the injection of HTML payloads.
Scenario 1: The /response-headers Endpoint
The attacker constructs a URL that sets the content type to HTML and injects a script into a dummy parameter.
http://target.com/response-headers?Content-Type=text/html¶m=<script>alert(document.domain)</script>Content-Type: text/html. It constructs a JSON body containing the key param and the value <script>alert(document.domain)</script>.Scenario 2: The /base64 Endpoint
The /base64 endpoint decodes a base64 string from the URL path. This allows for arbitrary binary or text injection.
<img src=x onerror=alert(1)> into base64: PGltZyBzcmM9eCBvbmVycm9yPWFsZXJ0KDEpPg==.http://target.com/base64/PGltZyBzcmM9eCBvbmVycm9yPWFsZXJ0KDEpPg==?Content-Type=text/htmlonerror event.The impact of this vulnerability is classified as Medium (CVSS 6.1), primarily because it requires user interaction (UI:R). However, the consequences of successful exploitation in a production environment or an internal development network can be significant.
Confidentiality & Integrity:
Since the script executes in the context of the go-httpbin origin, an attacker can access any cookies, local storage, or session tokens associated with that domain. If the httpbin instance is hosted on an internal network (e.g., intranet-tools.corp.local) and accessible without authentication, an attacker could use the victim's browser as a proxy to perform actions on other internal services or exfiltrate internal data.
Scope Change (S:C): The CVSS vector includes Scope: Changed because the vulnerable component (the server) impacts a separate component (the victim's browser/user session). This elevates the severity as the server itself is not the final target, but the vector for attacking client-side trust.
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
mccutchen/go-httpbin mccutchen | < 2.18.0 | 2.18.0 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-79 (Reflected XSS) |
| CVSS v3.1 | 6.1 (Medium) |
| Attack Vector | Network |
| Exploit Status | PoC Available |
| Affected Versions | < 2.18.0 |
| Patch Date | 2025-03-20 |
Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')