Developers are expected to write secure applications. Managers often think that making something secure can’t be that hard. It’s the developer’s job, right? So how hard can it be? As we’ll demonstrate in this real-world example, it can be extremely hard. We will be taking a look at how easy it is to implement login security in the wrong way, effectively allowing attackers to easily find correct logins to your application.
When an application checks a password, it is comparing the password that the user enters to a password that is stored in the application. Usually these will be encrypted, but for simplicity’s sake and readability, we will use unencrypted passwords in our examples. The simplest, and seriously flawed, implementation to create a login check is this:
IF ‘password123’ EQUALS ‘tr1ggre’
USER IS LOGGED IN
ELSE
USER IS NOT LOGGED IN
This example is logically correct. It correctly checks the password against the stored password, and will log in the user if the passwords match and will not log the user in otherwise. So what’s wrong with it then?
Developers use programming languages (such as Java, C# or Python) to build applications. These provide many standard functionality for basic problems. One such (extremely basic) problem is comparing two lines of text. This actually happens a lot in applications, and that means that performance is a high priority for such standard functionality. Let’s take a look at how such a text compare works.
Comparing texts is done by comparing each character. If the first characters are equal, check the second letter, then the third and so on. If the characters are not equal, stop, because the texts are not the same. Our password check would thus quickly decide that the two texts are not equal:
IF ‘p’ EQUALS ‘t’
CHECK NEXT LETTER
ELSE
TEXTS ARE NOT EQUAL
In this case, we know after just checking the first letter, the texts can not be equal. So we don’t check any more letters and simply decide that the texts are not equal. If we are comparing two equal texts however, this will be different. Let’s assume we are comparing the word ‘cat’ with the word ‘cat’:
IF ‘c’ EQUALS ‘c’ → Yes, check next character
IF ‘a’ EQUALS ‘a’ → Yes, check next character
IF ‘t’ EQUALS ‘t’ → Yes, check next character
NO MORE CHARACTERS → Words are equal
Notice that this will actually take longer to check than the check we did on the texts that were not equal. And herein lies the problem.
So where exactly is the weakness in this password check? To understand this, it is important to know that each action a computer performs, takes some time. Comparing a character is just about the smallest thing a computer can do, and in most modern computers this takes about 10-50 microseconds per character. To simplify the explanation we will assume the each characters takes 10 microseconds to check.
Let’s see what happens when the hacker checks several passwords. We will use T, for true, to indicate the letters are equal and F, for false, to indicate that letters are unequal. Each character that is checked is indicated by a T if they’re equal, or an F if they’re not. After we find that two characters are not equal or all characters are equal, we are done. We also specify how long the comparison took. So let’s compare some words the hacker might try:
‘password’ EQUALS ‘tr1ggre’ → F → 10 microseconds
‘timer’ EQUALS ‘tr1ggre’ → TF → 20 microseconds
‘travel’ EQUALS ‘tr1ggre’ → TTF → 30 microseconds
You can now see that a pattern is starting to emerge. It’s a bit like playing a game of Mastermind. Each time you guess a character correctly, the time it takes for the application to respond is 10 microseconds longer, even if the password is incorrect. This has major implications for ‘guessing’ passwords.
To fully understand how big of a weakness this is, we first need to understand what a hacker would do if he did not have any information. The only way the hacker would have to guess the password, is to simply try all possible combinations of characters. For this simple example, let’s assume that the user can use only small letters and numbers (in reality, users can use special characters and capital letters as well). This means that each character can be 1 of 36 possible characters (the 10 numbers from 0 to 9, plus the 26 letters in the alphabet). Let;s see how many times the hacker has to guess the password.
For all possible options of one, two and three characters this is already 47.988 options (36 + 1.296 + 46.656). This quickly gets out of control, because for all options with a maximum length of 8 characters, the number grows to 2.901.713.047.668. Even if we would use a computer to try all these options, taking into account checking a character takes 10 microseconds, this would take 231.307.983 seconds, which is roughly 7 years. This is clearly not an option …
To exploit the weakness we need to combine what we’ve found out about the time of comparing texts with our method of checking all passwords. Since we know that every time we guess a character correctly, the time of the check increases, we can now limit the number of guesses we need. For the first character this is not yet of help, since it will take as long to know it’s equal or inequal. But from the second character on, it starts to get interesting:
Basically, every extra character we add only takes us 36 guesses now, because we are sure we have the previous characters guessed correctly. This means we now only have to check 36 x 8 = 288 options instead of the billions of options we had to check earlier. This would take a computer about 3 milliseconds to do!
Creating vulnerabilities (by mistake) is easy, as you by now will surely understand. What you might also have guessed, is that exploiting vulnerabilities is not that easy, as the above example shows. So why care about these things at all?
The issue here is how easy it is to create such a vulnerability. And this is just one example of a vulnerability. There a literally endless ways to introduce vulnerabilities in systems. So even if a vulnerability is hard to find, once they are found they can very quickly be exploited. Most software applications use third party components, which are used by many projects world-wide. When a vulnerability is found in one of those components, it is easy to exploit all those applications.
Once you understand the balance between the ease at which vulnerabilities can be introduced, and the speed with which they can be exploited across the internet, it’s almost impossible not to care.