PHP: Let user download purchased file ONLY

2020-05-28 11:07发布

I am forseeing a problem with allowing customers who purchase some content from me via PayPal. I will offer multiple, intangible goods. When someone completes their purchase for one of these goods, they will be redirected to a landing page - let's call it "thank_you.php" - which will automatically queue up a download and allow a link to queue up download in case it doesn't start automatically. This will be done by passing the unique item ID to the download page ("download.php").

This method is essentially a mimic of the top answers from these threads:

PHP generate file for download then redirect

A PHP script to let users download a file from my website without revealing the actual file link in my website?

However, I fear that once the user is on "thank_you.php" they can download their item, then use Firebug (or equiv.) to edit the item ID and download another different item:

<a href="download.php/38a205ec300a3874c867b9db25f47c61">Download Here</a>

to

<a href="download.php/7c8ddc86c0e4c14517b9439c599f9957">Download Here</a>

I need ideas and help from you guys who are far better at this than I: what (& how) could I implement as a solution that would still allow the same customer access and leisure, yet prevent this manipulation?

EDIT: The ID-hashes are used to preview and reference the item throughout the site, I have no fear of people guessing but rather them browsing the site in a seperate tab to get the other IDs and just keep downloading different items.

5条回答
聊天终结者
2楼-- · 2020-05-28 11:48

My original answer:

You will need to store (be in database or session variable) what items the user can access, for each you will generate a unique random token. That token will be used to identify the purchased item. Pass the token to the page where they will be able to download (either in a session variable, a POST argument or, as last option in the url, ie GET). In the page when you need to download you will query the database/session variable using the session information to identify the customer and the passed token (however did you pass it) and with that retrieve what file to download.

If you need to keep a list of purchased items for re-download, you can do so too, but remember to create the tokens again when the user requests the download. You can also add an expiration date if you feel like it.


Now I've mentioned a couple alternatives, then again by the nature of the cited answers I guess you will need more detail in how to do that.

May be ernie is right, and I should not assume you have a session. May be I should show you how to do a session.

So I'll take one of the option to implementation, the simplest option.


<?php
    //Oh, I'm in a PHP page...
    //check if there is not a session
    if (session_id() != '')
    {
        //Ok, there is no session, let's create one
        session_start();
    }
    //Now we are sure there is a session
    //Let's store in the session the id of the file I want to allow to download
    $_SESSION['download'] = GetFileId();
    //GetFileId will do some mambo jambo expecto patronum to return an id
    //The id will be 38a205ec300a3874c867b9db25f47c61 or something
?>

Now in the download page....

<?php
    //Oh, I'm in another PHP page...
    //check if there is not a session
    if (session_id() != '')
    {
        //no session? screw you, no download for you
        header('Location: sorry.php');
    }
    else
    {
        //Now we are sure there is a session
        //Let's get from the session the id of the file I want to allow to download
        $id = $_SESSION['download'];
        //Now get the url to redirect to allow the download
        $url = GetUrl($id);
        //GetUrl will do some mambo jambo expecto patronum to return an url
        //Ok, now we are going to return that file...
        //So put the correct MIME type
        header('content-type: image/gif');  //if it is a gif...
        //Load the file
        $contents = file_get_contents($url); 
        echo $contents;
        //That's the only output
        exit();
     }
?>

Please observe that I do allow access to the file only from PHP, so I can verify first if the user has access. You should not allow the user to just put the url (even he cannot guess it) and access the file. So if you are running your server, you want to put those files outside of the server web folder, or if you are using a hosting protected them with .htaccess (or another mechanism your hosting provides).


Comenting on this solution:

It is simple, easy to implement. Yet it has some drawbacks:

  • If the session is terminated before the download, the user lost his money*.
  • There is no clear way implement a re-download.
  • It is still vulnerable to session hijacking (far fetch'd, I know, but better be safe).

*: Say the connection was lost, and the session expired in the client. Oh, no, we don't need no happy customers.

So, you really, really, need to back this up with a database and create random tokens, preferibly with an expiration date.

查看更多
欢心
3楼-- · 2020-05-28 11:53

While these other answers assume you have a session, with a username, it sounds as if you've just created a hash to a file to hide the filename. In other words, you might as well have a monkey hammer away on a keyboard and have that be your file name for each file you have.

It then sounds like you're concerned that a user will come by, pound on the keyboard, and somehow end up with an exact match to what your monkeys did. This is not likely.

What is more likely is that someone will copy and paste your link and share it. It sounds as if you need to add at least one more layer to your auth scheme (e.g. hash off of both a username and the item ID), or have links expire once used.

From your comment, it also sounds as if you're using the hash to identify the item on another part of your website, which means that users can determine the hash of an item by looking at the source on another part of your website. At this point, your security through obscurity is much like my using code words (e.g. "fish means John, cat means Lisa") while gossiping with someone and saying "the fish and the cat were making out", all while having a sheet of paper on the side with the mappings written down that anyone can see. It sounds like you're just trying to hide the file names so people can't guess them. This is known as security through obscurity, and doesn't really buy you much.

Most of the other answers are assuming that an item exists, and it will have multiple, different hashes pointing to it (e.g. file1, will have hash1, hash2, and hash3 all being valid links to it). In this case, they've created multiple hashes and each hash is unique to a certain user + item. I think they're also assuming that everyone on the website you would refer to the download item as item1 not hash1, which it sounds like you're doing?

You might be better off just creating a dictionary somewhere in your code that has mappings between the name of the item, and the hash, and then only on the download page, substitute the hash for the name. Again, this is only security through obscurity, the link could be shared, but people wouldn't be able to get the hash just be browsing your site.

查看更多
聊天终结者
4楼-- · 2020-05-28 11:53

You need to keep track of which items a user has purchased in a database. download.php can then check whether the user has purchased the item they're trying to download.

查看更多
在下西门庆
5楼-- · 2020-05-28 11:55

When they make the payment, store the ID of the download available to them, and a random hash - both in the payment table. Use that hash to then get the ID. The hash should then never relate to a specific product, but instead to a payment.

查看更多
唯我独甜
6楼-- · 2020-05-28 12:02

One of the simplest things you could implement is a logging system to track the user's purchases. You can cross reference the requested ID with IDs stored related to the user's purchases. If they are not related, don't serve the file.

查看更多
登录 后发表回答