Solved

Security for Downloadables

Posted on 2012-12-20
11
144 Views
Last Modified: 2013-01-03
Hello Experts!

I thought I'd tap into this great pool of genius to find out what the latest in technology is with relation to security of downloadable items.

A client of mine wants me to create a site in PHP where he can sell ebooks, videos and audio files and I wanted to know what is the best way to prevent the download of these items from off the server unless the person has paid.

Extra points to the person who can share a personal success story and how he did it.

Thanks!
0
Comment
Question by:OmniUnlimited
  • 3
  • 3
  • 2
  • +2
11 Comments
 
LVL 34

Expert Comment

by:gr8gonzo
ID: 38711721
I've created this setup several times. The methodology is secure and sound, so it hasn't evolved much over the past few years. The basic idea is done in 3 basic steps:

#1. Never keep the actual files in a web-accessible directory.

#2. Use a PHP script to access the file contents from outside the document root, and then stream it to the browser (combination of header() and readfile() lines - about 10-15 lines of code).

#3. Use the script to validate the user's permissions to access the requested file.

The bulk of the work is in step 3, because the "validation" varies per project. The most common and easy method is to have a database backend that gets updated whenever a user makes a purchase of an item, and the script simply checks the user ID when they're logged in to see if they purchased the item. If so, it streams the file. If not, it rejects with a message to indicate they need to purchase the item first.

If you want to be ultra-safe about it, then you generate a random token when the person buys the item. This token is good for one use. When they click on a link that passes the token to the download script, the download script first marks the token as used and then streams the file. This prevents the end user from sharing his credentials and letting others log in and copy the file, too.

However, all this does is handle the server-side permissions. Trying to prevent the file from being physically copied and distributed after it's been downloaded is a different story and that gets FAR FAR more complicated.
0
 
LVL 17

Author Comment

by:OmniUnlimited
ID: 38711790
Wow gr8gonzo, what fantastic information!  To be honest with you, this is my first time, so be gentle. :)

What command would you recommend using to access a non-web accessible file?
0
 
LVL 34

Expert Comment

by:gr8gonzo
ID: 38711795
There is a PHP command called readfile(). All it does is take in a filename as a parameter and then pushes all the data back to the browser.

Try creating a script like this to see how it works.

<?php
$file = "/path/to/some/file.txt";
$filename = basename($file);

if(file_exists($file))
{
	// Send download headers
	header('Pragma: public'); 
	header('Last-Modified: '.gmdate('D, d M Y H:i:s') . ' GMT'); 
	header('Cache-Control: no-store, no-cache, must-revalidate'); // HTTP/1.1 
	header('Cache-Control: pre-check=0, post-check=0, max-age=0'); // HTTP/1.1 
	header("Content-Transfer-Encoding: none");
	header("Content-Type: application/octetstream; name=\"" . $filename . "\""); // IE & Opera 
	header("Content-Type: application/octet-stream; name=\"" . $filename . "\""); // The rest  
	header("Content-Disposition: attachment; filename=\"" . $filename . "\"");
	header('Content-Disposition: inline; filename="' . $filename . '"'); 
	header("Content-length: " . filesize($file)); 
	
	// Send file contents
	readfile($file);
}
else
{
	echo "Invalid request";
}
?>

Open in new window

0
 
LVL 108

Expert Comment

by:Ray Paseur
ID: 38711814
No points for this, please, but @gr8gonzo has the right answer.  And if you want to save a lot of money and time, hire him to build it for you!
0
 
LVL 17

Author Comment

by:OmniUnlimited
ID: 38712018
Wow gr8gonzo, to get an endorsement from Ray must mean you really know what you are talking about.  Course, I could get the gist of that from the response you gave to my question.

@Ray, thanks for your always succinct comments, and you are absolutely correct.  If I want the job done right, I probably should hire gr8gonzo to do this for me.  But I am a curious, and very hands on type of person.  I want to know why things work, and so the only way I can learn is by asking and by trial and error.  I'm grateful to gr8gonzo for such great help.

@gr8gonzo, your code is freaking fantastic!!!  I attempted a test on a php file I stored in a web inaccessible section of the server and your code output its contents to the screen.  How would I do it if I wanted the browser to allow the user to download it to their computer?
0
IT, Stop Being Called Into Every Meeting

Highfive is so simple that setting up every meeting room takes just minutes and every employee will be able to start or join a call from any room with ease. Never be called into a meeting just to get it started again. This is how video conferencing should work!

 
LVL 30

Expert Comment

by:Marco Gasi
ID: 38712054
No points for this please. The only two differences I see between my code and the code posted by gr8gonzo are

1 - I don't use the line header('Content-Disposition: inline; filename="' . $filename . '"');
2 - I use basename: header ("Content-Disposition: attachment; filename=" . basename($file));

I'm not an expert about headers, but I suspect that Content-Disposition: inline make the file content to be printed on the screen.

Cheers
0
 
LVL 108

Expert Comment

by:Ray Paseur
ID: 38712833
If you want to force a download, this code will almost always work perfectly.  But you still need to test it on all browsers.
<?php // RAY_force_download.php
error_reporting(E_ALL);


// DEMONSTRATE HOW TO CAUSE A FILE DOWNLOAD


// REQUIRED FOR USE WITH THE PHP date() FUNCTIONS
date_default_timezone_set('America/New_York');

// A FILE TO DOWNLOAD - THIS LINK COULD COME IN THE URL VIA $_GET, OR COULD BE GENERATED INSIDE THE SCRIPT
$url = "http://www.LAPRBass.com/piechart.png";

// THE USE CASE FOR THE FUNCTION
force_download($url);



// FUNCTION TO FORCE A DOWNLOAD FROM A FILE
function force_download($filename)
{
    // GET THE CONTENTS OF THE FILE
    $filedata = file_get_contents($filename);

    if ($filedata)
    {
        // GET A NAME FOR THE FILE
        $basename = basename($filename);

        // THESE HEADERS ARE USED ON ALL BROWSERS
        header("Content-Type: application-x/force-download");
        header("Content-Disposition: attachment; filename=$basename");
        header("Content-length: ".(string)(strlen($filedata)));
        header("Expires: ".gmdate("D, d M Y H:i:s", mktime(date("H")+2, date("i"), date("s"), date("m"), date("d"), date("Y")))." GMT");
        header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT");

        // THIS HEADER MUST BE OMITTED FOR IE 6+
        if (FALSE === strpos($_SERVER["HTTP_USER_AGENT"], 'MSIE '))
        {
            header("Cache-Control: no-cache, must-revalidate");
        }

        // THIS IS THE LAST HEADER
        header("Pragma: no-cache");

        // FLUSH THE HEADERS TO THE BROWSER?
        flush(); // NOT SURE THIS IS NECESSARY

        // CAPTURE THE FILE IN THE OUTPUT BUFFERS - WILL BE FLUSHED AT SCRIPT END
        ob_start();
        echo $filedata;
    }

    // ERROR
    else
    {
        die("ERROR: UNABLE TO OPEN $filename");
    }
}

Open in new window

You can wrap that script with some kind of PHP authentication, perhaps integrating a data base that contains a record showing that the client has paid for the download.  You might use a design that is sort of like the "handshake" in this article.
http://www.experts-exchange.com/Web_Development/Web_Languages-Standards/PHP/A_3939-Registration-and-Email-Confirmation-in-PHP.html

There is another issue that may or may not matter to you, and that is the issue of copyright and redistribution.  Because it is so easy to take electronic information and distribute it, enforcement of online electronic copyright is essentially impossible.  Thus we resort to shaming the thieves.  The general design pattern I've seen for this includes adding the following information to the document:  The source URL, date and time of download, the identity of the individual who performed the download, and the statement of copyright, along with a URL link to report theft and abuse.  In the case of images, this information is often put into a watermark.  With PDF documents, it's usually put into the document footer on every page.  With video files it may be in the introduction or trailer and is usually repeated at some point in a semi-transparent watermark.  I have no idea how to do it with pure audio, so I would probably just make a video.
0
 
LVL 34

Accepted Solution

by:
gr8gonzo earned 475 total points
ID: 38713372
Yes, my apologies. marqusG is correct. I copied and pasted that code from another script that displays PDFs inline in the browser (and other valid types), and prompts downloads for other kinds of files, but I accidentally copied that "inline" line along with it. Take it out and it should prompt you for a download.

I appreciate the hiring comment, but I completely understand the DIY sort of mentality that most people here have. (My schedule's a bit booked anyway!)

I'm actually packing for a trip so I gotta run, but wanted to chime in and concur with marqus's comment. Also, marqus mentioned basename() but that is already performed on the second line of code. I think most of the veterans here have the same approach - nothing terribly unique about my code. It's just tried and true. :)
0
 
LVL 30

Expert Comment

by:Marco Gasi
ID: 38713440
lol @gr8gonzo: sometimes I'm really blind: I just didn't see it! :-)

Bye.
0
 
LVL 33

Assisted Solution

by:Slick812
Slick812 earned 25 total points
ID: 38714362
@  OmniUnlimited
if you're doing a commercial "pay for a file" kind of thing, my hint for you, which may save you some head ache at first, is to have a user friendly ERROR recovery for unsuccessful paid file retrieval, as one example here just has -
echo "Invalid request";
if there is no file, easy to guess that the paid user will be six shades of angry, OR having a -
"please contact administrator about error"
won't be much better,
you might consider an internal error alert, ,  so if they do not get their file, or have other system (dumb user) generated trouble, your site techno would be alerted. Also some marketing language (sales oriented) for error messages are a great help -

"Our system can't even find this file, we're not bringing you what you paid for, So Sorry!
Our Geek Techies have been alerted to get you what you paid for, If you need to contact us
about this big blunder, please click here -> HELP!"
0
 
LVL 17

Author Closing Comment

by:OmniUnlimited
ID: 38742761
My apologies to everyone for the delay.  Between the holidays and some system failures, I wasn't able to respond until now.

Thank you to all who participated.  gr8gonzo, your solution is absolutely fantastic, it works like a charm.  Thank you for the code.

Here's wishing the very best and prosperous New Year to everyone!
0

Featured Post

What Should I Do With This Threat Intelligence?

Are you wondering if you actually need threat intelligence? The answer is yes. We explain the basics for creating useful threat intelligence.

Join & Write a Comment

If you're not part of the solution, you're part of the problem.   Tips on how to secure IoT devices, even the dumbest ones, so they can't be used as part of a DDoS botnet.  Use PRTG Network Monitor as one of the building blocks, to detect unusual…
Find out what Office 365 Transport Rules are, how they work and their limitations managing Office 365 signatures.
The viewer will learn how to dynamically set the form action using jQuery.
The viewer will learn how to count occurrences of each item in an array.

705 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

18 Experts available now in Live!

Get 1:1 Help Now