¿Qué mejores prácticas deberían emplearse en un script de inicio de sesión PHP?

27

Quiero volver a escribir mis scripts de inicio de sesión para los sitios web de los clientes para que sean más seguros. Quiero saber qué mejores prácticas puedo implementar en esto. Los paneles de control protegidos con contraseña son abundantes, pero muy pocos parecen implicar mejores prácticas en términos de escritura de código, velocidad y seguridad.

Usaré PHP y una base de datos MYSQL.

Solía ​​usar md5, pero veo que sha256 o sha512 serían mejores (junto con hash seguro y sal).

Algunos scripts de inicio de sesión registran la dirección IP durante toda la sesión o incluso el agente de usuario, pero quiero evitarlo ya que no es compatible con servidores proxy.

También estoy un poco por detrás de las mejores prácticas en el uso de sesiones en PHP 5 (la última vez que leí fue PHP 4), por lo que algunas de las mejores prácticas con esto serían útiles.

Gracias.

baritoneuk
fuente
1
Mezcle su script en codereview.SE!
Chris
@Chris CodeReview ayuda con la claridad del código y similares. Definitivamente no debes ir a ellos por seguridad. En todo caso, casi siempre le recordarán que no "haga lo suyo", que es básicamente la misma respuesta que obtendrá en cualquier comunidad de programación en SE.
Alternatex

Respuestas:

21

Lo mejor es no reinventar la rueda. Pero, entiendo, en el mundo de PHP puede ser difícil encontrar un componente de alta calidad que ya lo haga (incluso estoy bastante seguro de que los marcos implementan tales cosas y sus implementaciones ya están probadas, sólidas, revisadas por código, etc. )

Si, por alguna razón, no puede usar un marco, aquí hay algunas sugerencias:

Sugerencias relacionadas con la seguridad

  • Usa PBKDF2 o Bcrypt si puedes . Está hecho para eso.

    Justificación: ambos algoritmos pueden hacer que el proceso de hashing sea arbitrariamente lento, que es exactamente lo que desea cuando se usan contraseñas de hashing (las alternativas más rápidas significan una fuerza bruta más fácil). Idealmente, debe ajustar los parámetros para que el proceso se vuelva más y más lento con el tiempo en el mismo hardware, mientras se lanza un hardware nuevo y más rápido.

  • Si no puede, al menos no use MD5 / SHA1. Nunca. Olvídate de eso . Utilice SHA512 en su lugar, por ejemplo. Usa sal también.

    Justificación: MD5 y SHA1 son demasiado rápidos. Si el atacante tiene acceso a su base de datos que contiene los hashes y tiene una máquina (ni siquiera particularmente poderosa), forzar una contraseña con fuerza bruta es rápido y fácil. Si no hay sales, las posibilidades de que el atacante encuentre la contraseña real aumenta (lo que podría causar un daño adicional si la contraseña se reutilizara en otro lugar).

  • En PHP 5.5.0 y posterior, use password_hashy password_verify.

    Justificación: llamar a una función proporcionada por el marco es fácil, por lo que se reduce el riesgo de cometer un error. Con esas dos funciones, no tiene que pensar en parámetros diferentes como el hash. La primera función devuelve una sola cadena que luego puede almacenarse en la base de datos. La segunda función usa esta cadena para la verificación de contraseña.

  • Protégete de la fuerza bruta . Si el usuario envía una contraseña incorrecta cuando ya envió otra contraseña incorrecta hace 0.01 segundos, es una buena razón para bloquearla. Si bien los seres humanos pueden escribir rápido, probablemente no puedan ser tan rápidos.

    Otra protección sería establecer un límite de fallas por hora. Si el usuario envió 3600 contraseñas incorrectas en una hora, 1 contraseña por segundo, es difícil creer que se trata de un usuario legítimo.

    Justificación: si sus contraseñas están codificadas de forma insegura, la fuerza bruta puede ser muy efectiva. Si las contraseñas se almacenan de forma segura, la fuerza bruta sigue desperdiciando los recursos del servidor y el ancho de banda de la red, lo que causa un menor rendimiento para los usuarios legítimos. La detección de fuerza bruta no es fácil de desarrollar y acertar, pero para cualquier sistema que no sea pequeño, vale la pena.

  • No solicite a sus usuarios que cambien sus contraseñas cada cuatro semanas. Esto es extremadamente molesto y disminuye la seguridad, ya que fomenta la seguridad basada en post-it.

    Justificación: la idea de que obligar a cambiar las contraseñas cada n semanas protege al sistema de la fuerza bruta es errónea. Los ataques de fuerza bruta suelen tener éxito en segundos, minutos, horas o días, lo que hace que los cambios mensuales de contraseña sean irrelevantes. Por otro lado, los usuarios son malos para recordar contraseñas. Si, además, necesitan cambiarlos, intentarán usar contraseñas muy simples o simplemente anotarán sus contraseñas en los post-it.

  • Auditar todo, siempre. Almacene inicios de sesión, pero nunca almacene contraseñas en el registro de auditoría. Asegúrese de que el registro de auditoría no pueda modificarse (es decir, puede agregar datos al final, pero no modificar los datos existentes). Asegúrese de que los registros de auditoría estén sujetos a copias de seguridad periódicas. Idealmente, los registros deberían almacenarse en un servidor dedicado con accesos muy restrictivos: si otro servidor es pirateado, el atacante no podrá borrar los registros para ocultar su presencia (y la ruta tomada durante el ataque).

  • No recuerde la credencial de usuario en las cookies, a menos que el usuario solicite hacerlo (la casilla de verificación "Recordarme" debe estar desmarcada de forma predeterminada para evitar errores humanos).

Sugerencias de facilidad de uso

  • Deje que el usuario recuerde la contraseña si lo desea, incluso si la mayoría de los navegadores ya tienen esta función.
  • No utilice el enfoque de Google cuando, en lugar de pedir el nombre de usuario y la contraseña, a veces se le pide al usuario solo la contraseña , el nombre de usuario ya se muestra en un <span/>. Los navegadores no pueden completar el campo de contraseña en este caso (al menos Firefox no puede hacer eso), por lo que obliga a cerrar la sesión y luego iniciar sesión con un formulario normal, completado por el navegador.
  • No use ventanas emergentes habilitadas para JavaScript para iniciar sesión. Rompe las características de recuperación de contraseña de los navegadores (y apesta en todos los casos).
  • Deje que el usuario ingrese su nombre de usuario o su dirección de correo . Cuando me registro, a veces el nombre de usuario que quiero ingresar ya está en uso, así que debo inventar uno nuevo. Tengo todas las posibilidades de olvidar este nombre en dos horas.
  • Mantenga siempre un enlace a la función "Olvidé mi contraseña" cerca del formulario de inicio de sesión. No lo muestre solo cuando el usuario no pudo iniciar sesión: el usuario que no recuerda su contraseña no tiene idea de que debe enviar una incorrecta para ver el enlace "Olvidé mi contraseña".
  • No use la seguridad por post-it .
  • No invente reglas estúpidas relacionadas con la contraseña para debilitarla . Ejemplo: "Su contraseña debe comenzar con una letra minúscula"; "Su contraseña no puede contener espacios".
Arseni Mourzenko
fuente
No se encuentra con bcrypt. ¿Cuál es su razonamiento para recomendar eso? ¿Por qué no md5 / sha1? Gracias por el resto de las sugerencias!
baritoneuk
El razonamiento es simple: bcryptlo realizan personas altamente calificadas. También se ha probado, revisado, etc. Así que no hay razón para implementar lo mismo usted mismo. Es como crear su propio algoritmo de criptografía para su sitio web. * "¿Por qué no md5 / sha1?": Ver por ejemplo en.wikipedia.org/wiki/MD5#Security
Arseni Mourzenko
55
bcrypt es lento y según lo mencionado implementado por profesionales. md5 es un algoritmo hash, no un cifrado que no es exactamente lo mismo. El hash de un archivo rápido es bueno, md5 se usaría aquí ... el hashing de una contraseña no necesita ser tan rápido, bcrypt puede ayudar aquí. La razón por la que digo esto es que la fuerza bruta se vuelve más difícil cuando el algoritmo de la cripta es "lento".
Chris
2
@baritoneuk: el mínimo es genial. Pero no puedo contar cuántos sitios he visitado que tenían restricciones, como longitudes máximas , sin caracteres no alfanuméricos, etc. , solo lo almacenan en su base de datos en claro.
Carson63000
1
@Brian Ortiz: no siempre. A veces es una buena idea establecer un límite. Si no lo hace, ¿prueba si su aplicación funciona durante algún tiempo? ¿Cómo? Si no lo prueba, ¿cómo puede estar seguro de que está funcionando? En cambio, al establecer un límite a un valor razonable, como 100, puede probar fácilmente una contraseña de 100 caracteres.
Arseni Mourzenko
6
  1. Su sitio debe usar HTTPS. En ningún momento debe presentar una página de inicio de sesión o aceptar inicios de sesión desde una conexión no cifrada.
  2. Sus cookies deberían restringirse a HTTP solamente y restringirse a conexiones seguras si usa HTTPS.
  3. El proceso de inicio de sesión no debería demorar menos de 2 segundos (1 si cree que 2 segundos son demasiado largos). Use un hash seguro al almacenar y verificar contraseñas, y use una sal que sea más difícil de adivinar. Úselo bcryptsi es posible. De lo contrario, use algún otro tipo de hash iterado.
  4. Nunca jamás componer sus consultas a la base de ninguna manera que requiere el uso de funciones como mysql_real_escape_string. Nunca use la concatenación de cadenas para elaborar su consulta. Use consultas preparadas y parametrizadas. La mayoría, si no todos, los controladores DB para PHP lo admiten. Que si no sabe cómo hacerlo, pasar algún tiempo en aprender cómo prepare, bindy executeutilizando cualquier base de datos que está utilizando. Es la única forma segura de protegerse contra la inyección de SQL. PDO apoya esto.
  5. Anime a sus usuarios a no usar la misma contraseña que usan para su correo electrónico. Recuérdeles que si su sitio se ve comprometido y si usan la misma contraseña en ambos lugares, alguien puede secuestrar su correo electrónico.
Greyfade
fuente
+1 para HTTPS, ya que cada vez hay más tráfico a través de redes WiFi públicas. Pero no estoy de acuerdo con el punto 3: 2 segundos es demasiado largo desde el punto de vista de los usuarios. 0.5 s. está bien, especialmente si se utilizan otras técnicas de fuerza bruta.
Arseni Mourzenko
Estoy confundido en el punto número 4. ¿Cómo se escapa de la convención de escapar de datos con mysql_real_escape_string? No se debe confiar en todos los datos enviados por el usuario y filtrarlos en consecuencia.
Chris
@chrisw: Sí, todas las entradas del usuario deben manejarse como si fueran maliciosas. Pero cuando utiliza consultas parametrizadas, es, sin excepción, la mejor manera de detener la inyección de SQL . Si no está utilizando consultas preparadas y parametrizadas, es vulnerable a la inyección de SQL. mysql_real_escape_stringes falsa seguridad, no es una garantía. Las consultas parametrizadas son.
greyfade
Estoy de acuerdo ahora después de leer un poco más. La función: mysql_real_escape_string es generalmente segura en su mayor parte, no lo consideraría una gran responsabilidad, pero puedo ver cómo el uso de las declaraciones preparadas es mucho más eficiente.
Chris
1
Puedes usar PDO.
Felix G
1

Use un hash unidireccional salado (preferiblemente SHA512 usando http://au2.php.net/manual/en/function.hash.php ) ... de esa manera, si alguien piratea su base de datos, incluso si conocen la sal , no pueden extraer las contraseñas (sin una tabla de arcoíris, que sería suficiente para SHA512).

Usa HTTPS.

No envíe confirmaciones de nombre de usuario / contraseña por correo electrónico al usuario cuando se registre.

Si permite las cookies para 'recordarme', agregue un inicio de sesión adicional para acceder a las funciones de administración y edición de perfil.

Si un usuario se equivoca con una contraseña, no diga que se equivocó (diga que se equivocó de nombre de usuario o contraseña en todo momento), de esa manera, hay menos pistas sobre los nombres de usuario válidos.

Pruebe e implemente el nombre de usuario frente al inicio de sesión: de esa forma, menos usuarios mostrarán su nombre de inicio de sesión en las listas de usuarios.

HorusKol
fuente
Totalmente de acuerdo con la parte acerca de no enviar correos electrónicos en texto sin formato por correos electrónicos. He perdido la cuenta de las veces que los sitios web hacen esto. Si tiene 1 contraseña para todos los sitios (que afortunadamente no tengo), entonces todos los sitios web están potencialmente comprometidos. Es probable que dichos sitios web también almacenen contraseñas en texto plano. suspiro
baritoneuk
1
  • Nunca use algoritmos de hash MD5 o SHA1. Siempre se adhieren a los más nuevos como SHA512
  • Utilice siempre una sal generada al azar fuerte
  • Nunca envíe por correo electrónico a sus usuarios sus contraseñas, incluso en caso de "Olvidé mi contraseña"
  • Nunca use las funciones mysql_ *. Se han depreciado por mucho tiempo. Apegarse a la DOP
  • Nunca almacene contraseñas en sesiones o cookies
Robin Thomas
fuente
0

Aquí hay un pequeño consejo: asegúrese de que no se pueda acceder a sus cookies con JS para evitar el robo, consulte Protección de sus cookies: artículo de HttpOnly de Jeff Atwood

... A través de una construcción inteligente, la URL malformada solo logra pasar el desinfectante. El código renderizado final, cuando se ve en el navegador, carga y ejecuta un script desde ese servidor remoto.

... quien carga esta página de perfil de usuario inyectada por script ha transmitido involuntariamente las cookies de su navegador a un servidor remoto malvado.

Como ya hemos establecido, una vez que alguien tiene las cookies de su navegador para un sitio web determinado, esencialmente tiene las claves del reino para su identidad allí ...

James
fuente