1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-22 14:52:41 +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 ------------------------------------------------------------- //
// 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.

View file

@ -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'));

View file

@ -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');

View file

@ -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) {

View file

@ -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;