Payment gateway security
Published on 5/10/2016
As part of our continued Counsel Connect development, I started implementing the online payment service “Pay Now” provided by Sage Pay. Unfortunately, things went bad a few days into the initial prototype.
After the extensive sign-up process I was presented with the technical documentation for implementing the online payment gateway and their secure SOAP endpoint calls and functions for debit order processing and other things. At this point in time I’m only interested in online card transactions, and so I immediately started looking through the SOAP endpoint for the relevant functions. It wasn’t there.
I read back and found that the only way to process card payments is through their proprietary web-form flow which you have to initiate with a POST call to a web endpoint. Their example looks as follows:
<form name="form" id="form" method="POST" action="http://paynow.sagepay.co.za/site/paynow.aspx"> <input type="hidden" name="m1" value="xxxxxxx-4ad4-7c2a-yyyy-eee9f23651097"> // Pay Now Service Key <input type="hidden" name="m2" value="24ade73c-98cf-47b3-99be-cc7b867b3080"> // Software Vendor Key <input type="hidden" name="p2" value="ID:123"> // Unique ID for this transaction <input type="hidden" name="p3" value="Test / Demo goods"> // Description of goods being purchased <input type="hidden" name="p4" value="5.00"> // Amount to be settled to the credit card <input type="hidden" name="Budget" value="Y"> // Budget facility being offered? <input type="hidden" name="m4" value="Extra 1"> // This is an extra field <input type="hidden" name="m5" value="Extra 2"> // This is an extra field <input type="hidden" name="m6" value="Extra 3"> // This is an extra field <input type="hidden" name="m9" value="[email protected]"> // Card holders email address <input type="hidden" name="m10" value="Demo attempt for testing"> // M10 data <input name="submit" type="submit" value="PROCESS R5,00 TEST PAYMENT"> // Submit button </form>
If it’s not immediately obvious, the first hidden input (m1) is supposed to hold my service key. This is the ONLY piece of data in this entire thing that identifies me, as Counsel Connect, to Sage Pay. And it’s right there in the open for anyone to see if they care to press F12 to view the source. Hell, you don’t even need to know the shortcut key; “view source” has been on the right-click menu for Windows users since Netscape Navigator.
What’s the next very obvious vulnerability? Yes, you can edit the amount (the p4 input) to your liking, and it is completely out of my control that you can do that, or to what you can set it. All this basically means that you, as a user of Counsel Connect, can pretty much initiate the payment transaction in any state that you fancy.
I raised this with my technical contact at Sage*:
His response was simply:
This raised a cacophony of alarm bells to me, since it was obvious that this person had no understanding of the issue raised, or any appreciation that they have vulnerabilities. Also, so far this only covers the initiation of the process. The hand-off at the end is also a concern. On their configuration pages I can set up URLs that serve as callbacks which presumably they will call with all the relevant information in the open (i.e. as url parameters). This is another failure, since it’s entirely feasible that a user can fake (or manipulate) a successful request (given access to the initiating POST) which will force my system into a processed-payment flow if I don’t do any additional checking against the SOAP endpoint (which is, thankfully, possible).
I was called by their technical director, and while conceding that I indeed raised valid vulnerabilities, I was instead offered an explanation about the limited use of my service key, and the mitigating function calls available in the SOAP interface to verify the transaction status. That’s not good enough. An analogy here is my bank telling my I don’t have to worry about keeping my card pin number secret, as long as I keep my daily limit low and I call them when I suspect fraud. It doesn’t fly. It leaves me, the customer, open to be unknowingly complicit to fraud which can only be mitigated by retrospective checks. This completely breaks the nature of 100% automated straight-through processing towards which I’m working. Any human intervention is too much, and every check that needs to be done is another possible point of failure. Additionally, having access to the actual values that constitutes the contents of an HTTPS call gives the competent attacker a massive advantage towards identifying correct decrypted bytes while guessing keys, and from there it’s a simple leap to decrypting the entire process and launching man-in-the-middle. This is not a hard problem to crack any more, and OpenSLL has been the subject of much controversy and bugs in the last year or so, with the last two really big vulnerabilities only recently patched. Furthermore, this coming from a representative of one of the biggest payment providers in South Africa, and a player even in international markets, speaks volumes about the proliferation of internet crime and calls into questions this provider’s attitude towards combating and preventing it.
So, what is a better architecture for this? There are two major options here. Firstly, they can extend their SOAP endpoint so that all the communication is initiated and handled on my server side, away from the user, and I simply capture the required details (card number, cvc etc) from a form that I build, and pass it on to the SOAP endpoint for processing. Problems with this method is that I then need to become PCI-compliant since I’m handling card numbers. The second problem is that my own web-request handlers are subject to waiting for web-requests to complete; also something to be advised against.
The second option is to only initiate the payment flow via the SOAP endpoint. I would basically pass all the relevant details (service-key, amount, transaction reference) from my server-side, away from my users, to a SOAP function. This will leave the initial request handler dependant on another web-request, but you have to start somewhere. Their SOAP endpoint then returns a single-use, timed transaction-key with which I can then seed a simple web form with a single hidden input field (the value of which means nothing to any user or criminal). When that form is then POSTed, that transaction-key identifies me and my associated transaction details on their side, and the process carries on as it currently does.
The second option is clearly superior. It leaves Sage to handle all the sensitive stuff inside their form-flow, but the transaction initiation is secure and out of reach of fiddly hands. I’m not sure who came up with their current architecture’s initiation flow, but it certainly does not stand-up to even a small amount of scrutiny, and is actually laughable**.
As for the hand-off; a similar strategy should apply. A hand-off token should be the only thing sent back in the callback (and there shouldn’t be separate callbacks for success and failure), my system should then retrieve the transaction state using said token and my service key. This token is never exposed to any users, and so they cannot manipulate the hand-over. Basically, you never push sensitive data. This is a basic security concept.
At this point you might note that I can still do the POST call server side as suggested in my initial email, and accept the Sage HTML page in their response manually and then return that as my controller result directly instead of injecting it via JS. Yes, that would allow me control of the initiation, but it doesn’t address the poor service architecture design, and it doesn’t address the additional processing and web-requesting required at hand-off at the end. It also doesn’t address the vulnerabilities that’s so obvious in their actual documentation example which would be followed by junior programmers without questioning it.
After inquiries I found that PayU implements the above suggested solution almost verbatim. PayGate still hasn't responded to my request for documentation at the time of publishing, but PayFast seems to have the same implementation problem as Sage. So before I take the plunge and sign up with another provider, I need to seriously evaluate the technical implementations of the providers considered. If you need to implement an online payment solution I suggest you do the same. This is really important since South Africa has recently been crowned the cyber-crime capital of Africa (not surprising since we’re also the biggest economy on the continent), but we’ve been in the top ten in the world since at least 2012, and third since 2013. Security is no joke.
*you’ll note that I mention a JS-based work-around. This could be implemented for the p4 input too, but it’s not a sustainable solution anyway.
**as proved when reciting this story to my friends.