Зарегистрируйтесь, чтобы продолжить обучение

Безопасность JS: Веб-разработка

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

Безопасность веб-приложений — важная тема, которой мы уделим весь этот урок.

Главное правило безопасности

Оно звучит так:

Никогда не доверяйте пользователям

В первую очередь, это правило касается данных, которые пользователи вводят на сайте. Предположим, что у нас есть страница профиля пользователя, где выводится его идентификатор, взятый из адресной строки:

import fastify from 'fastify'

const app = fastify()
const port = 3000

app.get('/users/:id', (req, res) => {
  res.type('html')
  res.send(`<h1>${req.params.id}</h1>`)
})

app.listen({ port }, () => {
  console.log(`Example app listening on port ${port}`)
})

Когда код реализует эту функциональность, он рассчитывает, что в адресе используются только допустимые имена. А теперь попробуем открыть такой адрес:

http://localhost:3000/users/%3Cscript%3Ealert('attack!')%3B%3C%2Fscript%3E

Если мы доверимся данным от пользователей и откроем такой адрес, случится вот это:

XSS

В этом адресе закодирован JavaScript-код, который в оригинале выглядит так:

<script>
  alert('attack!');
</script>

Проблема в том, что код не отобразился, но попал в HTML страницы и выполнился. Так произошло, потому что для браузера такой JavaScript-код выглядит как часть страницы.

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

<h1>
  <script>alert('attack!');</script>
</h1>

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

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

Вернемся к коду из начала урока:

import fastify from 'fastify'

const app = fastify()
const port = 3000

app.get('/users/:id', (req, res) => {
  res.type('html')
  res.send(`<h1>${req.params.id}</h1>`)
})

app.listen({ port }, () => {
  console.log(`Example app listening on port ${port}`)
})

Здесь мы выводим данные без какой-либо предварительной обработки. В таком случае браузер пытается интерпретировать как HTML все, что похоже на HTML. Любой пользователь может внедрить на сайт исполняемый JavaScript-код без нашего ведома. Другими словами, мы проявили доверие к пользовательским данным и создали уязвимость.

Чтобы закрыть ее, нужно использовать не сами теги, а HTML-эквиваленты символов. Тогда код выше начнет выглядеть так:

<h1>
  &lt;script&gt;alert('attack!');&lt;/script&gt;
</h1>

Здесь мы заменили:

  • < на &lt;
  • > на &gt

Это не экранирование, а именно замена спецсимволов на их HTML-эквиваленты. Если открыть браузер, то там мы увидим правильное отображение:

XSS

Замена символов на спецсимволы выполняется с помощью специальных библиотек, например, sanitize-html. Один из вариантов выглядит так:

# Установка пакета
npm i sanitize-html
import fastify from 'fastify'
import sanitize from 'sanitize-html'

const app = fastify()
const port = 3000

app.get('/users/:id', (req, res) => {
  const escapedId = sanitize(req.params.id)
  res.type('html')
  res.send(`<h1>${escapedId}</h1>`)
})

app.listen({ port }, () => {
  console.log(`Example app listening on port ${port}`)
})

Функция sanitize() принимает на вход HTML и заменяет в нем все спецсимволы на их HTML-эквиваленты. Остальные символы остаются без изменения.

Через такую обработку нужно пропускать любые данные, которые мы выводим. Исключение составляют лишь ситуации, в которых мы точно знаем, что в данных есть HTML и мы его хотим отобразить. К таким данным могут относиться статьи в блоге, потому что они содержат HTML-часть. Например, так работает блог Хекслета.


Самостоятельная работа

  1. Добавьте в приложение маршрут, который выводит идентификатор пользователя, основываясь на данных из строки запроса. Повторите шаги из урока. Экранируйте вывод идентификатора пользователя на странице пользователя по аналогии, как сделано в теории урока
  2. Убедитесь на собственном опыте, что шаблонизатор защищает от XSS атак. Измените обработчик таким образом, чтобы не экранированные данные пользователя отправлялись в шаблон и происходил его рендеринг. Запустите приложение и попробуйте передать в строке запроса какой-нибудь HTML. Проверьте, что данные в шаблоне проходят предварительную обработку автоматически

Дополнительные материалы

  1. Безопасность приложений (Хоть и написано что в Rails, но подходит для всех)

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff