Remark42: Cross-Site Scripting (XSS) on /api/v1/img via content-type spoofing
Description
Remark42 image proxy suffers from XSS due to content-type spoofing; an attacker can serve arbitrary HTML/JS under Remark42's origin, fixed in v1.16.0.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Remark42 image proxy suffers from XSS due to content-type spoofing; an attacker can serve arbitrary HTML/JS under Remark42's origin, fixed in v1.16.0.
Vulnerability
Remark42 versions 1.6.0 through 1.15.0 contain a Cross-Site Scripting (XSS) vulnerability in the image proxy at /api/v1/img [1]. The proxy downloads an image from a user-supplied URL by checking only the Content-Type header of the remote response; if it starts with image/, the proxy accepts the bytes without validating the actual content [1]. Later, when serving the cached image, http.DetectContentType inspects the bytes to set the response Content-Type. An attacker can exploit the inconsistency by hosting a URL that claims Content-Type: image/png but returns an HTML/JavaScript body: the download check passes, but the serving path emits Content-Type: text/html, causing the browser to render attacker-controlled HTML/JavaScript within Remark42's origin [1].
Exploitation
An attacker does not need a Remark42 account on the target instance. The attacker only needs to host a malicious upstream URL that advertises Content-Type: image/png but serves arbitrary HTML/JavaScript content [1]. The attacker then delivers the proxy link (e.g., https://remark42.example.com/api/v1/img?src=MALICIOUS_URL) to a victim via email, direct message, or by embedding it on another website. No user interaction beyond clicking the link is required; the browser will render the HTML/JavaScript in the context of the Remark42 origin.
Impact
Successful exploitation allows arbitrary JavaScript execution in the victim's browser within the Remark42 origin. This can lead to theft of authentication cookies, session hijacking, defacement, or extraction of sensitive information displayed by Remark42 [1]. The attacker gains the same privileges as the victim user, potentially including admin access if the victim is an administrator.
Mitigation
The vulnerability is fixed in Remark42 version 1.16.0 [2]. The fix includes rejecting non-image content-types in the image proxy and adding X-Content-Type-Options: nosniff headers to prevent MIME-sniffing [3]. Users should upgrade to v1.16.0 or later. No workaround is available for unpatched versions; administrators should restrict access to the image proxy if immediate upgrade is not possible.
AI Insight generated on Jun 17, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected products
2Patches
178d6de6bce1eAdd X-Content-Type-Options and Referrer-Policy security headers
2 files changed · +14 −1
backend/app/rest/api/rest.go+10 −1 modified@@ -618,7 +618,14 @@ func cacheControl(expiration time.Duration, version string) func(http.Handler) h } } -// securityHeadersMiddleware sets security-related headers: Content-Security-Policy and Permissions-Policy +// securityHeadersMiddleware sets security-related headers: +// - Content-Security-Policy: controls which resources the browser is allowed to load +// - Permissions-Policy: disables browser features (camera, mic, etc.) not needed by a comment widget +// - X-Content-Type-Options: prevents browsers from MIME-sniffing responses away from the declared type, +// stopping e.g. a user-uploaded image from being reinterpreted as executable HTML/JS +// - Referrer-Policy: controls how much URL information leaks in the Referer header on cross-origin +// requests; "strict-origin-when-cross-origin" sends only the origin (no path) to other domains +// and nothing at all on HTTPS→HTTP downgrades func securityHeadersMiddleware(imageProxyEnabled bool, allowedAncestors []string) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -632,6 +639,8 @@ func securityHeadersMiddleware(imageProxyEnabled bool, allowedAncestors []string } w.Header().Set("Content-Security-Policy", fmt.Sprintf("default-src 'none'; base-uri 'none'; form-action 'none'; connect-src 'self'; frame-src 'self' mailto:; img-src %s; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; font-src data:; object-src 'none'; frame-ancestors %s;", imgSrc, frameAncestors)) w.Header().Set("Permissions-Policy", "accelerometer=(), autoplay=(), camera=(), cross-origin-isolated=(), display-capture=(), encrypted-media=(), fullscreen=(), geolocation=(), gyroscope=(), keyboard-map=(), magnetometer=(), microphone=(), midi=(), payment=(), picture-in-picture=(), publickey-credentials-get=(), screen-wake-lock=(), sync-xhr=(), usb=(), xr-spatial-tracking=(), clipboard-read=(), clipboard-write=(), gamepad=(), hid=(), idle-detection=(), interest-cohort=(), serial=(), unload=(), window-management=()") + w.Header().Set("X-Content-Type-Options", "nosniff") + w.Header().Set("Referrer-Policy", "strict-origin-when-cross-origin") next.ServeHTTP(w, r) }) }
backend/app/rest/api/rest_test.go+4 −0 modified@@ -352,6 +352,8 @@ func TestRest_securityHeaders(t *testing.T) { defer resp.Body.Close() assert.Equal(t, http.StatusOK, resp.StatusCode) assert.Contains(t, resp.Header.Get("Content-Security-Policy"), "img-src *;") + assert.Equal(t, "nosniff", resp.Header.Get("X-Content-Type-Options")) + assert.Equal(t, "strict-origin-when-cross-origin", resp.Header.Get("Referrer-Policy")) teardown() // check CSP with proxy enabled @@ -364,6 +366,8 @@ func TestRest_securityHeaders(t *testing.T) { defer resp.Body.Close() assert.Equal(t, http.StatusOK, resp.StatusCode) assert.Contains(t, resp.Header.Get("Content-Security-Policy"), "img-src 'self';") + assert.Equal(t, "nosniff", resp.Header.Get("X-Content-Type-Options")) + assert.Equal(t, "strict-origin-when-cross-origin", resp.Header.Get("Referrer-Policy")) } func TestRest_subscribersOnly(t *testing.T) {
Vulnerability mechanics
Root cause
"The image proxy validates the remote resource solely by the Content-Type header without inspecting the actual bytes, while the serving path derives the Content-Type by sniffing those bytes, creating an inconsistency that allows HTML/JavaScript to be served as text/html."
Attack vector
An attacker hosts a URL that advertises `Content-Type: image/png` but returns an HTML/JavaScript body. The download check in `downloadImage()` accepts it because the header starts with `image/`; the serving path sniffs the body and emits `Content-Type: text/html`; the browser renders the attacker-controlled HTML/JavaScript as a document within Remark42's origin [ref_id=1]. Exploitation requires no Remark42 account on the target instance; the attacker only needs to host the malicious upstream URL and deliver the proxy link to a victim by any means, such as email, direct message, or a link on another website [ref_id=1].
Affected code
The image proxy in `backend/app/rest/proxy/image.go` — `downloadImage()` (lines 189–206) validates the remote resource solely by inspecting the `Content-Type` header without examining the actual bytes. The serving path in `Handler()` (line 131) calls `ImgContentType()` in `backend/app/store/image/image.go` (lines 242–249), which uses `http.DetectContentType` and returns whatever MIME type the body sniffs to, including `text/html`.
What the fix does
The patch adds `X-Content-Type-Options: nosniff` and `Referrer-Policy: strict-origin-when-cross-origin` headers to the existing `securityHeadersMiddleware` in `backend/app/rest/api/rest.go` [patch_id=6240375]. The `X-Content-Type-Options: nosniff` header prevents browsers from MIME-sniffing the response body away from the declared Content-Type, which stops an attacker's HTML/JavaScript payload from being rendered as a document even if the body sniffs as `text/html` [ref_id=2]. The advisory notes that the full fix in v1.16.0 also includes strict content-type validation in `SafeImgContentType` and a sandboxed CSP on image endpoints [ref_id=1].
Preconditions
- inputAttacker must host a URL that returns Content-Type: image/png but an HTML/JavaScript body
- inputVictim must open the proxy URL (https:///api/v1/img?src=<base64(attacker-host)>) in a browser
- authNo Remark42 account or authentication required on the target instance
Generated on Jun 17, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
3- github.com/umputun/remark42/commit/78d6de6bce1e961f023969da3ec8a00dd80c9ae8mitrex_refsource_MISC
- github.com/umputun/remark42/releases/tag/v1.16.0mitrex_refsource_MISC
- github.com/umputun/remark42/security/advisories/GHSA-4c8j-mgm4-qqvpmitrex_refsource_CONFIRM
News mentions
0No linked articles in our index yet.