2

Let's say that I have a website where people can make purchases, donations, etc.

The entire website is served with https.

I store the information regarding the purchases (item, quantity, price) as well as the user (name, address, email) in my database. The last page, where the user enters credit card information, sends an AJAX call to retrieve the data for this purchase as well as my API keys. When this returns, I send it together with the credit card information that the user entered on this page via AJAX to the payment processor (for instance Authorize.net).

The credit card information would never hit my server.

Would this be secure? It doesn't seem to me, but I can't pinpoint an attack vector.

  • Isn't your API key secret? – Julie Pelletier Jun 21 '16 at 7:16
  • @JuliePelletier what do you mean? the key is supposed to be secret, but I need to be able to send it to the processor. – pppp Jun 21 '16 at 7:18
  • 4
    Giving your clients' browser access to your API key makes it very accessible. You must follow one of Authorize.net's suggested methods to the letter or risk getting in serious trouble. – Julie Pelletier Jun 21 '16 at 7:25
  • apart from the API key issue, is the user able to modify the price before you send the request to the payment processor? Do you check that the amount paid is the amount you expect? – Jay Jun 21 '16 at 7:26
  • @Jay I guess part of the question is whether a user could intercept the response from the first call before the second call. – pppp Jun 21 '16 at 7:28
3

Never trust the client browser.

There are four main attack vectors I can see based on this model.

1) Leaked credentials

First, as @JuliePelletier commented, in order for this to work, all the information necessary to make a "charge this card" call to Authorize.Net has to be available to the customer's webbrowser. This includes whatever secure credentials you have to identify you to the processor. With those in hand, anyone can make the same AJAX post to Authorize.Net to charge any card or, worse, refund from your account to any card.

2) Altered order data

Second, as @Jay commented, this scheme requires all the information about the order to pass through the user's browser before getting to your processor. It's trivial for the user to put a breakpoint in the javascript between the return of "get order data" and the call to "send payment data" and alter the variables that are about to be sent. (All modern browsers support this with their built-in developer tools.) This lets the user order $5000 worth of stuff, then successfully charge their card for $0.01. Unless you explicitly checked the amount approved, you'd just see that the payment was successful and be out $4999.99. Even if you added an approved amount check, you'd still have to void off the transaction (which might incur extra fees), cancel the order in your system, and somehow communicate that to the user.

Also, even with a tamper-check, the algorithm for doing that is going to be part of the official Authorize.Net documentation. Unless it involves a secret key which you do not make available to the Javascript (see point 1), it would be very possible to just recalculate the check after making changes. And if the tamper-check is supposed to also validate that the card data is unchanged, there's no way to do that without making it available to the Javascript.

3) Spoofed response

Related to the approved amount check from point 2, your AJAX call is going to be expecting some kind of "Approved" response from Authorize.Net before updating the order in your system to indicate that it was paid. Nothing prevents the user from skipping the call to the processor and faking that response altogether. Then you think you've been paid, but now you have no record of it - not even a failed attempt.

4) Hijacked calls

Finally, none of this provides any protection against a malicious browser plugin hijacking the user's data. You can't prevent one from scraping the card data as the user enters it and just sending it off to a server in Russia (or wherever), but you have no protection against one which intercepts the AJAX POST, redirects the whole thing to that server (including your credentials and the card data), and then either passes it on to Authorize.Net itself or simulates a failure so that the user tries again.

It's via a different attack vector, but this article discusses a recent breach which had a similar effect. In that case, the server was serving a maliciously altered script which caused the card data to first be POSTed to the server, and then sent off to the processor.

From the consumer's point of view the data entered into the iFrame vanishes and the customer is left with an empty iFrame to complete again. It is expected the customer would assume something went wrong with the web page and re-enter and submit their payment details which, on the second attempt, are submitted direct to the Payment Service Provider.

Your site would have similar behavior - the malicious script would POST elsewhere, return what appeared to be an Authorize.Net "please try again" error, and then remove itself. The unsuspecting user would then resubmit, and it would go through, with no indication to you or them that anything untoward happened.


The secure method

The secure way to address all these issues (except for part of 4) is to instead have the user POST their card data to your server. Then, your server sends the request off to Authorize.Net and handles the response. This addresses all the points as follows:

  1. By sending the request to your processor from the backend, your credentials are never exposed to the user. This also includes any secret data needed for the tamper-check in 2.
  2. By pulling the data direct from your database to send, you can be sure that it's exactly what you want to be sending, with no chance for the client to modify it.
  3. Because you're calling Authorize.Net in a way the end user can't see or intercept, you don't have to worry about fake responses being returned.
  4. POSTing to your own server still doesn't prevent malicious plugins from scraping the user's data as they enter it, and there's really nothing at all you can do about that. Additionally, you still don't have any good way to prevent said malicious plugin from altering your page to POST to some random other server on the internet. But you can verify the IP address posting the data to your server is the same as created the order in the first place, and you can use anti-forgery/anti-XSS tokens to prevent other servers from trying to POST to you.

All that said YOU ARE NOW UNDER HIGHER PCI SCOPE, because you're sending card data to your server. Here's the summary, but you're basically moving yourself from A-EP (Direct POST) to D-Merchant. Even if you just immediately pass the card data along and never store it, you're still in that scope - it just means that you'll have an easier time passing your certification.

An alternative

The other secure way which keeps you out of scope is to outsource the entire payment screen to another site (such as Paypal). This will actually reduce your PCI level, since you have even less interaction with the card data, and it outsources dealing with all these security issues as well.

  • regarding 1, I see that refunding could be a problem, but what is the problem if someone charges any card with my credentials? (What could they do here that they can't do directly from my website?). Regarding 4, is there something inherently insecure about AJAX that makes it more susceptible to hijacking than a direct post to my website and from there to authorize.net? And what would prevent someone from adding an AJAX script to send them the information entered before the form submits? – pppp Jun 21 '16 at 14:55
  • @user1930608 - Regarding #1, it's mostly a automation thing. They can take a list of stolen credit card numbers and just spin through trying them until they find one that works. It's possible to do that via a form, but if it's going through your server it's much harder than direct to A.NET. For #4, I'll add a bit more on the difference. – Bobson Jun 21 '16 at 16:31
  • @user1930608 - How's that? – Bobson Jun 21 '16 at 17:21
  • thank you. Where can I find out what I need to do to get PCI certified? – pppp Jun 23 '16 at 7:23
  • @user1930608 - This FAQ (which is where I got the summary image from) is a good starting point. Depending on what category you fall into, it may be as simple as filling out a Self-Assessment Questionnaire, or as involved as contracting with a QSA to perform a full audit. If you have specific questions that that FAQ doesn't answer, feel free to ask a new question here and get some guidance. – Bobson Jun 23 '16 at 17:15
2

I guess part of the question is whether a user could intercept the response from the first call before the second call.

The answer to that is absolutely, Yes.

For this setup to be secure, you would have to have an encryption scheme.

  1. The Amount and Transaction Action (sale, refund, etc) either has to be encrypted or accompanied by a checksum of said info in combination with a secret value to prevent alteration.
  2. The API key has to be encrypted (kept secret) or the processor must require that it be accompanied by a secure hash of a supplemental secret value. (i.e. a hash combining a long random PIN with the amount and action and other identifying information)
  3. The processor (i.e. Authorize.Net) should protect against replay attacks.
  4. The processor would have to support this as a recommended approach before you even consider using it. Check your processor's documentation. Also you can get some questions together for a technical support call with them.

    If they support this approach, you are likely to see Access-Control-Allow-Origin as a response header thereby allowing your website's JavaScript to review the response of the AJAX request, and inform the user whether the charge was Approved.

With those conditions met, you should be OK letting the browser initiate the request to the processor from a data-integrity and data-security perspective.

However, you will still have to verify whether the charge went through somehow before you fulfill the order. One option is for the browser to take the processor's response, and then make another AJAX request to your website informing it of the successful transaction. This new AJAX request would have to have appropriate encryption or checksum verification written in by your Processor and then verified by your website to ensure it was not altered at the browser level.

Since this would be yet another AJAX request, there is the chance that this will not be received by your website in which case you will have the incoming money in your merchant account with no verified order associated.

  • programmer mistake
  • poor internet connection
  • server error (i.e. database crash) on your website
  • user tampering

One solution is to reconcile the incoming funds with accepted orders. (which you should do anyway)

If you take the more common approach which is to have your server equipment pass (but not store) the credit card information, then

  • you solve the poor internet connection and user tampering caveats above (still should reconcile in case of mistakes or errors)
  • you hide the details of which processor you are using
  • you simplify the security considerations by not needing as much review of the encryption/hashing and replay-attack-prevention scheme.
  • you reduce liability by reducing the number and public usage of secret values that (if compromised) could be used to thwart your encryption scheme allowing forged transactions.

In both cases (pass but not store and your server never touches), if properly implemented (and secrets are not compromised), the following conditions hold true.

  1. A potential hacker cannot access the data immediately if they breach your server because nothing was stored.
  2. A potential hacker could adjust the software on your server equipment to begin logging the sensitive credit card data. (i.e. the JavaScript files that initiate the AJAX request could still be altered even if you are not touching the credit card data)

So the security gain you have by never touching the card info is very minor (perhaps non-existent) because you are not logging the information in either case. For this reason, I would recommend you take the pass but not store approach. That of course is a decision ultimately you will make.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.