Formulario de autenticación

Consideraciones de seguridad durante la autenticación

Uno de los vectores más prolíficos de vulnerabilidades de seguridad en aplicaciones y sitios web es la autenticación: inicio de sesión. Obtener acceso a datos y funcionalidades importantes podría ser muy rentable, por lo que comprometer las credenciales de los usuarios es un objetivo principal, ya que he visto en el pasado. Aquí hay un par de trampas que debe tener en cuenta al diseñar su interfaz de autenticación.

Controles de contraseña adecuados

¡Obligue a sus usuarios a seleccionar contraseñas seguras! Una buena política de contraseñas hace que los ataques de fuerza bruta y de diccionario, es decir, adivinar la contraseña de un usuario a través de medios manuales o automatizados, sean poco prácticos. La restricción más importante es establecer la longitud mínima permitida en 8 caracteres, y esto debe hacerse en todo momento. Aparte de eso, aquí hay algunas restricciones comunes que intentan garantizar una contraseña adecuada con una complejidad mínima:

  • Una mezcla de letras mayúsculas y minúsculas.
  • Una mezcla de letras y números.
  • Al menos un carácter especial: !@#$%^&*() etc.

Aún así, ha sido encontrado por investigación patrocinada por el NIST de EE. UU. ¹ que los usuarios tienden a responder a estas restricciones adicionales de una manera muy predecible. Por ejemplo, si un usuario tenía la intención de usar la contraseña "contraseña", pero encontró las restricciones anteriores, probablemente cambiaría su contraseña a la predecible "¡Contraseña1!".
El NIST sugiere comparar la contraseña con una lista negra de contraseñas inaceptables y rechazar las contraseñas que coincidan. Cotizar:

Esta lista debe incluir contraseñas de corpus de infracciones anteriores, palabras de diccionario y palabras específicas (como el nombre del servicio en sí) que es probable que elijan los usuarios. Dado que la elección de contraseñas por parte del usuario también se regirá por un requisito de longitud mínima, este diccionario solo necesita incluir entradas que cumplan con ese requisito.

NIST
Mensaje de error que solicita al usuario que elija una contraseña más segura

Implementarlo usted mismo puede ser una tarea desalentadora, pero afortunadamente, existen servicios y bibliotecas diseñados para ayudarlo. los zxcvbn biblioteca disponible en muchos idiomas es una opción. los Pwned La base de datos de contraseñas contiene cientos de millones de contraseñas descifradas, contra las cuales puede verificar la contraseña elegida por el usuario. Puede alojarlo localmente o utilizar un API para comunicarse con él (¡usando hashes, por supuesto!).

Si espera que sus usuarios hablen idiomas que utilicen un alfabeto no latino o un latín extendido (con signos diacríticos o letras especiales como ø, æ, œ), teóricamente permitir la paleta completa de caracteres Unicode mejorará la seguridad. Esto evitará la mayoría de los ataques de fuerza bruta, que asumen los estándares ASCII de todos modos, y también aumentará enormemente el número de combinaciones posibles². Si bien esto puede sonar como una obviedad, en la práctica, debe tener en cuenta que es posible que no maneje UTF-8 correctamente (por ejemplo, manejar caracteres canónicamente equivalentes). El manejo inadecuado de Unicode podría reducir la usabilidad e incluso afectar negativamente a la seguridad. Solo haz esto si tienes confianza en tus habilidades.
Como última nota, es importante establecer una restricción de longitud máxima. No hacerlo puede exponerlo a una contraseña larga DOS ataque. 64 caracteres es un buen límite superior.

Hash de contraseña

La primera regla del almacenamiento de credenciales es nunca almacenar contraseñas sin procesar en el servidor. En su lugar, almacene hashes para la autenticación. Hashing es un método mucho mejor que restringir el uso de ciertos caracteres especiales.

Para dejar las cosas claras: Hashing es no encriptación Hashing es una función unidireccional que asigna cada contraseña a un hash único o casi siempre único (sí, se han descubierto hashes que chocan con ciertas funciones hash, y esta es una vector de ataque, por lo que la elección de la función es importante). Mientras que el cifrado se puede revertir para obtener la contraseña original. Con ese fin, las contraseñas sin formato nunca deben almacenarse cifradas en el servidor.

Para almacenar una contraseña, debe codificarse en el servidor (podría llegar a 10,000 iteraciones). El hashing del lado del cliente solo tiene beneficios marginales; un atacante man-in-the-middle (MITM) puede simplemente tomar el hash y enviarlo al servidor para iniciar sesión como un usuario legítimo. Podría evitar que la contraseña real se filtre y se reutilice para abrir otros servicios, pero para la seguridad de aplicaciones específicas, tiene un efecto bajo y agrega complejidad. El hash del lado del servidor es más poderoso ya que un atacante no conoce su configuración de hash en el servidor, por lo que incluso si todos sus hashes se filtraron, aún no puede autenticarse. Esto debería ser visto como obligatorio.

Incluso si se toman todos los pasos anteriores, es importante prepararse para la posible causa de la fuga de su base de datos junto con los hashes: un atacante puede usar los hashes por fuerza bruta. Esto se hace por

  1. Hashing una contraseña de candidato.
  2. Probar si el hash coincide con una contraseña en la base de datos.

Por lo tanto, es importante salar los hachís. Salar es concatenar una cadena aleatoria a la contraseña, codificar el resultado y luego almacenar el hash junto con la sal en la base de datos. Entonces, el atacante no puede verificar el hash de una posible contraseña con cada hash en la base de datos. En cambio, tiene que agregar la sal de un usuario a la contraseña propuesta, codificarla y luego probarla contra el hash del usuario, obligándolo a descifrar un hash a la vez. Las funciones modernas de hashing de contraseñas generalmente saltan automáticamente la entrada antes del hashing.

El mejor estiramiento clave algoritmos disponibles hoy para las contraseñas son Argón2id ³, que ganó el 2015 Competencia de hash de contraseñay PBKDF2 , que es recomendado por NIST. Estos usan funciones hash simples comunes como SHA256 como 'backends', pero las aplican miles de veces y agregan sal automáticamente. Por lo tanto, no es necesario use directamente funciones hash simples como SHA o MD5 no diseñadas para contraseñas, ni intente construir sus propios métodos de estiramiento de claves, es una tontería.

TLS y autenticación

Otra precaución obligatoria durante la autenticación es asegurarse de que toda la transacción pase por TLS, lo que hace que los ataques HTTP MITM sean significativamente más difíciles al requerir certificados.

TLS (Transport Layer Security) es el protocolo sucesor de SSL (Secure Socket Layer). En resumen, TLS primero incluye un procedimiento de reconocimiento que requiere que el servidor envíe un certificado de una autoridad confiable que avale su legitimidad. A continuación, las claves de cifrado se intercambian mediante el Método Deffie-Hellman (se generan tanto claves públicas como privadas). Luego, el resto de la sesión se cifra y descifra con estas claves.

Cómo implementar correctamente TLS para su servicio es un tema en sí mismo, por lo que no lo abordaremos hoy, pero puede leer por sí mismo el y las mejores prácticas.

Autenticación de múltiples factores

Con mucho, la mejor manera de evitar contratiempos de seguridad de contraseñas es recomendar o requerir la autenticación de múltiples factores (MFA). Esto significa usar uno o más métodos para autenticar al usuario. Esto puede incluir el uso de correo electrónico, SMS, códigos QR, escaneos biométricos y aplicaciones de autenticación. La forma más común de hacerlo es mediante correo electrónico o SMS para enviar al usuario una OTP que deberá ingresar para poder acceder. Entonces, un atacante tendría que tener acceso al correo electrónico del usuario para obtener acceso a su servicio.
Esto abre otros vectores de ataque, pero para la mayoría de las aplicaciones debería ser suficiente.

Una forma de reducir los gastos generales de MFA tanto para el desarrollador como para el usuario es requerir MFA solo durante las transacciones importantes. por ejemplo, cuando

  • Cambio de contraseñas.
  • Realización de transacciones monetarias.
  • Deshabilitar MFA.
  • Obtener acceso de administrador.

También es posible exigir MFA solo cada dos días, después de un largo período sin actividad o después de intentos fallidos de inicio de sesión (con el beneficio de evitar la fuerza bruta). 

Prevención de bots

Una forma de evitar los intentos de inicio de sesión automatizados, ya sea como parte de un ataque de fuerza bruta o DOS, es implementar tiempos de espera después de varios intentos fallidos. Esto hace que los ataques de fuerza bruta y de diccionario tomen demasiado tiempo para ser factibles. Otra posibilidad es solicitar al usuario que resuelva un captcha durante cada inicio de sesión, o quizás después de un número predeterminado de intentos fallidos. 

reCAPTCHA resuelto
reCAPTCHA

Posiblemente, el servicio de captcha de terceros más popular es reCAPTCHA de Google, pero ha visto alguna crítica por sus políticas de privacidad. reCAPTCHA también obliga efectivamente a sus usuarios a aceptar un acuerdo de términos de servicio de terceros para usar su servicio, lo que algunos ven como éticamente problemático. Sin embargo, usar reCAPTCHA es probablemente la forma más fácil y confiable de evitar el spam o los bots de fuerza bruta. Hay otras soluciones de Captcha en línea, como hCaptcha, pero como soluciones de terceros, todas podrían sufrir los mismos problemas que reCAPTCHA.

Mensajes de error

Para evitar que los atacantes puedan determinar si un nombre de usuario es válido o no, se considera una buena práctica tener un único mensaje de error ambiguo, independientemente de si el error se debe a

  • La contraseña era incorrecta.
  • La cuenta no existe.
  • La cuenta está bloqueada, deshabilitada o prohibida.

Un mensaje de error adecuado y seguro sería algo como

"Login failed; Invalid user ID or password."

La restauración de la cuenta tampoco debe reconocer si el proceso fue un éxito o no, sino enviar respuestas como

"If that email address is in our database, we will send you an email to reset your password."

Y

"A link to activate your account has been emailed to the address provided."

Respectivamente.

Incluso si sus mensajes de error son vagos, un atacante tenaz puede intentar comparar las discrepancias en el tiempo de respuesta para determinar si la contraseña es incorrecta o si la cuenta no existe. Por ejemplo, este pseudocódigo

if (user_exists(username)) {
    String password_hash = hash(password);
    bool valid = lookup_credentials(username, password_hash);
    if (!valid) {
        return Error("Invalid Username or Password!");
    }
} else {
   return Error("Invalid Username or Password!");
}

Regresará más rápido si el usuario no existe, lo que el atacante puede explotar, mientras que este código

String password_hash = hash(password);
bool valid = lookup_credentials(username, password_hash);
if (!valid) {
   return Error("Invalid Username or Password!");
}

Tomará aproximadamente la misma cantidad de tiempo en ambos casos.

Autenticación de inicio de sesión único

En los últimos años, se ha puesto de moda un método de autenticación conocido como inicio de sesión único. Cuando un usuario desea iniciar sesión en su servicio, se le solicita que elija un proveedor de identidad de confianza, generalmente una megacorporación que ofrece ese servicio, como Google, Amazon o Facebook. Luego, el usuario es redirigido al sitio web del proveedor de identidad y debe iniciar sesión con su cuenta allí. Luego, el proveedor de identidad envía a su servicio una garantía de que el usuario está autorizado, generalmente en forma de esquema XML, que también proporciona detalles sobre la identidad del usuario. Hay un par de protocolos para hacer esto, pero el más popular con diferencia es OpenID.

El principal beneficio es la facilidad de uso. El usuario sólo tiene que mantener una única cuenta con una única contraseña para acceder a muchos servicios diferentes. Además, si el usuario ya inició sesión en esa cuenta, no necesita volver a ingresar sus credenciales. Esto también permite al usuario recordar una sola contraseña para todas sus cuentas. Otro beneficio para el desarrollador es la reducción de la necesidad de encargarse de la autorización. Más bien, lo delega a un tercero de confianza como Google.

Pero hay algunos inconvenientes de seguridad en ese enfoque: el más evidente es el hecho de que si se viola la cuenta del proveedor de identidad del usuario, el atacante también puede acceder a una miríada de otros servicios. Esto tiene el mismo efecto que usar la misma contraseña y nombre de usuario en todas las cuentas, una práctica que, sin embargo, muchos usuarios realizan de todos modos.

Muchos sitios web modernos ofrecen con frecuencia SSO y cuentas tradicionales de un solo servicio, dejando la elección al usuario.


¹ Instituto Nacional de Normas y Tecnología


² Hay 143,859 caracteres Unicode disponibles. Esto significa que hay un máximo de 143,8598 ≈ 1.83 × 1041 posibles combinaciones de 8 caracteres. Comparar con el 1288 = 7.2 × 1016 combinaciones de ASCII.

³ ragon2id debe usarse con las siguientes configuraciones: 

  • m=37 MB, t=1, p=1
  • m=15 MB, t=2, p=1

Donde m es la memoria mínima, t es el número de iteraciones y p es el grado de paralelismo. Son equivalentes en seguridad, pero el primero consume más memoria pero exige menos procesador, y viceversa. Recuerda usar el Aragon2id variante.

⁴Al hacer hash con PBKDF2, el número de iteraciones debe establecerse en función del algoritmo hash interno utilizado. Las siguientes sugerencias son equivalentes en seguridad:

  • PBKDF2-HMAC-SHA1: 720,000 XNUMX iteraciones
  • PBKDF2-HMAC-SHA256: 310,000 XNUMX iteraciones
  • PBKDF2-HMAC-SHA512: 120,000 XNUMX iteraciones
Ir al contenido