开发者

Storing Credit Card Numbers in SESSION - ways around it?

I am well aware of PCI Compliance so don't need an earful about storing CC numbers (and especially CVV nums) within our company database during checkout process.

However, I want to be safe as possible when handling sensitive consumer information and am curious how to get around passing CC numbers from page to page WITHOUT using SESSION variables if at all possible.

My site is built in this way:

  1. Step 1) collect Credit Card information from customer - when customer hits submit, the information is first run through JS validation, then run through PHP validation, if all passes he moves to step 2.
  2. Step 2) Information is displayed on a review page for customer to make sure the details of their upcoming transaction are shown. Only the first 6 and last 4 of the CC are shown on this page but card type, and exp date are shwon fully. If he clicks proceed,
  3. Step 3) The information is sent to another php page which runs one last validation, sends information through secure payment gateway, and string is returned with details.
  4. Step 4) If all is good and well, the consumer information (personal, not CC) is stored in DB and redirected to a completion page. If anything is bad, he is informed and told to revisit the CC processing page to try again (max of 3 times).

Any suggestions?

EDIT

I have received a lot of really good response on this question - majority seem to agree on the following:

  1. taking POST variables after validation is run
  2. encrypting ccnum and cvv (not sure you are allowed to store cvv in DB at all though)
  3. Storing in temporary DB
  4. Access DB immediately after 'review' page is OK'd
  5. decrypt details from DB
  6. send information to processor
  7. receive response
  8. terminate DB

I think this makes sense overall. Does anybody have good method for the encryption/decryption along with best way to create temp DB info that is automatically deleted on later call?

I am programming in PHP and MySQL DB

EDIT #2

I came across Packet General which seems like an ideal solution but REALLY don't want to pay for another software license to accomplish this goal. http://www.packetgeneral.com/pcigeneralformysql开发者_如何学C.html

EDIT #3 - Sample Code

I have now posted some example code I put together trying to make sense of the encryption/decryption/key and storage mentioned in this post. Hopefully, the already helpful contributors can validate and others are able to use similar functionality. For the sake of length I will not go into the validation methods used for the actual CC num itself.

Form Input

<form action="<?php $_SERVER['PHP_SELF']; ?>" method="POST">
<input type="text" name="CC" />
<input type="text" name="CVV" />
<input type="text" name="CardType" />
<input type="text" name="NameOnCard" />
<input type="submit" name="submit" value="submit" />
</form>

PHP Encrypt and Storing Data

<?php

$ivs = mcrypt_get_iv_size(MCRYPT_DES,MCRYPT_MODE_CBC);
$iv = mcrypt_create_iv($ivs,MCRYPT_RAND);
$key = "1234"; //not sure what best way to generate this is!
$_SESSION['key'] = $key;

$ccnum = $_POST['CC'];
$cvv = $_POST['CVV'];
$cctype = $_POST['CardType'];
$ccname = $_POST['NameOnCard'];

$enc_cc = mcrypt_encrypt(MCRYPT_DES, $key, $ccnum, MCRYPT_MODE_CBC, $iv);
$enc_cvv = mcrypt_encrypt(MCRYPT_DES, $key, $cvv, MCRYPT_MODE_CBC, $iv);
$enc_cctype = mcrypt_encrypt(MCRYPT_DES, $key, $cctype, MCRYPT_MODE_CBC, $iv);
$enc_ccname = mcrypt_encrypt(MCRYPT_DES, $key, $ccname, MCRYPT_MODE_CBC, $iv);


//if we want to change BIN info to HEXIDECIMAL
// bin2hex($enc_cc)

$conn = mysql_connect("localhost", "username", "password");
mysql_select_db("DBName",$conn);

$enc_cc = mysql_real_escape_string($enc_cc);
$enc_cvv = mysql_real_escape_string($enc_cvv);
$enc_cctype = mysql_real_escape_string($enc_cctype); 
$enc_ccname = mysql_real_escape_string($enc_ccname);

$sql = "INSERT INTO tablename VALUES ('$enc_cc', '$enc_cvv', '$enc_cctype', '$enc_ccname');

$result = mysql_query($sql, $conn) or die(mysql_error());
mysql_close($conn);

Header ("Location: review_page.php");

?>

PHP decrypting data and sending off to gateway

    $conn = mysql_connect("localhost", "username", "password");
    mysql_select_db("DBName",$conn);

$result = mysql_query("SELECT * FROM tablename");

echo mcrypt_decrypt (MCRYPT_DES, $_SESSION['key'], $enc_ccnum, MCRYPT_MODE_CBC, $iv);
echo mcrypt_decrypt (MCRYPT_DES, $_SESSION['key'], $enc_cvv, MCRYPT_MODE_CBC, $iv);
echo mcrypt_decrypt (MCRYPT_DES, $_SESSION['key'], $enc_cctype, MCRYPT_MODE_CBC, $iv);
echo mcrypt_decrypt (MCRYPT_DES, $_SESSION['key'], $enc_ccname, MCRYPT_MODE_CBC, $iv);

mysql_close($con);
?>

then proceed to take the data just sent in the string and use in Gateway submission. Seem right?


Store the card details to any persistence medium (database, whatever), but encrypt the card number with a unique and random key that you store in the session. That way if the session is lost, the key is too - which gives you enough time to clean out expired/abandoned data.

Also make sure your sessions are protected from hijacking. There are hardware solutions to this, but a simple in-code way is to tie the session ID to a hash of the first octet of the IP plus the user agent. Not foolproof but it helps.

Edit: The key bits to minimizing your risk is to make sure you get rid of that info as soon as possible. Right after the transaction goes through, delete the record from the database. You also need a rolling job (say every 5 minutes) that deletes any records older than your session timeout (usually 20 minutes). Also, if you are using a database for this very temporary data, make sure it is not on an automated backup system.

Again, this solution is not foolproof and I am not even 100% sure it is compliant with CC security requirements. However, it should require an attacker have total runtime control of your environment to actively decrypt customer CC info, and if a snapshot of your database is compromised (much more likely/common), only one CC can be brute-forced at a time, which is about the best you can hope for.


Consider modifying your checkout process to get rid of the necessity of storing credit card information.

Page 1: User enters non-credit-card order information, like shipping and billing address
Page 2: User verifies non-credit-card order information, enters credit card information, and clicks "Pay Now" (or "Revise Order" if they want to change things)
Step 3: Info is submitted via a $_POST request to an SSL page, which completes serverside checks, submits credit card data to processor, and directs the user to a success or error page based on the response.

This way you'll avoid a haze of technical problems and compliance problems. Storing credit card data in a database or cookie, even for a short period of time, even if encrypted, WILL mean that you're responsible for a higher level of PCI compliance. The only tradeoff is you won't be able to show a "review order" page with credit card details. And how big a tradeoff is that, given that your "review order" page can't even show the full credit card number?


I know you mentioned you're aware of PCI compliance, but using any of the methods already described (eg persisting the card number to disc anywhere) will fall foul of PCI and mean you have a nightmare of compliance headaches ahead of you. If you really insist on persisting the card number to disc, then you might as well get a PCI auditor in now to help you through the process and offer advice. Ultimately they will need to validate the method you've taken is appropriate.

As an example, a lot of the answers here talk about using encryption. Thats the easy bit. They haven't talked about key management which is significantly harder

I think therefore a better approach would be to submit the card details to the payment gateway as soon as they are collected. A good many of payment gateways will allow you to perform a 'store only' style transaction, which will perform basic validation of card details and store the card number to their (already PCI compliant) server, and return you a token id instead. This method means you DONT store the full card number/cvv2 anywhere on your servers, and PCI compliance becomes a huge amount easier.

Later in the checkout process you use the token id to submit an authorisation and settlement.

PCI allows you to store the first six/last four digits (and expiry date) of the cardnumber in plaintext, so you can safely capture those wherever you're comfortable with so that they can be redisplayed just prior to the final step.


Is there any reason you can't skip the confirmation step and just submit the transaction immediately?

I don't see why keeping it in a database is any more secure than keeping it in a session variable — server compromise will still give away the credit card number, but if you keep it in the session it's far less likely to be written to disk. You can encrypt it if you want, but the usefulness of this is dubious (it'll still be swapped to disk). Adding another machine to do encrypted storage doesn't help either, since the compromised machine can just ask the other one to do decrypting.

EDIT: Just thought of this:

  1. Generate a random 128-bit key. Save this in the session.
  2. Encrypt the data with the key. Send it to the client in an <input type="hidden">
  3. On confirmation, decrypt the data and submit the transaction.

An attacker needs to compromise both the client and the server to get the credit card number (such an attacker would probably have the number already anyway). An online server compromise will still get the credit card numbers of future transactions, but you can't really stop that.

EDIT: And I forgot the details. For all of these schemes (not just mine), you also need a MAC to prevent replay attacks (or Eve distracts Alice, modifies the shopping basket and billing address, and hit the "confirm" page...). In general, you want to have a MAC on all the transaction data you have (CC, CVV, transaction ID, transaction amount, billing address...).


You are right, using sessions is very insecure for storing sensitive data, there are ways to break into sessions with what is know as:

Session hijacking
Session fixation

The most secure way that comes to my mind is that store the info in database (for temporary time) and then read that value on the page where you need it. Once you are finished doing it all, you can delete it back.

Note that:

  • you must encrypt it before saving to database.
  • be careful if you are on shared hosting
  • make sure that you delete it back once done with it

You may find this reflectively useful as well :)


It appears that anyway you touch it, you'll need to provide for secure credit card number storage capabilities. If the server is compromised, at any time it will contain enough information to decrypt currently stored credit card numbers (i.e. keys and encrypted numbers). Potential solution is to use an internal server that acts as a "encryptor/decryptor" service and nothing else. This way compromising one machine does not expose credit card numbers.


There is another way, but it requires Ajax. No storing of credit card numbers AND a review page.

Page 1: Form to capture shipping, billing and credit card information. Ensure that the "body" of the page, including the form, is in a DIV with a unique ID to allow you reference it with JavaScript.

Page 2: A file on the server that will accept a GET/POST request with the form fields in it and return a properly formatted "review" page to your liking.

Checkout process:

  1. Validate form.
  2. Copy credit card related fields into global JavaScript variables.
  3. Loop through form fields and build a query/data string with the form fields (excluding credit card related fields)
  4. Do an Ajax request to the "review" page, passing the query string of form field/values with it. Render on server and return to calling Ajax function.
  5. Take rendered HTML review page returned from Ajax request and replace content in your "DIV" container with it (effectively replacing the form and other elements with the review HTML).
  6. Use JavaScript to copy the credit card data stored in global JS variables into the appropriate place on the review page. You may also copy the card data to hidden form fields to submit when the user "completes" the order from the "review" page.
  7. User submits order from review page to server, performing card validation with the processor's gateway and then either placing the order, or returning to error handling page, never having stored the card details.
  8. I would recommend that the "place order" function perform a full HTTP request (rather than Ajax) in order to reload the browser with a page that no longer has the card data stored in global JS variables.

It's a bit of a hack, but when done properly, it's 100% seamless to the user and allows you a single transmission of the card data with no need to assume risks with temp DB storing, etc.


This is what a database is for. I'm not sure about the legal ramifications here (which vary based on country and region), but one approach would be to encrypt the CC number and store it in the database as soon as you receive it from the user. You may want to store the last 4 digits in a separate field so that you can show it to the user when required. When you need to interact with the card processor on the server, retrieve and decrypt the card number from your database.


Here's how I plan on doing it - everything over https using ssl of course.

Step 1. User enters CC info and presses the next button. The CC info is immediately saved to the CC processor's DB, not your own DB or you'd break PCI Compliance. The CC is not actually charged this step. But you should recieve some unique ID from the processor identifying the CC info. (store the unique id in your db if you want)

Step 2. On the confirmation page, retrieve the CC info from the CC processor using the unique id they gave you. My processor will only let me retrieve the last 4 numbers of the CC anyway.

Step 3. After they confirm the purchase and press the purchase now button, charge the credit card using the unique id.

Step 4. Redirect to a thank you page containing the invoice/reciept which only has the last 4 digits of the CC (Of course you don't have to display the last 4 of the CC, but I think it's a nice thing to show).


You could store a hash of the card nr in session and the same hash and the actual number and the user's session id in a database. Then for each page you can check the hash and the session info to get the card nr.


At some point later on in the payment processing (last part of step 3), you'll need to encrypt the CC# (and CVC) to be able to send it to the payment processer (I assume)

Why not do that encryption right when you recieve the information, next to the obfuscation needed for the confirmation page. (this is the last part of step 1)

From now on, only work with this encrypted or obfuscated data, making the CC-company the only one who can actually decrypt the full data.


There is no need for sessions or the database to hold the information.

Every page is a form that posts the data. On each subsequent page the post variables from the previous page are added to hidden form fields so that the next form submission posts the data again. This way nothing is ever stored, but the information is carried from page to page. This also forces the user to complete the process from start to finish without attempting to skip steps.

As long as the form is submitted over HTTPS, the data is encrypted automatically and the security burden is on your SSL certificate provider.

Many popular commerce sites implement this. For example OSCommerce.


Use Tokenization. Its fully PCI DSS complient.

More can be fouhd here, https://www.pcisecuritystandards.org/documents/Tokenization_Guidelines_Info_Supplement.pdf

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜