In this post as part of our QA Consulting and Software Testing series, we’ll go through the basics of web app security. Don’t use the name part of your email as an app sign-in username, if your email is everywhere. Don’t make selfies with a password written on a whiteboard in the background. And don’t implement Captcha unless you absolutely have to!
In our new Security 101 post (check out the previous post about Threat Modeling) we’ll touch upon the topic of brute force attacks and ways to defend against them. This type of attack is unfortunately common, but, thankfully, there are countermeasures that are relatively easy to take, though some of them often get either overlooked or overhyped.
In most cases, a username and password are required to access the app. If an attacker doesn’t have any valid credentials, either obtained from leaked database or stolen in some other way, for example, with phishing email, a brute force attack is what would likely be used. The goal of the attack is to find at least one valid pair of a username and password.
Sometimes, an attacker can obtain a valid password just by researching publicly available info on the company and watching closely. There are cases of a password been written down on the post-it note at the side of employee’s monitor or at the whiteboard in the conference room, which then pop up in a picture or a company video. Knowing only the password, but not the username, the attacker would perform a reverse brute force attack, seeking matching username for the password.
But quite frequently, the attacker will brute force passwords because usernames are easier to acquire – they’re either public (like a nickname on a forum) or can be acquired relatively easy in other ways.
The pool of login candidates can be easily created by browsing the company’s web pages. An intruder will look at different website’s sections, searching for people’s emails or names. It’s very probable that their logins would turn out to be the same as email addresses or contain letters or acronyms from names.
Files like photos or office documents very often contain additional information stored as their file properties. This information, called metadata, could include personal data, sometimes containing someone’s login.
Obtaining such kind of files may not be particularly hard if an attacked website has a photo gallery or a download section with white papers or catalogs or any other downloadable files with compromising metadata still present in them.
Even if no such section is available for public access from the main page, there’s still a chance that some of metadata-rich files are publicly hosted under your domain. If that is the case, then it’s possible that this content was indexed by a search engine. A Google search query that looks for Word files at a given domain can look like this: site:example.com filetype:docx
After getting the list of possible usernames, the attacker would need to find out which ones are valid. One of the options for doing that is to use the login page. It is important to implement the login form in a way that doesn’t specify whether login or password is invalid (“Wrong username or password”), giving no hint to an attacker which of them is actually incorrect. It’s also worth noting that it’s not only a matter of a message presented to end user. If there’s a back-end API that performs a sign in, make sure that there’s no difference not only in response’s body, but also in its status and headers.
After making sure that the login form can’t be used to verify the username, also check the recovery form (“I forgot my password” option). Here the situation is a bit trickier, and a generic message like “If there’s such user we’ve send him an email” might not be enough. In a so called blind attack it’s not the response’s body or status what is taken into consideration, but the amount of time it takes for the message to be displayed. An attacker would expect nearly immediate response to an invalid username.
On the other hand, a valid username would prompt a server to actually send a recovery email, and the response message would get slightly delayed. To prevent this the action has to be asynchronous, with an email sent in a background thread.
The original idea behind the family of brute force attacks is that the whole spectrum of possible values should be checked. This means password candidates are generated letter by letter, iterating over the set of allowed characters. It takes a lot of time, more than attacker would be ok with, so it is likely that a dictionary attack would be used. There are a lot of password dictionaries available. Some of them rely on users’ habit to use simple passwords, some them come from database leaks of real services.
A brute force attack usually is not performed manually, and most likely some automation tool would be used. There’s plenty of them out there, and they’re pretty simple to use, down to providing a file with password candidates, application’s URL and choosing the authentication method. Take, for example, THC-Hydra – this penetration testing tool is able to brute force not only HTTP login forms but also a great number of other interfaces secured by login/password: SSH, HTTP basic auth, databases etc.
Of course, before we get to fight a brute force attack we must first detect it. Make sure that your application has tools for monitoring network traffic. At least two things would be helpful: metrics and logs.
-The HTTP metrics should be detailed enough to determine the URL and method of each incoming request, status and number of produced responses, etc. Then special alerts might be created in monitoring tools that will inform you about any suspicious behavior (like increased number of 401 responses for the signing in endpoint).
-Logs will provide more detailed information about each request that cannot always be collected and presented as metrics. It can contain data like request headers, source IP, request body etc. Analyzing them could help understand the details of the attack and come up with defense tactic.
First thing that can come to mind after detecting an attack is to block an attacker’s IP address. But that’s not a very good way of deterring an attack, because:
Another idea might be to block an account with too much failed login attempts. A special action would then be required from user in order to unlock it.
This is a risky approach. An attacker could make numerous attempts with a lot of valid usernames, and you’ll end up blocking a good portion of your users. Most of them will probably unblock their accounts the first time it happens. But will they do that the second time and then again and again? Eventually this may turn users away from your app and that’s obviously not what we need.
A lighter version of this solution is to lock an account temporarily, with a response like “You entered your password incorrectly for a few times in a row. Try again in 30 seconds”. In this case the account owner doesn’t need to do anything really aside from waiting a bit. But a persistent attacker could likely trigger the temporary locking again and again, stripping the actual user of the ability to sign in.
CAPTCHA stands for Completely Automated Public Turing Test to Tell Computers and Humans Apart. No matter how it is implemented, a notorious “proof that you’re not a robot” request is always irritating. Quite often it’s not convenient to resolve CAPTCHA on desktop and sometimes on mobile versions it could suddenly turn into usability wall for a user. In applications that prioritize user friendliness, CAPTCHA should be considered a last resort.
Moreover, many CAPTCHA implementations are vulnerable to attacks – either automated, that utilize OCR or neural networks, or social engineering-type, in which large groups of people are paid (or lured) to solve CAPTCHA puzzles in real time.
If you want to (or must) add CAPTCHA to the login page, consider the following:
During brute force attack an attacker will try out many password options. Introducing a delay between failed login attempts would significantly slow the process down, probably rendering the whole deal too time-consuming for an attacker. That additional latency won’t bother real users, for who will likely not mind waiting a few extra seconds is they’ve made a mistake typing in a password. For longer periods, like 10 seconds, consider showing user some kind of a countdown timer.
The secret question and a corresponding answer are configured in user profile. Present that question for logins with too many failed login attempts, expecting the user to provide a valid answer. Make sure to ask such question also for invalid logins, so the attacker won’t have another way of discovering real (existing) accounts.
In a brute force attack some of the penetration testing tools might be used, like the above mentioned THC-Hydra. Those programs send requests with User-Agent header set to a default value, a tell-tale sign of the attack tool.
By randomly returning the 200 status response for requests with such header, an application can fool the attacker that will no longer be able to distinguish between correct and failed attempts. It’ll work on amateur hackers who don’t know how to modify Hydra’s request headers. Note this isn’t a fully secure solution as we can’t always rely on request headers.
To be continued…