¿Podría este tipo de autenticación ser atacada?

21

No soy un experto en ciberseguridad, solo soy un webmaster y me pregunto si este tipo de autenticación sería peligrosa. Pruebo la contraseña como esta en el lado del servidor usando PHP:

if (isset($_POST['pass_word']) AND $_POST['pass_word'] ==  $passwd)

$passwd proviene de mi base de datos PostgreSQL. Pensé que al menos no me arriesgo a la inyección de SQL. ¿Me arriesgo a algún otro tipo de inyección? Si es una forma peligrosa de autenticarse, explique por qué.

    
pregunta kevin ternet 29.12.2016 - 15:59
fuente

2 respuestas

68

Parece que estás guardando las contraseñas de forma clara, lo que es una mala idea. Debe asegurarse de que las contraseñas estén protegidas usando, como mínimo, password_hash() y password_verify() , que utiliza bcrypt bajo el capó. Esto es simple, fácil, seguro y perfectamente aceptable para la mayoría de los escenarios.

Alternativamente, puedes usar un método ligeramente más seguro, como Argon2 , que ganó el Password Hashing Competition y es resistente al craqueo de CPU y GPU, y también apunta a minimizar el potencial de ataques de canal lateral. Hay un un artículo que explica cómo usar Argon2 como parte del PHP de libsodium. wrapper, o directamente usando la biblioteca "Halite" de Paragon, que ofrece Argon2 con cifrado simétrico en la parte superior para evitar que el acceso solo a la base de datos proporcione hashes utilizables, debido al hecho de que la clave simétrica se almacena en el disco del servidor como un archivo. Esta opción es más complicada, pero ofrece cierta seguridad adicional si eres realmente paranoico. Sin embargo, sugiero evitar esto si no estás familiarizado con el desarrollo seguro, ya que aumentan las posibilidades de que arruines algo.

También recomendaría usar === para evitar casos extraños de falsa igualdad usando matrices en consultas URL o nulas.

    
respondido por el Polynomial 29.12.2016 - 16:15
fuente
15

La respuesta de Polynomial es acertada. Quería señalar otras posibles vulnerabilidades: precedencia del operador, facilidad de revisión y facilidad de prueba.

Algo de la forma foo AND bar == baz es vulnerable a que la precedencia sea incorrecta. Me gustaría analizar que, en resumen, hay una larga historia de medidas de seguridad que se han frustrado debido a condiciones compuestas y prioridad.

Ahora, no hay nada mal con lo que has escrito, pero es muy fácil hacerlo mal. En la mayoría de los idiomas está bien porque los operadores de comparación como == tienen mayor prioridad que los operadores lógicos como AND , por lo que su condición se lee como (observe los paréntesis explícitos):

isset($_POST['pass_word']) AND
  ($_POST['pass_word'] ==  $passwd)

... pero es muy fácil hacerlo mal. Por ejemplo, AND y && no tienen la misma prioridad. ¿Qué sucede si en lugar de usar isset usted (o alguien que viene después de usted) usó un método abreviado lógico, o para establecer un valor predeterminado? (Esto no es equivalente a isset , es solo un ejemplo de la precedencia que causa tonterías de seguridad)

$_POST['pass_word'] || '' ==  $passwd

Esta línea gotcha es en realidad esto:

$_POST['pass_word'] || ('' ==  $passwd)

Ahora cualquiera que tenga una contraseña en blanco, tal vez por un error al recuperarla, siempre iniciará sesión.

Cuando se trata de código seguro, haga explícitos sus parens para hacer conocer su intención al revisor y al programa.

Mejor aún, evite las condiciones compuestas por completo en el código de seguridad. No puedes joder con lo que no usaste. Por ejemplo, este código aprovecha la encapsulación y retorno temprano para proporcionar métodos de revisión y prueba muy fáciles.

// This deals with input and normalizing it.
function can_login($user) {
    if( !isset($_POST['pass_word']) ) {
        // Or it could throw an exception to give the
        // caller more information about why they didn't login
        return false;
    }

    return check_password($user, $_POST['pass_word']);
}

// This only deals with checking a password.
// Don't use this, it still has all the flaws Polynomial
// pointed out.
function check_password($user, $check) {
    // get_password() should throw an exception if it cannot
    // find that user's password to avoid accidentally thinking
    // their password is false or 0 or '' or something. This
    // avoids relying on the caller doing the check for failure.    
    return $check === get_password($user);
}

Puede que no sea el mejor método, pero es pequeño y fácil de probar y revisar. Esto separa los detalles de iniciar sesión en un usuario (lo que podría ser más que una verificación de contraseña, como si fueran un usuario válido) de verificar su contraseña. Cualquier problema puede detectarse y solucionarse sin tener que revisar el resto del código de inicio de sesión. Un revisor verificará get_password y se utilizarán todos los lugares check_password y can_login .

No puedo enfatizar lo suficiente lo importante que es aislar los componentes seguros en una pieza tan pequeña como sea posible, utilizando el código más simple posible, para facilitar su revisión y prueba.

BONUS : hay algo que pensé hacer, pero decidí que haría que el código menos sea seguro.

Mi idea original era tener una función que devolviera múltiples valores: no proporcionaron una contraseña, su contraseña era incorrecta, su contraseña era correcta. Esto permitiría a la persona que llama saber la diferencia entre "la contraseña es incorrecta" y "no proporcionaron una contraseña, algo está mal".

function can_login($user) {
    if( !isset($_POST['pass_word']) ) {
        return NO_PASSWORD;
    }

    $password = get_password($user);
    if( $_POST['pass_word'] === $password ) {
        return YES;
    }
    else {
        return NO;
    }
}

Y luego lo llamas así:

$rslt = can_login($user);

if( $rslt == YES ) {
    echo "Login!\n";
}
else if( $rslt == NO ) {
    echo "Wrong password.\n";
}
else if( $rslt == NO_PASSWORD ) {
    echo "No password given.\n";
}

El problema es que hacer que la rutina sea más complicada. Peor aún, si no lees los documentos, pensarías que era esto:

if( can_login($user) ) {
    echo "Yes!\n";
}
else {
    echo "No!\n";
}

Dado que can_login siempre devuelve un valor verdadero, ahora cualquiera puede iniciar sesión.

Esto hace que el punto de partida sea: haga que su código de seguridad sea lo más simple e infalible posible para el revisor, para el evaluador y para la persona que llama. En este caso, las excepciones son el camino correcto, ya que proporcionan un canal para obtener más información y, al mismo tiempo, mantienen a can_login como un simple booleano. Si olvida comprobar que el programa puede fallar, pero el inicio de sesión falla.

    
respondido por el Schwern 29.12.2016 - 20:53
fuente

Lea otras preguntas en las etiquetas