Protección contra CSRF en PHP
La protección contra CSRF en PHP tiene que ser una de las prioridades en tu sitio web.
Funcionamiento:
Por lo general esto se hace con una sesión y un token random creado, este token será pasado con en nuestro form y luego se comparará con el token de sesión, si el token de sesión es diferente no se procesará nada de lo que fue enviado por el formulario
¿Qué es un ataque CSRF?
CSRF es el acrónimo de Cross-Site Request Forgery, en este tipo de ataque los atacantes pueden aprovecharse de las sesiones activas de los usuarios para ejecutar acciones no autorizadas en su nombre.
Estos ataques generalmente se hacen desde otros sitios, aunque también podrían hacerse desde una app de escritorio o correo, estos ataques se realizan contra los formularios de tu web.
Vea que aquí estamos viendo el ataque partiendo de que nosotros somos los dueños de la página, en otras palabras está pasando algo similar a lo siguiente:
Protección CSRF en PHP
El crear un token que siempre cambie, así sabremos que el usuario está haciendo los cambios desde nuestra página.
Para crear el token podrías hacer esto:
$token = bin2hex(random_bytes(32)); // crea 64 caracteres
Por lo general, el token tiene un tiempo de expiración. Esto lo puedes hacer en la base de datos si es que almacenas el token ahí. Por poner un ejemplo, Laravel almacenaba el token en la tabla de usuarios, podrías poner un tiempo de expiación ahí.
//1 hora o 2 no mas $expires_at = time() + 3600;
Utiliza un hash para el token, recuerda que cuando encriptes este tiene que ser rápido de desencriptar pero seguro. Un hash 256 estará bien.
De esta manera tendríamos 2 columnas más en la tabla de usuarios: token(hash256)(32bytes) y expires_at(date)
Antes de seguir deberíamos pensar cuando la expiración y él toquen debe ser chequeado.
Claramente, el token es chequeado antes de procesar la data del formulario.
¿Pero y la expiación de este?
Puntos a tener en cuenta:
- Si chequeo antes el valor de expires_at estaríamos entrando a la base de datos constantemente.
- Si chequeamos en el archivo que procesara la data no tendría sentido porque ya habríamos pasado el formulario.
¿talvez debería tener la expiración del token en una COOKIE o en una sesión no?
setcookie("TokenExpires", $expires_at, [ 'expires' => $expires_at, 'httponly' => true, 'secure' => true, // al usar HTTPS(el uso de HTTPS es recomendado por google) 'samesite' => 'Strict' ]);
De esta manera podríamos ir chequeando cuando expira y rehacer el token.
¿Cómo lo hacen los Frameworks? Por ejemplo, Laravel utiliza sesiones para esto, además de hacer uso de middlewares para asegurarse de que es el usuario legítimo.
Seguridad a tener en cuenta para la protección contra CSRF en PHP
¿Por qué no hacer una cookie con el token y su expiración solamente?
Si hicieras tal cosa no sería tan seguro, de hecho sería aún más seguro usar el expires_at en una sesión también.
Aun asi no creas que las sesiones son seguras por si solas debes de tener en cuenta algunas cosas como por ejemplo:
php.ini file#las dos siguientes hoy en dia son absolutamente necesarias session.cookie_httponly=On session.cookie_secure=On #session.use_strict_mode esta desactivado por defecto pero deberias tenerlo en on session.use_strict_mode=On
en caso de no tenerlas definidas necesitarás definirlas con ini_set en tu proyecto PHP
Algo que si debes de tener en cuenta es la regeneración de ids de sesion, ten en cuenta que atravez de uno de los formularios será la entrada de los usuarios a tu web.
session_start(); session_regenerate_id(true);
También que puede que tus usuarios puede que tengan conexiones inestables, en ese caso deberias de poner un delay a la regeneración del id
Formulario y el Token
Básicamente, cuando tienes un formulario se debería cumplir con lo que se muestra en la siguiente imagen.
1- Es la sección del formulario, en esta le pasarás el token en un input hidden
2 – chequeamos si el token pasado corresponde con el token en la base de datos, si no, se manda al usuario sin aviso alguno a la primera instancia.
3 – se hace la validación de datos, si estos no cumplen con esta se le devuelve al usuario a la primera instancia con mensajes de su equivocación