Skip to main content

Seguridad en la administración de sesiones: prácticas recomendadas para proteger las sesiones de los usuarios

Escrito por
blog feature pypi spoof

11 de julio de 2023

0 minutos de lectura

La seguridad en la administración de sesiones es un componente esencial del desarrollo de aplicaciones web. Protege las sesiones de los usuarios y evita el acceso no autorizado. Administrar las sesiones protege la confidencialidad, integridad y disponibilidad de los datos confidenciales de los usuarios. También protege la privacidad de los usuarios en general, algo esencial para mantener su confianza en una aplicación.

Cuando administramos las sesiones de forma segura, establecemos procesos para destruir los tokens de sesión cuando los usuarios cierran sesión o esta termina. Este enfoque evita el acceso no autorizado de cualquier persona que haya estado interceptando la conexión de red o quiera suplantar la identidad del usuario.

En este artículo práctico, veremos cómo implementar una administración segura de sesiones y cuáles son las prácticas recomendadas para hacerlo.

Comprender la seguridad en la administración de sesiones

Para conocer la importancia de administrar las sesiones a nivel de dominio, desarrollaremos una aplicación sencilla de Node.js que implementa funciones básicas de inicio y cierre de sesión, así como de administración de sesiones.

Requisitos previos

Para seguir este tutorial, necesitarás:

Ten en cuenta que esta demostración usa el sistema operativo Windows y los comandos correspondientes.

Para ver un resumen del proyecto completo, consulta el código completo.

Implementar sesiones en Node.js

Para comenzar, abre el símbolo del sistema (CMD) y usa el código siguiente para crear una carpeta llamada sessions, entrar en ella e inicializar la aplicación:

mkdir session 
cd sessions
npm init -y

Al ejecutar los comandos anteriores, se genera automáticamente un archivo package.json que contiene todas las dependencias instaladas en el proyecto.

A continuación, debemos instalar algunos paquetes que permitirán cumplir los objetivos específicos. Estos paquetes son express, un framework para crear servidores web, y express-session, un framework HTTP del lado del servidor necesario para crear middleware de sesión. Usa el código siguiente para instalar ambos paquetes:

npm i express express-session

Ahora, abre el proyecto en Visual Studio Code.

Para incluir la lógica de nuestra aplicación, debemos crear un archivo index.js. En el CMD, usa el comando touch index.js para crear un archivo index.js. Luego, actualiza el archivo index.js con el código siguiente:

const express = require('express'); 
const session = require('express-session');

const  app = express();
const port = 3001;

// Session Middlewares, and Login and Logout Routes go here
app.use(session({
  secret: '38sisjsk92',
  resave: false,
  saveUninitialized: true
}));

// URL-encoded middleware
app.use(express.urlencoded({ extended: true }));

app.listen(port, () => {
  console.log(`Server listening on port ${port}`);
});

El fragmento de código anterior contiene el código inicial de una aplicación de Node.js. Configura el middleware de sesión para la aplicación Express en el cuerpo del proyecto.

El middleware de URL codificada se encarga de las solicitudes entrantes con datos de formulario codificados en la URL en cualquier aplicación Express.js.

A continuación, agrega el código siguiente después de los middlewares en el archivo index.js para gestionar las funciones de inicio y cierre de sesión de un usuario:

// Routes
app.get('/', (req, res) => {
  if (req.session.username) {
	res.send(`Welcome to the page, ${req.session.username}! <a href="/logout">Logout</a>`);
  } else {
	res.send('Welcome, Please <a href="/login">login</a>.');
  }
  console.log(req.session)
});

app.get('/login', (req, res) => {
  if (req.session.username) {
	res.redirect('/');
  } else {
	res.send(`
  	  <form method="post" action="/login">
<input type="text" name="username" placeholder="Username" required>
    		<br /> <br />
    		<input type="text" name="password" placeholder="Password" required>
    		<br /> <br />
    		<button type="submit">Login</button>
  	  </form>
	`);
  }
});

// validate your username and password here
app.post('/login', (req, res) => {
  const { username, password } = req.body;
  req.session.username = username;
  req.password = password;
  res.redirect('/');
});

app.get('/logout', (req, res) => {
  req.session.destroy();
  res.redirect('/');
});

Antes de continuar, repasemos rápidamente lo que sucede en el código anterior:

  • La primera ruta, app.get('/'), gestiona las solicitudes a la URL raíz de la aplicación. Da la bienvenida a los usuarios que tienen un nombre de usuario guardado en su sesión y les ofrece un enlace para cerrar sesión. De lo contrario, les pide que inicien sesión.

  • La segunda ruta, app.get('/login'), gestiona las solicitudes a la URL de inicio de sesión.

  • La tercera ruta, app.post('/login'), procesa el envío del formulario de inicio de sesión.

  • La cuarta ruta, app.get('/logout'), gestiona las solicitudes GET a la URL de cierre de sesión. Destruye la sesión del usuario y lo redirige a la URL raíz.

Guarda todos los cambios y luego ve a la terminal y ejecuta la aplicación con este comando:

node index.js

Como estamos escuchando en el puerto 3001, abre http://localhost:3001/ en un navegador. Al navegar a la URL, verás este sencillo enlace para iniciar sesión:

Página web sencilla que muestra el mensaje «¡Te damos la bienvenida! Inicia sesión».

Haz clic en el enlace para iniciar sesión e ingresa tu nombre. Esto te llevará a la siguiente página de inicio:

Página web que muestra “¡Te damos la bienvenida a la página, John Doe!” con un enlace para “Cerrar sesión”.

Ahora, revisa la terminal. Verás el objeto de sesión, que se ve así:

Terminal que muestra un servidor de Node.js escuchando en el puerto 3001 y un objeto de sesión que contiene una cookie y el nombre de usuario «John Doe».

La primera propiedad es la ruta. Representa la ruta válida de la cookie de sesión. A continuación, aparece la hora de expiración de la sesión, es decir, cuánto tarda en vencer una cookie, y el atributo httpOnly establecido en true para prevenir ataques del lado del cliente, como el cross-site scripting (XSS). El objeto de sesión también muestra el nombre de usuario actual.

La administración de sesiones y su función en las aplicaciones web

Ahora que preparamos una aplicación de ejemplo, veamos cómo funciona la administración de sesiones y por qué es tan importante para gestionarlas de forma segura.

En las aplicaciones web, las interacciones entre un usuario y la aplicación suelen incluir información confidencial; en este caso, una contraseña. La administración de sesiones garantiza que estas interacciones sean seguras para los usuarios autenticados y que la información proporcionada durante sus sesiones esté siempre protegida. Si no se hace, pueden surgir vulnerabilidades en la administración de sesiones.

Algunas vulnerabilidades comunes son:

  • Secuestro de sesión: ocurre cuando un atacante compromete un token de sesión al robarlo o predecirlo para obtener acceso no autorizado al servidor en nombre de su titular. Hay varias maneras de lograrlo. Una de ellas es mediante XSS. Si la aplicación web a la que accede el usuario tiene el atributo httpOnly establecido en false, el riesgo de secuestro de sesión desde el lado del cliente es muy alto, lo que abre la posibilidad de ataques XSS.

  • Ataques de intermediario (MITM) : son otro tipo de secuestro de sesión. Un actor malicioso puede acceder a una sesión interceptando el ID de sesión cuando el usuario y el servidor se comunican. Esto ocurre cuando el ID de sesión está en texto sin formato y un interceptor puede leerlo fácilmente en el canal de datos.

  • Fijación de sesión: ocurre cuando un atacante engaña al usuario para que use un ID de sesión existente y así acceder al servidor mediante el secuestro de una sesión de usuario válida. Suele ocurrir cuando el usuario que accede a la sesión no se valida correctamente mediante el ID de sesión. También puede ocurrir cuando un atacante envía a la víctima un ID de sesión vulnerable dentro de una URL.

Los riesgos asociados a los procesos inseguros de administración de sesiones pueden ser considerables. Con los datos confidenciales obtenidos, los actores maliciosos pueden cometer fraudes y robos, y acceder a información privilegiada. Estas brechas de seguridad perjudican mucho a los usuarios y pueden dañar gravemente su confianza en una aplicación.

Por suerte, podemos implementar varias estrategias para proteger los procesos de administración de sesiones. En las siguientes secciones, destacamos algunos métodos y mostramos cómo implementarlos.

Implementar cookies seguras

El uso de cookies puede ayudar a administrar las sesiones de forma segura. En la administración de sesiones, las cookies pueden vincular a los usuarios con un objeto de sesión en el servidor. Gracias a su identificador único, una cookie contiene todas las actividades relacionadas con el usuario en las distintas instancias que visitó.

Por ejemplo, cuando un usuario accede a una aplicación para iniciar sesión, esta acción crea un objeto de sesión y le asigna un ID de sesión. La cookie almacena el ID de sesión y lo envía al navegador como respuesta. Cuando el usuario realiza una solicitud, el navegador envía la cookie, que verifica el ID de sesión y recupera el objeto asociado. Esto permite que el servidor valide al usuario y mantenga sus sesiones en distintas páginas y solicitudes.

Por eso, los desarrolladores de sistemas, arquitectos de aplicaciones y administradores deben asegurarse de que las cookies se usen de la manera más segura posible.

Una forma de proteger las cookies es usar el indicador secure, que permite solicitudes únicamente a través de HTTPS. Protege al usuario en la red de un ataque MITM en la ruta de datos, ya que el paquete enviado no está en texto sin formato y, por lo tanto, no se puede leer.

Además de los ataques MITM, las cookies también son vulnerables a ataques XSS porque transmiten paquetes de datos a través de la red. Para prevenirlos, podemos usar el indicador HttpOnly, que impide que JavaScript lea la cookie de sesión.

Para implementar este indicador, debemos comprobar que el objeto de sesión HttpOnly esté asignado al valor booleano true.

Ve a la terminal e instala cookie-parser, el middleware para guardar las cookies, y bcrypt, la versión vulnerable del paquete, con el siguiente comando:

npm i cookie-parser bcrypt@v4.0.1

Luego, actualiza el código en el archivo index.js de la siguiente manera:

const cookieParser = require('cookie-parser');

// Enable cookie parsing
app.use(cookieParser());

// Set up session middleware
app.use(session({
  secret: '38sisjsk92',
  resave: false,
  saveUninitialized: true,
  cookie: {
	httpOnly: true, 
	secure: false, // Sent only over HTTPS if true
  },   
}));

Para ver las vulnerabilidades detectadas en tu código en Visual Studio Code, debes asegurarte de que Snyk Code esté habilitado en tu cuenta. Puedes hacerlo desde la extensión de Visual Studio Code o el sitio web de Snyk. Si observas la pestaña PROBLEMAS en la terminal de Visual Studio Code, verás que Snyk resaltó varias vulnerabilidades en el código:

Panel Problemas de VS Code que muestra cinco problemas de seguridad en una aplicación de Express, incluidos secretos codificados, protección CSRF deshabilitada y configuración de cookies.

El análisis muestra que hay un secreto codificado directamente en el código, la protección contra la falsificación de solicitudes entre sitios (CSRF) está deshabilitada, el indicador secure está deshabilitado y hay un paquete vulnerable.

Snyk sugiere varias formas de corregir estos problemas siguiendo las indicaciones. Vamos a resolver un par de ellos.

Primero, Snyk recomienda usar variables de entorno para evitar codificar datos directamente. Para implementar este cambio, ve a la terminal y ejecuta los siguientes comandos:

npm i dotenv
touch .env

El primer comando instala el paquete dotenv para leer las variables de entorno configuradas y el segundo crea un archivo de entorno para almacenar esa información confidencial.

Ahora, agrega esta configuración al archivo .env:

SESSION_SECRET=38sisjsk92

Luego, actualiza el middleware de sesión en el archivo index.js con este código:

require('dotenv').config(); // loads env variables 
app.use(session({
  secret: process.env.SESSION_SECRET,
}));

Con estas actualizaciones, el secreto se leerá de la variable de entorno.

A continuación, cambia el indicador secure a true.

Como habilitar CSRF agregaría bastante código a nuestra aplicación, lo omitiremos en esta demostración. Pero recuerda abordarlo en tus propias aplicaciones con el paquete csrf.

Para resolver el problema del paquete vulnerable, consulta la documentación oficial de npm e instala una versión actualizada o el paquete bcryptjs, que ha sido probado más exhaustivamente y es más seguro.

Una vez que la extensión de Snyk haya analizado la aplicación después de estas actualizaciones, verás que la mayoría de los errores desaparecieron. Si ejecutas la aplicación en este punto, localhost no funcionará si no se usa HTTPS y seguirá redirigiéndote a la URL raíz.

Además de usar el almacenamiento local para guardar datos de las solicitudes del cliente, puedes almacenar los datos de sesión de tu aplicación en una base de datos. Esto permite reiniciar el servidor sin perder los datos de sesión, que se conservan mediante un ID de sesión único, y facilita que los usuarios inicien sesión sin ingresar sus credenciales cada vez. Como alternativa, puedes usar cachés en memoria, como Redis.

Implementar tiempos de espera de sesión

Los tiempos de espera de sesión se producen cuando vence la sesión de un usuario, lo que cierra su sesión automáticamente. Esto reduce el período durante el cual puede ocurrir un ataque a la sesión y garantiza que esta se destruya después de un tiempo determinado.

Al definir la duración de los tiempos de espera de sesión, hay varios factores que debes considerar:

  • El tipo de aplicación: determina la duración adecuada de los tiempos de espera según los datos que maneja tu aplicación.

  • El comportamiento de los usuarios: asegúrate de que los tiempos de espera no afecten su experiencia.

Implementamos los tiempos de espera de sesión en el middleware de sesión. Actualiza el código con el siguiente fragmento para establecer el tiempo de espera de la sesión en una hora:

// Set up session middleware
app.use(session({
  secret: process.env.SESSION_SECRET,
  resave: false,
  saveUninitialized: true,
  cookie: {
	maxAge: 3600000, // Expires after 1 hour (in milliseconds)
	httpOnly: true, // Accessible only via HTTP(S)
	secure: false, // Sent only over HTTPS
  },   
}));

Implementar una función de cierre de sesión adecuada

La función de cierre de sesión garantiza que los usuarios puedan finalizar sus sesiones explícitamente cuando terminan de interactuar con una aplicación. Al implementarla, los usuarios pueden terminar sus sesiones de cookies y tener mayor control sobre su seguridad.

Sin embargo, la funcionalidad de cierre de sesión todavía requiere la atención de los desarrolladores. Cuando un usuario inicia el cierre de sesión, es posible que la sesión del lado del cliente termine, pero que la cookie de sesión no se haya invalidado por completo en el servidor. Un impostor puede usar estos tokens de sesión y realizar acciones como si fuera el usuario original.

Para prevenir esta brecha de seguridad, debemos asegurarnos de que el token y el ID de sesión del usuario se destruyan en el servidor. Implementa esta función en la aplicación usando el siguiente código como la última ruta del archivo index.js:

app.get('/logout', (req, res) => {
  req.session.destroy(err => {
	if (err) {
  	  console.log(err)
	} else {
  	  res.clearCookie('connect.sid');
  	  res.redirect('/');
	}
  });
});

Este código llama a destroy() con una función de devolución de llamada para eliminar una sesión en el servidor cuando un usuario cierra sesión. También llama a clearCookie() y elimina connection.sid, que es una clave asignada al valor del usuario que inició sesión.

Si inspeccionas las cookies del usuario que inició sesión en el navegador mediante la opción de inspección, deberías ver connection.sid, como se muestra a continuación:

Herramientas para desarrolladores del navegador que muestran seleccionada una cookie de sesión connect.sid de localhost en el panel Cookies.

Al implementar los pasos anteriores, podemos asegurarnos de que los usuarios del sistema cierren sesión sin preocuparse por su seguridad en línea. La extensión Snyk Code permite ver todos los análisis de vulnerabilidades realizados en nuestro proyecto y el estado de cada uno, lo que nos ayuda a garantizar que la aplicación no esté expuesta a riesgos antes de ponerla a disposición de los usuarios.

A lo largo de este artículo, usamos Snyk para analizar nuestro proyecto y vimos cómo abordar problemas de vulnerabilidad, como los secretos codificados de forma rígida, los problemas relacionados con las sesiones y el uso de un paquete vulnerable. Ahora que mitigamos las vulnerabilidades detectadas por Snyk, nuestro sistema no tiene ninguna, como se muestra en el informe a continuación:

Panel de Snyk con tema oscuro que muestra que no hay vulnerabilidades ni problemas en los análisis de código abierto, código, configuración y calidad del código.

Más prácticas recomendadas para una administración segura de sesiones

Además de los procesos que revisamos a lo largo de este artículo, aquí tienes algunas prácticas recomendadas más para administrar las sesiones de forma más segura:

  • Usa identificadores de sesión seguros — Los identificadores de sesión seguros son cadenas largas de caracteres únicos que se usan para autenticar sesiones de usuario. Podemos generar estos identificadores automáticamente. La aleatoriedad y la imprevisibilidad dificultan que incluso los atacantes meticulosos los adivinen mediante técnicas de análisis estadístico, y la longitud de la cadena permite resistir ataques de fuerza bruta.

  • Usa la administración de sesiones del lado del servidor — Capturar identificadores de sesión en el lado del cliente puede generar una vulnerabilidad, aunque se hayan generado de forma única. Podemos usar la administración de sesiones del lado del servidor para almacenar estos identificadores y asociarlos con los usuarios de la aplicación. Con este tipo de almacenamiento de sesiones, las credenciales confidenciales de los usuarios están mucho más seguras y pueden usar el sistema con libertad, sin temor a que alguien se haga pasar por ellos.

  • Valida correctamente los cierres de sesión — Cuando un usuario inicia sesión en una instancia y luego cierra sesión, el sistema debe invalidar correctamente la sesión en todas las instancias que visitó. Lograrlo sin sincronizar los datos de sesión entre las instancias puede ser complicado. Aun así, es esencial hacerlo, ya que las instancias pueden convertirse en un punto de entrada para que los impostores roben información de los usuarios.

Conclusión

En este artículo, exploramos varias estrategias para mejorar la seguridad de la administración de sesiones, maximizar la seguridad de los visitantes y reducir la cantidad de ataques. Una administración segura de sesiones garantiza que la información personal, los datos y las cuentas estén protegidos, a pesar de las huellas digitales que los usuarios puedan dejar.

También protege a los usuarios mientras interactúan con nuestros sitios web y aplicaciones web. Al implementar las prácticas recomendadas y las estrategias de seguridad que se describen aquí, podemos ayudar a que nuestros usuarios confíen en que sus credenciales y su información confidencial están seguras.

Comienza con Capture the Flag

Aprende a resolver desafíos de Capture the Flag con nuestro taller virtual 101 a pedido.

Leer más

feature insights context
Blog

Los ataques autónomos ya están aquí. La defensa debe estar a su altura.

Los atacantes autónomos están reduciendo el tiempo disponible para defenderse. Descubre cómo el descubrimiento, la corrección, la validación y la prevención continuos pueden ayudar a los equipos de seguridad a seguirles el ritmo.

illustration hero ai
Blog

El huracán de la IA ha llegado

La IA está acelerando por igual la creación de software y los ciberataques. Los líderes deben proteger los agentes y el código desde el inicio, aplicar controles en tiempo de ejecución y validar las defensas de forma independiente.

feature insights context
Blog

¿La prevención es, en esencia, un problema ya resuelto?

La prevención en el código generado por agentes está resuelta desde el punto de vista arquitectónico, pero elegir controles que protejan la seguridad sin ralentizar el desarrollo sigue siendo el desafío.