Введение в практическую безопасность (2019)/Атаки на клиента веб-приложения - CSRF & XSS

Материал из SecSem Wiki
Перейти к навигации Перейти к поиску

При атаке на клиента атакующий пытается вынудить браузер клиента (жертвы атаки) выполнить какое-то действие с веб-приложением. Причина почему это может быть полезно атакующему - браузер клиента может быть аутентифицирован в приложении, запросы от него могут быть отличимы сервером, то есть сервер может понимать, что данный запрос сделан от имени конкретного пользователя.

Аутентификация

Существуют различные способы, которыми сервер может отличать, от какого клиента пришёл запрос.

Сессии на основе Cookie

Наиболее часто используемый способ аутентификации в современный веб-приложениях - это cookie (википедия, MDN). Работает это очень просто: в HTTP-ответе сервер отправляет заголовок Set-Cookie: SOMEUNIQUEVALUE, где SOMEUNIQUEVALUE - какое-то значение, уникальное для этой сессии. Получив это значение, браузер запомнит его и будет отправлять на этот сайт с каждым последующим запросом в заголовке Cookie (кстати, что именно значит "на этот сайт" очень интересно - кука будет отправлена если обращаться на другой TCP, порт а также по дефолту на все поддомены. В доке MDN есть про это). По тому что в запросе в заголовке Cookie будет это уникальное значение сервер будет понимать, что этот запрос относится к этой сессии, в том числе, если эта сессия принадлежит какому-то клиенту, что этот запрос от этого клиента. Для примера можно залогиниться на http://pwnitter.stands.course.secsem.ru/ (для этого надо сначала зарегиться). При логине на сервер отправится такой запрос

POST /login HTTP/1.1
Host: pwnitter.stands.course.secsem.ru
...
Content-Type: application/x-www-form-urlencoded
Content-Length: 24
...

login=test&password=test

В ответ сервер присылает заголовок Set-Cookie: session=eyJ1c2VyIjoidGVzdCIsInVzZXJfaWQiOjN9.D3Az7g.wEXp46Qb6OpIPnwIYspV_cIUgug; Path=/. Все последующие запросы клиента содержат заголовок с этим значением Cookie: session=eyJ1c2VyIjoidGVzdCIsInVzZXJfaWQiOjN9.D3Az7g.wEXp46Qb6OpIPnwIYspV_cIUgug. В качестве эксперимента можно убрать этот загловок или изменить его (скажем, удалить половину) и посмотреть, как изменится ответ сервера при запросе относящихся к конкретному пользователю ресурсов (к примеру GET /api/get).

Также можно попробовать залогиниться на каком-нибудь реальном сайте (скажем, vk.com или mail.ru) и посмотреть (например через Burp) на заголовки HTTP-запросов и ответов.

Другие варианты аутентификации

Протокол HTTP сам по себе поддерживает несколько вариантов аутентификации через заголовок Authorization, также при использовании HTTPS возможна аутентификация по клиентскому сертификату, наконец, возможны более простые варианты, например, по IP-адресу.

Все перечисленные варианты обладают одним общим свойством - любой запрос, отправленный аутентифицированным браузером на сайт, будет автоматически обладать аутентифицирующим признаком (например содержать аутентифицирующий токен - куку/учетные данные/...) и будет считаться сервером как сделанный от имени этого клиента. Это удобно, но это может помочь атакующему, если тот сможет вынудить браузер жертвы отправить запрос. (Кстати, таким свойством обладает не любой способ аутентификации. К примеру, если аутентифицировать запросы по кастомному заголовку или кастомному параметру запроса, то их браузер автоматически не пошлёт - правда, в случае кастомного заголовка всё взаимодействие с сервером придётся осуществлять через JavaScript, в целом, программирование сайта будет несколько сложнее).

Cross-site request forgery

Прямой эксплуатацией описанного выше свойства является атака Cross-site request forgery (CSRF, OWASP, википедия). Эта атака имеет смысл когда существует существует запрос к серверу, который может внести на сервере какие-то изменения, причем эффект запроса зависит от того, какой пользователь его сделал (к примеру, запрос имеет право только определенный пользователь). Например, на сайте http://zp.stands.course.secsem.ru/ у пользователей есть возможность изменить свой номер карты, записанный в профиле. В личном кабинете есть форма, посылающая на сервер запрос на изменение номера карты. Изменение делается запросом вида

POST /lk HTTP/1.1
Host: zp.stands.course.secsem.ru
...
Cookie: session=eyJwYXNzd29yZCI6InRlc3QiLCJ1c2VybmFtZSI6InRlc3QifQ.XI7Y8g.pVkMZg6ZzsBYm8vZZw-PxGDMXaQ
...

card_number=372757798399775

При этом то, в чьем профиле будет изменён номер карты, определяется именно кукой (отпрака этого запроса без куки или с невалидной кукой приводит к ошибке 400 BAD REQUEST). Допустим, атакующий хочет изменить номер карты в профиле жертвы на свой - на 6011720544333229. Он может создать страницу с HTML-кодом

<form action="http://zp.stands.course.secsem.ru/lk" method="POST">
    <input name="card_number" value="6011720544333229">
</form>
<script>
    document.forms[0].submit();
</script>

Эта страница содержит форму, отправка которой приводит к созданию как раз такого запроса на изменение номера карты как тот что создаёт сайт (атрибут action формы задаёт URL, на который будет отправлен запрос при отправке формы, method задаёт метод, параметры запроса определяются input'ами, при этом атрибут name задаёт название параметра, значением будет значение, заданое в input'е, атрибут value задаёт изначальнок значение). JS-код document.forms[0].submit(); автоматически отправляет форму.

Если атакующему удастся каким-то образом заманить жертву на такую страницу (к примеру, он встроит её в сайт который жертва иногда посещает или атакующий может убедить жертву пройти по присланной ссылке), то при её посещении форма автоматически отправится и данные в профиле изменятся (в данном случае - номер карты поменяется на 6011720544333229, это можно легко проверить, сохранив приведенный выше HTML-код в HTML-файл, скажем, test.html и открыв его в браузере, будучи залогиненым на сайте, к стенду zp.stands.course.secsem.ru подходят логин/пароль test/test). Это и есть атака CSRF. В реальности могут быть и более серьёзные действия, которые можно сделать через CSRF, например удалить аккаунт или дать другому пользователю админские права или, скажем, сменить пароль.

Следует отметить что, если бы действие на сайте выполнялось не через POST-запрос, а через простой GET с передачей всех параметров в URL, атакующему было бы проще - никакую форму делать бы не пришлось, достаточно было бы перенаправить браузер жертвы на выполняющий действие URL, уже содержащий нужные атакующему параметры (перенаправление можно сделать со своего сервера через HTTP-редирект или из JavaScript кодом location.href = 'http://victim.site/doaction?param=val...') или, если атакующему удаётся заставить жертву перейти по присланной ссылке, просто дать жертве сразу выполняющую действие ссылку. Это одна из причин почему считается, что GET-запросы (а также запросы с другими safe методами) никогда не должны изменять состояние сервера, изменения должны делаться только через POST или другой unsafe метод. Про safe/unsafe методы: RFC, MDN.