0

I am writing a small HTTP web server in C++ as part of a hobby project, and I need to serve static files. However, one problem I want to avoid is a user typing in, for example, http://example.com/../passwd. To ensure that users don't enter in a malicious path, I want to check if a path entered is in the current parent directory.

My current approach is to use std::filesystem::directory_iterator, checking if the provided one is a file and if its the same as the one provided. However, this is very slow and clunky, and I believe that there is a better solution.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
bsdsylvia
  • 75
  • 5
  • 1
    "*To ensure that users don't enter in a malicious path*" Could you just make sure the path doesn't have `..` in it, or resolve the path against the file system and make sure that the result doesn't go outside some particular root directory? – Nicol Bolas Jan 14 '23 at 20:48
  • 2
    Maybe convert the path to a canonical path and check the start of the path conforms to legitimate root path? – Galik Jan 14 '23 at 20:52

1 Answers1

1

A better solution would be to simply append the user's specified path to your desired root path, canonicalize the result, and then check if the result is still within the root path.

For example, when the user requests http://example.com/../passwd, your server will see a request like:

GET /../passwd HTTP/1.1

So, append just "../passwd" to your root folder and compare the result, for example:

#include <string>
#include <filesystem>
namespace fs = std::filesystem;

bool isSubDir(fs::path p, fs::path root)
{
    static const fs::path emptyPath;
    while (p != emptyPath) {
        if (fs::equivalent(p, root)) {
             return true;
        }
        p = p.parent_path();
    }
    return false;
}

...

fs::path requestedPath = fs::path("../passwd").make_preferred();
fs::path parentPath = fs::path("C:\\webroot\\");
fs::path actualPath = fs::canonical(parentPath / requestedPath);

if (isSubDir(actualPath, parentPath))
{
    // serve file at actualPath as needed...
}
else
{
    // send error reply...
}
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • @NicolBolas For instance, `canonical(parentPath / requestedPath).lexically_relative(parentPath)` returns `"..\passwd"`, how does that help to validate that the requested path is a child of the parent path? If `lexically_relative()` returns an empty path? – Remy Lebeau Jan 14 '23 at 21:12