Seguridad en el sistema de Login

En este artículo vamos a explicar cómo reforzar la seguridad en el sistema de login que hemos construido con PHP y MySLQ. La seguridad en un sistema de login no consiste en una única gran medida, sino en un conjunto de pequeñas prácticas bien aplicadas. Si hasta ahora hemos visto como hacer nuestro formulario, validarlo, conectar a MySQL, registrar usuarios y contraseñas, validar el login y gestionar sesiones y control de acceso. Ahora toca blindarlo.

Este artículo forma parte de la serie Sistema de login con PHP, donde se explica paso a paso cómo construir un sistema completo de login de usuarios usando PHP y MySQL.

Uso de HTTPS

En Internet, cuando enviamos datos a través de un formulario, como por ejemplo un usuario y una contraseña, si utilizamos el protocolo HTTP es como enviar una postal: los datos se envían tal cual los escribimos y cualquiera por el camino puede leerlos. Por ello, actualmente es imprescindible que nuestra web disponga de un certificado de seguridad en el sistema de login y que los datos se envíen cifrados mediante el protocolo HTTPS. Con esto evitamos un primer problema muy importante:

  • Los datos viajan cifrados.
  • Se evita que terceros puedan robar las credenciales.
  • Google y los navegadores penalizan las webs sin HTTPS.

En local no es obligatorio, pero en producción, siempre.

Protección frente a XSS

Los ataques XSS o Cross-Site Scripting, ocurren cuando un atacante consigue inyectar JavaScript en tu propia web para ejecutarlo en el navegador del usuario. Un ejemplo clásico sería mostrar los datos recibidos de un formulario sin haber sanitizado o validado su contenido, como:

echo $_GET['msg'];  // peligroso!

La solución, para dotar de mayor seguridad a nuestro sistema de Login se basa en dos partes:

  • Validar y sanitizar: Todos los datos que vienen de un formulario (entrada) deben ser comprobados para asegurar que contienen lo que esperamos y están limpios de código malicioso, tal y como ya comentamos en el artículo formulario de login de la serie.
  • Escapar: Una vez tenemos los datos «limpios», siempre debemos escaparlos con htmlspecialchars(); cuando los mostremos en nuestra web. De esta manera protegemos la salida y evitamos que caracteres especiales puedan transformarse en código ejecutables.

Protección frente a CSRF

Los ataques CSRF o Cross-Site Request Forgery son ataques que intentan que el usuario realice acciones sin querer (por ejemplo, cerrar sesión automáticamente o cambiar su contraseña). Para evitarlo y dar mayor seguridad a nuestro sistema de login podemos realizar las siguientes acciones:

1 – Generar un token CSRF: En el momento de iniciar la sessión PHP o justo cuando el usuario hace login correctamente (ambas son válidas). Tal y como explicamos en el artículo gestión de sesiones en PHP, al iniciar la sesión o completar el login de usuario podemos añadir nuestro token. Este token es único para la sessión del usuario y permite verificar su autenticidad durante toda la sesión.

session_start();
if (!isset($_SESSION['csrf_token'])) {
    $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}

2 – Añadir token al formulario: Una vez tenemos el token de usuario podemos utilizarlo al construir cualquier parte HTML «exclusiva» para ese nivel de usuario y añadirlo de forma dinámica en la web. Con esto nos aseguramos de que estamos tratando con el mismo usuario.

<input type="hidden" name="csrf" value="<?php echo $_SESSION['csrf_token']; ?>">

3 – Validar token al procesar: Por último, cada vez que el usuario realice acciones en nuestra web, como modificar sus datos o contraseña, debemos asegurarnos de que el formulario que recibimos contenga el token definido para ese usuario y sesión.

if (!isset($_POST['csrf']) || $_POST['csrf'] !== $_SESSION['csrf_token']) {
    die("Solicitud no válida");
}

Con estas buenas prácticas eliminamos el 90% de los ataques CSRF o Cross-Site Request Forgery típicos, dando mayor seguridad a nuestro sistema de Login.

Regeneración del session_id()

Otra medida que se puede o se debe tomar como medida de seguridad en un sistema de Login, es regenerar el ID de la sesión para evitar secuestros de sesión (session fixation). Justo después de verificar al usuario y su contraseña añadimos:

session_regenerate_id(true); // genera un nuevo ID seguro

Esto evita que un atacante pueda reutilizar un session_id antiguo y secuestrar la sesión.

Configuración segura de cookies de sesión

PHP permite activar medidas extra de seguridad en las cookies de sesión desde el propio script, usando directivas de configuración en el php.ini:

ini_set('session.cookie_httponly', 1); // impide acceso por JavaScript
ini_set('session.cookie_secure', 1);   // solo con HTTPS
ini_set('session.use_strict_mode', 1); // evita reutilizar IDs caducados

NOTA: session.cookie_secure solo debe activarse si estás usando HTTPS, ya que en local con HTTP podría generar problemas.

Almacenamiento seguro de contraseñas

Ya lo hicimos en el artículo registro de usuarios, pero lo recordamos porque forma parte de la seguridad en nuestro sistema de Login:

Para guardar contraseñas:

$hash = password_hash($password, PASSWORD_DEFAULT);

Para verificar contraseñas:

if (password_verify($password, $fila['password'])) {
    // contraseña correcta
}

El uso de password_hash(); y password_verify(); implica que PHP pueda cambiar a un algoritmo más fuerte en el futuro sin que cambies la lógica.

Prevención por fuerza bruta

Actualmente existen muchos robots que realizan ataques por fuerza bruta, intentando acceder a los logins mediante cientos o miles de intentos basados en diccionarios con los usuarios y contraseñas más habituales. Esto se puede evitar fácilmente añadiendo un CAPTCHA a nuestros formularios públicos o utilizando un sistema para limitar los intentos de sesión fallidos. Por ejemplo:

session_start();

// Inicializamos el contador de intentos si no existe
if (!isset($_SESSION['intentos'])) {
    $_SESSION['intentos'] = 0;
}

// Comprobamos si se ha alcanzado el límite
if ($_SESSION['intentos'] >= 5) {
    die("Demasiados intentos.");
}

// Si el login falla
$_SESSION['intentos']++;

// Si el login acierta
$_SESSION['intentos'] = 0;

A través de este código podemos programar diferentes tipos de penalizaciones al alcanzar el máximo número permitido de intentos de login fallidos. Por ejemplo:

  • Bloqueo temporal por IP: impedir que la misma dirección IP vuelva a intentar logins durante un período determinado.
  • Retrasos progresivos en el login: aumentar progresivamente el tiempo de espera tras cada fallo para ralentizar ataques de fuerza bruta.
  • CAPTCHAs tras X intentos: exigir que el usuario resuelva un CAPTCHA después de varios intentos fallidos para verificar que es humano.
  • Notificaciones o alertas: opcionalmente, podemos notificar al usuario o al administrador sobre intentos repetidos de acceso.

Estas medidas se pueden combinar para crear un sistema de protección robusto y flexible frente a ataques automatizados.

Seguridad en el sistema de Login

En este artículo hemos aprendido a reforzar la seguridad en el sistema de login que hemos construido con PHP y MySQL. Con las prácticas comentadas, contamos con un sistema de login realmente profesional, robusto y preparado para entornos reales.

Hemos visto cómo aplicar múltiples medidas de seguridad fundamentales, entre ellas: el uso de HTTPS para cifrar las comunicaciones, la protección frente a XSS y CSRF, la sanitización de los datos de entrada y el correcto manejo de contraseñas con password_hash() y password_verify().

Además, hemos aprendido a aumentar la seguridad de las sesiones mediante la regeneración de session_id() y la configuración de cookies seguras, así como la implementación de un sistema anti fuerza bruta para limitar intentos de login y proteger a los usuarios frente a ataques automatizados.

Aplicando estas buenas prácticas, tu sistema de login alcanza un nivel profesional, robusto y preparado para entornos reales, garantizando tanto la protección de los usuarios como la integridad de tu aplicación.

¡ Espero que este artículo sea de vuestro interés !

Deja un comentario