mirror of
https://github.com/Comfy-Org/ComfyUI.git
synced 2026-08-24 18:10:27 +08:00
Fix SVG previews broken by the stored-XSS forced-download (#15149)
* Fix SVG previews broken by the stored-XSS forced-download /view and the assets download route force every SVG to application/octet-stream + attachment. That blocks the stored XSS from GHSA-779p-m5rp-r4h4, but it also breaks the SVG node output and Media Assets previews, which request the file with a plain <img>. Exempt only that case. An SVG referenced by an <img> loads in secure static mode with scripting and external references disabled, so the payload cannot fire. The attack needs the SVG to become a document, which arrives with a different Sec-Fetch-Dest. Browsers set that header themselves and page script cannot override it. A missing header, from a non-browser client or a proxy that strips it, fails closed. The blocklist itself is unchanged; this is a call-site gate. * Don't let a cache replay the inline SVG into document context The Sec-Fetch-Dest exemption makes /view and the assets content route vary their Content-Type and Content-Disposition on a request header, but neither response said so. FileResponse emits Last-Modified/ETag and the cache_control middleware skips /view (the filename is in the query string, not the path), so the inline image/svg+xml variant is heuristically cacheable. A cache keyed on the URL alone could hand an entry primed by an <img> load to a later top-level navigation of the same URL, turning the SVG back into a document and re-enabling the stored XSS the forced download blocks. Set Vary: Sec-Fetch-Dest and Cache-Control: no-store on both branches, not just the exempt one: a cached attachment replayed to an <img> would re-break the preview this fix exists to restore. Also strip parameters from content_type before building the assets response. mime_type there is uploader-supplied and unvalidated, and aiohttp rejects a charset in the content_type argument with ValueError, so a stored "image/svg+xml; charset=utf-8" turned a valid inline SVG into a 500. Route-level guards now pin the headers on both branches and the parameterised mime type; all three fail against the previous commit.
This commit is contained in:
committed by
Jedrzej Kosinski
parent
12fc94b336
commit
fa2124c52e
34
server.py
34
server.py
@@ -624,8 +624,9 @@ class PromptServer():
|
||||
# For security, force renderable/active types (HTML, JS,
|
||||
# CSS, SVG, XML — anything that can carry inline <script>
|
||||
# and execute in the page origin) to download instead of
|
||||
# displaying inline, preventing stored XSS. The
|
||||
# attachment disposition is the load-bearing guard: a
|
||||
# displaying inline, preventing stored XSS. SVG loaded
|
||||
# into an <img> is exempt, see renders_safely_as_image.
|
||||
# The attachment disposition is the load-bearing guard: a
|
||||
# bare filename= hint does not force a download per
|
||||
# RFC 6266, so we only attach it on the dangerous branch
|
||||
# to avoid breaking inline display of legitimate images.
|
||||
@@ -635,18 +636,27 @@ class PromptServer():
|
||||
# header's quoted-string and malform the disposition.
|
||||
safe_filename = filename.replace("\\", "\\\\").replace('"', '\\"')
|
||||
disposition = f"filename=\"{safe_filename}\""
|
||||
headers = {"X-Content-Type-Options": "nosniff"}
|
||||
sec_fetch_dest = request.headers.get('Sec-Fetch-Dest')
|
||||
if folder_paths.is_dangerous_content_type(content_type):
|
||||
content_type = 'application/octet-stream'
|
||||
disposition = f"attachment; filename=\"{safe_filename}\""
|
||||
# This response now depends on a request header, so
|
||||
# it must not be reused across destinations.
|
||||
# FileResponse emits Last-Modified/ETag and nothing
|
||||
# sets Cache-Control on /view, which makes it
|
||||
# heuristically cacheable: without these headers a
|
||||
# cache could replay the inline SVG served to an
|
||||
# <img> to a later document navigation of the same
|
||||
# URL and re-enable the stored XSS, or replay the
|
||||
# attachment to an <img> and re-break the preview.
|
||||
headers["Vary"] = "Sec-Fetch-Dest"
|
||||
headers["Cache-Control"] = "no-store"
|
||||
if not folder_paths.renders_safely_as_image(content_type, sec_fetch_dest):
|
||||
content_type = 'application/octet-stream'
|
||||
disposition = f"attachment; filename=\"{safe_filename}\""
|
||||
|
||||
return web.FileResponse(
|
||||
file,
|
||||
headers={
|
||||
"Content-Disposition": disposition,
|
||||
"Content-Type": content_type,
|
||||
"X-Content-Type-Options": "nosniff"
|
||||
}
|
||||
)
|
||||
headers["Content-Disposition"] = disposition
|
||||
headers["Content-Type"] = content_type
|
||||
return web.FileResponse(file, headers=headers)
|
||||
|
||||
return web.Response(status=404)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user