Throttling means regulating the flow of input to reduce speed or processing. It is a general concept in computing which can be applied to many areas. In terms of security, it is usually applied to throttling of requests to a webserver to prevent Denial of Service Attacks or to throttling attempts to login to a password-protected area to prevent Brute Force Attacks.
In non-computing terms, throttling is most often associated with a car throttle which regulates the amount of air entering an engine in order to increase or decrease combustion and engine output. In computing, throttling is always a slowing down of input. Throttling keeps a system from being overwhelmed, keeps traffic below a maximum level, and slows down malicious activity. Throttling can be performed by hardware, such as [firewalls|Firewalls]), or by software, or application code.
In order throttle requests it is necessary to track them to identify the pattern of abuse. Application code typically tracks failed login attempts either by the username submitted, but the requesting IP address can tracked as an additional protection.
Always store failed attempts in a database table, not in a cookie or session which can be ignored, cleared, or reset by at attacker. Failed attempts should be kept in a separate "failed_logins" table. Do not add a column on a user table to track failed attempts because it provides no way to track failed attempts on non-existent usernames and is likely to lead to User Enumeration. (For example, it could take longer to update the user record than it does to handle non-existent usernames.) The "failed_logins" table should, at a minimum, record the "username", the "count" of failed attempts on the username, and the "last_attempt" time.
After each failed attempt to login, the application code should search "failed_logins" for the requested username. It will update an existing record or create a new record for the username if none exists. The "count" value should be incremented by one for the failed attempt and the "last_attempt" should be updated with a current timestamp.
<?php
function record_failed_login($username) {
// The failure technically already happened, so
// get the time ASAP.
$sql_date = date("Y-m-d H:i:s");
$fl_result = find_failed_login($username);
$failed_login = db_fetch_assoc($fl_result);
if(!$failed_login) {
$failed_login = [
'username' => $username,
'count' => 1,
'last_attempt' => $sql_date
];
insert_failed_login($failed_login);
} else {
$failed_login['count'] = $failed_login['count'] + 1;
$failed_login['last_attempt'] = $sql_date;
update_failed_login($failed_login);
}
return true;
}
?>
A value should be set for the throttle "threshold", the number of failed attempts which will be allowed before throttling is enabled. A threshold of 10 to 20 will have little effect on users who have need a few tries to remember or type their password correctly, but it will be a low enough number for an attacker attempting to Brute Force the password to hit many times.
A value should be set for the "lockout" period, the duration of time which must pass before logins may be attempted again. A lockout period of 5 to 10 minutes is a reasonable, minor inconvenience to users who failed many times trying to remember their password—most users will resort to a "forgot my password" feature. But a 5 to 10 minute lockout will be crippling to a Brute Force Attack.
Another strategy is to set both threshold and lockout to low numbers. For example, a threshold of 1 and a lockout of 10 seconds would mean that any failed login would need to wait 10 seconds before trying again. A user would likely never trigger the lockout while a Brute Force Attack would.
The code for throttling should be activated before the login authorization code. At the beginning of a login attempt, search for the username in the "failed_logins" table. If the username exists, is already over the threshold, and is still within the lockout period, then the login attempt should be rejected immediately. It is acceptable, and probably a good idea, to provide an explanation to users that the account has had too many failed logins and must now wait until the lockout time expires.
If the username does not exist, is not the over the threshold, or is past the lockout period, then the login attempt can proceed. If the username is over the threshold but past the lockout period, then the failed login count can be reset to 0.
This PHP function returns the lockout time remaining or 0 if there is no lockout.
<?php
function throttle_time($username) {
$threshold = 10;
$lockout = 60 * 10; // in seconds
$fl_result = find_failed_login($username);
$failed_login = db_fetch_assoc($fl_result);
if(!isset($failed_login)) { return 0; }
if($failed_login['count'] < $threshold) { return 0; }
$last_attempt = strtotime($failed_login['last_attempt']);
$since_last_attempt = time() - $last_attempt;
$remaining_lockout = $lockout - $since_last_attempt;
if($remaining_lockout < 0) {
reset_failed_login($username);
return 0;
} else {
return $remaining_lockout;
}
}
?>
Last of all, after each successful login, the failure count can be reset to 0.
eBay implemented login throttling similar to the code described above to prevent Brute Force Attacks. Unfortunately it had unintended consequences which opened them up to another type of attack.
A malicious bidder could wait until an auction was about to end. Then, because they knew the usernames of the competing bidders, the malicious bidder could make intentional bad login attempts with the usernames of their competition. The eBay throttling code would lock out the competition for five minutes. The malicious bidder would make a new higher bid in the auction. The competing bidders would be notified via email or text message, but they would be able to login to counter-bid. The malicious bidder would win the auction.
Firewalls can perform throttling on data packets entering a network or a server. For more information see the guide on Firewalls.