Thursday, April 25, 2013

Preventing filesystem traversal attacks

I am implementing a software feature that takes a filename from a client and accesses it on the server. The filename is supposed to be a simple filename, not a path. The server appends the simple filename to a predefined absolute path to access a file of the user's choice within a specific directory. (Symbolic links are not an issue.)

But of course, the client can send any filename whatsoever, exposing the server to a filesystem traversal attack.

Initially I had hoped that Boost::Filesystem would have a bulletproof solution. This module is already sensitive to the operating environment; it changes its behavior depending on whether it's on a POSIX-compatible system or Windows. I was hoping for something like path::contains_upward_traversal() or path::is_simple_filename(). But no such luck.

Instead, I cobbled together the following:
using boost::filesystem;
path p = get_untrusted_filename();
bool p_is_name_only = !p.has_root_path() && !p.has_parent_path();

Here's my test set:
hello is name only
he/lo reject
/ reject
. is name only
.. is name only
/hello reject
/../../../hello reject
../../hello reject

I have not found a way to combine path accessors to eliminate the dot and double-dot while still permitting a simple filename. But fortunately, those two false positives point to a directory. If you add Boost's is_regular_file(path)to the mix, only the simple filename gets accepted.

Another option is POSIX's realpath, which we can use like so:
#include <stdlib.h>
#include <limits.h>
char abs_path[PATH_MAX];
realpath(p.string().c_str(), abs_path); //check ret val

Now if p contains an absolute path, abs_path will contain the same absolute path, with relative traversal collapsed. If p contains a relative path, abs_path will contain an absolute path starting from the application's present working directory.

Lastly, to verify that the client's given filename is within the trusted directory, simply check whether abs_path starts with it. Any attempted traversal would cause this check to fail.

In my case, the client is trusted anyway, so this is a case of gold-plating. If the client was truly hostile, as would be true for public-facing servers, I would certainly consult a security specialist and do further research. But this suffices.

Is there an existing library for guarding against filesystem traversal attacks, or an improvement to what I have?