Exploiting Weaknesses in Payment System IPN's To Validate Transactions

Michael Ness
CEO, Founder
August 12, 2022

Every purchase an individual makes online usually relies on various Payment Systems and these systems generally validate transactions through Instant Payment Notification Systems (IPNs) and the general flow of an IPN can be seen below. The flow involves the vendor generating a transaction with the payment processor through an API, the payment processor takes payment from the transaction and then upon successfully validating the payment, signs the transaction and sends this back to the vendor to then process the transaction for the client who has just paid. There are many points for failure within this process, but imagine if you could create a transaction with the vendor, sign it and send the signed message yourself? This is exactly what is possible here, there was a way within this system to create transactions and validate them yourself without having to pay anything!

General overview of the IPN process.

The most important concept in this research was the relay attack, it was key for the attack to be able to edit the statusUrl parameter, which was responsible for telling the payment processor where to send the signed transaction data too. Being able to change this, allows you to get the payment processor to send you the signed transaction to a server that you own rather than the vendor you are purchasing from. I found out that it is pretty common for default configurations of payment systems to use a HTML form with a GET/POST request to submit the data to the payment provider, in order to generate the transaction in the first place. All you simply had to do is capture the request in burp suite and edit the statusUrl parameter, which would generate a valid transaction with your server as a status URL and from there you can capture the valid transaction request from the payment provider that would usually be processed by the targeted site.

Showing how to manipulate the status URL so you can receive the response from payment provider in this one Skrill.

The next step in this was picking a payment system that had a weakness in the protocol for the way they verified legitimate transactions. I settled on then UK based payment provider — Skrill. The weakness here was immediately obvious and I almost could not believe my eyes from what I was seeing in the API documentation, I will talk more about this later. At the time the whole validation process relied upon six different factors which were combined to make a MD5 signature. These factors are merchant_id, transaction_id, secret_word, mb_amount, mb_currency, status_code and a secret word.

Parameters all involved in generating the MD5 signature that make up a valid transaction.

The image below shows how these parameters are combined to make the signature. Essentially in simplified terms it looks like this: MD5(merchant_id + transaction_id + MD5(secret_word).toUppercase() + mb_amount + mb_currency + status).

How Skrill generates the MD5 signature.

This is firstly done by Skrill and is sent alongside other payment data for it to be validated by the target server. The information that is sent to the target, that was intercepted looked a lot like this:

Data sent from Skrill for their clients to verify the transaction as legitimate.

Now you have an idea of all the information that it is possible to intercept via changing statusUrl parameter. You can capture this information and then confirm this it is valid by then relaying it back to your target and the transaction should process just fine. If you are vigilant you may have noticed that all of the parameters used to make the MD5 signature are sent back in the response so the target can generate a signature to match what has been received. Matching these signatures is what ultimately confirms the transaction as legitimate. The real vulnerability lies within the secret word used to sign the transaction mainly due to the limitations of this being no more than 10 characters, all lowercase, 0–9 and no symbols allowed. This makes brute-forcing it a great possibility.

The limitations to a secret word that controls the full validation process…

A quick note… below is an example of how targets will generally process the information that Skrill or you an attacker sends to them. The MD5 signatures have to match in order for anything to be done with the data and therefor the transaction. The MD5 signature is the token of legitimacy for every transaction that goes through the Skrill IPN.

Code to show the general implementation of validating transactions via Skrill from request data.

Brute-forcing is exactly what was done, a brute-forcing tool was written in Java that would hash the exact same way as mentioned above. The static parameters from a valid transaction were set before brute-forcing. In essence the tool would iterate through a massive 50GB wordlist, hashing each word in the following format MD5(merchant_id + transaction_id + MD5([WORD-HERE]).toUppercase() + mb_amount + mb_currency + status) until it found a valid match for the MD5 signature that was intercepted. Brute-forcing was conducted on two of the UK’s biggest gambling chains and it had successfully cracked both passwords in less than 24 hours.

The next stage of this was to try and turn this into a valid attack scenario by creating a “valid transactions” from thin air. This was initially thought to be more complex but in fact all that was required was to fill in the form for the 25,000GBP that would be deposited and intercept the request. In this request there were  all the parameters I needed to generate a valid MD5 signature: transaction_id, merchant_id, mb_amount, mb_currency and status. All that was left to do was to generate a valid MD5 signature. This was done in the same manner as above using the newly cracked secret word.

How I generated the “fake” MD5 signature.

Finally once the MD5 signature was generated to match the data of the transaction that was just started, all that was left to do is send the POST request to the IPN processor on the target. A cURL request was crafted to do so and it looked something like this: curl https://target.com/moneybookers.aspx — data “[TRANSACTION DATA HERE]” with the data looking like the image below:

Format of the transaction data I sent to the targets, with the parameters replaced with genuine parameters for the transactions I had started on the platforms.

The request was sent and immediately it was noticed that 25,000GBP was credited in the account that was utilised for testing. The Application Security teams of both companies were contacted after validating the vulnerability. They were advised on the best remediation steps which were: generate the transaction internally so an attacker cannot manipulate the statusUrl parameter, validate that the requests come from the Skrill servers before processing them and finally use better PASSWORDS!