Posted on: January 12, 2018Updated: January 13, 2018
How to survive against online cracking tools?
Today, I want to talk about why one needs to be careful when it comes to the login application, why to avoid using outdated hashing methods when authenticating and how to protect against online dictionary and brute force attacks. But sometimes, it's better to look at it from different perspective to get a better picture. So for this discussion, I have designed a python tool that attacks login apps to get the password of the username provided. To help you understand what is going on, I'll test my own two login apps and show you examples of results based on different situations.
The tool against configuration and hashing methods
So to get started, we'll attack the login app that uses cookies to ban a user after a few failed attempts. You can download the login script here. Note that to demonstrate the effects of different hashing methods, I'll be modifying the authentication design of the script. But first, let's answer the question why to avoid using cookies to ban a user whenever you can in the first place?
It is because a script can be made that requests new session cookies for each attempt in the loop and completely bypasses the cookie responsible for banning. Here is the code from the login script with changed $user and $pass variables and a simpler hashing method:
<?php
session_start() ;
$user = '[email protected]' ;
$pass = '9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08' ;
if (isset ($_POST['auth' ])) {
if ($_POST['username' ] === $user && hash('sha256' , $_POST['password' ]) === $pass ) {
session_regenerate_id() ;
$_SESSION['userlog' ] = $_POST['username' ];
$_SESSION['agent' ] = hash('sha256' , $_SERVER['HTTP_USER_AGENT' ]);
}
if ($_SESSION['userlog' ] !== $_POST['username' ]) {$incorrect = "Your login combination is incorrect! Please try again..." ;}
if (isset ($_SESSION['counter' ])) {
$_SESSION['counter' ]++;
if ($_SESSION['counter' ] >= 4 ) {setcookie( 'banned' , 1 , time() +60 *10 ) ;}
}
else {$_SESSION['counter' ] = 1 ;}
}
?>
You can see how it sets the cookie banned after 4 attempts and can test this via browser trying to login with false information. It will ban you. The same result is to be expected, if you used the following script.
#Python example
session = requests.session()
for key in keys:
payload = {'username' :user, 'password' :key}
attempt = session.post(url, data=payload)
Let's test if this is true:
Works like a charm. The cookie was only requested once and then attempts were initiated under the same cookie which triggered the ban. However, if we pass the requests.session() inside the loop, it will request a new cookie each time an attempt is made and completely bypass the ban restriction. So after modifying the python tool, I'll fill it with exactly the same input(url, username and path to the dictionary file).
Exactly like predicted. Also, did you notice how fast that was. It made 278 attempts in one and a half second! (on 2.67GHZ CPU) This is because SHA256 provides zero-to-none computing cost protection. What about other better hashing methods? One of the most default choices these days are PBKDF2 and BCRYPT but quite too often the default setting of iteration cost is set too low. For example, if we try PBKDF2 with SHA512, 10k rounds and with the same credentials.
$user = '[email protected]' ;
$pass = '896c11f72d5c28a04e96731bbda252c27d7cc7803eea60c05b17ee6bdfc65982' ;
$rounds = '10000' ;
if ($_POST['username' ] === $user && hash_pbkdf2('sha512' , $_POST['password' ], $user , $rounds , '64' ) === $pass ) {
It's now at 13.35 seconds. Keep in mind that the gap in time increases at the amounts of passwords tried so this is much better but still not enough. So let's try with 30k rounds, same method, same credentials.
$pass = '9e03da14ad54c399ee1a7be1c701b735c53c0bfe03263348077f2a61a11da02a' ;
$rounds = '30000' ;
This is better, for 2780 attempts you would need 5 minutes and 38 seconds (same machine). So if you are on a decent server that keep tabs on traffic, someone should have noticed by now. Or not. I would say it depends. In any case, the damage might have already been done in that time although very unlikely but you can never tell and as a developer myself, I would never take chances.
If I'm forced to use this or similar type of login script, I would most definitely restrict access to the admin because you shouldn't use such scripts for public login application, ever. Remember that this script is more or less inteded for coffee shops and schools that use the same LAN as customers, to edit their pages with limited information such as editing menus, schedule and similar stuff, nothing too important. So you can basically restrict it to one external IP address and only people on the same LAN, using the IP, will be able to access the login page. To protect such logins, I would use BCRYPT which is a slow hashing method, especially in a combination with high iteration cost. Alternatively, you may still use PBKDF2 but set the rounds close or over 100k rounds. It would make cracking too time consuming and most likely sway away the attacker. Again though, remember that here information or potential access is not too valuable.
Anyway, for the purpose of the discussion I'll test also the default settings(BCRYPT cost 11) of the script. This time I'll uncomment the HTML comparison in the python tool so you'll be able to see how it knows that the password was found:
$user = '[email protected]' ;
$passhash = '$2y$11$piYx3BIPMKzhSaEXJJRseO/9ydQTKoiCLgijgnuSk6dCMbN.cdYLa' ;
if ($_POST['username' ] === $user && password_verify($_POST['password' ], $passhash )) {
First of all, 44 seconds is not bad but you would have always liked more. Depends on your needs really. A big site with thousands of regular users also needs to worry about performance quality. Each authentication is a process that needs to be processed which, in large amounts at the same time, can be heavy on the machine. Secondly, now you can see what it compares to find a password. If the source code changes after the post request, then it's a match. However, at the same time if it redirected to 404 page or any other page, this wouldn't work. Similarily if the HTML hadn't changed, at least the first 150 chars of the output, the script would just go on. This can of course be modified easily to capture larger portions of the code to identify what's what. Similarily, the 404 page could be ignored with something like:
#Python example
if HTMLbefore[0:150] != HTMLafter[0:150] and '404 not found' not in HTMLafter:
#Password found
else :
#continue
Note that this can also capture when you get banned but there is another condition that predicts when the ban usually happens. It is from 3 to 10 attempts. So if the HTML output changes when the attempt index is between those numbers, then it's assuming that the IP was banned or the user was locked. And to prove the theory, let's look at the mysql driven login system with attempts counter measure.
We can now confirm that the HTML output result and the prediction were right. If it acutally found the correct password in the questioned range, there is a HTML output to confirm it. Also note that if you ran the tool just after you were banned, its HTML output would be the same at the end and it would say banned and password not found. That's why there is a link below to confirm it manually.
In some cases, however, there is a client or server side condition on the login application which won't redirect you to ban page but will only display the ban message upon trying to authenticate. In such case, as mentioned above already, the script would just go on because there will be no changes in the HTML output, at least not in the first 150 characters. But this too can probably be circumvented with enough imagination.
How to protect your login application?
Well the beauty and the curse at the same time about this tool is that, beside HTML input names for login form, it doesn't need any information beforehand. It doesn't need to know the hashing method or the iteration rounds or the password hash itself. Not to mention that static salts hidden somewhere on the server are completely pointless in this case. It is the most direct approach to extract sensitive information. It's true that it's quite noisy but it can perform the job given it has enough time to operate.
Locking the user after a few failed attempts may be an option but not the best choice because that would enable DoS attacks on users. Banning IP on the other hand is better but it would have to done in a way that after each ban the period of the ban increases so that the potnetial attacker can't automate a script to resume its work after ban in hope of success. Even if the script changes IP after a few attempts, no one has so many relays to successfuly cycle through them to crack anything, especially in a combination with high iteration cost. This option, however, would cause banning of multiple people behind a proxy or VPN even if they were not the perpetrator. Same for people on the same LAN that uses one external IP address. Another problem are IPv6 addresses, similarily a lot of innocent people in the region could get banned because of one perpetrator.
So how to protect your app? This is still an ongoing debate over the internet because there can be no satisfaction for all the users. In my opinion, it's best to have some sort of lockout feature and good high-cost hashing method. You could also check for a username and IP and ban the IP just in a combination with that user but once again, this opens a new door for explotation. A good choice is also to monitor traffic and check for high amount of authentication post requests in a short time but, again, for large sites, this is something normal. Anyhow you want to put it, at the end of the day you do have to choose one of the options or a combination of options or be exposed to black-hat minded people. I'm just saying that if this tool or similar, better, off-the-shelf, tools can run normally on your login app, then your app is not safe.
There may be, however, a solution to the problem that the big companies use. It comes in the form of captcha condition which is triggered after several failed attempts, so that a user now also has to, beside entering login credentials, solve the captcha manually to authenticate. This way it won't lock out the user or ban the IP of the user but it will prevent a tool, such as Invad3r, from operating properly because a script cannot solve a well designed captcha system.
The tool is explained here and in the code itself are further comments but if you have any more questions, please feel free to ask.