PHP display/download directory files outside the webserver root - php

I have downloaded and added this very simple, one file, php web file explorer system(called Indexer) to my XAMPP server.
My XAMMP server is on my C: drive, but I want Indexer to display a directory on my G: drive. But when I change (what I think are) the right configuration variables, it doesn't work properly.
Here is the code I think is to do with the problem:
// configuration
$Root = realpath("G:/test");
$AllowDownload = TRUE;
$WebServerPath = dirname("G:/test");
and later on in the code...
elseif ($AllowDownload) {
echo "".$item["name"]."";
}
This is what happens: The script does correctly display the contents of the "test" directory on the G: drive, but when I click the filename, to download/view the file, the link is broken because the php constructs the link wrong (I suppose).
The link looks like this: http://localhostg//[name of file].
Would you know how to solve this problem?
This script works perfectly if I change the configuration variables so it displays the contents of a relative subdirectory. And it also says $Root variable can be located outside the webserver root.
Also, even though clicking the link doesn't work, right-clicking and selecting "Save Target As" allows me to save/download the file.
(Feel free to ask if you need more information) :)

Your web server can not see the files outside the DocRoot, so it can not serve the files via the browser with direct links. You need to print their contents into the browser with readfile() with the headers properly set.
To make this work, you need to change the configuration in indexer.php:
// this way it works with accentuated letters in Windows
$Root = utf8_decode("G:\test"); // define the directory the index should be created for (can also be located outside the webserver root)
$AllowDownload = TRUE; // enclose file items with the anchor-tag (only makes sense when the files are in the webserver root)
// you need to place download.php in the same directory as indexer.php
$WebServerPath = dirname($_SERVER['SCRIPT_NAME']) . "/download.php?path="; // path where the indexed files can be accessed via a http URL (only required when $AllowDownload is TRUE)
And you have to place a new file called download.php in the same directory as indexer.php, with this content:
<?php
// it must be the same as in indexer.php
$Root = utf8_decode("G:\test");
function checkFileIsInsideRootDirectory($path, $root_directory) {
$realpath = realpath($path);
if (!file_exists($realpath))
die("File is not readable: " . $path);
// detects insecure path with for example /../ in it
if (strpos($realpath, $root_directory) === false || strpos($realpath, $root_directory) > 0)
die("Download from outside of the specified root directory is not allowed!");
}
function forceDownload($path) {
$realpath = realpath($path);
if (!is_readable($realpath))
die("File is not readable: " . $path);
$savename = (basename($path));
header("Pragmaes: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Cache-Control: private", false);
header("Content-type: application/force-download");
header("Content-Transfer-Encoding: Binary");
header("Content-length: " . filesize($path));
header("Content-disposition: attachment; filename=\"$savename\"");
readfile("$path");
exit;
}
if (!isset($_GET['path']))
die("Path not specified!");
$fullPath = $Root . $_GET['path'];
checkFileIsInsideRootDirectory($fullPath, $Root);
forceDownload($fullPath);

You have to change your apache configuration. The problem is not the php script, the problem is the webserver (which is not able to serve files outside web root, unless you configure it to).
Try something like this in your apache configuration:
Alias /testalias "G:/test"
<Directory "G:/test">
Options Indexes FollowSymLinks MultiViews ExecCGI
AllowOverride All
Order allow,deny
Allow from all
</Directory>
This tells Apache to serve files from G:/test when you access http://localhost/testalias
Then change your script configuration like that:
$WebServerPath = dirname("testalias");
and it should work!

Let's take a look at that script:
$Root = realpath("."); // define the directory the index should be created for (can also be located outside the webserver root)
$AllowDownload = TRUE; // enclose file items with the anchor-tag (only makes sense when the files are in the webserver root)
$WebServerPath = dirname(getenv("SCRIPT_NAME")); // path where the indexed files can be accessed via a http URL (only required when $AllowDownload is TRUE)
Notice "only makes sense when the files are in the webserver root" and "path where the indexed files can be accessed via a http URL". Which indicates that this script was not designed to be able to download files that are outside the web server root dir.
However, you could modify this script to be able to do that in the way that styu has noted in his answer. You could then send your changes to the author of the script.
BTW, I tested this on my own server.

Related

How to grab an image file in codeigniter [duplicate]

I'm making an intranet for a post-sale customer service entreprise. Employee need to be able to upload img files to the intranet's server and i need to store them in a directory with is BEFORE www (the website's root directory).
Doing this using php is pretty easy but how to include these imgs on the website once they're uploaded ?
I tried this code
<img src="../img/img.png"/>
This is not working because i can't send a file if it is OUTSIDE the server's www directory ...
Is there any proper way to do that ?
Current treeview :
server root directory
|www
|(all server files)
|img
|(all img files)
(the server's index.php is located in www and the files are in img)
You cannot directly access any file outside your web directory. As your question includes the tag PHP as well, I assume you may want to use it.
What you can do is the following:
Inside your www directory, create a "image.php" file, with a similar content to:
<?php
header('Content-Type: image/png');
readfile("../img/" . $_GET['img']);
?>
And call your images with
<img src="image.php?img=myimage.png" />
Please be aware that your PHP file shouldn't be that simple :) As you may want to address multiple image formats (and providing the correct header for them), checking for malicious file path/inclusions (you don't want to use $_GET without validating/sanitizing the input), extra caching etc. etc. etc.
But this should give you an idea on how you can target your issue.
It depends on what you are trying to accomplish and how.
With "simple" html commands it is as you found out. You can't go to a directory outside of the www root.
(for xampp applications on C for exmple it is most of the time c:\xampp\htdocs).
If you are ok with using serverside commands to accomplish what you want to achieve then you could use php to do a workaround, by reading the appropriate files in via PHP.
For example if your file is named "myimg.gif" and lies in "c:\pics"
<img SRC="data:image/gif;base64,<?php echo base64_encode(file_get_contents("c:\pics\myimg.gif"));?>">
With that you are reading the contents of the file and writing it directly into the img tag.
Be aware that you need to change image/gif to what you really need there.
You can't directly access file outside/above your root directory (www or public_html).
You can create virtual directory in Apache server configuration. Google "apache virtual directory" for more information.
Virtual directory configuration example:
<IfModule alias_module>
Alias /uploaded_images/ "/home/someuser/somewhere"
<Directory "/home/someuser/somewhere">
Allow from all
</Directory>
</IfModule>
uploaded_images directory will be available from web like normal directory in www directory. You can copy files from there to another location (kind of buffering).
You can also open/copy that file by ftp from php level without changing anything in apache, but this may be really slow (use it only if you can't control apache config, virtual directory is much better).
php.net: FTP
I Agree with #kamil's idea about creating a virtual directory. However if you want to go the php route, I wrote some code that open's images in a directory before WWW and copies them to the www/images folder.
<?php
$imagepath = "../images/";
define('IMGPATH', realpath($imagepath).DIRECTORY_SEPARATOR);
$cachewwwimg = "images/";
$imagename = 'image.jpg';
copy(IMGPATH.$imagename, $cachewwwimg.$imagename)
?>
<img src="<?php echo $cacheimg.$imagename;?>"/>
I solved this by simply creating a symbolic link pointing to the global physical folder on my hosting. I didn't have root access, so I created a simple PHP file, calling it symbolic_link.php, that i put in the root of my website and that creates the symbolic link for me. Here it is:
<?php
$target = '/home/<user>/images/'; // hosting physical directory
$link = 'images'; // symbolic link inside root of web site
unlink($link);
symlink($target, $link);
After the creation of the symbolic link, I deleted the file.
This is the hosting directory structure:
/home/<user> // hosting root
|
+--/images // hosting global dir
| |
| +--/logo.png // global image
|
+--/www.website.com // hosting website dir
|
+--/images // symbolic link
Now you can use the image as if it were in your website folder:
<img src="https://www.website.com/images/logo.png" alt="...">

php nginx X-Accel-Redirect headers

i have an issue regarding protecting folder with static files using nginx
so basically i have root folder on nginx setup to :
/home/rise/rises/wwwdir
and the secured folder is :
/home/rise/rises/videop
as we can see i moved that folder outside root folder to prevent/allow only specific to see under criteria
when i first made a search before posting i read some ideas that to access the videop folder outside the root , i need to create alias in nginx conf like this which i made
and access internal
location /videop {
root /home/rise/rises/;
internal;
}
however i have an issue on php side to load the video...
$aliasedFile = '/videop/5_.m3u8';
$filename = '5_.m3u8';
header("Content-Description: File Transfer");
header("Content-Type application/x-mpegURL ");
header('Content-Disposition: attachment; filename='.$filename.'');
header('X-Accel-Redirect: '. $aliasedFile);
readfile($aliasedFile);
i'm missing something ?
Your root directive has a trailing / which will be followed by the leading / of the URL, so use:
location /videop {
root /home/rise/rises;
internal;
}
Your PHP has a badly formed header, which should include a : after Content-Type.
The PHP should should not include a body. The readfile is wrong. The whole purpose of the PHP is to issue an internal redirect which is picked up by nginx. So the PHP should return headers only.

Access a file which is located before / outside the server root directory?

I'm making an intranet for a post-sale customer service entreprise. Employee need to be able to upload img files to the intranet's server and i need to store them in a directory with is BEFORE www (the website's root directory).
Doing this using php is pretty easy but how to include these imgs on the website once they're uploaded ?
I tried this code
<img src="../img/img.png"/>
This is not working because i can't send a file if it is OUTSIDE the server's www directory ...
Is there any proper way to do that ?
Current treeview :
server root directory
|www
|(all server files)
|img
|(all img files)
(the server's index.php is located in www and the files are in img)
You cannot directly access any file outside your web directory. As your question includes the tag PHP as well, I assume you may want to use it.
What you can do is the following:
Inside your www directory, create a "image.php" file, with a similar content to:
<?php
header('Content-Type: image/png');
readfile("../img/" . $_GET['img']);
?>
And call your images with
<img src="image.php?img=myimage.png" />
Please be aware that your PHP file shouldn't be that simple :) As you may want to address multiple image formats (and providing the correct header for them), checking for malicious file path/inclusions (you don't want to use $_GET without validating/sanitizing the input), extra caching etc. etc. etc.
But this should give you an idea on how you can target your issue.
It depends on what you are trying to accomplish and how.
With "simple" html commands it is as you found out. You can't go to a directory outside of the www root.
(for xampp applications on C for exmple it is most of the time c:\xampp\htdocs).
If you are ok with using serverside commands to accomplish what you want to achieve then you could use php to do a workaround, by reading the appropriate files in via PHP.
For example if your file is named "myimg.gif" and lies in "c:\pics"
<img SRC="data:image/gif;base64,<?php echo base64_encode(file_get_contents("c:\pics\myimg.gif"));?>">
With that you are reading the contents of the file and writing it directly into the img tag.
Be aware that you need to change image/gif to what you really need there.
You can't directly access file outside/above your root directory (www or public_html).
You can create virtual directory in Apache server configuration. Google "apache virtual directory" for more information.
Virtual directory configuration example:
<IfModule alias_module>
Alias /uploaded_images/ "/home/someuser/somewhere"
<Directory "/home/someuser/somewhere">
Allow from all
</Directory>
</IfModule>
uploaded_images directory will be available from web like normal directory in www directory. You can copy files from there to another location (kind of buffering).
You can also open/copy that file by ftp from php level without changing anything in apache, but this may be really slow (use it only if you can't control apache config, virtual directory is much better).
php.net: FTP
I Agree with #kamil's idea about creating a virtual directory. However if you want to go the php route, I wrote some code that open's images in a directory before WWW and copies them to the www/images folder.
<?php
$imagepath = "../images/";
define('IMGPATH', realpath($imagepath).DIRECTORY_SEPARATOR);
$cachewwwimg = "images/";
$imagename = 'image.jpg';
copy(IMGPATH.$imagename, $cachewwwimg.$imagename)
?>
<img src="<?php echo $cacheimg.$imagename;?>"/>
I solved this by simply creating a symbolic link pointing to the global physical folder on my hosting. I didn't have root access, so I created a simple PHP file, calling it symbolic_link.php, that i put in the root of my website and that creates the symbolic link for me. Here it is:
<?php
$target = '/home/<user>/images/'; // hosting physical directory
$link = 'images'; // symbolic link inside root of web site
unlink($link);
symlink($target, $link);
After the creation of the symbolic link, I deleted the file.
This is the hosting directory structure:
/home/<user> // hosting root
|
+--/images // hosting global dir
| |
| +--/logo.png // global image
|
+--/www.website.com // hosting website dir
|
+--/images // symbolic link
Now you can use the image as if it were in your website folder:
<img src="https://www.website.com/images/logo.png" alt="...">

PHP protected file access

I am using Martin Barker's code/answer from ( PHP to protect PDF and DOC ) almost verbatum, only difference is the file I am protecting is in my user folder above the public_html folder
Folder structure
/users/websupport
/public_html
File to download is at:
/users/websupport/FileToDownload.pdf
The download.php file is at
/public_html/download.php
but Firefox tells me it cannot find the file at Firefox can't find the file at download.php.
I have verified that the file is there via ftp.
If placing the file outside the webroot do I need to add something to the sites .htaccess ? Just not sure where I am going wrong with this. Below is the code within download.php
//check users is loged in and valid for download if not redirect them out
// YOU NEED TO ADD CODE HERE FOR THAT CHECK
// array of support file types for download script and there mimetype
$mimeTypes = array(
'doc' => 'application/msword',
'pdf' => 'application/pdf',
);
// set the file here (best of using a $_GET[])
$file = "../users/websupport/2011cv.pdf";
// gets the extension of the file to be loaded for searching array above
$ext = explode('.', $file);
$ext = end($ext);
// gets the file name to send to the browser to force download of file
$fileName = explode("/", $file);
$fileName = end($fileName);
// opens the file for reading and sends headers to browser
$fp = fopen($file,"r") ;
header("Content-Type: ".$mimeTypes[$ext]);
header('Content-Disposition: attachment; filename="'.$fileName.'"');
// reads file and send the raw code to browser
while (! feof($fp)) {
$buff = fread($fp,4096);
echo $buff;
}
// closes file after whe have finished reading it
fclose($fp);
Make sure the user your php script runs as has read access to that directory.
On php embedded in apache on most debian derivatives, the user will be 'www-data'.
I had the same issue recently where readfile() and fpassthru() just would not work on my server.
What I ended up doing was creating symlinks for the files as needed and passing those to the user. You can learn how to create symlinks here.
I used
exec("ln -s source_file_full_path full_path_to_fake_file");
if you wanted your user to have a link like 'http://somesite.com/folder/fake_file.pdf' then the full path would be to where 'folder' lives on your server and you would include 'fake_file.pdf' in your fake file path.
then to expire the links I made another call to find all of the symlinks with a creation date older than x minutes. You can see how to do that in this answer. (That could be a cron job to ensure they expire on time.)

PHP secure root

My friend found a problem in my script, it gives acces to root files.
This url gives passwd file:
http://site.com/attachment.php?file=../../../../../../etc/passwd
How to escape this security hole?
Dont download the files using URL String.... Define unique IDs to denote a file, rather than paths.
You might have seen downloads like this http://www.mysite.com/download.php?id=23423 what they do, use this id, to take out the file name and path from the db and then download it.
There are several different solutions.
If there can be only a filename, a basename() solution would work.
However, if it can be path, a more complex solution is needed
//assume current directory, but can be set anything. Absolute path of course
$basedir = dirname(__FILE__);
//assume our files are below document root.
//Otherwise use it's root dir instead of DOCUMENT_ROOT
$filename = realpath($_SERVER['DOCUMENT_ROOT'].$_GET['file']);
if (substr($filename,0,strlen($basedir)) !== $basedir) {
header ("HTTP/1.0 403 Forbidden");
exit;
}
there is also a useful PHP configuration option open_basedir
You can use realpath() and dirname() to check URLs against $_SERVER['DOCUMENT_ROOT'] (or whatever directory is "safe" for downloading).
If the result of realpath() points outside the safe directory, you can deny the download request.
There's also the open_basedir security directive (and runtime option as of 5.3).
I suppose you have a directory where all attachments are stored.
Just test if file is located in your directory.
// http://www.php.net/manual/en/function.basename.php
// http://cz.php.net/manual/en/function.file-exists.php
if (file_exists($attachments_path . "/" . basename($_GET['file'])) {
// do work
}
Starx posted a solution which seems fine. It can be done without a database, though. If somebody uploads a file you can store the file as md5($filename).$extension and use your script.

Categories