views:

583

answers:

9

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.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?

+2  A: 

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.

David Lively
@David Lively - thanks, I added new edit
JM4
PCI compliance is a requirement of your agreement with credit card companies (Visa, Mastercard) - not really the country or region you live in. Unless you're willing to spend a lot of time and effort on compliance, you should not store customer credit card numbers, since doing so automatically moves you into a more stringent set of compliance requirements.
Summer
@Summer - we are already forced to become PCI compliant as I said in this first line. I am well aware of the policies and what it takes. I am only trying to find out, technically, best practices for passing information from page to page.
JM4
It'd be great if whoever voted "-1" would post an explanation. Considering that my suggestion is identical to others (store it somewhere other than the session, and don't pass it back and forth within the browser), I fail to see why this answer was singled out.
David Lively
@David - i assume it was Summer given the comment abotu PCI Compliance for country/regions. Who knows. He/She chose to ignore the very first line in my question anyway so I'll bump you for good measure.
JM4
@JM4 Thanks very much!
David Lively
Explanation: this answer seems to suggest that you SHOULD store CC numbers in the database, that a two-way encryption process is adequate data security for credit card numbers, and that PCI compliance depends somehow on your country/region. I don't believe any of those three statements are true. I truly didn't mean to offend, just want to make sure the highest-voted answers are the best ones and at the time of the downvote, yours was rated quite highly.
Summer
@Summer Thanks for the clarification. If this is the case, however, how do sites keep card information associated with your account for your next purchase? My bank, New Egg, Amazon, etc all do this.
David Lively
@EVERYBODY - I would be very interested to know that answer as well. My initial guess is that they have gone through rigorous PCI-DSS Level 1 Compliance reviews and honestly have a staff of people dedicated to that work only.
JM4
+3  A: 

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 :)

Web Logic
@web logic - thanks for the info, i added new edit. I am also programming in PHP
JM4
How is the DB more secure than storing it (encrypted) in a session? If they can hijack your session, then your app will be tricked into reading it from the DB just as easily.
Mark
@Mark: I have agreed with him in not using sessions :) I have also asked him to store it encrypted.
Web Logic
@Web: But he already *is* using sessions to authenticate the user. I'm saying if a hacker steals your session, you're screwed either way (whether you store it in the DB, or in the session).
Mark
@Mark: in his question, his text reads *WITHOUT using SESSION variables if at all possible.*
Web Logic
@Mark - I am not authenticating any user at this point. I do not make a single DB call until the end of the enrollment process and by this point have already unset() the CCNum. My goal is to try and eliminate CCNum from SESSION altogether and ONLY use the POST variable from page 1 to 2
JM4
Oops... I thought you must be using sessions to maintain the state across the pages... but I guess you're right. You could POST it along and use the DB instead then.
Mark
@Mark - I will store on page 7 and need to call that same information again on page 9/10 but just want to avoid SESSIONS with all the hijacking possibilities.
JM4
+1  A: 

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.

Ólafur Waage
unsure how to go about this entirely - but appreciate info
JM4
This doesn't make much sense. It sounds like you're advocating storing the CC number in the DB in clear text, then you've sprinkled some hashes around that don't really serve a purpose.
Mark
+8  A: 

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.

Rex M
@ Rex - added new edit
JM4
new comments added
JM4
@JM4 added to my answer
Rex M
That's not how you prevent session hijacking. Usually it'll be provided by whatever framework you're using (e.g. PHP) so there's not much you can do about it anyway.
tc.
@tc how do you prevent session hijacking?
Rex M
You securely generate a session identifier. Assuming that the web framework you're using is secure, your sessions are secure. If the web framework you're using is not secure, then upgrade to a version that is. Do not write your own session-generation/handling code, you **will** get it wrong the first time.
tc.
@tc how is a session identifier "secure"? It is a string in a cookie, regardless of how securely that string was generated. If I take the value out of your cookie and put it in mine, and the only piece of information the server uses to identify you is the value of the cookie, I have your session. There has to be a second piece of information to validate the cookie value that is also independent of the cookie value.
Rex M
@tc - generating "secure" session_ids is just as secure as storing the information in SESSION to begin with. If the ID could not be jacked then the information within the session could not either. Perhaps you know incredible methods of doing this that none of the rest of us do. Judging by your answers on SO, I can see you know what you are talking about for the most part but I dont know if we are on the same page. For reference, I am using basic PHP 5.3.2 for this code (please dont tell me I need to switch to Zend and an MVC framework)
JM4
I did not choose to go this route but felt it was the best answer. I am working directly with our vendor to store the information in a PCI secured vault they actually maintain and working with token system. Thank you so much for your help. Perhaps I will be able to sit down one day and manage this all on our end.
JM4
@JM4. So you went with my answer? Nice :) I may be biased, but I think anyone considering storing credit card numbers would think twice if they went over the PCI requirements.
PaulG
+1  A: 

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.

Greg Adamski
@Greg - added new edit
JM4
+4  A: 

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...).

tc.
@tc - I have been tinkering with that idea for awhile now. Being an online customer - I feel it gives the consumer peace of mind then they are entering their payment information to be able to review it all before hitting the final 'submit' button. It is also fairly common practice on every single site out there
JM4
@tc - your edit doesn't really address the goal (store CC and CVV outside of session). Assuming it is the best method though - Are you assuming I generate the key (no clue how to do that), encrypt data, store data and key both into session, then when needed use the same information to decrypt the card? How is this any safer than what I am alrady doing given the key is in SESSION now. If the SESSION is hijacked, now the key and card number are both exposed.
JM4
@JM4: How is the card number exposed? It's no where on the server now, it's sent to the client (encrypted).
Mark
@Mark - Perhaps I am misundestanding but what Client are we referring to? The processor? The consumer?
JM4
@JM4: By "client" I mean the person viewing the site. Their computer.
Mark
@Mark - so i'd be sending 'encrypted' CC numbers as a cookie to the client machine? Seems like a bad idea
JM4
@JM4 no, the key to decrypt the CC info is on the client. However, this is not really an improvement over storing in session, since either way the decryption info is in-memory on the server for some short period of time. If the server is compromised to the point that the attacker can "listen" for session data coming through, they can also get POST data.
Rex M
@JM4: I thought TC meant to send the CC num back, but yes, it does make more sense to send the key back to the client and then wipe it from the server ASAP. They shouldn't be able to read any of the transmissions if it's running over HTTPS...
Mark
@Rex M - that is not what TC states above - "Generate a random 128-bit key. Save this in the session." The key is staying server-side in this example and the CC is being sent back. I truthfully don't like any of the options really just given the horror stories some people post about info being jacked. No 'review' page is ugly but likely the best method (although like you said, POST variables can be stolen anyway so there is no difference here).
JM4
@JM4 you're right, I misread it.
Rex M
@Rex M - just added new edit, would LOVE your feedback
JM4
You should not be writing crypto if you do not know how to generate a key.
tc.
@Rex M: The plaintext-equivalent data is **ALWAYS** going to be on the server at some point. There's absolutely nothing you can do to change that. My scheme sends the client (browser/user) something it (he/she) already knows; what's the attack? It's encrypted largely so that someone can't just "view source" to get the number.
tc.
@tc that's my point - it's not an improvement, it's just different.
Rex M
@tc - trying to learn best practices so advice is much better than telling me not to do something.
JM4
A: 

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.

alexanderpas
@alexanderpas - there is no way to allow the CC company to decrypt the information as they do not provide unified encryption methods.
JM4
+2  A: 

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.

PaulG
@PaulG - very good answer. I checked with our merchant yesterday morning and am still waiting a response. From what I can tell, they are trying to charge us additional per month and per transaction which is absurd in my opinion but MAY be worth it.
JM4
@PaulG - looking over again, we actually can't show any part of the credit card number or show a confirmation page at all. Doing so would require storing the credit card number in a session during the review process (hence this question) and then posting those variables to the merchant API. We use direct POST variables when sending the information off.
JM4
@JM4. I possibly havent explained clearly, but I was suggesting POSTing directly to the gateway as soon as the details are collected, and storing (PCI compliant) the last 4 digits only. The POST to the gateway is (at this point) solely storing the details onto the payment gateway. The confirmation page can then reshow the last four digits and (after confirmed) make another POST to the gateway to perform auth/settlement using the token id (not payment details) returned from the gateway. There are other slightly different approaches you could take also if you still wanted a confirmation page.
PaulG
Its a fairly common approach to the problem, and is hugely easier than the 'accepted' answer, which will cause PCI headaches
PaulG
@PaulG - going the route you mention requires significant additional cost to merchants through almost every gateway. In our particular case, there is a $50 monthly charge for the 'customer vault' then an additional $0.25 per transaction which does not justify the means. Thank you for your help though.
JM4
+1  A: 

Hi -

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?

Summer
@Summer - we have discussed just this below but I think it is a good point. After doing some more research, even some of the big companies like Go Daddy don't offer 'review' pages. All pages during the enrollment process are 2048 bit SSL protected by the way (page 1 - page 10)
JM4
This method is the easiest, I think, but prevents using a Post/Redirect/Get form model.
swt83