Los ataques de fuerza bruta para adivinar contraseñas siguen siendo una amenaza real para la seguridad web. Esta guía explica paso a paso cómo proteger el formulario de inicio de sesión en el framework Yii2 activando la validación CAPTCHA automáticamente tras varios intentos fallidos, añadiendo así una capa de defensa robusta a tu aplicación.
¿Por qué es Crucial el CAPTCHA para el Login?
Los ataques de fuerza bruta son un método antiguo pero persistentemente eficaz. Los atacantes usan bots para probar automáticamente miles de combinaciones de usuario y contraseña. Un formulario de login sin protección puede llevar al robo de cuentas de administrador y datos sensibles.
- Bloquea Bots: El CAPTCHA (Prueba de Turing completamente automática y pública para diferenciar ordenadores de humanos) está diseñado para distinguir humanos de robots.
- Aumenta la Dificultad: Activar un CAPTCHA tras varios intentos fallidos aumenta significativamente el esfuerzo requerido por el atacante.
- Estrategia de Defensa en Profundidad: El CAPTCHA es una capa de seguridad adicional vital, que complementa las contraseñas fuertes y la limitación de intentos.
Implementar CAPTCHA de forma condicional (solo cuando es necesario) mantiene una buena experiencia de usuario para los legítimos, a la vez que crea una barrera para bots y atacantes.
Paso 1: Modificar el Modelo LoginForm.php
Primero, añade una propiedad para el CAPTCHA y su lógica de validación al modelo `LoginForm`. La clave es hacer que el CAPTCHA sea obligatorio solo tras un número determinado de intentos fallidos.
<?php
namespace app\models;
use Yii;
use yii\base\Model;
class LoginForm extends Model
{
public $username;
public $password;
public $rememberMe = true;
public $verifyCode; // Nueva propiedad para el CAPTCHA
// ... otro código existente ...
public function rules()
{
return [
// Reglas para usuario y contraseña
[['username', 'password'], 'required'],
['rememberMe', 'boolean'],
['password', 'validatePassword'],
// REGLAS CAPTCHA: Obligatorio solo si loginFailed es true
['verifyCode', 'required', 'when' => function($model) {
return $model->loginFailed;
}, 'message' => 'El código CAPTCHA es obligatorio tras varios intentos fallidos.'],
['verifyCode', 'captcha', 'when' => function($model) {
return $model->loginFailed;
}],
];
}
public function validatePassword($attribute, $params)
{
if (!$this->hasErrors()) {
$user = $this->getUser();
if (!$user || !$user->validatePassword($this->password)) {
// LÓGICA CLAVE: Incrementar el contador de intentos fallidos
$attempts = Yii::$app->session->get('_loginAttempts', 0) + 1;
Yii::$app->session->set('_loginAttempts', $attempts);
$this->addError($attribute, 'Usuario o contraseña incorrectos.');
} else {
// Reiniciar contador si el login es exitoso
Yii::$app->session->remove('_loginAttempts');
}
}
}
// Getter para verificar el estado de fallo
public function getLoginFailed()
{
// El CAPTCHA se activa tras 3 o más fallos
return Yii::$app->session->get('_loginAttempts', 0) >= 3;
}
// ... método getUser() y otros ...
}Explicación del Código Clave:
- La propiedad
$verifyCodealmacena la entrada del CAPTCHA del usuario. - El método
getLoginFailed()verifica la variable de sesión_loginAttempts. Devuelvetruesi los intentos fallidos son ≥ 3. - El parámetro
'when'en las reglas de validación asegura que las reglas paraverifyCodesolo se apliquen cuandologinFailedseatrue. - El contador de intentos fallidos en la sesión se incrementa con cada fallo en la validación de la contraseña. Un inicio de sesión exitoso lo reinicia.
Paso 2: Mostrar CAPTCHA en la Vista Login.php
A continuación, modifica el archivo de la vista de login para renderizar el widget CAPTCHA solo cuando se cumpla la condición $model->loginFailed.
<?php
use yii\helpers\Html;
use yii\bootstrap5\ActiveForm; // o yii\widgets\ActiveForm
/* @var $this yii\web\View */
/* @var $form yii\widgets\ActiveForm */
/* @var $model app\models\LoginForm */
$this->title = 'Iniciar Sesión';
?>
<div class="site-login">
<h1><?= Html::encode($this->title) ?></h1>
<div class="row">
<div class="col-lg-5">
<?php $form = ActiveForm::begin(['id' => 'login-form']); ?>
<?= $form->field($model, 'username')->textInput(['autofocus' => true]) ?>
<?= $form->field($model, 'password')->passwordInput() ?>
<?= $form->field($model, 'rememberMe')->checkbox() ?>
<!-- SECCIÓN CONDICIONAL DEL CAPTCHA -->
<?php if ($model->loginFailed): ?>
<div class="alert alert-warning">
Demasiados intentos de inicio de sesión fallidos. Por favor, verifica que eres humano.
</div>
<?= $form->field($model, 'verifyCode')->widget(\yii\captcha\Captcha::className()) ?>
<?php endif; ?>
<div class="form-group">
<?= Html::submitButton('Iniciar Sesión', ['class' => 'btn btn-primary', 'name' => 'login-button']) ?>
</div>
<?php ActiveForm::end(); ?>
</div>
</div>
</div>Paso 3: Configurar la Acción CAPTCHA en el Controlador
El widget CAPTCHA requiere una acción dedicada en el controlador para generar la imagen y validar el código. Asegúrate de que la acción ‘captcha’ esté configurada en `SiteController`.
<?php
namespace app\controllers;
use yii\web\Controller;
class SiteController extends Controller
{
// ... otras actions() ...
public function actions()
{
return [
'error' => ['class' => 'yii\web\ErrorAction'],
// LA ACCIÓN CAPTCHA DEBE ESTAR PRESENTE
'captcha' => [
'class' => 'yii\captcha\CaptchaAction',
'fixedVerifyCode' => YII_ENV_TEST ? 'testme' : null, // Importante para testing
'minLength' => 4, // Longitud mínima del código
'maxLength' => 5, // Longitud máxima del código
'offset' => 2, // Dificultad visual
'testLimit' => 1, // Cuántas veces se puede usar un código (1 = un solo uso)
],
// ... otras acciones ...
];
}
// ... actionLogin() y otros métodos ...
}Parámetros como minLength, maxLength y testLimit te permiten ajustar la seguridad y usabilidad del CAPTCHA para tu aplicación .
Notas Importantes y Seguridad Adicional
- Basado en Sesión: Esta implementación depende de las sesiones de PHP. Asegúrate de que la configuración de sesión de tu servidor sea segura.
- No es la Única Solución: El CAPTCHA es una excelente capa defensiva, pero debes combinarla con otras medidas como:
- Limitación de Tasa de Login: Usa componentes como `yiifiltersRateLimiter` para restringir intentos de login desde una misma IP en un período .
- Contraseñas Fuertes: Exige contraseñas complejas para todos los usuarios, especialmente administradores.
- Registro y Monitorización: Registra todos los intentos de inicio de sesión, tanto exitosos como fallidos, para su análisis.
- Reinicio del Contador: En el código anterior, el contador se reinicia con un login exitoso. También podrías implementar un reinicio basado en tiempo (ej., tras 30 minutos) para mejorar la experiencia de usuario.
Al implementar este CAPTCHA condicional, añades una capa de seguridad vital que interrumpe inteligentemente el flujo automatizado de los ataques de fuerza bruta, manteniendo la facilidad de uso para usuarios legítimos.
