1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2025-02-18 09:48:39 +01:00

Prevent login brute forcing with captchas

Summary: If a remote address has too many recent login failures, require they
fill out a captcha before they can attempt to login.

Test Plan: Tried to login a bunch of times, then submitted the CAPTHCA form with
various combinations of valid/invalid passwords and valid/invalid captchas.

Reviewers: btrahan, jungejason

Reviewed By: jungejason

CC: aran, epriestley, jungejason

Maniphest Tasks: T765

Differential Revision: https://secure.phabricator.com/D1379
This commit is contained in:
epriestley 2012-01-12 12:56:11 -08:00
parent 7f7710a24d
commit bfbe6ec594
5 changed files with 76 additions and 34 deletions

View file

@ -390,7 +390,9 @@ return array(
// -- Recaptcha ------------------------------------------------------------- // // -- Recaptcha ------------------------------------------------------------- //
// Is Recaptcha enabled? If disabled, captchas will not appear. // Is Recaptcha enabled? If disabled, captchas will not appear. You should
// enable Recaptcha if your install is public-facing, as it hinders
// brute-force attacks.
'recaptcha.enabled' => false, 'recaptcha.enabled' => false,
// Your Recaptcha public key, obtained from Recaptcha. // Your Recaptcha public key, obtained from Recaptcha.

View file

@ -43,11 +43,33 @@ class PhabricatorLoginController extends PhabricatorAuthController {
$forms = array(); $forms = array();
$error_view = null;
$errors = array();
if ($password_auth) { if ($password_auth) {
$error = false; $require_captcha = false;
$e_captcha = true;
$username_or_email = $request->getCookie('phusr'); $username_or_email = $request->getCookie('phusr');
if ($request->isFormPost()) { if ($request->isFormPost()) {
if (AphrontFormRecaptchaControl::isRecaptchaEnabled()) {
$failed_attempts = PhabricatorUserLog::loadRecentEventsFromThisIP(
PhabricatorUserLog::ACTION_LOGIN_FAILURE,
60 * 15);
if (count($failed_attempts) > 5) {
$require_captcha = true;
if (!AphrontFormRecaptchaControl::processCaptcha($request)) {
if (AphrontFormRecaptchaControl::hasCaptchaResponse($request)) {
$e_captcha = 'Invalid';
$errors[] = 'CAPTCHA was not entered correctly.';
} else {
$e_captcha = 'Required';
$errors[] = 'Too many login failures recently. You must '.
'submit a CAPTCHA with your login request.';
}
}
}
}
$username_or_email = $request->getStr('username_or_email'); $username_or_email = $request->getStr('username_or_email');
$user = id(new PhabricatorUser())->loadOneWhere( $user = id(new PhabricatorUser())->loadOneWhere(
@ -60,43 +82,46 @@ class PhabricatorLoginController extends PhabricatorAuthController {
$username_or_email); $username_or_email);
} }
$okay = false; if (!$errors) {
if ($user) { // Perform username/password tests only if we didn't get rate limited
if ($user->comparePassword($request->getStr('password'))) { // by the CAPTCHA.
if (!$user || !$user->comparePassword($request->getStr('password'))) {
$session_key = $user->establishSession('web'); $errors[] = 'Bad username/password.';
$request->setCookie('phusr', $user->getUsername());
$request->setCookie('phsid', $session_key);
$uri = new PhutilURI('/login/validate/');
$uri->setQueryParams(
array(
'phusr' => $user->getUsername(),
));
return id(new AphrontRedirectResponse())
->setURI((string)$uri);
} else {
$log = PhabricatorUserLog::newLog(
null,
$user,
PhabricatorUserLog::ACTION_LOGIN_FAILURE);
$log->save();
} }
} }
if (!$okay) { if (!$errors) {
$session_key = $user->establishSession('web');
$request->setCookie('phusr', $user->getUsername());
$request->setCookie('phsid', $session_key);
$uri = new PhutilURI('/login/validate/');
$uri->setQueryParams(
array(
'phusr' => $user->getUsername(),
));
return id(new AphrontRedirectResponse())
->setURI((string)$uri);
} else {
$log = PhabricatorUserLog::newLog(
null,
$user,
PhabricatorUserLog::ACTION_LOGIN_FAILURE);
$log->save();
$request->clearCookie('phusr'); $request->clearCookie('phusr');
$request->clearCookie('phsid'); $request->clearCookie('phsid');
} }
$error = true;
} }
if ($error) { if ($errors) {
$error_view = new AphrontErrorView(); $error_view = new AphrontErrorView();
$error_view->setTitle('Bad username/password.'); $error_view->setTitle('Login Failed');
$error_view->setErrors($errors);
} else {
$error_view = null;
} }
$form = new AphrontFormView(); $form = new AphrontFormView();
@ -114,7 +139,15 @@ class PhabricatorLoginController extends PhabricatorAuthController {
->setName('password') ->setName('password')
->setCaption( ->setCaption(
'<a href="/login/email/">'. '<a href="/login/email/">'.
'Forgot your password? / Email Login</a>')) 'Forgot your password? / Email Login</a>'));
if ($require_captcha) {
$form->appendChild(
id(new AphrontFormRecaptchaControl())
->setError($e_captcha));
}
$form
->appendChild( ->appendChild(
id(new AphrontFormSubmitControl()) id(new AphrontFormSubmitControl())
->setValue('Login')); ->setValue('Login'));

View file

@ -14,6 +14,7 @@ phutil_require_module('phabricator', 'applications/people/storage/user');
phutil_require_module('phabricator', 'infrastructure/env'); phutil_require_module('phabricator', 'infrastructure/env');
phutil_require_module('phabricator', 'view/form/base'); phutil_require_module('phabricator', 'view/form/base');
phutil_require_module('phabricator', 'view/form/control/password'); phutil_require_module('phabricator', 'view/form/control/password');
phutil_require_module('phabricator', 'view/form/control/recaptcha');
phutil_require_module('phabricator', 'view/form/control/submit'); phutil_require_module('phabricator', 'view/form/control/submit');
phutil_require_module('phabricator', 'view/form/control/text'); phutil_require_module('phabricator', 'view/form/control/text');
phutil_require_module('phabricator', 'view/form/error'); phutil_require_module('phabricator', 'view/form/error');

View file

@ -1,7 +1,7 @@
<?php <?php
/* /*
* Copyright 2011 Facebook, Inc. * Copyright 2012 Facebook, Inc.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -53,6 +53,8 @@ class PhabricatorUserLog extends PhabricatorUserDAO {
if ($user) { if ($user) {
$log->setUserPHID($user->getPHID()); $log->setUserPHID($user->getPHID());
} else {
$log->setUserPHID('');
} }
if ($action) { if ($action) {

View file

@ -31,7 +31,7 @@ class AphrontFormRecaptchaControl extends AphrontFormControl {
return self::isRecaptchaEnabled(); return self::isRecaptchaEnabled();
} }
private static function isRecaptchaEnabled() { public static function isRecaptchaEnabled() {
return PhabricatorEnv::getEnvConfig('recaptcha.enabled'); return PhabricatorEnv::getEnvConfig('recaptcha.enabled');
} }
@ -40,6 +40,10 @@ class AphrontFormRecaptchaControl extends AphrontFormControl {
require_once dirname($root).'/externals/recaptcha/recaptchalib.php'; require_once dirname($root).'/externals/recaptcha/recaptchalib.php';
} }
public static function hasCaptchaResponse(AphrontRequest $request) {
return $request->getBool('recaptcha_response_field');
}
public static function processCaptcha(AphrontRequest $request) { public static function processCaptcha(AphrontRequest $request) {
if (!self::isRecaptchaEnabled()) { if (!self::isRecaptchaEnabled()) {
return true; return true;