Mono is building various online banking solutions for years, so comparing our experiences with the work of others comes as a second nature to many of us. However, what I discovered last week in e-banking web application of one of our nation’s biggest banks came as a total surprise. We are constantly reminded to never take security for granted, but in reality, even big players make basic mistakes that can have huge consequences.
If it wasn’t for the Chrome Development Tools that I’ve been using all the time, I guess nothing of this would ever happen. I had plenty of job assignments waiting to be finished, and didn’t have a particular interest in the inner workings of the e-banking application I’ve been using for years. So there I was, freshly logged into my banking account, ready to perform a couple of transactions and log out. However, the reflex of paying attention to what’s happening in the DevTools window kicked in as soon as I noticed console errors and warnings. Production versions in such environments should be clean of client-side issues, right? It turned out that this was the smallest and the most benign issue I stumbled upon that day.
A couple of mouse clicks later, there was this strange GET call in the Network section of the DevTools being repeated all the time.
My bank account number was replaced with “x” for obvious reasons. HRK stands for the local currency, and the last part is obviously a timestamp.
Why strange, you might ask? It could be a perfectly legitimate API call to fetch data that is to be rendered in the browser, and possibly in other (mobile) clients. However, since I was already authenticated, it felt weird that my account number had to be passed around in plain sight - some sort of authentication token would surely be a better choice. Feeling a bit uneasy, I copied this request, together with its headers (right click-Copy as cURL), and pasted it into Postman. The server quickly responded with a big chunk of JSON, holding my complete profile: my account balances, overdrafts, postal and e-mail addresses, phone numbers, credit card limits and spent amounts, average income, interest rates, employer’s information, etc. Now this was a big red flag: why would anyone send complete profile data just to render a tiny fraction of it in the UI? If not for the sake of security, what about performance?
At this point, I was eager to discuss this with my colleagues. Many of them are clients of the same bank, so they were eager to see if we are on to something, as the whole setup just didn’t look right. They offered their account numbers and suggested to try and see what’s going to happen if the “regular” account number in the URL above is replaced with their data. Sure enough, the back-end happily returned a complete profile of another person as soon as it was provided with another account number. We didn’t want to go any further and to test if there are issues related to the POST (write) requests - instead, we quickly reported the issue to the bank, and they managed to resolve it within a couple of days. We got a big thank you and a sense of accomplishment.
Back to basics
Authentication is the process of verifying who you are. Authorization determines whether you should be granted access to a specific resource. In our case, authentication was performed, but no further authorization was done to check if the user should really be allowed to fetch the data. This translates to hundreds of thousands of account profiles, all at the disposal of every arbitrary user. Go figure.
The timing of this discovery was quite significant. Apart from the damage this type of security issue can inflict on a public image of a financial institution, there are other liability concerns. The General Data Protection Regulation (GDPR), a regulation in EU law on data protection and privacy for all individuals within the European Union, will become enforceable on 25 May. It imposes hefty sanctions in a case of infringement or noncompliance - a fine up to 20 million EUR or up to 4% of the annual worldwide turnover of the preceding financial year. If nothing, this will motivate a lot of companies to take a more focused approach to privacy protection.
Security cannot be taken for granted. Automated vulnerability/security/penetration testing and scanning tools will often miss this type of subtle, but significant issues. Code reviews, internal audits (but not just for the sake of compliance with regulatory requirements) and ethical hacking could be more effective in such cases. Here is what you should keep in mind on a practical level:
- make sure both authentication and authorization workflows are thoroughly tested in your applications. Just because you successfully authenticated your users does not mean all the work has been done. This especially applies to API-based scenarios and users coming from different devices and using different authentication and authorization mechanisms.
- do not send unnecessary data to your views, especially if they are rendered on the client side. One model should not service all views, and the same holds for POST requests and issues caused by the object properties that developer does not expect to be set (leading to over-posting or mass assignment attacks).
Did you have similar security issues with online services and how did you discover them? Please send us your thoughts and comments.