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:
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|
|.||is name only|
|..||is name only|
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:
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?