A menudo, cuando trabajamos en algún proyecto web, es habitual que en algún momento necesitemos subirlo a un servidor en Internet, ya sea para detectar problemas que podrían no haber surgido en el entorno local de desarrollo, o para compartirlo con un grupo de usuarios específicos. En muchos casos, el proyecto puede no estar basado en un framework o marco de trabajo existente, y por lo tanto, no contará con sistemas de autenticación y protección contra accesos no autorizados, que suelen ser incluidos por frameworks conocidos. En estos casos, es crucial implementar medidas de seguridad adecuadas antes de proceder con el despliegue.
Índice del contenido
Introducción
En esta primera parte del artículo, comenzaremos con una breve descripción cronológica de la evolución de algunos de los protocolos y componentes clave que han conformado la World Wide Web hasta el día de hoy. A continuación, repasaremos de forma somera los principales métodos de autenticación con que contamos para la protección de recursos publicados en Internet. Finalmente, en la segunda parte presentaremos LoginSkel, un sistema de login escrito en PHP y en el que hemos intentado implementar las recomendaciones de seguridad más actuales en cuanto a mecanismos de autenticación basada en formularios.
Además, si quieres profundizar en la materia, en la bibliografía encontrarás algunos de los artículos que nos han servido como base de conocimiento, lo más actualizada posible, para escribir LoginSkel, así que recomendamos encarecidamente su lectura.
Breve cronología de la autenticación basada en formularios
Algunos de los protocolos y tecnologías mencionados a continuación no fueron diseñados específicamente para la autenticación en formularios web, pero han sido fundamentales para proteger la información transmitida a través de ellos. Aunque nos enfocamos en los desarrollos desde el surgimiento de la World Wide Web, es relevante mencionar que los conceptos de autenticación y autorización tienen raíces más antiguas, como en el Compatible Time-Sharing System de Fernando Corbató (MIT) en 1961.
Las fechas en la tabla son aproximadas y pueden variar según la fuente consultada.
1991 | Con el lanzamiento de WWW por Tim Berners-Lee, la necesidad de autenticación basada en formularios para la web comienza a emerger. |
1994 | Netscape introduce Secure Sockets Layer (SSL). SSL proporciona cifrado para la transmisión de datos entre el navegador y el servidor, protegiendo las credenciales de autenticación durante el tránsito. |
1995 | SSL 2.0 es lanzado en este año y se actualiza posteriormente. |
1996 | Se publica el RFC 1945 HTTP/1.0 que define métodos básicos de autenticación, incluyendo Basic Authentication, aunque esta no es segura por sí sola ya que las credenciales se transmiten en texto plano. |
1996 | Hans Dobbertin anuncia una colisión de hash en MD5 |
1997 | Mark D. Lillibridge, Martín Abadi, Krishna Bharat y Andrei Broder utilizan un sistema tipo «CAPTCHA» para evitar que se agregaran URL a su motor de búsqueda WEB (AltaVista). |
1999 | RFC 2616 introduce HTTP/1.1, mejorando el soporte para autenticación. La autenticación Digest se agrega, proporcionando un método más seguro que Basic Authentication al usar hashing. |
2000s | Con el auge de la web, las vulnerabilidades de autenticación se vuelven un foco importante. Se comienzan a implementar medidas adicionales para proteger las credenciales de usuario y prevenir ataques comunes. |
2001 | El hash MD5 es ampliamente utilizado para almacenar contraseñas debido a su eficiencia, pero pronto se descubrirá que es vulnerable a ataques de colisión y crackeo. |
2002 | IBM, Microsoft y VeriSign proponen la especificación de seguridad de servicios web (WS-Security) en sus sitios web- |
2003 | Se acuña el término CAPTCHA, introducido por Luis von Ahn y su equipo para evitar ataques automatizados en formularios web, desafiando a los usuarios a completar tareas que son fáciles para los humanos pero difíciles para los bots. |
2004 | Xiaoyun Wang, Dengguo Feng, Xuejia Lai y Hongbo Yu anuncian el descubrimiento de colisiones de hash para MD5. |
2005 | RFC 6454. La técnica de Cross-Site Scripting (XSS) se convierte en una amenaza significativa, donde los atacantes inyectan scripts maliciosos en formularios web para robar credenciales o ejecutar otros ataques. |
2006 | Se introduce el concepto de Session Fixation en la gestión de sesiones. La seguridad de las sesiones de usuario se vuelve crucial, con mecanismos para prevenir ataques de fijación de sesión. RFC 6896. |
2010 | RFC 7519 introduce JSON Web Tokens (JWT) como un estándar para la autenticación en aplicaciones web. JWT permite la transmisión segura de información entre el servidor y el cliente en un formato compacto y auto-contenido. |
2011 | Se hace evidente que SHA-1 y MD5 son inseguros para el almacenamiento de contraseñas debido a la creciente potencia de cómputo y técnicas de ataque. Se recomienda el uso de algoritmos más seguros como bcrypt o Argon2. |
2014 | RFC 6404. Se desarrollan técnicas avanzadas para mitigar ataques de SQL Injection (SQLi) en los formularios de entrada de datos. Las prácticas recomendadas incluyen el uso de consultas preparadas y la validación de entradas. |
2015 | Se populariza el uso de Refresh Tokens (RFC6749) junto con JWT para gestionar sesiones de usuario y mejorar la seguridad de autenticación a largo plazo. |
2020 | La autenticación multifactor (MFA) se convierte en una norma para proteger las cuentas de usuario. Métodos como TOTP (Time-based One-Time Password) y autenticación biométrica se implementan para añadir capas adicionales de seguridad. |
2022 | Se adopta ampliamente el uso de OAuth 2.0 (RFC 6749) y OpenID Connect para la autenticación y autorización de aplicaciones web y móviles, permitiendo un control más granular y seguro del acceso a recursos. |
2024 | Con el avance de la computación cuántica, los investigadores están explorando nuevas técnicas de cifrado y autenticación resistentes a la computación cuántica para asegurar las futuras aplicaciones web. |
Mecanismos de Autenticación HTTP
Los esquemas de autenticación HTTP tienen como objetivo verificar la identidad de los usuarios que intentan acceder a un recurso web, garantizando que solo usuarios autorizados puedan realizar solicitudes a servidores protegidos. Estos métodos proporcionan diversos esquemas para transmitir credenciales de manera «segura», cada uno con sus características y niveles de seguridad. A continuación resumimos algunos de los más comunes, aunque aquí puedes ver una lista exhaustiva (IANA – HTTP Authentication Schemes).
HTTP Basic Authentication
Cuando nos encontramos en la situación al principio descrita, una forma habitual y sencilla para proteger el directorio donde se encuentran los archivos del proyecto, es mediante Autenticación HTTP Básica (RFC 7617), el marco general básico para el control de acceso y autenticación con el que cuenta el propio protocolo HTTP.
Este método solo es seguro si se implementa mediante el protocolo HTTPS (HTTP + SSL) para cifrar la comunicación, ya que base64 solo codifica los datos, no los cifra, por lo que cualquier agente intermedio que lea dicha comunicación podrá descodificar:
Authorization: Basic dXN1YXJpbzpzZWNyZXRv
echo «dXN1YXJpbzpzZWNyZXRv» | base64 -d
usuario:secreto
Además de implementar cifrado en la comunicación (SSL), también se debe contar con un sistema para detectar ataques de fuerza bruta contra el área privada que queremos proteger, de otro modo también podría ser vulnerable.
HTTP Digest Authentication
El método de Autenticación HTTP Digest (RFC 7616) es algo más sofisticado y seguro que Basic Auth, la contraseña no se transmite en texto plano (base64), en su lugar, la respuesta es «hasheada» mediante MD5. Aunque sin la contraseña del usuario, generar un hash response
válido es prácticamente imposible, también es susceptible a ciertos atques si no se toman medidas adicionales, es decir, cifrado mediante HTTPS y protección contra ataques de fuerza bruta.
En la cebecera:
- «nonce», es un valor único generado por el servidor para cada respuesta de autenticación e incluye un componente de tiempo para que expire después de un cierto período, por lo que previene ciertos ataques de repetición (Replay Attacks).
- «qop» (Quality of Protection), es un parámetro que indica el nivel de protección aplicado al mensaje. Puede tomar valores como
auth
(autenticación) yauth-int
(autenticación con integridad).auth
: Solo proporciona autenticación.auth-int
: Proporciona autenticación y garantiza la integridad del mensaje. - «opaque», es un valor aleatorio generado por el servidor que debe ser incluido por el cliente en las solicitudes subsiguientes
HTTP Bearer Authentication
El esquema de autenticación Bearer Token (RFC 6750) es una parte integral de OAuth 2.0 (RFC 6749). De hecho, OAuth 2.0 utiliza Bearer Tokens como uno de los mecanismos principales para autenticar y autorizar solicitudes. En esencia, el Bearer Token es un tipo de token de acceso que se utiliza en el encabezado HTTP Authorization
para acceder a recursos protegidos y que es presentado por el cliente como prueba de autenticación.
Si bien la cabecera Authorization: Bearer <token> puede ser usada sin definir un esquema OAuth propiamente, o dicho de otro modo, podemos construir un esquema propio que haga uso de dicha cabecera, aquí omitiremos dicha explicación y veremos un ejemplo de uso en la pestaña OAuth y JWT.
OAuth 2.0 (RFC 6749) es un protocolo de autorización (ámpliamente utilizado) que permite a las aplicaciones obtener acceso limitado a recursos del usuario en un servidor sin exponer sus credenciales. OAuth utiliza diferentes flujos de autorización («grant types» o tipos de concesión) para diferentes tipos de aplicaciones y escenarios (apps móviles, páginas web, APIs RESTful, microservicios, aplicaciones de escritorio, dispositivos IoT, servicios backend (Servidor-a-Servidor), integraciones empresariales, clientes de línea de comandos (CLI), plataformas de mensajería y chatbots, aplicaciones de TV y dispositivos de Streaming, servicios en la Nube (Cloud Services).
Dichos «grant types» se definen en la cabecera de la petición mediante el parámetro grant_type: Authorization Code, PKCE, Client Credentials, Device Code y Refresh Token son los princpales tipos de concesión que utiliza OAuth, cada uno enfocado a un tipo diferente de entorno y arquitectura.
Diagrama de flujo OAuth para autenticación y autorización web
Los principales parámetros OAuth en las cabeceras:
- response_type=code – Indica que su servidor espera recibir un código de autorización
- client_id – El ID de cliente que recibió cuando creó la aplicación por primera vez
- redirect_uri – Indica el URI al que regresará el usuario después de que se complete la autorización
- scope – Uno o más valores de alcance que indican a qué partes de la cuenta del usuario desea acceder
- state – Una cadena aleatoria generada por su aplicación, que verificará más adelante
JWT (JSON Web Token) es un estándar abierto (RFC 7519) que define una forma compacta y autónoma para transmitir información de manera segura entre diferentes sistemas. Dicha información, que se envía como un objeto JSON ( Javascript Object Notation o «Notación de objeto Javascript», RFC 8259), puede ser verificada ya que está firmada digitalmente mediante una clave privada y validada mediante una llave pública. Comúnmente, los JWT se utilizan para la autenticación y la autorización en aplicaciones web y servicios API.
Estructura de un token JWT
Un JWT se compone esencialmente de tres bloques:
- header (o encabezado), identifica qué algoritmo [alg] fue usado para generar la firma y el tipo [typ] de token
{ "typ": "JWT", "alg": "RS256", }
- payload (o contenido), está compuesto por las claims del token y las hay de tres tipos:
- Registered Claim Names: contiene información acerca del propio registro del token.
iss
(Issuer): Identifica el proveedor de identidad que emitió el JWTsub
(Subject): Identifica el objeto o usuario en nombre del cual fue emitido el JWTaud
(Audience): Identifica la audiencia o receptores para lo que el JWT fue emitido, normalmente el/los servidor/es de recursos (e.g. la API protegida).exp
(Expiration time): Identifica la marca temporal cuya superación invalida el JWT.nbf
(Not before): Identifica la marca temporal en que el JWT comienza a ser válido, antes no es válido.iat
(Issued at): Identifica la marca temporal en qué el JWT fue emitido.jti
(JWT ID): Identificador único del token incluso entre diferente proveedores de servicio.
- Public Claim Names: Reclamos que pueden ser definidos por el propio desarrollador. Sin embargo, para evitar colisiones, cualquier nombre de reclamo nuevo debe estar registrado en IANA JSON Web Token Registry.
- Private Claim Names: Son campos privados acordados entre el generador del token y el consumidor del mismo.
- Registered Claim Names: contiene información acerca del propio registro del token.
- signature (o firma)5. El encabezado identifica qué algoritmo fue usado para generar la firma y normalmente se ve de la siguiente forma:
{
"aud": "https://mybusiness.com/mi-project",
"iss": "https://linux.mybusiness.com/common/",
"sub": "sas67fasb478dsaf783ba",
"iat": 1722029237,
"nbf": 1722029237,
"exp": 1722029237,
"family_name": "Waters",
"given_name": "Roger",
"ipaddr": "10.0.0.5",
"name": "Roger Waters"
}
Los JSON Web Tokens (JWT) se han convertido en un artefacto popular para la autenticación en aplicaciones web y móviles debido a su capacidad para transmitir información de manera compacta y segura. Sin embargo, a medida que estas aplicaciones han evolucionado, se han revelado importantes problemas de seguridad con los JWT. Estos problemas incluyen la dificultad para revocar tokens comprometidos (obtenidos por atacantes) y el riesgo de exposición prolongada si los tokens no expiran adecuadamente. Los Refresh tokens fueron diseñados para abordar estos desafíos, mitigando las vulnerabilidades inherentes a los JWT y mejorando la seguridad general del sistema de autenticación.
Principales vulnerabilidades y contramedidas relacionadas con la autenticación y autorización en formularios web
En este apartado, enumeraremos las principales vulnerabilidades (además de las ya mencionadas), que se han dado a lo largo del tiempo en los procesos de autenticación y autorización mediante formularios web. Muchas de estas vulnerabilidades siguen siendo relevantes y presentes en las aplicaciones actuales. Junto a cada vulnerabilidad, se ofrecen los conceptos generales para mitigar sus riesgos. Adoptar estas medidas es crucial para asegurar la integridad y confiabilidad de cualquier aplicación web.
1. Almacenamiento inseguro de contraseñas:
El almacenamiento inseguro de contraseñas se refiere a prácticas como guardar contraseñas en texto plano o usando algoritmos de hash débiles como MD5 o SHA-1, que pueden ser fácilmente vulnerados por atacantes.
- Contramedidas:
- Usar funciones de hashing seguras como
bcrypt
,Argon2
o Scrypt para almacenar contraseñas. - Añadir un valor aleatorio («salt») a las contraseñas antes de hashearlas (como hacen los alogirtmos mencionados), para asegurar que incluso contraseñas idénticas resulten en hashes diferentes y evitar así ataques de rainbow tables.
- Asegurarse de que los hashes de contraseñas se almacenen en bases de datos seguras, con acceso restringido y utilizando cifrado adicional si es necesario.
- Usar funciones de hashing seguras como
2. Autenticación basada en sesiones inseguras:
Las sesiones de usuario pueden ser secuestradas si no se protegen adecuadamente.
- Contramedidas:
- Utilizar cookies de sesión con las banderas
HttpOnly
ySecure
. - Regenerar el ID de sesión después del inicio de sesión para prevenir el secuestro de sesión.
- Implementar un tiempo de expiración corto para las sesiones inactivas y la invalidación de sesiones en el servidor después de un tiempo determinado.
- Utilizar cookies de sesión con las banderas
3. Fuga de información en los mensajes de error:
Mensajes de error detallados pueden proporcionar información útil a un atacante.
- Contramedidas:
- Usar mensajes de error genéricos como «Usuario o contraseña incorrectos» sin especificar cuál de los dos es incorrecto.
- No mostrar detalles de excepción o stack traces en producción. Estos deben registrarse de manera segura para su análisis interno.
4. Autenticación básica HTTP sin cifrar:
El uso de autenticación básica sin cifrado (HTTP en lugar de HTTPS) expone credenciales en texto plano.
- Contramedidas:
- Implementar HTTPS en toda la aplicación para cifrar el tráfico entre el cliente y el servidor.
- Deshabilitar la autenticación básica HTTP y utilizar métodos de autenticación más seguros, como OAuth o JWT.
5. CSRF (Cross-Site Request Forgery) o falsificación de petición en sitios cruzados:
Un atacante puede hacer que un usuario autenticado realice acciones no deseadas en su cuenta.
- Contramedidas:
- Incluir tokens CSRF únicos en todos los formularios y verificarlos en el servidor.
- Implementar la cabecera
SameSite
en las cookies para limitar el envío de cookies en solicitudes entre sitios.
6. Fuerza bruta en formularios de inicio de sesión:
Los atacantes pueden intentar múltiples combinaciones de usuario/contraseña hasta encontrar una válida.
- Contramedidas:
- Implementar límites en el número de intentos de inicio de sesión (rate limiting).
- Introducir CAPTCHAs para diferenciar entre humanos y bots.
- Monitorear intentos de inicio de sesión y bloquear IPs o cuentas después de demasiados intentos fallidos.
7. Validación insuficiente de entradas (SQL Injection):
Entradas no validadas pueden ser utilizadas para ejecutar comandos SQL maliciosos.
- Contramedidas:
- Utilizar consultas preparadas y sentencias parametrizadas para interactuar con la base de datos.
- Validar y sanitizar todas las entradas del usuario antes de procesarlas.
- Evitar concatenar entradas del usuario directamente en las consultas SQL.
8. Redirecciones abiertas (Open Redirects):
Las redirecciones sin validación pueden ser utilizadas para enviar usuarios a sitios maliciosos.
- Contramedidas:
- Validar y restringir las URLs de redirección permitidas.
- Evitar redirecciones basadas en entradas del usuario o al menos, sanitizarlas adecuadamente.
// Suponinedo que en la aplicación tenemos:
$redirect_url = $_GET['redirect_to'];
// Realiza la redirección
header("Location: " . $redirect_url);
exit();
// Un atacante puede manipular "redirect_url":
http://example.com/redirect.php?redirect_to=http://malicious-site.com
<?php
// Lista de URLs permitidas
$allowed_urls = [
'home' => '/home.php',
'profile' => '/profile.php',
'dashboard' => '/dashboard.php'
];
// Verificar si la URL proporcionada está en la lista permitida
if (isset($_GET['redirect_to']) && array_key_exists($_GET['redirect_to'], $allowed_urls)) {
$redirect_url = $allowed_urls[$_GET['redirect_to']];
} else {
// Si no es válida, redirigir a una página por defecto
$redirect_url = '/home.php';
}
// Realiza la redirección segura
header("Location: " . $redirect_url);
exit();
?>
9. Ataques de replay en tokens de autenticación:
Un ataque de replay es un tipo de ataque en el que un atacante intercepta un token de autenticación válido que fue transmitido en una sesión previa, y lo reutiliza para acceder a un sistema como si fuera el usuario legítimo. El problema surge cuando los tokens de autenticación no tienen mecanismos adecuados para evitar que sean reutilizados.
- Contramedidas:
- Implementar un nonce o un timestamp en los tokens que se verifiquen en el servidor.
- Utilizar tokens con expiración corta y permitir su revocación.
- Siempre usa HTTPS para cifrar la comunicación entre el cliente y el servidor, lo que dificulta que un atacante pueda interceptar y reutilizar tokens.
10. Secuestro de sesión (Session Hijacking):
Un atacante roba la cookie de sesión para hacerse pasar por el usuario legítimo.
- Contramedidas:
- Utilizar cookies con las banderas
HttpOnly
ySecure
. - Regenerar el ID de sesión después de operaciones sensibles, como el inicio de sesión.
- Detectar y manejar el acceso desde múltiples IPs o dispositivos inesperados.
- Utilizar cookies con las banderas
11. Sesiones no expiran después de un tiempo de inactividad:
Las sesiones que no expiran pueden ser utilizadas por un atacante si obtiene acceso al dispositivo del usuario.
- Contramedidas:
- Implementar una política de tiempo de expiración para las sesiones inactivas.
- Informar al usuario sobre la expiración de la sesión y permitirle renovar la sesión de forma segura.
- Utilizar cookies con un tiempo de expiración y eliminar la sesión del servidor cuando esta expire.
12. Insuficiente rotación de claves:
Las claves criptográficas utilizadas en la autenticación no se rotan periódicamente, aumentando el riesgo de comprometer el sistema.
- Contramedidas:
- Implementar una política de rotación periódica de claves, como mínimo cada 90 días o según el riesgo identificado.
- Asegurarse de que la rotación de claves no interrumpa las operaciones, usando claves anteriores para verificar autenticaciones en curso durante un período de transición.
- Forzar a los usuarios a cambiar sus contraseñas periódicamente y a regenerar claves API si es necesario.
13. Ataques de side-channel:
Información no directa, como el tiempo de respuesta o el consumo de energía, se utiliza para inferir datos sensibles.
- Contramedidas:
- Normalizar los tiempos de respuesta para todas las solicitudes, incluso cuando las credenciales son incorrectas.
- Almacena contraseñas de forma segura utilizando un «salt» único para cada usuario, combinado con una «pepper» secreta del servidor, para complicar aún más la posibilidad de ataques de side-channel.
- Introducir pausas aleatorias dentro de un rango de tiempo razonable para dificultar la extracción de patrones.
- Asegurarse de que los errores en la autenticación no proporcionen retroalimentación adicional (como detalles específicos de la falla).
// Función vulnerable a ataques side-chanel, dependiendo de cuántos caracteres coinciden entre las dos cadenas el tiempo de respuesta variará. Esta información no debería poder ser filtrada.
function comparePasswords($stored_password, $input_password) {
return $stored_password === $input_password;
}
// La metodología no vulnerable compara las dos cadenas en un tiempo que es constante, es decir, no se basa en el número de caracteres coincidentes.
function secureComparePasswords($stored_password, $input_password) {
return hash_equals($stored_password, $input_password);
}
// Ejemplo de uso:
$stored_password = password_hash('usuario_password', PASSWORD_BCRYPT);
$input_password = $_POST['password'];
if (secureComparePasswords($stored_password, password_hash($input_password, PASSWORD_BCRYPT))) {
echo "Contraseña correcta";
} else {
echo "Contraseña incorrecta";
}
14. Manipulación de JWT (JSON Web Tokens):
JWT mal configurados pueden ser manipulados por un atacante, especialmente si se utiliza un algoritmo débil o si la clave secreta es débil.
- Contramedidas:
- Utilizar algoritmos de firma fuertes como
HS256
oRS256
. - Asegurarse de que la clave secreta utilizada para firmar el JWT sea lo suficientemente larga y compleja.
- Validar adecuadamente el JWT en cada solicitud y asegurarse de que no se puedan reutilizar tokens viejos (
replay attacks
).
- Utilizar algoritmos de firma fuertes como
15. Almacenamiento de tokens de autenticación en lugares inseguros:
Almacenar tokens de autenticación (como JWT) en localStorage o en cookies sin las banderas de seguridad adecuadas puede exponerlos a ataques XSS.
- Contramedidas:
- Almacenar los tokens de autenticación en cookies seguras (
HttpOnly
,Secure
,SameSite=Strict
). - Evitar almacenar tokens en
localStorage
osessionStorage
, ya que son vulnerables a ataques XSS. - Implementar una política de rotación y expiración para los tokens de autenticación.
- Almacenar los tokens de autenticación en cookies seguras (
16. Autorización basada en roles mal implementada:
Errores en la implementación de controles de acceso basados en roles (RBAC) pueden permitir que usuarios no autorizados accedan a funciones o datos sensibles.
- Contramedidas:
- Implementar una lógica clara y consistente para la asignación de roles y permisos en toda la aplicación.
- Asegurarse de que todas las rutas y acciones críticas verifiquen el rol y los permisos del usuario.
- Realizar pruebas de acceso a las funcionalidades de la aplicación bajo diferentes roles para asegurar que no haya accesos indebidos.
17. Ataques de «Cross-Site Scripting» (XSS):
Un atacante puede inyectar scripts maliciosos en páginas web que los usuarios legítimos visitan, permitiéndole robar tokens de autenticación, cookies de sesión o realizar acciones en nombre del usuario.
- Contramedidas:
- Escapar y sanitizar todas las entradas del usuario antes de renderizarlas en la página.
- Implementar una política de Content Security Policy (CSP) para restringir la ejecución de scripts no autorizados.
- Utilizar herramientas de detección de XSS en las fases de desarrollo y pruebas. Algunas herramientas conocidas son: OWASP ZAP (Zed Attack Proxy), Burp Suite, Acunteix, Netsparket (ahora Invicti), XSSer o W3af, entre otras.
18. Falta de validación de la integridad de datos en formularios:
Datos manipulados en formularios pueden ser enviados al servidor, causando errores o comportamientos inesperados.
- Contramedidas:
- Implementar validaciones de integridad y formato en el lado del servidor, además de las validaciones en el lado del cliente.
- Utilizar
input types
correctos y validaciones en HTML5 para restringir la entrada del usuario. - Verificar la integridad de los datos que provienen de formularios antes de procesarlos o almacenarlos.
19. Inyección de comandos:
Si el sistema permite la entrada del usuario que se utiliza en comandos del sistema operativo o en otras interfaces, un atacante puede ejecutar comandos arbitrarios.
- Contramedidas:
- Evitar usar entradas de usuario en comandos del sistema operativo o sanitizarlas adecuadamente.
- Usar funciones seguras como
exec()
oshell_exec()
con precaución, asegurando que todas las entradas estén validadas y escapadas. - Minimizar el uso de comandos del sistema en la aplicación y preferir APIs seguras cuando sea posible.
20. Autenticación multifactor inadecuada:
Realizar pruebas de penetración y auditorías de seguridad para verificar que la MFA esté correctamente implementada y no pueda ser burlada. Implementaciones de autenticación multifactor (MFA) que pueden ser eludidas debido a configuraciones débiles o errores en la implementación.
- Contramedidas:
- Asegurarse de que todos los factores de autenticación sean sólidos y no triviales de eludir (por ejemplo, usar aplicaciones de autenticación en lugar de SMS).
- Implementar la verificación MFA en todas las etapas críticas, como inicio de sesión, recuperación de contraseñas, y cambios en las configuraciones de seguridad.
Autenticación básica HTTP y algunos ejemplos de uso
Como decíamos, el esquema Basic HTTP Auth puede ser adecuado (siempre implementando HTTPS y Brute-Force Protection) para casos de uso simples, donde solo un usuario (o unos pocos) necesita acceso a un recurso al que solo debe poder acceder él, sin embargo, en proyectos más complejos, especialmente aquellos con múltiples usuarios, roles y requisitos de seguridad más avanzados, Basic HTTP Auth no proporciona la flexibilidad ni la seguridad necesaria. Aun así, veamos un poco más a fondo su configuración.
Configuración estándar de autenticación HTTP básica para un usuario
La forma más elemental de configurar una restricción de acceso a un directorio web se lleva a cabo mediante dos operaciones. Primero, y esto sirve tanto para Apache como para NGINX, debemos crear un archivo .htpasswd que contendrá la información del usuario y contraseña al que queremos dar acceso. Por razones de seguridad, lo correcto es que este archivo no resida en la parte «pública» de la web, ya que lo podría dejar expuesto:
En los sistemas operativos basados en UNIX, como Linux o macOS, los archivos que empiezan con un punto son archivos ocultados por el sistema, es decir, no los veremos como vemos un archivo regular cuando listemos los directorios donde se encuentran. Normalmente estos archivos suelen contener información «sensible» de diferente índole, así que cuando necesitemos crear un archivo de este tipo deberemos asegurarnos de que está protegido.
¿Qué hago si no tengo permisos para escribir fuera del directorio público del sitio web?
En ese caso deberás escribir el archivo .htpasswd en la raíz publica de tu sitio web, no sin antes asegurarte de que el servidor web (Apache o NGINX) están configurados para no permitir el acceso a este tipo de archivos. En todo caso, suponiendo que no sea posible dicha comprobación o que dichos archivos no estén protegidos, si tu servidor web es Apache, podrías crear un archivo .htaccess en el mismo directorio e incluir la siguiente directiva, la cual protege el acceso a cualquier archivo cuyo nombre empiece por un punto, es decir los archivos ocultos.
<FilesMatch "^\.">
Order allow,deny
Deny from all
</FilesMatch>
Y si tu servidor web corre sobre NGINX algo así:
location ~ /\. {
deny all;
}
Aclarado esto, podemos continuar con la configuración, no sin antes apuntar que las rutas que usamos a continuación son un ejemplo que deberás adaptar en función de como está configurado tu dominio y espacio web en el servidor:
# En Linux, posiciónate en un directorio anterior al espacio web público.
# Suponiendo que tus archivos web se encuentran por ejemplo en:
# /var/www/vhosts/dominio.net/proyecto.dominio.net/public_html/
cd /var/www/vhosts/dominio.net/proyecto.dominio.net
# Ejecutamos la utilidad
htpasswd
htpasswd -c .htpasswd usuario_admin (Nos pedirá que contraseña queremos asignarle, usa una muy robusta)
# En Windows puedes encontrar una utilidad que hace lo mismo.
htpasswd.exe -c C:\path\a\passwords\.htpasswd usuario_admin
Habiendo configurado nuestro archivo .htpasswd, podemos configurar el servidor web como sigue:
En el archivo /var/www/vhosts/dominio.net/proyecto.dominio.net/public_html/.htaccess
AuthType Basic
AuthName "Área: Mi Proyecto"
AuthUserFile /var/www/html/proyecto.dominio.net/.htpasswd
Require user usuario_admin
server {
listen 443 ssl;
server_name proyecto.dominio.com;
ssl_certificate /path/a/tu/certificado.crt;
ssl_certificate_key /path/a/tu/private.key;
location /protected {
auth_basic "Área: Mi Proyecto";
auth_basic_user_file/var/www/html/proyecto.dominio.net/.htpasswd;
# Otras configuraciones
# Si estás usando PHP-FPM
fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}
Con el servidor web configurado, cuando accedamos a https://proyecto.dominio.com se nos abrirá la ventana de login.
Manejando la Autenticación HTTP básica desde PHP
Tambien podríamos usar PHP para extender algo más su funcionalidad o incluso manejarla al completo, lo que nos permitiría algo más de flexibilidad y dinamismo.
Ahora, en el index.php de nuestro proyecto podríamos tener algo como lo que sigue. Ojo, es solo un ejemplo básico! Requeriría algunas funciones más para poder usarlo sin problemas.
if (!isset($_SERVER['PHP_AUTH_USER'])) {
header('WWW-Authenticate: Basic realm="Área: Mi proyecto"');
header('HTTP/1.0 401 Unauthorized');
echo 'Texto que enviamos si el usuario presiona "Cancelar"';
exit;
} else {
$usuario = $_SERVER['PHP_AUTH_USER'];
$password = $_SERVER['PHP_AUTH_PW'];
// Aquí validas el usuario y la contraseña
if (validar_credenciales($usuario, $password)) {
echo "Bienvenido, $usuario!";
} else {
header('HTTP/1.0 401 Unauthorized');
echo 'Credenciales inválidas';
exit;
}
}
function validar_credenciales($username, $password) {
// Aquí se implementa la lógica de validación de credenciales
// Por ejemplo, comparando con valores en una base de datos
return $username === 'admin' && $password === 'secret';
}
Autenticación HTTP mediante Bearer Token y PHP (CORS)
Si nuestro proyecto a proteger fuera una API (Application programming interface o Interfaz de programación de aplicaciones) a la cual queramos acceder desde otra pagina web en otro dominio, lo que necesitaremos es implementar CORS (Cross-Origin Resource Sharing o Compartir Recursos entre Orígenes Cruzados), un mecanismo que permite solicitar recursos restringidos entre diferentes orígenes. A continuación un ejemplo muy básico de su funcionamiento, donde por un lado estaría por ejemplo un PHP en el dominio/proyecto a proteger y por otro lado y en un dominio diferente, tendremos otra pagina desde la que haríamos las solicitiudes a esta API.
En el index.php (o endpoint) de nuestro proyecto a proteger tendríamos:
<?php
header('Access-Control-Allow-Origin: https://externo.dominio.org'); // Permitir acceso desde este dominio, si se substituye por *, permitirá el acceso a la API desde cualquier dominio.
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
// responder a las preflight requests con las configuraciones de cabeceras CORS permitidas
header('Access-Control-Allow-Methods: GET, POST, OPTIONS'); // Permitir estos métodos
header('Access-Control-Allow-Headers: Content-Type, Authorization'); // Debe incluir la cabecera Authorization con el Bearer Token
header('Access-Control-Allow-Credentials: true'); // Permitir peticiones con credenciales
header('Access-Control-Max-Age: 86400'); // Especificar cuánto tiempo puede ser cacheado el resultado
http_response_code(200); // Enviar una respuesta con código de estado 200 OK
exit;
}
$headers = apache_request_headers();
if (!isset($headers['Authorization']) || strpos($headers['Authorization'], 'Bearer ') !== 0) {
header('HTTP/1.1 401 Unauthorized');
echo 'Access denied';
exit;
}
$token = str_replace('Bearer ', '', $headers['Authorization']);
if ($token !== 'YOUR_SECRET_TOKEN') {
header('HTTP/1.1 401 Unauthorized');
echo 'Invalid token';
exit;
}
// Lógica de la aplicación, que en este caso devuelve una respuesta JSON
$data = [
'message' => 'Hello, this is a protected resource!',
'timestamp' => time()
];
header('Content-Type: application/json');
echo json_encode($data);
?>
Ahora en el dominio externo desde el que queremos realizar llamadas a nuestro proyecto (API), un simple ejemplo de cómo llamamos mediante javascript a dicha API, pasando nuestro BearerToken.
document.getElementById('testApiButton').addEventListener('click', () => {
fetch('https://proyecto.dominio.net/', {
method: 'GET',
headers: {
'Authorization': 'Bearer 01-your-bearer-token'
}
})
.then(response => response.json())
.then(data => {
document.getElementById('response').innerText = JSON.stringify(data);
})
.catch(error => {
document.getElementById('response').innerText = 'Error: ' + error;
});
});
¿Qué son las preflight requests?
Las preflight requests son solicitudes automáticas enviadas por el navegador en el contexto de las peticiones CORS (Cross-Origin Resource Sharing). Estas solicitudes son realizadas antes de las peticiones principales (como GET, POST, PUT, DELETE) para verificar si el servidor permite las operaciones solicitadas desde un origen diferente.
LoginSkel
Dado que consideramos que el artículo se ha extendido demasiado, los hemos dividido en dos partes.
Sigue leyendo la segunda parte en: Protección de Interfaces Web: LoginSkel (Parte II)
Bibliografía
- Evolution of HTTP
- Everything you ever wanted to know about building a secure password reset feature (2012)
- Hypertext Transfer Protocol (HTTP) Authentication Scheme Registry (2014 – 2024)
- The God Login (2015)
- What is Bearer token and How it works? (2021)
- Bearer token out of an OAUTH setting – question about correct use of standards (2019)
- Blogthinkbig.com – Fernando Corbató, inventor de algo tan sencillo como la contraseña (2019
- OWASP: Authentication Cheat Sheet (2019-2024)
- OWASP: Session fixation (2020-2024)
Like this Post