http://www.perlmonks.org?node_id=221743

BrentDax has asked for the wisdom of the Perl Monks concerning the following question:

Sorry for the question that borders on "Seekers of CGI Wisdom", but I don't have anywhere better to turn, and this is being implemented in Perl.

I'm writing a specialized shopping-cart-like application. Are there any non-obvious security-related tips (i.e. besides "use taint checks well" and "encrypt credit card numbers") or other tips on things that I should look out for?

(As an aside, I wrote three or four modules while creating this app, so you can expect to see a few modules hitting the CPAN soon.)

=cut
--Brent Dax
There is no sig.

Replies are listed 'Best First'.
Re: Any tips on writing a shopping cart?
by mt2k (Hermit) on Dec 22, 2002 at 19:27 UTC

    As already mentioned by someone who posted as Anonymous Monk, merlyn wrote an article, along with an example script (like always), on the best (and only) way cookies should be used (if they need to be used at all). Here's the article, which contains good advice and suggestions. Obey what you read here :)

    That being said, definitely don't trust anything you get from the client. The cookie should only indicate which browser (notice the word 'browser', not 'user', since the same user should be able to have multiple sessions open with your web applications) is accessing the script. All data should be contained within a database, including prices (do NOT put prices in hidden html fields), all contact info, and anything else you can possibly think of. Also, make sure you expire the session from the database after a reasonable amount of inactivity. What is 'reasonable' is up to you, but don't leave inactive sessions around for too long, unless you want these picked up by somebody other than the original session creater :)

    More from a view of making things work the way you expect, rather than security (but security can always hold a place in these things), is making sure the data you are being forced to receive from the client is the data you are expecting. What if a user tells your script they want 0.5 pairs of shoes? Your script had better insure that the only data it is accepting for quantities is a whole integer: no alpha characters, no decimal, etc etc. If you don't, your web app will charge your shopper the price of one shoe rather than the pair of them. And are you willing to ship out one shoe? :) Even worse, what do you do when someone tells your app "I want to purchase -10 pairs of shoes."? Will your app reject this value or will you be offering a refund for the amount of 10 pairs of shoes?

    Are there any non-obvious security-related tips (i.e. besides "use taint checks well" and "encrypt credit card numbers" ...
    Um... encrypting credit card numbers? No. Just don't do it. Don't even save the shopper's credit card number in your database, I don't care how safe you think you've encrypted it. If you can decrypt it to the true credit card number, then so can someone else who might find their way into your database. Will this require shoppers to re-enter their credit card number every time they buy something? Yes. Will it make them feel more secure? Perhaps. Will it make you feel better and more responsible when you find out your database has been hacked into by someone else? Definitely!

    -------------------------------------
    eval reverse@{[lreP
    =>q{ tsuJ\{qq},' rehtonA'
    ,q{\}rekcaH },' tnirp']}[1+1+
    1,1-1,1+1,1*1,(1+1)*(1+1)];
    -------------------------------------
    
      As already mentioned by someone who posted as Anonymous Monk, merlyn wrote an article, along with an example script (like always), on the best (and only) way cookies should be used (if they need to be used at all). Here's the article, which contains good advice and suggestions. Obey what you read here :)
      Good article. Thanks, Merlyn! In this case, I'm using hidden form fields instead of cookies, cause I hate cookies and they're hard to handle.
      That being said, definitely don't trust anything you get from the client...All data should be contained within a database, including prices (do NOT put prices in hidden html fields), all contact info, and anything else you can possibly think of.
      Prices are in a Perl data structure that's refreshed every time one of the constituent scripts is accessed. Everything else is in a session DBM.
      Also, make sure you expire the session from the database after a reasonable amount of inactivity. What is 'reasonable' is up to you, but don't leave inactive sessions around for too long, unless you want these picked up by somebody other than the original session creater :)
      Done.
      More from a view of making things work the way you expect, rather than security (but security can always hold a place in these things), is making sure the data you are being forced to receive from the client is the data you are expecting. What if a user tells your script they want 0.5 pairs of shoes?
      if($q->param("$name:copies") =~ /^\d+$/) {
      Are there any non-obvious security-related tips (i.e. besides "use taint checks well" and "encrypt credit card numbers" ...
      Um... encrypting credit card numbers? No. Just don't do it. Don't even save the shopper's credit card number in your database
      I was speaking about client-to-server. The (still encrypted) credit card number is e-mailed to the order-processing people; once they've processed it, all electronic data about the order is destroyed.

      =cut
      --Brent Dax
      There is no sig.

Re: Any tips on writing a shopping cart?
by Ovid (Cardinal) on Dec 22, 2002 at 21:47 UTC

    This tip may seem a bit silly, but I've seen people bitten by this, so I thought I would mention it. I'm sure you're aware of this, but just in case ...

    In a properly normalized database, data should usually be represented in one and one one place. As a result, when creating an order_item table, it seems to make sense to merely provide the product id in that table and lookup the price via a join. Unfortunately, this doesn't work. The price at the time of the sale (and perhaps other things) needs to be stored directly in the order_item table lest the customer be charged a different amount due to a price change. This is not denormalizing the database because price at the time of sale is similar, but not identical to, the price of a given item.

    Cheers,
    Ovid

    New address of my CGI Course.
    Silence is Evil (feel free to copy and distribute widely - note copyright text)

Re: CGI Security Tips
by cjf-II (Monk) on Dec 22, 2002 at 19:23 UTC
Re: Any tips on writing a shopping cart?
by Anonymous Monk on Dec 22, 2002 at 16:34 UTC
    Don't trust the client?

    I am almost embarrassed to mention it, but you would be amazed at how many sites keep prices in form data and blindly accept any garbage you choose to submit back for that...

      And for those who are patting themselves on the back for not including the prices in something you can get to by viewing as HTML, this reminded me that using cookies for it is no better...
      I've heard about such setups. Pathetic, isn't it? :^)

      No, prices are specified in a server-side Perl module, separate from the session data structure. I'm inexperienced with this sort of application, not stupid. :^)

      =cut
      --Brent Dax
      There is no sig.

Re: Any tips on writing a shopping cart?
by theorbtwo (Prior) on Dec 22, 2002 at 18:42 UTC

    Always consider the ease of updating by somebody who doesn't really understand the system deeply.

    Unless you aren't planning on getting paid, get paid in advance.


    Warning: Unless otherwise stated, code is untested. Do not use without understanding. Code is posted in the hopes it is useful, but without warranty. All copyrights are relinquished into the public domain unless otherwise stated. I am not an angel. I am capable of error, and err on a fairly regular basis. If I made a mistake, please let me know (such as by replying to this node).

      Always consider the ease of updating by somebody who doesn't really understand the system deeply.
      I'm the only one who will be updating the system for the forseeable future, but the data is stored in a Perl module in a simple format, so anyone who can edit any of my other scripts won't have a problem adding new items.
      Unless you aren't planning on getting paid, get paid in advance.
      It's part of a regular job, but thanks anyways--that's always good advice.

      =cut
      --Brent Dax
      There is no sig.

        Ah, no, I meant updating the items being sold.

        Bah, I can't write today. It should be possible for people who know nothing about computers (or at least programming) to update the list of items and (more importantly) prices. Even if this is a computer (or related) compony, the people doing pricing are likely to be busniessmen, not comptuerpeople.


        Warning: Unless otherwise stated, code is untested. Do not use without understanding. Code is posted in the hopes it is useful, but without warranty. All copyrights are relinquished into the public domain unless otherwise stated. I am not an angel. I am capable of error, and err on a fairly regular basis. If I made a mistake, please let me know (such as by replying to this node).

Re: Any tips on writing a shopping cart?
by shotgunefx (Parson) on Dec 22, 2002 at 21:32 UTC
Re: Any tips on writing a shopping cart?
by tachyon (Chancellor) on Dec 23, 2002 at 00:24 UTC

    I would suggest maintaining state either with a session ID and all the data on the server or an encrypted param hash (using say Crypt::CBC and Crypt::Blowfish) - stored in a single 'hidden' field. Cookies are not secure. Nor can you rely on them being available so a hidden field holding either a session ID MD5 hash or an encrypted param hash is the way to go IMHO. You also need to have some sort of timeout mechanism to expire sessions so cached data rapidly becomes useless. In some applications of mine I will reset the timeout to say 10 minutes each time the client hits the server - no activity for 10 minutes and a new login is required (old state data is then restored). This avoids a malicious user being able to steal a session from cache very easily. When forcing a re-login you need to loose the old session data if the login username/pass is not for the expected session.

    One major issue is how you want to handle BACK. You can use no cache but users expect to be able to use the back button. For this reason I favour maintaining state with a well encrypted param hash embedded in the page rather than a session id with data on the server. This way the state relating to that page is always accurate. If you use a session ID and data on the server if a user goes back the web page and the session data are no longer in sync. It is simply not possible to consider all the possible permutations that can occur when users go back but you avoid the issue if the state data is *securely* embedded in the page.

    It has been said again and again but you should never trust the client (even with encrypted data) and always validate prices on the server side before confirming the order.

    If you don't keep credit card numbers you are far less likely to have them hacked from you. I prefer sites that always ask to the number again. If using an encrypted param hash in the pages I would just store a reference to the credit card number embedded on the page with the actual number kept on the server. Perhaps just paranoia but this ensures the CC number only ever makes one pass through the ether when the client submits it.

    For added security there is no particularly good reason not to run the whole cart on an https server, including the initial login. Also to protect from know plaintext attacks on your encryption you can double/triple encrypt the param hash which makes it a little harder and more time consuming. If the cart is going to be offered to others I always automate the setting of the salt/key for the encryption when the thing is setup - if you do not do this and rely on the installer to make the required change your encryption is trivial to break in the case where the default salt is used. People will forget and you will get blamed! The encryption has to be two way and with the salt/key the decode is a no brainer.

    cheers

    tachyon

    s&&rsenoyhcatreve&&&s&n.+t&"$'$`$\"$\&"&ee&&y&srve&&d&&print

      One major issue is how you want to handle BACK. You can use no cache but users expect to be able to use the back button.

      I just had a great realization here. I can't believe I never really thought about users using the BACK button on their browser. I can imagine the amount of confusion and damage done with this. Besides using a param hash in a hidden field, I suppose you could use the "Cache-control: no-cache" and then keep a 'step' number/key in the database as well. This identification token could then indicate which step in the process the user is working on, and load only the page that step corresponds to, unless the parameter to change the step is sent to the script. So in a shopping cart app, after saying "I want 10 pairs of shoes", and hitting the "Add to cart" button, say I want to change it to 5 pairs. I might hit the 'back' button, but this would load the same page I'm already looking at, perhaps with a 'sorry, do it the long way around' message.

      I'm not sure how efficient what I am saying would be to implement and use, so I am going to give it a whirl of a test to see how it does work out. The only downside I can come up with is huffy, puffy users who want their BACK button functionality to function the way it's supoosed to :) I'm going to put something together, and then perhaps throw it in the code catacombs or code snippets. We'll see...

      -------------------------------------
      eval reverse@{[lreP
      =>q{ tsuJ\{qq},' rehtonA'
      ,q{\}rekcaH },' tnirp']}[1+1+
      1,1-1,1+1,1*1,(1+1)*(1+1)];
      -------------------------------------
      
Re: Any tips on writing a shopping cart?
by zentara (Archbishop) on Dec 23, 2002 at 16:30 UTC
    This may seem kind of obvious, but since it hasn't been mentioned, I will. Have the cart check to see if credit card orders are being shipped to the address on the card. If not, it should set a warning flag for extra security checks.