2

I have a php script in a folder (I call it the root folder). The script can basically list all files in subfolders of this root folder. The user can specify which subfolder should be displayed by using GET-parameters.

script.php?foo

would display the content of

<root folder>/foo/

and script.php?.bar would display the content of

<root folder>/.bar/

However, users could also "cheat" and use commands like /.. to display the content of folders they souldn't be able to see.

For example with

script.php?/../..

the users could get very high in the folder hierarchy.

Do you have an idea how to prevent users of doing "cheats" like this.

For reason of simplicity, let's say the GET-parameter is stored in $searchStatement.

4 Answers 4

5

You could use realpath to resolve the relative path to an absolute one and then check if that path begins with your "root" folder's path:

$absolutePath = realpath(__DIR__ . '/' . trim($searchStatement, '/'));

if (strpos($absolutePath, __DIR__ .'/') !== 0) {
    die('Access denied.');
}
Sign up to request clarification or add additional context in comments.

2 Comments

Pretty much my idea but a little better and faster ;) +1
sounds like a very good solution! I will try it tommorow and accept your answer then!
3

You just should validate the input before you use it.

For example you might want to only allow the characters a-z and / to allow subdirectories. Probably you want to allow the . as well. If you make this subset small, it's easy to validate if the input is allowed or not by the allowed characters already.

At the moment you allow ., as you have noticed, you have the problem that relative paths could be created like /../../ which could be used for directory traversal attacks.

To validate if a string contains only characters of a specific range, you can validate this with a regular expression or the filter functions. If your website does not need to allow any relative path parts you can look if they exist in the path to validate the input:

$valid = !array_intersect(array('', '.', '..'), explode('/', $path));

Valid will be FALSE if there is any // or /./ or /../ part inside the path.

If you need to allow relative paths, realpath has already been suggested, so to query the input against your directory structure first. I would only use it as last resort as it is relatively expensive, but it's good to know about.

However you can resolve the string your own as well with some simple function like the following one:

/**
 * resolve path to itself
 *
 * @param string $path
 * @return string resolved path
 */
function resolvePath($path)
{
    $path = trim($path, '/');
    $segmentsIn = explode('/', $path);
    $segmentsOut = array();

    foreach ($segmentsIn as $in)
    {
        switch ($in)
        {
            case '':
                $segmentsOut = array();
                break;
            case '.':
                break;
            case '..';
                array_pop($segmentsOut);
                break;
            default:
                $segmentsOut[] = $in;
        }
    }
    return implode('/', $segmentsOut);
}

Usage:

$tests = array(
    'hello',
    'world/.',
    '../minka',
    '../../42',
    '../.bar',
    '../hello/path/./to/../../world',
);

foreach($tests as $path)
{
    printf("%s -> %s\n", $path, resolvePath($path));
}

Output:

hello -> hello
world/. -> world
../minka -> minka
../../42 -> 42
../.bar -> .bar
../hello/path/./to/../../world -> hello/world

I can only suggest you first validate the input based on it's own data before letting touch it the filesystem, even through realpath.

3 Comments

the code looks good. But what do you mean with realpath "is relatively expensive"? Thank you!
do you mean expensive like runtime expensive? because my script wouldn't have a lot of users at the same time (it is in a private area of the page), therefore I wouldn't really mind.
First of all, it's good to know about realpath from a developer perspective. It is expensive in the meaning of disk I/O. It is a file-system function, so you already touch the file-system if you use it. Probably you want to prevent that because you're still validating the input. The resolvePath function is not really strict, but should give an example. But I think you should prevent that type of input firsthand, just invalidate the malicious request.
1

Have a look at the chroot function:

bool chroot ( string $directory )

Changes the root directory of the current process to directory, and changes the current working directory to "/".

A call to that method prevents further access to files outside of the current directory.

Note however that requires root privileges.

1 Comment

great idea! However, I don't have root privileges :(
1

Have you tried something with realpath, it should resolve all the /.. in your path. By testing the realpath of the arguments against your current path like:

substr($realpath, 0, strlen('/basepath/cant/go/above')) === '/basepath/cant/go/above'

you make sure that any /.. havent escaped from where you want.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.