Author Topic: Secure Web Download Token System  (Read 12171 times)

0 Members and 5 Guests are viewing this topic.

Offline Armin

  • Honorary Leader
  • x86
  • Hero Member
  • *****
  • Posts: 2480
    • View Profile
Secure Web Download Token System
« on: January 30, 2012, 05:59:06 pm »
What's up dudes?

I'm looking to set up a secure download token system on our website by tonight for distributing a DVD download. Basically, tonight we will send to all the pre-orders, a download token embedded in a hyperlink that will only allow to download the DVD once. If they need to download it again, then they press a button that generates and emails them a new download token. The goal is to try and minimize sharing of this content until the physical release date in March.

Does anyone know much about this subject, ie do they have packages for this, or will I need to program it from scratch, and if so, which language is most ideal for me to program this in, and (@iago) what steps would I have to take to prevent vulnerabilities?
« Last Edit: January 30, 2012, 06:01:35 pm by Armin »
Hitmen: art is gay

Offline rabbit

  • x86
  • Hero Member
  • *****
  • Posts: 8092
  • I speak for the entire clan (except Joe)
    • View Profile
Re: Secure Web Download Token System
« Reply #1 on: January 30, 2012, 06:14:32 pm »
Hash the (time . email) and use that as a token, then store it along with what email it was sent to.  When they download, mark it used.

Offline Armin

  • Honorary Leader
  • x86
  • Hero Member
  • *****
  • Posts: 2480
    • View Profile
Re: Secure Web Download Token System
« Reply #2 on: January 30, 2012, 06:43:51 pm »
so I imagine I would store it in an SQL database, using PHP to execute the "if"s and "write"s? and if I were to store this data in its own SQL database, I imagine there wouldn't be anything important they could exploit?
Hitmen: art is gay

Offline Armin

  • Honorary Leader
  • x86
  • Hero Member
  • *****
  • Posts: 2480
    • View Profile
Re: Secure Web Download Token System
« Reply #3 on: January 31, 2012, 03:20:10 am »
sweet... A little over 8 hours later without leaving the room, I've successfully accomplished my first project in PHP/MySQL.

Thanks for the direction, rabbit. :)

Though I'm still curious about how I should go about preventing anyone from taking advantage of SQL vulnerabilities and gathering the email addresses (most important), and also maliciously changing any table data. Any suggestions would be greatly appreciated. :D
« Last Edit: January 31, 2012, 05:03:05 am by Armin »
Hitmen: art is gay

Offline Sidoh

  • x86
  • Hero Member
  • *****
  • Posts: 17634
  • MHNATY ~~~~~
    • View Profile
    • sidoh
Re: Secure Web Download Token System
« Reply #4 on: January 31, 2012, 05:30:56 am »
The easiest thing you can do is probably to use something like MDB2 as a database wrapper and use prepared statements to prevent SQL injection.

Do that and you'll probably be fairly well off.

Offline iago

  • Leader
  • Administrator
  • Hero Member
  • *****
  • Posts: 17914
  • Fnord.
    • View Profile
    • SkullSecurity
Re: Secure Web Download Token System
« Reply #5 on: February 01, 2012, 11:26:46 am »
If you post the code somewhere - or send it to me - I can probably give you some advice.

I'd suggest that instead of storing the email address in the database, you store a hash of the email address - md5($address). When the user does the request, take the md5() of the address they submit and compare it to the md5 stored in the database to see if it matches. That way, you never have to store their actual email address.

Offline Sidoh

  • x86
  • Hero Member
  • *****
  • Posts: 17634
  • MHNATY ~~~~~
    • View Profile
    • sidoh
Re: Secure Web Download Token System
« Reply #6 on: February 01, 2012, 02:57:43 pm »
If you post the code somewhere - or send it to me - I can probably give you some advice.

I'd suggest that instead of storing the email address in the database, you store a hash of the email address - md5($address). When the user does the request, take the md5() of the address they submit and compare it to the md5 stored in the database to see if it matches. That way, you never have to store their actual email address.

But then hackers don't have anything juicy to get to when they infiltrate your mainframez. :(

Offline dark_drake

  • Mufasa was 10x the lion Simba was.
  • x86
  • Hero Member
  • *****
  • Posts: 2440
  • Dun dun dun
    • View Profile
Re: Secure Web Download Token System
« Reply #7 on: February 01, 2012, 07:17:37 pm »
But then hackers don't have anything juicy to get to when they infiltrate your mainframez. :(
Aha! So that was your plan all along. Never trust a Hawaiian!
errr... something like that...

Offline Armin

  • Honorary Leader
  • x86
  • Hero Member
  • *****
  • Posts: 2480
    • View Profile
Re: Secure Web Download Token System
« Reply #8 on: February 01, 2012, 08:12:59 pm »
The easiest thing you can do is probably to use something like MDB2 as a database wrapper and use prepared statements to prevent SQL injection.

Do that and you'll probably be fairly well off.
Thank you sir! I will look into this probably tomorrow.

If you post the code somewhere - or send it to me - I can probably give you some advice.

I'd suggest that instead of storing the email address in the database, you store a hash of the email address - md5($address). When the user does the request, take the md5() of the address they submit and compare it to the md5 stored in the database to see if it matches. That way, you never have to store their actual email address.

Great advice. I already launched the distribution at midnight last night, so I'll have to migrate the email data.

In the meantime, here is the code I used only once to initially add the users into the database (I deleted the script from the server after running it):

Code: [Select]
<?php
$con 
mysql_connect("server_removed","user_removed","password_removed"); //Establishes SQL connection
if (!$con) {
    die(
'Could not connect: ' mysql_error());                           //Ends script in case of connection error
}
$inpEmail "emails@are.listed here@with.spaces seperating@each.email";   //List of Email Input
$arrEmail=(explode(" ",$inpEmail));                                       //Explodes Email Input into an array
mysql_select_db("database_removed"$con);                                //selects the database
foreach ($arrEmail as $email) {                                           //For each email in the array do:
    
$gToken=hash('md5'$email time());                                 //Creates token as md5 hash of email . time
    
$sql="INSERT INTO TableRemoved (Email, Token, Used)
    VALUES ('
$email','$gToken','0')";                                     //Inserts 3 values into the SQL Table: Email, Token, number of uses
    
if (!mysql_query($sql,$con)) {
        die(
'Error: ' mysql_error());                                   //Ends script in case of connection error
    
}
    
$DownloadURL="http://www.link.com/removed?email="$email ."&token=" $gToken//Generates download URL
    
$subject="blah blah blah";
    
$message="blah blah blah" $DownloadURL . ;                                                        
    
$header="From:VAYDEN <contact@vaydenmusic.com>";
    
mail($email,$subject,$message,$header);                               //Sends URL to email address
    
echo "1 record added.<br />";                                         //Success!
}
mysql_close($con);                                                        //Closes SQL connection
?>

and here is the code I use for when the user tries to download the file:

Code: [Select]
<?php
$n
=0;                                                                   //Defines "email found" counter
$email=$_GET["email"];                                                  //GETs email from URL
$token=$_GET["token"];                                                  //GETS token from URL

$con mysql_connect("removed","removed","removed");                    //Establishes connection with SQL server
if (!$con) {
    die(
'Could not connect: ' mysql_error());                         //Ends script in case of connection error
}
mysql_select_db("removed"$con);                                       //Selects appropriate databse
$result mysql_query("SELECT * FROM removed
WHERE Email='
$email'");                                                 //Creates array for the email + all associated data
while($row mysql_fetch_array($result)) {
    ++
$n;                                                               //Increments "email found" counter -- I use this because there is no 'while {} else {}'
    
if ($row['Token']!=$token) {                                        //If the token does not match, do:
        
$gToken=hash('md5'$email time());                           //Generates new Token
        
mysql_query("UPDATE removed SET Token = '$gToken', Used = '0'
        WHERE Email = '
$email'");                                       //Adds new token to database, resets uses to '0'
        
$DownloadURL "removed"$email ."&token=" $gToken;          //Generates new download URL
        
$subject="blah blah blah";
        
$message $DownloadURL "blah blah blah";
        
$header="From:VAYDEN <contact@vaydenmusic.com>"
        
mail($email,$subject,$message,$header);                         //Sends new download URL to email address
        
die('blah blah blah');                                          //Ends script with "token expired" error message
    
}
    elseif (
$row['Used']>='3') {                                        //If the token has been used 3 or more times, do:
        
$gToken=hash('md5'$email time());                           //Generates new Token
        
mysql_query("UPDATE removed SET Token = '$gToken', Used = '0'
        WHERE Email = '
$email'");                                       //Adds new token to database, resets uses to '0'
        
$DownloadURL "removed"$email ."&token=" $gToken;          //Generates new download URL
        
$subject="blah blah blah";
        
$message $DownloadURL "blah blah blah";
        
$header="From:VAYDEN <contact@vaydenmusic.com>"
        
mail($email,$subject,$message,$header);                         //Sends new download URL to email address
        
die('blah blah blah');                                          //Ends script with "token expired" error message
    
}
    elseif (
$row['Token']==$token && $row['Used']!='3') {               //If token matches and has not been used more than 3 times, do:
        
$incToken=$row['Used'];
        ++
$incToken;                                                    //Increments token used variable
        
mysql_query("UPDATE removed SET Used = '$incToken'
        WHERE Email = '
$email'");                                       //Updates database with number of token uses
        
if ($fd fopen ($fullPath"r")) {                             //Runs this following download script:
            
$path $_SERVER['DOCUMENT_ROOT']."/hidden/";
            
$fullPath $path.$_GET['download_file'];
            
$fsize filesize($fullPath);
            
$path_parts pathinfo($fullPath);
            
$ext strtolower($path_parts["extension"]);
            switch (
$ext) {
                case 
"pdf":
                
header("Content-type: application/pdf");
                
header("Content-Disposition: attachment; filename=\"".$path_parts["basename"]."\"");
                break;
                case 
"mp4":
                
header("Content-type: video/mp4");
                
header("Content-Disposition: attachment; filename=\"".$path_parts["basename"]."\"");
                break;
                default;
                
header("Content-type: application/octet-stream");
                
header("Content-Disposition: attachment; filename=\"".$path_parts["basename"]."\"");
            }
            
header("Content-length: $fsize");
            
header("Cache-control: private");
            while(!
feof($fd)) {
                
$buffer fread($fd2048);
                echo 
$buffer;
            }
        }
        
fclose ($fd);
        exit;
    }
}
if (
$n==0) {                                                            //This is my "while {} else {}" solution for if the email is not found
    
die "blah blah blah";                                               //Ends script with "email not found" error message
}
?>

I plan to clean this up by having the emails be sent from a separate PHP script, among other things. Also, forgive me for being new to programming. :P
« Last Edit: February 01, 2012, 09:22:50 pm by Armin »
Hitmen: art is gay

Offline rabbit

  • x86
  • Hero Member
  • *****
  • Posts: 8092
  • I speak for the entire clan (except Joe)
    • View Profile

Offline Armin

  • Honorary Leader
  • x86
  • Hero Member
  • *****
  • Posts: 2480
    • View Profile
Re: Secure Web Download Token System
« Reply #10 on: February 01, 2012, 08:52:51 pm »
Sorry, is that better? Also, that article doesn't say anything about empty new lines, even though some examples included them. Is there a time and place for them?
Hitmen: art is gay

Offline Sidoh

  • x86
  • Hero Member
  • *****
  • Posts: 17634
  • MHNATY ~~~~~
    • View Profile
    • sidoh
Re: Secure Web Download Token System
« Reply #11 on: February 01, 2012, 09:40:44 pm »
http://en.wikipedia.org/wiki/Indent_style#K.26R_style pls

I've stopped caring about this kind of stuff. I used to be a total K&R nazi, but I've since worked for enough companies using alternative standards that I've grown accustomed to using whatever style people are comfortable with.

It's not like you can't just paste it into an IDE and press some keys to get it to look exactly how you want it to. :P

Offline Blaze

  • x86
  • Hero Member
  • *****
  • Posts: 7136
  • Canadian
    • View Profile
    • Maide
Re: Secure Web Download Token System
« Reply #12 on: February 02, 2012, 12:03:16 am »
Vulnerbilties:

You do not sanitize 'email' on your download page which leads to a SQL injection (http://ca2.php.net/manual/en/function.mysql-real-escape-string.php)

You do not sanitize your "$_GET['download_file']" request on the download page which leads to full web-server-user access to the filesystem.  A crafty attacker could make their file '../../../../../../../../../etc/passwd' and access any file that your web server has access to.  There are several ways to correct this, you could do a regex filter for just "A-Za-z0-9\." which would only allow alpha-num and periods, or you can filter out slashes.  There are several other methods, but I prefer the regex.


Errors:

You set "$fullPath" after you have used it first (logical error)

Your switch at the end uses a semicolon instead of a colon for "default"

You are missing a semicolon in your $header assignments. (I assume this is because you just edited these in quickly at the end)

Die is a function, not a language construct: you must surround paramaters with parenthesis. (The last die in your script should be die("blah blah blah"); instead of die "blah blah blah";)


Suggestions:

You do not sanitize your URLs (http://ca3.php.net/manual/en/function.urlencode.php)

Convert all user-enterable values as a single case when hashing.  ie, $gToken = hash('md5', $email . time()); would be $gToken = hash('md5', strtolower($email) . time());

Do not store your downloadable content in a 'hidden' web-accessible folder.  If possible, keep the content is a folder below your web accessible folder (ie, if your content was in /var/www/htdocs/, keep your downloadable content in /var/www/downloads/)

Change your 'Used Increment' statement to use the DB value rather than a fetched value.  This prevents users from accessing the page all at once quickly and the counter not incrementing properly. (Allows for more than 3 possible downloads).  The query would be "UPDATE removed SET Token = Token + 1, Used = '0' WHERE Email = '" . mysql_real_escape_string($email, $con) . "'"

Avoid printing mysql_errors in non-dev environments, and just leave the 'could not connect' message.

While not wrong, I suggest you include your link identifier ($con) in your query and sanitation function calls.  It's a good habit to be specific when you can.  :)


And that's just a quick scan.  It is really easy to make vulnerable code in PHP as you can tell.  :)
« Last Edit: February 02, 2012, 12:20:03 am by Blaze »
And like a fool I believed myself, and thought I was somebody else...

Offline Armin

  • Honorary Leader
  • x86
  • Hero Member
  • *****
  • Posts: 2480
    • View Profile
Re: Secure Web Download Token System
« Reply #13 on: February 02, 2012, 06:22:25 am »
hahaha shit man. I really opened up a Pandora's box with this ambitious idea. I spent equally as much time today failing to figure out why Internet Explorer times out halfway through the 713MB download (something to do with the download script, or server-side PHP settings [I wish my host would install mod_xsendfile]), as I did figuring out how to write this token script.

Due to this issue, and that of the scope of the vulnerabilities, I temporarily bypassed this system altogether with a direct link to the download. I will re-implement these features after I fix these issues.

When I first started this project on Monday, I took a small 5mg dose of Vyvanse to help me focus on this logical task. Later that night, shortly after I made the post about 'accomplishing' the project, loud sirens were coming from up and down my neighborhood streets, and some dude evading arrest walked into my apartment, out of my line of sight, and took refuge in my out-of-town roommate's bedroom.

My Vyvanse-strung-out, 3AM logic kicked in, and I asked if anyone was there. "And if anyone is there, it's okay. I'm going to my room," where I subsequently fell asleep. Albeit, a very skiddish sleep, but sleep nonetheless. [spoiler]of course, because of the day's tedious events + Vyvanse intake + lack of sleep, the entire experience was actually a paranoid hallucination.[/spoiler]

In other words, I need to play music for the next couple of days, or I may fall off into the deep end. :)

Many endless thanks for the suggestions, Blaze. I look forward to finishing this up over the next week so I can use it, along the knowledge I gained from this experience, on future releases/projects.
« Last Edit: February 02, 2012, 06:31:31 am by Armin »
Hitmen: art is gay

Offline iago

  • Leader
  • Administrator
  • Hero Member
  • *****
  • Posts: 17914
  • Fnord.
    • View Profile
    • SkullSecurity
Re: Secure Web Download Token System
« Reply #14 on: February 02, 2012, 10:36:10 am »
Blaze pointed out the things I noticed, plus a whooooole bunch more.

This is why PHP is dangerous, it's just too darn easy. :)