5

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.

Community
  • 1
  • 1
JeremyB
  • 83
  • 1
  • 7
  • ... how would they know about 7c8ddc86c0e4c14517b9439c599f9957? – ceejayoz Oct 04 '12 at 00:16
  • 3
    Isn't it obvious ceejay? That was the first hash I thought of – AlienWebguy Oct 04 '12 at 00:18
  • What are your hashes corresponding to? e.g. are they to a specific file, or are they built off of user, file, etc? In any case, it sounds as if you're afraid of someone guessing a random hash that will be valid . . . unless you want to timebomb the hashes and cause them to expire, this sounds like something you'll have to deal with? – ernie Oct 04 '12 at 00:19
  • The item ID is a unique reference to the item throughout the site - while it shouldn't be displayed prominiently, it will be passed to the client from time to time. For example, when displaying the "Buy Now" page, the ID is in the header (premium.php/[itemID]). – JeremyB Oct 04 '12 at 00:20
  • I guess he's more paranoid of people sharing the hashed ID's after purchase – BenOfTheNorth Oct 04 '12 at 00:21
  • If that's the case, it seems you should be more worried about people sharing the download link, than about guessing random hashes . . . – ernie Oct 04 '12 at 00:21
  • Haha, I didn't think anyone would care to share the hash - I was thinking you could open 2 tabs, keep one on the download page and just keep browsing the site to get different IDs in the second tab, and reediting in Firebug – JeremyB Oct 04 '12 at 00:23
  • Seriously, you shouldn't worry about people guessing hashes. If you use one of the answers below, you'll never have any problems with sharing them either. – BenOfTheNorth Oct 04 '12 at 00:25
  • 1
    Um, right click -> copy link, paste, and then anyone can use the link . . . it sounds as if you need to add at least one more layer to your auth scheme (username?), or have links expire once used. – ernie Oct 04 '12 at 00:25

5 Answers5

4

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.

ernie
  • 6,356
  • 23
  • 28
  • I'm really not too worried with people guessing the hashes. Moreso, I'm worried with people gathering the hashes of other items, and simply copy->paste over the original item an indefinite number of times to keep downloading different items. I'm looking for help in getting what layer of security I'm missing. – JeremyB Oct 04 '12 at 00:39
  • @JeremyBuller Right, so the purpose of using the hashes would be that everywhere else on the website, you refer to the item as `item1`. The only place you should be using the hash would be on the download link. – ernie Oct 04 '12 at 00:40
  • Ahh, I see. Theraot and Ben Griffins said it earlier, and I think that may be it. The hash you see is the actual item identifier I use; so I should use a random hash (and a timestamp + x for an expiration date) to refer to that purchase only in my reciepts table. – JeremyB Oct 04 '12 at 00:50
3

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.

BenOfTheNorth
  • 2,904
  • 1
  • 20
  • 46
  • You're absolutely right - that's the security layer I was missing. This should allow people to still buy it - without signing up, and I guess the only exploitation would be people sharing the link during the hash's lifespan (thats no biggie, I don't believe people will be clamoring to download-for-free from me). Thank you very much for the quick reply. – JeremyB Oct 04 '12 at 01:00
3

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.

Theraot
  • 31,890
  • 5
  • 57
  • 86
  • Actually your first answer was pretty much the solution I needed - in the reciepts table, generate a new random string to serve as the id for that purchase only, instead of using the item's id. I only picked Ben Griffin's answer because he answered first, but you're both right. – JeremyB Oct 04 '12 at 01:05
1

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.

Kai Qing
  • 18,793
  • 5
  • 39
  • 57
  • Thank you very much for replying, unfortunately I know that a lot of people would really hate "signing up" to more and more sites; therefore, I would really like to keep this available to "guests" with valid PayPal accounts. – JeremyB Oct 04 '12 at 00:35
  • Well, why not just make the id read once, then delete? Like, say they want music.mp3. You store a db relation to music.mp3 with a hash of random4356string or whatever, then when the download happens once, remove that relation from the DB or set it to a used state. If the user needs it restored, maybe they need to contact you and provide the original hash – Kai Qing Oct 04 '12 at 00:38
1

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.

Barmar
  • 741,623
  • 53
  • 500
  • 612