[Aporte]: Sistema Anti ataques CSRF

Iniciado por Leguim, 8 Diciembre 2019, 23:49 PM

0 Miembros y 1 Visitante están viendo este tema.

Leguim

Sistema anti ataques CSRF


Con este pequeño sistema se espera que los ataques CSRF ya no nos afecten, este es el segundo aporte que hago. El primero era un contador de llaves donde se podía ingresar un código y el programa iba a devolver la cantidad de llaves abiertas y cerradas.

Con esto quiero decir que este es mi primer aporte "avanzado" para mi así que por eso muy posiblemente existan fallos o áreas donde el código pueda ser mejorado.

El sistema lo que hace es crear tokens de seguridad de clave única cifrada, yo lo uso para evitar ataques en las paginas donde existan formularios, donde se use $_GET como por ejemplo un perfil (profile.php?id_user=x) y también lo uso en ficheros AJAX (realmente no estoy seguro si es necesario pero quería que en todas las partes de mi aplicación web donde se ejecuten funciones importantes exista este control de seguridad)

Con este código espero que me lluevan criticas, sugerencias para que podamos mejorarlo entre todos.

En principio creo 6 funciones, creo que esta de más decir que uso PHP para esto pero quería aclararlo.

La primera función servirá para crear un código de 22 caracteres unos 20 números + 2 letras en mayúsculas.
Código (php) [Seleccionar]

function Create_Code()
{
$year_code = date('Y');
$month_code = date('m');
$day_code = date('d');
$hour_code = date('H');
$minute_code = date('i');
$second_code = date('s');

$random_code_A = rand(100000, 999999);
$random_code_B = chr(rand(ord('A'), ord('Z')));
$random_code_C = chr(rand(ord('A'), ord('Z')));

$result = ($random_code_A).($year_code).($month_code).($day_code).($hour_code).($minute_code).($second_code).($random_code_B).($random_code_C);

return $result;
}


La segunda función servirá para crear un token determinado para cada pagina que exista en nuestra aplicación.
Código (php) [Seleccionar]

function Create_Token($page)
{
if(is_string($page))
{
if(!isset($_SESSION['token_'.($page)]))
{
$token_csrf = Create_Code(); // Creamos un codigo unico
$token_csrf = Hash_Data($token_csrf); // ciframos el codigo lo que nos va a dar una cadena de 60 caracteres
$_SESSION['token_'.($page)] = $token_csrf; // se crea una session con el token generado
}
}
}


La funciòn hash_data (me había olvidado de ponerla, gracias por el aviso!)
Código (php) [Seleccionar]

function Hash_Data($data) // El tipo de cifrado es general, con esto quiero decir que uso esta función para cifrar contraseñas, códigos de activación de un email, etcétera, ustedes pueden usar el que quieran pero en mi aplicación como norma general uso este.
{
$result = 'Error';

if(is_string($data))
{
$result = password_hash($data, PASSWORD_BCRYPT);
}

return $result;
}


La tercera función servirá para crear una lista de tokens (acá van a tener que poner todas las paginas que van a usar tokens, donde usen formularios, peticiones $_get, o ajax)
Código (php) [Seleccionar]

function Create_Tokens()
{
// Ajax
Create_Token('ajax'); // acá recomiendo que creen un solo token para validar ficheros ajax

// Pages ($_POST or $_GET)
Create_Token('login'); // este token pertenece a login.php donde estará el formulario de acceso
}


La cuarta función servirá para validar un token determinado.
Código (php) [Seleccionar]

function Validate_Token($page, $value)
{
$result = false;

if(is_string($page) && is_string($value))
{
if(isset($_SESSION['token_'.($page)]))
{
if(!empty($_SESSION['token_'.($page)]) && $_SESSION['token_'.($page)] === $value)
{
$result = true;
}
}
}

   return $result;
}


Ahora vamos a ponerlo en práctica, por supuesto todas las funciones de arriba deberían ponerlas en una libreria que ustedes pueden crear e incluirla en todas sus paginas php incluyendo "Create_Tokens();" pero para este ejemplo vamos a dejarlo acá.
Código (html) [Seleccionar]

<!-- login.php -->
<?php
Create_Tokens
();
?>


<form method="post">
    <input type="text" name="el_texto"></input>
    <input type="hidden" name="token_csrf" value="<?php echo($_SESSION['token_login']); ?>"> // Acá va a ir su token, fijense que le puse "token_login" esto tiene que ir personalizado dependiendo en que paginas lo usen
     <button type="submit" name="el_submit"></button>
</form>

<?php
if(isset($_POST['el_submit'])) // cuando se envia el formulario
{
     if(isset(
$_POST['token_csrf']) && Validate_Token('login'$_POST['token_csrf']))
     {
        echo 
"mandaste ".$_POST['el_texto'];
     }
     else
     {
          echo 
"Se detecto un ataque CSRF";
     }
}
?>



Bueno ahora vamos a aplicar esto a una pagina que use $_GET como por ejemplo un perfil.
Código (html) [Seleccionar]

<!-- profile.php -->
<?php
if(isset($_GET['id_user']) && isset($_GET['token_csrf']))
{
    
$_GET['token_csrf'] = urldecode($_GET['token_csrf']);

    if(
Validate_Token('token_profile'$_GET['token_csrf']))
    {
        [
Ejecución]
    }
    else  
    {
        [
Interrumpimosredirigimosetcétera// ya que no es valido el token
    
}
}
else
{
     [
Interrumpimosredirigimosetcétera]
}
?>



Cuando vayamos a redirigir a el usuario a una pagina donde se use $_get debe de ser así...
Código (javascript) [Seleccionar]

window.location.href = 'profile.php?id_user=<?php echo urlencode($id); ?>&token_csrf=<?php echo urlencode($_SESSION['token_profile']); ?>';


En ajax sería así:
Código (php) [Seleccionar]

// ajax/fichero.php
if(Validate_Token('token_ajax', $_POST['token_csrf'])) // $_POST['token_csrf'] depende de si en ajax ustedes usan GET o en este caso POST
{
    [Ejecución]
}
else
{
    [Interrupción]
}




Espero que les guste y si lo quieren usar que me comenté si les gusto, escucho criticas como ya dije y entre todos poder ir mejorando este sistema. Gracias!

@XSStringManolo


AlbertoBSD

#2
Cita de: @?0!,5^34 en 13 Diciembre 2019, 10:22 AM
Estoy ciego o te falta la función hash_data() ?

Lo mismo estaba por comentar.

La funcion create_code podria quedar mas sencilla.

Código (php) [Seleccionar]

<?php
function Create_Code()
{
return hash("sha256",openssl_random_pseudo_bytes(1024));
}
?>




Por cierto tus llamdas a la funcion Validate_Token no van a funcionar ejemplo:

  if(Validate_Token('token_profile', $_GET['token_csrf']))
Validate_Token('token_login'

cuando en el codigo que publicaste dice:
if(!empty($_SESSION['token_'.($page)]) && $_SESSION['token_'.($page)] === $value)

Con lo cual estarias tratando de valiar la variable $_SESSION['token_token_profile']

Por cierto cree un tema respondiendote como implementar criptografia.
[Aporte] Validacion de token de forma Criptografica, evitar ataques CSRF
Donaciones
1Coffee1jV4gB5gaXfHgSHDz9xx9QSECVW

Leguim

Cita de: AlbertoBSD en 13 Diciembre 2019, 18:16 PM
Lo mismo estaba por comentar.

La funcion create_code podria quedar mas sencilla.

Código (php) [Seleccionar]

<?php
function Create_Code()
{
return hash("sha256",openssl_random_pseudo_bytes(1024));
}
?>




Por cierto tus llamdas a la funcion Validate_Token no van a funcionar ejemplo:

  if(Validate_Token('token_profile', $_GET['token_csrf']))
Validate_Token('token_login'

cuando en el codigo que publicaste dice:
if(!empty($_SESSION['token_'.($page)]) && $_SESSION['token_'.($page)] === $value)

Con lo cual estarias tratando de valiar la variable $_SESSION['token_token_profile']

Por cierto cree un tema respondiendote como implementar criptografia.
[Aporte] Validacion de token de forma Criptografica, evitar ataques CSRF

Ya agregue la función Hash_Data (me había olvidado, gracias por el aviso a los dos)
Ya corregí la función Validate_Token, acerca de la función "Create_Code" lo uso para que lo que vaya a devolver sea único e irrepetible, esta función es perfectamente aplicable cuando quieren renombrar archivos, imagenes, vídeos, etcétera que no quieren que tenga el mismo nombre para que no se pisen entre ellas, en este caso un token de seguridad que igualmente va a ser cifrado. string(60)
Por ahí la función que me mandaste que dijiste que es más sencilla capaz hace lo mismo la verdad nose no estoy muy familiarizado con eso xD

Vi tu tema, parece muy interesante igual le voy a dar una repasada otra vez xD

AlbertoBSD

Cita de: MiguelCanellas en 14 Diciembre 2019, 19:12 PM
Por ahí la función que me mandaste que dijiste que es más sencilla capaz hace lo mismo la verdad nose no estoy muy familiarizado con eso xD

Creo que la función que te recomende es mejor incluso se podría utilizar la función random_bytes en lugar de la función openssl_random_pseudo_bytes.

La principal idea de genera un código de "seguridad" es que no pueda ser adivinable.

En función los valores que son tomados de la fecha son calculables año, mes dia, hora minuto y segundo son calculables, posteriormente los únicos valores random que tienes es el numero de de 6 dígitos que va del 100 mil al 999,999 adicional  eso los otros 2 valores que van de la A a Z.

Si es difícil adivinarlo, pero puede se puede mas o menos predecir su valor original. En cambio si obtienes X cantidad de bytes al azar, digamos 16 bytes al azar.
El numero de posibilidades es

255 *255*255 *255*255 *255*255 *255*255 *255*255 *255*255 *255*255 *255

Ahora en el ejemplo que te puse, indique que se podrían tomar 1024  bytes, sin embargo si viste el post que publique, el debate fue solo utilizar 16 o 32 bytes en la función.

Totalmente impredecible.

Código (php) [Seleccionar]

<?php
function Create_Code()
{
return hash("sha256",random_bytes(32));
}
?>



Aplicamos la función hash sha256 a eso datos binarios y listo ya tenemos nuestro string.

Saludos
Donaciones
1Coffee1jV4gB5gaXfHgSHDz9xx9QSECVW