In the 12+ years of doing penetration tests against various critical environments, we've seen numerous online banking servers and found all sorts of vulnerabilities in them, including bugs that allowed users to take money from other users' accounts, make unlimited overdrafts on their own accounts, transfer negative amounts to other accounts (effectively sucking other users' money from these accounts) and even - frightening as it may sound - create unlimited amounts of money out of thin air. These types of critical bugs in financial systems are not nearly as rare as one would hope; in fact, our experience shows that at least one such defect exists in your online bank if it hasn't been thoroughly and regularly reviewed by different researchers with a lot of knowledge about banking vulnerabilities and attacks. (As a rule of thumb, if your bank's penetration testing call for proposals contains the word "scanning" or specifies the number of IP addresses to test instead of focusing on application code and logic, their online systems are likely hosting various logical security flaws.)
While such vulnerabilities can allow an online thief to take a lot of money from bank's customers or the bank itself, doing so would positively qualify as a punishable criminal act in most jurisdictions.
Legally Exploitable Security Flaws
There exist, however, other types of logical security flaws in financial systems whose "exploitation" can be perfectly legal. One such flaw is in the way rounding is done in currency exchange and allows users to effectively influence the currency exchange rates to such extent that, for instance, they get 100 EUR (Euro) for 100 USD (US Dollar) even though 100 EUR would normally cost approximately 130 USD.
To our knowledge, this type of flaw was first described in the 2001 paper titled Assymetric Currency Rounding by M'Raïhi, Naccache and Tunstall of Gemplus, and later in a 2008 Corsaire paper Breaking the bank -Vulnerabilities in numeric processing within financial applications. We've been regularly detecting it in online banking systems we've reviewed. Here's how it works.
Currency Exchange 101
Banks usually have two exchange rates for currency exchange: the buying rate is the rate at which the bank will buy the foreign currency, while the selling rate is the rate at which they will sell it. These rates can be expressed either as direct quotations (how many units of local currency equal to one unit of foreign currency) or indirect quotations (how many units of foreign currency equal to one unit of local currency). According to Wikipedia, most countries are using direct quotations, while indirect quotations are used in Euro zone and a few others.
In this article, we'll be using numerals with decimal comma and a point as a thousands separator. For example, one thousand will be written as 1.000,00. (Apologies to the readers accustomed to different notations.)
Suppose a European bank is providing the following indirect quotation for US Dollar exchange rates:
- Buying rate: 1,388 (you pay 1,388 USD for 1,00 EUR)
- Selling rate: 1,364 (you get 1,364 USD for 1,00 EUR)
Rounding
Banks typically operate with two decimal digits and while various calculations (e.g., interests or currency exchange) are done with higher precision, the final results are rounded to two decimal digits. Now, if the bank is fair, this rounding is done to the nearest number, i.e., such that 7,237 is rounded up to 7,24 while 2,221 is rounded down to 2,22. Whether a tie-breaking case such as 1,505 is rounded to 1,50 or 1,51 is of little relevance to this article.
How Much Do I Get For One Cent?
While we've seen that the above exchange rates provide a sensible business model for the bank in a typical case, security researchers are usually more interested in atypical cases, especially various corner cases. One such corner case is the lowest possible value, typically 0,01 - which in many currencies means one cent.
What happens if you want to convert one Euro cent to US Dollars? The European online bank will use the selling rate:
0,01 EUR * 1,364 USD/EUR = 0,01364 USD
After rounding, you will get 0,01 USD. Obviously you'll make a loss (a Dollar cent is worth less than a Euro cent in our example), which nominally doesn't look significant, but is actually a 27% loss.
But what if you reverse the direction and convert one USD cent to EUR? This time, the buying rate is used:
0,01 USD / 1,388 USD/EUR = 0,0072046109510086455331412103746398 EUR
After rounding, you get 0,01 EUR. Now this is better: the rounding just made you an instant profit of 38,8%. Of course, the profit is nominally tiny and probably won't get you a cup of coffee anywhere in the world, but this procedure can be executed repeatedly in a fully automated way. By doing so a hundred times, 1 whole US Dollar can be exchanged for one whole Euro. A hundred thousand times, and one gets 1.000,00 EUR for 1.000,00 USD (a profit of 280,00 EUR). Even if one has to first buy the 1.000,00 USD at the same bank (at bank's selling rate) for 733,14 EUR, the total profit equals to 266,86 EUR. And this profit is from a legitimate use of service provided by the bank on their terms and in their controlled environment.
But How Many Cents Can I Sell?
Suppose an online bank can accept a hundred requests per second (a highly conservative assumption - large banks have to support thousands of requests per second in peak periods), an automated script can make 100*60*60*24 = 8.640.000 one-cent exchange requests per day, making a profit of about 23.000 EUR every 24 hours if this online bank provides currency exchange services around the clock.
Many online banks allow users to upload prepared packages with multiple requests, possibly thousands of them, which the server will then process in a batch. Obviously, this can further expedite the procedure.
Another Example: Japanese Yen
Our above example used two currencies of a similar value. The same method works with any two currencies, for instance with Euro and Japanese Yen (JPY). Suppose the bank's buying rate for Yen is 97,5949. Therefore, exchanging Yens to Euro would work like this:
0,50 JPY / 97,5949 JPY/EUR = 0,0051232185288370601332651603721096
After rounding, you get 0,01 EUR (a profit of over 95%).
Maximizing The Yield
By now it is clear that the profit comes from rounding errors. When working with two decimal digits, the maximum rounding error can be 0,005 for any individual operation. The closer the converted value is to 0,005 (but above it), the higher the profit will be when the number is rounded to 0,01 - and it is easier to maximize the profit using currencies with larger exchange rates (see the Yen example above), as they allow for a better "fine tuning" when trying to get as close to 0,005 as possible.
This also means that a single exchange operation can, at best, produce a profit of 0,005 units of the target currency, so obtaining a non-negligible nominal profit would certainly require hundreds of thousands of operations.
Is This Really Legal?
The essential property of this "exploitation" is that you're really not doing anything to bypass any security mechanisms the bank has put in place; you're simply using the provided functionality the way it was intended, and it is inconceivable that exchanging 1.00 USD would be legal, and exchanging 0.02 USD would also be legal, but entering 0.01 in the same form field would not be.
Note that the legality of this can change significantly if one has to actively "help" the system to accept the low amount. Even if there is just a client-side JavaScript validation in place preventing entering of too small values, which we all know can be easily bypassed, the law (at least European law) could interpret such bypassing as illegal.
Another catch may also be in the Terms and Conditions that a particular bank sets for their users. These could include provisions for annulling certain transactions, e.g., transactions by individual users not done manually through the intended user interface or erroneous transactions due to logical errors in the code.
We've had many conversations with our banking customers who were affected by this logical flaw, some of them losing more than 100.000 EUR or USD, and their final conclusion was always that they didn't have legal grounds for persecuting the "attacker", and would have to just swallow the loss and add some countermeasures to the code to prevent it from happening again.
Countermeasures
After becoming aware of this vulnerability, banks can employ various countermeasures to eliminate it:
- Charging a conversion fee. This is the simplest countermeasure, and even if the fee is really tiny (e.g., 0.01 EUR), it quickly takes away all the profit provided by the rounding.
- Setting a minimum conversion amount. Much like most physical exchange bureaus refuse to accept small change, an online currency exchange can refuse to exchange anything less than one unit of the "larger" currency - say, 1 EUR in the above examples. Note that exchanging an amount larger than 1 EUR can still provide up to 0,005 EUR profit from rounding error but this profit is more than neutralized by the difference between buying and selling rates.
- Always rounding to bank's benefit: While arguably unfair - and possibly disallowed by local banking regulations -, this is also an effective countermeasure.
- Limiting the number of operations per user: Obviously, harvesting a significant profit requires hundreds of thousands of exchange operations. An online bank can limit the allowed number of exchange operations for any individual user to, say, a thousand per day, without causing any problems to users.
Are There Any Other Legally Exploitable Security Flaws?
There are many. We decided to publish a blog post about this one because it's been publicly known for a decade, is being actively exploited, and we keep finding it in online banks we're reviewing. Many other flaws are not publicly known and it would be a disservice to our existing and future customers to reveal them.
Final Thoughts
This flaw is a great example of how things can go wrong when you take a simple, well-specified physical process such as manual currency exchange - and turn it into an online service. This particular case introduced two critical changes: first, the online service allows hundreds of thousands of operations in a short period of time, while a physical exchange office could probably not make more than two exchanges per minute. And second, the online system accepts the smallest of small change and doesn't find it either suspect or annoying that someone wants to exchange one cent over and over again - it just dutifully executes its code.
Actually, having looked at many different logical flaws we find in online banking systems, a lot of them exist only because the transformation of processes from the physical banking world into the complexity of an online application introduced many new and unwanted possibilities that would have been alarming, suspect or at least unacceptably irritating to the people involved in the same physical process.
And they say people are the weakest link in security.