ISSUE: How To Hack PayU – And Buy 10x More For The Same Price

Do you know PayU? It is a Paypal competitor on emerging markets. A few weeks ago I was talking with my friend, who was working on online payment integration with PayU REST API. He showed me that you can play around with payment details and PayU API won’t complain about amount not mathematically correct. It is possible, that client will increase products quantity without increasing the amount to pay. I invested some time to test all the cases and this article will show you how to hack PayU payments and to get more than you paid for.
Disclaimer: I wrote this article to draw attention to a problem. Remember that I don’t approve using it for personal advantages and exploiting this bug can result in legal action.

PayU – What Is It?

To cut long story short, PayU is online payment provider. A few years back everyone buying online was providing his credit card details directly to store. It resulted with credit card credentials (sufficient to make payment) being all over the place in trusted (or not) databases. Now we have many proxies (PayPal, Google Wallet), to which we give everything needed to make payments on our behalf (using many different payment forms). Well, PayU is one of them.
It was founded in 2006, and from that time they expanded to 16 countries all over the world, for example: Hungary, Peru, Nigeria or Poland. On their site there’s information that at that moment they have more than 100,000+ merchants using their solution. Personally I’m using their account for most of my online payments for a few years now and I’m pretty satisfied.
It would be fair to assume that such solution is well tested. Well, let’s move on to API specification.

API – possible integration

Integrating PayU with your site is really easy as all payment management lies on their side. Firstly, buyer is defining cart on your site and then he’s redirected to payment form choice. After payment being accepted, buyer’s redirected to your thank you page and you’ll be notified about payment being accepted.
Data flow during the payment process
Data flow during the payment process
So let’s get to the details. Consider very basic shopping cart when someone’s buying at your site 1 hardcover book for $98.50.
Very basic PayU form ready to hack and submit
Very basic PayU form ready to hack and submit
It’s internal code looks like this:
Everything’s clear to this point. Simple form, simple settings and we’re ready to make API call. To secure this data, we need to set hash signature based on our private key, so no one would ever modify it in-the-middle. And that’s where the fun begins.

Signing parameters with hash

To make sure that all parameters remained unchanged, we must sign them with our private key. As we can read in PayU REST API documentation signing form parameters consist of several steps.
  1. Sort all the fields available in the form in the alphabetic, ascending order according to the names of parameters name attribute of the input element.
  2. Concatenate the values of all the fields in the indicated order.
  3. Attach your private key to this sequence of characters.
  4. Use one of the available hash function (e.g. MD5).
  5. Complete the OpenPayU-Signature field with the value signature=A;algorithm=B;sender=C, where:
    • A is a result of an hash function,
    • B is the name of an applied hash function,
    • C is a POS identifier.
OK, we know how to do it, let’s calculate MD5 for our form! Firstly, let’s build sorted array of form inputs:
Now we need to concatenate all field values into one string:
“PLN127.0.0.1CodeL10n PayU hack145227Best Localization Tips from CodeL10n hardbook198509850”
And now generating hash from such string after adding our private key (let’s assume that it’s “13a980d4f851f3d9a1cfc792fb1f5e50” – PayU test key) would be:
And the result is 430e1f80b1dbb44527ec8b3e218cafc6. Now, this form would be valid only if all parameters are unchanged. We’re safe!
Remember that your signing key shouldn’t be accessible to the end user

PayU Hacking – Where Magic Happens

Now, there’s no way to modify any of parameters and still get same MD5, right? Our concatenated params are unchangeable:
PLN127.0.0.1CodeL10n PayU hack10Best Localization Tips from CodeL10n hardbook198509850
Oh, wait… Can you see red part of the string? That’s concatenated quantity “1” and unit price “9850”. What if we would change quantity to “19” and price to “850” right before submit?
PayU wouldn't complain if hash is correct. And it will be if you will move numbers around from beginning to the end of parameter
PayU wouldn’t complain if hash is correct. And it will be if you will move numbers around from beginning to the end of parameter
Hash would remain the same even if we modified parameters. On form-level we can modify quantity and price, but leave total amount unchanged. How to modify it? Google Chrome Console will be enough to move number from the beginning of price to the end of quantity right before submit. Secured private key won’t protect you and is useless in this case.
Google Chrome console is sufficient to modify form parameters right before submit to PayU
PayU API wouldn’t complain about total amount not mathematically correct, but that’s just half of it.
When PayU server will call back your URL after payment, you will receive with request the same incorrect payment details which was sent (!!!). For example (note modified price):
Now, it’s the one good practice to validate and store order details on your side, but let’s assume that a programmer is using parameters sent by PayU callback to complete order. If system wouldn’t do simple check quantity*unitPrice=totalAmount it will queue consumer package with 19 items, when he paid for 1.
I created hacking sandbox for you. Check it yourself here: Hack PayU Form.

How Much Can Be Stolen?

Well, it all depends on price and quantity. In our case it’s nothing complicated, just math. Let’s analyse our example:
1 * 98.50 = 98.50 to pay and you get one item
after modifying
19 * 8.50 = 161.50 to pay but worth 1871.50 and you get 19 items
So for this price we got more than 17 items free.
Disclaimer: remember, this article shows the issue, so you can check PayU implementation on your side. I personally condemn stealing or exploiting bugs.
But one more important note! Even if Payu is showing total amount (after modification) for product as 161.50, total amount to pay remains the same (!!!) so you will pay only 98.50 in their interface (I double checked that). You would get 18 items free!

How to Stay Secured – Best Practices When Dealing With PayU

I imagine that it’s PayU job to check parameters sent to their server. Hashing algorithm isn’t ideal and shouldn’t be so lossy (concatenating parameters to one string will always be risky). So simple equation check quantity*unitPrice=totalAmount can be the answer here.
Let’s summarise what you can do when dealing with PayU, to stay secure:
  1. Always store product data on your side.
  2. Never make your private key accessible to end-user.
  3. Always get order parameters from your storage when PayU is calling your webhook.
Please, don’t get me wrong – I’m using PayU almost on daily basis and it’s great solution. There’s just small room for improvement :) This bug won’t affect any customer, but merchants should be concerned and check their implementations. Be sure to spread the word to your friends working with e-commerce.
UPDATE: I’ve sent message to PayU regarding this issue to ask for their comment, and I received answer that: “The issue you had described was forwarded to a suitable department to be handled. We will take all the necessary steps to prevent the situation described or similar from happening in the future.” so everything’s on good way to being solved.
UPDATE 05-01-2016: I asked Egor Homakov from Sakurity about signing concatenated parameters: “It’s not the first time I see a vulnerability like this and not even a second. I called it “format injection”. This is how Duo Security could be exploited and more critical bug (subscribe to any channel) in pusher. Concatenating with : , ; or anything else is a bad idea because it destroys structure of the message with unescaped user input. Everyone should use more reliable encode_uri_params or better JSON.dump (see JSON web token for more standardized format of signed data)”. What’s interesting PayU isn’t the only one with such vulnerability: LiqPay and WalletOne works in the same way
And that’s all for today. As always, writing this article wouldn’t make any sense without you, my dear reader. I would love to hear what you’re thinking. If you have some spare time to give me any feedback, I really appreciate it.

PS: if you like article’s about security, you’ll probably like my Story of First Computer Virus.
[do action=”cc-image-attribution” author=”Rick Harris” photourl=”″ cclicense=”by-sa”/]

Post a Comment