PayPal IPN Security Review

Coding Critique is the place to post source code for peer review by other members of DevNetwork. Any kind of code can be posted. Code posted does not have to be limited to PHP. All members are invited to contribute constructive criticism with the goal of improving the code. Posted code should include some background information about it and what areas you specifically would like help with.

Popular code excerpts may be moved to "Code Snippets" by the moderators.

Moderator: General Moderators

Post Reply
atyler
Forum Newbie
Posts: 17
Joined: Tue May 26, 2009 10:28 pm

PayPal IPN Security Review

Post by atyler »

Hey All -

I'm going to be implementing some code on a website to help automate a subscription service. For this, I'll be using PayPal's IPN.

I have the following code to check all parameters, and I just want to make sure that, above all, the code is reasonable secure. For example, I'm not sure if it is wise to put the database credentials in this file, or somewhere else, and what should be done to properly secure this file from prying eyes if it is ok to leave the DB credentials in it.

Keep in mind I'm not a developer, so I realize that there are probably better ways to run some of this script, but this is what I've hacked together.

I appreciate all feedback, though my primary concern is security.

Thanks!
AT

Code: Select all

<?php
// read the post from PayPal system and add 'cmd'
$req = 'cmd=_notify-validate';
 
foreach ($_POST as $key => $value) {
$value = urlencode(stripslashes($value));
$req .= "&$key=$value";
}
 
// post back to PayPal system to validate
$header .= "POST /cgi-bin/webscr HTTP/1.0\r\n";
$header .= "Content-Type: application/x-www-form-urlencoded\r\n";
$header .= "Content-Length: " . strlen($req) . "\r\n\r\n";
$fp = fsockopen ('ssl://www.paypal.com', 443, $errno, $errstr, 30);
 
// assign posted variables to local variables
$item_name = $_POST['item_name'];
$item_number = $_POST['item_number'];
$payment_status = $_POST['payment_status'];
$payment_amount = $_POST['mc_gross'];
$payment_currency = $_POST['mc_currency'];
$txn_id = $_POST['txn_id'];
$receiver_email = $_POST['receiver_email'];
$payer_email = $_POST['payer_email'];
 
if (!$fp) { //http error
$message = "Error processing order for $payer_email";
mail('my@Email.com', 'ERROR! PayPal IPN', $message);
} else {
fputs ($fp, $header . $req);
while (!feof($fp)) {
$res = fgets ($fp, 1024);
 
$mysql = mysql_connect("databaseHost", "user", "password");
if (mysql_error()){
    mail('my@Email.com', 'ERROR! PayPal IPN', 'Error connecting to MYSQL thus, '.$message);
}
mysql_select_db("database");
if (mysql_error()){
    mail('my@Email.com', 'ERROR! PayPal IPN', 'Error selecting DB thus, '.$message);
}
$sql = "SELECT txnid FROM subscribers WHERE txnid = $txn_id";
$res = mysql_query($sql);
if (mysql_error()){
    mail('my@Email.com', 'ERROR! PayPal IPN', 'Error querying DB thus, '.$message);
}
$txnArray = mysql_fetch_array($res);
$txnOnFile = $txnArray[0];
$timestamp = date("F j, Y, g:i a");
 
if (strcmp ($res, "VERIFIED") == 0) {
    if ($payment_status == "Completed"){
       $statusCheck = 1;
    } else {
        $statusCheck = 0;
    }
    if ($txnOnFile == ""){
        $txnCheck = 1;
    } else {
        $txnCheck = 0;
    }
    if ($receiver_email == "myPaypalAccount@Email.com"){
        $emailCheck = 1;
    } else {
        $emailCheck = 0;
    }
    if ($payment_amount == 49){
        $paymentCheck = 1;
    } else {
        $paymentCheck = 0;
    }
if ($statusCheck == 1 && $txnCheck == 1 && $emailCheck == 1 && $paymentCheck == 1){
    $sql = "INSERT INTO subscribers VALUES ('', '$timestamp', '$txn_id', '$payer_email', '$payment_status', '$payment_amount')";
    $res = mysql_query($sql);
    if (mysql_error()){
    $message2 = "Error writing to subscribers table with:\nTime $timestamp\nTxnID $txn_id\nSubscriber $payer_email\nPayment Status $payment_status\n Amount $payment_amount";
    mail('my@Email.com', 'ERROR! PayPal IPN', $message2);
    }
    $message3 = "Time $timestamp\nTxnID $txn_id\nSubscriber $payer_email\nPayment Status $payment_status\n Amount $payment_amount";
    // fire off welcome email
    mail('my@Email.com', 'Successful Payment Confirmation', $message3);
} else {
    $sql = "INSERT INTO fraud VALUES ('', '$timestamp', '$txn_id', '$payer_email', '$payment_status', '$payment_amount')";
    $res = mysql_query($sql);
    if (mysql_error()){
    $message4 = "Error writing to fraud table with:\nTime $timestamp\nTxnID $txn_id\nSubscriber $payer_email\nPayment Status $payment_status\n Amount $payment_amount";
    mail('my@Email.com', 'ERROR! PayPal IPN', $message4);
    }
    $message5 = "Time $timestamp\nTxnID $txn_id\nSubscriber $payer_email\nPayment Status $payment_status\n Amount $payment_amount\n\nStatus Check $statusCheck\nTxnCheck $txnCheck\nEmail Check $emailCheck\nPayment Check $paymentCheck";
    mail('my@Email.com', 'Fraud Attempt Recorded!', $message5);
    }
}
else if (strcmp ($res, "INVALID") == 0) {
    $sql = "INSERT INTO spoofed VALUES ('', '$timestamp', '$txn_id', '$payer_email', '$payment_status', '$payment_amount')";
    $res = mysql_query($sql);
    if (mysql_error()){
    $message6 = "Error writing to spoofed table with:\nTime $timestamp\nTxnID $txn_id\nSubscriber $payer_email\nPayment Status $payment_status\n Amount $payment_amount";
    mail('my@Email.com', 'ERROR! PayPal IPN', $message6);
    }
}
mysql_close($mysql);
}
fclose ($fp);
}
?>
User avatar
califdon
Jack of Zircons
Posts: 4484
Joined: Thu Nov 09, 2006 8:30 pm
Location: California, USA

Re: PayPal IPN Security Review

Post by califdon »

I'm not a security expert, but I believe that your IPN script is no different than any other PHP script in its vulnerability to exposure. I'd say your greatest risk is from insiders who already have access to your hosting account or from someone who obtains your account credentials. Other than those risks, I think your database credentials in that script are as secure as they are in any other PHP script. That's not to say they are 100% safe, but if you have other scripts on your site that contain the same data, this one does not increase your exposure. If your site is already secured like Fort Knox, then this script should be treated in the same manner.
User avatar
Eran
DevNet Master
Posts: 3549
Joined: Fri Jan 18, 2008 12:36 am
Location: Israel, ME

Re: PayPal IPN Security Review

Post by Eran »

This has less to do with security and more with proper procedure - you don't validate or filter any of the user input. You send it to PayPal as is, and while I'm sure they have enough protections, you would do well to reduce erroneous / malicious input from going out. Validate the input and show appropriate error messages in case it is invalid.
User avatar
califdon
Jack of Zircons
Posts: 4484
Joined: Thu Nov 09, 2006 8:30 pm
Location: California, USA

Re: PayPal IPN Security Review

Post by califdon »

The part about error messages is certainly good advice, but PayPal's IPN process is a tightly constrained protocol that doesn't permit ANY changes to the received data when it is sent back for validation, other than the addition of the "cmd" line. That's how they determine that you received unadulterated data, since it matches what you return to the same transaction already in its database, then PayPal sends the confirmation. So all you do is repeat back to PayPal what you received; if it matches, you'll get a confirmation, if it doesn't, you won't.
atyler
Forum Newbie
Posts: 17
Joined: Tue May 26, 2009 10:28 pm

Re: PayPal IPN Security Review

Post by atyler »

Thanks for the great feedback, all.

That is also how I understood the process so thanks for the confirmation, califdon.

I appreciate all of your input.

Cheers.
8)
Post Reply