* Add hooks for captcha in main user login form

* Add hook point for detecting and logging login attempts with invalid password
* Add captcha support for triggering a captcha after a bad password attempt. Legit users shouldn't be inconvenienced much, but password-guesser bots will be severely speedbumped.
This commit is contained in:
Brion Vibber 2007-05-07 21:54:06 +00:00
parent e0c76adae7
commit 3d65c3adaf
3 changed files with 101 additions and 1 deletions

View file

@ -12,6 +12,8 @@ $wgConfirmEditMessages['en'] = array(
the box ([[Special:Captcha/help|more info]]):',
'captcha-addurl' => 'Your edit includes new external links. To help protect against automated
spam, please solve the simple sum below and enter the answer in the box ([[Special:Captcha/help|more info]]):',
'captcha-badpass' => 'To help protect against automated password cracking, please solve the simple sum
below and enter the answer in the box ([[Special:Captcha/help|more info]]):',
'captcha-createaccount' => 'To help protect against automated account creation, please solve the simple sum
below and enter the answer in the box ([[Special:Captcha/help|more info]]):',
'captcha-createaccount-fail' => "Incorrect or missing confirmation code.",

View file

@ -79,6 +79,7 @@ $wgCaptchaTriggers['edit'] = false; // Would check on every edit
$wgCaptchaTriggers['create'] = false; // Check on page creation.
$wgCaptchaTriggers['addurl'] = true; // Check on edits that add URLs
$wgCaptchaTriggers['createaccount'] = true; // Special:Userlogin&type=signup
$wgCaptchaTriggers['badlogin'] = true; // Special:Userlogin after failure
/**
* You may wish to apply special rules for captcha triggering on some namespaces.
@ -108,7 +109,7 @@ global $wgCaptchaStorageClass;
$wgCaptchaStorageClass = 'CaptchaSessionStore';
/**
* Number of sections a captcha session should last in the data cache
* Number of seconds a captcha session should last in the data cache
* before expiring when managing through CaptchaCacheStore class.
*
* Default is a half hour.
@ -116,6 +117,18 @@ $wgCaptchaStorageClass = 'CaptchaSessionStore';
global $wgCaptchaSessionExpiration;
$wgCaptchaSessionExpiration = 30 * 60;
/**
* Number of seconds after a bad login that a captcha will be shown to
* that client on the login form to slow down password-guessing bots.
*
* Has no effect if 'badlogin' is disabled in $wgCaptchaTriggers or
* if there is not a caching engine enabled.
*
* Default is five minutes.
*/
global $wgCaptchaBadLoginExpiration;
$wgCaptchaBadLoginExpiration = 5 * 60;
/**
* Allow users who have confirmed their e-mail addresses to post
* URL links without being harassed by the captcha.
@ -162,6 +175,10 @@ function ceSetup() {
$wgHooks['UserCreateForm'][] = array( &$wgCaptcha, 'injectUserCreate' );
$wgHooks['AbortNewAccount'][] = array( &$wgCaptcha, 'confirmUserCreate' );
$wgHooks['LoginBadPass'][] = array( &$wgCaptcha, 'triggerUserLogin' );
$wgHooks['UserLoginForm'][] = array( &$wgCaptcha, 'injectUserLogin' );
$wgHooks['AbortLogin'][] = array( &$wgCaptcha, 'confirmUserLogin' );
}
/**
@ -258,6 +275,66 @@ class SimpleCaptcha {
return true;
}
/**
* Inject a captcha into the user login form after a failed
* password attempt as a speedbump for mass attacks.
* @fixme if multiple thingies insert a header, could break
* @param SimpleTemplate $template
* @return bool true to keep running callbacks
*/
function injectUserLogin( &$template ) {
if( $this->isBadLoginTriggered() ) {
global $wgOut;
$template->set( 'header',
"<div class='captcha'>" .
$wgOut->parse( $this->getMessage( 'badlogin' ) ) .
$this->getForm() .
"</div>\n" );
}
return true;
}
/**
* When a bad login attempt is made, increment an expiring counter
* in the memcache cloud. Later checks for this may trigger a
* captcha display to prevent too many hits from the same place.
* @param User $user
* @param string $password
* @return bool true to keep running callbacks
*/
function triggerUserLogin( $user, $password ) {
global $wgCaptchaTriggers, $wgCaptchaBadLoginExpiration, $wgMemc;
if( $wgCaptchaTriggers['badlogin'] ) {
$key = $this->badLoginKey();
$count = $wgMemc->get( $key );
if( !$count ) {
$wgMemc->add( $key, 0, $wgCaptchaBadLoginExpiration );
}
$count = $wgMemc->incr( $key );
}
return true;
}
/**
* Check if a bad login has already been registered for this
* IP address. If so, require a captcha.
* @return bool
* @access private
*/
function isBadLoginTriggered() {
global $wgMemc;
return intval( $wgMemc->get( $this->badLoginKey() ) ) > 0;
}
/**
* Internal cache key for badlogin checks.
* @return string
* @access private
*/
function badLoginKey() {
return wfMemcKey( 'captcha', 'badlogin', 'ip', wfGetIP() );
}
/**
* Check if the submitted form matches the captcha session data provided
* by the plugin when the form was generated.
@ -436,6 +513,25 @@ class SimpleCaptcha {
}
return true;
}
/**
* Hook for user login form submissions.
* @param User $u
* @param string $message
* @return bool true to continue, false to abort user creation
*/
function confirmUserLogin( $u, $pass, &$retval ) {
if( $this->isBadLoginTriggered() ) {
$this->trigger = "post-badlogin login '" . $u->getName() . "'";
if( !$this->passCaptcha() ) {
$message = wfMsg( 'captcha-badlogin-fail' );
// Emulate a bad-password return to confuse the shit out of attackers
$retval = LoginForm::WRONG_PASS;
return false;
}
}
return true;
}
/**
* Given a required captcha run, test form input for correct

View file

@ -13,6 +13,8 @@ function efFancyCaptchaMessages() {
'en' => array(
'fancycaptcha-addurl' => 'Your edit includes new external links. To help protect against automated
spam, please enter the words that appear below in the box ([[Special:Captcha/help|more info]]):',
'fancycaptcha-badlogin' => 'To help protect against automated password cracking, please enter the words
that appear below in the box ([[Special:Captcha/help|more info]]):',
'fancycaptcha-createaccount' => 'To help protect against automated account creation, please enter the words
that appear below in the box ([[Special:Captcha/help|more info]]):',
'fancycaptcha-create' => 'To create the page, please enter the words that appear below in the box