3
\$\begingroup\$

PHP realpath($path); is insufficient for some cases since it returns false on paths that dont exist on a file system.

I need a function that extends realpath($path); to return even path that does not exist yet. Such function would be used for keeping a user within his filesystem directory and/or restricting modifications to some directories and files.

function DesiredRealPath($path_string) {
    $desired_path = explode("/", str_replace("\\", "/", $path_string)); // convert back slashes to front slashes and create array of desired directories
    $real_path = explode("/", str_replace("\\", "/", realpath("."))); // convert back slashes to front slashes and create array of actual directories
    if(mb_substr($path_string, 0, 1) == "/" || mb_substr($path_string, 0, 1) == "\\") { // if path string begins with a slash, slice all actual directories except for root
        $real_path = array_slice($real_path, 0, 1); // "/" points to root directory
    }
    foreach ($desired_path as $desired_element) {
        switch ($desired_element) {
            case "":
                break;
            case ".":
                break;
            case "..": // remove last element of actual directories if array has at least 2 directories left
                if(count($real_path) >= 2) {
                    array_pop($real_path);
                }
                break;
            default: // push desired directory into actual directories
                array_push($real_path, $desired_element);
                break;
        }
    }
    return implode("/", $real_path); // put array of actual directories into a string
}

So far I have tested on both Windows and Linux, this function has determined precise directory even non-existant ones.

My question is, whether this function has any flaws or security vulnerabilities?

\$\endgroup\$
0
\$\begingroup\$

Because you’re allowing path traversal, your function doesn’t restrict even system paths. To exemplify the danger of your function, check examples below, where realpath('.') points to /web/users/alice:

DesiredRealPath('files/photos'); # It returns `/web/users/alice/files/photos` and this is OK
DesiredRealPath('/etc/passwd'); # It returns `/etc/passwd` and it’s very BAD
DesiredRealPath('../bob/photos'); # It returns `/web/users/bob/photos` and this is also BAD

To protect against Path Traversal Attack, you should ignore “dot” directories. For example, consider this function:

function basepath($rel_path)
{
    $base = str_replace('\\', '/', realpath('.'));
    $parts = explode('/', str_replace('\\', '/', $rel_path));

    foreach ($parts as $part) {
        if ($part && $part != '.' && $part != '..') {
            $base .= "/{$part}";
        }
    }
    return $base;
}

Testing the same paths as in the example above, no one escapes the /web/users/alice directory:

basepath('files/photos'); # /web/users/alice/files/photos
basepath('/etc/passwd'); # /web/users/alice/etc/passwd
basepath('../bob/photos'); # /web/users/alice/bob/photos

For better security, make sure to configure the open_basedir directive correctly.

By the way, if you have a script for which you want prevent path traversal, add the following at the top of your script:

ini_set('open_basedir', __DIR__);
\$\endgroup\$
  • \$\begingroup\$ open_basedir is an excellent point, I didnt know about it and it seems suitable if the only restriction is one path. DesiredRealPath(); itself is not designed to make any restrictions, its purpose is to inform the server what path is going to be tempered with before any real tempering takes place. \$\endgroup\$ – Gordon Casper Dec 17 '18 at 10:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.