Read previous: How to rob an online bank
In this part of the presentation, Mitja Kolsek speaks on direct resource access and the use of negative numbers to trick e-banking systems.
This is one of the top vulnerabilities in all web applications, and online banking is mostly web based. Even if you have a thick client, there is usually HTTP going in the background. Let’s see what can happen here.This is a really lame example. You wouldn’t believe that this would really exist in the real world. So, we have a bank and we have a URL, and the user ID in the URL. So, when I log into the bank and I click on this URL, I get my account balance data. But if I change the ID to something else, I get someone else’s account balance data (see image).
Who doesn’t believe that this actually exists in real world? Well, this really happened in Citibank – this exact thing. How lame is this? So, this is the actual state of security today in some of the largest banks.
But it’s not usually the case. Usually the arguments are Base641 encoded. So, when you do the pen testing and you see this, of course when you see the typical characters of Base64, of course the first thing you do is you decode this. And then when you find something like this, you just Base64 encode something else, and request it – and you get someone else’s account balance (see image). We actually see this in real world.
But the better banks have gone further. They are using URL encryption which is again Base64 encoded, so it’s not a problem per se if it’s encrypted or you have HEX (hexadecimal) encoding. So, whenever you see something like this, the first instinct of a hacker is: “Let’s try to decode this first and see what’s hiding behind.”What’s usually hiding behind is a code like this (see image). We have the same types of params that we used in the previous examples. And then, we are encrypting this with some encryption key, and then we add that to the URL. So the application gives us a link that is encrypted, and when you click on the link, it receives the request and decrypts the link.
It’s much better than what we saw before because we would need to know the key to be able to encrypt something that the server would understand. But it still happens that it’s easy to guess the code if you are really motivated. And the second problem is that some people actually know the key: the people in the bank, the developers, those who have actually selected the key – know the key. So it’s not a really good idea if that guy who knows the key leaves the bank and then still knows the key. He can still get access to other people’s accounts.
The developers have been aware of this problem, and they usually make it a little bit more difficult for you to guess what’s going on in the background, so they add some salt to the original arguments, and they even add something random, which is a good idea. If you have some random data, then it would be more difficult to guess what’s happening in the background if you don’t know the code. But we still have that guy who knows this code and he knows this key.
Doing this is not a good substitute for actual server side authorization of the received request. So whenever you see something like this in the URL, you know there is something fishy going on in the background.
Well, we are not here to steal other people’s data. The bank robber wants to actually steal money, so we can apply this knowledge to stealing money. We have s similar case: we have a transfer URL here, we have source account, destination account, and an amount. This is like what I would do if I wanted to transfer money from my account to someone else’s.
And of course by the same token, if we change the source account, the application does not allow me to do this through the GUI, so I have to do it manually. But if I do this and there is no server side validation or if it can be bypassed, then money can be taken from someone else’s account instead of mine. So, how does this money transaction work? Let’s go a little bit deeper into that. We have the user with the browser, and the online banking server. First of all, the user decides that he wants to make a transfer of his funds, so the server provides an empty transaction form.
So you get an empty transaction form that you can fill out: you select your account, you select the other account, and the amount of money you want to transfer, and a few other data that’s not important here.
The server then sends you a filled-out form just for the confirmation, so that you don’t mistakenly send money to someone else; it allows you to confirm what you decided to do.
Now, there is server side validation in this step. Whenever I provide some data (I want to do this and that with my money), the server validates my data, and looks at it and says: “Okay, this guy is trying to do something, can he do that? Does he have authorization for this account? Is the amount okay?” and so on. And then the server returns that form to me, and when I confirm it, it can validate it again.
Well, the most important validation is the third one because after that’s been done, the server is going to say: “This validation is okay; I am going to send it to back-end.” And the back-end just does everything that the server tells it to do.
A frequent security error here is that the server only does validation number two. So if you change your data in the confirmation form, you can bypass that validation and give the back-end server whatever you want to give.
Next case is negative numbers. Would you believe that you can actually use negative numbers in online banking systems? Yes, you can. Negative numbers are surprisingly often overlooked. Let’s see. This is a typical line of code that does the validation of the amount that we selected. So, we want to transfer 100 EUR, and this is the line of code that says: “Is it okay? Does he have 100 EUR?”
If I want to transfer 3000 EUR and I have only 2000 on my account, this line is going to block my attempt. But if I want to transfer minus 100 EUR, this line will have no problem with that (see image). So checking for negative numbers is very often overlooked, maybe not in the entire online banking application, but if you can find one place where it’s overlooked, you can do some funny things with that.
This is the actual case. The attacker has an empty account balance, the victim has 100 EUR. The attacker transfers minus 100 EUR to the victim – very generous, right? So, the end result is that the attacker has 100 EUR, and the victim has zero. It is the same as the transaction from the victim to the attacker, only triggered by the attacker. This is really cool. This is a very little known problem. If you try to search Google for this specific problem, it’s really not easy to find references. I only found a couple. But in the banking sector, they are very well aware of that after our pen test.
And again, the business logic behind this is whatever the amount to transfer is, this amount will be subtracted from my account and added to his account, so if minus 100 is subtracted from my account, it’s like adding 100 to my account – minus and minus gives you a plus.
Let’s see something that we once came across: a similar case, we are going to transfer minus 100 EUR, but not to another person’s account – we are going to transfer it to our own savings account (see image). Now, savings accounts are just like any other account, but they have a specific property that they cannot have a negative balance, well, at least those that we see.
The advantage to the attacker is that if we want to transfer minus 100 EUR from a normal account to a savings account, this is what we get: we get 100 EUR, but we do not get minus 100 EUR on the savings account because it cannot have a negative value. Now, the business logic said: “That is not okay, I am not going to do that part of the transaction.”
What’s fun with this is that you can actually create new money. Before, we had a total of 0 EUR; now, we have a total of 100 EUR. So there is actually more money in existence because of this failed transaction.
Read next: How to rob an online bank 3: SQL injection
1 – Base64 denotes a group of similar encoding schemes that represent binary data in an ASCII (American Standard Code for Information Interchange) string format by translating it into a radix-64 representation. Base64 is relatively easy to encode/decode.