Part one – intro
Part two – post-auth rce
Part three – pre-auth password retrieval
Part four – pre-auth remote code execution
This post concludes our deep dive into the Railo application server by detailing not only one, but two pre-auth remote code execution vulnerabilities. If you’ve skipped the first three parts of this blog post to get to the juicy stuff, I don’t blame you, but I do recommend going back and reading them; there’s some important information and details back there. In this post, we’ll be documenting both vulnerabilities from start to finish, along with some demonstrations and notes on clusterd’s implementation on one of these.
The first RCE vulnerability affects versions 4.1 and 4.2.x of Railo, 4.2.1 being the latest release. Our vulnerability begins with the file thumbnail.cfm
, which Railo uses to store admin thumbnails as static content on the server. As previously noted, Railo relies on authentication measures via the cfadmin tag, and thus none of the cfm files actually contain authentication routines themselves.
thumbnail.cfm
first generates a hash of the image along with it’s width and height:
1 2 3 |
|
Once it’s got a hash, it checks if the file exists, and if not, attempts to read and write it down:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
The cffile
tag is used to read the raw image and then cast it via the cfimage
tag. The wonderful thing about cffile
is that we can provide URLs that it will arbitrarily retrieve. So, our URL can be this:
1
|
|
And Railo will go and fetch the image and cast it. Note that if a height and width are not provided it will attempt to resize it; we don’t want this, and thus we provide large width and height values. This file is written out to /railo/temp/admin-ext-thumbnails/[HASH].[EXTENSION]
.
We’ve now successfully written a file onto the remote system, and need a way to retrieve it. The temp folder is not accessible from the web root, so we need some sort of LFI to fetch it. Enter jsloader.cfc
.
jsloader.cfc
is a Railo component used to fetch and load Javascript files. In this file is a CF tag called get
, which accepts a single argument lib
, which the tag will read and return. We can use this to fetch arbitrary Javascript files on the system and load them onto the page. Note that it MUST be a Javascript file, as the extension is hard-coded into the file and null bytes don’t work here, like they would in PHP. Here’s the relevant code:
1 2 3 4 5 6 7 8 |
|
Let’s tie all this together. Using thumbnail.cfm
, we can write well-formed images to the file system, and using the jsloader.cfc
file, we can read arbitrary Javascript. Recall how log injection works with PHP; we can inject PHP tags into arbitrary files so long as the file is loaded by PHP, and parsed accordingly. We can fill a file full of junk, but if the parser has its way a single <?phpinfo();?>
will be discovered and executed; the CFML engine works the same way.
Our attack becomes much more clear: we generate a well-formed PNG file, embed CFML code into the image (metadata), set the extension to .js
, and write it via thumbnail.cfm
. We then retrieve the file via jsloader.cfc
and, because we’re loading it with a CFM file, it will be parsed and executed. Let’s check this out:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
A couple things to note; as you may notice, the module currently requires the Railo server to connect back twice. Once is for the image with embedded CFML, and the second for the payload. We embed only a stager in the image that then connects back for the actual payload.
Sadly, the LFI was unknowingly killed in 4.2.1 with the following fix to jsloader.cfc
:
1 2 3 4 |
|
The arguments.lib
variable contains our controllable path, but it kills our ability to traverse out. Unfortunately, we can’t substitute the .. with unicode or utf-16 due to the way Jetty and Java are configured, by default. This file is pretty much useless to us now, unless we can write into the folder that jsloader.cfc
reads from; then we don’t need to traverse out at all.
We can still pop this on Express installs, due to the Jetty LFI discussed in part 3. By simply traversing into the extensions folder, we can load up the Javascript file and execute our shell. Railo installs still prove elusive.
buuuuuuuuuuuuuuuuuuuuuuuuut
Recall the img.cfm
LFI from part 3; by tip-toeing back into the admin-ext-thumbnails folder, we can summon our vulnerable image and execute whatever coldfusion we shove into it. This proves to be an even better choice than jsloader.cfc
, as we don’t need to traverse as far. This bug only affects versions 4.1 – 4.2.1, as thumbnail.cfm
wasn’t added until 4.1. CVE-2014-5468
has been assigned to this issue.
The second RCE vulnerability is a bit easier and has a larger attack vector, spanning all versions of Railo. As previously noted, Railo does not do per page/URL authentication, but rather enforces it when making changes via the <cfadmin>
tag. Due to this, any pages doing naughty things without checking with the tag may be exploitable, as previously seen. Another such file is overview.uploadNewLangFile.cfm
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
I mean, this might as well be an upload form to write arbitrary files. It’s stupid simple to get arbitrary data written to the system:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
The tricky bit is where it’s written to; Railo uses a compression system that dynamically generates compressed versions of the web server, contained within railo-context.ra
. A mirror of these can be found under the following:
1
|
|
The compressed data is then obfuscated behind two more folders, both MD5s. In my example, it becomes:
1
|
|
So we cannot simply traverse into this path, as the hashes change every single time a file is added, removed, or modified. I’ll walk the logic used to generate these, but as a precusor, we aren’t going to figure these out without some other fashionable info disclosure bug.
The hashes are calculated in railo-java/railo-core/src/railo/commons/io/res/type/compress/Compress.java
:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
The first hash is then cid + "-" + ffile.getAbsolutePath()
, where cid
is the randomly generated ID found in the id
file (see part two) and ffile.getAbsolutePath()
is the full path to the classes resource. This is doable if we have the XXE, but 4.1+ is inaccessible.
The second hash is actLastMode + ":" + ffile.length()
, where actLastMode
is the last modified time of the file and ffile.length()
is the obvious file length. Again, this is likely not brute forcable without a serious infoleak vulnerability. Hosts <= 4.0 are exploitable, as we can list files with the XXE via the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
|
http_test_xxe.py
is just a small hack I wrote to exploit the XXE, in which we eventually obtain both valid hashes. So we can exploit this in versions <= 4.0 Express. Later versions, as far as I can find, have no discernible way of obtaining full RCE without another infoleak or resorting to a slow, loud, painful death of brute forcing two MD5 hashes.
The first RCE is currently available in clusterd dev, and a PR is being made to Metasploit thanks to @BrandonPrry. Hopefully it can be merged shortly.
As we conclude our Railo analysis, lets quickly recap the vulnerabilities discovered during this audit:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
This does not include the random XSS bugs or post-authentication issues. At the end of it all, this appears to be a framework with great ideas, but desperately in need of code TLC. Driving forward with a checklist of features may look nice on a README page, but the desolate wasteland of code left behind can be a scary thing. Hopefully the Railo guys take note and spend some serious time evaluating and improving existing code. The bugs found during this series have been disclosed to the developers; here’s to hoping they follow through.