Pull to refresh

Payment Village at PHDays 11: pentesting our online bank

Reading time13 min

Hello everyone! We've already talked in our blog about how the Positive Hack Days 11 forum had a special Payment Village zone, where anyone could look for vulnerabilities in an online bank, ATMs, and POS terminals. Our competition to find vulnerabilities in an online bank is not new, but in recent years it has been somewhat supplanted by ethical hacking activities for other financial systems. In 2022, we decided to correct this injustice and created a new banking platform, making use of all our years of experience. We asked the participants to find typical banking vulnerabilities and report them to us. In the competition, the participants could play for either the "white hats" (participate in the bug bounty program of an online bank) or for the "black hats" (try to steal as much money from the bank as possible).

We evaluated the severity of each bug found, then counted the total number of vulnerabilities discovered by each participant during the competition. As per tradition, 50,000 rubles was awarded to the person who discovered the most dangerous flaws—this year the winner was catferq. He found the largest number of vulnerabilities that we planted, including two critical bugs, as well as one bug that we didn't even know about 😊. In addition to catferq, other participants who discovered less dangerous vulnerabilities were also rewarded: hundred303, null1337undefined, kab5fa2hs, HackathonDev, and c0rv4x. Thanks to everyone who took part in our competition! It was very interesting to read your reports and feedback on how we can improve this activity. You're the best!

A little about our online bank

This year we decided not to pivot on our previous setups, but to create a new service for the competition from scratch. One of the youngest members of our banking security research team took on this tricky challenge, working on the project under the mentorship of more experienced colleagues. Our online bank consisted of a frontend and backend, as well as an automated banking system (ABS). Together with the database (postgres) and cookie storage (redis), it was run on a number of connected containers. By the way, the bank is still online—you can check it out right now.

To enter the competition, participants were required to go through a simple registration process—which we had already built a few flaws into (more on that later). Reports were submitted via a special form, opened by clicking on the bug icon on the main page.

The participants had to use all of their ingenuity and pentesting skills to discover the vulnerabilities that we built into the online bank.

Flaws in the online bank's authorization and password policy

Let's start our debrief from the entry point for any attacker: the personal account login form.

Bruteforcing the login form

When registering, attentive participants might have noticed that:

  • The password policy of the online bank was weak.

  • The IP addresses of attackers trying to hack into accounts by bruteforcing passwords were not blocked.

Let's look at an example—trying to crack the password for the account User1234. Despite the large number of attempts to enter an incorrect password, we get the message "invalid username or password" every time:

Now let's try to enter the correct password:

As you can see, the answer changed: the system blocked logins to the account for five minutes. In fact, this blocking occurred during one of the attempts to enter a wrong password. In addition to the fact that the system does not prohibit credential bruteforcing (although it tries to hide this with the message "invalid username or password"), the participants could also use dictionaries to guess passwords for known accounts. For example, in the footer of the main page of the online bank, the administrators' contact details (username and email address) were indicated, supposedly for the participants to get in touch. As an option, participants could have bruteforced an administrator's password using the rockyou dictionary.

PHDays 11 challenge results: none of the participants detected this vulnerability.

CVV bruteforcing

The lack of password protection against enumeration opened up another attack possibility: bruteforcing bank card CVVs. To obtain the PANs of cards contained in the system, participants could exploit a card data disclosure vulnerability. To exploit it, they had to take the card data from the system and use it to enter the online bank:

We get the standard response about incorrectly entered data, run a CVV brute-force attack and see how, for one of the requests, the response code changed from 200 to 302. Let's take a closer look at this request/response pair.

The system behaved in this way only once in thousands of login attempts, so we can conclude that it's possible to guess the CVV of known bank cards in this way.

PHDays 11 challenge results: none of the participants detected this vulnerability.

One-time password bruteforcing

When logging in to the personal account, participants could optionally enable two-factor authentication. In this case, when the participant enters the correct login and password, the system asks to additionally confirm the login using a one-time password (OTP):

The one-time password was sent to the participants via the Telegram account they specified when registering.

Account verification and receiving an OTP to log in:

Upon trying to enter an incorrect password, we receive a response from the server that the one-time password is incorrect.

By trial, we can discover that the online bank replaces the OTP once every thousand login attempts.

At this stage, bells should have been ringing for the participants: the password consisted of just four digits. This creates a non-zero chance of guessing the one-time login password. It should be noted that real banking systems have stronger restrictions on sending one-time passwords, even though these are also not always sufficient in terms of information security.

Let's revise a little bit of university (or school) mathematics. What are the chances of guessing a four-digit number in 10,000 tries, if the number changes every 1,000 tries? Answer: , where 0.9 is the probability of not guessing the number until it's changed.

If you don't want to get into the math or come up with your own scripts in Python (or some other language), but want to start the process of bruteforcing numbers right away, you can use the popular Burp Suite web vulnerability testing utility. Having studied how the server responds to the successful verification of login data (login, password or OTP), we set a regular expression for the response in the Intruder parameters and launch a brute-force attack.

As you can see in this screenshot, it was possible to guess the one-time password (without peeping at the SMS with the password!) in the eighth run, or on the 7317th attempt.

PHDays 11 challenge results: two participants discovered and reported this vulnerability. Interestingly, we did not see in the logs that any of the participants had further exploited this vulnerability to gain access to the accounts. Our idea was that the participants would bruteforce an administrator's password (which could be guessed using the rockyou password dictionary) and then try to log in to the administrator's account using this vulnerability.

OTP business logic flaws

To link a Telegram account for receiving one-time passwords, participants had to verify it. They were required to enter a special code that confirms their account information in the system. Curious hackers would start thinking like this: "What if we have two Telegram accounts, and we need to link the second one instead of the first one, or we just want to unlink the first one from a specific personal account—what will happen to it in the system? In theory, it should be removed." But is it really so? Let's try to link it to another personal account. And, miraculously, the system just adds it immediately without requiring verification.

How can attackers exploit this flaw in the system? A potential attacker can link a legitimate user's Telegram account to their own personal account without verification. The legitimate user can then no longer reconnect their own Telegram account to their online bank account for two-factor authentication. Additionally, during a complex attack, this business logic error can lead to a bank client not being able to use two-factor authentication from their trusted account. Also, now the attackers don't need to worry about one-time passwords—they can just bruteforce the victim's credentials. In addition, this vulnerability allows attackers to carry out a CSRF attack and link their "lost" Telegram account to the victim's profile, thereby blocking the victim's access to his or her own personal account. All this makes the user account vulnerable to intrusion.

PHDays 11 challenge results: only one participant reported this vulnerability.

Disclosing masked data by username

Another vulnerability built into the online bank was the leakage of card data. Participants could use the leaked data to bruteforce a card's CVV and exploit vulnerabilities related to loans.

Money transfers can be made using a bank card number or a user's name in the system. The first method is familiar to us all, but the second is rarely used to transfer money. Let's look at it in more detail.

Let's make a transfer to a user by username:

  • 5572443375087227 is the ruble card number of the user who will make a transfer to another user by username in the system;

  • 8531606207370870 is the dollar card number of the user who will make the transfer to another user by username in the system.

The system successfully completed the transaction:

Having checked what the frontend does to receive this data, we see that the hidecard parameter is set to true in the request for receiving transactions. In response, the server sends "correct" (security-wise) data with the card number masked.

We repeat the request, but instead of true, we set hidecard parameter to false:

The information that can be obtained by making a transfer by username is highlighted in the screenshot above. If this were a real banking system, the ability to obtain a card number would not only be a direct violation of PCI DSS requirements[1], but also a loophole allowing attackers to make fraudulent payments with little effort. Also, in this exercise, this would have helped the participants to implement one of the possible scenarios in a complex attack vector (when several simple vulnerabilities are exploited together for greater effect).

PHDays 11 challenge results: four participants reported this vulnerability.

Rounding errors

Another interesting bug that we added to our competition—not for the first time—is the rounding error. For example, if the exchange rate of the dollar to the ruble is 1:60, then for 60 rubles we get one dollar. What if we try to convert 36 kopecks into cents? The amount will be less than the cost of one cent, but the bank can't charge you 36 kopecks and send 0 cents back in return. It will round the amount up to 0.01 dollars. Thus you can end up with a higher value than the original, but in another currency. In this case, when transferring back, you can get 0.60 rubles. The amount thus earned is 60 - 36 = 24 kopeks.

In our online bank, we simplified this vulnerability slightly so that participants could notice and exploit it.

The user's ruble card number is 7047169154995669, the user's dollar card number is 9384411725418749.

The bank exchange rates:

The initial balance of the bank cards:

Let's transfer, for example, 100 rubles from the ruble card to the dollar card at the current rate.

The transfer was successful. Let's pay attention to the changes in the card balances:

Already at this stage, a vulnerability is noticeable—when transferring 100 rubles at the declared rate, the bank's processing produced the following number: 1.257861… However, cents are hundredths of a dollar, not thousandths, which is why the system rounds the result up (as required by the rules of arithmetic). It ends up as 1.26. So we got an extra 0.003 dollars.

Let's demonstrate the severity of this vulnerability by trying to capitalize on it. To do this, we will make a transfer in the opposite direction—to a ruble card:

At the current exchange rate, this will be 1.26 x 79.5 = 100.17 rubles! The changed card balance confirms this:

The balance of the dollar card returned to the original value, but the balance of the ruble card went up. A couple of simple operations lets us earn 17 kopecks. Imagine the consequences for a bank if it leaves such a loophole for attackers in real life.

 PHDays 11 challenge results: five participants discovered this vulnerability. Moreover, one of them provided a script that automates the process of earning money by exploiting this vulnerability. Fortunately for him, the developers of the RBS system—that is, us—did not get in his way with various one-time passwords and captchas 😁. Through automation, he stole a modest 500 rubles to demonstrate the possibility of exploiting this vulnerability. Although if it were a real banking system and real attackers, the script he created could help steal millions.

Issuing loans

Business logic flaws when issuing loans to the same card:

As can be seen from the server response, the online bank has a limit on the number of loans that can be issued per card—no more than three. However, Burp Suite logger shows that we still managed to get four loans on the same card.

The first loan request:

The last loan request:

The screenshots confirm that the online bank does not in fact have the stated maximum limit of three loans.

PHDays 11 challenge results: none of the participants reported this error, although we saw from the RBS logs that three of them encountered this flaw.

Insecure direct object reference (IDOR)

When applying for a loan, you can specify the card of another user to pay the loan off. In this way, money will not be debited from the attacker's account. You can get a card number by exploiting the vulnerability related to the leakage of user card data.

Available cards:

The operation to debit funds from the card of another user:

PHDays 11 challenge results: two participants reported this vulnerability.

Bypassing the limit on the number of loans

While testing the system, participants could have noticed that the details of the same card for paying off a loan can only be entered four times. This means that a user with two cards could take a maximum of eight loans (this follows from the business logic error described above). These restrictions can be easily bypassed using the previous vulnerability in the same service. Here's how it all looks:

The screenshot shows that the number of loans debited to the card has reached the maximum. The same thing happens with the second card.

Now let's try to get around the theoretical limit using IDOR:

We managed to bypass the limit on the number of loans by specifying another user's bank card (we got the number by exploiting the vulnerability related to disclosing card data by username). A message from the server confirms that there are nine loan applications open, not eight. Below is a screenshot of one of the last requests.

PHDays 11 challenge results: unfortunately, participants who discovered the previous vulnerability didn't pay attention to the limitation we've described and didn't try to work around it. On our side, we didn't identify anyone who could have covertly exploited this flaw.

Denial of service

Another flaw in the loan functionality was the potential for DoS (Denial of Service) attacks. Participants had access to a loan calculator that allowed them to calculate the number and schedule of payments for the chosen program. Let's see how we could detect the possibility of DoS attacks.

First, we send a standard request with a calculation for the near future:

The response time seems to be normal—nothing to worry about here, right? But now let's play with the period and end_time parameters. First, let's change the end_time parameter from 2023 to 2070.

As you can see, adding years increases the server response time. Now let's change the period parameter from 1 day to 1 hour.

This also significantly increased the server response time. We can conclude that this functionality is vulnerable to a DoS attack.

Note that we set a limit on the number of requests that can be sent to this functionality, since otherwise any participant could randomly generate a large number of requests during the competition. This would lead to a system overload, which would prevent the other contestants from freely exploring the other elements and subsystems. That's why we set a limit: no more than three requests, with the user being subsequently blocked for several minutes.

PHDays 11 challenge results: we received only one report about this vulnerability, although we recorded a high load on the system on the first night of the competition. This was the result of many participants trying to exploit this vulnerability.

RCE vulnerability (remote code execution)

You can't overestimate the danger of an arbitrary code execution vulnerability in critical systems such as an online bank. However, such flaws can be occasionally found. To use one of the notorious vulnerabilities from the end of 2021—CVE-2021-44228 aka Log4Shell, we have included a vulnerable version of the Log4j package in our bank system.

While we were busy trying to come up with an elegant scenario for the complete compromise of the banking system through a web vulnerability, we didn't notice some errors when compiling the service. Thus, there were two ways to execute arbitrary code, one of which was available for a limited time at the very beginning of the competition (thanks to Apache and the default DEBUG logging it contains 🤬). In both cases, to exploit the vulnerability, it was necessary to use a PoC from GitHub.

The official way

Let's look at a way to get a permanent reverse shell using Log4Shell. To do this, you need to take control of one of the administrator accounts, since through their panel you can remove any user from the system. You can take control of an account using a combination of simple attacks on the banking system. The removal request itself looks like this:

Let's exploit Log4j through the username parameter by entering the payload into it:

The vulnerability is successfully exploited:

PHDays 11 challenge results: none of the participants reached the stage of exploiting this vulnerability, as they were unable to take control of an administrator account.

The unofficial way

This method was suggested by one of the participants after the competition ended—we couldn't help but talk about it. He used a scanner that discovered this vulnerability by injecting the payload into the cookie of one of the requests. As it turned out later, this method worked with any request to the system. We were very surprised that no one found and reported this vulnerability during the competition.

First, let's run the script:

Via Burp Repeater, we send any request that we receive from the frontend. In this case it's /sharedData/admins. We inject payload with our IP address so that the banking service connects to our server launched earlier and we obtain a reverse shell.

It works! We have a reverse shell on the bank backend host 😊

Successful exploitation of the vulnerability:

It should be noted that this method works only once. Apache logs this event with a note that if it repeats it will not be logged again (invalid cookie). Therefore, participants in the competition could use this vulnerability only once after the start of the service.

CSRF vulnerabilities

The online bank was also weak against CSRF attacks. Attentive readers have probably already noticed that in the requests described earlier, CSRF tokens were not used in any way. For example, participants could generate a series of requests to the online bank from any site visited by a user to receive the user's bank card information in his or her name and transfer money from the user's cards to themselves. The system was open to this and many similar attacks.

PHDays 11 challenge results: only one participant reported this security flaw.

Possible attack scenarios, or What's the point of the contest?

By creating the Payment Village at Positive Hack Days, we aimed not only to raise awareness about potential vulnerabilities in banking systems, but also to help both the attackers and defenders understand the possible consequences of cyberattacks on banking systems. How can attackers take advantage of found IDORs in loans, rounding errors in cross-currency transfers, weak password policies, or poorly implemented two-factor authentication? The following are possible attack scenarios:

  • The ability to bruteforce passwords at login allows attackers to get an administrator's password.

  • A business logic vulnerability in OTP allows attackers to guess the one-time password. With this, they can log into an administrator's account. Following that, attackers might not be limited to just removing users, as we described earlier in this article, but, for example, they could use Log4Shell to penetrate the bank's infrastructure.

  • Exploiting the rounding error well, and also automating currency transfers between cards, could make attackers very rich indeed (with our contestants being living proof 🤑).

  • A business logic error in OTP handling and a CSRF vulnerability allow attackers to change credentials during a two-factor authentication attack and link their Telegram account to a legitimate user's personal account without any verification. The victim can't enter their personal account—they have to contact bank representatives for help.


Despite the fact that we've been holding the competition for several years, some of this year's challenges seemed non-standard to the participants, and sometimes not entirely obvious. Many participants were looking for standard web vulnerabilities, without considering the fact that our competition was primarily aimed at demonstrating the vulnerability of banks. We've taken all of this into account and will definitely use it when preparing the contest next year, to make it even more interesting and attract as many participants as possible!

The contest to find vulnerabilities in the online bank is still open, although there are no more awards. In August 2022, the banking platform received two updates adding new vulnerabilities. Now anyone can practice and improve their pentesting skills all year round on our site 😊.

 You can learn more about the Payment Village in Telegram. See you at the next Positive Hack Days forum!

 Author: Telegram: @geralt_iz_rivii

[1] Payment card industry data security standard (PCI DSS) is an industry standard established by the international payment systems Visa, MasterCard, American Express, JCB, and Discover. 




1,001–5,000 employees