VYPR
Unrated severityNVD Advisory· Published Nov 13, 2019· Updated Aug 5, 2024

CVE-2019-18923

CVE-2019-18923

Description

Insufficient content type validation of proxied resources in go-camo before 2.1.1 allows a remote attacker to serve arbitrary content from go-camo's origin.

AI Insight

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

go-camo before 2.1.1 allows a remote attacker to bypass Content-Type validation by supplying multiple values in the Content-Type header, enabling proxy of arbitrary resources and potential XSS.

Vulnerability

In go-camo versions before 2.1.1, the proxy's content type validation inspects only the first value when the Content-Type header contains multiple values. The code, which relies on glob matching against a whitelist (e.g., image/*), accepts the first value if it matches, ignoring subsequent values [1]. For example, a header Content-Type: image/png, text/html bypasses the filter. This affects all deployments using the default whitelist (image/*) or similar configurations. The vulnerable code path is in pkg/camo/proxy.go lines 453-460 [2].

Exploitation

An attacker needs the ability to control the Content-Type header of a resource that go-camo fetches on behalf of a client. This typically requires the attacker to host a resource under their control (e.g., a malicious file on an attacker-controlled server) and induce the victim to load that URL through the go-camo proxy. The attacker crafts a response with a Content-Type header containing multiple comma-separated values, starting with an allowed type (e.g., image/png) followed by a malicious type (e.g., text/html). Browsers such as Chrome and Firefox will use the second value for rendering, effectively treating the response as the attacker's desired content type [1].

Impact

Successful exploitation allows an attacker to serve arbitrary content (e.g., HTML, JavaScript) as if it originated from the go-camo proxy's origin. This can lead to cross‑site scripting (XSS) attacks, as the malicious payload may bypass same-origin policies. It can also be used for phishing or serving other web‑based attacks. The attacker achieves the ability to inject and render arbitrary content in the context of the domain hosting the go-camo service [1].

Mitigation

The vulnerability is fixed in go-camo version 2.1.1, released by the maintainer [1]. Users should upgrade to go-camo 2.1.1 or later. No workaround is available for earlier versions, as the validation logic must be corrected. There is no indication that this CVE is listed in CISA's Known Exploited Vulnerabilities (KEV) catalog as of the publication date.

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

Affected products

2

Patches

1
c1a5f28e28dd

Merge pull request from GHSA-jg2r-qf99-4wvr

https://github.com/cactus/go-camoeliNov 12, 2019via osv
4 files changed · +67 2
  • CHANGELOG.adoc+3 0 modified
    @@ -19,6 +19,9 @@ toc::[]
     
     == HEAD
     
    +== v2.1.1 - 2019-11-11
    +
    +*   Security fixes / content-type validation
     *   Add `ProxyFromEnvironment` support. This uses HTTP proxies directed by the
         `HTTP_PROXY` and `NO_PROXY` (or `http_proxy` and `no_proxy`) environment
         variables. See {link-proxy-from-env} for more info.
    
  • pkg/camo/helpers_test.go+1 1 modified
    @@ -88,7 +88,7 @@ func bodyAssert(t *testing.T, expected string, resp *http.Response) {
     func headerAssert(t *testing.T, expected, name string, resp *http.Response) {
     	assert.Equal(
     		t, expected, resp.Header.Get(name),
    -		"Expected response header 'Server' not found",
    +		"Expected response header mismatch",
     	)
     }
     
    
  • pkg/camo/proxy.go+29 1 modified
    @@ -11,6 +11,7 @@ import (
     	"errors"
     	"fmt"
     	"io"
    +	"mime"
     	"net"
     	"net/http"
     	"net/url"
    @@ -218,10 +219,12 @@ func (p *Proxy) ServeHTTP(w http.ResponseWriter, req *http.Request) {
     		return
     	}
     
    +	var responseContentType string
     	switch resp.StatusCode {
     	case 200, 206:
     		contentType := resp.Header.Get("Content-Type")
     
    +		// early abort if content type is empty. avoids empty mime parsing overhead.
     		if contentType == "" {
     			if mlog.HasDebug() {
     				mlog.Debug("Empty content-type returned")
    @@ -230,13 +233,36 @@ func (p *Proxy) ServeHTTP(w http.ResponseWriter, req *http.Request) {
     			return
     		}
     
    -		if !p.acceptTypesFilter.CheckPath(contentType) {
    +		// content-type: image/png; charset=...
    +		// be careful of malformed content type records. apparently some browsers
    +		// only loosely validate this, and tend to pick whichever one "seems correct",
    +		// or have a "default fallback" such as text/html, which would be insecure in
    +		// this context.
    +		// content-type: image/png, text/html; charset=...
    +		mediatype, param, err := mime.ParseMediaType(contentType)
    +		if err != nil || !p.acceptTypesFilter.CheckPath(mediatype) {
     			if mlog.HasDebug() {
     				mlog.Debugm("Unsupported content-type returned", mlog.Map{"type": u})
     			}
     			http.Error(w, "Unsupported content-type returned", http.StatusBadRequest)
     			return
     		}
    +
    +		// add params back in, as certain content types have various optional and/or
    +		// required parameters.
    +		// refs: https://www.iana.org/assignments/media-types/media-types.xhtml
    +		responseContentType = mime.FormatMediaType(mediatype, param)
    +
    +		// also check if the parsed content type is empty, just to be safe.
    +		// note: round trip of mediatype and params _should_ be fine, but guard
    +		// against implementation changes or bugs.
    +		if responseContentType == "" {
    +			if mlog.HasDebug() {
    +				mlog.Debug("Unsupported content-type returned")
    +			}
    +			http.Error(w, "Unsupported content-type returned", http.StatusBadRequest)
    +			return
    +		}
     	case 300:
     		http.Error(w, "Multiple choices not supported", http.StatusNotFound)
     		return
    @@ -264,6 +290,8 @@ func (p *Proxy) ServeHTTP(w http.ResponseWriter, req *http.Request) {
     
     	h := w.Header()
     	p.copyHeaders(&h, &resp.Header, &ValidRespHeaders)
    +	// set content type based on parsed content type, not originally supplied
    +	h.Set("content-type", responseContentType)
     	w.WriteHeader(resp.StatusCode)
     
     	// get a []byte from bufpool, and put it back on defer
    
  • pkg/camo/proxy_test.go+34 0 modified
    @@ -101,6 +101,40 @@ func TestBadContentType(t *testing.T) {
     	assert.Nil(t, err)
     }
     
    +func TestContentTypeParams(t *testing.T) {
    +	t.Parallel()
    +	testURL := "http://httpbin.org/response-headers?Content-Type=image/svg%2Bxml;charset%3DUTF-8"
    +	resp, err := makeTestReq(testURL, 200, camoConfig)
    +
    +	assert.Nil(t, err)
    +	if assert.Nil(t, err) {
    +		headerAssert(t, "image/svg+xml; charset=UTF-8", "content-type", resp)
    +	}
    +}
    +
    +func TestBadContentTypeSmuggle(t *testing.T) {
    +	t.Parallel()
    +	testURL := "http://httpbin.org/response-headers?Content-Type=image/png,%20text/html;%20charset%3DUTF-8"
    +	_, err := makeTestReq(testURL, 400, camoConfig)
    +	assert.Nil(t, err)
    +
    +	testURL = "http://httpbin.org/response-headers?Content-Type=image/png,text/html;%20charset%3DUTF-8"
    +	_, err = makeTestReq(testURL, 400, camoConfig)
    +	assert.Nil(t, err)
    +
    +	testURL = "http://httpbin.org/response-headers?Content-Type=image/png%20text/html"
    +	_, err = makeTestReq(testURL, 400, camoConfig)
    +	assert.Nil(t, err)
    +
    +	testURL = "http://httpbin.org/response-headers?Content-Type=image/png%;text/html"
    +	_, err = makeTestReq(testURL, 400, camoConfig)
    +	assert.Nil(t, err)
    +
    +	testURL = "http://httpbin.org/response-headers?Content-Type=image/png;%20charset%3DUTF-8;text/html"
    +	_, err = makeTestReq(testURL, 400, camoConfig)
    +	assert.Nil(t, err)
    +}
    +
     func TestXForwardedFor(t *testing.T) {
     	t.Parallel()
     
    

Vulnerability mechanics

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

References

2

News mentions

0

No linked articles in our index yet.