Umbraco CMS <= 7.2.1 is vulnerable to local file inclusion (LFI) in the ClientDependency package included in a default installation. Whether this vulnerability is exploitable depends on a number of configuration options, and on the exact version of Umbraco installed.
The ClientDependency package, used by Umbraco, exposes the “DependencyHandler.axd” file in the root of the website. This file is used to combine and minify CSS and JavaScript files, which are supplied in a base64 encoded string.
A legitimate request to this page looks something like this:
https://localhost/DependencyHandler.axd?s=L3VtYnJhY28vbGliL2pxdWVyeS9qcXVlcnktdWktMS4xMC4zLmN1c3R vbS5taW4uanM7L3VtYnJhY28vbGliL2FuZ3VsYXIvMS4xLjUvYW5ndWxhci1jb29raWVzLm1pbi5qczsvdW1icmFjby9saWI vYW5ndWxhci8xLjEuNS9hbmd1bGFyLW1vYmlsZS5qczsvdW1icmFjby9saWIvYW5ndWxhci8xLjEuNS9hbmd1bGFyLXN hbml0aXplLm1pbi5qczsvdW1icmFjby9saWIvYW5ndWxhci9hbmd1bGFyLXVpLXNvcnRhYmxlLmpz&t=Javascript&cdv=1
Base64 decoding the “s” parameter gives the following list of files (semi-colon separated):
/umbraco/lib/jquery/jquery-ui-1.10.3.custom.min.js;/umbraco/lib/angular/1.1.5/angular-cookies.min.js;/umbraco/lib/angular/1.1.5/angular-mobile.js;/umbraco/lib/angular/1.1.5/angular-sanitize.min.js;/umbraco/lib/angular/angular-ui-sortable.js
These files are retrieved, server-side, over HTTP by the script, and are then minified and served back to the user.
Umbraco <= 7.0.4 and <= 6.2.0
In Umbraco versions 7.0.0 <= n <= 7.0.4 (which corresponds to ClientDependency versions <= 1.7.1.1), it is possible to access files on the server by passing local filesystem paths to the “s” parameter directly. An example path is shown below:
C:\Windows\win.ini
This is then base64 encoded, and passed as the “s” parameter to create the following URL:
https://localhost/DependencyHandler.axd?s=QzpcV2luZG93c1x3aW4uaW5p&t=CSS&…
When the URL is requested, the application then serves the (minified) contents of the file:
HTTP/1.1 200 OK <...> Content-Length: 82 ;for 16-bit app support[fonts][extensions][mci extensions][files][Mail]MAPI=1
Umbraco <= 7.2.1 and <= 6.2.4
On Umbraco versions >= 7.1.0 (ClientDependency versions 1.7.1.2 <= n <= 1.8.1), local filesystem paths are rejected, as they are not valid absolute URIs. To try and get around this, the most obvious thing to try would be using file URIs, such as the following:
file:///c:/windows/win.ini
However, attempts to make a request with this path resulted in the following error in the Umbraco log file:
Could not load file contents from file:///c:/windows/win.ini Domain is not white-listed.
The script validates the hostname in the URI against an approved white-list of hostnames. By default, this list will only include the current hostname of the server (more on this later). As the file URI doesn’t include a hostname, this check fails, and the URI is rejected.
Despite the fact that file URIs are almost always used for files on the local system, they do support the inclusion of the hostname in the URI.
This is almost always left blank (and is the reason that file URIs usually have three forward slashes after the colon). The next thing to try was a file URI with localhost as the hostname, such as the following:
file://localhost/c:/Windows/win.ini
This filepath also failed. After some digging by Phill (one of our .NET guys), we discovered that the second colon in the URI (c:) was causing .NET to throw an error. There’s some discussion online about Windows supporting the use of pipes after the drive letter (below), but this didn’t seem to work.
file:///c|/Windows/win.ini
The next thing we tried was network shares. Windows creates a number of administrative shares by default (such as c$ and admin$), and normally accessing these shares requires administrative privileges; however accessing them from localhost doesn’t. Using this, we came up with the following filepath:
file://localhost/c$/Windows/win.ini
Base64 encoding this gives the following URL:
https://localhost/DependencyHandler.axd?s=ZmlsZTovL2xvY2FsaG9zdC9jJC9XaW5kb3dzL3dpbi5pbmk=&t=CSS&cdv=1
HTTP/1.1 200 OK <...> Content-Length: 82 ;for 16-bit app support[fonts][extensions][mci extensions][files][Mail]MAPI=1
Umbraco Website Hostnames
At this point we had a working exploit against the latest version of Umbraco, so I reported the vulnerability to the Umbraco developers. At this point, things get a little more complicated.
When you install Umbraco using the default WebMatrix installer, it installs your site with the hostname “localhost” – which is the hostname that we use in the file URI. Both my test install and the client’s install were using this hostname, so this appears to be a fairly common way of setting up Umbraco. Because the site’s hostname is “localhost”, it automatically whitelists the “localhost” domain, allowing you to include files from there, even if you’re accessing the Umbraco site using it’s external FQDN. When the Umbraco developers were looking into this vulnerability, they found that if you installed the Umbraco site with a proper FQDN for its hostname (or another other than localhost), then you would get a “domain has not been white-listed” error when attempting to use the file URIs above.
After some experimentation, we found that it was possible to exploit this vulnerability when Umbraco was installed with a proper hostname, but only in specific circumstances.
The first is that the server has to be able to resolve the hostname the site is installed under to itself (which would generally be the case, although it has to resolve to the server’s internal IP).
Secondly, trying to access the administrative shares using the server’s FQDN appears to require local admin rights (although strangely it doesn’t if you try through Windows Explorer). This would mean the vulnerability would only be exploitable if the webserver was running with local admin rights (a very bad idea, but depressingly common). In this case, a filepath including the FQDN could be used:
file://umbraco.example.org/c$/windows/win.ini
It would also be possible to access any normal Windows shares created on the server (assuming the account had appropriate permissions) – however this would require a badly configured share on the webserver, and the attacker to have a good knowledge of which shares existed (which is unlikely).
If the website is installed on localhost, then it needs a way to find it’s (external) FQDN, and in this case, ClientDependency is getting that information from the “Host” HTTP header, and then white-lists that domain for inclusion. By spoofing this header, it is possible to add “localhost” to the list of allowed domains, assuming that the webserver has a binding for * (which is a common configuration). A filepath pointing to localhost can then be used:
file://localhost/c$/Windows
umbraco.example.org
GET /DependencyHandler.axd?s=ZmlsZTovL2xvY2FsaG9zdC9jJC9XaW5kb3dzL3dpbi5pbmk=&t=CSS&cdv=1 Host: localhost
Using a tampered Host header bypasses the whitelist protection (and no errors warning that the domain hadn’t been whitelisted appear in the log files. However, I had mixed results trying to exploit this – I kept getting “access denied” errors from Windows when it tried to access the c$ share. Whether this technique is exploitable or not may depend on the exact configuration of the server.
Remote File Inclusion
Since we can tamper the Host header to add arbitrary domains to the whitelist, this means we can also make requests out to other webservers over HTTP. By setting our filepath to a plain https:// URI (such as google.com), and modifying the Host header to match the domain in the filepath, we can cause the webserver to fetch other webpages for us:
https://umbraco.example.org/DependencyHandler.axd?s=aHR0cDovL2dvb2dsZS5jb20=&t=CSS&cdv=1 HTTP/1.1 Host: google.com <...> HTTP/1.1 200 OK <...> Content-Length: 51514 <!doctype html><html itemscope="" itemtype="https://schema.org/WebPage" lang="en-GB"><head><meta content="/images/google_favicon_128.png" itemprop="image"><title>Google</title><...>
The requirement to spoof a Host header prevents this from being usable in a cross-site scripting attack (and if you can inject arbitrary headers into your victim’s requests, you probably don’t need to do XSS).
However this could be used to proxy traffic through a vulnerable site (although you’re limited to GET requests, and the minification of responses may cause issues). A more interest use would be using this to gain access to webservers on the same network as the Umbraco server (which would hopefully be in a DMZ, but often isn’t), or to other internal resources.
Solution
The Umbraco team have released a fixed version of the ClientDependency package. This can either be updated with NuGet, or you can manually replace the relevant DLL on your sites. Full details of the fix on the Umbraco security advisory.
Thoughts
There are a few things to take away from this. Firstly, from the developer’s side:
- There are lots of uncommon types of URI (data URIs are another one that can often be exploited), and whether or not different functions will support them is fairly random. If you’re only expecting http(s) URIs, then explicitly whitelist them and reject all others.
- HTTP headers should be considered untrusted, like any other form of user input.
There are also some points about defence-in-depth from the sysadmin’s side:
- Never let webservers run with local admin rights (for obvious reasons).
- Never put web facing servers on your internal network (that’s what a DMZ is for).
- Where possible, ensure that websites are given explicit hostnames, so that they do not respond to arbitrary Host headers.
- Fix those low-risk path disclosure issues (because they make LFI much easier to exploit).
As a final note I’d like to give some credit the Umbraco developer team.
It’s never nice to get a security issue reported to you on a Friday (sorry guys), but the fact that it was investigated and I got a response that same day, and that a fix was developed and tested over the weekend is a very encouraging sign, especially given the number of companies that ignore security reports, or take weeks to respond to them.