mirror of
https://we.phorge.it/source/phorge.git
synced 2024-12-26 15:30:58 +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:
parent
7f7710a24d
commit
bfbe6ec594
5 changed files with 76 additions and 34 deletions
|
@ -390,7 +390,9 @@ return array(
|
|||
|
||||
// -- 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,
|
||||
|
||||
// Your Recaptcha public key, obtained from Recaptcha.
|
||||
|
|
|
@ -43,11 +43,33 @@ class PhabricatorLoginController extends PhabricatorAuthController {
|
|||
|
||||
$forms = array();
|
||||
|
||||
$error_view = null;
|
||||
|
||||
$errors = array();
|
||||
if ($password_auth) {
|
||||
$error = false;
|
||||
$require_captcha = false;
|
||||
$e_captcha = true;
|
||||
$username_or_email = $request->getCookie('phusr');
|
||||
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');
|
||||
|
||||
$user = id(new PhabricatorUser())->loadOneWhere(
|
||||
|
@ -60,43 +82,46 @@ class PhabricatorLoginController extends PhabricatorAuthController {
|
|||
$username_or_email);
|
||||
}
|
||||
|
||||
$okay = false;
|
||||
if ($user) {
|
||||
if ($user->comparePassword($request->getStr('password'))) {
|
||||
|
||||
$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();
|
||||
if (!$errors) {
|
||||
// Perform username/password tests only if we didn't get rate limited
|
||||
// by the CAPTCHA.
|
||||
if (!$user || !$user->comparePassword($request->getStr('password'))) {
|
||||
$errors[] = 'Bad username/password.';
|
||||
}
|
||||
}
|
||||
|
||||
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('phsid');
|
||||
}
|
||||
|
||||
$error = true;
|
||||
}
|
||||
|
||||
if ($error) {
|
||||
if ($errors) {
|
||||
$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();
|
||||
|
@ -114,7 +139,15 @@ class PhabricatorLoginController extends PhabricatorAuthController {
|
|||
->setName('password')
|
||||
->setCaption(
|
||||
'<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(
|
||||
id(new AphrontFormSubmitControl())
|
||||
->setValue('Login'));
|
||||
|
|
|
@ -14,6 +14,7 @@ phutil_require_module('phabricator', 'applications/people/storage/user');
|
|||
phutil_require_module('phabricator', 'infrastructure/env');
|
||||
phutil_require_module('phabricator', 'view/form/base');
|
||||
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/text');
|
||||
phutil_require_module('phabricator', 'view/form/error');
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2011 Facebook, Inc.
|
||||
* Copyright 2012 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -53,6 +53,8 @@ class PhabricatorUserLog extends PhabricatorUserDAO {
|
|||
|
||||
if ($user) {
|
||||
$log->setUserPHID($user->getPHID());
|
||||
} else {
|
||||
$log->setUserPHID('');
|
||||
}
|
||||
|
||||
if ($action) {
|
||||
|
|
|
@ -31,7 +31,7 @@ class AphrontFormRecaptchaControl extends AphrontFormControl {
|
|||
return self::isRecaptchaEnabled();
|
||||
}
|
||||
|
||||
private static function isRecaptchaEnabled() {
|
||||
public static function isRecaptchaEnabled() {
|
||||
return PhabricatorEnv::getEnvConfig('recaptcha.enabled');
|
||||
}
|
||||
|
||||
|
@ -40,6 +40,10 @@ class AphrontFormRecaptchaControl extends AphrontFormControl {
|
|||
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) {
|
||||
if (!self::isRecaptchaEnabled()) {
|
||||
return true;
|
||||
|
|
Loading…
Reference in a new issue