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

Материал из SecSem Wiki
Перейти к навигации Перейти к поиску
(Новая страница: «При атаке на клиента атакующий пытается вынудить '''браузер''' клиента ('''жертвы''' атаки) в…»)
 
Строка 17: Строка 17:
  
 
login=test&password=test
 
login=test&password=test
</pre>
+
</pre>  
 
В ответ сервер присылает заголовок <code>Set-Cookie: session=eyJ1c2VyIjoidGVzdCIsInVzZXJfaWQiOjN9.D3Az7g.wEXp46Qb6OpIPnwIYspV_cIUgug; Path=/</code>. Все последующие запросы клиента содержат заголовок с этим значением <code>Cookie: session=eyJ1c2VyIjoidGVzdCIsInVzZXJfaWQiOjN9.D3Az7g.wEXp46Qb6OpIPnwIYspV_cIUgug</code>. В качестве эксперимента можно убрать этот загловок или изменить его (скажем, удалить половину) и посмотреть, как изменится ответ сервера при запросе относящихся к конкретному пользователю ресурсов (к примеру <code>GET /api/get</code>).
 
В ответ сервер присылает заголовок <code>Set-Cookie: session=eyJ1c2VyIjoidGVzdCIsInVzZXJfaWQiOjN9.D3Az7g.wEXp46Qb6OpIPnwIYspV_cIUgug; Path=/</code>. Все последующие запросы клиента содержат заголовок с этим значением <code>Cookie: session=eyJ1c2VyIjoidGVzdCIsInVzZXJfaWQiOjN9.D3Az7g.wEXp46Qb6OpIPnwIYspV_cIUgug</code>. В качестве эксперимента можно убрать этот загловок или изменить его (скажем, удалить половину) и посмотреть, как изменится ответ сервера при запросе относящихся к конкретному пользователю ресурсов (к примеру <code>GET /api/get</code>).
  
Строка 27: Строка 27:
  
 
Все перечисленные варианты обладают одним общим свойством - любой запрос, отправленный аутентифицированным браузером на сайт, будет автоматически обладать аутентифицирующим признаком (например содержать аутентифицирующий токен - куку/учетные данные/...) и будет считаться сервером как сделанный от имени этого клиента. Это удобно, но это может помочь атакующему, если тот сможет вынудить браузер жертвы отправить запрос. (Кстати, таким свойством обладает не любой способ аутентификации. К примеру, если аутентифицировать запросы по кастомному заголовку или кастомному параметру запроса, то их браузер автоматически не пошлёт - правда, в случае кастомного заголовка всё взаимодействие с сервером придётся осуществлять через JavaScript, в целом, программирование сайта будет несколько сложнее).
 
Все перечисленные варианты обладают одним общим свойством - любой запрос, отправленный аутентифицированным браузером на сайт, будет автоматически обладать аутентифицирующим признаком (например содержать аутентифицирующий токен - куку/учетные данные/...) и будет считаться сервером как сделанный от имени этого клиента. Это удобно, но это может помочь атакующему, если тот сможет вынудить браузер жертвы отправить запрос. (Кстати, таким свойством обладает не любой способ аутентификации. К примеру, если аутентифицировать запросы по кастомному заголовку или кастомному параметру запроса, то их браузер автоматически не пошлёт - правда, в случае кастомного заголовка всё взаимодействие с сервером придётся осуществлять через JavaScript, в целом, программирование сайта будет несколько сложнее).
 +
 +
== CSRF ==
 +
 +
Прямой эксплуатацией описанного выше свойства является атака CSRF. Эта атака возможна когда существует существует запрос к серверу, который может внести на сервере какие-то изменения, причем эффект запроса зависит от того, какой пользователь его сделал (к примеру, запрос имеет право только определенный пользователь).
 +
Например, на сайте http://zp.stands.course.secsem.ru/ у пользователей есть возможность изменить свой номер карты, записанный в профиле. В личном кабинете есть форма, посылающая на сервер запрос на изменение номера карты. Изменение делается запросом
 +
<pre>
 +
POST /lk HTTP/1.1
 +
Host: zp.stands.course.secsem.ru
 +
...
 +
Cookie: session=eyJwYXNzd29yZCI6InRlc3QiLCJ1c2VybmFtZSI6InRlc3QifQ.XI7Y8g.pVkMZg6ZzsBYm8vZZw-PxGDMXaQ
 +
...
 +
 +
card_number=372757798399775
 +
</pre>
 +
При этом то, в чьем профиле будет изменён номер карты, определяется именно кукой (отпрака этого запроса без куки или с невалидной кукой приводит к ошибке <code>400 BAD REQUEST</code>). Допустим, атакующий хочет изменить номер карты в профиле на свой - на '''6011720544333229'''. Он может создать страницу с HTML-кодом
 +
<pre>
 +
<form action="http://zp.stands.course.secsem.ru/lk" method="POST">
 +
    <input name="card_number" value="6011720544333229">
 +
</form>
 +
<script>
 +
    document.forms[0].submit();
 +
</script>
 +
</pre>
 +
Эта страница содержит форму, отправка которой приводит к созданию как раз такого запроса на изменение номера карты как тот что создаёт сайт (атрибут <code>action</code> задаёт URL, на который будет отправлен запрос, <code>method</code> задаёт метод, параметры формы определяются <code>input</code>'ами, при этом атрибут <code>name</code> задаёт название параметра, значением будет значение, заданое в <code>input</code>'е, атрибут <code>value</code> задаёт изначальнок значение). JS-код <code>document.forms[0].submit();</code> автоматически отправляет форму (а значит, соответствующий HTTP-запрос).

Версия 02:59, 18 марта 2019

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

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

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

Сессии на основе 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, в целом, программирование сайта будет несколько сложнее).

CSRF

Прямой эксплуатацией описанного выше свойства является атака CSRF. Эта атака возможна когда существует существует запрос к серверу, который может внести на сервере какие-то изменения, причем эффект запроса зависит от того, какой пользователь его сделал (к примеру, запрос имеет право только определенный пользователь). Например, на сайте 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(); автоматически отправляет форму (а значит, соответствующий HTTP-запрос).