PHP is_dir() doesn't work when nested - php

Here is my folder structure:
main
- folder_1
-- folder_1_1
-- folder_1_2
-- folder_1_3
- folder_2
-- folder_2_1
-- folder_2_2
-- folder_2_3
And here is my code:
<?php
$mainfolder ="main";
function readDirs($mainfolder){
if(hasSubFolder($mainfolder)){
echo("$mainfolder HAS sub folder");
}else{
echo("$mainfolder DOESN'T HAVE sub folder");
}
}
function hasSubFolder($folder){
$newPath="";
if (is_dir($folder)) {
echo ("$folder IS a folder</br>");
$handle = opendir($folder);
while (false !== ($entry = readdir($handle))) {
$newPath = $folder."\\".$entry;
if (is_dir($newPath)){
echo "$newPath IS a folder</br>";
} else {
echo "$newPath IS NOT a folder</br>";
}
}
}
}
readDirs($mainfolder);
?>
And this is what I get:
main IS a folder
main\. is NOT a folder
main\.. is NOT a folder
main\folder_1 is NOT a folder
main\folder_2 is NOT a folder
main DOESN'T HAVE sub folder
So I'd like to know why "if (is_dir($newPath))" doesn't return true even $newPath is a folder/dir. Is it because it's nested inside another another is_dir()?
Thank

Since PHP can run on a multitude of platforms, which might use even more exotic directory separators than forward or backward slash, there's a beautiful system constant defined you can used instead: DIRECTORY_SEPARATOR.
It automatically contains the right separator for the current host OS, such as the backslash for Windows or the forward slash for a *nix system. You can then use:
$newPath = $folder.DIRECTORY_SEPARATOR.$entry;
This is always better than hardcoding it for any OS as the other answer suggests.
Currently, for backwards compatibility reasons, PHP internally always does a str_replace from / to DIRECTORY_SEPARATOR within its internal file functions, but this is not guaranteed to keep working forever - it costs performance and isn't strictly needed.

Linux uses / for folder separation, and Windows does too. It's only DOS that doesn't. Replace \\ with / and you should be good to go.

Related

Checking permission for a path [duplicate]

I have a base path /whatever/foo/
and
$_GET['path'] should be relative to it.
However how do I accomplish this (reading the directory), without allowing directory traversal?
eg.
/\.\.|\.\./
Will not filter properly.
Well, one option would be to compare the real paths:
$basepath = '/foo/bar/baz/';
$realBase = realpath($basepath);
$userpath = $basepath . $_GET['path'];
$realUserPath = realpath($userpath);
if ($realUserPath === false || strpos($realUserPath, $realBase) !== 0) {
//Directory Traversal!
} else {
//Good path!
}
Basically, realpath() will resolve the provided path to an actual hard physical path (resolving symlinks, .., ., /, //, etc)... So if the real user path does not start with the real base path, it is trying to do a traversal. Note that the output of realpath will not have any "virtual directories" such as . or .....
ircmaxell's answer wasn't fully correct. I've seen that solution in several snippets but it has a bug which is related to the output of realpath(). The realpath() function removes the trailing directory separator, so imagine two contiguous directories such as:
/foo/bar/baz/
/foo/bar/baz_baz/
As realpath() would remove the last directory separator, your method would return "good path" if $_GET['path'] was equal to "../baz_baz" as it would be something like
strpos("/foo/bar/baz_baz", "/foo/bar/baz")
Maybe:
$basepath = '/foo/bar/baz/';
$realBase = realpath($basepath);
$userpath = $basepath . $_GET['path'];
$realUserPath = realpath($userpath);
if ($realUserPath === false || strcmp($realUserPath, $realBase) !== 0 || strpos($realUserPath, $realBase . DIRECTORY_SEPARATOR) !== 0) {
//Directory Traversal!
} else {
//Good path!
}
It is not sufficient to check for patterns like ../ or the likes. Take "../" for instance which URI encodes to "%2e%2e%2f". If your pattern check happens before a decode, you would miss this traversal attempt. There are some other tricks hackers can do to circumvent a pattern checker especially when using encoded strings.
I've had the most success stopping these by canonicalizing any path string to its absolute path using something like realpath() as ircmaxwell suggests. Only then do I begin checking for traversal attacks by matching them against a base path I've predefined.
You may be tempted to try and use regex to remove all ../s but there are some nice functions built into PHP that will do a much better job:
$page = basename(realpath($_GET));
basename - strips out all directory information from the path e.g. ../pages/about.php would become about.php
realpath - returns a full path to the file e.g. about.php would become /home/www/pages/about.php, but only if the file exists.
Combined they return just the files name but only if the file exists.
When looking into the creation of new files or folders, I've figured I can use a two stage approach:
First check for traversal attempts using a custom implementation of a realpath() like function, which however works for arbitrary paths, not just existing files. There's a good starting point here. Extend it with urldecode() and whatever else you think may worth checking.
Now using this crude method you can filter out some traversal attempts, but it may be possible that you miss some hackish combination of special characters, symlinks, escaping sequences etc. But since you know for sure the target file does not exist (check using file_exists) noone can overwrite anything. The worst case scenario would be that someone can get your code creating a file or folder somewhere, which may be an acceptable risk in most cases, provided your code does not allow them to write into that file/folder straight away.
Finally so the path now points to an existing location, therefore you can now do the proper check using the methods suggested above utilising realpath(). If at this point it turns out a traversal has happened, you are still safe more or less, as long as you make sure to prevent any attempts writing into the target path. Also right now you can delete the target file/dir and say it was a traversal attempt.
I'm not saying it cannot be hacked, since after all still it may allow illegitimate changes to be done to the FS, but still better than only doing custom checks, that cannot utilise realpath(), and the window for abuse left open by making a temporary and empty file or folder somewhere is lower, than allowing them to make it permanent and even write into it, as it would happen with only a custom check that may miss some edge cases.
Also correct me if I'm wrong pls!
I have written a function to check for traversal:
function isTraversal($basePath, $fileName)
{
if (strpos(urldecode($fileName), '..') !== false)
return true;
$realBase = realpath($basePath);
$userPath = $basePath.$fileName;
$realUserPath = realpath($userPath);
while ($realUserPath === false)
{
$userPath = dirname($userPath);
$realUserPath = realpath($userPath);
}
return strpos($realUserPath, $realBase) !== 0;
}
This line alone if (strpos(urldecode($fileName), '..') !== false) should be enough to prevent traversal, however, there are many different ways hackers can traverse directories so its better to make sure the user starts with the real base path.
Just checking the user starts with the real base path is not enough because a hacker could traverse to the current directory and discover the directory structure.
The while allows the code to work when $fileName does not exist.
1
put a null index.htm for -Index block
2
filter sQS on start
// Path Traversal Attack
if( strpos($_SERVER["QUERY_STRING"], "../") ){
exit("P.T.A. B-(");
}

PHP function 'scandir' not working on WordPress hosted on windows

When i am using scandir function i get different results on linux and windows.
Windows i see only folder name
Linux it does the job under
I run a check if WordPress files has been changed ?
function find_all_files($dir, $deep)
{
if(strpos($dir,"wp-content") == false){
$root = scandir($dir);
foreach($root as $value)
{
if($value === '.' || $value === '..') {continue;}
if(is_file("$dir/$value")) {
$tmpFile = "$dir/$value";
if(5000000>#filesize($tmpFile)){
$result[]="$tmpFile";
}
continue;
}
$tmp = "$dir/$value";
if(strpos($tmp,"wp-content") == false){
if($deep == true){
foreach(#$this->find_all_files("$dir/$value",true) as $value)
{
$tmpFile = $value;
if(5000000>#filesize($tmpFile)){
$result[]="$tmpFile";
}
}
}
}
}
//var_dump($result);
return $result;
}
}
I had a similar issue and it all came down to:
1) if you mean "running on windows" OS you need to include, and start from the drive directory (where server is installed) in $dir to scan.
2) Windows might be picky about separators, compare your $dir with Wordpress ABSPATH and try to use PHP constant DIRECTORY_SEPARATOR instead of your own '/' separators.
3) And I asume you working with paths, not urls (http://localhost/wordpress/...plugin/dir/). This is working for me on Windows 7, running Uniserver (LAMP):
$rootfolder = 'startfolder';
$dir = plugin_dir_path(dirname( __FILE__ )).DIRECTORY_SEPARATOR.$rootfolder;
$files = array_slice(scandir($dir), 2);
However, you need to cut down the path end begin in the install dir with ABSPATH or something. But I recommend to test where your filescript is, like plugins folder, to not consume a huge stall on errors while working with this kind of stuff.
Hint!
The PHP docs about scandir has a lot off contributed functions about reading, retriving, scanning functions and issues. Take a look at: http://php.net/manual/en/function.scandir.php
Important!
Do not start messing with windows folder access, and if you do, take a RAW copy of whole www directory first, otherwise exported Worpdress installs could get messy on a Apache server live.
Remember, did you manage to install a new plugin from the Wordpress
admin, you propably dont have a folder access issue.
Hop this helps!

How can I detect whether a path escapes the current directory in php? [duplicate]

I have a base path /whatever/foo/
and
$_GET['path'] should be relative to it.
However how do I accomplish this (reading the directory), without allowing directory traversal?
eg.
/\.\.|\.\./
Will not filter properly.
Well, one option would be to compare the real paths:
$basepath = '/foo/bar/baz/';
$realBase = realpath($basepath);
$userpath = $basepath . $_GET['path'];
$realUserPath = realpath($userpath);
if ($realUserPath === false || strpos($realUserPath, $realBase) !== 0) {
//Directory Traversal!
} else {
//Good path!
}
Basically, realpath() will resolve the provided path to an actual hard physical path (resolving symlinks, .., ., /, //, etc)... So if the real user path does not start with the real base path, it is trying to do a traversal. Note that the output of realpath will not have any "virtual directories" such as . or .....
ircmaxell's answer wasn't fully correct. I've seen that solution in several snippets but it has a bug which is related to the output of realpath(). The realpath() function removes the trailing directory separator, so imagine two contiguous directories such as:
/foo/bar/baz/
/foo/bar/baz_baz/
As realpath() would remove the last directory separator, your method would return "good path" if $_GET['path'] was equal to "../baz_baz" as it would be something like
strpos("/foo/bar/baz_baz", "/foo/bar/baz")
Maybe:
$basepath = '/foo/bar/baz/';
$realBase = realpath($basepath);
$userpath = $basepath . $_GET['path'];
$realUserPath = realpath($userpath);
if ($realUserPath === false || strcmp($realUserPath, $realBase) !== 0 || strpos($realUserPath, $realBase . DIRECTORY_SEPARATOR) !== 0) {
//Directory Traversal!
} else {
//Good path!
}
It is not sufficient to check for patterns like ../ or the likes. Take "../" for instance which URI encodes to "%2e%2e%2f". If your pattern check happens before a decode, you would miss this traversal attempt. There are some other tricks hackers can do to circumvent a pattern checker especially when using encoded strings.
I've had the most success stopping these by canonicalizing any path string to its absolute path using something like realpath() as ircmaxwell suggests. Only then do I begin checking for traversal attacks by matching them against a base path I've predefined.
You may be tempted to try and use regex to remove all ../s but there are some nice functions built into PHP that will do a much better job:
$page = basename(realpath($_GET));
basename - strips out all directory information from the path e.g. ../pages/about.php would become about.php
realpath - returns a full path to the file e.g. about.php would become /home/www/pages/about.php, but only if the file exists.
Combined they return just the files name but only if the file exists.
When looking into the creation of new files or folders, I've figured I can use a two stage approach:
First check for traversal attempts using a custom implementation of a realpath() like function, which however works for arbitrary paths, not just existing files. There's a good starting point here. Extend it with urldecode() and whatever else you think may worth checking.
Now using this crude method you can filter out some traversal attempts, but it may be possible that you miss some hackish combination of special characters, symlinks, escaping sequences etc. But since you know for sure the target file does not exist (check using file_exists) noone can overwrite anything. The worst case scenario would be that someone can get your code creating a file or folder somewhere, which may be an acceptable risk in most cases, provided your code does not allow them to write into that file/folder straight away.
Finally so the path now points to an existing location, therefore you can now do the proper check using the methods suggested above utilising realpath(). If at this point it turns out a traversal has happened, you are still safe more or less, as long as you make sure to prevent any attempts writing into the target path. Also right now you can delete the target file/dir and say it was a traversal attempt.
I'm not saying it cannot be hacked, since after all still it may allow illegitimate changes to be done to the FS, but still better than only doing custom checks, that cannot utilise realpath(), and the window for abuse left open by making a temporary and empty file or folder somewhere is lower, than allowing them to make it permanent and even write into it, as it would happen with only a custom check that may miss some edge cases.
Also correct me if I'm wrong pls!
I have written a function to check for traversal:
function isTraversal($basePath, $fileName)
{
if (strpos(urldecode($fileName), '..') !== false)
return true;
$realBase = realpath($basePath);
$userPath = $basePath.$fileName;
$realUserPath = realpath($userPath);
while ($realUserPath === false)
{
$userPath = dirname($userPath);
$realUserPath = realpath($userPath);
}
return strpos($realUserPath, $realBase) !== 0;
}
This line alone if (strpos(urldecode($fileName), '..') !== false) should be enough to prevent traversal, however, there are many different ways hackers can traverse directories so its better to make sure the user starts with the real base path.
Just checking the user starts with the real base path is not enough because a hacker could traverse to the current directory and discover the directory structure.
The while allows the code to work when $fileName does not exist.
1
put a null index.htm for -Index block
2
filter sQS on start
// Path Traversal Attack
if( strpos($_SERVER["QUERY_STRING"], "../") ){
exit("P.T.A. B-(");
}

file_exists() returns false, but the file DOES exist

I'm having a very weird issue with file_exists(). I'm using this function to check if 2 different files in the same folders do exist. I've double-checked, they BOTH do exist.
echo $relative . $url['path'] . '/' . $path['filename'] . '.jpg';
Result: ../../images/example/001-001.jpg
echo $relative . $url['path'] . '/' . $path['filename'] . '.' . $path['extension'];
Result: ../../images/example/001-001.PNG
Now let's use file_exists() on these:
var_dump(file_exists($relative . $url['path'] . '/' . $path['filename'] . '.jpg'));
Result: bool(false)
var_dump(file_exists($relative . $url['path'] . '/' . $path['filename'] . '.' . $path['extension']));
Result: bool(true)
I don't get it - both of these files do exist. I'm running Windows, so it's not related to a case-sensitive issue. Safe Mode is off.
What might be worth mentioning though is that the .png one is uploaded by a user via FTP, while the .jpg one is created using a script. But as far as I know, that shouldn't make a difference.
Any tips?
Thanks
file_exists() just doesn't work with HTTP addresses.
It only supports filesystem paths (and FTP, if you're using PHP5.)
Please note:
Works :
if (file_exists($_SERVER['DOCUMENT_ROOT']."/folder/test.txt")
echo "file exists";
Does not work:
if (file_exists("www.mysite.com/folder/test.txt")
echo "file exists";
Results of the file_exists() are cached, so try using clearstatcache(). If that not helped, recheck names - they might be similar, but not same.
I found that what works for me to check if a file exists (relative to the current php file it is being executed from) is this piece of code:
$filename = 'myfile.jpg';
$file_path_and_name = dirname(__FILE__) . DIRECTORY_SEPARATOR . "{$filename}";
if ( file_exists($file_path_and_name) ){
// file exists. Do some magic...
} else {
// file does not exists...
}
Just my $.02: I just had this problem and it was due to a space at the end of the file name. It's not always a path problem - although that is the first thing I check - always. I could cut and paste the file name into a shell window using the ls -l command and of course that locates the file because the command line will ignore the space where as file_exists does not. Very frustrating indeed and nearly impossible to locate were it not for StackOverflow.
HINT: When outputting debug statements enclose values with delimiters () or [] and that will show a space pretty clearly. And always remember to trim your input.
It's because of safe mode. You can turn it off or include the directory in safe_mode_include_dir. Or change file ownership / permissions for those files.
php.net: file_exists()
php.net: safe mode
Try using DIRECTORY_SEPARATOR instead of '/' as separator. Windows uses a different separator for file system paths (backslash) than Linux and Unix systems.
A very simple trick is here that worked for me.
When I write following line, than it returns false.
if(file_exists('/my-dreams-files/'.$_GET['article'].'.html'))
And when I write with removing URL starting slash, then it returns true.
if(file_exists('my-dreams-files/'.$_GET['article'].'.html'))
I have a new reason this happens - I am using PHP inside a Docker container with a mounted volume for the codebase which resides on my local host machine.
I was getting file_exists == FALSE (inside Composer autoload), but if I copied the filepath into terminal - it did exist! I tried the clearstatche(), checked safe-mode was OFF.
Then I remembered the Docker volume mapping: the absolute path on my local host machine certainly doesn't exist inside the Docker container - which is PHP's perspective on the world.
(I keep forgetting I'm using Docker, because I've made shell functions which wrap the docker run commands so nicely...)
It can also be a permission problem on one of the parent folders or the file itself.
Try to open a session as the user running your webserver and cd into it. The folder must be accessible by this user and the file must be readable.
If not, php will return that the file doesn't exist.
have you tried manual entry. also your two extensions seem to be in different case
var_dump(file_exists('../../images/example/001-001.jpg'));
var_dump(file_exists('../../images/example/001-001.PNG'));
A custom_file_exists() function inspired by #Timur, #Brian, #Doug and #Shahar previous answers:
function custom_file_exists($file_path=''){
$file_exists=false;
//clear cached results
//clearstatcache();
//trim path
$file_dir=trim(dirname($file_path));
//normalize path separator
$file_dir=str_replace('/',DIRECTORY_SEPARATOR,$file_dir).DIRECTORY_SEPARATOR;
//trim file name
$file_name=trim(basename($file_path));
//rebuild path
$file_path=$file_dir."{$file_name}";
//If you simply want to check that some file (not directory) exists,
//and concerned about performance, try is_file() instead.
//It seems like is_file() is almost 2x faster when a file exists
//and about the same when it doesn't.
$file_exists=is_file($file_path);
//$file_exists=file_exists($file_path);
return $file_exists;
}
This answer may be a bit hacky, but its been working for me -
$file = 'path/to/file.jpg';
$file = $_SERVER['REQUEST_SCHEME'].'://'.$_SERVER['HTTP_HOST'].'/'.$file;
$file_headers = #get_headers($file);
if($file_headers[0] == 'HTTP/1.1 404 Not Found') {
$exists = false;
}else{
$exists = true;
}
apparently $_SERVER['REQUEST_SCHEME'] is a bit dicey to use with IIS 7.0 + PHP 5.3 so you could probably look for a better way to add in the protocol.
I found this answer here http://php.net/manual/en/function.file-exists.php#75064
I spent the last two hours wondering what was wrong with my if statement: file_exists($file) was returning false, however I could call include($file) with no problem.
It turns out that I didn't realize that the php include_path value I had set in the .htaccess file didn't carry over to file_exists, is_file, etc.
Thus:
<?PHP
// .htaccess php_value include_path '/home/user/public_html/';
// includes lies in /home/user/public_html/includes/
//doesn't work, file_exists returns false
if ( file_exists('includes/config.php') )
{
include('includes/config.php');
}
//does work, file_exists returns true
if ( file_exists('/home/user/public_html/includes/config.php') )
{
include('includes/config.php');
}
?>
Just goes to show that "shortcuts for simplicity" like setting the include_path in .htaccess can just cause more grief in the long run.
In my case, the problem was a misconception of how file_exists() behaves with symbolic links and .. ("dotdot" or double period) parent dir references. In that regard, it differs from functions like require, include or even mkdir().
Given this directory structure:
/home/me/work/example/
www/
/var/www/example.local/
tmp/
public_html -> /home/me/work/example/www/
file_exists('/var/www/example.local/public_html/../tmp/'); would return FALSE even though the subdir exists as we see, because the function traversed up into /home/me/work/example/ which does not have that subdir.
For this reason, I have created this function:
/**
* Resolve any ".." ("dotdots" or double periods) in a given path.
*
* This is especially useful for avoiding the confusing behavior `file_exists()`
* shows with symbolic links.
*
* #param string $path
*
* #return string
*/
function resolve_dotdots( string $path ) {
if (empty($path)) {
return $path;
}
$source = array_reverse(explode(DIRECTORY_SEPARATOR, $path));
$balance = 0;
$parts = array();
// going backwards through the path, keep track of the dotdots and "work
// them off" by skipping a part. Only take over the respective part if the
// balance is at zero.
foreach ($source as $part) {
if ($part === '..') {
$balance++;
} else if ($balance > 0) {
$balance--;
} else {
array_push($parts, $part);
}
}
// special case: path begins with too many dotdots, references "outside
// knowledge".
if ($balance > 0) {
for ($i = 0; $i < $balance; $i++) {
array_push($parts, '..');
}
}
$parts = array_reverse($parts);
return implode(DIRECTORY_SEPARATOR, $parts);
}
I just encountered this same problem and I solved it in a mysterious way. After inserting a a filepath I copied from Windows File explorer. file_exists() keeps returning false continuously, but if I copy same path from VSCode editor it works perfectly.
After dumping variables with var_dump($path); I noticed something mysterious.
For path I copied from file explorer it shows length 94.
For path I copied from VSCode Editor it shows length 88.
Both path look same length on my code Editor.
My suggestion: if string contain hidden characters, it may fail and not work.

Preventing Directory Traversal in PHP but allowing paths

I have a base path /whatever/foo/
and
$_GET['path'] should be relative to it.
However how do I accomplish this (reading the directory), without allowing directory traversal?
eg.
/\.\.|\.\./
Will not filter properly.
Well, one option would be to compare the real paths:
$basepath = '/foo/bar/baz/';
$realBase = realpath($basepath);
$userpath = $basepath . $_GET['path'];
$realUserPath = realpath($userpath);
if ($realUserPath === false || strpos($realUserPath, $realBase) !== 0) {
//Directory Traversal!
} else {
//Good path!
}
Basically, realpath() will resolve the provided path to an actual hard physical path (resolving symlinks, .., ., /, //, etc)... So if the real user path does not start with the real base path, it is trying to do a traversal. Note that the output of realpath will not have any "virtual directories" such as . or .....
ircmaxell's answer wasn't fully correct. I've seen that solution in several snippets but it has a bug which is related to the output of realpath(). The realpath() function removes the trailing directory separator, so imagine two contiguous directories such as:
/foo/bar/baz/
/foo/bar/baz_baz/
As realpath() would remove the last directory separator, your method would return "good path" if $_GET['path'] was equal to "../baz_baz" as it would be something like
strpos("/foo/bar/baz_baz", "/foo/bar/baz")
Maybe:
$basepath = '/foo/bar/baz/';
$realBase = realpath($basepath);
$userpath = $basepath . $_GET['path'];
$realUserPath = realpath($userpath);
if ($realUserPath === false || strcmp($realUserPath, $realBase) !== 0 || strpos($realUserPath, $realBase . DIRECTORY_SEPARATOR) !== 0) {
//Directory Traversal!
} else {
//Good path!
}
It is not sufficient to check for patterns like ../ or the likes. Take "../" for instance which URI encodes to "%2e%2e%2f". If your pattern check happens before a decode, you would miss this traversal attempt. There are some other tricks hackers can do to circumvent a pattern checker especially when using encoded strings.
I've had the most success stopping these by canonicalizing any path string to its absolute path using something like realpath() as ircmaxwell suggests. Only then do I begin checking for traversal attacks by matching them against a base path I've predefined.
You may be tempted to try and use regex to remove all ../s but there are some nice functions built into PHP that will do a much better job:
$page = basename(realpath($_GET));
basename - strips out all directory information from the path e.g. ../pages/about.php would become about.php
realpath - returns a full path to the file e.g. about.php would become /home/www/pages/about.php, but only if the file exists.
Combined they return just the files name but only if the file exists.
When looking into the creation of new files or folders, I've figured I can use a two stage approach:
First check for traversal attempts using a custom implementation of a realpath() like function, which however works for arbitrary paths, not just existing files. There's a good starting point here. Extend it with urldecode() and whatever else you think may worth checking.
Now using this crude method you can filter out some traversal attempts, but it may be possible that you miss some hackish combination of special characters, symlinks, escaping sequences etc. But since you know for sure the target file does not exist (check using file_exists) noone can overwrite anything. The worst case scenario would be that someone can get your code creating a file or folder somewhere, which may be an acceptable risk in most cases, provided your code does not allow them to write into that file/folder straight away.
Finally so the path now points to an existing location, therefore you can now do the proper check using the methods suggested above utilising realpath(). If at this point it turns out a traversal has happened, you are still safe more or less, as long as you make sure to prevent any attempts writing into the target path. Also right now you can delete the target file/dir and say it was a traversal attempt.
I'm not saying it cannot be hacked, since after all still it may allow illegitimate changes to be done to the FS, but still better than only doing custom checks, that cannot utilise realpath(), and the window for abuse left open by making a temporary and empty file or folder somewhere is lower, than allowing them to make it permanent and even write into it, as it would happen with only a custom check that may miss some edge cases.
Also correct me if I'm wrong pls!
I have written a function to check for traversal:
function isTraversal($basePath, $fileName)
{
if (strpos(urldecode($fileName), '..') !== false)
return true;
$realBase = realpath($basePath);
$userPath = $basePath.$fileName;
$realUserPath = realpath($userPath);
while ($realUserPath === false)
{
$userPath = dirname($userPath);
$realUserPath = realpath($userPath);
}
return strpos($realUserPath, $realBase) !== 0;
}
This line alone if (strpos(urldecode($fileName), '..') !== false) should be enough to prevent traversal, however, there are many different ways hackers can traverse directories so its better to make sure the user starts with the real base path.
Just checking the user starts with the real base path is not enough because a hacker could traverse to the current directory and discover the directory structure.
The while allows the code to work when $fileName does not exist.
1
put a null index.htm for -Index block
2
filter sQS on start
// Path Traversal Attack
if( strpos($_SERVER["QUERY_STRING"], "../") ){
exit("P.T.A. B-(");
}

Categories