CSRF и его предотвращение

CSRF (Cross-Site Request Forgery) — это тип атаки на веб-приложения, при которой злоумышленник заставляет пользователя выполнить нежелательные действия на веб-сайте, на который он авторизован. Атака заключается в подделке запросов, отправленных пользователем, что может привести к выполнению опасных действий от его имени, таких как изменение пароля, отправка денежных средств или удаление данных.

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

Механизм работы CSRF

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

Простой пример атаки CSRF может выглядеть так:

<img src="http://bank.com/transfer?amount=1000&to_account=12345" style="display:none">

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

Механизмы защиты от CSRF

Для защиты от CSRF в современных веб-приложениях используют несколько методов. Рассмотрим основные из них.

1. Использование токенов CSRF

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

Для реализации защиты CSRF с использованием токенов в Svelte можно воспользоваться следующим примером:

Серверная сторона (например, с использованием Express.js):

app.get('/form', (req, res) => {
  const token = generateCSRFToken(); // Генерация токена
  res.render('form', { csrfToken: token });
});

app.post('/submit', (req, res) => {
  const csrfToken = req.body.csrfToken;
  if (!validateCSRFToken(csrfToken)) {
    return res.status(403).send('CSRF token mismatch');
  }
  // Обработка запроса
});

Клиентская сторона (Svelte):

<script>
  let csrfToken;
  
  // Функция отправки формы
  async function submitForm() {
    const response = await fetch('/submit', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ csrfToken: csrfToken, data: formData })
    });
    if (response.ok) {
      // Успех
    } else {
      // Ошибка
    }
  }

  // Загрузка токена
  async function fetchToken() {
    const response = await fetch('/get-csrf-token');
    const data = await response.json();
    csrfToken = data.csrfToken;
  }

  fetchToken();
</script>

<form on:submit|preventDefault={submitForm}>
  <!-- Поля формы -->
  <input type="hidden" name="csrfToken" bind:value={csrfToken} />
  <button type="submit">Отправить</button>
</form>

В данном примере токен CSRF передается через скрытое поле в форме, и сервер проверяет его при получении запроса.

2. Заголовки HTTP

Некоторые фреймворки и библиотеки предлагают защиту с помощью специальных заголовков HTTP. Одним из таких заголовков является SameSite, который добавляется к кукам, отправляемым браузером. Этот заголовок предотвращает отправку куков при кросс-сайтовых запросах, тем самым защищая от CSRF.

Для того чтобы использовать этот заголовок в приложении, необходимо указать его при отправке куки на сервер:

Серверная сторона (например, с использованием Express.js):

app.use(session({
  secret: 'some-secret',
  resave: false,
  saveUninitialized: true,
  cookie: {
    httpOnly: true,
    secure: true,  // Только для HTTPS
    sameSite: 'Strict' // Предотвращает отправку куки на кросс-сайтовые запросы
  }
}));

Заголовок SameSite может принимать значения:

  • Strict — запрещает отправку куки на кросс-сайтовые запросы.
  • Lax — позволяет отправку куки на безопасные кросс-сайтовые запросы, например, при переходах по ссылкам.
  • None — позволяет отправку куки на любые кросс-сайтовые запросы, но требует использования защищенного соединения (HTTPS).

3. Защита через Referer

Проверка заголовка Referer — это еще один способ защиты от CSRF. Сервер может проверять, что запрос пришел с доверенного домена. Однако этот метод не является универсальным, так как заголовок Referer можно подделать, и некоторые браузеры могут не передавать его по соображениям конфиденциальности.

Пример использования на сервере:

app.post('/submit', (req, res) => {
  const referer = req.headers['referer'];
  if (!referer || !referer.startsWith('https://trusted-domain.com')) {
    return res.status(403).send('Invalid referer');
  }
  // Обработка запроса
});

4. Окна и IFrame

Чтобы предотвратить использование уязвимостей CSRF в сочетании с XSS-атаками, можно использовать такие механизмы как Content Security Policy (CSP), которые ограничивают выполнение небезопасных скриптов и использование фреймов.

CSP позволяет указать, какие источники контента считаются безопасными, и ограничивает выполнение скриптов на основе их происхождения. Например:

Content-Security-Policy: frame-ancestors 'self'

Этот заголовок запрещает встраивание страницы в IFrame с других источников.

Реализация защиты CSRF в Svelte

Для более комплексной защиты от CSRF в приложении на Svelte можно интегрировать методы, которые описаны выше. Например, можно использовать библиотеку для работы с токенами CSRF, а также применить защиту через заголовки SameSite для куков.

Сначала на сервере генерируется и передается токен CSRF в виде скрытого поля, которое затем отправляется с запросом. Также следует убедиться, что на сервере правильно настроены заголовки для предотвращения атак, например, через SameSite для куков.

Пример отправки защищенного запроса:

<script>
  let csrfToken;
  
  // Отправка запроса с CSRF-защитой
  async function sendRequest() {
    const response = await fetch('/action', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-CSRF-TOKEN': csrfToken // Отправка токена в заголовке
      },
      body: JSON.stringify({ actionData: someData })
    });

    if (response.ok) {
      // Обработка успешного ответа
    } else {
      // Обработка ошибки
    }
  }
  
  // Получение токена
  async function fetchToken() {
    const response = await fetch('/get-csrf-token');
    const data = await response.json();
    csrfToken = data.csrfToken;
  }

  fetchToken();
</script>

<form on:submit|preventDefault={sendRequest}>
  <input type="hidden" name="csrfToken" bind:value={csrfToken} />
  <button type="submit">Отправить запрос</button>
</form>

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

Заключение

Защита от CSRF является важным аспектом безопасности веб-приложений. Использование токенов, правильная настройка заголовков куки и проверка источника запросов — все это помогает предотвратить атаки CSRF и обеспечить безопасность данных пользователя. В случае работы с фреймворком Svelte важно правильно интегрировать эти механизмы защиты на уровне сервера и клиента, чтобы избежать уязвимостей в приложении.