Secure documents with PHP - php

I have a simple login / access control system to secure some restricted pages, but within these pages there are links that need to be secure, i.e. Word documents. Therefore if I keep these resources within the webroot they could accessible via a URL. What is the best method to secure these resources that are within the restricted page. I know I could password protect the folder but the user would then be challenged twice, one for the restricted page and then for the resource link. Any advice ?

You have a few options here, depending on your use-case.
Use PHP to serve the file. Basically, either intercept all attempts to read the file by PHP (using a mod_rewrite rule), or link directly to PHP and put the file(s) below the document root. Then use something like fpassthru to send the file to the browser. Note that you must properly set the content-type headers. Also note that this will eat up a lot of server resources since the server needs to read the entire file in PHP and send it, so it's easy, but not light.
$f = fopen('file.doc', 'r');
if (!$f) {
//Tell User Can't Open File!
}
header('Content-Type: ...');
header('Content-Length: '.filesize('file.doc'));
fpassthru($f);
die();
The main benefit to doing it this way is that it's easy and portable (will work on all servers). But you're trading off valuable server resources (since while PHP is serving the file, it can't be serving another page) for that benefit...
Use the web-server to send the file using something like X-SendFile (Lighttpd), X-SendFile (Apache2/2.2) or X-Accel-Redirect (NginX). So you'd redirect all requests to the file to PHP (either manually or rewrite). In PHP you'd do your authentication. You'd send the Content-Type headers, and then send a header like X-SendFile: /foo/file.doc. The server will actually send the file, so you don't have to (it's far more efficient than sending from PHP natively).
header('Content-Type: ...');
header('X-SendFile: /foo/file.doc');
die();
The main benefit here is that you don't need to serve the file from PHP. You can still do all of your authentication and logging that you'd like, but free up PHP as soon as you start transferring the file.
Use something like mod_secdownload (lighttpd) or mod_auth_token (Apache). Basically, you create a token in PHP when you generate the link to the file. This token is a combination of a MD5 of a secret password combined with the current timestamp. The benefit here, is the URL is only valid for how long you specify in the configuration (60 seconds by default). So that means that the link you give out will only be active for 60 seconds, and then any further attempts to see the content will generate a 400 series error (I'm not positive which off the top of my head).
$filename = '/file.doc';
$secret = 'your-configured-secret-string';
$time = dechex(time());
$token = md5($secret . $filename . $time);
$url = "/downloads/$token/$time$filename";
echo "Click Here To Download";
The main benefit to doing it this way is that there is very little overhead associated with the implementation. But you have to be comfortable with having URLs being valid for a set time only (60 seconds by default)...
Push it off onto a CDN to handle. This is like option #3 (the one above), but uses a CDN to handle the file serving instead of your local server. Some CDNs such as EdgeCast provide a similar functionality where you set a token which expires after a set amount of time. This case will work nicely if you have a lot of traffic and can justify the expense of a CDN. (Note: no affiliation with the linked CDN, only linked because I know they offer the functionality).
As far as how I'd personally do it, I've done all of the above. It really matters what your use-case is. If you're building a system that's going to be installed on shared hosts or multiple different servers which you don't control, stick to the first option. If you have full control and need to save server resources, do one of the other two.
Note: there are other options than these three. These are just the easiest to implement, and most of the other options are similar enough to these to fit into the category...

I haven´t tried it with word documents (only with images), but I would try to serve the document directly from php, see my answer about images.
It would be something like an a tag linking to a php page that serves a Word document as its content type.

Related

Which is more efficient: redirecting to an image,or pushing the image through php?

An external party has to use the dynamicly generated images that are used on our site. For that, I created a function that serves the image through a url. E.g. http://test.com/image/$code/$width/$height. So, it finds the image with code $code, resizes it to $width and $height, and then serves the image itself (not the url). The external party can now use <img src="http://test.com/image/$code/$width/$height" />
This is working fine, but of course this is a quite a hit on the server every time it is used, especially if the image is used in newsletters that are sent to 1000s of people.
I can make it a little more efficient by checking if the image is already existing and then returning it without generating it first, of course. But I was also looking at redirection.
So, basically, my question is if it is more efficient to generate/load the image and then serve it, or doing a 301 redirect to the actual image. I know that this also has some drawbacks, most notably needing two requests per image, but I am wondering how that compares to pushing an entire image through php and the image generation process.
Update:
Maybe I should clarify things a bit.
I am interested in server load, not so much in UX. Most probably the latter is worse off by redirecting, as it does the double amount of server requests).
The difference in the two situations is as follows:
Image generation situation:
- Check if image exists. If not, generate.
- Then do this
$path = BASE_PATH."/".$image->Filename;
$mimetype = image_type_to_mime_type(exif_imagetype($path));
header("Content-type: ".$mimetype);
echo readfile($path);
die;
Image redirect situation:
- Check if image exists. If not, generate.
- Then do this
$location = BASE_HREF."/".$image->Filename;
$mimetype = image_type_to_mime_type(exif_imagetype($path));
header('Location: '.$location,true,301); //or maybe a 303 (other)
die;
Obviously, in the second situation php has to do less and apache more (serve 2 files instead of one). In the first situation apache has to do more and php less. So the question is, is the extra work that php has to do more or less than the extra work that Apache has to do?
I don't know, but my gut feeling is that if you're already running a PHP script, then the additional cost of writing some headers and calling readfile() will be trivial.
More importantly, is the file going to be used more than once by the same user?
If so, you could benefit significantly by making the file cacheable. If you redirect to a static file, the web server will automatically take care of the caching. If you serve the file through PHP, you will have to cache it yourself.
To do this, you need to:
Compute a Last-Modified date or an ETag (unique ID).
Check the request headers for an If-Modified-Since: or If-None-Match: header.
Compare the header values against the computed date or ETag.
If your file is newer or doesn't match the ETag (or the request headers don't exist), send a 200 response including the file.
Otherwise, send a 304 response without the file.
In both cases, send the current Last-Modified: or ETag header, and maybe a sensible Expires: header.
First, it is critical that you check to see if the image already exists before you generate the thing. It isn't going to help "a little." Creating a .jpg is a lot more expensive than checking for a filename.
The rest of your question isn't completely clear to me, but I'll try for an answer anyway.
301 redirects aren't generally used for what you're talking about. They're to tell a search spider that a page has moved permanently. Functionally, it doesn't work any differently than a regular redirect.
Moving on, I'm even more confused. "and then serves the image itself (not the url)"
Servers pretty much always serve both. The url tells it which file to send and then it sends the binary data it finds at that URL. And files are always saved somewhere, even if it's just a tmp folder where it will be deleted. Unless you've done something exotic, in which case, ask yourself why you did that.
If the image will never be used again (not even if the same user re-vists the same page) simply send the remote server a link to where the lives in the temporary folder on your server after the image was created. If the image might be re-used, save it somewhere. The local server would be easier and the extra request by the remote won't slow things down a noticeable amount.

Preventing the browser caching a linked file

I've been tasked to maintain a PHP website with a function which automatically generates RTF files and provides a link to download. Each time the previously generated file is overwritten by the new one.
However, it seems that upon attempting to download the generated file, the browser will sometimes retrieve a cached version which is normally different from the latest version (so you get the same document as last time rather than the one you requested).
I managed to work around this by giving each generated file a unique name based on the current timestamp but this generates lots of clutter in the directory which will need to be cleaned out periodically. Ideally I would like to tag this file such that the browser won't cache it and will get the latest version every time. How might I achieve this?
In addition to the possibility of adding a random GET string to the URL (often the easiest way), it would also be possible to solve this by sending the right headers.
Because you are generating static files, this would require a setting in a .htaccess file. It would have to look like this:
<FilesMatch "\.(rtf)$">
Header set Cache-Control "no-store"
</FilesMatch>
Easiest way? Instead of linking to http://yoursite.com/file.rtf, link to http://yoursite.com/file.rtf?<?=time()?> . This will append a query string parameter which will vary each time the client requests it, and it won't therefore be cached.
You could tag the current time value to the file you serve
.../file.rtf?15072010141000
That way you don't have to generate unique names, but you can ensure future requests are not cached.
Although the simple solution of using no-cache header as suggested by Pekka will work, you will lose the potential benefit of caching if same file is downloaded several times.
If the RTF file is big and you would like your user to enjoy benefit of caching when the file is actually not changed, you might want to check this answer:
How to use HTTP cache headers with PHP

How to find out how many times a file been downloaded?

I have an image that send to affiliate for advertising.
so, how can I find it out from my server the number of times that image been downloaded?
does server log keep track of image upload count?
---- Addition ----
Thanks for the reply.. few more questions
because I want to do ads rotation, and tracking IP address, etc.
so, i think I should do it by making a dynamic page (php) and return the proper images, right?
In this case, is there anyway that I can send that information to Google Analytics from the server? I know I can do it in javascript. but now, since the PHP should just return the images file. so what I should do? :)
Well This can be done irrespective of your web Server or Language / Platform.
Assuming the File is Physically stored in a Certain Directory.
Write a program that somehow gets to know which file has to be downloaded. Through GET/POST parameters. There can be even more ways.
then point that particullar file physically.
fopen that file
read through it byte by byte
print them
fclose
store/increment/updatethe download counter in database/flatfile
and in the database you may keep the record as md5checksum -> downloadCounter
It depends on a server and how you download the image.
1) Static image (e.g. URL points to actual image file): Most servers (e.g. Apache) store each URL served (including the GET request for the URL for the image) in access log. There are a host of solutions for slicing and dicing access logs from web servers (especially Apache) and obtaining all sorts of statistics including count of accesses.
2) Another approach for fancier stuff is to serve the image by linking to a dynamic page which does some sort of computation (from simple counter increment to some fancy statistics collection) and responds with HTTP REDIRECT to a real image.
Use Galvanize a PHP class for GA that'll allow you to make trackPageView (for a virtual page representing your download, like the file's url) from PHP.
HTTP log should have a GET for every time that image was accessed.
You should be able to configure your server to log each download. Then, you can just count the number of times the image appears in the log file.

how to make a user restrict to download?

I know this question is silly.
But as per our intelligent Client request, I am not able to answer his question. Any one help for this.
We are building a online tutoring site. where it contains pdf, .ppt, .doc formats files are uploaded for reading as course materials. His (Client) request is that user can read all the contents but they must not download the materials and use this.
That is all the documents must be opened in their browsers.
Is it possible? Any other ideas?
Any other ideas?
Explain to your client that the only way for a document to appear on a user's computer screen is for the document to exist on that user's computer.
In other words, viewing a document involves downloading it. Even supposing the software on the user's computer somehow makes it impossible for the user to directly manipulate an electronic copy of the material, the user can take out a digital camera and take a picture of the screen.
There are ways to make it difficult for the user to save a copy of the file. However, it's likely that this will do more harm (frustrating users) than good (preventing theft).
Some users may want to peruse the material at times when they do not have an internet connection, or may want to copy it onto their mobile device (for instance), but accessing the internet on their mobile device is expensive so they would like to do the download on their computer.
If you send the data to the client the client has effectively downloaded it. You can make this difficult, but not impossible.
The only sure way to prevent downloading is to prevent viewing.
If this is a copyright problem it should be solved with legalese, not software.
Here are some guide-lines you may consider:
Don't put direct link of files such as:
Download
Instead, try to generate your pdf dynamically or put a another encrypted medium for
downloading eg:
Download
2: Don't allow directory browsing, use htaccess file with following commands:
Deny from ALL
3: Not sure, but you may possibly allow file opening this way too:
$filename="/path/to/file.jpg"; //<-- specify the image file
if(file_exists($filename)){
header('Content-Length: '.filesize($filename])); //<-- sends filesize header
header('Content-Type: image/jpg'); //<-- send mime-type header
header('Content-Disposition: inline; filename="'.$filename.'";'); //<-- sends filename header
readfile($filename); //<--reads and outputs the file onto the output buffer
exit; //and exit
}
Note: above is just an example of image not pdf but you can modify it for your needs.
An online site does not necessarily mean it is a web site. You could write a custom client that accesses the data and displays it.
The data would need to be encrypted between the client and the server. It probably should not be sent 'in bulk' either.
The effort associated with developing that is prohibitive.
You could license the software that allows users to read books, page by page, that is part of the Safari Books Online web site.
As best I can tell, they take the pages that they are going to display and turn them into small images. These images look as if they are sent in a random order, and assembled by the browser via javascript.
These tactics won't stop a determined person from getting your clients content... but the effort is unlikely to be worth it.
You could put the docs into Google docs and embed the docs viewer into your site. Of course, there's no stopping people from taking screenshots, copy/pasting text, downloading HTML, etc.
What do you mean by "read" but not "download"?? Do you know that even if you disable cache (which by itself is a bad idea) won't restrict an eaaaasy right-click>view source, "save target as", etc.?
I mean, the best you can have is a flash reader that is harder to save the content from, and that means disabling selection and copying, but anyway, it doesn't forbid anything.
The only way to forbid download is to return HTTP 403 :)

How to password protect streaming videos with php

What is the best way to password protect quicktime streaming videos using php/.htaccess. They are being streamed using rtsp, but I can use other formats if necessary.
I know how to do authentication with php, but I'm not sure how to setup authentication so that will protect the streaming files urls so that a user can't just copy the url and share it.
Or am I overthinking this and I can just use a normal authentication scheme and place the files in a protected directory?
Both nginx and lighttpd web servers have X-Send-File headers you can return from PHP. So you can do your checks in PHP and then conditionally server out the file.
if (check_user_can_access()){
header('X-sendfile: /path/to/file');
} else {
header('HTTP/1.1 403 Fail!');
}
Lighttpd also has a neat module called mod_secure_download that allows you to programatically generate a URL that will only be valid for a short time period.
Nginx, and possibly lighttpd, allow you to cap the download speed, so you're not sending out streaming data faster than it can be consumed.
Either way, you want to use your web server for serving files. Serving them through PHP is possible, but slow.
Try to use Amazon S3 service, it got it's quirks but it makes sense once you get familiar with it.
There are hooks in their API to achieve temporally URL's that are active for specified time, so you can freely show url to visitor because it won't work 10 minutes or so later.
It's almost trivial thing to do with php (around 15 lines of code), there are a lot of examples on their forums so you dont need to go from scratch and read full documentation on how to achieve this.
What kind of authorization you will do before generate and show links it's up to you.
You can also have it look like it's served from your domain like video.yourdomain.com instead of standard s3 URL's.
Last thing, it's cheap - we payed around 2 US$ for the month of testing and deployment when I uploaded 8 GB and downloaded it 3 times completely and initialized download for around 100 times. The person I was doing this for is so satisfied by price that he wants to move all of his downloadable media to s3.
Now, re reading everything I wrote it looks like commercial/spam but I'm so satisfied with service because I coded everything for audio files earlier, and it took days until everything worked just fine and this took couple of hours to implement (mostly getting familiar with service).
You might want to take a look at:
mod_xsendfile (for apache)
It enables you to internally redirect to a file.
So you could point your download link to checkCredentials.php
<?php
if ( isAuthorised($_POST['user'], $_POST['pass']) ) {
header("X-Sendfile: $somefile");
header("Content-Type: application/octet-stream");
header("Content-Disposition: attachment; file=\"$somefile\"");
exit(0);
} else {
show403('bad credentials');
}
?>
This module is also available for other webservers. If I remember correctly, the idea originally comes from lighttpd, but - as Josh states- is also available for nginx.
First off, it is very easy to spoof a referer. This information is stored in the user's browser, so a user can simply telnet into your server and provide his own referer which matches your domain.
A couple things you could try:
First, more secure, but still spoofable. mod_rewrite provides the ability to check cookies. What you could do is set a cookie when the user visits your website that contains some obscure data. Then, you could modify your RerwriteCond to something like this:
RewriteEngine On
RewriteCond %{HTTP_REFERER} !^$
RewriteCond %{HTTP_COOKIE} obscurename=obscurevalue [NC]
RewriteCond %{HTTP_REFERER} !^http://(www\.)?yourdomain.com/.*$ [NC]
RewriteRule \.(asx¦ASX)$ http://www.yourdomain.com/images/leech.gif [R,L]
Another, better technique would involve working with PHP and mime-types. I'm not sure to what extent this would support streaming content, but I assume it'll work. What you can do is have all your video links point to a .php file (the query string will determine which video has been selected). Then, when a user tries to visit this link, you do something like so:
<?php
// You could also check some sort of session variable
// that is set when the user visits another part of your
// site
if(!isLoggedIn()){
header("Location: errorPage.htm");
exit;
}else{
// Get the name of the file specified
$file = get_file_name($_GET['fileID']);
// Specify the proper mime-type for the data you're sending
// (this may have to change, depending on your situation)
header("Content-type: video/vnd.rn-realvideo");
// Read the file and output it to the browser
readfile($file);
}
?>
From what I read, most servers know which mime-types are streaming mime-types, so the browser should be able to figure out how to handle the streaming file properly.

Categories