SVG foreignObject XSS: HTML Inside Images That Execute
SVG is XML pretending to be an image. Inside it, <foreignObject> can embed HTML. If that HTML includes script-capable tags and the SVG is opened as a document—or inlined into a same-origin page—your "avatar upload" becomes HTML injection with extra steps.
Plenty of pipelines strip <script> from SVG but forget foreignObject, animation handlers, or <a> with javascript: URLs.
Design systems love SVG icons. That means the same upload path may feed marketing sites, admin panels, and email templates. Map every consumer of the stored object before you decide severity—one rasterized thumbnail does not cancel a raw SVG viewer elsewhere.
Where to aim
- Profile image uploads that allow
image/svg+xml - Icon pipelines that sanitize with regex
- Markdown or rich-text editors that permit inline SVG
- Endpoints that serve SVG with
Content-Type: image/svg+xmland inline disposition
Navigate directly to the uploaded object URL. If the response is image/svg+xml without Content-Disposition: attachment, the browser may treat it as an active document on that origin. That alone can be stored XSS.
foreignObject example shape:
<svg xmlns="http://www.w3.org/2000/svg">
<foreignObject width="100" height="100">
<body xmlns="http://www.w3.org/1999/xhtml">
<img src=x onerror="/* canary */" />
</body>
</foreignObject>
</svg>
Also try onload on SVG elements, <script> with CDATA, and event handlers on <use> when external references are allowed.
Sanitizer blind spots
Some libraries remove scripts but keep foreignObject. Others parse with an HTML parser and mangle namespaces, accidentally creating new vectors. Upload the same bytes as .svg, .xml, and .png with a forged content type. Server-side type sniffing and client-side rendering often disagree.
CSP matters. A strict CSP on the app may not apply to a CDN origin that serves user media. Host-only cookies still stick to the media origin if it shares the parent site's registrable domain carelessly—check cookie Domain attributes before claiming session theft.
Also try compressed SVG (.svgz) and SVGs that reference external entities or remote xlink:href resources when the parser allows it. Even when script is stripped, a hostile external fetch can become an SSRF-shaped side finding—keep that separate unless it clearly ties to XSS.
Proof and severity
Use a canary beacon from inside the SVG script context. Show whether execution is on media.target.example or www.target.example. Sandboxed image rendering (<img src="file.svg">) usually will not run script—that is expected. Direct navigation or inline embedding might. Your PoC must match a realistic victim action: opening a shared media link, viewing a preview that inlines SVG, or hitting an admin "view source file" route.
Check preview UIs carefully. A React component that injects SVG via dangerouslySetInnerHTML can execute even when the raw media URL is cookieless. The stored file is only half the bug; the viewer is the other half.
Recommend serving user SVG only as attachment, converting to safe raster formats, using a real SVG sanitizer that drops foreignObject and event handlers, and isolating media on a cookieless host. Server-side rasterization before any browser sees the bytes removes most of this class at once.
If the program bans SVG uploads entirely after your report, take the win. Images should not need an embedded browser document.