Page 1 of 1

PayPal IPN Security Review

Posted: Fri Aug 28, 2009 12:05 pm
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);
}
?>

Re: PayPal IPN Security Review

Posted: Fri Aug 28, 2009 1:04 pm
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.

Re: PayPal IPN Security Review

Posted: Fri Aug 28, 2009 1:06 pm
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.

Re: PayPal IPN Security Review

Posted: Fri Aug 28, 2009 1:58 pm
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.

Re: PayPal IPN Security Review

Posted: Fri Aug 28, 2009 11:48 pm
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)