Понимание CSRF-атак

Недавно, когда я писал статью «Понимание асинхронного JavaScript», я занялся исследованием веб-безопасности — я хотел убедиться, что мои рекомендации безопасны, и я не окажу услугу никому из своих студентов своими рекомендациями.

К сожалению, статьи по безопасности были довольно трудны для понимания. В статьях было много слов, вызывающих страх, неуверенность и сомнения. Когда я читаю эти статьи, меня охватывает эмоциональная паника, и я беспокоюсь, что в итоге могу сделать что-то не так, хотя намерения этих статей были благими!

Многие статьи также не раскрывают всех деталей CSRF, как организовать CSRF атаку, и как предотвратить CSRF атаку, что заставляет меня сомневаться в том, что я узнал. В итоге мне приходится разбираться во всем самостоятельно.

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

Два вида CSRF-атак

Существует два вида CSRF-атак:

  1. Обычная CSRF-атака
  2. Login CSRF

Сначала мы обсудим обычную CSRF-атаку, а затем CSRF на вход.

Что такое CSRF-атака

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

Запрос должен исходить от другого сайта, что дает название «Межсайтовый». Этот запрос также выдает себя за аутентифицированного пользователя, что дает ему название «Подделка запроса».

CSRF-атаки являются слепыми — это означает, что атакующий не видит, что происходит после того, как жертва отправляет запрос. Поэтому CSRF-атаки часто направлены на изменение состояния сервера.

Что такое изменение состояния? В принципе, все, что изменяет базу данных, является изменением состояния. Примеры изменения состояния включают:

  • изменение имени пользователя и пароля
  • Отправка денег на счет
  • Отправка фальшивых сообщений с аккаунта пользователя
  • Обмен неприемлемыми изображениями или видео с аккаунта пользователя.

CSRF-атаки используют тот факт, что браузеры автоматически отправляют cookies на сервер при каждом запросе. Без какой-либо защиты от CSRF сервер может считать запрос действительным, если в нем присутствует аутентификационный cookie.

Куки аутентификации могут быть любыми, если сервер использует их для проверки подлинности пользователя. Это может быть токен доступа. Это также может быть идентификатор сессии. Это зависит от того, как сервер обрабатывает аутентификацию.

Необходимые условия для работы CSRF-атаки

Для успешного проведения CSRF-атаки необходимы четыре предпосылки.

  1. На сервер отправляется запрос любого метода.
  2. Пользователь должен быть аутентифицирован.
  3. Сервер должен хранить информацию об аутентификации в cookies.
  4. На сервере не реализованы методы предотвращения CSRF (которые будут рассмотрены ниже).

Как работают CSRF-атаки

Прежде чем злоумышленник сможет осуществить CSRF-атаку, ему необходимо найти последовательный запрос, на который он может нацелиться. Они должны знать, что делает запрос. Это может быть любой запрос — GET, POST, PUT или DELETE. Все, что угодно.

Выбрав целевой запрос, они должны создать поддельный запрос, чтобы обмануть пользователя.

Наконец, они должны обманом заставить пользователя отправить запрос. Чаще всего это означает:

  1. Найти способ автоматической отправки запроса без ведома пользователя. Наиболее распространенными способами являются использование тегов изображений и автоматическая отправка формы JavaScript.
  2. Искажение ссылки (или кнопки), что обманывает пользователя, заставляя его нажать на нее. (AKA Social Engineering).

Атаки через GET-запрос

CSRF-атаки с помощью GET-запроса работают только в том случае, если сервер позволяет пользователю изменять состояние с помощью GET-запросов. Вам не стоит беспокоиться об этом типе CSRF-атаки, если ваши GET-запросы предназначены только для чтения.

Но, допустим, у нас есть сервер, который не следует лучшим практикам программирования и разрешил изменение состояния через GET-запрос. Если они это сделают, у них будут проблемы — огромные проблемы.

Например, допустим, есть банк, который позволяет вам переводить деньги с помощью следующей конечной точки. Вам просто нужно ввести account и amount в GET-запросе, чтобы отправить деньги человеку.

https://bank.com/transfer?account=Mary&amount=100
Вход в полноэкранный режим Выход из полноэкранного режима

Злоумышленник может сгенерировать ссылку, которая отправит деньги на его счет.

# Sends 9999 to the Attacker's account
https://bank.com/transfer?account=Attacker&amount=9999
Вход в полноэкранный режим Выход из полноэкранного режима

На этом этапе злоумышленник может найти способ автоматического запуска ссылки без ведома пользователя.

Один из способов — включить ссылку в изображение 0x0 на веб-странице или в электронном письме. Если пользователь посетит эту веб-страницу или письмо, GET-запрос сработает автоматически, поскольку браузеры и электронная почта настроены на автоматическую выборку изображений.

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

<!-- Downloading this image triggers the GET request attack -->
<img
  src="https://bank.com/transfer?account=Attacker&amount=9999"
  width="0"
  height="0"
  border="0"
/>
Вход в полноэкранный режим Выход из полноэкранного режима

Другой способ — исказить смысл ссылки. Это работает, потому что люди не проверяют ссылки, прежде чем нажать на них. Если человек щелкнет по ссылке, он, сам того не зная, отправит GET-запрос злоумышленнику.

<!-- Fake link that triggers the GET request attack -->
<a href="https://bank.com/transfer?account=Attacker&amount=9999"
  >View my Pictures</a
>
Вход в полноэкранный режим Выход из полноэкранного режима

Если пользователь прошел аутентификацию, сервер получит аутентификационный cookie, который заставит его поверить, что запрос действителен. Если сервер не использовал никаких механизмов защиты CSRF, деньги будут отправлены злоумышленнику.

Примеры GET CSRF атак:

  • uTorrent подвергся CSRF-атаке еще в 2008 году, когда он позволял изменять состояние с помощью GET-запросов.
  • Youtube в 2008 году имел уязвимость в безопасности, которая позволяла злоумышленнику выполнять практически все возможные для пользователя действия, включая отправку сообщений, добавление в список друзей и т.д.

Если вы перейдете по приведенным выше ссылкам. Вы сможете найти примеры реальных GET-запросов, создающих такую CSRF-атаку. (Не волнуйтесь, здесь нет странных ссылок 😜).

CSRF-атаки с помощью POST-запросов

CSRF-атаки с помощью POST-запросов происходят по той же схеме, но они не могут быть отправлены через ссылки или теги изображений. Они должны быть отправлены через форму или JavaScript.

Предположим, что у нас есть та же уязвимая конечная точка, и злоумышленнику достаточно ввести информацию account и amount, чтобы вызвать запрос.

POST https://bank.com/transfer?account=Attacker&amount=9999
Вход в полноэкранный режим Выход из полноэкранного режима

Злоумышленник может создать форму и скрыть от пользователя значения account и amount. Людям, которые нажмут на эту искаженную форму, будет отправлен POST-запрос без их ведома.

<!-- Form disguised as a button! -->
<form action="https://bank.com/transfer" method="POST">
  <input type="hidden" name="acct" value="Attacker" />
  <input type="hidden" name="amount" value="9999" />
  <button>View my pictures</button>
</form>
Вход в полноэкранный режим Выход из полноэкранного режима

Эта форма также может быть выполнена с помощью JavaScript автоматически без ведома людей — реальным пользователям даже не нужно нажимать на кнопку, но они уже в беде.

<form>...</form>
<script>
  const form = document.querySelector('form')
  form.submit()
</script>
Вход в полноэкранный режим Выход из полноэкранного режима

POST CSRF-атаки — это страшно, но есть способы их предотвратить. Мы поговорим об этих способах в разделе о предотвращении ниже.

CSRF-атаки с помощью запросов PUT и DELETE

CSRF-атаки не могут быть выполнены с помощью запросов PUT и DELETE, потому что используемые нами технологии не позволяют этого сделать.

Ага. Вы все правильно поняли.

CSRF-атаки не могут быть выполнены через HTML-формы, потому что формы не поддерживают запросы PUT и DELETE. Они поддерживают только GET и POST. Если вы используете любой другой метод (кроме GET и POST), браузеры автоматически преобразуют их в GET-запрос.

<!-- Form doesn't send a PUT request because HTML doesn't support PUT method. This will turn into a GET request instead. -->
<form action="https://bank.com/transfer" method="PUT"></form>
Вход в полноэкранный режим Выход из полноэкранного режима

Таким образом, вы никогда не сможете осуществить CSRF-атаку через HTML-файл.

Теперь забавное отступление: как люди отправляют запросы PUT и w через форму, если HTML не позволяет этого? После некоторых исследований я обнаружил, что большинство фреймворков позволяют отправлять запрос POST с параметром _method.

<!-- How most frameworks handle PUT requets -->
<form method="post" ...>
  <input type="hidden" name="_method" value="put" />
</form>
Вход в полноэкранный режим Выход из полноэкранного режима

Вы можете выполнить CSRF-атаку PUT через JavaScript, но стандартные механизмы предотвращения в браузерах и серверах сегодня делают эти атаки очень сложными — вы должны намеренно ослабить защиту, чтобы это произошло.

Вот почему.

Чтобы выполнить CSRF-атаку PUT, необходимо отправить запрос Fetch с методом put. Также необходимо включить параметр credentials.

const form = document.querySelector('form')

// Sends the request automatically
form.submit()

// Intercepts the form submission and use Fetch to send an AJAX request instead.
form.addEventListener('submit', event => {
  event.preventDefault()
  fetch(/*...*/, {
    method: 'put'
  credentiials: 'include' // Includes cookies in the request
 })
    .then(/*...*/)
    .catch(/*...*/)
})
Вход в полноэкранный режим Выйти из полноэкранного режима

Это не сработает по трем причинам.

Во-первых, этот запрос НЕ будет выполнен браузерами автоматически из-за CORS. Если, конечно, сервер не создает уязвимость, разрешая запросы от любого пользователя со следующим заголовком:

Access-Control-Allow-Origin: *
Войти в полноэкранный режим Выйти из полноэкранного режима

Во-вторых, даже если вы разрешите всем источникам доступ к вашему серверу, вам все равно понадобится опция Access-Control-Allow-Credentials, чтобы браузеры отправляли cookies на сервер.

Access-Control-Allow-Credentials: true
Вход в полноэкранный режим Выход из полноэкранного режима

В-третьих, даже если вы разрешите отправлять файлы cookie на сервер, браузеры будут отправлять только те файлы cookie, у которых атрибут sameSite установлен в none. (Такие файлы также называются сторонними файлами cookie).

Если вы не понимаете, о чем я говорю в отношении третьего пункта, вы в безопасности — вы действительно должны быть злобным разработчиком, который хочет испортить ваш сервер, если вы отправляете аутентификационные cookies как сторонние cookies.

Этот раздел огромен. Я создал еще несколько статей, чтобы помочь вам понять, что именно происходит — и почему так чертовски трудно подвергнуть себя PUT CSRF-атаке:

  • Понимание cookies sameSite
  • Понимание учетных данных Fetch

Короче говоря — вам нужно беспокоиться только о POST CSRF-атаках, если только вы действительно не испортили свой сервер.

Методы предотвращения CSRF

Наиболее распространенными методами предотвращения CSRF на сегодняшний день являются:

  • Шаблон «Двойное отправление куки
  • Метод «куки к заголовку

Оба метода следуют одной и той же формуле.

Когда пользователь посещает ваш сайт, ваш сервер должен создать CSRF-токен и поместить его в cookies браузера. Общепринятыми названиями для этого маркера являются:

  • CSRF-TOKEN .
  • X-SRF-TOKEN
  • X-XSRF-TOKEN
  • X-CSRF-TOKEN .

Используйте любое имя токена, которое вы предпочитаете. Все они работают.

Важно то, что CSRF-токен должен быть случайно сгенерированной, криптографически сильной строкой. Если вы используете Node, вы можете сгенерировать строку с помощью crypto.

import crypto from 'crypto'

function csrfToken (req, res, next) {
  return crypto.randomBytes(32).toString('base64')
}
Вход в полноэкранный режим Выход из полноэкранного режима

Если вы используете Express, вы можете поместить этот CSRF-токен в ваши cookies следующим образом. При этом я рекомендую также использовать строгую опцию sameSite. (О sameSite мы поговорим чуть позже).

import cookieParser from 'cookie-parser'

// Use this to read cookies
app.use(cookieParser())

// Setting CSRF Token for all endpoints
app.use(*, (req, res) => {
  const { CSRF_TOKEN } = req.cookies

 // Sets the token if the user visits this page for the first time in this session
 if (!CSRF_TOKEN) {
  res.cookie('CSRF_TOKEN', csrfToken(), { sameSite: 'strict' })
 }
})
Вход в полноэкранный режим Выход из полноэкранного режима

Способ использования CSRF Token меняется в зависимости от того, поддерживаете ли вы шаблон двойной отправки cookie или метод Cookie to header (или оба метода).

Шаблон двойного отправления куки

Название этого шаблона немного вводит в заблуждение, потому что кажется, что он означает отправку cookie дважды с помощью «Double Submit Cookie».

На самом деле это означает следующее:

  1. Вы отправляете CSRF-токен в cookie.
  2. Вы отображаете <form> с CSRF Token — который будет включен в отправку формы.

(Следовательно, двойная отправка).

Если вы используете Express, вы можете передать CSRF-токен в HTML следующим образом:

app.get('/some-url', (req, res) => {
  const { CSRF_TOKEN } = req.cookies

  // Render with Nunjucks.
  // Replace Nunjucks with any other Template Engine you use
  res.render('page.nunjucks', {
    CSRF_TOKEN: CSRF_TOKEN
  })
})
Войти в полноэкранный режим Выйти из полноэкранного режима

Затем вы можете использовать CSRF_TOKEN в форме следующим образом:

<form>
  <input type="hidden" name="csrf" value="{{CSRF_TOKEN}}" />
  <!-- ... -->
</form>
Войти в полноэкранный режим Выйти из полноэкранного режима

Затем сервер может проверить валидность сессии, сравнив два CSRF-токена. Если они совпадают, это означает, что запрос не подделан — ведь у злоумышленника нет возможности угадать значение CSRF-токена на другом сайте.

// Checks the validity of the CSRF Token
app.post('/login', (req, res) => {
  const { CSRF_TOKEN } = req.cookies
  const { csrf } = req.body

  // Abort the request
  // You can also throw an error if you wish to
  if (CSRF_TOKEN !== csrf) return

  // ...
})
Вход в полноэкранный режим Выход из полноэкранного режима

Метод «куки к заголовку

Метод cookie to header аналогичен — за исключением того, что он выполняется с помощью JavaScript. В этом случае CSRF-токен должен быть включен как в cookie, так и в заголовок запроса.

В этом случае нам необходимо:

  1. Установить credentials в include или same-origin для включения куки.
  2. Возьмите CSRF-токен из document.cookies и добавьте его в заголовок запроса.

Вот пример запроса:

// Gets the value of a named cookie
function getCookie () {
  const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)'))
  if (match) return match[2]
}

// Sends the request
fetch('/login', (req, res) => {
  credentials: 'include',
  headers: {
    'CSRF_TOKEN': getCookie('CSRF_TOKEN')
 }
})
Войти в полноэкранный режим Выйти из полноэкранного режима

Сервер может проверить валидность CSRF-токена следующим образом:

// Checks the validity of the CSRF Token
app.post('/login', (req, res) => {
  const { CSRF_TOKEN } = req.cookies
  const { CSRF_TOKEN: csrf } = req.headers

  // Abort the request
  // You can also throw an error if you wish to
  if (CSRF_TOKEN !== csrf) return

  // ...
})
Войти в полноэкранный режим Выйти из полноэкранного режима

Сделать все это проще с помощью библиотеки

Я показал вам, как вручную создавать и проверять CSRF-токены, потому что хотел дать вам понимание процесса.

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

Если вы используете Express, я рекомендую использовать библиотеку csurf, поскольку она более надежна и гибка по сравнению с тем, что я смог показать в этом примере выше.

Установка sameSite в strict в приведенном выше примере гарантирует, что CSRF Token cookie будет отправлен на сервер, только если запрос исходит от того же сайта. Это гарантирует, что CSRF Token никогда не будет передан на внешние страницы.

Вы можете — опционально, но рекомендуется — установить атрибут sameSite в значение strict при установке cookie аутентификации. Это гарантирует невозможность проведения CSRF-атак, поскольку файл cookie аутентификации больше не будет включаться в межсайтовые запросы.

Нужна ли вам защита CSRF Token, если вы установили sameSite в strict для вашего аутентификационного cookie?

Я бы сказал, что нет в большинстве случаев — потому что sameSite уже защищает сервер от межсайтовых запросов. Но нам все еще нужен CSRF-токен для защиты от одного конкретного типа CSRF: Login CSRF.

Подробнее о cookies sameSite вы можете прочитать в этой статье.

Login CSRF

Login CSRF полностью отличается от обычной CSRF-атаки в плане намерения.

При Login CSRF злоумышленник обманом заставляет пользователя войти в систему с учетными данными злоумышленника. После успешного завершения атаки пользователь, если он не обратит внимания, продолжит использовать учетную запись злоумышленника.

<form action="http://target/login" method="post">
  <input name="user" value="Attacker" />
  <input name="pass" type="password" value="AttackerPassword" />
  <button>Submit</button>
</form>
Вход в полноэкранный режим Выход из полноэкранного режима

Они также могут вызвать форму автоматически с помощью JavaScript.

const form = document.querySelector('form')

// Sends the request automatically
form.submit()
Вход в полноэкранный режим Выход из полноэкранного режима

Если пользователь не понимает, что вошел в учетную запись злоумышленника, он может добавить в нее личные данные — например, информацию о кредитной карте или историю поиска. Затем злоумышленники могут снова войти в аккаунт, чтобы просмотреть эти данные.

В прошлом Google был уязвим к атакам Login CSRF.

Мы можем предотвратить Login CSRF с помощью упомянутого выше шаблона Double Submit Cookie — злоумышленники не смогут угадать CSRF Token, а значит, не смогут осуществить CSRF Login Attack.

Завершение

CSRF расшифровывается как подделка межсайтовых запросов. Существует два вида атак CSRF:

  1. Обычная CSRF
  2. Login CSRF

В обычной CSRF атакующий стремится создать изменение состояния через запрос.

В Login CSRF атакующий стремится обманом заставить пользователя войти в учетную запись атакующего — и надеяться получить выгоду от действий пользователя, если он не знает об этом.

Вы можете предотвратить оба вида CSRF-атак с помощью шаблона Double Submit Cookie и метода Cookie to header. Установка sameSite в strict предотвращает обычную CSRF, но не Login CSRF.

Вот и все!


Спасибо за прочтение. Эта статья была первоначально опубликована в моем блоге. Подпишитесь на мою рассылку, если хотите получать больше статей, которые помогут вам стать лучшим frontend-разработчиком.

Оцените статью
Procodings.ru
Добавить комментарий