Welcome Guest, Not a member yet? Register   Sign In
Securing the upload directory against directory path crafting
#1

[eluser]Xeoncross[/eluser]
Almost every site has an uploads directory - especially blogging systems. But having nothing more than a single folder to cram all your junk in is no fun. Who wants to dig through 567 files in the uploads directory looking for that PDF from last year?

So for this we build things which allow our admins to create sub directories and addition folders to help organize the chaos.

However, if you were going to allow admins to create a new folder some things would have to be taken into account to protect your system from them - even if they are admins. Actually, especially if they are admins.

So I'm trying to figure out a way to handle this. I first thought that I would use realpath() and then compair the two final normalized paths - but realpath() only works when the path actually exists - so I can't use it to check a folder and admin wants to create before the admin has created it.

Then I thought that I could do some cleaning and just remove relative paths links like

Code:
//
\\
/../
/./

But I am unsure if there are other ways my sneaky admins might be able to craft a path that reaches outside of that uploads folder I am trying to keep them in.

I know someone is going to mention something about files (which is unrelated), so here, I am in fact cleaning them (or even sha1() them).

Code:
[^a-z0-9\.\-_]
#2

[eluser]cahva[/eluser]
If you let admin create one subdirectory at a time, wouldnt using basename function be just enough?
Code:
$dir_to_be_created = '../../../evildir';
echo basename($dir_to_be_created); // would give "evildir"
#3

[eluser]Xeoncross[/eluser]
Yes, that is certainally how I plan on allowing the creation of directories.


However, to keep scripts in check that might also go awry I have been playing with this little function which seems to work.

Code:
<?php

function is_sub_dir($path = NULL, $parent_folder = SITE_PATH) {

    //If no path was given
    if( !$path OR ! trim($path, '/\\. ') ) {
        return $path. '[no path]';
        return FALSE;
    }
    
    $s = (DS === '/' ? '\\' : '/');
    
    // Normalize paths (WINDOWS to *NIX or vice-versa)
    $path = str_replace($s, DS, $path);

    // Don't allow relative links or multiple forward slashes
    if( strpos($path, DS. '..') !== FALSE OR strpos($path, DS.DS) !== FALSE) {
        return $path. '[bad relative links]';
        return FALSE;
    }
    
    // Remove start and end slashes and dots
    $path = trim($path, DS. '. ');
    $parent_folder = trim($parent_folder, DS. '. ');
    
    $front = '';
    
    //If windows
    if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
    
        //Allow "C:" and "AB:" styled dives
        $front = substr($path, 0, 4);
        $path = substr($path, 4);
        
        //If something else is here
        if( preg_match('/[^a-z:'. ( DS == '/' ? DS : '\\\\'). ']/i', $front) ) {
            return $path. '[bad chars]';
        }
    
    }
    
    //Allowed filesystem path chars
    $regex = '[^a-z0-9 \.\-_ '. ( DS == '/' ? DS : '\\\\'). ']';
    
    //If anything funny looking is found - fail (to be safe)
    if( preg_match('#'. $regex. '#i', $path, $matches) ) {
        print_pre($matches);
        return $path. '[bad chars]';
        return FALSE;
    }
    
    //Rejoin
    $path = $front. $path;
    
    //Must be the same starting path
    if( substr($path, 0, strlen($parent_folder)) != $parent_folder) {
        return $path. ' [not the same] '. $parent_folder;
    }
    
    //If this path is higher than the parent folder
    if( strcmp($path, $parent_folder) > 0 ) {
        return '<b>'. $path. ' [great than] '. $parent_folder. '</b>';
    }

    return $path. ' [not match]';
    return FALSE;
}


/*
* Test
*/

define('DS', DIRECTORY_SEPARATOR);
define('SITE_PATH', realpath(dirname(__FILE__)). DS);


$paths = array(
    '',
    '../',
    '\\',
    './',
    SITE_PATH,
    realpath(dirname(__FILE__)),
    SITE_PATH. 'dir1/',
    SITE_PATH. 'dir1/../',
    SITE_PATH. 'dir1/. ./',
    SITE_PATH. 'dir1/../',
    SITE_PATH. 'dir1/.'. "\n". './',
    SITE_PATH. 'dir1',
    SITE_PATH. 'dir1/dir2',
    SITE_PATH. 'dir4/',
    SITE_PATH. 'dir4/dir3/none/../',
    SITE_PATH. '.././strcasecmp/dir1/',
    SITE_PATH. '../strcasecmp/dir1/./',
    SITE_PATH. 'dir1/*file/$',
    SITE_PATH. 'dir1/$#(%#',
    SITE_PATH. '#$#$/',
    'C:\wamp\www\\',
    '\\wamp\www\\'
);

foreach( $paths as $path ) {
    print '<pre>'. $path. ' = '. is_sub_dir($path). '</pre>';
}
#4

[eluser]BrianDHall[/eluser]
"To create a directory for your uploaded files, the process is very simple. First, just apply these electrodes to your..."

...too extreme?
#5

[eluser]Xeoncross[/eluser]
Hey Brian, It looks like in my leave you have grown to almost surpass my post count. Wink
Every time I look at your avatar I think back to RPG's and kid mages.
#6

[eluser]BrianDHall[/eluser]
[quote author="Xeoncross" date="1256711699"]Hey Brian, It looks like in my leave you have grown to almost surpass my post count. Wink
Every time I look at your avatar I think back to RPG's and kid mages.[/quote]

I'm not sure if that says something good about my job or something bad, LOL.

The nice thing with a forum is you need only tackle interesting problems. Work requires trivial things Smile




Theme © iAndrew 2016 - Forum software by © MyBB