BitsDevelopmentTeam/bits-server — pull request #21 by esseks (2014-06-11 20:21:30+00:00)
Title: Mitigating DoS, remote password bruteforce and timing attacks

Rationale
=======

Mitigating DoS in verify()
-------------------------------

Located in package `bitsd.server.auth`, `verify()` will validate user credentials. It has been rewritten to mitigate three specific attacks:

1. **timing** the password hash is calculated regardless of the existence of the username, so that the response time is approximately the same whether the username exists or not, mitigating possible timing attacks for revealing valid usernames.
2. **brute force/DoS** BITSd will accept at most one failed login attempt per IP address per second.
3. **brute force/DoS** Whether the username exists or not, each failed login attempt will prevent further attempts for a configurable amount of time, defaulting to 5 seconds. Relevant config property/command line switch is `min_log_retry`

None of the three scenarios was tackled by the current code, which only prevents too many login attempts for _existing_ usernames, _regardless of the remote IP address_. In particular, ignoring the remote IP address might result in a DoS, if one keeps POSTing login attempts with an existing username and an intentionally wrong password, effectively locking out the legitimate account owner.

Protection no. 1 involves a trick: since each password hash stored in the DB is salted with a random value, we need that specific salt to compute the right hash. This implies that the password hash can be calculated only _after_ successfully retrieving user credentials from DB. So, if the user credentials cannot be retrieved (because the username is invalid), a random salt will be generated and a password hash will be calculated and discarded.
Salt generation time is negligible when compared to hash calculation.

LoginAttempt table and disk usage
-------------------------------------------

A `LoginAttempt` DB table has been added to keep track of failed login attempts.

Since each block record is written to disk, a DoS could be caused by generating a lot of `LoginAttempt` records. Thus, I tried to minimize disk usage as much as possible, in three ways:

* Once blocked, further login attempts with the same IP address/username tuple will update the existing `LoginAttempt` record, without creating a new one.
* If an IP address whose temporary block has expired POSTs a request, the expired record will be deleted from `LoginAttempt` table.
* Successful attempts do not generate an insert into `LoginAttempt` (well, not that relevant though)

There is only one scenario left: an attacker can POST login requests for randomly generated usernames and generate a block record for each. DoS protection no. 2 mitigates this attack by limiting the attempts to 1 per second for each IP address, making the process very slow unless the attacker controls a significant number of different IP addresses.

DB Migration
===========

DB migration will be (hopefully) painless. In fact:

* Table `LoginAttempt` will be automatically created on server restart. It can also be added manually and it does not require any initial population.
* Column `User.lastLoginAttempt` will not be used anymore. It can be either deleted or simply ignored.