I'm working on file storage and my design is to display data from my database about the file along with a button fitted with an a href tag to download my file. I successfully do all of this when I keep my storage in my website root - but upon suggestions, i've made changes.
I now have my project root in my C:/ drive but my storage I want outside of the root but I keep my storage in my d:/ drive, per suggestions due to security - along with the fact my d:/ drive is my cloud storage anyway. From what I see online, that means I can't use <a href="d:/storage/Username/file download> anymore to download my file!
If that is true, how do you download from outside your website root?
And if not, how do I get past the security restrictions on downloading outside the root using my href method?
Thanks!
you can use readfile()
either link to downloader.php?f=somefile.jpg
of use rewriterules.
in downloader.php do something like
<?php
$path = 'D:/map/';
if( login_check($user) && is_file($path . $_GET['f']) ) {
header('Content-Type: image/jpeg');
readfile($path . $_GET['f']);
}
?>
I added the login_check() because you mentioned something about security.
Further it could be advisable to check the parameter for illegal characters (especially slashes)
And the content type may vary with chosen file
Got it to work, thanks for the start from Ivo P.
I started by sending to download.php my file name, file extension, and file path all separated by * (The * is not an allowed file name). An example is:
download.php?f=myTextFile*txt*d:/storage/test/myTextFile.txt
Then, I explode $_GET['f'] by *, make sure the array is size 3, then set content headers:
$file_info = explode("*", $_GET['f'], 3);
if(is_file($file_info[2]) && count($file_info) == 3) {
header('Content-Description: File Transfer');
header("Content-Type: " . mime_content_type($file_info[2]));
header('Content-Disposition: attachment; filename="'.$file_info[0] . '.' .$file_info[1].'"');
header('Expires: 0');
header('Pragma: public');
readfile($file_info[2]);
}
And that was the working solution for me! Tested on txt, jpg, and exe - works perfectly. I have other personalized for my project tests to make sure that 'f' is valid (so users can't screw it up by going to address bar and making a mess of things, see real_escape_string) but that's the gist of it!
Edit thanks to Bjorn: Keep in mind this allows full access of server, make sure you personalize your own download.php to accept inputs following your own format (otherwise anyone could steal your php files, js files, ect!)
Related
On a godaddy hosted website using CPanel, I have a small PHP script that shows each line in a text file that's on the server. Each line contains a private href link to a PDF that only the logged-in user can see. The links points to various PDFs in the same folder on the server. The code works fine and I can click on the link and see each PDF.
The problem is that each PDF can also be seen by using a direct URL query (i.e. website/folder/pdfname.pdf). As these are private PDFs, I don't want them public. I've tried changing CPanel permissions on the folder to "owner" - but that seems to prevent the PHP script from opening the PDFs also.
Is there a way to allow a PHP script access to PDFs in a folder - but prevent direct URL references?
NOTE: I'm not particularly adept at PHP or CPanel - sorry.
Code...
$fname = "PDF-" . $user_name.".txt";
$fnum = fopen($fname,"r");
echo "<tr>";
While (($str = fgets($fnum)) !== false) {
$arr = explode("|",$str);
for ($x = 0 ; $x < count($arr); $x++) {
echo "<td>$arr[$x]</td>";
}
echo "</tr>";
}
echo "</tr>";
fclose($fnum);
File contents...
Xyz Company|21 Jan 2018| website link
Xyz Company|21 Jan 2018| website link
Xyz Company|21 Jan 2018| website link
Xyz Company|21 Jan 2018| website link*
Asside from removing the files from the root, if you are running apache, you can change your .htaccess (I'm sure windows-based system have a web.config equivalent) to forbid access to certain files directly. If you add this snippet to that file, it will deny files with .pdf extension:
<FilesMatch "\.(pdf)$">
Order Allow,Deny
Deny from all
</FilesMatch>
From there, inside your app, you can create some sort of system for curating your PDF links, so if you store the real path in a database and use the id as the link similar to:
http://www.example.com/?file=1
or if you just do a simple scan:
<?php
# The folder that the PDFs are in
$dir = __DIR__.'/website/folder/';
# Loop over a scan of the directory (you can also use glob() here)
foreach(scandir($dir) as $file):
# If file, create a link
if(is_file($dir.$file)): ?>
<?php echo $file ?>
<?php
endif;
endforeach;
Then, if the user tries to download using the link, you check they are first logged in and if they are, download the file by doing a script like so BEFORE you output anything else to the browser (including spaces):
<?php
session_start();
# First check that the user is logged in
if(empty($_SESSION['username']))
die('You must be logged in to download this document.');
# Not sure which directory you are currently in, so I will assume root
# I would do basename() here incase the user tries to add in something like:
# ../index.php and tries to download files they are not supposed to
$file = __DIR__.'/website/folder/'.basename($_GET['file']);
if(!is_file($file))
die('File does not exist.');
# Double check that the file is a pdf
elseif(strtolower(pathinfo($file, PATHINFO_EXTENSION)) != 'pdf')
die('File appears to be invalid.');
# Start download headers
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="'.basename($file).'"');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . filesize($file));
readfile($file);
exit;
One simpler and basic example (and derivative of previous answer) is to use two separate PHP files, where one is evaluating a set cookie (set to expire soon) in the browser upon link click (set via JS or PHP or other). If the cookie was read correctly, the first PHP page imports a second page that utilizes the PHP header() redirect containing your original file name forcibly downloaded with another name. Using the Content Disposition header field.
In action this works like this
1: Original page with download links - we set the cookie to work for 2 minutes
<a onclick="setCookie(1, 1, 2, 60)" href="php-secure-files-delivery-page.php">Download My Final PDF name.pdf</a>
<script type="text/javascript">
// set a cookie with your own time limits.
function setCookie(days, hours, minutes, seconds) { // Create cookie
var expires;
var date = new Date();
date.setTime(date.getTime()+(days*hours*minutes*seconds*1000));
expires = "; expires="+date.toGMTString();
document.cookie = "my_cookie_name"+"="+"my_cookie_value"+expires+"; path=/";
}
</script>
On the link page we include a hyperlink with the evaluating PHP page. Here we use JavaScript to set a cookie using the custom function setCookie(days, hours, minutes, seconds), that will receive your wishes for expiry. Just note that 1 is the minimum number. Not 0.
2: Download page - evaluating cookie and presenting texts, or simply downloading the file
(php-secure-files-delivery-page.php)
<?php
// if the cookie is set correctly, load the file downloader page.
if (isset($_COOKIE['my_cookie_name'] && $_COOKIE['my_cookie_name'] === 'my_cookie_value')) {
require_once 'file-downloader.php'; // the file will force the download upon import.
} else {
die('The link expired, go to your downloads section and click on the link again.');
}
?>
Here we evaluate the cookie, present either the correct info or die(). Using require_once we get the PHP page into the current one.
3: Imported file includer PHP page
(file-downloader.php)
<?php
// We'll be outputting a PDF
header('Content-Type: application/pdf');
// It will be downloaded as your-downloaded.pdf
header('Content-Disposition: attachment; filename="your-downloaded.pdf"');
// The PDF source is in your own specified long name
readfile('original-with-really-weird-original-name.pdf');
?>
Results
User always go to the same page, being presented with the appropriate information.
You can name your original files on your server anything you want, like "my_really_difficult_and_long_file_name.pdf", while the user sees only the nice pretty file name when the file is downloaded.
for more files, use an extra input in the cookie function to take the file name too, and some if statements in the php downloader page, that looks for separate end PHP pages to require_once.
If you go to the browsers "Downloads" section to try to get the url of the downloaded file, you see the initiating PHP page, the second page, that leaves you empty with a die() if no correct cookie was set. That cookie is only set when you want it to. On your pages. You can of course do this in JavaScript too, but that will expose the cookie, still, for most unauthorized sharing, that takes care of it.
Lastly, easy security for your folder (without Apache/Nginx/.htaccess stuff)
Using .htaccess files on local folders or directives on your server is the best and most secure way. But that´s not transferable to your other applications on other systems. Instead use a index.php and a default.php page on your PDF file´s parent folder, where they are located, including this header redirect to wear off unwanted visits:
<?php
header("Location: http://yoursite.com/some-other-page/"); /* Redirect browser here */
?>
This is more of a security question that i am not sure if this approach is safe and secure enough way to download a file and present to a web user ?
We have customers invoice files stored in a server location (publicly inaccessible location), then we do read them via the PHP code in a file (in public location with a file ) like below,
of course we authenticate the user with the session before accessing them, however if the end-user knows where the files are he can manipulate his request to read UN-authorised invoices.
I just wonder, if this way of
1. Presenting files to the end-user is secure enough ?
2. End user will not have any knowledge at all, of where the files are stored in the server ?
3. Any other recommendation on how to handle similar situation ?
$i = $invoice->get();
$filename = sprintf(INV_PDF_FILENAME,$i['customerid'],date('Ymd',$i['dateIssued']));
$x = sprintf('/tmp/invoices/%s',$filename);
header('Content-type: application/pdf');
header('Content-Disposition: attachment; filename="'.$filename.'"');
header('Expires: 0');
header('Pragma: cache');
header('Cache-Control: private');
readfile($x);
Yes, assuming there are no other vulnerabilities (i.e. the user being able to alter the parameters that go into $filename to perform directory traversal attacks).
Barring any additional vulnerabilities, the code provided will not leak its local file path to the end user.
Make sure uploaded files are stored outside the document root. Verify that realpath($x) begins with the directory you expect them to be located in.
For example:
$x = sprintf('/tmp/invoices/%s',$filename);
$realpath = realpath($x);
if (strpos($realpath, '/tmp/invoices/') !== 0) {
// Note: The strict comparison to 0 matters.
// It means that the real file path must begin with "/tmp/invoices/"
// or else this redirect/exit gets called.
header("Location: /error");
exit;
}
When user want to download file from my web site, user have to click link like below
https://www.example.com/download.php?aaa=111&bbb=222
download.php
<?PHP
session_start();
include("connect.php");
$aaa = mysql_real_escape_string($_GET[aaa]);
$bbb = mysql_real_escape_string($_GET[bbb]);
if(($aaa = '111')&($bbb = '222')) // this line is example for ask stackoverflow //
{
$filePath_try_to_download = 'attachments_files/test.pdf';
if(file_exists($filePath_try_to_download))
{
$fileSize = filesize($filePath_try_to_download);
$fileName = "test.pdf";
header("Cache-Control: private");
header("Content-Type: application/stream");
header("Content-Length: ".$fileSize);
header("Content-Disposition: attachment; filename=".$fileName);
// Output file.
readfile ($filePath_try_to_download);
exit();
}
}
?>
I want to know when user download file from this link https://www.example.com/download.php?aaa=111&bbb=222 user can get my file path on server or not (attachments_files/test.pdf). If user can get my file path, how can i hide it's ? (file in this dir is very importance)
Since I was posting comments from my Phone, they couldn't really explain much, so here goes your answer.
I want to know when user download file from this link https://www.example.com/download.php?aaa=111&bbb=222 user can get my file path on server or not (attachments_files/test.pdf).
No, Users can not see that file path which you are reading via readfile(). They will not be able to find out that file's location at all.
And if you want to eliminate any chances of people guessing the file path simply put those files outside of your web root folder and then readfile() them from there.
$filePath_try_to_download = 'attachments_files/test.pdf';
That path is only known to your PHP code, which is not visible to users hence they have no idea from where did you read the file they are downloading, just eliminate the guesswork chances though :)
And Obviously you have to secure access to this url https://www.example.com/download.php?aaa=111&bbb=222 otherwise what's the point!
No. The user cannot get the file path. He only get the content outputed by PHP script.
Your can do this and the user only get the "Hello" string. So it's your PHP script's role determining which contents the user can get.
<?php
echo "Hello";
?>
I face a case I never did, and I dont know how to properly do it.
I have a php script which generate files for clients. At the end of the script, I echo the path for them to download the file, simply.
How can I do to provide the file - or the path or any what - for downloading it, and be sure to delete the file once downloaded.
Widely, I'd like to make the file available for one/unique download only. How to ?
EDIT
I cannot use headers
There are a few components to getting this to work. Without knowing which framework you use, I'll use comments as placeholders.
There is no way to do it without using the header function, though.
Here is the source for a file that outlines the process:
<?php
$fileid = $_GET['fileid'];
$key = $_GET['key'];
// find the file in the database, and store it in $file
if ($keyMatches) {
// it is important for security to only use file paths from the database
$actualPath = $file->getPathOnDisk();
$fileInfo = finfo_open(FILEINFO_MIME_TYPE);
$mime = finfo_file($fileInfo, $actualPath);
$fp = fopen($actualPath, 'rb');
header("Content-Type: " . $mime);
header("Content-Length: " . filesize($actualPath));
fpassthru($fp);
}
else
{
http_response_code(403); // forbidden
}
You'll use this by linking to download.php?fileid=1234&key=foobar, and generating the URL at the same time you generate the key and store it in the database.
For security, you'll keep the files outside of the web root, meaning they cannot be accessed through the web server without going through a script.
fpassthru is reasonably fast, and will not likely have a performance impact.
You must do a download file gateway, like download.php?id=XXX
Where XXX is the unique ID of each file you will store in DB. And of course, the file to be downloaded.
Then, each time a user will visit the page, you can :
- Check if he has already downloaded the file
- If no, redirect it to the real path of file
- If yes, display 403 message.
When a user download a file, update the DB, generate or copy the file to a new name, you play with headers, and delete file upon download or after a small timeout.
Coult not find any similar problem solved on the web, so here's my situation:
I have a .jsp "webpage" that generates a .csv file based on specific parameters.
As an example, if I use my browser to open the site, I type in:
redownloadsubmitter.jsp?id=225&batch_id=2013_11_20&orgshort=NEP
The script then uses the data in the query string and generates the matching .csv file, named: NEP_DETAILS_2013_11_20.csv
Now what I want is to not manually having to use my browser, open the script and download the file to my local harddrive. Instead I want to use a PHP script that grabs the content and then can further format it, based on my needs.
I thought about the following code, but that did not work. Instead it returns nothing, empty website when I try it..
$download = file_get_contents('redownloadsubmitter.jsp?id=225&batch_id=2013_11_20&orgshort=NEP');
echo $download;
Any other ideas?
NOTE: just in case someone has this question: I have no access to the .jsp file and I therefore cannot change how it operates.
file_get_contents() isn't smart and doesn't know that's a URL you're passing in. It's trying to literally open a local file whose name is redownloadsubmitted.jsp.etc......
If you want f_g_c() to do an HTTP operation, then you'll have to include a full-blown URL:
$download = file_get_contents('http://example.com/redownloadsubmitter.jsp etc....');'
Try this code for download file.
<?php
/**
* $filename filename in server
* $downloadname filename when download file
*/
$filename = __FILE__;
$dowloadname = 'PHPDownload.php';
Header("content-type:application/octet-stream");
Header("Accept-Ranges: bytes");
Header("Accept-Length: ".filesize($filename));
Header("Content-Disposition: attachment; filename=".$dowloadname);
if(file_exists($filename) && $fp=fopen($filename,"r")) //file exists and open it
{
echo fread($fp,filesize($filename)); //read write to the browser
fclose($fp);
}
//End_php