SSRF to Stored XSS via SVG: Turning Fetches into Script Sink Chains
Alone, SSRF is "server fetched my URL." Alone, SVG XSS is "I uploaded a script-shaped image." Together, the server fetches attacker SVG and stores it on a media domain victims trust.
That chain is worth more than either half.
Find a fetch-and-store sink
Look for features that import by URL:
- "Add image from link"
- link unfurlers that cache Open Graph images
- webhook test buttons that store response bodies
- PDF/logo importers for white-label branding
- "clone from template URL" flows in design tools
Point them at a Collaborator URL first to prove server-side fetch. Then host an SVG on a server you control:
<svg xmlns="http://www.w3.org/2000/svg" onload="alert(document.domain)">
If the app stores the bytes on cdn.target.com and serves them as image/svg+xml, you may have stored XSS on that origin. Check Content-Type and Content-Disposition. Attachment download lowers impact; inline SVG raises it.
Redirect tricks help when the first hop must look like a PNG: serve 302 from evil/logo.png to evil/x.svg and see whether the importer follows and stores the final body.
Content-Type confusion at your host matters. Some importers trust the URL extension; others trust the response header; a few trust magic bytes. Probe each assumption with a canary that is SVG bytes labeled as image/png and the reverse.
Why SSRF matters in the chain
Some programs already sanitize uploads but trust remote imports. Others block SVG uploads by extension yet follow redirects to .svg. SSRF-shaped importers may hit internal template stores—less common, higher severity when the internal object is later rendered for staff.
I've also seen the server rewrite the SVG through a "safe" optimizer that strips onload but leaves <script> or href="javascript:" in <a>. Test after the optimizer, not only on your origin. ForeignObject with embedded HTML is another bypass class when sanitizers only scrub attributes on <svg> itself.
Prove victim impact
Use two accounts when the image is private. Public CDNs are easier: open the stored URL in a clean profile. Prefer document.domain or a canary cookie read over noisy alerts in the final report.
Note whether the XSS origin is the main app or a cookie-less media host. Severity follows cookie scope. If the media host sets a session cookie for the parent via a poorly scoped domain attribute, say that explicitly—that upgrades a "SVG on CDN" note into account impact.
Fixes to recommend
Disable SVG on import, re-encode images to raster server-side, serve user media with restrictive CSP and sandbox, and treat URL import as untrusted as file upload. Block redirects to unexpected content types when the feature promised a photo.
If the importer stores on an object store with a predictable key, include the final public URL pattern in the report so triage can reproduce without your Collaborator still running. A dead OOB host should not be required to verify stored XSS.
Fetch is the delivery truck. SVG is the payload. Show both hops or you leave half the severity on the table.