VYPR
Moderate severityNVD Advisory· Published Oct 30, 2024· Updated Jan 10, 2025

Consul Vulnerable To Reflected XSS On Content-Type Error Manipulation

CVE-2024-10086

Description

A vulnerability was identified in Consul and Consul Enterprise such that the server response did not explicitly set a Content-Type HTTP header, allowing user-provided inputs to be misinterpreted and lead to reflected XSS.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Consul server responses lack a Content-Type header, allowing reflected XSS via user-supplied input.

CVE-2024-10086 describes a reflected cross-site scripting (XSS) vulnerability in HashiCorp Consul and Consul Enterprise. The root cause is that the server's HTTP responses do not explicitly set a Content-Type header, leaving the browser to interpret the response content. If an attacker can inject arbitrary HTML or JavaScript into a response (e.g., via a crafted URL), the browser may render it as HTML, enabling XSS [1].

Exploitation requires an attacker to convince a user to visit a maliciously crafted URL. The attack is network-based with low complexity and does not require authentication or special privileges. The vulnerable endpoints include those that return user-controlled data without a proper Content-Type, such as error pages or debug handlers [2].

A successful attack could allow an attacker to execute arbitrary JavaScript in the context of the victim's session with the Consul UI. This could lead to session hijacking, credential theft, or unauthorized actions on the Consul cluster [1].

The vulnerability was fixed in a commit that explicitly sets the Content-Type header to text/plain; charset=utf-8 for responses that may include user input. Users should upgrade to the latest patched version of Consul to mitigate this issue [2].

AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
github.com/hashicorp/consulGo
>= 1.4.1, < 1.20.01.20.0

Affected products

30

Patches

1
07fae7bb0be8

[Security] Fix XSS Vulnerability where content-type header wasn't explicitly set (#21704)

https://github.com/hashicorp/consulsarahalsmillerSep 11, 2024via ghsa
3 files changed · +65 5
  • agent/http.go+34 1 modified
    @@ -6,6 +6,7 @@ package agent
     import (
     	"encoding/json"
     	"fmt"
    +	"github.com/hashicorp/go-hclog"
     	"io"
     	"net"
     	"net/http"
    @@ -43,6 +44,11 @@ import (
     	"github.com/hashicorp/consul/proto/private/pbcommon"
     )
     
    +const (
    +	contentTypeHeader = "Content-Type"
    +	plainContentType  = "text/plain; charset=utf-8"
    +)
    +
     var HTTPSummaries = []prometheus.SummaryDefinition{
     	{
     		Name: []string{"api", "http"},
    @@ -220,6 +226,7 @@ func (s *HTTPHandlers) handler() http.Handler {
     			// If enableDebug register wrapped pprof handlers
     			if !s.agent.enableDebug.Load() && s.checkACLDisabled() {
     				resp.WriteHeader(http.StatusNotFound)
    +				resp.Header().Set(contentTypeHeader, plainContentType)
     				return
     			}
     
    @@ -228,6 +235,7 @@ func (s *HTTPHandlers) handler() http.Handler {
     
     			authz, err := s.agent.delegate.ResolveTokenAndDefaultMeta(token, nil, nil)
     			if err != nil {
    +				resp.Header().Set(contentTypeHeader, plainContentType)
     				resp.WriteHeader(http.StatusForbidden)
     				return
     			}
    @@ -237,6 +245,7 @@ func (s *HTTPHandlers) handler() http.Handler {
     			// TODO(partitions): should this be possible in a partition?
     			// TODO(acl-error-enhancements): We should return error details somehow here.
     			if authz.OperatorRead(nil) != acl.Allow {
    +				resp.Header().Set(contentTypeHeader, plainContentType)
     				resp.WriteHeader(http.StatusForbidden)
     				return
     			}
    @@ -317,6 +326,8 @@ func (s *HTTPHandlers) handler() http.Handler {
     	}
     
     	h = withRemoteAddrHandler(h)
    +	h = ensureContentTypeHeader(h, s.agent.logger)
    +
     	s.h = &wrappedMux{
     		mux:     mux,
     		handler: h,
    @@ -337,6 +348,20 @@ func withRemoteAddrHandler(next http.Handler) http.Handler {
     	})
     }
     
    +// Injects content type explicitly if not already set into response to prevent XSS
    +func ensureContentTypeHeader(next http.Handler, logger hclog.Logger) http.Handler {
    +
    +	return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
    +		next.ServeHTTP(resp, req)
    +
    +		val := resp.Header().Get(contentTypeHeader)
    +		if val == "" {
    +			resp.Header().Set(contentTypeHeader, plainContentType)
    +			logger.Debug("warning: content-type header not explicitly set.", "request-path", req.URL)
    +		}
    +	})
    +}
    +
     // nodeName returns the node name of the agent
     func (s *HTTPHandlers) nodeName() string {
     	return s.agent.config.NodeName
    @@ -380,6 +405,8 @@ func (s *HTTPHandlers) wrap(handler endpoint, methods []string) http.HandlerFunc
     				"from", req.RemoteAddr,
     				"error", err,
     			)
    +			//set response type to plain to prevent XSS
    +			resp.Header().Set(contentTypeHeader, plainContentType)
     			resp.WriteHeader(http.StatusInternalServerError)
     			return
     		}
    @@ -406,6 +433,8 @@ func (s *HTTPHandlers) wrap(handler endpoint, methods []string) http.HandlerFunc
     				"from", req.RemoteAddr,
     				"error", errMsg,
     			)
    +			//set response type to plain to prevent XSS
    +			resp.Header().Set(contentTypeHeader, plainContentType)
     			resp.WriteHeader(http.StatusForbidden)
     			fmt.Fprint(resp, errMsg)
     			return
    @@ -585,6 +614,8 @@ func (s *HTTPHandlers) wrap(handler endpoint, methods []string) http.HandlerFunc
     					resp.Header().Add("X-Consul-Reason", errPayload.Reason)
     				}
     			} else {
    +				//set response type to plain to prevent XSS
    +				resp.Header().Set(contentTypeHeader, plainContentType)
     				handleErr(err)
     				return
     			}
    @@ -596,6 +627,8 @@ func (s *HTTPHandlers) wrap(handler endpoint, methods []string) http.HandlerFunc
     		if contentType == "application/json" {
     			buf, err = s.marshalJSON(req, obj)
     			if err != nil {
    +				//set response type to plain to prevent XSS
    +				resp.Header().Set(contentTypeHeader, plainContentType)
     				handleErr(err)
     				return
     			}
    @@ -606,7 +639,7 @@ func (s *HTTPHandlers) wrap(handler endpoint, methods []string) http.HandlerFunc
     				}
     			}
     		}
    -		resp.Header().Set("Content-Type", contentType)
    +		resp.Header().Set(contentTypeHeader, contentType)
     		resp.WriteHeader(httpCode)
     		resp.Write(buf)
     	}
    
  • agent/http_test.go+28 4 modified
    @@ -639,14 +639,14 @@ func TestHTTPAPIResponseHeaders(t *testing.T) {
     	`)
     	defer a.Shutdown()
     
    -	requireHasHeadersSet(t, a, "/v1/agent/self")
    +	requireHasHeadersSet(t, a, "/v1/agent/self", "application/json")
     
     	// Check the Index page that just renders a simple message with UI disabled
     	// also gets the right headers.
    -	requireHasHeadersSet(t, a, "/")
    +	requireHasHeadersSet(t, a, "/", "text/plain; charset=utf-8")
     }
     
    -func requireHasHeadersSet(t *testing.T, a *TestAgent, path string) {
    +func requireHasHeadersSet(t *testing.T, a *TestAgent, path string, contentType string) {
     	t.Helper()
     
     	resp := httptest.NewRecorder()
    @@ -661,6 +661,9 @@ func requireHasHeadersSet(t *testing.T, a *TestAgent, path string) {
     
     	require.Equal(t, "1; mode=block", hdrs.Get("X-XSS-Protection"),
     		"X-XSS-Protection header value incorrect")
    +
    +	require.Equal(t, contentType, hdrs.Get("Content-Type"),
    +		"")
     }
     
     func TestUIResponseHeaders(t *testing.T) {
    @@ -680,7 +683,28 @@ func TestUIResponseHeaders(t *testing.T) {
     	`)
     	defer a.Shutdown()
     
    -	requireHasHeadersSet(t, a, "/ui")
    +	//response header for the UI appears to be being handled by the UI itself.
    +	requireHasHeadersSet(t, a, "/ui", "text/plain; charset=utf-8")
    +}
    +
    +func TestErrorContentTypeHeaderSet(t *testing.T) {
    +	if testing.Short() {
    +		t.Skip("too slow for testing.Short")
    +	}
    +
    +	t.Parallel()
    +	a := NewTestAgent(t, `
    +		http_config {
    +			response_headers = {
    +				"Access-Control-Allow-Origin" = "*"
    +				"X-XSS-Protection" = "1; mode=block"
    +				"X-Frame-Options" = "SAMEORIGIN"
    +			}
    +		}
    +	`)
    +	defer a.Shutdown()
    +
    +	requireHasHeadersSet(t, a, "/fake-path-doesn't-exist", "text/plain; charset=utf-8")
     }
     
     func TestAcceptEncodingGzip(t *testing.T) {
    
  • .changelog/21704.txt+3 0 added
    @@ -0,0 +1,3 @@
    +```release-note:security
    +Explicitly set 'Content-Type' header to mitigate XSS vulnerability.
    +```
    \ No newline at end of file
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

5

News mentions

0

No linked articles in our index yet.