2010-04-22 34 views
15

He leído sobre cómo prevenir ataques de CSRF en los últimos días. Voy a actualizar el token en cada carga de página, guardar el token en la sesión y hacer una verificación al enviar un formulario.PHP - CSRF - ¿Cómo hacer que funcione en todas las pestañas?

Pero, ¿y si el usuario tiene, digamos que 3 pestañas abiertas en mi sitio web, y solo almaceno el último token en la sesión? Esto sobrescribirá el token con otro token y algo de post-acción va a fallar.

¿Necesito almacenar todos los tokens en la sesión, o hay una mejor solución para hacerlo funcionar?

Respuesta

29

Sí, con el enfoque de tokens almacenados tendría que conservar todos los tokens generados por si volvían en cualquier momento. Un solo token almacenado no solo falla para múltiples pestañas/ventanas del navegador sino también para navegación hacia atrás/adelante. Por lo general, desea administrar la posible explosión de almacenamiento caducando tokens antiguos (por edad y/o cantidad de tokens emitidos desde entonces).

Otro enfoque que evita por completo el almacenamiento de tokens es emitir un token firmado generado utilizando un secreto del lado del servidor. Luego, cuando recuperes el token, puedes verificar la firma y, si coincide, sabes que la has firmado. Por ejemplo:

// Only the server knows this string. Make it up randomly and keep it in deployment-specific 
// settings, in an include file safely outside the webroot 
// 
$secret= 'qw9pDr$wEyq%^ynrUi2cNi3'; 

... 

// Issue a signed token 
// 
$token= dechex(mt_rand()); 
$hash= hash_hmac('sha1', $token, $secret); 
$signed= $token.'-'.$hash; 

<input type="hidden" name="formkey" value="<?php echo htmlspecialchars($signed); ?>"> 

... 

// Check a token was signed by us, on the way back in 
// 
$isok= FALSE; 
$parts= explode('-', $_POST['formkey']); 
if (count($parts)===2) { 
    list($token, $hash)= $parts; 
    if ($hash===hash_hmac('sha1', $token, $secret)) 
     $isok= TRUE; 
} 

Con esto, si obtienes un token con una firma coincidente, sabes que la has generado. Eso no es de mucha ayuda en sí mismo, pero entonces usted puede poner cosas extras en el token que no sea la aleatoriedad, por ID de usuario de ejemplo:

$token= dechex($user->id).'.'.dechex(mt_rand()) 

... 

    if ($hash===hash_hmac('sha1', $token, $secret)) { 
     $userid= hexdec(explode('.', $token)[0]); 
     if ($userid===$user->id) 
      $isok= TRUE 

Ahora cada envío del formulario tiene que ser autorizada por el mismo usuario que recogió el forma, que prácticamente derrota a CSRF.

Otra cosa que es una buena idea poner un token es un tiempo de caducidad, por lo que un compromiso momentáneo del cliente o un ataque MitM no pierde un token que funcionará para ese usuario para siempre, y un valor que cambia al restablecer la contraseña, de modo que cambiar la contraseña invalide los tokens existentes.

+0

¿Pero cómo hacer que funcione en todas las ventanas? –

+0

Er, sí, esto funcionará globalmente. Varias pestañas y ventanas múltiples estarán bien. – bobince

+0

@bobince No, la sesión se sobrescribirá? –

1

Puede simplemente usar un token que es persistente para la sesión actual o incluso para el usuario (por ejemplo, un hash del hash de la contraseña del usuario) y no puede ser determinado por un tercero (utilizando un hash de la IP del usuario) malo por ejemplo).

Entonces no tiene que almacenar posiblemente toneladas de tokens generados y, a menos que la sesión expire (lo que probablemente requeriría que el usuario vuelva a iniciar sesión de todos modos), el usuario puede usar tantas pestañas como desee.

Cuestiones relacionadas