Hacker News new | past | comments | ask | show | jobs | submit login
SVG images can contain JavaScript (github.com/berthubert)
93 points by mooreds on Jan 21, 2024 | hide | past | favorite | 50 comments



I'm kind of confused by the title. This shouldn't be a surprise/news as SVG explicitly uses JavaScript as its scripting language. That's why there's an SVG DOM. Yes you can do animations with SMIL, but JavaScript is explicitly there for more complex interactions.

https://www.w3.org/TR/SVG11/script.html


There's a huge difference between an HTML page being able to animate contained SVG, and SVG being allowed to contain Javascript in itself.

There's a huge difference between Javascript-in-SVG being allowed to manipulate the SVG itself, versus being interpreted as part of the HTML page.


I don't disagree with these statements, but I don't understand how they're relevant to my point. The Javascript-in-SVG is what I pointed to in the spec.

18.2: "A ‘script’ element is equivalent to the ‘script’ element in HTML and thus is the place for scripts (e.g., ECMAScript). Any functions defined within any ‘script’ element have a "global" scope across the entire current document."


Many many aeons ago when I was still working on the webkit SVG implementation[1], we had to deal with this. Essentially all other complex "images" are in isolated worlds from the page itself, e.g. they're static images (no issue at all), or a sequence of frames that require periodic repaints[2].

Now logically an SVG "image" is a webpage, and you could easily just embed an iframe or such like (I _think_ <object> also worked? long time ago now) and it would just work because the browser engine understands that an iframe means "time for a whole new world". A big problem for SVG images in image tags is that it could just arbitrarily make any <img> tag also now have the same essential "I'm a full web page now" semantic, which was really something webkit wasn't really ready for at the time. The original implementation was not super great and I hope/assume it's somewhat better now, but at the time we essentially made it so we had a hacked in a image element subclass that would copy the render image from a pseudo iframe. There are a whole bunch of other issues that had to be addressed, and also at the time weren't covered by any specification, like what happens with event handlers in the SVG vs the Image element.

Now you run into performance problems. The overwhelming majority of SVGs you hit are entirely static, a small subset have declarative animation, so now we have a question: do we really want to burn the cost of instantiating creating essentially a full iframe for every SVG image? that impacts load time and memory use so you want to avoid it if possible, and in principle the engine can detect whether the SVG image contains any scripts or event handlers, etc. But if you do try to do this, then you hit issues if the embedding page then tries to interact with the image, and trying to handle that kind of change dynamically would involve that kind of complexity that leads to errors (oh for safer memory handling in C++, but at least these days there are better options everyone is working on).

IIRC the end solution that every browser went for was to just say "screw this" and drop most SVG features from SVGs loaded through image elements.

One thing that people don't understand when looking at SVG, is that they _think_ it's an image format, but the reality is that was never the actual intent of the specification, and that's why SVG is so insanely complex.

Many aeons ago people were like "what we really need is a standard format for vector graphics", and then some people said "it should support animation". If they stopped there you'd just have an image format. But the commercial groups that worked on the standard, wanted a more, for a few reasons. The big party I saw referenced a lot in the past was Adobe (or Macromedia at the time?). They didn't seem meaningfully involved in the SVG-WG when I was a member so I don't know how accurate the claim is, but essentially what people have said is that Adobe/MM was actually interested in SVG as essentially a future for Flash so wanted the specification to be feature compatible, but also because capitalism wanted to sell their software and so pushed for a specification that was "human writable" but fundamentally you would need well funded commercial software to actually develop content. That seems plausible in the "capitalism is evil" sense, but also the design space for "technical standard", "supports everything flash can do", and "uses xml" is super awful and complicated so I don't think that the complexity and weirdness of SVG is direct evidence of that.

The _much_ _much_ more interesting party that was involved in the SVG WG were japanese makers of mapping software for phones (from prior to and overlapping iPhone era browsers), or such companies targeting the japanese market. I do not understand the exact specifics of the legal (or maybe just contractual?) constraints that they were operating under, but the general gist was that they needed their mapping software to be entirely in terms of actual standards. And this is where the feature additions in SVG go insane. The constraints they were under meant that everything they did had to be a standard, but any implementation had to support the _entire_ standard. At the time SVG started, and for a long time after I had become involved in the SVG WG and then escaped, the html specification was a single standard, so the SVG spec could not just (for example) reference XHR even when it was standardized. As a result for these manufacturers the SVG spec could reference the XML and ECMAScript specs, and then essentially everything else had to be entirely self contained. So if it needed network APIs in JS, they needed to specify them internally, and long story short, that's how the SVG specification got raw socket access (no browser ever supported this :D).

It's also how SVG got profiles: these manufacturers had to be able to say "we fully implement this standard" but at the same time this is meant to be an image format for actual art and such, and so it needed complex blends that these manufacturers could not afford (in terms of hardware perf). Unfortunately these profiles were often mutually incompatible, even for basics like blending, because the profiles were selected on the basis of cost with no consideration of interaction.

Anyway, that's a very abbreviated tale of how the "image format" SVG became the gigantic and complex specification it is.

[1] This came from importing ksvg2 into webkit, and the constant refrain of "webkit just being a fork of khtml" could much more legitimately have applied here. In terms of compatibility with content that actually existed ksvg2 was far more complete than khtml ever was, and the general dearth of non-static or generally complex SVG content meant that for years very little work actually needed to be done on the merged ksvg2 logic beyond tracking changes in the rest of webkit. By the first release of Safari a huge amount of basic compatibility and perf work had been done, by Safari 2 large rearchitecting had also occurred, and around the time I started contributing (while avoiding my thesis) KJS was also being significantly rearchitected. By the Safari for Windows release I would say in essence khtml had been rewritten, and I think JSCs only real remaining architectural link to KJS was that it was still an AST interpreter, but I think that was changed by Safari 4? I could be a little off on timing vs releases because this was a long time ago, but even by that point in history the claims of webkit being khtml were simply not accurate let alone today. Bringing it to modern terms by the point of Safari 1's release WebKit was to KHTML as Blink today is to WebKit.

[2] Apple's webkit used to also support pdfs as images because it just through all "image" data at CoreImage or ImageIO that just sniffed the image type and would work out how to decode a much larger set of image formats than anyone would ever think reasonable. I'm not sure how much that has been cut down now, but this is how things like that OpenEXR vulnerability happened - ImageIO supported OpenEXR a format that wasn't generally considered a "web" image format, but if you throw it at ImageIO it would parse it. ImageIO also supports a wide variety of RAW image formats, and you could have those directly as well.


That is super interesting. Is there some sort of "lower common denominator" svg standard used nowadays on browsers? Is there any implementation that also implements the entire spec?


Every browser implements the full profile, and all of the non-insane parts. I think it was the defunct SVG2 attempt that may have had raw sockets.


There are quite a few ways in which SVGs can contain evil things. iframes, script tags, the use tag (can load external sources), links with javascript, links with base 64 encoded javascript, bloody DOM event handlers (on-click etc).

At my previous job, user uploadable SVGs were the largest source of vulnerabilities in our product.

My unwanted advice: don't let users upload SVGs unless you know what you're doing. We clearly didn't.


If you use mesh gradient with Inkscape, it will append about 17K of Javascript to the end of the SVG that starts like this:

    <script id="mesh_polyfill" type="text/javascript">
It surprised me the first time I saw it.

The script appears to be minimized version of what's here:

https://gitlab.com/inkscape/inkscape/-/tree/master/src/exten...


Does it warn you? This sort of "magic" could do with a giant disclaimer really..


You can tell Inkscape not to insert those scripts (edit -> input/output -> SVG export -> uncheck the two options for inserting javascript), but you have to know to look for them, and I suspect most people don't look at the output SVGs.

These compatibility polyfills seem harmless so it doesn't bother me, but maybe you are asking if Inkscape can be used to pass arbitrary javascript to unsuspecting viewers without warning? The answer to that appears to be "yes" via the extensions system.


It's not malicious, but if you're expecting to be able to use the SVG in the context of an <img> element, or on a site with a strict (in line with security best practice) Content-Security-Policy, this is just a sharp edge that you can cut yourself on because the polyfill simply won't work in those contexts.

I know I'd be annoyed if I realised my image editor had "tried to be clever" and done this under the hood without telling me.


If anybody wants to see an intended use of javascript in SVG images take a look at flamegraph profiles[1]. Very useful to find program hotspots when you can click to focus on particular code paths.

[1] https://brendangregg.com/FlameGraphs/cpu-bash-flamegraph.svg


Still not clear why the javascript needs to live inside the SVG. In my mind, the SVG would have declarative metadata and some external javascript would attach behaviour.


That sounds like a workable model for vector graphics in browsers; it's just not describing SVG's design. It very deliberately was meant to contain not just vector model, but also a dynamic/interactive environment.

From the standard: https://www.w3.org/TR/SVG11/intro.html

"Sophisticated applications of SVG are possible by use of a supplemental scripting language which accesses SVG Document Object Model (DOM), which provides complete access to all elements, attributes and properties. A rich set of event handlers such as ‘onmouseover’ and ‘onclick’ can be assigned to any SVG graphical object. Because of its compatibility and leveraging of other Web standards, features like scripting can be done on XHTML and SVG elements simultaneously within the same Web page."


But we're explicitly criticizing SVG's design! SVG doesn't contain horrible things by accident of implementation, it was specified to contain those horrible things.

Defending against this criticism by saying the behavior is in the spec is not relevant.


I think you're missing that it was a design goal to have that support in the spec. Scripting was a requirement, not a design choice.


Then Javascript inside SVG should not have any power over the HTML page it's contained in.


At the time, it's not what people wanted. It was pretty common for Flash apps to have access to the whole page.


Notably, there's significant restrictions on loading external <script> tags if the original file was loaded through file://. This means that your idea would likely require a static file server or similar.


So a single .svg file can actually be a whole app? Wow we reinvented flash.


SVG was the result of an effort to make an open standard for vector graphics. As is typical for open standards, it wasn't an entirely novel concept. There were several competing alternatives; IIRC, SVG was one of half a dozen proposals submitted, and most of those proposals were evolutions of prior designs/attempts at a standard. While flash was an original design, it was as much a "reinvented Shockwave" as SVG was a "reinvented flash".


That's part of the original design goals for it. Scripting support has been part of it from the very beginning, to allow for time-based animation etc.


While scripting could be used for time-based animations, the preference was to use the declarative SMIL. Scripting support was intended to allow more sophisticated interactive graphics.


Hand authoring complex SMIL animations really sucks. The declarative nature introduces obnoxious limitations, and none of the browsers interpret it identically. I’ve spent hours writing it just to find out it only works in chrome, then spend a few more hours hunting down and finding work arounds for all of the bugs in each browser’s rendering engine.


It would have probably taken off if there was a way to have wrapped text in the early days.


Agree. What (legitimate) use case does Javascript inside an SVG address?


Not nearly as portable as what we have now.


Agreed, at some point custom “declarative metadata” just becomes a nonstandard onclick handler.


I have been told that another intended use of javascript was to size a visual element like a rectangle to be the bounding box of a text string.


One fun thing to be aware of is escaping requirements.

  <script>alert("&lt;".length)</script>
In HTML with HTML syntax, this will alert 4, the string being "&lt;".

In HTML with XML syntax (yes, still a thing), or in SVG standalone, or in SVG in HTML of either syntax, this will alert 1, the string being "<".

There’s a lot more one can talk about with this, HTML parser details, script data double escaped state, CDATA, but I’ll leave it at that. Just don’t be surprised when you see this sort of thing in your SVG:

  if (a &lt; b) { … }
Allowing users to place JavaScript inside a <script> tag in HTML but preventing them from breaking out of it is rather difficult, requiring semantic handling of the JavaScript and some rather convoluted changes which could still break things. Similar deal on CSS, just that it can be done perfectly since you don’t have to worry about Function.prototype.toString detecting the changes. But no, if you want to do such a thing, the sensible and principled way is either to serve the document with XML syntax (though there are various problems with this and I don’t advise trying), or to use SVG’s <script> or <style> instead of HTML’s, and do regular HTML entity encoding of the source. <svg><script>…</script></svg>, easy enough.


For this reason, it’s a good rule of thumb to always serve user-uploaded files from a different top-level domain, one which has no session cookies and localstorage and the likes. This way, if someone manages to upload a file with JS in it, and trick a user into opening it, the JS cannot do any harm because it doesn’t have access to anything.

This won’t stop an onslaught of incompetent security researchers from contacting you about an XSS vulnerability, because they think that XSS means anything that can do `alert` and they don’t understand what the XS in XSS stands for, but meanwhile you’re secure even if you forget (or chose to skip) sanitation.

I recognize that this feels a bit out of scope for a self-contained c++ image sharing service, but if you have a more classical cloud setup, you get this for free simply by serving your user-uploaded files from some object storage bucket.


Addendum: this is safe because browsers only run scripts in SVGs when the SVG is served as a page, and not when it’s embedded in a different page as an image or background.

Doing <img src=“evil.svg”> is always safe.


Tangentially, I learned a few days ago that any attribute that can contain a URI will be executed as javascript if the URI uses the javascript: key

For a list, see https://stackoverflow.com/questions/2725156/complete-list-of...


This is true, but also so old that it is well known by now.

It can be blocked by CSP IIRC.


That SVG can execute Javascript also used to be well known, back when SVG was considered the future replacement to Flash. I even remember some websites that were just one handcrafted SVG. And browsers deciding not to execute these scripts when you load an SVG via an <img> tag was a big deal.

Of course SVG didn't become the Flash replacement everyone hoped for, so that knowledge seems to have become more obscure. The same probably happened to knowledge about javascript links after frameworks like react made event handlers the more convenient alternative.


Yeah I once went down the rabbit hole of googling SMIL and there's a nice example on Wikipedia of a live clock, right?

At the time, this example even worked on my phone, will have to check if that's still the case.

Generally, SVG reminds of PDF with its feature creep (foreign entities?)... but I feel web browsers are beating SVG into becoming a sane vector graphics format without such shenanigans.


SVG animation elements are now mostly supported by all modern browsers. You can even use `transform-origin` and `transform-box` CSS properties to control the origin point (this was a major pain point in the past). I think "paced" interpolation is the only important feature that is still missing.


Suppose that all browsers hide javascript-inside-svg behind a preferences checkbox that's turned off by default.

What on the web breaks?

Most of the scripting related to SVGs I've seen are SVGs controlled by javascript in the containing HTML, usually through an HTML framework.

Hell, in the history of the web has there ever been a framework written specifically for the javascript-inside-svg case?

The irony is that svg has not gained generally usability as an uploadable image format precisely because of this feature that nearly no one uses.


For people learning about this today, be sure to see the script-in-SVG quirks, restrictions, and support (or for some features like modules, lack thereof): https://developer.mozilla.org/en-US/docs/Web/SVG/Element/scr..., https://developer.mozilla.org/en-US/docs/Web/SVG/Scripting, and https://web.archive.org/web/20100223210744/http://wiki.svg.o...


The example SVG: https://berthub.eu/trifecta/i/Tcp2y3TbBzU

doesn't execute the javascript when visited directly in any browser I tried. Does it make a difference if it's embedded in a html page?


If linked via an <img> tag, browsers will run the SVG in a script-less context. The problem is direct linking, then you have a potential XSS.

Here it’s the CSP directive (Content-Security-Policy: script-src ‘none’) which is neutering it, as indicated by the comment your got the SVG from.

However from what I remember of my experiments script-src none can severely degrade SVGs even though they don’t use JS, because it affects some styling.


According to the page the HTTP CORS headers prevent JS execution. But the problem remains that svg can embed and execute JS on servers without these non-default headers.


CSP, not CORS.

CORS is for allowing other origins to read resources (and send special requests/special headers). It's not really relevant here.


If you save it locally then drag it into your browser, it will run.

Bert blocked it from running when hosted on his domain a couple days ago https://github.com/berthubert/trifecta/commit/ed7a10d783dda2...


Walkthrough of remote code execution in Scratch Desktop using JavaScript in SVGs: https://web.archive.org/web/20220416143159/https://www.mnemo... (2021)


When doing the assembly instructions for the opensource CNC machine the Shapeoko 2:

https://shapeoko.github.io/Docs/

I worked up a mechanism which allowed opening SVG files in separate windows where they were interactive so that one could click on an entry in the parts list and have the matching parts in the diagram highlighted.

(but for some reason the site isn't loading at the moment)


People forget that SVG was not just meant as a vector graphics format. It was meant as Adobe's competitor to Flash. Adobe wrote most of the original specification, runtime ("Adobe SVG Viewer"), and tooling ("Adobe LiveMotion").

Animation, scripting and multimedia are still in the spec because Flash had those.


It's also XML-based and thus potentially vulnerable to XML XXE inclusions.

XML, the over-engineered-by-committee gift that keeps giving.

Fun fact: until recently my boss' boss Vincent Hardy was a co-editor of the SVG spec.


Good. I'd hate for it to break out of containment!


you can but its not widely supported

and sometimes for security reasons already

been down that rabbit hole when making interactive NFTs without externally hosted images, since that was one of the many criticisms of common implementations of NFTs




Consider applying for YC's Spring batch! Applications are open till Feb 11.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: