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:
parent
7f7710a24d
commit
bfbe6ec594
5 changed files with 76 additions and 34 deletions
|
@ -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.
|
||||||
|
|
|
@ -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'));
|
||||||
|
|
|
@ -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');
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Add table
Reference in a new issue