@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Put a hard limit on password login attempts from the same remote address

Summary:
Ref T13222. Currently, if a remote address fails a few login attempts (5) in a short period of time (15 minutes) we require a CAPTCHA for each additional attempt.

This relies on:

- Administrators configuring ReCAPTCHA, which they may just not bother with.
- Administrators being comfortable with Google running arbitrary trusted Javascript, which they may not be comfortable with.
- ReCAPTCHA actually being effective, which seems likely true for unsophisticated attackers but perhaps less true for more sophisticated attackers (see <https://github.com/ecthros/uncaptcha2>, for example).

(For unsophisticated attackers and researchers, "Rumola" has been the standard CAPTCHA bypass tool for some time. This is an extension that pays humans to solve CAPTCHAs for you. This is not practical at "brute force a strong password" scale. Google appears to have removed it from the Chrome store. The "submit the captcha back to Google's APIs" trick probably isn't practical at brute-force-scale either, but it's easier to imagine weaponizing that than weaponizing human solvers.)

Add a hard gate behind the CAPTHCA wall so that we fail into a secure state if there's no CAPTCHA or the attacker can defeat CAPTCHAs at a very low cost.

The big downside to this is that an attacker who controls your remote address (e.g., is behind the same NAT device you're behind on corpnet) can lock you out of your account. However:

- That //should// be a lot of access (although maybe this isn't that high of a barrier in many cases, since compromising a "smart fridge" or "smart water glass" or whatever might be good enough).
- You can still do "Forgot password?" and login via email link, although this may not be obvious.

Test Plan:
- Logged in normally.
- Failed many many login attempts, got hard gated.

Reviewers: amckinley

Reviewed By: amckinley

Maniphest Tasks: T13222

Differential Revision: https://secure.phabricator.com/D19997

+34 -4
+34 -4
src/applications/auth/provider/PhabricatorPasswordAuthProvider.php
··· 255 255 $viewer = $request->getUser(); 256 256 $content_source = PhabricatorContentSource::newFromRequest($request); 257 257 258 + $captcha_limit = 5; 259 + $hard_limit = 32; 260 + $limit_window = phutil_units('15 minutes in seconds'); 261 + 262 + $failed_attempts = PhabricatorUserLog::loadRecentEventsFromThisIP( 263 + PhabricatorUserLog::ACTION_LOGIN_FAILURE, 264 + $limit_window); 265 + 266 + // If the same remote address has submitted several failed login attempts 267 + // recently, require they provide a CAPTCHA response for new attempts. 258 268 $require_captcha = false; 259 269 $captcha_valid = false; 260 270 if (AphrontFormRecaptchaControl::isRecaptchaEnabled()) { 261 - $failed_attempts = PhabricatorUserLog::loadRecentEventsFromThisIP( 262 - PhabricatorUserLog::ACTION_LOGIN_FAILURE, 263 - 60 * 15); 264 - if (count($failed_attempts) > 5) { 271 + if (count($failed_attempts) > $captcha_limit) { 265 272 $require_captcha = true; 266 273 $captcha_valid = AphrontFormRecaptchaControl::processCaptcha($request); 267 274 } 275 + } 276 + 277 + // If the user has submitted quite a few failed login attempts recently, 278 + // give them a hard limit. 279 + if (count($failed_attempts) > $hard_limit) { 280 + $guidance = array(); 281 + 282 + $guidance[] = pht( 283 + 'Your remote address has failed too many login attempts recently. '. 284 + 'Wait a few minutes before trying again.'); 285 + 286 + $guidance[] = pht( 287 + 'If you are unable to log in to your account, you can '. 288 + '[[ /login/email | send a reset link to your email address ]].'); 289 + 290 + $guidance = implode("\n\n", $guidance); 291 + 292 + $dialog = $controller->newDialog() 293 + ->setTitle(pht('Too Many Login Attempts')) 294 + ->appendChild(new PHUIRemarkupView($viewer, $guidance)) 295 + ->addCancelButton('/auth/start/', pht('Wait Patiently')); 296 + 297 + return array(null, $dialog); 268 298 } 269 299 270 300 $response = null;