Description
What's the problem this feature will solve?
Currently, there's a rate limiter on 2FA to avoid abuse, but an attacker patient enough can hope to correctly guess the code (>50% chance) in 3 months. There's no limit on the maximum number of failed tries before a protection measure is added. Having a 6 digits code can only be a thing if you can't bruteforce it, otherwise it's just a very weak code.
Describe the solution you'd like
A sufficient (to determine) number of failed 2FA tries should raise a protective measure (to determine).
Additional context
I reported the problem via the official Security mailing list back in March 2020, but (AFAICT) the security team didn't have the time to handle it in a private patch. I recently got the confirmation from @di that it was OK to post it publicly, and develop the patch publicly. I might be interested in helping and/or coding but I'm currently involved in #7124 and I'd much rather prefer to finish the big works before starting on another one. So feel free to grab the issue and improve PyPI's security :)
Here's the original report as sent to security@ in March:
This one may be harder than usual to reproduce but, here I go :)
Again, not sure this qualifies as an important security problem, but you're the judge.Looking at the TOTP code in warehouse, I noticed it uses an anti-bruteforce policy which is a moving window of (unless I'm wrong) 10 attempts per 5 minutes. Each attempt counts for 3 because the TOTP validates with 3 30-second windows: the current one, the previous one and the next one, and we can fit 10*3 independent 30-second windows in 5 minutes. I'll be using 9 attempts below rather than 10 so that the victim won't notice if they try to login too.
As it stands, if I have someone's credentials, I have more than 50% chance of success bypassing TOTP without raising any alarm by quietly having a script run 9 tries every 5 minutes for 3 months. In 1 year time, I have >95% chances of success. [1]
So, I know 3 months is not, like, 3 minutes, but we're clearly not looking at a case of "3000 years" nor "3 months with access to a supercomputer", so that might be pulled off easily (assuming user doesn't change their password in 3 months, but it's not very long in terms of how often we should do it.). Also, it's just 50% chance, flip of a coin. Also, it assumes that the attacker has working credentials in the first place (but that's the whole point of 2FA). And also, I wasn't able to try this one out, obviously, I didn't sit on it for 3 months to check if it was correct :) [Note: I haven't tried since]
The HOTP RFC (which is the basis for the TOTP RFC) has a paragraph on throttling (https://tools.ietf.org/html/rfc4226#section-7.3) which says sadly not so much, but suggests having something break after too many tries. They also mention a timeout growing linearly (so the total time would become quadratic), that would render this attack unfeasible, but it would also make it very strange for the intended user who tries to connect and is told they need to try again in 4 days :/
All in all, I think if TOTP was failed more than 10 or 20 times in a row on my account, I'd be much more comfortable knowing my account password was reset.
(Also, did you notice: 1 login going through password and TOTP decreases the rate limit by 2 for global (one for login, one for 2FA) and 2 for user (same), which means either the margin on the rate limit was high enough that dividing it by 2 changes nothing, or users get rate limited more often with 2FA on ?)
[1]: Formula: if 0<x<1 is the probability you're aiming then the number of months to bypass 2FA with a probability >=x with 27 tries every 5 minutes is given by:
(math.log(1-x)/(math.log(1-1/1000000) * 27/5)) /60/24/30
(If my math is wrong, I'll happily stand corrected)